qmllint: Move a lot of warning logic to qmlcompiler

Moves the majority of qmllint warning logic to qmlcompiler, making them available to all tools using the library.
The end goal is to get rid off the additional AST visitor in qmllint altogether (We can't quite yet until we have ported over type interference).
This also prepares qmlcompiler to move to a two pass approach which isn't fully implemented in here yet due to the size of the change.

Pick-to: 6.2
Change-Id: Id2e108340d26a75085ce6ed97d56dec03ea3a12d
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Maximilian Goldstein 2021-06-07 14:25:01 +02:00
parent 919e16acae
commit 3c95bf5b6d
7 changed files with 628 additions and 639 deletions

View File

@ -32,6 +32,9 @@
#include <QtCore/qfileinfo.h>
#include <QtCore/qdir.h>
#include <QtCore/qqueue.h>
#include <QtCore/qscopedvaluerollback.h>
#include <QtQml/private/qv4codegen_p.h>
#include <algorithm>
@ -65,17 +68,46 @@ inline QString getScopeName(const QQmlJSScope::ConstPtr &scope, QQmlJSScope::Sco
return scope->baseTypeName();
}
QQmlJSImportVisitor::QQmlJSImportVisitor(
QQmlJSImporter *importer, const QString &implicitImportDirectory,
const QStringList &qmltypesFiles, const QString &fileName, const QString &code, bool silent)
: m_implicitImportDirectory(implicitImportDirectory)
, m_qmltypesFiles(qmltypesFiles)
, m_currentScope(QQmlJSScope::create(QQmlJSScope::JSFunctionScope))
, m_importer(importer)
, m_logger(fileName, code, silent)
QQmlJSImportVisitor::QQmlJSImportVisitor(QQmlJSImporter *importer,
const QString &implicitImportDirectory,
const QStringList &qmltypesFiles, const QString &fileName,
const QString &code, bool silent)
: m_implicitImportDirectory(implicitImportDirectory),
m_code(code),
m_filePath(fileName),
m_rootId(u"<id>"_qs),
m_qmltypesFiles(qmltypesFiles),
m_currentScope(QQmlJSScope::create(QQmlJSScope::JSFunctionScope)),
m_importer(importer),
m_logger(fileName, code, silent)
{
m_globalScope = m_currentScope;
m_currentScope->setIsComposite(true);
m_currentScope->setInternalName(u"global"_qs);
QLatin1String jsGlobVars[] = { /* Not listed on the MDN page; browser and QML extensions: */
// console/debug api
QLatin1String("console"), QLatin1String("print"),
// garbage collector
QLatin1String("gc"),
// i18n
QLatin1String("qsTr"), QLatin1String("qsTrId"),
QLatin1String("QT_TR_NOOP"), QLatin1String("QT_TRANSLATE_NOOP"),
QLatin1String("QT_TRID_NOOP"),
// XMLHttpRequest
QLatin1String("XMLHttpRequest")
};
QQmlJSScope::JavaScriptIdentifier globalJavaScript = {
QQmlJSScope::JavaScriptIdentifier::LexicalScoped, QQmlJS::SourceLocation()
};
for (const char **globalName = QV4::Compiler::Codegen::s_globalNames; *globalName != nullptr;
++globalName) {
m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName), globalJavaScript);
}
for (const auto &jsGlobVar : jsGlobVars)
m_currentScope->insertJSIdentifier(jsGlobVar, globalJavaScript);
}
void QQmlJSImportVisitor::enterEnvironment(QQmlJSScope::ScopeType type, const QString &name,
@ -264,6 +296,35 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiProgram *)
void QQmlJSImportVisitor::endVisit(UiProgram *)
{
resolveAliases();
processDefaultProperties();
checkPropertyBindings();
checkSignals();
for (const auto &scope : m_objectBindingScopes)
checkInheritanceCycle(scope);
for (const auto &scope : m_objectDefinitionScopes) {
checkGroupedAndAttachedScopes(scope);
checkInheritanceCycle(scope);
}
auto unusedImports = m_importLocations;
for (const QString &type : m_usedTypes) {
for (const auto &importLocation : m_importTypeLocationMap.values(type))
unusedImports.remove(importLocation);
// If there are no more unused imports left we can abort early
if (unusedImports.isEmpty())
break;
}
for (const auto &import : unusedImports) {
m_logger.log(QString::fromLatin1("Unused import at %1:%2:%3")
.arg(m_filePath)
.arg(import.startLine)
.arg(import.startColumn),
Log_UnusedImport, import);
}
}
static QQmlJSAnnotation::Value bindingToVariant(QQmlJS::AST::Statement *statement)
@ -328,6 +389,329 @@ QVector<QQmlJSAnnotation> QQmlJSImportVisitor::parseAnnotations(QQmlJS::AST::UiA
return annotationList;
}
void QQmlJSImportVisitor::processDefaultProperties()
{
for (auto it = m_pendingDefaultProperties.constBegin();
it != m_pendingDefaultProperties.constEnd(); ++it) {
// We can't expect custom parser default properties to be sensible, discard them for now.
if (it.key()->isInCustomParserParent())
continue;
const QQmlJSScope *scopeOfDefaultProperty = nullptr;
QString defaultPropertyName;
// NB: start looking for default property in parent scope (because this
// scope is not suitable), but progress through baseType()
bool isComponent = false;
for (const auto *s = it.key().get(); s; s = s->baseType().get()) {
defaultPropertyName = s->defaultPropertyName();
if (!defaultPropertyName.isEmpty()) {
scopeOfDefaultProperty = s;
break;
}
if (s->internalName() == QStringLiteral("QQmlComponent")) {
isComponent = true;
break;
}
}
// If the parent scope is based on Component it can have any child element
// TODO: We should also store these somewhere
if (isComponent)
continue;
if (defaultPropertyName.isEmpty()) {
m_logger.log(QStringLiteral("Cannot assign to non-existent default property"),
Log_Property, it.value().constFirst()->sourceLocation());
continue;
}
Q_ASSERT(scopeOfDefaultProperty);
QQmlJSMetaProperty defaultProp = scopeOfDefaultProperty->property(defaultPropertyName);
if (it.value().length() > 1 && !defaultProp.isList()) {
m_logger.log(
QStringLiteral("Cannot assign multiple objects to a default non-list property"),
Log_Property, it.value().constFirst()->sourceLocation());
}
// TODO: Currently we only support binding one scope, adjust this once this is no longer
// true
const QQmlJSScope::ConstPtr scope = it.value().constFirst();
QQmlJSMetaPropertyBinding binding(defaultProp);
binding.setValue(scope);
binding.setValueTypeName(getScopeName(scope, QQmlJSScope::QMLScope));
it.key()->addOwnPropertyBinding(binding);
auto propType = defaultProp.type();
if (propType.isNull() || !propType->isFullyResolved()
|| !scope->isFullyResolved()) // should be an error somewhere else
return;
// Assigning any element to a QQmlComponent property implicitly wraps it into a Component
// Check whether the property can be assigned the scope
if (propType->canAssign(scope))
return;
m_logger.log(QStringLiteral("Cannot assign to default property of incompatible type"),
Log_Property, scope->sourceLocation());
}
}
void QQmlJSImportVisitor::checkPropertyBindings()
{
for (auto it = m_propertyBindings.constBegin(); it != m_propertyBindings.constEnd(); ++it) {
QQmlJSScope::Ptr propertyScope = it.key();
QQmlJSScope::Ptr scope = propertyScope->parentScope();
for (const QString &name : it.value()) {
if (!scope->hasProperty(name)) {
// These warnings do not apply for custom parsers and their children and need to be
// handled on a case by case basis
if (scope->isInCustomParserParent())
continue;
// TODO: Can this be in a better suited category?
m_logger.log(QStringLiteral("Binding assigned to \"%1\", but no property \"%1\" "
"exists in the current element.")
.arg(name),
Log_Type, propertyScope->sourceLocation());
continue;
}
const auto property = scope->property(name);
if (!property.type()) {
m_logger.log(QStringLiteral("No type found for property \"%1\". This may be due "
"to a missing import statement or incomplete "
"qmltypes files.")
.arg(name),
Log_Type, propertyScope->sourceLocation());
}
const auto &annotations = property.annotations();
const auto deprecationAnn =
std::find_if(annotations.cbegin(), annotations.cend(),
[](const QQmlJSAnnotation &ann) { return ann.isDeprecation(); });
if (deprecationAnn != annotations.cend()) {
const auto deprecation = deprecationAnn->deprecation();
QString message = QStringLiteral("Binding on deprecated property \"%1\"")
.arg(property.propertyName());
if (!deprecation.reason.isEmpty())
message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
m_logger.log(message, Log_Deprecation, propertyScope->sourceLocation());
}
}
}
}
static QString signalName(QStringView handlerName)
{
if (handlerName.startsWith(u"on") && handlerName.size() > 2) {
QString signal = handlerName.mid(2).toString();
for (int i = 0; i < signal.length(); ++i) {
QChar &ch = signal[i];
if (ch.isLower())
return QString();
if (ch.isUpper()) {
ch = ch.toLower();
return signal;
}
}
}
return QString();
}
void QQmlJSImportVisitor::checkSignals()
{
for (auto it = m_signals.constBegin(); it != m_signals.constEnd(); ++it) {
for (const auto &pair : it.value()) {
const QString signal = signalName(pair.first);
if (!it.key()->hasMethod(signal)) {
m_logger.log(QStringLiteral("no matching signal found for handler \"%1\"")
.arg(pair.first),
Log_UnqualifiedAccess, m_currentScope->sourceLocation());
continue;
}
QQmlJSMetaMethod scopeSignal;
for (QQmlJSScope::ConstPtr scope = it.key(); scope; scope = scope->baseType()) {
const auto methods = scope->ownMethods();
const auto methodsRange = methods.equal_range(signal);
for (auto method = methodsRange.first; method != methodsRange.second; ++method) {
if (method->methodType() != QQmlJSMetaMethod::Signal)
continue;
scopeSignal = *method;
break;
}
}
const QStringList signalParameters = scopeSignal.parameterNames();
if (pair.second.length() > signalParameters.length()) {
m_logger.log(QStringLiteral("Signal handler for \"%2\" has more formal"
" parameters than the signal it handles.")
.arg(pair.first),
Log_Signal, it.key()->sourceLocation());
continue;
}
for (qsizetype i = 0; i < pair.second.length(); i++) {
const QStringView handlerParameter = pair.second.at(i);
const qsizetype j = signalParameters.indexOf(handlerParameter);
if (j == i || j < 0)
continue;
m_logger.log(QStringLiteral("Parameter %1 to signal handler for \"%2\""
" is called \"%3\". The signal has a parameter"
" of the same name in position %4.")
.arg(i + 1)
.arg(pair.first, handlerParameter)
.arg(j + 1),
Log_Signal, it.key()->sourceLocation());
}
}
}
}
void QQmlJSImportVisitor::addDefaultProperties()
{
if (m_currentScope == m_exportedRootScope || m_currentScope->parentScope()->isArrayScope()
|| m_currentScope->isInlineComponent()) // inapplicable
return;
m_pendingDefaultProperties[m_currentScope->parentScope()] << m_currentScope;
}
void QQmlJSImportVisitor::checkInheritanceCycle(QQmlJSScope::ConstPtr scope)
{
QQmlJSScope::ConstPtr originalScope = scope;
QList<QQmlJSScope::ConstPtr> scopes;
while (!scope.isNull()) {
for (const QQmlJSAnnotation &annotation : scope->annotations()) {
if (annotation.isDeprecation()) {
QQQmlJSDeprecation deprecation = annotation.deprecation();
QString message =
QStringLiteral("Type \"%1\" is deprecated").arg(scope->internalName());
if (!deprecation.reason.isEmpty())
message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
m_logger.log(message, Log_Deprecation, originalScope->sourceLocation());
}
}
if (scopes.contains(scope)) {
QString inheritenceCycle;
for (const auto &seen : qAsConst(scopes)) {
if (!inheritenceCycle.isEmpty())
inheritenceCycle.append(QLatin1String(" -> "));
inheritenceCycle.append(seen->baseTypeName());
}
m_logger.log(QStringLiteral("%1 is part of an inheritance cycle: %2")
.arg(scope->internalName())
.arg(inheritenceCycle),
Log_InheritanceCycle);
break;
}
scopes.append(scope);
if (scope->baseTypeName().isEmpty()) {
break;
} else if (auto newScope = scope->baseType()) {
scope = newScope;
} else {
m_logger.log(scope->baseTypeName()
+ QStringLiteral(" was not found. Did you add all import paths?"),
Log_Import);
break;
}
}
}
void QQmlJSImportVisitor::checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope)
{
// These warnings do not apply for custom parsers and their children and need to be handled on a
// case by case basis
if (scope->isInCustomParserParent())
return;
auto children = scope->childScopes();
while (!children.isEmpty()) {
auto childScope = children.takeFirst();
const auto type = childScope->scopeType();
switch (type) {
case QQmlJSScope::GroupedPropertyScope:
case QQmlJSScope::AttachedPropertyScope:
if (!childScope->baseType()) {
m_logger.log(QStringLiteral("unknown %1 property scope %2.")
.arg(type == QQmlJSScope::GroupedPropertyScope
? QStringLiteral("grouped")
: QStringLiteral("attached"),
childScope->internalName()),
Log_UnqualifiedAccess, childScope->sourceLocation());
}
children.append(childScope->childScopes());
default:
break;
}
}
}
void QQmlJSImportVisitor::flushPendingSignalParameters()
{
const QQmlJSMetaSignalHandler handler = m_signalHandlers[m_pendingSignalHandler];
for (const QString &parameter : handler.signalParameters) {
m_currentScope->insertJSIdentifier(
parameter, { QQmlJSScope::JavaScriptIdentifier::Injected, m_pendingSignalHandler });
}
m_pendingSignalHandler = QQmlJS::SourceLocation();
}
bool QQmlJSImportVisitor::visit(QQmlJS::AST::ExpressionStatement *ast)
{
if (m_pendingSignalHandler.isValid()) {
enterEnvironment(QQmlJSScope::JSFunctionScope, u"signalhandler"_qs,
ast->firstSourceLocation());
flushPendingSignalParameters();
}
return true;
}
void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ExpressionStatement *)
{
if (m_currentScope->scopeType() == QQmlJSScope::JSFunctionScope
&& m_currentScope->baseTypeName() == u"signalhandler"_qs) {
leaveEnvironment();
}
}
bool QQmlJSImportVisitor::visit(QQmlJS::AST::StringLiteral *sl)
{
const QString s = m_code.mid(sl->literalToken.begin(), sl->literalToken.length);
if (s.contains(QLatin1Char('\r')) || s.contains(QLatin1Char('\n')) || s.contains(QChar(0x2028u))
|| s.contains(QChar(0x2029u))) {
m_logger.log(QStringLiteral("String contains unescaped line terminator which is "
"deprecated. Use a template "
"literal instead."),
Log_MultilineString, sl->literalToken);
}
return true;
}
bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition)
{
@ -356,7 +740,6 @@ bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition)
void QQmlJSImportVisitor::endVisit(UiObjectDefinition *)
{
QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes);
leaveEnvironment();
}
@ -418,6 +801,15 @@ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember)
Log_Alias,
expression->firstSourceLocation());
}
} else {
const auto name = publicMember->memberType->name.toString();
if (m_rootScopeImports.contains(name) && !m_rootScopeImports[name].isNull()) {
if (m_importTypeLocationMap.contains(name))
m_usedTypes.insert(name);
} else {
m_logger.log(name + QStringLiteral(" was not found. Did you add all import paths?"),
Log_Import);
}
}
QQmlJSMetaProperty prop;
prop.setPropertyName(publicMember->name.toString());
@ -441,6 +833,7 @@ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember)
break;
}
}
return true;
}
@ -545,34 +938,88 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ClassExpression *)
bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
{
auto scope = m_currentScope;
const auto id = scriptBinding->qualifiedId;
const auto *statement = cast<ExpressionStatement *>(scriptBinding->statement);
if (!id->next && id->name == QLatin1String("id")) {
const auto *idExpression = cast<IdentifierExpression *>(statement->expression);
m_scopesById.insert(idExpression->name.toString(), m_currentScope);
const QString &name = idExpression->name.toString();
m_scopesById.insert(name, m_currentScope);
// TODO: Discard this once we properly store binding values and can just use
// QQmlJSScope::property() to obtain this
if (m_currentScope->parentScope() && !m_currentScope->parentScope()->parentScope())
m_rootId = name;
return true;
}
for (auto group = id; group->next; group = group->next) {
const QString name = group->name.toString();
if (name.isEmpty())
break;
enterEnvironmentNonUnique(name.front().isUpper() ? QQmlJSScope::AttachedPropertyScope
: QQmlJSScope::GroupedPropertyScope,
name, group->firstSourceLocation());
}
// TODO: remember the actual binding, once we can process it.
while (m_currentScope->scopeType() == QQmlJSScope::GroupedPropertyScope
|| m_currentScope->scopeType() == QQmlJSScope::AttachedPropertyScope) {
leaveEnvironment();
}
if (!statement || !statement->expression->asFunctionDefinition()) {
enterEnvironment(QQmlJSScope::JSFunctionScope, QStringLiteral("binding"),
scriptBinding->statement->firstSourceLocation());
}
auto name = id->name;
const QString signal = signalName(name);
if (signal.isEmpty()) {
for (const auto &childScope : scope->childScopes()) {
if ((childScope->scopeType() == QQmlJSScope::AttachedPropertyScope
|| childScope->scopeType() == QQmlJSScope::GroupedPropertyScope)
&& childScope->internalName() == name) {
return true;
}
}
m_propertyBindings[m_currentScope] << name.toString();
} else {
for (auto group = id; group->next; group = group->next) {
const QString name = group->name.toString();
const auto statement = scriptBinding->statement;
QStringList signalParameters;
if (name.isEmpty())
if (ExpressionStatement *expr = cast<ExpressionStatement *>(statement)) {
if (FunctionExpression *func = expr->expression->asFunctionDefinition()) {
for (FormalParameterList *formal = func->formals; formal; formal = formal->next)
signalParameters << formal->element->bindingIdentifier.toString();
}
}
m_signals[scope] << QPair<QString, QStringList> { name.toString(), signalParameters };
QQmlJSMetaMethod scopeSignal;
for (QQmlJSScope::ConstPtr qmlScope = scope; qmlScope; qmlScope = qmlScope->baseType()) {
const auto methods = qmlScope->ownMethods();
const auto methodsRange = methods.equal_range(signal);
for (auto method = methodsRange.first; method != methodsRange.second; ++method) {
if (method->methodType() != QQmlJSMetaMethod::Signal)
continue;
scopeSignal = *method;
break;
enterEnvironmentNonUnique(name.front().isUpper() ? QQmlJSScope::AttachedPropertyScope
: QQmlJSScope::GroupedPropertyScope,
name, group->firstSourceLocation());
}
}
// TODO: remember the actual binding, once we can process it.
while (m_currentScope->scopeType() == QQmlJSScope::GroupedPropertyScope
|| m_currentScope->scopeType() == QQmlJSScope::AttachedPropertyScope) {
leaveEnvironment();
}
if (!statement || !statement->expression->asFunctionDefinition()) {
enterEnvironment(QQmlJSScope::JSFunctionScope, QStringLiteral("binding"),
scriptBinding->statement->firstSourceLocation());
}
const auto firstSourceLocation = statement->firstSourceLocation();
bool hasMultilineStatementBody =
statement->lastSourceLocation().startLine > firstSourceLocation.startLine;
m_pendingSignalHandler = firstSourceLocation;
m_signalHandlers.insert(firstSourceLocation,
{ scopeSignal.parameterNames(), hasMultilineStatementBody });
}
return true;
@ -755,6 +1202,10 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::Block *ast)
{
enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("block"),
ast->firstSourceLocation());
if (m_pendingSignalHandler.isValid())
flushPendingSignalParameters();
return true;
}
@ -796,6 +1247,12 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::WithStatement *ast)
{
enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("with"),
ast->firstSourceLocation());
m_logger.log(QStringLiteral("with statements are strongly discouraged in QML "
"and might cause false positives when analysing unqualified "
"identifiers"),
Log_WithStatement, ast->firstSourceLocation());
return true;
}
@ -868,6 +1325,8 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
enterEnvironment(QQmlJSScope::QMLScope, name,
uiob->qualifiedTypeNameId->identifierToken);
QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes);
m_objectBindingScopes << m_currentScope;
return true;
}

