qmlls: Add autofix capabilities
Allows qmlls to make use of qmllint's existing autofix infrastructure and translates them into code actions. Change-Id: I855e131f621b16a8bb72997aa8e2b36833ac4dab Reviewed-by: Fawzi Mohamed <fawzi.mohamed@qt.io>
This commit is contained in:
parent
a7c5f86419
commit
5fc74d216f
|
@ -73,6 +73,17 @@ public:
|
|||
return num;
|
||||
}
|
||||
|
||||
QList<Diagnostic> diagnostics(const QByteArray &uri) const
|
||||
{
|
||||
QList<Diagnostic> result;
|
||||
for (const auto ¶ms : m_received) {
|
||||
if (params.uri == uri)
|
||||
result << params.diagnostics;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void clear() { m_received.clear(); }
|
||||
|
||||
private:
|
||||
|
@ -159,6 +170,75 @@ void tst_Qmlls::didOpenTextDocument()
|
|||
|
||||
QTRY_VERIFY_WITH_TIMEOUT(m_diagnosticsHandler.numDiagnostics(uri) != 0, 10000);
|
||||
QVERIFY(m_diagnosticsHandler.contains(uri, 3, 4, 3, 10));
|
||||
|
||||
auto diagnostics = m_diagnosticsHandler.diagnostics(uri);
|
||||
|
||||
CodeActionParams codeActionParams;
|
||||
codeActionParams.textDocument = { textDocument.uri };
|
||||
codeActionParams.context.diagnostics = diagnostics;
|
||||
codeActionParams.range.start = Position { 0, 0 };
|
||||
codeActionParams.range.end =
|
||||
Position { static_cast<int>(textDocument.text.split(u'\n').size()), 0 };
|
||||
|
||||
bool success = false;
|
||||
m_protocol.requestCodeAction(
|
||||
codeActionParams,
|
||||
[&](const std::variant<QList<std::variant<Command, CodeAction>>, std::nullptr_t>
|
||||
&response) {
|
||||
using ListType = QList<std::variant<Command, CodeAction>>;
|
||||
|
||||
QVERIFY(std::holds_alternative<ListType>(response));
|
||||
|
||||
auto list = std::get<ListType>(response);
|
||||
|
||||
QList<QPair<QString, QString>> expectedData = {
|
||||
{ QLatin1StringView("Did you mean \"width\"?"), QLatin1StringView("width") },
|
||||
{ QLatin1StringView("Did you mean \"z\"?"), QLatin1StringView("z") }
|
||||
};
|
||||
QCOMPARE(list.size(), expectedData.size());
|
||||
|
||||
for (const auto &entry : list) {
|
||||
QVERIFY(std::holds_alternative<CodeAction>(entry));
|
||||
CodeAction action = std::get<CodeAction>(entry);
|
||||
|
||||
QString title = QString::fromUtf8(action.title);
|
||||
QVERIFY(action.kind.has_value());
|
||||
QCOMPARE(QString::fromUtf8(action.kind.value()),
|
||||
QLatin1StringView("refactor.rewrite"));
|
||||
QVERIFY(action.edit.has_value());
|
||||
WorkspaceEdit edit = action.edit.value();
|
||||
|
||||
QVERIFY(edit.documentChanges.has_value());
|
||||
auto docChangeVariant = edit.documentChanges.value();
|
||||
QVERIFY(std::holds_alternative<QList<TextDocumentEdit>>(docChangeVariant));
|
||||
auto documentChanges = std::get<QList<TextDocumentEdit>>(docChangeVariant);
|
||||
QCOMPARE(documentChanges.size(), 1);
|
||||
|
||||
TextDocumentEdit textDocEdit = documentChanges.first();
|
||||
QCOMPARE(textDocEdit.textDocument.uri, textDocument.uri);
|
||||
|
||||
QCOMPARE(textDocEdit.edits.size(), 1);
|
||||
auto editVariant = textDocEdit.edits.first();
|
||||
QVERIFY(std::holds_alternative<TextEdit>(editVariant));
|
||||
|
||||
TextEdit textEdit = std::get<TextEdit>(editVariant);
|
||||
QString newText = QString::fromUtf8(textEdit.newText);
|
||||
QPair<QString, QString> data = { title, newText };
|
||||
|
||||
qsizetype dataIndex = expectedData.indexOf(data);
|
||||
QVERIFY2(dataIndex != -1,
|
||||
qPrintable(QLatin1String("{\"%1\",\"%2\"}").arg(title, newText)));
|
||||
// Make sure every expected entry only occurs once
|
||||
expectedData.remove(dataIndex);
|
||||
}
|
||||
|
||||
success = true;
|
||||
},
|
||||
[](const QLspSpecification::ResponseError &error) {
|
||||
qWarning() << "CodeAction Error:" << QString::fromUtf8(error.message);
|
||||
});
|
||||
|
||||
QTRY_VERIFY_WITH_TIMEOUT(success, 10000);
|
||||
m_diagnosticsHandler.clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,64 @@ static DiagnosticSeverity severityFromMsgType(QtMsgType t)
|
|||
return DiagnosticSeverity::Error;
|
||||
}
|
||||
|
||||
static void codeActionHandler(
|
||||
const QByteArray &, const CodeActionParams ¶ms,
|
||||
LSPPartialResponse<std::variant<QList<std::variant<Command, CodeAction>>, std::nullptr_t>,
|
||||
QList<std::variant<Command, CodeAction>>> &&response)
|
||||
{
|
||||
QList<std::variant<Command, CodeAction>> responseData;
|
||||
|
||||
for (const Diagnostic &diagnostic : params.context.diagnostics) {
|
||||
if (!diagnostic.data.has_value())
|
||||
continue;
|
||||
|
||||
QJsonArray suggestions = diagnostic.data.value().toArray();
|
||||
|
||||
QList<TextDocumentEdit> edits;
|
||||
QString message;
|
||||
for (const QJsonValue &suggestion : suggestions) {
|
||||
QString replacement = suggestion[u"replacement"].toString();
|
||||
message += suggestion[u"message"].toString() + u"\n";
|
||||
|
||||
TextEdit textEdit;
|
||||
textEdit.range = { Position { suggestion[u"lspBeginLine"].toInt(),
|
||||
suggestion[u"lspBeginCharacter"].toInt() },
|
||||
Position { suggestion[u"lspEndLine"].toInt(),
|
||||
suggestion[u"lspEndCharacter"].toInt() } };
|
||||
textEdit.newText = replacement.toUtf8();
|
||||
|
||||
TextDocumentEdit textDocEdit;
|
||||
textDocEdit.textDocument = { params.textDocument };
|
||||
textDocEdit.edits.append(textEdit);
|
||||
|
||||
edits.append(textDocEdit);
|
||||
}
|
||||
message.chop(1);
|
||||
WorkspaceEdit edit;
|
||||
edit.documentChanges = edits;
|
||||
|
||||
CodeAction action;
|
||||
action.kind = u"refactor.rewrite"_qs.toUtf8();
|
||||
action.edit = edit;
|
||||
action.title = message.toUtf8();
|
||||
|
||||
responseData.append(action);
|
||||
}
|
||||
|
||||
response.sendResponse(responseData);
|
||||
}
|
||||
|
||||
void QmlLintSuggestions::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
|
||||
{
|
||||
protocol->registerCodeActionRequestHandler(&codeActionHandler);
|
||||
}
|
||||
|
||||
void QmlLintSuggestions::setupCapabilities(const QLspSpecification::InitializeParams &,
|
||||
QLspSpecification::InitializeResult &serverInfo)
|
||||
{
|
||||
serverInfo.capabilities.codeActionProvider = true;
|
||||
}
|
||||
|
||||
QmlLintSuggestions::QmlLintSuggestions(QLanguageServer *server, QmlLsp::QQmlCodeModel *codeModel)
|
||||
: m_server(server), m_codeModel(codeModel)
|
||||
{
|
||||
|
@ -168,6 +226,29 @@ void QmlLintSuggestions::diagnose(const QByteArray &uri)
|
|||
addLength(range.end, message[u"charOffset"].toInt(), message[u"length"].toInt());
|
||||
diagnostic.message = message[u"message"].toString().toUtf8();
|
||||
diagnostic.source = QByteArray("qmllint");
|
||||
|
||||
auto suggestions = message[u"suggestions"].toArray();
|
||||
if (!suggestions.isEmpty()) {
|
||||
// We need to interject the information about where the fix suggestions end
|
||||
// here since we don't have access to the textDocument to calculate it later.
|
||||
QJsonArray fixedSuggestions;
|
||||
for (const QJsonValue &value : suggestions) {
|
||||
QJsonObject object = value.toObject();
|
||||
int line = message[u"line"].toInt(1) - 1;
|
||||
int column = message[u"column"].toInt(1) - 1;
|
||||
object[u"lspBeginLine"] = line;
|
||||
object[u"lspBeginCharacter"] = column;
|
||||
|
||||
Position end = { line, column };
|
||||
|
||||
addLength(end, object[u"charOffset"].toInt(), object[u"length"].toInt());
|
||||
object[u"lspEndLine"] = end.line;
|
||||
object[u"lspEndCharacter"] = end.character;
|
||||
|
||||
fixedSuggestions << object;
|
||||
}
|
||||
diagnostic.data = fixedSuggestions;
|
||||
}
|
||||
return diagnostic;
|
||||
};
|
||||
doc.iterateErrors(
|
||||
|
|
|
@ -41,13 +41,18 @@ struct LastLintUpdate
|
|||
std::optional<QDateTime> invalidUpdatesSince;
|
||||
};
|
||||
|
||||
class QmlLintSuggestions : public QObject
|
||||
class QmlLintSuggestions : public QLanguageServerModule
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QmlLintSuggestions(QLanguageServer *server, QmlLsp::QQmlCodeModel *codeModel);
|
||||
|
||||
QString name() const override { return QLatin1StringView("QmlLint Suggestions"); }
|
||||
public slots:
|
||||
void diagnose(const QByteArray &uri);
|
||||
void registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol) override;
|
||||
void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
|
||||
QLspSpecification::InitializeResult &) override;
|
||||
|
||||
private:
|
||||
QMutex m_mutex;
|
||||
|
|
|
@ -69,6 +69,7 @@ QQmlLanguageServer::QQmlLanguageServer(std::function<void(const QByteArray &)> s
|
|||
{
|
||||
m_server.addServerModule(this);
|
||||
m_server.addServerModule(&m_textSynchronization);
|
||||
m_server.addServerModule(&m_lint);
|
||||
m_server.finishSetup();
|
||||
qCWarning(lspServerLog) << "Did Setup";
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue