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:
Sami Shalayel 2023-06-29 11:56:26 +02:00
parent 7d01e7ef87
commit de6da96030
14 changed files with 847 additions and 133 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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";
}

View File

@ -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;
};

View File

@ -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

View File

@ -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);
};

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -50,6 +50,8 @@ private slots:
void findUsages();
void documentFormatting_data();
void documentFormatting();
void renameUsages_data();
void renameUsages();
private:
QProcess m_server;

View File

@ -139,4 +139,17 @@ Item {
______42Changed()
_123aChanged()
}
TapHandler {
onTapped: myHelloHandler
function f() {
tapped()
}
}
function anotherF() {
helloPropertyChanged()
}
onHelloPropertyChanged: myHelloHandler
Type {}
}

View File

@ -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()

View File

@ -49,6 +49,9 @@ private slots:
void findUsages();
void findUsages_data();
void renameUsages();
void renameUsages_data();
void resolveExpressionType();
void resolveExpressionType_data();