View File

@ -71,6 +71,12 @@ public:
const QString &localFile, QQmlJSResourceFileMapper *mapper);
protected:
// Linter warnings, we might want to move this at some point
bool visit(QQmlJS::AST::StringLiteral *) override;
bool visit(QQmlJS::AST::ExpressionStatement *ast) override;
void endVisit(QQmlJS::AST::ExpressionStatement *ast) override;
bool visit(QQmlJS::AST::UiProgram *) override;
void endVisit(QQmlJS::AST::UiProgram *) override;
bool visit(QQmlJS::AST::UiObjectDefinition *) override;
@ -125,6 +131,9 @@ protected:
void throwRecursionDepthError() override;
QString m_implicitImportDirectory;
QString m_code;
QString m_filePath;
QString m_rootId;
QStringView m_inlineComponentName;
bool m_nextIsInlineComponent = false;
QStringList m_qmltypesFiles;
@ -152,11 +161,40 @@ protected:
void leaveEnvironment();
QVector<QQmlJSAnnotation> parseAnnotations(QQmlJS::AST::UiAnnotationList *list);
void addDefaultProperties();
void processDefaultProperties();
void checkPropertyBindings();
void checkSignals();
void flushPendingSignalParameters();
void checkInheritanceCycle(QQmlJSScope::ConstPtr scope);
void checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope);
QQmlJSLogger m_logger;
// Used to temporarily store annotations for functions and generators wrapped in UiSourceElements
QVector<QQmlJSAnnotation> m_pendingMethodAnnotations;
QHash<QQmlJSScope::Ptr, QVector<QQmlJSScope::Ptr>> m_pendingDefaultProperties;
QVector<QQmlJSScope::Ptr> m_objectBindingScopes;
QVector<QQmlJSScope::Ptr> m_objectDefinitionScopes;
QHash<QQmlJSScope::Ptr, QVector<QString>> m_propertyBindings;
QHash<QQmlJSScope::Ptr, QVector<QPair<QString, QStringList>>> m_signals;
QHash<QQmlJS::SourceLocation, QQmlJSMetaSignalHandler> m_signalHandlers;
QQmlJS::SourceLocation m_pendingSignalHandler;
struct OutstandingConnection
{
QString targetName;
QQmlJSScope::Ptr scope;
QQmlJS::AST::UiObjectDefinition *uiod;
};
QVarLengthArray<OutstandingConnection, 3>
m_outstandingConnections; // Connections whose target we have not encountered
private:
void importBaseModules();
void resolveAliases();

