qmlls: add support for renaming symbols
Add a qmlls module for renaming symbols, and start implementing the rename operation in qmllsutils. ToDos for later commits: * sanitize the new name obtained from the user and warn if invalid. Also warn if current symbol cannot be renamed (because it is defined in another module or in a cpp file for example). * support renaming Qml Components (including changing the qml file name defining the component) (requires implementing finding usages of Qml Components first) Task-number: QTBUG-114788 Fixes: QTBUG-114948 Change-Id: I045949bd3a83c32fcb50ffb718c5a63e1f002c0c Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
parent
7d01e7ef87
commit
de6da96030
|
@ -22,6 +22,7 @@ qt_internal_add_module(QmlLSPrivate
|
|||
qqmllsutils_p.h qqmllsutils.cpp
|
||||
qqmlfindusagessupport_p.h qqmlfindusagessupport.cpp
|
||||
qqmlgotodefinitionsupport.cpp qqmlgotodefinitionsupport_p.h
|
||||
qqmlrenamesymbolsupport_p.h qqmlrenamesymbolsupport.cpp
|
||||
|
||||
PUBLIC_LIBRARIES
|
||||
Qt::LanguageServerPrivate
|
||||
|
|
|
@ -68,7 +68,8 @@ void QQmlFindUsagesSupport::process(QQmlFindUsagesSupport::RequestPointerArgumen
|
|||
cacheEntry = codeCache.insert(usage.filename, file->code());
|
||||
}
|
||||
|
||||
location.range = QQmlLSUtils::qmlLocationToLspLocation(cacheEntry.value(), usage.location);
|
||||
location.range =
|
||||
QQmlLSUtils::qmlLocationToLspLocation(cacheEntry.value(), usage.sourceLocation);
|
||||
|
||||
results.append(location);
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ void QmlGoToDefinitionSupport::process(RequestPointerArgument request)
|
|||
return;
|
||||
}
|
||||
const QString qmlCode = fileOfBasePtr->code();
|
||||
l.range = QQmlLSUtils::qmlLocationToLspLocation(qmlCode, location->location);
|
||||
l.range = QQmlLSUtils::qmlLocationToLspLocation(qmlCode, location->sourceLocation);
|
||||
|
||||
results.append(l);
|
||||
}
|
||||
|
|
|
@ -64,7 +64,8 @@ QQmlLanguageServer::QQmlLanguageServer(std::function<void(const QByteArray &)> s
|
|||
m_navigationSupport(&m_codeModel),
|
||||
m_definitionSupport(&m_codeModel),
|
||||
m_referencesSupport(&m_codeModel),
|
||||
m_documentFormatting(&m_codeModel)
|
||||
m_documentFormatting(&m_codeModel),
|
||||
m_renameSupport(&m_codeModel)
|
||||
{
|
||||
m_server.addServerModule(this);
|
||||
m_server.addServerModule(&m_textSynchronization);
|
||||
|
@ -75,6 +76,7 @@ QQmlLanguageServer::QQmlLanguageServer(std::function<void(const QByteArray &)> s
|
|||
m_server.addServerModule(&m_definitionSupport);
|
||||
m_server.addServerModule(&m_referencesSupport);
|
||||
m_server.addServerModule(&m_documentFormatting);
|
||||
m_server.addServerModule(&m_renameSupport);
|
||||
m_server.finishSetup();
|
||||
qCWarning(lspServerLog) << "Did Setup";
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "qqmlgototypedefinitionsupport_p.h"
|
||||
#include "qqmlformatting_p.h"
|
||||
#include "qqmlgotodefinitionsupport_p.h"
|
||||
#include "qqmlrenamesymbolsupport_p.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
|
@ -67,6 +68,7 @@ private:
|
|||
QmlGoToDefinitionSupport m_definitionSupport;
|
||||
QQmlFindUsagesSupport m_referencesSupport;
|
||||
QQmlDocumentFormatting m_documentFormatting;
|
||||
QQmlRenameSymbolSupport m_renameSupport;
|
||||
int m_returnValue = 1;
|
||||
};
|
||||
|
||||
|
|
|
@ -675,8 +675,8 @@ QList<QQmlLSUtilsLocation> QQmlLSUtils::findUsagesOf(DomItem item)
|
|||
qCDebug(QQmlLSUtilsLog) << "Found following usages:";
|
||||
for (auto r : result) {
|
||||
qCDebug(QQmlLSUtilsLog)
|
||||
<< r.filename << " @ " << r.location.startLine << ":" << r.location.startColumn
|
||||
<< " with length " << r.location.length;
|
||||
<< r.filename << " @ " << r.sourceLocation.startLine << ":"
|
||||
<< r.sourceLocation.startColumn << " with length " << r.sourceLocation.length;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -771,7 +771,11 @@ QQmlJSScope::ConstPtr QQmlLSUtils::resolveExpressionType(QQmlJS::Dom::DomItem it
|
|||
"QQmlLSUtils::findDefinitionOf",
|
||||
"JS definition does not actually define the JS identifer. "
|
||||
"It should be empty.");
|
||||
auto scope = definitionOfItem.semanticScope().value()->JSIdentifier(name)->scope.toStrongRef();;
|
||||
auto scope = definitionOfItem.semanticScope()
|
||||
.value()
|
||||
->JSIdentifier(name)
|
||||
->scope.toStrongRef();
|
||||
;
|
||||
return scope;
|
||||
}
|
||||
|
||||
|
@ -937,7 +941,7 @@ findMethodDefinitionOf(DomItem file, QQmlJS::SourceLocation location, const QStr
|
|||
|
||||
if (auto it = regions.constFind(u"identifier"_s); it != regions.constEnd()) {
|
||||
QQmlLSUtilsLocation result;
|
||||
result.location = *it;
|
||||
result.sourceLocation = *it;
|
||||
result.filename = method.canonicalFilePath();
|
||||
return result;
|
||||
}
|
||||
|
@ -955,7 +959,7 @@ findPropertyDefinitionOf(DomItem file, QQmlJS::SourceLocation propertyDefinition
|
|||
return {};
|
||||
|
||||
QQmlLSUtilsLocation result;
|
||||
result.location = FileLocations::treeOf(propertyDefinition)->info().fullRegion;
|
||||
result.sourceLocation = FileLocations::treeOf(propertyDefinition)->info().fullRegion;
|
||||
result.filename = propertyDefinition.canonicalFilePath();
|
||||
return result;
|
||||
}
|
||||
|
@ -1026,7 +1030,7 @@ std::optional<QQmlLSUtilsLocation> QQmlLSUtils::findDefinitionOf(DomItem item)
|
|||
QQmlJSScope::ConstPtr fromId = resolver.value()->scopeForId(name, referrerScope.value());
|
||||
if (fromId) {
|
||||
QQmlLSUtilsLocation result;
|
||||
result.location = fromId->sourceLocation();
|
||||
result.sourceLocation = fromId->sourceLocation();
|
||||
result.filename = item.canonicalFilePath();
|
||||
return result;
|
||||
}
|
||||
|
@ -1041,4 +1045,165 @@ std::optional<QQmlLSUtilsLocation> QQmlLSUtils::findDefinitionOf(DomItem item)
|
|||
Q_UNREACHABLE_RETURN(std::nullopt);
|
||||
}
|
||||
|
||||
std::optional<QQmlLSUtilsErrorMessage> QQmlLSUtils::checkNameForRename(DomItem item,
|
||||
const QString &newName)
|
||||
{
|
||||
// TODO
|
||||
Q_UNUSED(item);
|
||||
Q_UNUSED(newName);
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::optional<QString> oldNameFrom(DomItem item)
|
||||
{
|
||||
switch (item.internalKind()) {
|
||||
case DomType::ScriptIdentifierExpression: {
|
||||
return item.field(Fields::identifier).value().toString();
|
||||
}
|
||||
case DomType::ScriptVariableDeclarationEntry: {
|
||||
return item.field(Fields::identifier).value().toString();
|
||||
}
|
||||
case DomType::PropertyDefinition:
|
||||
case DomType::Binding:
|
||||
case DomType::MethodInfo: {
|
||||
return item.field(Fields::name).value().toString();
|
||||
}
|
||||
default:
|
||||
qCDebug(QQmlLSUtilsLog) << item.internalKindStr()
|
||||
<< "was not implemented for QQmlLSUtils::renameUsagesOf";
|
||||
return std::nullopt;
|
||||
}
|
||||
Q_UNREACHABLE_RETURN(std::nullopt);
|
||||
}
|
||||
|
||||
static std::optional<QString> newNameFrom(const QString &dirtyNewName,
|
||||
QQmlLSUtilsIdentifierType alternative)
|
||||
{
|
||||
// When renaming signal/property changed handlers and property changed signals:
|
||||
// Get the actual corresponding signal name (for signal handlers) or property name (for
|
||||
// property changed signal + handlers) that will be used for the renaming.
|
||||
switch (alternative) {
|
||||
case SignalHandlerIdentifier: {
|
||||
return QQmlSignalNames::handlerNameToSignalName(dirtyNewName);
|
||||
}
|
||||
case PropertyChangedHandlerIdentifier: {
|
||||
return QQmlSignalNames::changedHandlerNameToPropertyName(dirtyNewName);
|
||||
}
|
||||
case PropertyChangedSignalIdentifier: {
|
||||
return QQmlSignalNames::changedSignalNameToPropertyName(dirtyNewName);
|
||||
}
|
||||
case SignalIdentifier:
|
||||
case PropertyIdentifier:
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
Q_UNREACHABLE_RETURN(std::nullopt);
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
\brief Rename the appearance of item to newName.
|
||||
|
||||
Special cases:
|
||||
\list
|
||||
\li Renaming a property changed signal or property changed handler does the same as renaming
|
||||
the underlying property, except that newName gets
|
||||
\list
|
||||
\li its "on"-prefix and "Changed"-suffix chopped of if item is a property changed handlers
|
||||
\li its "Changed"-suffix chopped of if item is a property changed signals
|
||||
\endlist
|
||||
\li Renaming a signal handler does the same as renaming a signal, but the "on"-prefix in newName
|
||||
is chopped of.
|
||||
|
||||
All of the chopping operations are done using the static helpers from QQmlSignalNames.
|
||||
\endlist
|
||||
*/
|
||||
QList<QQmlLSUtilsEdit> QQmlLSUtils::renameUsagesOf(DomItem item, const QString &dirtyNewName)
|
||||
{
|
||||
QList<QQmlLSUtilsEdit> results;
|
||||
const QList<QQmlLSUtilsLocation> locations = findUsagesOf(item);
|
||||
if (locations.isEmpty())
|
||||
return results;
|
||||
|
||||
auto oldName = oldNameFrom(item);
|
||||
if (!oldName)
|
||||
return results;
|
||||
|
||||
QQmlJSScope::ConstPtr targetType =
|
||||
QQmlLSUtils::resolveExpressionType(item, QQmlLSUtilsResolveOptions::JustOwner);
|
||||
|
||||
QString newName;
|
||||
if (const auto resolved = resolveNameInQmlScope(*oldName, targetType)) {
|
||||
newName = newNameFrom(dirtyNewName, resolved->type).value_or(dirtyNewName);
|
||||
oldName = resolved->name;
|
||||
} else {
|
||||
newName = dirtyNewName;
|
||||
}
|
||||
|
||||
const qsizetype oldNameLength = oldName->length();
|
||||
const qsizetype oldHandlerNameLength =
|
||||
QQmlSignalNames::signalNameToHandlerName(*oldName).length();
|
||||
const qsizetype oldChangedSignalNameLength =
|
||||
QQmlSignalNames::propertyNameToChangedSignalName(*oldName).length();
|
||||
const qsizetype oldChangedHandlerNameLength =
|
||||
QQmlSignalNames::propertyNameToChangedHandlerName(*oldName).length();
|
||||
|
||||
const QString newHandlerName = QQmlSignalNames::signalNameToHandlerName(newName);
|
||||
const QString newChangedSignalName = QQmlSignalNames::propertyNameToChangedSignalName(newName);
|
||||
const QString newChangedHandlerName =
|
||||
QQmlSignalNames::propertyNameToChangedHandlerName(newName);
|
||||
|
||||
// set the new name at the found usages, but add "on"-prefix and "Changed"-suffix if needed
|
||||
for (const auto &location : locations) {
|
||||
const qsizetype currentLength = location.sourceLocation.length;
|
||||
QQmlLSUtilsEdit edit;
|
||||
edit.location = location;
|
||||
if (oldNameLength == currentLength) {
|
||||
// normal case, nothing to do
|
||||
edit.replacement = newName;
|
||||
|
||||
} else if (oldHandlerNameLength == currentLength) {
|
||||
// signal handler location
|
||||
edit.replacement = newHandlerName;
|
||||
|
||||
} else if (oldChangedSignalNameLength == currentLength) {
|
||||
// property changed signal location
|
||||
edit.replacement = newChangedSignalName;
|
||||
|
||||
} else if (oldChangedHandlerNameLength == currentLength) {
|
||||
// property changed handler location
|
||||
edit.replacement = newChangedHandlerName;
|
||||
|
||||
} else {
|
||||
qCDebug(QQmlLSUtilsLog) << "Found usage with wrong identifier length, ignoring...";
|
||||
continue;
|
||||
}
|
||||
results.append(edit);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
QQmlLSUtilsLocation QQmlLSUtilsLocation::from(const QString &fileName, const QString &code,
|
||||
quint32 startLine, quint32 startCharacter,
|
||||
quint32 length)
|
||||
{
|
||||
quint32 offset = QQmlLSUtils::textOffsetFrom(code, startLine - 1, startCharacter - 1);
|
||||
|
||||
QQmlLSUtilsLocation location{
|
||||
fileName, QQmlJS::SourceLocation{ offset, length, startLine, startCharacter }
|
||||
};
|
||||
return location;
|
||||
}
|
||||
|
||||
QQmlLSUtilsEdit QQmlLSUtilsEdit::from(const QString &fileName, const QString &code,
|
||||
quint32 startLine, quint32 startCharacter, quint32 length,
|
||||
const QString &newName)
|
||||
{
|
||||
QQmlLSUtilsEdit rename;
|
||||
rename.location = QQmlLSUtilsLocation::from(fileName, code, startLine, startCharacter, length);
|
||||
rename.replacement = newName;
|
||||
return rename;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
@ -44,20 +44,49 @@ enum QQmlLSUtilsIdentifierType : char {
|
|||
SignalHandlerIdentifier,
|
||||
};
|
||||
|
||||
struct QQmlLSUtilsErrorMessage
|
||||
{
|
||||
int code;
|
||||
QString message;
|
||||
};
|
||||
|
||||
struct QQmlLSUtilsLocation
|
||||
{
|
||||
QString filename;
|
||||
QQmlJS::SourceLocation location;
|
||||
QQmlJS::SourceLocation sourceLocation;
|
||||
|
||||
static QQmlLSUtilsLocation from(const QString &fileName, const QString &code, quint32 startLine,
|
||||
quint32 startCharacter, quint32 length);
|
||||
|
||||
friend bool operator<(const QQmlLSUtilsLocation &a, const QQmlLSUtilsLocation &b)
|
||||
{
|
||||
return std::make_tuple(a.filename, a.location.begin(), a.location.end())
|
||||
< std::make_tuple(b.filename, b.location.begin(), b.location.end());
|
||||
return std::make_tuple(a.filename, a.sourceLocation.begin(), a.sourceLocation.end())
|
||||
< std::make_tuple(b.filename, b.sourceLocation.begin(), b.sourceLocation.end());
|
||||
}
|
||||
friend bool operator==(const QQmlLSUtilsLocation &a, const QQmlLSUtilsLocation &b)
|
||||
{
|
||||
return std::make_tuple(a.filename, a.location.begin(), a.location.end())
|
||||
== std::make_tuple(b.filename, b.location.begin(), b.location.end());
|
||||
return std::make_tuple(a.filename, a.sourceLocation.begin(), a.sourceLocation.end())
|
||||
== std::make_tuple(b.filename, b.sourceLocation.begin(), b.sourceLocation.end());
|
||||
}
|
||||
};
|
||||
|
||||
struct QQmlLSUtilsEdit
|
||||
{
|
||||
QQmlLSUtilsLocation location;
|
||||
QString replacement;
|
||||
|
||||
static QQmlLSUtilsEdit from(const QString &fileName, const QString &code, quint32 startLine,
|
||||
quint32 startCharacter, quint32 length, const QString &newName);
|
||||
|
||||
friend bool operator<(const QQmlLSUtilsEdit &a, const QQmlLSUtilsEdit &b)
|
||||
{
|
||||
return std::make_tuple(a.location, a.replacement)
|
||||
< std::make_tuple(b.location, b.replacement);
|
||||
}
|
||||
friend bool operator==(const QQmlLSUtilsEdit &a, const QQmlLSUtilsEdit &b)
|
||||
{
|
||||
return std::make_tuple(a.location, a.replacement)
|
||||
== std::make_tuple(b.location, b.replacement);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -90,6 +119,10 @@ public:
|
|||
static std::optional<QQmlLSUtilsLocation> findDefinitionOf(QQmlJS::Dom::DomItem item);
|
||||
static QList<QQmlLSUtilsLocation> findUsagesOf(QQmlJS::Dom::DomItem item);
|
||||
|
||||
static std::optional<QQmlLSUtilsErrorMessage> checkNameForRename(QQmlJS::Dom::DomItem item,
|
||||
const QString &newName);
|
||||
static QList<QQmlLSUtilsEdit> renameUsagesOf(QQmlJS::Dom::DomItem item, const QString &newName);
|
||||
|
||||
static QQmlJSScope::ConstPtr resolveExpressionType(QQmlJS::Dom::DomItem item,
|
||||
QQmlLSUtilsResolveOptions);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include "qqmlrenamesymbolsupport_p.h"
|
||||
#include <utility>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
QQmlRenameSymbolSupport::QQmlRenameSymbolSupport(QmlLsp::QQmlCodeModel *model) : BaseT(model) { }
|
||||
|
||||
QString QQmlRenameSymbolSupport::name() const
|
||||
{
|
||||
return u"QmlRenameSymbolSupport"_s;
|
||||
}
|
||||
|
||||
void QQmlRenameSymbolSupport::setupCapabilities(
|
||||
const QLspSpecification::InitializeParams &,
|
||||
QLspSpecification::InitializeResult &serverCapabilities)
|
||||
{
|
||||
// use a bool for now. Alternatively, if the client supports "prepareSupport", one could
|
||||
// use a RenameOptions here. See following page for more information about prepareSupport:
|
||||
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_prepareRename
|
||||
serverCapabilities.capabilities.renameProvider = true;
|
||||
}
|
||||
|
||||
void QQmlRenameSymbolSupport::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
|
||||
{
|
||||
protocol->registerRenameRequestHandler(getRequestHandler());
|
||||
}
|
||||
|
||||
void QQmlRenameSymbolSupport::process(QQmlRenameSymbolSupport::RequestPointerArgument request)
|
||||
{
|
||||
QLspSpecification::WorkspaceEdit result;
|
||||
std::optional<QQmlLSUtilsErrorMessage> error;
|
||||
|
||||
QScopeGuard onExit([&error, &request, &result]() {
|
||||
if (error.has_value()) {
|
||||
request->m_response.sendErrorResponse(error->code, error->message.toUtf8());
|
||||
} else {
|
||||
request->m_response.sendResponse(result);
|
||||
}
|
||||
});
|
||||
|
||||
auto itemsFound = itemsForRequest(request);
|
||||
if (!itemsFound) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: sanity checks: check for errors
|
||||
|
||||
QList<QLspSpecification::TextDocumentEdit> editsByFileForResult;
|
||||
// The QLspSpecification::WorkspaceEdit requires the changes to be grouped by files, so
|
||||
// collect them into editsByFileUris.
|
||||
QMap<QUrl, QList<QLspSpecification::TextEdit>> editsByFileUris;
|
||||
auto renames = QQmlLSUtils::renameUsagesOf(itemsFound->front().domItem,
|
||||
QString::fromUtf8(request->m_parameters.newName));
|
||||
|
||||
QQmlJS::Dom::DomItem files =
|
||||
itemsFound->front().domItem.top().field(QQmlJS::Dom::Fields::qmlFileWithPath);
|
||||
|
||||
QHash<QString, QString> codeCache;
|
||||
|
||||
for (const auto &rename : renames) {
|
||||
QLspSpecification::TextEdit edit;
|
||||
|
||||
const QUrl uri = QUrl::fromLocalFile(rename.location.filename);
|
||||
|
||||
auto cacheEntry = codeCache.find(rename.location.filename);
|
||||
if (cacheEntry == codeCache.end()) {
|
||||
auto file = files.key(rename.location.filename)
|
||||
.field(QQmlJS::Dom::Fields::currentItem)
|
||||
.ownerAs<QQmlJS::Dom::QmlFile>();
|
||||
if (!file) {
|
||||
qDebug() << "File" << rename.location.filename
|
||||
<< "not found in DOM! Available files are" << files.keys();
|
||||
continue;
|
||||
}
|
||||
cacheEntry = codeCache.insert(rename.location.filename, file->code());
|
||||
}
|
||||
|
||||
edit.range = QQmlLSUtils::qmlLocationToLspLocation(cacheEntry.value(),
|
||||
rename.location.sourceLocation);
|
||||
edit.newText = rename.replacement.toUtf8();
|
||||
|
||||
editsByFileUris[uri].append(edit);
|
||||
}
|
||||
|
||||
for (auto it = editsByFileUris.keyValueBegin(); it != editsByFileUris.keyValueEnd(); ++it) {
|
||||
QLspSpecification::TextDocumentEdit editsForCurrentFile;
|
||||
editsForCurrentFile.textDocument.uri = it->first.toEncoded();
|
||||
|
||||
// TODO: do we need to take care of the optional versioning in
|
||||
// editsForCurrentFile.textDocument.version? see
|
||||
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#optionalVersionedTextDocumentIdentifier
|
||||
// for more details
|
||||
|
||||
for (const auto &x : std::as_const(it->second)) {
|
||||
editsForCurrentFile.edits.append(x);
|
||||
}
|
||||
editsByFileForResult.append(editsForCurrentFile);
|
||||
}
|
||||
|
||||
result.documentChanges = editsByFileForResult;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#ifndef QQMLRENAMESYMBOLSUPPORT_P_H
|
||||
#define QQMLRENAMESYMBOLSUPPORT_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include "qlanguageserver_p.h"
|
||||
#include "qqmlcodemodel_p.h"
|
||||
#include "qqmlbasemodule_p.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
struct RenameRequest : public BaseRequest<QLspSpecification::RenameParams,
|
||||
QLspSpecification::Responses::RenameResponseType>
|
||||
{
|
||||
};
|
||||
|
||||
class QQmlRenameSymbolSupport : public QQmlBaseModule<RenameRequest>
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QQmlRenameSymbolSupport(QmlLsp::QQmlCodeModel *codeModel);
|
||||
|
||||
QString name() const override;
|
||||
void registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol) override;
|
||||
void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
|
||||
QLspSpecification::InitializeResult &) override;
|
||||
|
||||
void process(RequestPointerArgument request) override;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QQMLRENAMESYMBOLSUPPORT_P_H
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
#include "tst_qmlls_modules.h"
|
||||
#include "QtQmlLS/private/qqmllsutils_p.h"
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
|
||||
// Check if QTest already has a QTEST_CHECKED macro
|
||||
#ifndef QTEST_CHECKED
|
||||
|
@ -621,6 +624,24 @@ void tst_qmlls_modules::goToDefinition()
|
|||
QTRY_VERIFY_WITH_TIMEOUT(*didFinish, 30000);
|
||||
}
|
||||
|
||||
// startLine and startCharacter start at 1, not 0
|
||||
static QLspSpecification::Range rangeFrom(const QString &code, quint32 startLine,
|
||||
quint32 startCharacter, quint32 length)
|
||||
{
|
||||
QLspSpecification::Range range;
|
||||
|
||||
// the LSP works with lines and characters starting at 0
|
||||
range.start.line = startLine - 1;
|
||||
range.start.character = startCharacter - 1;
|
||||
|
||||
quint32 startOffset = QQmlLSUtils::textOffsetFrom(code, startLine - 1, startCharacter - 1);
|
||||
auto end = QQmlLSUtils::textRowAndColumnFrom(code, startOffset + length);
|
||||
range.end.line = end.line;
|
||||
range.end.character = end.character;
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
// startLine and startCharacter start at 1, not 0
|
||||
static QLspSpecification::Location locationFrom(const QByteArray fileName, const QString &code,
|
||||
quint32 startLine, quint32 startCharacter,
|
||||
|
@ -628,15 +649,7 @@ static QLspSpecification::Location locationFrom(const QByteArray fileName, const
|
|||
{
|
||||
QLspSpecification::Location location;
|
||||
location.uri = QQmlLSUtils::qmlUrlToLspUri(fileName);
|
||||
// the LSP works with lines and characters starting at 0
|
||||
location.range.start.line = startLine - 1;
|
||||
location.range.start.character = startCharacter - 1;
|
||||
|
||||
quint32 startOffset = QQmlLSUtils::textOffsetFrom(code, startLine - 1, startCharacter - 1);
|
||||
auto end = QQmlLSUtils::textRowAndColumnFrom(code, startOffset + length);
|
||||
location.range.end.line = end.line;
|
||||
location.range.end.character = end.character;
|
||||
|
||||
location.range = rangeFrom(code, startLine, startCharacter, length);
|
||||
return location;
|
||||
}
|
||||
|
||||
|
@ -857,6 +870,150 @@ void tst_qmlls_modules::documentFormatting()
|
|||
QTRY_VERIFY_WITH_TIMEOUT(*didFinish, 50000);
|
||||
}
|
||||
|
||||
void tst_qmlls_modules::renameUsages_data()
|
||||
{
|
||||
QTest::addColumn<QByteArray>("uri");
|
||||
QTest::addColumn<int>("line");
|
||||
QTest::addColumn<int>("character");
|
||||
QTest::addColumn<QString>("newName");
|
||||
QTest::addColumn<QLspSpecification::WorkspaceEdit>("expectedEdit");
|
||||
|
||||
QByteArray jsIdentifierUsagesUri = testFileUrl("findUsages/jsIdentifierUsages.qml").toEncoded();
|
||||
|
||||
QString jsIdentifierUsagesContent;
|
||||
{
|
||||
QFile file(testFile("findUsages/jsIdentifierUsages.qml").toUtf8());
|
||||
QVERIFY(file.open(QIODeviceBase::ReadOnly));
|
||||
jsIdentifierUsagesContent = QString::fromUtf8(file.readAll());
|
||||
}
|
||||
|
||||
// TODO: create workspace edit for the tests
|
||||
QLspSpecification::WorkspaceEdit sumRenames{
|
||||
std::nullopt, // TODO
|
||||
QList<TextDocumentEdit>{
|
||||
TextDocumentEdit{
|
||||
OptionalVersionedTextDocumentIdentifier{ { jsIdentifierUsagesUri } },
|
||||
{
|
||||
TextEdit{
|
||||
rangeFrom(jsIdentifierUsagesContent, 8, 13, strlen("sum")),
|
||||
"specialSum" },
|
||||
TextEdit{
|
||||
rangeFrom(jsIdentifierUsagesContent, 10, 13, strlen("sum")),
|
||||
"specialSum" },
|
||||
TextEdit{
|
||||
rangeFrom(jsIdentifierUsagesContent, 10, 19, strlen("sum")),
|
||||
"specialSum" },
|
||||
} },
|
||||
}
|
||||
};
|
||||
|
||||
// line and character start at 1!
|
||||
QTest::addRow("sumUsagesFromUsage")
|
||||
<< jsIdentifierUsagesUri << 10 << 14 << u"specialSum"_s << sumRenames;
|
||||
QTest::addRow("sumUsagesFromUsage2")
|
||||
<< jsIdentifierUsagesUri << 10 << 20 << u"specialSum"_s << sumRenames;
|
||||
QTest::addRow("sumUsagesFromDefinition")
|
||||
<< jsIdentifierUsagesUri << 8 << 14 << u"specialSum"_s << sumRenames;
|
||||
}
|
||||
|
||||
void tst_qmlls_modules::renameUsages()
|
||||
{
|
||||
QFETCH(QByteArray, uri);
|
||||
// line and character start at 1!
|
||||
QFETCH(int, line);
|
||||
QFETCH(int, character);
|
||||
QFETCH(QString, newName);
|
||||
QFETCH(QLspSpecification::WorkspaceEdit, expectedEdit);
|
||||
|
||||
QVERIFY(uri.startsWith("file://"_ba));
|
||||
|
||||
RenameParams params;
|
||||
params.position.line = line - 1;
|
||||
params.position.character = character - 1;
|
||||
params.textDocument.uri = uri;
|
||||
params.newName = newName.toUtf8();
|
||||
|
||||
std::shared_ptr<bool> didFinish = std::make_shared<bool>(false);
|
||||
auto clean = [didFinish]() { *didFinish = true; };
|
||||
m_protocol.requestRename(
|
||||
params,
|
||||
[&](auto res) {
|
||||
QScopeGuard cleanup(clean);
|
||||
auto *result = std::get_if<QLspSpecification::WorkspaceEdit>(&res);
|
||||
|
||||
QVERIFY(result);
|
||||
QCOMPARE(result->changes.has_value(), expectedEdit.changes.has_value());
|
||||
QCOMPARE(result->changeAnnotations.has_value(),
|
||||
expectedEdit.changeAnnotations.has_value());
|
||||
QCOMPARE(result->documentChanges.has_value(),
|
||||
expectedEdit.documentChanges.has_value());
|
||||
|
||||
std::visit(
|
||||
[](auto &&documentChanges, auto &&expectedDocumentChanges) {
|
||||
QCOMPARE(documentChanges.size(), expectedDocumentChanges.size());
|
||||
using U = std::decay_t<decltype(documentChanges)>;
|
||||
using V = std::decay_t<decltype(expectedDocumentChanges)>;
|
||||
|
||||
if constexpr (std::conjunction_v<
|
||||
std::is_same<U, V>,
|
||||
std::is_same<U, QList<TextDocumentEdit>>>) {
|
||||
for (qsizetype i = 0; i < expectedDocumentChanges.size(); ++i) {
|
||||
QCOMPARE(documentChanges[i].textDocument.uri,
|
||||
expectedDocumentChanges[i].textDocument.uri);
|
||||
QVERIFY(documentChanges[i].textDocument.uri.startsWith(
|
||||
"file://"));
|
||||
QCOMPARE(documentChanges[i].textDocument.version,
|
||||
expectedDocumentChanges[i].textDocument.version);
|
||||
QCOMPARE(documentChanges[i].edits.size(),
|
||||
expectedDocumentChanges[i].edits.size());
|
||||
|
||||
for (qsizetype j = 0; j < documentChanges[i].edits.size();
|
||||
++j) {
|
||||
std::visit(
|
||||
[](auto &&textEdit, auto &&expectedTextEdit) {
|
||||
using U = std::decay_t<decltype(textEdit)>;
|
||||
using V = std::decay_t<
|
||||
decltype(expectedTextEdit)>;
|
||||
|
||||
if constexpr (std::conjunction_v<
|
||||
std::is_same<U, V>,
|
||||
std::is_same<U,
|
||||
TextEdit>>) {
|
||||
QCOMPARE(textEdit.range.start.line,
|
||||
expectedTextEdit.range.start.line);
|
||||
QCOMPARE(textEdit.range.start.character,
|
||||
expectedTextEdit.range.start
|
||||
.character);
|
||||
QCOMPARE(textEdit.range.end.line,
|
||||
expectedTextEdit.range.end.line);
|
||||
QCOMPARE(textEdit.range.end.character,
|
||||
expectedTextEdit.range.end
|
||||
.character);
|
||||
QCOMPARE(textEdit.newText,
|
||||
expectedTextEdit.newText);
|
||||
} else {
|
||||
QFAIL("Comparison not implemented");
|
||||
}
|
||||
},
|
||||
documentChanges[i].edits[j],
|
||||
expectedDocumentChanges[i].edits[j]);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
QFAIL("Comparison not implemented");
|
||||
}
|
||||
},
|
||||
result->documentChanges.value(), expectedEdit.documentChanges.value());
|
||||
},
|
||||
[clean](const ResponseError &err) {
|
||||
QScopeGuard cleanup(clean);
|
||||
ProtocolBase::defaultResponseErrorHandler(err);
|
||||
QVERIFY2(false, "error computing the completion");
|
||||
});
|
||||
QTRY_VERIFY_WITH_TIMEOUT(*didFinish, 3000);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_qmlls_modules)
|
||||
|
||||
#include <tst_qmlls_modules.moc>
|
||||
|
|
|
@ -50,6 +50,8 @@ private slots:
|
|||
void findUsages();
|
||||
void documentFormatting_data();
|
||||
void documentFormatting();
|
||||
void renameUsages_data();
|
||||
void renameUsages();
|
||||
|
||||
private:
|
||||
QProcess m_server;
|
||||
|
|
|
@ -139,4 +139,17 @@ Item {
|
|||
______42Changed()
|
||||
_123aChanged()
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: myHelloHandler
|
||||
function f() {
|
||||
tapped()
|
||||
}
|
||||
}
|
||||
|
||||
function anotherF() {
|
||||
helloPropertyChanged()
|
||||
}
|
||||
onHelloPropertyChanged: myHelloHandler
|
||||
Type {}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ const static int outOfOne = 1;
|
|||
const static int outOfTwo = 2;
|
||||
|
||||
// enable/disable additional debug output
|
||||
constexpr static bool enable_debug_output = true;
|
||||
constexpr static bool enable_debug_output = false;
|
||||
|
||||
static QString printSet(const QSet<QString> &s)
|
||||
{
|
||||
|
@ -681,20 +681,6 @@ void tst_qmlls_utils::findBaseObject()
|
|||
.toLatin1());
|
||||
}
|
||||
|
||||
using QQmlJS::SourceLocation;
|
||||
|
||||
static QQmlLSUtilsLocation sourceLocationFrom(const QString &fileName, const QString &code,
|
||||
quint32 startLine, quint32 startCharacter,
|
||||
quint32 length)
|
||||
{
|
||||
quint32 offset = QQmlLSUtils::textOffsetFrom(code, startLine - 1, startCharacter - 1);
|
||||
|
||||
QQmlLSUtilsLocation location{
|
||||
fileName, QQmlJS::SourceLocation{ offset, length, startLine, startCharacter }
|
||||
};
|
||||
return location;
|
||||
}
|
||||
|
||||
void tst_qmlls_utils::findUsages_data()
|
||||
{
|
||||
QTest::addColumn<QString>("filePath");
|
||||
|
@ -711,141 +697,153 @@ void tst_qmlls_utils::findUsages_data()
|
|||
}
|
||||
|
||||
QList<QQmlLSUtilsLocation> sumUsages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 8, 13, strlen("sum")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 10, 13, strlen("sum")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 10, 19, strlen("sum")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 8, 13, strlen("sum")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 10, 13, strlen("sum")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 10, 19, strlen("sum")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> iUsages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 9, 17, strlen("i")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 9, 24, strlen("i")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 9, 32, strlen("i")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 9, 36, strlen("i")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 10, 25, strlen("i")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 17, strlen("i")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 24, strlen("i")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 32, strlen("i")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 9, 36, strlen("i")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 10, 25, strlen("i")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> helloPropertyUsages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 17, 18, strlen("helloProperty")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 24, 13, strlen("helloProperty")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 24, 29, strlen("helloProperty")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 65, 60, strlen("helloProperty")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 17, 18, strlen("helloProperty")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 24, 13, strlen("helloProperty")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 24, 29, strlen("helloProperty")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 65, 60, strlen("helloProperty")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 151, 9,
|
||||
strlen("helloPropertyChanged")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 153, 5,
|
||||
strlen("onHelloPropertyChanged")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> subItemHelloPropertyUsages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 32, 20, strlen("helloProperty")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 34, 25, strlen("helloProperty")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 32, 20, strlen("helloProperty")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 34, 25, strlen("helloProperty")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> ICHelloPropertyUsages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 37, 22, strlen("helloProperty")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 39, 20, strlen("helloProperty")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 37, 22, strlen("helloProperty")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 39, 20, strlen("helloProperty")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> p2Usages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 18, 18, strlen("p2")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 24, 55, strlen("p2")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 32, 36, strlen("p2")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 39, 36, strlen("p2")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 66, 31, strlen("p2")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 67, 37, strlen("p2")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 68, 43, strlen("p2")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 69, 49, strlen("p2")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 18, 18, strlen("p2")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 24, 55, strlen("p2")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 32, 36, strlen("p2")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 39, 36, strlen("p2")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 66, 31, strlen("p2")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 67, 37, strlen("p2")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 68, 43, strlen("p2")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 69, 49, strlen("p2")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> nestedUsages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 62, 13, strlen("myNested")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 65, 17, strlen("myNested")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 66, 17, strlen("myNested")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 67, 17, strlen("myNested")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 68, 17, strlen("myNested")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 69, 17, strlen("myNested")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 62, 13, strlen("myNested")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 65, 17, strlen("myNested")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 66, 17, strlen("myNested")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 67, 17, strlen("myNested")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 68, 17, strlen("myNested")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 69, 17, strlen("myNested")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> rootIdUsages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 81, 9, strlen("rootId")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 84, 20, strlen("rootId")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 108, 13, strlen("rootId")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 114, 13, strlen("rootId")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 81, 9, strlen("rootId")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 84, 20, strlen("rootId")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 108, 13, strlen("rootId")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 114, 13, strlen("rootId")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> nestedComponent3Usages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 47, 35, strlen("inner")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 65, 32, strlen("inner")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 68, 32, strlen("inner")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 69, 32, strlen("inner")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 47, 35, strlen("inner")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 65, 32, strlen("inner")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 68, 32, strlen("inner")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 69, 32, strlen("inner")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> nestedComponent3P2Usages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 68, 38, strlen("p2")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 53, 22, strlen("p2")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 68, 38, strlen("p2")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 53, 22, strlen("p2")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> recursiveUsages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 72, 14, strlen("recursive")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 74, 24, strlen("recursive")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 74, 34, strlen("recursive")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 74, 51, strlen("recursive")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 74, 68, strlen("recursive")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 76, 20, strlen("recursive")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 79, 34, strlen("recursive")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 84, 27, strlen("recursive")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 72, 14, strlen("recursive")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 74, 24, strlen("recursive")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 74, 34, strlen("recursive")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 74, 51, strlen("recursive")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 74, 68, strlen("recursive")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 76, 20, strlen("recursive")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 79, 34, strlen("recursive")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 84, 27, strlen("recursive")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> helloSignalUsages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 88, 12, strlen("helloSignal")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 91, 9, strlen("helloSignal")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 93, 13, strlen("helloSignal")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 97, 17, strlen("helloSignal")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 101, 9, strlen("helloSignal")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 119, 5, strlen("onHelloSignal")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 88, 12, strlen("helloSignal")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 91, 9, strlen("helloSignal")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 93, 13, strlen("helloSignal")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 97, 17, strlen("helloSignal")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 101, 9, strlen("helloSignal")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 119, 5, strlen("onHelloSignal")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> widthChangedUsages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 103, 13, strlen("widthChanged")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 107, 17, strlen("widthChanged")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 108, 20, strlen("widthChanged")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 114, 20, strlen("widthChanged")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 103, 13, strlen("widthChanged")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 107, 17, strlen("widthChanged")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 108, 20, strlen("widthChanged")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 114, 20, strlen("widthChanged")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> helloPropertyBindingUsages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 121, 18, strlen("helloPropertyBinding")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 122, 5, strlen("helloPropertyBinding")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 121, 18,
|
||||
strlen("helloPropertyBinding")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 122, 5,
|
||||
strlen("helloPropertyBinding")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> myHelloHandlerUsages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 118, 14, strlen("myHelloHandler")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 119, 20, strlen("myHelloHandler")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 125, 29, strlen("myHelloHandler")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 126, 24, strlen("myHelloHandler")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 134, 17, strlen("myHelloHandler")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 135, 24, strlen("myHelloHandler")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 136, 21, strlen("myHelloHandler")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 118, 14, strlen("myHelloHandler")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 119, 20, strlen("myHelloHandler")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 125, 29, strlen("myHelloHandler")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 126, 24, strlen("myHelloHandler")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 134, 17, strlen("myHelloHandler")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 135, 24, strlen("myHelloHandler")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 136, 21, strlen("myHelloHandler")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 144, 19, strlen("myHelloHandler")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 153, 29, strlen("myHelloHandler")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> checkHandlersUsages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 124, 18, strlen("checkHandlers")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 125, 5, strlen("onCheckHandlersChanged")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 128, 9, strlen("checkHandlersChanged")),
|
||||
QList<QQmlLSUtilsLocation> checkHandlersUsages {
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 124, 18, strlen("checkHandlers")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 125, 5,
|
||||
strlen("onCheckHandlersChanged")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 128, 9,
|
||||
strlen("checkHandlersChanged")),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsLocation> checkCppHandlersUsages{
|
||||
sourceLocationFrom(testFileName, testFileContent, 126, 5, strlen("onChildrenChanged")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 129, 9, strlen("childrenChanged")),
|
||||
QList<QQmlLSUtilsLocation> checkCppHandlersUsages {
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 126, 5,
|
||||
strlen("onChildrenChanged")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 129, 9, strlen("childrenChanged")),
|
||||
};
|
||||
QList<QQmlLSUtilsLocation> checkHandlersUsages2{
|
||||
sourceLocationFrom(testFileName, testFileContent, 131, 18, strlen("_")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 134, 5, strlen("on_Changed")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 138, 9, strlen("_Changed")),
|
||||
QList<QQmlLSUtilsLocation> checkHandlersUsages2 {
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 131, 18, strlen("_")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 134, 5, strlen("on_Changed")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 138, 9, strlen("_Changed")),
|
||||
};
|
||||
QList<QQmlLSUtilsLocation> checkHandlersUsages3{
|
||||
sourceLocationFrom(testFileName, testFileContent, 132, 18, strlen("______42")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 135, 5, strlen("on______42Changed")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 139, 9, strlen("______42Changed")),
|
||||
QList<QQmlLSUtilsLocation> checkHandlersUsages3 {
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 132, 18, strlen("______42")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 135, 5,
|
||||
strlen("on______42Changed")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 139, 9, strlen("______42Changed")),
|
||||
};
|
||||
QList<QQmlLSUtilsLocation> checkHandlersUsages4{
|
||||
sourceLocationFrom(testFileName, testFileContent, 133, 18, strlen("_123a")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 136, 5, strlen("on_123AChanged")),
|
||||
sourceLocationFrom(testFileName, testFileContent, 140, 9, strlen("_123AChanged")),
|
||||
QList<QQmlLSUtilsLocation> checkHandlersUsages4 {
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 133, 18, strlen("_123a")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 136, 5, strlen("on_123AChanged")),
|
||||
QQmlLSUtilsLocation::from(testFileName, testFileContent, 140, 9, strlen("_123aChanged")),
|
||||
};
|
||||
|
||||
std::sort(sumUsages.begin(), sumUsages.end());
|
||||
|
@ -979,21 +977,207 @@ void tst_qmlls_utils::findUsages()
|
|||
if (usages != expectedUsages) {
|
||||
qDebug() << "Got:\n";
|
||||
for (auto &x : usages) {
|
||||
qDebug() << x.filename << "(" << x.location.startLine << ", "
|
||||
<< x.location.startColumn << "), " << x.location.offset << "+"
|
||||
<< x.location.length;
|
||||
qDebug() << x.filename << "(" << x.sourceLocation.startLine << ", "
|
||||
<< x.sourceLocation.startColumn << "), " << x.sourceLocation.offset << "+"
|
||||
<< x.sourceLocation.length;
|
||||
}
|
||||
qDebug() << "But expected: \n";
|
||||
for (auto &x : expectedUsages) {
|
||||
qDebug() << x.filename << "(" << x.location.startLine << ", "
|
||||
<< x.location.startColumn << "), " << x.location.offset << "+"
|
||||
<< x.location.length;
|
||||
qDebug() << x.filename << "(" << x.sourceLocation.startLine << ", "
|
||||
<< x.sourceLocation.startColumn << "), " << x.sourceLocation.offset << "+"
|
||||
<< x.sourceLocation.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
QCOMPARE(usages, expectedUsages);
|
||||
}
|
||||
|
||||
void tst_qmlls_utils::renameUsages_data()
|
||||
{
|
||||
QTest::addColumn<QString>("filePath");
|
||||
QTest::addColumn<int>("line");
|
||||
QTest::addColumn<int>("character");
|
||||
QTest::addColumn<QString>("newName");
|
||||
QTest::addColumn<QList<QQmlLSUtilsEdit>>("expectedRenames");
|
||||
QTest::addColumn<std::optional<QQmlLSUtilsErrorMessage>>("expectedError");
|
||||
|
||||
const QString testFileName = testFile(u"JSUsages.qml"_s);
|
||||
QString testFileContent;
|
||||
{
|
||||
QFile file(testFileName);
|
||||
QVERIFY(file.open(QIODeviceBase::ReadOnly));
|
||||
testFileContent = QString::fromUtf8(file.readAll());
|
||||
}
|
||||
|
||||
const std::optional<QQmlLSUtilsErrorMessage> noError;
|
||||
|
||||
QList<QQmlLSUtilsEdit> methodFRename{
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 72, 14, strlen("recursive"),
|
||||
u"newNameNewMe"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 74, 24, strlen("recursive"),
|
||||
u"newNameNewMe"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 74, 34, strlen("recursive"),
|
||||
u"newNameNewMe"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 74, 51, strlen("recursive"),
|
||||
u"newNameNewMe"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 74, 68, strlen("recursive"),
|
||||
u"newNameNewMe"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 76, 20, strlen("recursive"),
|
||||
u"newNameNewMe"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 79, 34, strlen("recursive"),
|
||||
u"newNameNewMe"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 84, 27, strlen("recursive"),
|
||||
u"newNameNewMe"_s),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsEdit> JSIdentifierSumRename{
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 8, 13, strlen("sum"),
|
||||
u"sumsumsum123"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 10, 13, strlen("sum"),
|
||||
u"sumsumsum123"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 10, 19, strlen("sum"),
|
||||
u"sumsumsum123"_s),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsEdit> qmlSignalRename{
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 88, 12, strlen("helloSignal"),
|
||||
u"finalSignal"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 91, 9, strlen("helloSignal"),
|
||||
u"finalSignal"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 93, 13, strlen("helloSignal"),
|
||||
u"finalSignal"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 97, 17, strlen("helloSignal"),
|
||||
u"finalSignal"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 101, 9, strlen("helloSignal"),
|
||||
u"finalSignal"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 119, 5, strlen("onHelloSignal"),
|
||||
u"onFinalSignal"_s),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsEdit> helloPropertyRename{
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 17, 18, strlen("helloProperty"),
|
||||
u"freshPropertyName"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 24, 13, strlen("helloProperty"),
|
||||
u"freshPropertyName"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 24, 29, strlen("helloProperty"),
|
||||
u"freshPropertyName"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 65, 60, strlen("helloProperty"),
|
||||
u"freshPropertyName"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 151, 9, strlen("helloPropertyChanged"),
|
||||
u"freshPropertyNameChanged"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 153, 5,
|
||||
strlen("onHelloPropertyChanged"), u"onFreshPropertyNameChanged"_s),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsEdit> nestedComponentRename{
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 42, 15, strlen("NestedComponent"),
|
||||
u"SuperInlineComponent"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 61, 5, strlen("NestedComponent"),
|
||||
u"SuperInlineComponent"_s),
|
||||
};
|
||||
|
||||
QList<QQmlLSUtilsEdit> myNestedIdRename{
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 62, 13, strlen("myNested"),
|
||||
u"freshNewIdForMyNested"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 65, 17, strlen("myNested"),
|
||||
u"freshNewIdForMyNested"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 66, 17, strlen("myNested"),
|
||||
u"freshNewIdForMyNested"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 67, 17, strlen("myNested"),
|
||||
u"freshNewIdForMyNested"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 68, 17, strlen("myNested"),
|
||||
u"freshNewIdForMyNested"_s),
|
||||
QQmlLSUtilsEdit::from(testFileName, testFileContent, 69, 17, strlen("myNested"),
|
||||
u"freshNewIdForMyNested"_s),
|
||||
};
|
||||
|
||||
std::sort(methodFRename.begin(), methodFRename.end());
|
||||
std::sort(JSIdentifierSumRename.begin(), JSIdentifierSumRename.end());
|
||||
std::sort(qmlSignalRename.begin(), qmlSignalRename.end());
|
||||
std::sort(helloPropertyRename.begin(), helloPropertyRename.end());
|
||||
std::sort(helloPropertyRename.begin(), helloPropertyRename.end());
|
||||
std::sort(nestedComponentRename.begin(), nestedComponentRename.end());
|
||||
std::sort(myNestedIdRename.begin(), myNestedIdRename.end());
|
||||
|
||||
QTest::addRow("renameMethod") << testFileName << 72 << 19 << u"newNameNewMe"_s << methodFRename
|
||||
<< noError;
|
||||
QTest::addRow("renameJSIdentifier")
|
||||
<< testFileName << 10 << 19 << u"sumsumsum123"_s << JSIdentifierSumRename << noError;
|
||||
QTest::addRow("renameQmlSignal")
|
||||
<< testFileName << 93 << 19 << u"finalSignal"_s << qmlSignalRename << noError;
|
||||
QTest::addRow("renameQmlSignalHandler")
|
||||
<< testFileName << 119 << 10 << u"onFinalSignal"_s << qmlSignalRename << noError;
|
||||
|
||||
QTest::addRow("renameQmlProperty")
|
||||
<< testFileName << 17 << 20 << u"freshPropertyName"_s << helloPropertyRename << noError;
|
||||
QTest::addRow("renameQmlPropertyChanged")
|
||||
<< testFileName << 151 << 18 << u"freshPropertyNameChanged"_s << helloPropertyRename
|
||||
<< noError;
|
||||
QTest::addRow("renameQmlPropertyChangedHandler")
|
||||
<< testFileName << 153 << 22 << u"onFreshPropertyNameChanged"_s << helloPropertyRename
|
||||
<< noError;
|
||||
|
||||
QTest::addRow("renameQmlObjectId") << testFileName << 65 << 21 << u"freshNewIdForMyNested"_s
|
||||
<< myNestedIdRename << noError;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void tst_qmlls_utils::renameUsages()
|
||||
{
|
||||
// findAndRenameUsages() already tests if all usages will be renamed
|
||||
// now test that the new name is correctly passed
|
||||
QFETCH(QString, filePath);
|
||||
QFETCH(int, line);
|
||||
QFETCH(int, character);
|
||||
QFETCH(QString, newName);
|
||||
QFETCH(QList<QQmlLSUtilsEdit>, expectedRenames);
|
||||
QFETCH(std::optional<QQmlLSUtilsErrorMessage>, expectedError);
|
||||
|
||||
QVERIFY(std::is_sorted(expectedRenames.begin(), expectedRenames.end()));
|
||||
|
||||
QQmlJS::Dom::DomCreationOptions options;
|
||||
options.setFlag(QQmlJS::Dom::DomCreationOption::WithSemanticAnalysis);
|
||||
options.setFlag(QQmlJS::Dom::DomCreationOption::WithScriptExpressions);
|
||||
|
||||
auto [env, file] = createEnvironmentAndLoadFile(filePath, options);
|
||||
|
||||
auto locations = QQmlLSUtils::itemsFromTextLocation(
|
||||
file.field(QQmlJS::Dom::Fields::currentItem), line - 1, character - 1);
|
||||
|
||||
if constexpr (enable_debug_output) {
|
||||
if (locations.size() > 1) {
|
||||
for (auto &x : locations)
|
||||
qDebug() << x.domItem.toString();
|
||||
}
|
||||
}
|
||||
QCOMPARE(locations.size(), 1);
|
||||
|
||||
auto edits = QQmlLSUtils::renameUsagesOf(locations.front().domItem, newName);
|
||||
|
||||
if constexpr (enable_debug_output) {
|
||||
if (edits != expectedRenames) {
|
||||
qDebug() << "Got:\n";
|
||||
for (auto &x : edits) {
|
||||
qDebug() << x.replacement << x.location.filename << "("
|
||||
<< x.location.sourceLocation.startLine << ", "
|
||||
<< x.location.sourceLocation.startColumn << "), "
|
||||
<< x.location.sourceLocation.offset << "+"
|
||||
<< x.location.sourceLocation.length;
|
||||
}
|
||||
qDebug() << "But expected: \n";
|
||||
for (auto &x : expectedRenames) {
|
||||
qDebug() << x.replacement << x.location.filename << "("
|
||||
<< x.location.sourceLocation.startLine << ", "
|
||||
<< x.location.sourceLocation.startColumn << "), "
|
||||
<< x.location.sourceLocation.offset << "+"
|
||||
<< x.location.sourceLocation.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
QCOMPARE(edits, expectedRenames);
|
||||
}
|
||||
|
||||
void tst_qmlls_utils::findDefinitionFromLocation_data()
|
||||
{
|
||||
QTest::addColumn<QString>("filePath");
|
||||
|
@ -1135,9 +1319,9 @@ void tst_qmlls_utils::findDefinitionFromLocation()
|
|||
|
||||
QCOMPARE(definition->filename, expectedFilePath);
|
||||
|
||||
QCOMPARE(definition->location.startLine, quint32(expectedLine));
|
||||
QCOMPARE(definition->location.startColumn, quint32(expectedCharacter));
|
||||
QCOMPARE(definition->location.length, quint32(expectedLength));
|
||||
QCOMPARE(definition->sourceLocation.startLine, quint32(expectedLine));
|
||||
QCOMPARE(definition->sourceLocation.startColumn, quint32(expectedCharacter));
|
||||
QCOMPARE(definition->sourceLocation.length, quint32(expectedLength));
|
||||
}
|
||||
|
||||
void tst_qmlls_utils::resolveExpressionType_data()
|
||||
|
|
|
@ -49,6 +49,9 @@ private slots:
|
|||
void findUsages();
|
||||
void findUsages_data();
|
||||
|
||||
void renameUsages();
|
||||
void renameUsages_data();
|
||||
|
||||
void resolveExpressionType();
|
||||
void resolveExpressionType_data();
|
||||
|
||||
|
|
Loading…
Reference in New Issue