forked from qt-creator/qt-creator
ClangCodeModel: Highlight Q_PROPERTY declarations with clangd
We re-use the moc parser for this purpose. Change-Id: Ib0ef4f727d1f0b862a202a95a3ae9c551cb502a5 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
427
src/plugins/clangcodemodel/clangdqpropertyhighlighter.cpp
Normal file
427
src/plugins/clangcodemodel/clangdqpropertyhighlighter.cpp
Normal file
@@ -0,0 +1,427 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "clangdqpropertyhighlighter.h"
|
||||
|
||||
#include "moc/parser.h"
|
||||
#include "moc/preprocessor.h"
|
||||
#include "moc/utils.h"
|
||||
|
||||
#include <utils/textutils.h>
|
||||
|
||||
#include <QVersionNumber>
|
||||
|
||||
using namespace TextEditor;
|
||||
|
||||
namespace ClangCodeModel {
|
||||
namespace Internal {
|
||||
|
||||
class QPropertyHighlighter::Private
|
||||
{
|
||||
public:
|
||||
QByteArray lexemUntil(Token target);
|
||||
|
||||
void highlightProperty();
|
||||
|
||||
const QTextDocument *document;
|
||||
QString input;
|
||||
int position;
|
||||
Preprocessor pp;
|
||||
Parser parser;
|
||||
TextEditor::HighlightingResults results;
|
||||
|
||||
private:
|
||||
void highlightType();
|
||||
void highlightAttributes();
|
||||
void highlightRevision();
|
||||
bool until(Token target);
|
||||
void skipCxxAttributes();
|
||||
void addResult(TextStyle textStyle, int symbolOffset = 0);
|
||||
};
|
||||
|
||||
QPropertyHighlighter::QPropertyHighlighter(const QTextDocument *doc, const QString &input,
|
||||
int position)
|
||||
: d(new Private)
|
||||
{
|
||||
d->document = doc;
|
||||
d->input = input;
|
||||
d->position = position;
|
||||
|
||||
d->pp.macros["Q_MOC_RUN"];
|
||||
d->pp.macros["__cplusplus"];
|
||||
|
||||
// Don't stumble over GCC extensions
|
||||
Macro dummyVariadicFunctionMacro;
|
||||
dummyVariadicFunctionMacro.isFunction = true;
|
||||
dummyVariadicFunctionMacro.isVariadic = true;
|
||||
dummyVariadicFunctionMacro.arguments += Symbol(0, PP_IDENTIFIER, "__VA_ARGS__");
|
||||
d->pp.macros["__attribute__"] = dummyVariadicFunctionMacro;
|
||||
d->pp.macros["__declspec"] = dummyVariadicFunctionMacro;
|
||||
}
|
||||
|
||||
QPropertyHighlighter::~QPropertyHighlighter() { delete d; }
|
||||
|
||||
const HighlightingResults QPropertyHighlighter::highlight()
|
||||
{
|
||||
try {
|
||||
d->highlightProperty();
|
||||
return d->results;
|
||||
} catch (const MocParseException &) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void QPropertyHighlighter::Private::highlightProperty()
|
||||
{
|
||||
parser.symbols = pp.preprocessed({}, input.toUtf8());
|
||||
parser.next(Q_PROPERTY_TOKEN);
|
||||
parser.next(LPAREN);
|
||||
highlightType();
|
||||
parser.next();
|
||||
addResult(C_FIELD);
|
||||
highlightAttributes();
|
||||
}
|
||||
|
||||
void QPropertyHighlighter::Private::highlightType()
|
||||
{
|
||||
for (;;) {
|
||||
skipCxxAttributes();
|
||||
switch (parser.next()) {
|
||||
case SIGNED:
|
||||
case UNSIGNED:
|
||||
case CONST_TOKEN:
|
||||
case VOLATILE:
|
||||
addResult(C_KEYWORD);
|
||||
continue;
|
||||
case Q_MOC_COMPAT_TOKEN:
|
||||
case Q_INVOKABLE_TOKEN:
|
||||
case Q_SCRIPTABLE_TOKEN:
|
||||
case Q_SIGNALS_TOKEN:
|
||||
case Q_SLOTS_TOKEN:
|
||||
case Q_SIGNAL_TOKEN:
|
||||
case Q_SLOT_TOKEN:
|
||||
return;
|
||||
case NOTOKEN:
|
||||
return;
|
||||
default:
|
||||
parser.prev();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
skipCxxAttributes();
|
||||
parser.test(ENUM) || parser.test(CLASS) || parser.test(STRUCT);
|
||||
for(;;) {
|
||||
skipCxxAttributes();
|
||||
switch (parser.next()) {
|
||||
case IDENTIFIER:
|
||||
addResult(C_TYPE);
|
||||
break;
|
||||
case CHAR_TOKEN:
|
||||
case SHORT_TOKEN:
|
||||
case INT_TOKEN:
|
||||
case LONG_TOKEN:
|
||||
addResult(C_KEYWORD);
|
||||
// preserve '[unsigned] long long', 'short int', 'long int', 'long double'
|
||||
if (parser.test(LONG_TOKEN) || parser.test(INT_TOKEN) || parser.test(DOUBLE)) {
|
||||
parser.prev();
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case FLOAT_TOKEN:
|
||||
case DOUBLE:
|
||||
case VOID_TOKEN:
|
||||
case BOOL_TOKEN:
|
||||
case AUTO:
|
||||
addResult(C_KEYWORD);
|
||||
break;
|
||||
case NOTOKEN:
|
||||
return;
|
||||
default:
|
||||
parser.prev();
|
||||
break;
|
||||
}
|
||||
if (parser.test(LANGLE)) {
|
||||
if (results.size() < 2) {
|
||||
// '<' cannot start a type
|
||||
return;
|
||||
}
|
||||
until(RANGLE); // TODO: Highlight template parameter?
|
||||
}
|
||||
if (parser.test(SCOPE)) {
|
||||
addResult(C_PUNCTUATION);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (parser.test(CONST_TOKEN) || parser.test(VOLATILE) || parser.test(SIGNED)
|
||||
|| parser.test(UNSIGNED) || parser.test(STAR) || parser.test(AND)
|
||||
|| parser.test(ANDAND)) {
|
||||
TextStyle textStyle = parser.test(CONST_TOKEN) || parser.test(VOLATILE)
|
||||
? C_KEYWORD
|
||||
: parser.test(SIGNED) || parser.test(UNSIGNED)
|
||||
? C_TYPE
|
||||
: C_PUNCTUATION;
|
||||
addResult(textStyle);
|
||||
}
|
||||
}
|
||||
|
||||
void QPropertyHighlighter::Private::highlightAttributes()
|
||||
{
|
||||
auto checkIsFunction = [&](const QByteArray &def, const char *name) {
|
||||
if (def.endsWith(')')) {
|
||||
QByteArray msg = "Providing a function for ";
|
||||
msg += name;
|
||||
msg += " in a property declaration is not be supported in Qt 6.";
|
||||
parser.error(msg.constData());
|
||||
}
|
||||
};
|
||||
|
||||
while (parser.test(IDENTIFIER)) {
|
||||
const QByteArray l = parser.lexem();
|
||||
if (l[0] == 'C' && l == "CONSTANT") {
|
||||
addResult(C_KEYWORD);
|
||||
continue;
|
||||
} else if (l[0] == 'F' && l == "FINAL") {
|
||||
addResult(C_KEYWORD);
|
||||
continue;
|
||||
} else if (l[0] == 'N' && l == "NAME") {
|
||||
addResult(C_KEYWORD);
|
||||
parser.next(IDENTIFIER);
|
||||
addResult(C_FIELD);
|
||||
continue;
|
||||
} else if (l[0] == 'R' && l == "REQUIRED") {
|
||||
addResult(C_KEYWORD);
|
||||
continue;
|
||||
} else if (l[0] == 'R' && l == "REVISION" && parser.test(LPAREN)) {
|
||||
parser.prev();
|
||||
highlightRevision();
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray v, v2;
|
||||
if (parser.test(LPAREN)) {
|
||||
v = lexemUntil(RPAREN);
|
||||
v = v.mid(1, v.length() - 2); // removes the '(' and ')'
|
||||
} else if (parser.test(INTEGER_LITERAL)) {
|
||||
v = parser.lexem();
|
||||
if (l != "REVISION")
|
||||
parser.error(1);
|
||||
} else {
|
||||
addResult(C_KEYWORD);
|
||||
parser.next(IDENTIFIER);
|
||||
addResult(C_FUNCTION);
|
||||
v = parser.lexem();
|
||||
if (parser.test(LPAREN))
|
||||
v2 = lexemUntil(RPAREN);
|
||||
else if (v != "true" && v != "false")
|
||||
v2 = "()";
|
||||
}
|
||||
switch (l[0]) {
|
||||
case 'M':
|
||||
if (l != "MEMBER")
|
||||
parser.error(2);
|
||||
break;
|
||||
case 'R':
|
||||
if (l == "READ")
|
||||
break;
|
||||
if (l == "RESET")
|
||||
break;
|
||||
if (l == "REVISION") {
|
||||
parser.prev();
|
||||
highlightRevision();
|
||||
break;
|
||||
}
|
||||
parser.error(2);
|
||||
break;
|
||||
case 'S':
|
||||
if (l == "SCRIPTABLE") {
|
||||
checkIsFunction(v + v2, "SCRIPTABLE");
|
||||
} else if (l == "STORED") {
|
||||
checkIsFunction(v + v2, "STORED");
|
||||
} else
|
||||
parser.error(2);
|
||||
break;
|
||||
case 'W': if (l != "WRITE") parser.error(2);
|
||||
break;
|
||||
case 'B': if (l != "BINDABLE") parser.error(2);
|
||||
break;
|
||||
case 'D': if (l != "DESIGNABLE") parser.error(2);
|
||||
checkIsFunction(v + v2, "DESIGNABLE");
|
||||
break;
|
||||
case 'N': if (l != "NOTIFY") parser.error(2);
|
||||
break;
|
||||
case 'U': if (l != "USER") parser.error(2);
|
||||
checkIsFunction(v + v2, "USER");
|
||||
break;
|
||||
default:
|
||||
parser.error(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QPropertyHighlighter::Private::highlightRevision()
|
||||
{
|
||||
addResult(C_KEYWORD);
|
||||
QByteArray revisionString;
|
||||
const bool hasParen = parser.test(LPAREN);
|
||||
if (hasParen) {
|
||||
revisionString = lexemUntil(RPAREN);
|
||||
revisionString.remove(0, 1);
|
||||
revisionString.chop(1);
|
||||
revisionString.replace(',', '.');
|
||||
} else {
|
||||
parser.next(INTEGER_LITERAL);
|
||||
revisionString = parser.lexem();
|
||||
}
|
||||
QVersionNumber version = QVersionNumber::fromString(QString::fromUtf8(revisionString));
|
||||
if (version.isNull() || version.segmentCount() > 2)
|
||||
parser.error("Invalid revision");
|
||||
}
|
||||
|
||||
QByteArray QPropertyHighlighter::Private::lexemUntil(Token target)
|
||||
{
|
||||
int from = parser.index;
|
||||
until(target);
|
||||
QByteArray s;
|
||||
while (from <= parser.index) {
|
||||
QByteArray n = parser.symbols.at(from++-1).lexem();
|
||||
if (s.size() && n.size()) {
|
||||
char prev = s.at(s.size()-1);
|
||||
char next = n.at(0);
|
||||
if ((is_ident_char(prev) && is_ident_char(next))
|
||||
|| (prev == '<' && next == ':')
|
||||
|| (prev == '>' && next == '>'))
|
||||
s += ' ';
|
||||
}
|
||||
s += n;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
bool QPropertyHighlighter::Private::until(Token target)
|
||||
{
|
||||
int braceCount = 0;
|
||||
int brackCount = 0;
|
||||
int parenCount = 0;
|
||||
int angleCount = 0;
|
||||
if (parser.index) {
|
||||
switch(parser.symbols.at(parser.index-1).token) {
|
||||
case LBRACE: ++braceCount; break;
|
||||
case LBRACK: ++brackCount; break;
|
||||
case LPAREN: ++parenCount; break;
|
||||
case LANGLE: ++angleCount; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
//when searching commas within the default argument, we should take care of template depth (anglecount)
|
||||
// unfortunatelly, we do not have enough semantic information to know if '<' is the operator< or
|
||||
// the beginning of a template type. so we just use heuristics.
|
||||
int possible = -1;
|
||||
|
||||
while (parser.index < parser.symbols.size()) {
|
||||
Token t = parser.symbols.at(parser.index++).token;
|
||||
switch (t) {
|
||||
case LBRACE: ++braceCount; break;
|
||||
case RBRACE: --braceCount; break;
|
||||
case LBRACK: ++brackCount; break;
|
||||
case RBRACK: --brackCount; break;
|
||||
case LPAREN: ++parenCount; break;
|
||||
case RPAREN: --parenCount; break;
|
||||
case LANGLE:
|
||||
if (parenCount == 0 && braceCount == 0)
|
||||
++angleCount;
|
||||
break;
|
||||
case RANGLE:
|
||||
if (parenCount == 0 && braceCount == 0)
|
||||
--angleCount;
|
||||
break;
|
||||
case GTGT:
|
||||
if (parenCount == 0 && braceCount == 0) {
|
||||
angleCount -= 2;
|
||||
t = RANGLE;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
if (t == target
|
||||
&& braceCount <= 0
|
||||
&& brackCount <= 0
|
||||
&& parenCount <= 0
|
||||
&& (target != RANGLE || angleCount <= 0)) {
|
||||
if (target != COMMA || angleCount <= 0)
|
||||
return true;
|
||||
possible = parser.index;
|
||||
}
|
||||
|
||||
if (target == COMMA && t == EQ && possible != -1) {
|
||||
parser.index = possible;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (braceCount < 0 || brackCount < 0 || parenCount < 0
|
||||
|| (target == RANGLE && angleCount < 0)) {
|
||||
--parser.index;
|
||||
break;
|
||||
}
|
||||
|
||||
if (braceCount <= 0 && t == SEMIC) {
|
||||
// Abort on semicolon. Allow recovering bad template parsing (QTBUG-31218)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (target == COMMA && angleCount != 0 && possible != -1) {
|
||||
parser.index = possible;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void QPropertyHighlighter::Private::skipCxxAttributes()
|
||||
{
|
||||
int rewind = parser.index;
|
||||
if (parser.test(LBRACK) && parser.test(LBRACK) && until(RBRACK) && parser.test(RBRACK))
|
||||
return;
|
||||
parser.index = rewind;
|
||||
}
|
||||
|
||||
void QPropertyHighlighter::Private::addResult(TextStyle textStyle, int symbolOffset)
|
||||
{
|
||||
const Symbol &s = parser.symbol_lookup(symbolOffset);
|
||||
int line, column;
|
||||
Utils::Text::convertPosition(document, position + s.from, &line, &column);
|
||||
if (line > 0 && column > 0) {
|
||||
TextStyles styles;
|
||||
styles.mainStyle = textStyle;
|
||||
results << HighlightingResult(line, column, s.len, styles);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace ClangCodeModel
|
||||
Reference in New Issue
Block a user