View File

@ -418,6 +418,12 @@ public:
}
};
struct QQmlJSMetaSignalHandler
{
QStringList signalParameters;
bool isMultiline;
};
QT_END_NAMESPACE
#endif // QQMLJSMETATYPES_P_H

View File

@ -250,9 +250,9 @@ void CheckIdentifiers::checkMemberAccess(const QVector<FieldMember> &members,
void CheckIdentifiers::operator()(
const QHash<QString, QQmlJSScope::ConstPtr> &qmlIDs,
const QHash<QQmlJS::SourceLocation, SignalHandler> &signalHandlers,
const MemberAccessChains &memberAccessChains,
const QQmlJSScope::ConstPtr &root, const QString &rootId) const
const QHash<QQmlJS::SourceLocation, QQmlJSMetaSignalHandler> &signalHandlers,
const MemberAccessChains &memberAccessChains, const QQmlJSScope::ConstPtr &root,
const QString &rootId) const
{
// revisit all scopes
QQueue<QQmlJSScope::ConstPtr> workQueue;
@ -442,7 +442,7 @@ void CheckIdentifiers::operator()(
const auto handler = signalHandlers[id.location];
colorOut.write(QLatin1String(handler.isMultiline ? "function(" : "("), QtInfoMsg);
const auto parameters = handler.signal.parameterNames();
const auto parameters = handler.signalParameters;
for (int numParams = parameters.size(); numParams > 0; --numParams) {
colorOut.write(parameters.at(parameters.size() - numParams), QtInfoMsg);
if (numParams > 1)

View File

@ -32,14 +32,10 @@
#include <QtQmlCompiler/private/qqmljslogger_p.h>
#include <QtQmlCompiler/private/qqmljsscope_p.h>
#include <QtQmlCompiler/private/qqmljsimporter_p.h>
#include <QtQmlCompiler/private/qqmljsmetatypes_p.h>
class QColorOutput;
struct SignalHandler {
QQmlJSMetaMethod signal;
bool isMultiline;
};
struct FieldMember
{
QString m_name;
@ -57,10 +53,10 @@ public:
m_logger(logger), m_code(code), m_types(types), m_fileName(fileName)
{}
void operator ()(const QHash<QString, QQmlJSScope::ConstPtr> &qmlIDs,
const QHash<QQmlJS::SourceLocation, SignalHandler> &signalHandlers,
const MemberAccessChains &memberAccessChains,
const QQmlJSScope::ConstPtr &root, const QString &rootId) const;
void operator()(const QHash<QString, QQmlJSScope::ConstPtr> &qmlIDs,
const QHash<QQmlJS::SourceLocation, QQmlJSMetaSignalHandler> &signalHandlers,
const MemberAccessChains &memberAccessChains, const QQmlJSScope::ConstPtr &root,
const QString &rootId) const;
private:
void checkMemberAccess(const QVector<FieldMember> &members,

View File

@ -36,378 +36,107 @@
#include <QtQml/private/qqmljsast_p.h>
#include <QtQml/private/qqmljslexer_p.h>
#include <QtQml/private/qqmljsparser_p.h>
#include <QtQml/private/qv4codegen_p.h>
#include <QtQml/private/qqmlimportresolver_p.h>
#include <QtCore/qfile.h>
#include <QtCore/qdiriterator.h>
#include <QtCore/qscopedvaluerollback.h>
void FindWarningVisitor::checkInheritanceCycle(QQmlJSScope::ConstPtr scope)
bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod)
{
QQmlJSScope::ConstPtr originalScope = scope;
QList<QQmlJSScope::ConstPtr> scopes;
while (!scope.isNull()) {
QQmlJSImportVisitor::visit(uiod);
for (const QQmlJSAnnotation &annotation : scope->annotations()) {
if (annotation.isDeprecation()) {
QQQmlJSDeprecation deprecation = annotation.deprecation();
const QString name = m_currentScope->baseTypeName();
if (name.isEmpty() || name.front().isLower())
return false; // Ignore grouped properties for now
QString message = QStringLiteral("Type \"%1\" is deprecated")
.arg(scope->internalName());
if (!deprecation.reason.isEmpty())
message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
m_logger.log(message, Log_Deprecation, originalScope->sourceLocation()
);
}
}
if (scopes.contains(scope)) {
QString inheritenceCycle;
for (const auto &seen: qAsConst(scopes)) {
if (!inheritenceCycle.isEmpty())
inheritenceCycle.append(QLatin1String(" -> "));
inheritenceCycle.append(seen->baseTypeName());
}
m_logger.log(QStringLiteral("%1 is part of an inheritance cycle: %2")
.arg(scope->internalName())
.arg(inheritenceCycle),
Log_InheritanceCycle);
m_unknownImports.insert(scope->internalName());
break;
}
scopes.append(scope);
if (scope->baseTypeName().isEmpty()) {
break;
} else if (auto newScope = scope->baseType()) {
scope = newScope;
} else {
m_logger.log(scope->baseTypeName()
+ QStringLiteral(" was not found. Did you add all import paths?"),
Log_Import);
m_unknownImports.insert(scope->baseTypeName());
break;
}
}
}
void FindWarningVisitor::checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope)
{
// These warnings do not apply for custom parsers and their children and need to be handled on a
// case by case basis
if (scope->isInCustomParserParent())
return;
auto children = scope->childScopes();
while (!children.isEmpty()) {
auto childScope = children.takeFirst();
const auto type = childScope->scopeType();
switch (type) {
case QQmlJSScope::GroupedPropertyScope:
case QQmlJSScope::AttachedPropertyScope:
if (!childScope->baseType()) {
m_logger.log(
QStringLiteral("unknown %1 property scope %2.")
.arg(type == QQmlJSScope::GroupedPropertyScope
? QStringLiteral("grouped")
: QStringLiteral("attached"),
childScope->internalName()),
Log_UnqualifiedAccess,
childScope->sourceLocation()
);
}
children.append(childScope->childScopes());
default:
break;
}
}
}
void FindWarningVisitor::flushPendingSignalParameters()
{
const SignalHandler handler = m_signalHandlers[m_pendingSingalHandler];
for (const QString &parameter : handler.signal.parameterNames()) {
m_currentScope->insertJSIdentifier(
parameter, {
QQmlJSScope::JavaScriptIdentifier::Injected,
m_pendingSingalHandler
});
}
m_pendingSingalHandler = QQmlJS::SourceLocation();
}
void FindWarningVisitor::checkDefaultProperty(const QQmlJSScope::ConstPtr &scope)
{
if (scope == m_exportedRootScope || scope->isArrayScope()
|| scope->isInlineComponent()) // inapplicable
return;
// These warnings do not apply for custom parsers and their children and need to be handled on a
// case by case basis
if (scope->isInCustomParserParent())
return;
const QQmlJSScope *scopeOfDefaultProperty = nullptr;
QString defaultPropertyName;
// NB: start looking for default property in parent scope (because this
// scope is not suitable), but progress through baseType()
for (auto s = scope->parentScope(); s; s = s->baseType()) {
defaultPropertyName = s->defaultPropertyName();
if (!defaultPropertyName.isEmpty()) {
scopeOfDefaultProperty = s.data();
break;
}
// If the parent scope is based on Component it can have any child element
if (s->internalName() == QStringLiteral("QQmlComponent"))
return;
}
if (defaultPropertyName.isEmpty()) {
if (scope->parentScope()->isFullyResolved()) {
m_logger.log(QStringLiteral("Cannot assign to non-existent default property"),
Log_Property, scope->sourceLocation());
}
return;
}
Q_ASSERT(scopeOfDefaultProperty);
Q_ASSERT(scope->parentScope());
QQmlJSMetaProperty defaultProp = scopeOfDefaultProperty->property(defaultPropertyName);
const QQmlJSScope *parentScope = scope->parentScope().get();
// abuse QHash feature to construct default value through
// operator[]. default bool is false, which is what's needed
if (m_scopeHasDefaultPropertyAssignment[parentScope] && !defaultProp.isList()) {
// already has some object assigned to a default property and
// this default property is not a list property
m_logger.log(QStringLiteral("Cannot assign multiple objects to a default non-list property"),
Log_Property, scope->sourceLocation());
}
m_scopeHasDefaultPropertyAssignment[parentScope] = true;
auto propType = defaultProp.type();
if (propType.isNull() || !propType->isFullyResolved()
|| !scope->isFullyResolved()) // should be an error somewhere else
return;
// Assigning any element to a QQmlComponent property implicitly wraps it into a Component
// Check whether the property can be assigned the scope
if (propType->canAssign(scope))
return;
m_logger.log(QStringLiteral("Cannot assign to default property of incompatible type"),
Log_Property, scope->sourceLocation());
}
void FindWarningVisitor::throwRecursionDepthError()
{
QQmlJSImportVisitor::throwRecursionDepthError();
}
bool FindWarningVisitor::visit(QQmlJS::AST::ExpressionStatement *ast)
{
if (m_pendingSingalHandler.isValid()) {
enterEnvironment(QQmlJSScope::JSFunctionScope, "signalhandler", ast->firstSourceLocation());
flushPendingSignalParameters();
}
return true;
}
void FindWarningVisitor::endVisit(QQmlJS::AST::ExpressionStatement *)
{
if (m_currentScope->scopeType() == QQmlJSScope::JSFunctionScope
&& m_currentScope->baseTypeName() == "signalhandler") {
leaveEnvironment();
}
}
bool FindWarningVisitor::visit(QQmlJS::AST::Block *block)
{
if (!QQmlJSImportVisitor::visit(block))
return false;
if (m_pendingSingalHandler.isValid())
flushPendingSignalParameters();
return true;
}
bool FindWarningVisitor::visit(QQmlJS::AST::WithStatement *withStatement)
{
m_logger.log(QStringLiteral("with statements are strongly discouraged in QML "
"and might cause false positives when analysing unqualified "
"identifiers"),
Log_WithStatement, withStatement->firstSourceLocation());
return QQmlJSImportVisitor::visit(withStatement);
}
static QString signalName(QStringView handlerName)
{
if (handlerName.startsWith(u"on") && handlerName.size() > 2) {
QString signal = handlerName.mid(2).toString();
for (int i = 0; i < signal.length(); ++i) {
QChar &ch = signal[i];
if (ch.isLower())
return QString();
if (ch.isUpper()) {
ch = ch.toLower();
return signal;
}
}
}
return QString();
}
bool FindWarningVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb)
{
using namespace QQmlJS::AST;
const auto qmlScope = m_currentScope;
if (!QQmlJSImportVisitor::visit(uisb))
return false;
auto name = uisb->qualifiedId->name;
if (name == QLatin1String("id")) {
// Figure out whether the current scope is the root scope.
if (auto parentScope = qmlScope->parentScope()) {
if (!parentScope->parentScope()) {
const auto expstat = cast<ExpressionStatement *>(uisb->statement);
const auto identexp = cast<IdentifierExpression *>(expstat->expression);
m_rootId = identexp->name.toString();
}
}
return true;
}
if (!qmlScope->isFullyResolved())
return true;
const QString signal = signalName(name);
if (signal.isEmpty()) {
for (const auto &childScope : qmlScope->childScopes()) {
if ((childScope->scopeType() == QQmlJSScope::AttachedPropertyScope
|| childScope->scopeType() == QQmlJSScope::GroupedPropertyScope)
&& childScope->internalName() == name) {
return true;
}
}
if (!qmlScope->hasProperty(name.toString())) {
// These warnings do not apply for custom parsers and their children and need to be
// handled on a case by case basis
if (qmlScope->isInCustomParserParent())
return true;
// TODO: Can this be in a better suited category?
m_logger.log(QStringLiteral("Binding assigned to \"%1\", but no property \"%1\" "
"exists in the current element.")
.arg(name),
Log_Type, uisb->firstSourceLocation());
return true;
}
const auto property = qmlScope->property(name.toString());
if (!property.type()) {
m_logger.log(QStringLiteral("No type found for property \"%1\". This may be due "
"to a missing import statement or incomplete "
"qmltypes files.")
.arg(name),
Log_Type, uisb->firstSourceLocation());
}
const auto &annotations = property.annotations();
const auto deprecationAnn = std::find_if(annotations.cbegin(), annotations.cend(), [](const QQmlJSAnnotation &ann) { return ann.isDeprecation(); });
if (deprecationAnn != annotations.cend()) {
const auto deprecation = deprecationAnn->deprecation();
QString message = QStringLiteral("Binding on deprecated property \"%1\"")
.arg(property.propertyName());
if (!deprecation.reason.isEmpty())
message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
m_logger.log(message, Log_Deprecation, uisb->firstSourceLocation());
}
return true;
}
if (!qmlScope->hasMethod(signal)) {
m_logger.log(
QStringLiteral("no matching signal found for handler \"%1\"").arg(name.toString()),
Log_UnqualifiedAccess, uisb->firstSourceLocation());
return true;
}
QQmlJSMetaMethod scopeSignal;
for (QQmlJSScope::ConstPtr scope = qmlScope; scope; scope = scope->baseType()) {
const auto methods = scope->ownMethods();
const auto methodsRange = methods.equal_range(signal);
for (auto method = methodsRange.first; method != methodsRange.second; ++method) {
if (method->methodType() != QQmlJSMetaMethod::Signal)
continue;
scopeSignal = *method;
break;
}
}
const auto statement = uisb->statement;
if (ExpressionStatement *expr = cast<ExpressionStatement *>(statement)) {
if (FunctionExpression *func = expr->expression->asFunctionDefinition()) {
// functions are already handled
// they do not get names inserted according to the signal, but access their formal
// parameters. Let's still check if the names match, though.
const QStringList signalParameters = scopeSignal.parameterNames();
qsizetype i = 0, end = signalParameters.length();
for (FormalParameterList *formal = func->formals;
formal; ++i, formal = formal->next) {
if (i == end) {
m_logger.log(
QStringLiteral("Signal handler for \"%2\" has more formal"
" parameters than the signal it handles.")
.arg(name),
Log_Signal,
uisb->firstSourceLocation()
);
if (name.endsWith(u"Connections"_qs)) {
QString target;
auto member = uiod->initializer->members;
while (member) {
if (member->member->kind == QQmlJS::AST::Node::Kind_UiScriptBinding) {
auto asBinding = static_cast<QQmlJS::AST::UiScriptBinding *>(member->member);
if (asBinding->qualifiedId->name == u"target"_qs) {
if (asBinding->statement->kind == QQmlJS::AST::Node::Kind_ExpressionStatement) {
auto expr = static_cast<QQmlJS::AST::ExpressionStatement *>(
asBinding->statement)
->expression;
if (auto idexpr =
QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression *>(expr)) {
target = idexpr->name.toString();
} else {
// more complex expressions are not supported
}
}
break;
}
const QStringView handlerParameter = formal->element->bindingIdentifier;
const qsizetype j = signalParameters.indexOf(handlerParameter);
if (j == i || j < 0)
continue;
m_logger.log(QStringLiteral("Parameter %1 to signal handler for \"%2\""
" is called \"%3\". The signal has a parameter"
" of the same name in position %4.")
.arg(i + 1)
.arg(name, handlerParameter)
.arg(j + 1),
Log_Signal, uisb->firstSourceLocation());
}
return true;
member = member->next;
}
QQmlJSScope::ConstPtr targetScope;
if (target.isEmpty()) {
// no target set, connection comes from parentF
QQmlJSScope::Ptr scope = m_currentScope;
do {
scope = scope->parentScope(); // TODO: rename method
} while (scope->scopeType() != QQmlJSScope::QMLScope);
targetScope = m_rootScopeImports.value(scope->baseTypeName());
} else {
// there was a target, check if we already can find it
auto scopeIt = m_scopesById.find(target);
if (scopeIt != m_scopesById.end()) {
targetScope = *scopeIt;
} else {
m_outstandingConnections.push_back({ target, m_currentScope, uiod });
return false; // visit children later once target is known
}
}
for (const auto scope = targetScope; targetScope; targetScope = targetScope->baseType()) {
const auto connectionMethods = targetScope->ownMethods();
for (const auto &method : connectionMethods)
m_currentScope->addOwnMethod(method);
}
}
const auto firstSourceLocation = statement->firstSourceLocation();
bool hasMultilineStatementBody
= statement->lastSourceLocation().startLine > firstSourceLocation.startLine;
m_pendingSingalHandler = firstSourceLocation;
m_signalHandlers.insert(firstSourceLocation, {scopeSignal, hasMultilineStatementBody});
addDefaultProperties();
m_objectDefinitionScopes << m_currentScope;
return true;
}
void FindWarningVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *uiod)
{
auto childScope = m_currentScope;
QQmlJSImportVisitor::endVisit(uiod);
if (m_currentScope == m_globalScope
|| m_currentScope->baseTypeName() == QStringLiteral("Component")) {
return;
}
QString parentPropertyName;
for (QQmlJSScope::ConstPtr scope = childScope; scope; scope = scope->baseType()) {
parentPropertyName = scope->parentPropertyName();
if (parentPropertyName.isEmpty())
continue;
auto property = scope->property(parentPropertyName);
property.setType(QQmlJSScope::ConstPtr(m_currentScope));
if (childScope->hasOwnProperty(parentPropertyName)) {
Q_ASSERT(childScope->ownProperty(parentPropertyName).index() >= 0);
} else {
// it's a new property, so must adjust the index. the index is
// "outdated" as it's a relative index of scope, not childScope (or
// it might even be -1 in theory but this is likely an error)
property.setIndex(childScope->ownProperties().size());
}
// TODO: This is bad. We shouldn't add a new property but rather amend the existing one.
childScope->addOwnProperty(property);
}
}
bool FindWarningVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp)
{
const QString name = idexp->name.toString();
@ -426,38 +155,8 @@ FindWarningVisitor::FindWarningVisitor(QQmlJSImporter *importer, QStringList qml
QString fileName, bool silent)
: QQmlJSImportVisitor(importer,
implicitImportDirectory(fileName, importer->resourceFileMapper()),
qmltypesFiles, fileName, code, silent),
m_code(code),
m_rootId(QLatin1String("<id>")),
m_filePath(fileName)
qmltypesFiles, fileName, code, silent)
{
m_currentScope->setInternalName("global");
QLatin1String jsGlobVars[] = {
/* Not listed on the MDN page; browser and QML extensions: */
// console/debug api
QLatin1String("console"), QLatin1String("print"),
// garbage collector
QLatin1String("gc"),
// i18n
QLatin1String("qsTr"), QLatin1String("qsTrId"), QLatin1String("QT_TR_NOOP"),
QLatin1String("QT_TRANSLATE_NOOP"), QLatin1String("QT_TRID_NOOP"),
// XMLHttpRequest
QLatin1String("XMLHttpRequest")
};
QQmlJSScope::JavaScriptIdentifier globalJavaScript = {
QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
QQmlJS::SourceLocation()
};
for (const char **globalName = QV4::Compiler::Codegen::s_globalNames;
*globalName != nullptr;
++globalName) {
m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName), globalJavaScript);
}
for (const auto& jsGlobVar: jsGlobVars)
m_currentScope->insertJSIdentifier(jsGlobVar, globalJavaScript);
parseComments(comments);
}
@ -549,106 +248,17 @@ bool FindWarningVisitor::check()
outstandingConnection.scope->addOwnMethod(method);
}
}
QScopedValueRollback<QQmlJSScope::Ptr> rollback(m_currentScope, outstandingConnection.scope);
QScopedValueRollback<QQmlJSScope::Ptr> rollback(m_currentScope,
outstandingConnection.scope);
outstandingConnection.uiod->initializer->accept(this);
}
auto unusedImports = m_importLocations;
for (const QString &type : m_usedTypes) {
for (const auto &importLocation : m_importTypeLocationMap.values(type))
unusedImports.remove(importLocation);
// If there are no more unused imports left we can abort early
if (unusedImports.isEmpty())
break;
}
for (const auto &import : unusedImports) {
m_logger.log(QString::fromLatin1("Unused import at %1:%2:%3")
.arg(m_filePath)
.arg(import.startLine)
.arg(import.startColumn),
Log_UnusedImport, import);
}
CheckIdentifiers check(&m_logger, m_code, m_rootScopeImports, m_filePath);
check(m_scopesById, m_signalHandlers, m_memberAccessChains, m_globalScope, m_rootId);
return !m_logger.hasWarnings() && !m_logger.hasErrors();
}
bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
{
if (!QQmlJSImportVisitor::visit(uiob))
return false;
checkInheritanceCycle(m_currentScope);
return true;
}
bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod)
{
using namespace QQmlJS::AST;
if (!QQmlJSImportVisitor::visit(uiod))
return false;
const QString name = m_currentScope->baseTypeName();
if (name.isEmpty() || name.front().isLower())
return false; // Ignore grouped properties for now
checkInheritanceCycle(m_currentScope);
if (name.endsWith("Connections")) {
QString target;
auto member = uiod->initializer->members;
while (member) {
if (member->member->kind == QQmlJS::AST::Node::Kind_UiScriptBinding) {
auto asBinding = static_cast<QQmlJS::AST::UiScriptBinding*>(member->member);
if (asBinding->qualifiedId->name == QLatin1String("target")) {
if (asBinding->statement->kind == QQmlJS::AST::Node::Kind_ExpressionStatement) {
auto expr = static_cast<QQmlJS::AST::ExpressionStatement*>(asBinding->statement)->expression;
if (auto idexpr = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression*>(expr)) {
target = idexpr->name.toString();
} else {
// more complex expressions are not supported
}
}
break;
}
}
member = member->next;
}
QQmlJSScope::ConstPtr targetScope;
if (target.isEmpty()) {
// no target set, connection comes from parentF
QQmlJSScope::Ptr scope = m_currentScope;
do {
scope = scope->parentScope(); // TODO: rename method
} while (scope->scopeType() != QQmlJSScope::QMLScope);
targetScope = m_rootScopeImports.value(scope->baseTypeName());
} else {
// there was a target, check if we already can find it
auto scopeIt = m_scopesById.find(target);
if (scopeIt != m_scopesById.end()) {
targetScope = *scopeIt;
} else {
m_outstandingConnections.push_back({target, m_currentScope, uiod});
return false; // visit children later once target is known
}
}
for (const auto scope = targetScope; targetScope; targetScope = targetScope->baseType()) {
const auto connectionMethods = targetScope->ownMethods();
for (const auto &method : connectionMethods)
m_currentScope->addOwnMethod(method);
}
}
checkDefaultProperty(m_currentScope);
return true;
}
bool FindWarningVisitor::visit(QQmlJS::AST::PatternElement *element)
{
if (element->isVariableDeclaration()) {
@ -668,46 +278,6 @@ bool FindWarningVisitor::visit(QQmlJS::AST::PatternElement *element)
return true;
}
void FindWarningVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *uiod)
{
auto childScope = m_currentScope;
QQmlJSImportVisitor::endVisit(uiod);
checkGroupedAndAttachedScopes(childScope);
if (m_currentScope == m_globalScope
|| m_currentScope->baseTypeName() == QStringLiteral("Component")) {
return;
}
QString parentPropertyName;
for (QQmlJSScope::ConstPtr scope = childScope; scope; scope = scope->baseType()) {
parentPropertyName = scope->parentPropertyName();
if (parentPropertyName.isEmpty())
continue;
auto property = scope->property(parentPropertyName);
property.setType(QQmlJSScope::ConstPtr(m_currentScope));
if (childScope->hasOwnProperty(parentPropertyName)) {
Q_ASSERT(childScope->ownProperty(parentPropertyName).index() >= 0);
} else {
// it's a new property, so must adjust the index. the index is
// "outdated" as it's a relative index of scope, not childScope (or
// it might even be -1 in theory but this is likely an error)
property.setIndex(childScope->ownProperties().size());
}
// TODO: This is bad. We shouldn't add a new property but rather amend the existing one.
childScope->addOwnProperty(property);
}
}
bool FindWarningVisitor::visit(QQmlJS::AST::FieldMemberExpression *)
{
return true;
}
void FindWarningVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember)
{
using namespace QQmlJS::AST;
@ -745,11 +315,6 @@ void FindWarningVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMembe
}
}
bool FindWarningVisitor::visit(QQmlJS::AST::BinaryExpression *)
{
return true;
}
void FindWarningVisitor::endVisit(QQmlJS::AST::BinaryExpression *binExp)
{
if (binExp->op == QSOperator::As
@ -759,36 +324,3 @@ void FindWarningVisitor::endVisit(QQmlJS::AST::BinaryExpression *binExp)
m_fieldMemberBase = nullptr;
}
}
bool FindWarningVisitor::visit(QQmlJS::AST::UiPublicMember *uipb)
{
QQmlJSImportVisitor::visit(uipb);
if (uipb->type == QQmlJS::AST::UiPublicMember::Property && uipb->memberType != nullptr
&& !uipb->memberType->name.isEmpty() && uipb->memberType->name != QLatin1String("alias")) {
const auto name = uipb->memberType->name.toString();
if (m_rootScopeImports.contains(name) && !m_rootScopeImports[name].isNull()) {
if (m_importTypeLocationMap.contains(name))
m_usedTypes.insert(name);
} else {
m_logger.log(name + QStringLiteral(" was not found. Did you add all import paths?"),
Log_Import);
}
}
return true;
}
bool FindWarningVisitor::visit(QQmlJS::AST::StringLiteral *sl)
{
const QString s = m_code.mid(sl->literalToken.begin(), sl->literalToken.length);
if (s.contains(QLatin1Char('\r')) || s.contains(QLatin1Char('\n')) || s.contains(QChar(0x2028u))
|| s.contains(QChar(0x2029u))) {
m_logger.log(QStringLiteral("String contains unescaped line terminator which is "
"deprecated. Use a template "
"literal instead."),
Log_MultilineString, sl->literalToken);
}
return true;
}

