qmllint: Implement deprecation warnings
Make qmllint warn about @Deprecated {} annotations. Also adds support for annotations in qmlcompiler. [ChangeLog][QML][qmllint] Add support for deprecation annotations. Task-number: QTBUG-84895 Change-Id: Ia506a6c0077a2b9ab3bf4fdac207bd0540635b30 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
parent
7ea690c61d
commit
bbba1e5614
|
@ -20,6 +20,7 @@ qt_internal_add_module(QmlCompiler
|
|||
qqmljstypedescriptionreader.cpp qqmljstypedescriptionreader_p.h
|
||||
qqmljstypereader.cpp qqmljstypereader_p.h
|
||||
qresourcerelocater.cpp qresourcerelocater_p.h
|
||||
qqmljsannotation_p.h qqmljsannotation.cpp
|
||||
PUBLIC_LIBRARIES
|
||||
Qt::CorePrivate
|
||||
Qt::QmlDevToolsPrivate
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the tools applications of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
||||
** 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.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qqmljsannotation_p.h"
|
||||
|
||||
bool QQmlJSAnnotation::isDeprecation() const { return name == QStringLiteral("Deprecated"); }
|
||||
|
||||
QQQmlJSDeprecation QQmlJSAnnotation::deprecation() const {
|
||||
Q_ASSERT(isDeprecation());
|
||||
QQQmlJSDeprecation deprecation;
|
||||
if (bindings.contains(QStringLiteral("reason"))) {
|
||||
|
||||
auto reason = bindings[QStringLiteral("reason")];
|
||||
|
||||
if (reason.typeId() == QMetaType::QString) {
|
||||
deprecation.reason = reason.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return deprecation;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the tools applications of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
||||
** 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.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QQMLJSANNOTATION_P_H
|
||||
#define QQMLJSANNOTATION_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 <private/qqmljsast_p.h>
|
||||
#include <qvariant.h>
|
||||
#include <qglobal.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace QQmlJS::AST;
|
||||
|
||||
struct QQQmlJSDeprecation
|
||||
{
|
||||
QString reason;
|
||||
};
|
||||
|
||||
struct QQmlJSAnnotation
|
||||
{
|
||||
QString name;
|
||||
QHash<QString, QVariant> bindings;
|
||||
|
||||
bool isDeprecation() const;
|
||||
QQQmlJSDeprecation deprecation() const;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QQMLJSANNOTATION_P_H
|
|
@ -135,6 +135,69 @@ void QQmlJSImportVisitor::endVisit(UiProgram *)
|
|||
resolveAliases();
|
||||
}
|
||||
|
||||
static QVariant bindingToVariant(QQmlJS::AST::Statement *statement)
|
||||
{
|
||||
ExpressionStatement *expr = cast<ExpressionStatement *>(statement);
|
||||
|
||||
if (!statement || !expr->expression)
|
||||
return QVariant();
|
||||
|
||||
switch (expr->expression->kind) {
|
||||
case Node::Kind_StringLiteral:
|
||||
return cast<StringLiteral *>(expr->expression)->value.toString();
|
||||
case Node::Kind_NumericLiteral:
|
||||
return cast<NumericLiteral *>(expr->expression)->value;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVector<QQmlJSAnnotation> QQmlJSImportVisitor::parseAnnotations(QQmlJS::AST::UiAnnotationList *list)
|
||||
{
|
||||
|
||||
QVector<QQmlJSAnnotation> annotationList;
|
||||
|
||||
for (UiAnnotationList *item = list; item != nullptr; item = item->next) {
|
||||
UiAnnotation *annotation = item->annotation;
|
||||
|
||||
QString name;
|
||||
for (auto id = annotation->qualifiedTypeNameId; id; id = id->next)
|
||||
name += id->name.toString() + QLatin1Char('.');
|
||||
|
||||
name.chop(1);
|
||||
|
||||
|
||||
|
||||
QQmlJSAnnotation qqmljsAnnotation;
|
||||
|
||||
qqmljsAnnotation.name = name;
|
||||
|
||||
for (UiObjectMemberList *memberItem = annotation->initializer->members; memberItem != nullptr; memberItem = memberItem->next) {
|
||||
switch (memberItem->member->kind) {
|
||||
case Node::Kind_UiScriptBinding: {
|
||||
auto *scriptBinding = QQmlJS::AST::cast<UiScriptBinding*>(memberItem->member);
|
||||
QString bindingName;
|
||||
for (auto id = scriptBinding->qualifiedId; id; id = id->next)
|
||||
bindingName += id->name.toString() + QLatin1Char('.');
|
||||
|
||||
bindingName.chop(1);
|
||||
|
||||
qqmljsAnnotation.bindings[bindingName] = bindingToVariant(scriptBinding->statement);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// We ignore all the other information contained in the annotation
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
annotationList.append(qqmljsAnnotation);
|
||||
}
|
||||
|
||||
return annotationList;
|
||||
}
|
||||
|
||||
|
||||
bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition)
|
||||
{
|
||||
QString superType;
|
||||
|
@ -147,6 +210,8 @@ bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition)
|
|||
if (!m_exportedRootScope)
|
||||
m_exportedRootScope = m_currentScope;
|
||||
|
||||
m_currentScope->setAnnotations(parseAnnotations(definition->annotations));
|
||||
|
||||
QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports);
|
||||
return true;
|
||||
}
|
||||
|
@ -189,6 +254,8 @@ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember)
|
|||
prop.setIsWritable(!publicMember->isReadonlyMember);
|
||||
prop.setIsAlias(isAlias);
|
||||
prop.setType(m_rootScopeImports.value(prop.typeName()));
|
||||
prop.setAnnotations(parseAnnotations(publicMember->annotations));
|
||||
|
||||
m_currentScope->insertPropertyIdentifier(prop);
|
||||
if (publicMember->isRequired)
|
||||
m_currentScope->setPropertyLocallyRequired(prop.propertyName(), true);
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
// We mean it.
|
||||
|
||||
#include "qqmljsscope_p.h"
|
||||
#include "qqmljsannotation_p.h"
|
||||
|
||||
#include <private/qqmljsast_p.h>
|
||||
#include <private/qqmljsdiagnosticmessage_p.h>
|
||||
|
@ -126,6 +127,8 @@ protected:
|
|||
const QQmlJS::SourceLocation &location);
|
||||
void leaveEnvironment();
|
||||
|
||||
QVector<QQmlJSAnnotation> parseAnnotations(QQmlJS::AST::UiAnnotationList *list);
|
||||
|
||||
private:
|
||||
void importBaseModules();
|
||||
void resolveAliases();
|
||||
|
|
|
@ -43,6 +43,8 @@
|
|||
#include <QtCore/qstringlist.h>
|
||||
#include <QtCore/qsharedpointer.h>
|
||||
|
||||
#include "qqmljsannotation_p.h"
|
||||
|
||||
// MetaMethod and MetaProperty have both type names and actual QQmlJSScope types.
|
||||
// When parsing the information from the relevant QML or qmltypes files, we only
|
||||
// see the names and don't have a complete picture of the types, yet. In a second
|
||||
|
@ -238,6 +240,7 @@ class QQmlJSMetaProperty
|
|||
QString m_typeName;
|
||||
QString m_bindable;
|
||||
QWeakPointer<const QQmlJSScope> m_type;
|
||||
QVector<QQmlJSAnnotation> m_annotations;
|
||||
bool m_isList = false;
|
||||
bool m_isWritable = false;
|
||||
bool m_isPointer = false;
|
||||
|
@ -259,6 +262,9 @@ public:
|
|||
void setType(const QSharedPointer<const QQmlJSScope> &type) { m_type = type; }
|
||||
QSharedPointer<const QQmlJSScope> type() const { return m_type.toStrongRef(); }
|
||||
|
||||
void setAnnotations(const QList<QQmlJSAnnotation> &annotation) { m_annotations = std::move(annotation); }
|
||||
const QList<QQmlJSAnnotation> &annotations() const { return m_annotations; }
|
||||
|
||||
void setIsList(bool isList) { m_isList = isList; }
|
||||
bool isList() const { return m_isList; }
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
|
||||
#include "qqmljsmetatypes_p.h"
|
||||
#include "qdeferredpointer_p.h"
|
||||
#include "qqmljsannotation_p.h"
|
||||
|
||||
#include <QtQml/private/qqmljssourcelocation_p.h>
|
||||
|
||||
|
@ -186,6 +187,9 @@ public:
|
|||
bool hasEnumerationKey(const QString &name) const;
|
||||
QQmlJSMetaEnum enumeration(const QString &name) const;
|
||||
|
||||
void setAnnotations(const QList<QQmlJSAnnotation> &annotation) { m_annotations = std::move(annotation); }
|
||||
const QList<QQmlJSAnnotation> &annotations() const { return m_annotations; }
|
||||
|
||||
QString fileName() const { return m_fileName; }
|
||||
void setFileName(const QString &file) { m_fileName = file; }
|
||||
|
||||
|
@ -296,6 +300,7 @@ private:
|
|||
QHash<QString, QQmlJSMetaProperty> m_properties;
|
||||
QHash<QString, QQmlJSMetaEnum> m_enumerations;
|
||||
|
||||
QVector<QQmlJSAnnotation> m_annotations;
|
||||
QVector<QQmlJSScope::Ptr> m_childScopes;
|
||||
QQmlJSScope::WeakPtr m_parentScope;
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import QtQml
|
||||
|
||||
@Deprecated {}
|
||||
QtObject {}
|
|
@ -0,0 +1,4 @@
|
|||
import QtQml
|
||||
|
||||
@Deprecated { reason: "Test" }
|
||||
QtObject {}
|
|
@ -0,0 +1,10 @@
|
|||
import QtQml
|
||||
|
||||
QtObject {
|
||||
@Deprecated {}
|
||||
property int deprecated: 10
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log(deprecated);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import QtQml
|
||||
|
||||
QtObject {
|
||||
@Deprecated {
|
||||
reason: "Test"
|
||||
}
|
||||
property int deprecated: 10
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log(deprecated);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import QtQml
|
||||
|
||||
TypeDeprecated {}
|
|
@ -0,0 +1,3 @@
|
|||
import QtQml
|
||||
|
||||
TypeDeprecatedReason {}
|
|
@ -329,6 +329,22 @@ void TestQmllint::dirtyQmlCode_data()
|
|||
<< QStringLiteral("No type found for property \"bad\". This may be due to a missing "
|
||||
"import statement or incomplete qmltypes files.")
|
||||
<< QString();
|
||||
QTest::newRow("Deprecation (Property, with reason)")
|
||||
<< QStringLiteral("deprecatedPropertyReason.qml")
|
||||
<< QStringLiteral("Property \"deprecated\" is deprecated (Reason: Test)")
|
||||
<< QString();
|
||||
QTest::newRow("Deprecation (Property, no reason)")
|
||||
<< QStringLiteral("deprecatedProperty.qml")
|
||||
<< QStringLiteral("Property \"deprecated\" is deprecated")
|
||||
<< QString();
|
||||
QTest::newRow("Deprecation (Type, with reason)")
|
||||
<< QStringLiteral("deprecatedTypeReason.qml")
|
||||
<< QStringLiteral("Type \"TypeDeprecatedReason\" is deprecated (Reason: Test)")
|
||||
<< QString();
|
||||
QTest::newRow("Deprecation (Type, no reason)")
|
||||
<< QStringLiteral("deprecatedType.qml")
|
||||
<< QStringLiteral("Type \"TypeDeprecated\" is deprecated")
|
||||
<< QString();
|
||||
}
|
||||
|
||||
void TestQmllint::dirtyQmlCode()
|
||||
|
|
|
@ -347,6 +347,27 @@ bool CheckIdentifiers::operator()(
|
|||
|
||||
const auto property = qmlScope->property(memberAccessBase.m_name);
|
||||
if (!property.propertyName().isEmpty()) {
|
||||
for (const QQmlJSAnnotation &annotation : property.annotations()) {
|
||||
if (annotation.isDeprecation()) {
|
||||
QQQmlJSDeprecation deprecation = annotation.deprecation();
|
||||
|
||||
QString message = QStringLiteral("Property \"%1\" is deprecated")
|
||||
.arg(memberAccessBase.m_name);
|
||||
|
||||
if (!deprecation.reason.isEmpty())
|
||||
message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
|
||||
|
||||
message.append(QStringLiteral(" at %2:%3:%4")
|
||||
.arg(m_fileName)
|
||||
.arg(memberAccessBase.m_location.startLine)
|
||||
.arg(memberAccessBase.m_location.startColumn)
|
||||
);
|
||||
|
||||
m_colorOut->writePrefixedMessage(message, Warning);
|
||||
identifiersClean = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (memberAccessChain.isEmpty() || unknownBuiltins.contains(property.typeName()))
|
||||
continue;
|
||||
|
||||
|
|
|
@ -45,8 +45,30 @@
|
|||
|
||||
void FindWarningVisitor::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_errors.append({
|
||||
message,
|
||||
QtWarningMsg,
|
||||
originalScope->sourceLocation()
|
||||
});
|
||||
|
||||
m_visitFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (scopes.contains(scope)) {
|
||||
QString inheritenceCycle;
|
||||
for (const auto &seen: qAsConst(scopes)) {
|
||||
|
|
Loading…
Reference in New Issue