forked from qt-creator/qt-creator
Add QmlJS semantic checker.
* Add SemanticHighlighter to QmlJSTextEditor to update the semantic info in a background thread. * Add QmlJS::Check to run semantic checks on qml and js documents. * Add a check for incorrect property names. * Fix hoverhandler to show tool tips from extra selections over help tooltips.
This commit is contained in:
@@ -16,7 +16,8 @@ HEADERS += \
|
||||
$$PWD/qmljsdocument.h \
|
||||
$$PWD/qmljsscanner.h \
|
||||
$$PWD/qmljsinterpreter.h \
|
||||
$$PWD/qmljslink.h
|
||||
$$PWD/qmljslink.h \
|
||||
$$PWD/qmljscheck.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/qmljsbind.cpp \
|
||||
@@ -25,7 +26,8 @@ SOURCES += \
|
||||
$$PWD/qmljsscanner.cpp \
|
||||
$$PWD/qmljsinterpreter.cpp \
|
||||
$$PWD/qmljsmetatypesystem.cpp \
|
||||
$$PWD/qmljslink.cpp
|
||||
$$PWD/qmljslink.cpp \
|
||||
$$PWD/qmljscheck.cpp
|
||||
|
||||
contains(QT_CONFIG, declarative) {
|
||||
QT += declarative
|
||||
|
||||
140
src/libs/qmljs/qmljscheck.cpp
Normal file
140
src/libs/qmljs/qmljscheck.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
/**************************************************************************
|
||||
**
|
||||
** This file is part of Qt Creator
|
||||
**
|
||||
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
|
||||
**
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** Commercial Usage
|
||||
**
|
||||
** Licensees holding valid Qt Commercial licenses may use this file in
|
||||
** accordance with the Qt Commercial License Agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Nokia.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
**
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** If you are unsure which license is appropriate for your use, please
|
||||
** contact the sales department at http://qt.nokia.com/contact.
|
||||
**
|
||||
**************************************************************************/
|
||||
|
||||
#include "qmljscheck.h"
|
||||
#include "qmljsbind.h"
|
||||
#include "qmljsinterpreter.h"
|
||||
#include "parser/qmljsast_p.h"
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
using namespace QmlJS;
|
||||
using namespace QmlJS::AST;
|
||||
using namespace QmlJS::Interpreter;
|
||||
|
||||
Check::Check(Document::Ptr doc, const Snapshot &snapshot)
|
||||
: _doc(doc)
|
||||
, _snapshot(snapshot)
|
||||
, _context(&_engine)
|
||||
, _link(&_context, doc, snapshot)
|
||||
{
|
||||
}
|
||||
|
||||
Check::~Check()
|
||||
{
|
||||
}
|
||||
|
||||
QList<DiagnosticMessage> Check::operator()()
|
||||
{
|
||||
_messages.clear();
|
||||
Node::accept(_doc->ast(), this);
|
||||
return _messages;
|
||||
}
|
||||
|
||||
bool Check::visit(UiProgram *ast)
|
||||
{
|
||||
// build the initial scope chain
|
||||
if (ast->members && ast->members->member)
|
||||
_link.scopeChainAt(_doc, ast->members->member);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Check::visit(UiObjectDefinition *ast)
|
||||
{
|
||||
const ObjectValue *oldScopeObject = _context.qmlScopeObject();
|
||||
_context.setQmlScopeObject(_doc->bind()->findQmlObject(ast));
|
||||
|
||||
Node::accept(ast->initializer, this);
|
||||
|
||||
_context.setQmlScopeObject(oldScopeObject);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Check::visit(UiObjectBinding *ast)
|
||||
{
|
||||
checkScopeObjectMember(ast->qualifiedId);
|
||||
|
||||
const ObjectValue *oldScopeObject = _context.qmlScopeObject();
|
||||
_context.setQmlScopeObject(_doc->bind()->findQmlObject(ast));
|
||||
|
||||
Node::accept(ast->initializer, this);
|
||||
|
||||
_context.setQmlScopeObject(oldScopeObject);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Check::visit(UiScriptBinding *ast)
|
||||
{
|
||||
checkScopeObjectMember(ast->qualifiedId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Check::visit(UiArrayBinding *ast)
|
||||
{
|
||||
checkScopeObjectMember(ast->qualifiedId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Check::checkScopeObjectMember(const AST::UiQualifiedId *id)
|
||||
{
|
||||
const ObjectValue *scopeObject = _context.qmlScopeObject();
|
||||
|
||||
if (! id)
|
||||
return; // ### error?
|
||||
|
||||
const QString propertyName = id->name->asString();
|
||||
|
||||
if (propertyName == QLatin1String("id") && ! id->next)
|
||||
return;
|
||||
|
||||
// attached properties
|
||||
if (! propertyName.isEmpty() && propertyName[0].isUpper())
|
||||
scopeObject = _context.typeEnvironment(_doc.data());
|
||||
|
||||
const Value *value = scopeObject->lookupMember(propertyName, &_context);
|
||||
if (!value) {
|
||||
error(id->identifierToken,
|
||||
QString("'%1' is not a valid property name").arg(propertyName));
|
||||
}
|
||||
|
||||
// ### check for rest of qualifiedId
|
||||
}
|
||||
|
||||
void Check::error(const AST::SourceLocation &loc, const QString &message)
|
||||
{
|
||||
_messages.append(DiagnosticMessage(DiagnosticMessage::Error, loc, message));
|
||||
}
|
||||
|
||||
void Check::warning(const AST::SourceLocation &loc, const QString &message)
|
||||
{
|
||||
_messages.append(DiagnosticMessage(DiagnosticMessage::Warning, loc, message));
|
||||
}
|
||||
73
src/libs/qmljs/qmljscheck.h
Normal file
73
src/libs/qmljs/qmljscheck.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/**************************************************************************
|
||||
**
|
||||
** This file is part of Qt Creator
|
||||
**
|
||||
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
|
||||
**
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** Commercial Usage
|
||||
**
|
||||
** Licensees holding valid Qt Commercial licenses may use this file in
|
||||
** accordance with the Qt Commercial License Agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Nokia.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
**
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** If you are unsure which license is appropriate for your use, please
|
||||
** contact the sales department at http://qt.nokia.com/contact.
|
||||
**
|
||||
**************************************************************************/
|
||||
|
||||
#ifndef QMLJSCHECK_H
|
||||
#define QMLJSCHECK_H
|
||||
|
||||
#include <qmljs/qmljsdocument.h>
|
||||
#include <qmljs/qmljsinterpreter.h>
|
||||
#include <qmljs/qmljslink.h>
|
||||
#include <qmljs/parser/qmljsastvisitor_p.h>
|
||||
|
||||
namespace QmlJS {
|
||||
|
||||
class QMLJS_EXPORT Check: protected AST::Visitor
|
||||
{
|
||||
public:
|
||||
Check(Document::Ptr doc, const Snapshot &snapshot);
|
||||
virtual ~Check();
|
||||
|
||||
QList<DiagnosticMessage> operator()();
|
||||
|
||||
protected:
|
||||
virtual bool visit(AST::UiProgram *ast);
|
||||
virtual bool visit(AST::UiObjectDefinition *ast);
|
||||
virtual bool visit(AST::UiObjectBinding *ast);
|
||||
virtual bool visit(AST::UiScriptBinding *ast);
|
||||
virtual bool visit(AST::UiArrayBinding *ast);
|
||||
|
||||
private:
|
||||
void checkScopeObjectMember(const AST::UiQualifiedId *id);
|
||||
|
||||
void warning(const AST::SourceLocation &loc, const QString &message);
|
||||
void error(const AST::SourceLocation &loc, const QString &message);
|
||||
|
||||
Document::Ptr _doc;
|
||||
Snapshot _snapshot;
|
||||
|
||||
Interpreter::Engine _engine;
|
||||
Interpreter::Context _context;
|
||||
Link _link;
|
||||
|
||||
QList<DiagnosticMessage> _messages;
|
||||
};
|
||||
|
||||
} // namespace QmlJS
|
||||
|
||||
#endif // QMLJSCHECK_H
|
||||
@@ -216,6 +216,19 @@ void Snapshot::insert(const Document::Ptr &document)
|
||||
_documents.insert(document->fileName(), document);
|
||||
}
|
||||
|
||||
Document::Ptr Snapshot::documentFromSource(const QString &code,
|
||||
const QString &fileName) const
|
||||
{
|
||||
Document::Ptr newDoc = Document::create(fileName);
|
||||
|
||||
if (Document::Ptr thisDocument = document(fileName)) {
|
||||
newDoc->_documentRevision = thisDocument->_documentRevision;
|
||||
}
|
||||
|
||||
newDoc->setSource(code);
|
||||
return newDoc;
|
||||
}
|
||||
|
||||
QList<Document::Ptr> Snapshot::importedDocuments(const Document::Ptr &doc, const QString &importPath) const
|
||||
{
|
||||
// ### TODO: maybe we should add all imported documents in the parse Document::parse() method, regardless of whether they're in the path or not.
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
namespace QmlJS {
|
||||
|
||||
class Bind;
|
||||
class Snapshot;
|
||||
|
||||
class QMLJS_EXPORT Document
|
||||
{
|
||||
@@ -96,6 +97,9 @@ private:
|
||||
QString _path;
|
||||
QString _componentName;
|
||||
QString _source;
|
||||
|
||||
// for documentFromSource
|
||||
friend class Snapshot;
|
||||
};
|
||||
|
||||
class QMLJS_EXPORT Snapshot
|
||||
@@ -118,6 +122,9 @@ public:
|
||||
Document::Ptr document(const QString &fileName) const
|
||||
{ return _documents.value(fileName); }
|
||||
|
||||
Document::Ptr documentFromSource(const QString &code,
|
||||
const QString &fileName) const;
|
||||
|
||||
QList<Document::Ptr> importedDocuments(const Document::Ptr &doc, const QString &importPath) const;
|
||||
QMap<QString, Document::Ptr> componentsDefinedByImportedDocuments(const Document::Ptr &doc, const QString &importPath) const;
|
||||
};
|
||||
|
||||
@@ -686,7 +686,9 @@ void StringValue::accept(ValueVisitor *visitor) const
|
||||
|
||||
Context::Context(Engine *engine)
|
||||
: _engine(engine),
|
||||
_lookupMode(JSLookup)
|
||||
_lookupMode(JSLookup),
|
||||
_qmlScopeObjectIndex(-1),
|
||||
_qmlScopeObjectSet(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -739,6 +741,39 @@ void Context::pushScope(const ObjectValue *object)
|
||||
void Context::popScope()
|
||||
{
|
||||
_scopeChain.removeLast();
|
||||
if (_scopeChain.length() <= _qmlScopeObjectIndex)
|
||||
_qmlScopeObjectSet = false;
|
||||
}
|
||||
|
||||
// Marks this to be the location where a scope object can be inserted.
|
||||
void Context::markQmlScopeObject()
|
||||
{
|
||||
_qmlScopeObjectIndex = _scopeChain.length();
|
||||
}
|
||||
|
||||
// Sets or inserts the scope object if scopeObject != 0, removes it otherwise.
|
||||
void Context::setQmlScopeObject(const ObjectValue *scopeObject)
|
||||
{
|
||||
if (_qmlScopeObjectSet) {
|
||||
if (scopeObject == 0) {
|
||||
_scopeChain.removeAt(_qmlScopeObjectIndex);
|
||||
_qmlScopeObjectSet = false;
|
||||
} else {
|
||||
_scopeChain[_qmlScopeObjectIndex] = scopeObject;
|
||||
}
|
||||
} else if (scopeObject != 0 && _scopeChain.length() >= _qmlScopeObjectIndex) {
|
||||
_scopeChain.insert(_qmlScopeObjectIndex, scopeObject);
|
||||
_qmlScopeObjectSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the scope object, if set. Returns 0 otherwise.
|
||||
const ObjectValue *Context::qmlScopeObject() const
|
||||
{
|
||||
if (!_qmlScopeObjectSet)
|
||||
return 0;
|
||||
else
|
||||
return _scopeChain[_qmlScopeObjectIndex];
|
||||
}
|
||||
|
||||
const Value *Context::lookup(const QString &name)
|
||||
|
||||
@@ -241,6 +241,10 @@ public:
|
||||
void pushScope(const ObjectValue *object);
|
||||
void popScope();
|
||||
|
||||
void markQmlScopeObject();
|
||||
void setQmlScopeObject(const ObjectValue *scopeObject);
|
||||
const ObjectValue *qmlScopeObject() const;
|
||||
|
||||
const Value *lookup(const QString &name);
|
||||
const ObjectValue *lookupType(const Document *doc, AST::UiQualifiedId *qmlTypeName);
|
||||
|
||||
@@ -253,8 +257,10 @@ private:
|
||||
Engine *_engine;
|
||||
LookupMode _lookupMode;
|
||||
QHash<const ObjectValue *, Properties> _properties;
|
||||
ScopeChain _scopeChain;
|
||||
QHash<const Document *, const ObjectValue *> _typeEnvironments;
|
||||
ScopeChain _scopeChain;
|
||||
int _qmlScopeObjectIndex;
|
||||
bool _qmlScopeObjectSet;
|
||||
};
|
||||
|
||||
class QMLJS_EXPORT Reference: public Value
|
||||
|
||||
@@ -102,8 +102,11 @@ void Link::pushScopeChainForComponent(Document::Ptr doc, QStringList *linkedDocs
|
||||
if (bind->rootObjectValue())
|
||||
_context->pushScope(bind->rootObjectValue());
|
||||
|
||||
if (scopeObject && scopeObject != bind->rootObjectValue())
|
||||
_context->pushScope(scopeObject);
|
||||
if (scopeObject) {
|
||||
_context->markQmlScopeObject();
|
||||
if (scopeObject != bind->rootObjectValue())
|
||||
_context->setQmlScopeObject(scopeObject);
|
||||
}
|
||||
|
||||
const QStringList &includedScripts = bind->includedScripts();
|
||||
for (int index = includedScripts.size() - 1; index != -1; --index) {
|
||||
|
||||
Reference in New Issue
Block a user