View File

@ -64,68 +64,26 @@ public:
bool check();
private:
QHash<QQmlJS::SourceLocation, SignalHandler> m_signalHandlers;
QQmlJS::SourceLocation m_pendingSingalHandler;
MemberAccessChains m_memberAccessChains;
QQmlJS::AST::ExpressionNode *m_fieldMemberBase = nullptr;
QString m_code;
QString m_rootId;
QString m_filePath;
QSet<QString> m_unknownImports;
struct OutstandingConnection
{
QString targetName;
QQmlJSScope::Ptr scope;
QQmlJS::AST::UiObjectDefinition *uiod;
};
QVarLengthArray<OutstandingConnection, 3> m_outstandingConnections; // Connections whose target we have not encountered
// records of whether a default property has object assigned to it. for
// correctness, the scope that defines the default property acts as a key
QHash<const QQmlJSScope *, bool> m_scopeHasDefaultPropertyAssignment;
void parseComments(const QList<QQmlJS::SourceLocation> &comments);
void checkInheritanceCycle(QQmlJSScope::ConstPtr scope);
void checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope);
void flushPendingSignalParameters();
void checkDefaultProperty(const QQmlJSScope::ConstPtr &scope);
void throwRecursionDepthError() override;
// work around compiler error in clang11
using QQmlJSImportVisitor::visit;
using QQmlJSImportVisitor::endVisit;
// start block/scope handling
bool visit(QQmlJS::AST::ExpressionStatement *ast) override;
void endVisit(QQmlJS::AST::ExpressionStatement *ast) override;
bool visit(QQmlJS::AST::Block *ast) override;
bool visit(QQmlJS::AST::WithStatement *withStatement) override;
/* --- end block handling --- */
bool visit(QQmlJS::AST::UiObjectBinding *uiob) override;
bool visit(QQmlJS::AST::UiObjectDefinition *uiod) override;
void endVisit(QQmlJS::AST::UiObjectDefinition *) override;
bool visit(QQmlJS::AST::UiScriptBinding *uisb) override;
bool visit(QQmlJS::AST::UiPublicMember *uipb) override;
void endVisit(QQmlJS::AST::UiObjectDefinition *uiod) override;
// expression handling
bool visit(QQmlJS::AST::IdentifierExpression *idexp) override;
bool visit(QQmlJS::AST::PatternElement *) override;
bool visit(QQmlJS::AST::FieldMemberExpression *idprop) override;
void endVisit(QQmlJS::AST::FieldMemberExpression *) override;
bool visit(QQmlJS::AST::BinaryExpression *) override;
void endVisit(QQmlJS::AST::BinaryExpression *) override;
bool visit(QQmlJS::AST::StringLiteral *) override;
};
#endif // FINDUNQUALIFIED_H