forked from qt-creator/qt-creator
QmlJS: Move the exported-C++-type detection out of C++ code.
It now lives in qmljstools/qmljsfindexportedcpptypes, all in one place. Also ensures that the source code is available when a file is being scanned for QML exports. This will enable checking comments for annotations about the URI a plugin is usually imported as. Change-Id: I1da36d0678e0a8d34b171dbe0f6b5690d89eb18b Reviewed-on: http://codereview.qt.nokia.com/3392 Reviewed-by: Fawzi Mohamed <fawzi.mohamed@nokia.com>
This commit is contained in:
@@ -256,7 +256,8 @@ Document::Document(const QString &fileName)
|
||||
: _fileName(QDir::cleanPath(fileName)),
|
||||
_globalNamespace(0),
|
||||
_revision(0),
|
||||
_editorRevision(0)
|
||||
_editorRevision(0),
|
||||
_fastCheck(false)
|
||||
{
|
||||
_control = new Control();
|
||||
|
||||
@@ -574,8 +575,10 @@ void Document::check(CheckMode mode)
|
||||
|
||||
_globalNamespace = _control->newNamespace(0);
|
||||
Bind semantic(_translationUnit);
|
||||
if (mode == FastCheck)
|
||||
if (mode == FastCheck) {
|
||||
_fastCheck = true;
|
||||
semantic.setSkipFunctionBodies(true);
|
||||
}
|
||||
|
||||
if (! _translationUnit->ast())
|
||||
return; // nothing to do.
|
||||
@@ -589,253 +592,19 @@ void Document::check(CheckMode mode)
|
||||
}
|
||||
}
|
||||
|
||||
class FindExposedQmlTypes : protected ASTVisitor
|
||||
void Document::keepSourceAndAST()
|
||||
{
|
||||
Document *_doc;
|
||||
QList<Document::ExportedQmlType> _exportedTypes;
|
||||
CompoundStatementAST *_compound;
|
||||
ASTMatcher _matcher;
|
||||
ASTPatternBuilder _builder;
|
||||
Overview _overview;
|
||||
|
||||
public:
|
||||
FindExposedQmlTypes(Document *doc)
|
||||
: ASTVisitor(doc->translationUnit())
|
||||
, _doc(doc)
|
||||
, _compound(0)
|
||||
{}
|
||||
|
||||
QList<Document::ExportedQmlType> operator()()
|
||||
{
|
||||
_exportedTypes.clear();
|
||||
accept(translationUnit()->ast());
|
||||
return _exportedTypes;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool visit(CompoundStatementAST *ast)
|
||||
{
|
||||
CompoundStatementAST *old = _compound;
|
||||
_compound = ast;
|
||||
accept(ast->statement_list);
|
||||
_compound = old;
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool visit(CallAST *ast)
|
||||
{
|
||||
IdExpressionAST *idExp = ast->base_expression->asIdExpression();
|
||||
if (!idExp || !idExp->name)
|
||||
return false;
|
||||
TemplateIdAST *templateId = idExp->name->asTemplateId();
|
||||
if (!templateId || !templateId->identifier_token)
|
||||
return false;
|
||||
|
||||
// check the name
|
||||
const Identifier *templateIdentifier = translationUnit()->identifier(templateId->identifier_token);
|
||||
if (!templateIdentifier)
|
||||
return false;
|
||||
const QString callName = QString::fromUtf8(templateIdentifier->chars());
|
||||
if (callName != QLatin1String("qmlRegisterType"))
|
||||
return false;
|
||||
|
||||
// must have a single typeid template argument
|
||||
if (!templateId->template_argument_list || !templateId->template_argument_list->value
|
||||
|| templateId->template_argument_list->next)
|
||||
return false;
|
||||
TypeIdAST *typeId = templateId->template_argument_list->value->asTypeId();
|
||||
if (!typeId)
|
||||
return false;
|
||||
|
||||
// must have four arguments
|
||||
if (!ast->expression_list
|
||||
|| !ast->expression_list->value || !ast->expression_list->next
|
||||
|| !ast->expression_list->next->value || !ast->expression_list->next->next
|
||||
|| !ast->expression_list->next->next->value || !ast->expression_list->next->next->next
|
||||
|| !ast->expression_list->next->next->next->value
|
||||
|| ast->expression_list->next->next->next->next)
|
||||
return false;
|
||||
|
||||
// last argument must be a string literal
|
||||
const StringLiteral *nameLit = 0;
|
||||
if (StringLiteralAST *nameAst = ast->expression_list->next->next->next->value->asStringLiteral())
|
||||
nameLit = translationUnit()->stringLiteral(nameAst->literal_token);
|
||||
if (!nameLit) {
|
||||
// disable this warning for now, we don't want to encourage using string literals if they don't mean to
|
||||
// in the future, we will also accept annotations for the qmlRegisterType arguments in comments
|
||||
// translationUnit()->warning(ast->expression_list->next->next->next->value->firstToken(),
|
||||
// "The type will only be available in Qt Creator's QML editors when the type name is a string literal");
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the first argument is a string literal, things are easy
|
||||
QString packageName;
|
||||
if (StringLiteralAST *packageAst = ast->expression_list->value->asStringLiteral()) {
|
||||
const StringLiteral *packageLit = translationUnit()->stringLiteral(packageAst->literal_token);
|
||||
packageName = QString::fromUtf8(packageLit->chars(), packageLit->size());
|
||||
}
|
||||
// as a special case, allow an identifier package argument if there's a
|
||||
// Q_ASSERT(QLatin1String(uri) == QLatin1String("actual uri"));
|
||||
// in the enclosing compound statement
|
||||
IdExpressionAST *uriName = ast->expression_list->value->asIdExpression();
|
||||
if (packageName.isEmpty() && uriName && _compound) {
|
||||
for (StatementListAST *it = _compound->statement_list; it; it = it->next) {
|
||||
StatementAST *stmt = it->value;
|
||||
|
||||
packageName = nameOfUriAssert(stmt, uriName);
|
||||
if (!packageName.isEmpty())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// second and third argument must be integer literals
|
||||
const NumericLiteral *majorLit = 0;
|
||||
const NumericLiteral *minorLit = 0;
|
||||
if (NumericLiteralAST *majorAst = ast->expression_list->next->value->asNumericLiteral())
|
||||
majorLit = translationUnit()->numericLiteral(majorAst->literal_token);
|
||||
if (NumericLiteralAST *minorAst = ast->expression_list->next->next->value->asNumericLiteral())
|
||||
minorLit = translationUnit()->numericLiteral(minorAst->literal_token);
|
||||
|
||||
// build the descriptor
|
||||
Document::ExportedQmlType exportedType;
|
||||
exportedType.typeName = QString::fromUtf8(nameLit->chars(), nameLit->size());
|
||||
if (!packageName.isEmpty() && majorLit && minorLit && majorLit->isInt() && minorLit->isInt()) {
|
||||
exportedType.packageName = packageName;
|
||||
exportedType.majorVersion = QString::fromUtf8(majorLit->chars(), majorLit->size()).toInt();
|
||||
exportedType.minorVersion = QString::fromUtf8(minorLit->chars(), minorLit->size()).toInt();
|
||||
} else {
|
||||
// disable this warning, see above for details
|
||||
// translationUnit()->warning(ast->base_expression->firstToken(),
|
||||
// "The module will not be available in Qt Creator's QML editors because the uri and version numbers\n"
|
||||
// "cannot be determined by static analysis. The type will still be available globally.");
|
||||
exportedType.packageName = QLatin1String("<default>");
|
||||
}
|
||||
|
||||
// we want to do lookup later, so also store the surrounding scope
|
||||
unsigned line, column;
|
||||
translationUnit()->getTokenStartPosition(ast->firstToken(), &line, &column);
|
||||
exportedType.scope = _doc->scopeAt(line, column);
|
||||
|
||||
// and the expression
|
||||
const Token begin = translationUnit()->tokenAt(typeId->firstToken());
|
||||
const Token last = translationUnit()->tokenAt(typeId->lastToken() - 1);
|
||||
exportedType.typeExpression = _doc->source().mid(begin.begin(), last.end() - begin.begin());
|
||||
|
||||
_exportedTypes += exportedType;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
QString stringOf(AST *ast)
|
||||
{
|
||||
const Token begin = translationUnit()->tokenAt(ast->firstToken());
|
||||
const Token last = translationUnit()->tokenAt(ast->lastToken() - 1);
|
||||
return _doc->source().mid(begin.begin(), last.end() - begin.begin());
|
||||
}
|
||||
|
||||
ExpressionAST *skipStringCall(ExpressionAST *exp)
|
||||
{
|
||||
if (!exp)
|
||||
return 0;
|
||||
|
||||
IdExpressionAST *callName = _builder.IdExpression();
|
||||
CallAST *call = _builder.Call(callName);
|
||||
if (!exp->match(call, &_matcher))
|
||||
return exp;
|
||||
|
||||
const QString name = stringOf(callName);
|
||||
if (name != QLatin1String("QLatin1String")
|
||||
&& name != QLatin1String("QString"))
|
||||
return exp;
|
||||
|
||||
if (!call->expression_list || call->expression_list->next)
|
||||
return exp;
|
||||
|
||||
return call->expression_list->value;
|
||||
}
|
||||
|
||||
QString nameOfUriAssert(StatementAST *stmt, IdExpressionAST *uriName)
|
||||
{
|
||||
QString null;
|
||||
|
||||
IdExpressionAST *outerCallName = _builder.IdExpression();
|
||||
BinaryExpressionAST *binary = _builder.BinaryExpression();
|
||||
// assert(... == ...);
|
||||
ExpressionStatementAST *pattern = _builder.ExpressionStatement(
|
||||
_builder.Call(outerCallName, _builder.ExpressionList(
|
||||
binary)));
|
||||
|
||||
if (!stmt->match(pattern, &_matcher)) {
|
||||
outerCallName = _builder.IdExpression();
|
||||
binary = _builder.BinaryExpression();
|
||||
// the expansion of Q_ASSERT(...),
|
||||
// ((!(... == ...)) ? qt_assert(...) : ...);
|
||||
pattern = _builder.ExpressionStatement(
|
||||
_builder.NestedExpression(
|
||||
_builder.ConditionalExpression(
|
||||
_builder.NestedExpression(
|
||||
_builder.UnaryExpression(
|
||||
_builder.NestedExpression(
|
||||
binary))),
|
||||
_builder.Call(outerCallName))));
|
||||
|
||||
if (!stmt->match(pattern, &_matcher))
|
||||
return null;
|
||||
}
|
||||
|
||||
const QString outerCall = stringOf(outerCallName);
|
||||
if (outerCall != QLatin1String("qt_assert")
|
||||
&& outerCall != QLatin1String("assert")
|
||||
&& outerCall != QLatin1String("Q_ASSERT"))
|
||||
return null;
|
||||
|
||||
if (translationUnit()->tokenAt(binary->binary_op_token).kind() != T_EQUAL_EQUAL)
|
||||
return null;
|
||||
|
||||
ExpressionAST *lhsExp = skipStringCall(binary->left_expression);
|
||||
ExpressionAST *rhsExp = skipStringCall(binary->right_expression);
|
||||
if (!lhsExp || !rhsExp)
|
||||
return null;
|
||||
|
||||
StringLiteralAST *uriString = lhsExp->asStringLiteral();
|
||||
IdExpressionAST *uriArgName = lhsExp->asIdExpression();
|
||||
if (!uriString)
|
||||
uriString = rhsExp->asStringLiteral();
|
||||
if (!uriArgName)
|
||||
uriArgName = rhsExp->asIdExpression();
|
||||
if (!uriString || !uriArgName)
|
||||
return null;
|
||||
|
||||
if (stringOf(uriArgName) != stringOf(uriName))
|
||||
return null;
|
||||
|
||||
const StringLiteral *packageLit = translationUnit()->stringLiteral(uriString->literal_token);
|
||||
return QString::fromUtf8(packageLit->chars(), packageLit->size());
|
||||
}
|
||||
};
|
||||
|
||||
void Document::findExposedQmlTypes()
|
||||
{
|
||||
if (! _translationUnit->ast())
|
||||
return;
|
||||
|
||||
QByteArray qmlRegisterTypeToken("qmlRegisterType");
|
||||
if (_translationUnit->control()->findIdentifier(
|
||||
qmlRegisterTypeToken.constData(), qmlRegisterTypeToken.size())) {
|
||||
FindExposedQmlTypes finder(this);
|
||||
_exportedQmlTypes = finder();
|
||||
}
|
||||
_keepSourceAndASTCount.ref();
|
||||
}
|
||||
|
||||
void Document::releaseSource()
|
||||
void Document::releaseSourceAndAST()
|
||||
{
|
||||
if (!_keepSourceAndASTCount.deref()) {
|
||||
_source.clear();
|
||||
}
|
||||
|
||||
void Document::releaseTranslationUnit()
|
||||
{
|
||||
_translationUnit->release();
|
||||
if (_fastCheck)
|
||||
_control->squeeze();
|
||||
}
|
||||
}
|
||||
|
||||
Snapshot::Snapshot()
|
||||
|
@@ -40,6 +40,7 @@
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QAtomicInt>
|
||||
|
||||
namespace CPlusPlus {
|
||||
|
||||
@@ -125,11 +126,6 @@ public:
|
||||
|
||||
void check(CheckMode mode = FullCheck);
|
||||
|
||||
void findExposedQmlTypes();
|
||||
|
||||
void releaseSource();
|
||||
void releaseTranslationUnit();
|
||||
|
||||
static Ptr create(const QString &fileName);
|
||||
|
||||
class DiagnosticMessage
|
||||
@@ -320,18 +316,8 @@ public:
|
||||
const MacroUse *findMacroUseAt(unsigned offset) const;
|
||||
const UndefinedMacroUse *findUndefinedMacroUseAt(unsigned offset) const;
|
||||
|
||||
class ExportedQmlType {
|
||||
public:
|
||||
QString packageName;
|
||||
QString typeName;
|
||||
int majorVersion;
|
||||
int minorVersion;
|
||||
Scope *scope;
|
||||
QString typeExpression;
|
||||
};
|
||||
|
||||
QList<ExportedQmlType> exportedQmlTypes() const
|
||||
{ return _exportedQmlTypes; }
|
||||
void keepSourceAndAST();
|
||||
void releaseSourceAndAST();
|
||||
|
||||
private:
|
||||
QString _fileName;
|
||||
@@ -344,11 +330,12 @@ private:
|
||||
QList<Block> _skippedBlocks;
|
||||
QList<MacroUse> _macroUses;
|
||||
QList<UndefinedMacroUse> _undefinedMacroUses;
|
||||
QList<ExportedQmlType> _exportedQmlTypes;
|
||||
QByteArray _source;
|
||||
QDateTime _lastModified;
|
||||
QAtomicInt _keepSourceAndASTCount;
|
||||
unsigned _revision;
|
||||
unsigned _editorRevision;
|
||||
bool _fastCheck;
|
||||
|
||||
friend class Snapshot;
|
||||
};
|
||||
|
@@ -140,8 +140,6 @@ public:
|
||||
|
||||
virtual void findMacroUsages(const CPlusPlus::Macro ¯o) = 0;
|
||||
|
||||
virtual QList<LanguageUtils::FakeMetaObject::ConstPtr> exportedQmlObjects(const CPlusPlus::Document::Ptr &doc) const = 0;
|
||||
|
||||
Q_SIGNALS:
|
||||
void documentUpdated(CPlusPlus::Document::Ptr doc);
|
||||
void sourceFilesRefreshed(const QStringList &files);
|
||||
|
@@ -502,7 +502,9 @@ void Link::loadImplicitDefaultImports(Imports *imports)
|
||||
import.info = info;
|
||||
import.object = new ObjectValue(d->valueOwner);
|
||||
foreach (QmlObjectValue *object,
|
||||
d->valueOwner->cppQmlTypes().typesForImport(defaultPackage, ComponentVersion())) {
|
||||
d->valueOwner->cppQmlTypes().typesForImport(
|
||||
defaultPackage,
|
||||
ComponentVersion(ComponentVersion::MaxVersion, ComponentVersion::MaxVersion))) {
|
||||
import.object->setMember(object->className(), object);
|
||||
}
|
||||
d->importCache.insert(ImportCacheKey(info), import);
|
||||
|
@@ -290,15 +290,11 @@ public:
|
||||
void operator()()
|
||||
{
|
||||
_doc->check(_mode);
|
||||
_doc->findExposedQmlTypes();
|
||||
_doc->releaseSource();
|
||||
_doc->releaseTranslationUnit();
|
||||
|
||||
if (_mode == Document::FastCheck)
|
||||
_doc->control()->squeeze();
|
||||
|
||||
if (_modelManager)
|
||||
_modelManager->emitDocumentUpdated(_doc); // ### TODO: compress
|
||||
|
||||
_doc->releaseSourceAndAST();
|
||||
}
|
||||
};
|
||||
} // end of anonymous namespace
|
||||
@@ -590,6 +586,7 @@ void CppPreprocessor::sourceNeeded(QString &fileName, IncludeType type, unsigned
|
||||
const QByteArray preprocessedCode = preprocess(fileName, contents);
|
||||
|
||||
doc->setSource(preprocessedCode);
|
||||
doc->keepSourceAndAST();
|
||||
doc->tokenize();
|
||||
|
||||
snapshot.insert(doc);
|
||||
@@ -1284,166 +1281,6 @@ void CppModelManager::GC()
|
||||
protectSnapshot.unlock();
|
||||
}
|
||||
|
||||
static FullySpecifiedType stripPointerAndReference(const FullySpecifiedType &type)
|
||||
{
|
||||
Type *t = type.type();
|
||||
while (t) {
|
||||
if (PointerType *ptr = t->asPointerType())
|
||||
t = ptr->elementType().type();
|
||||
else if (ReferenceType *ref = t->asReferenceType())
|
||||
t = ref->elementType().type();
|
||||
else
|
||||
break;
|
||||
}
|
||||
return FullySpecifiedType(t);
|
||||
}
|
||||
|
||||
static QString toQmlType(const FullySpecifiedType &type)
|
||||
{
|
||||
Overview overview;
|
||||
QString result = overview(stripPointerAndReference(type));
|
||||
if (result == QLatin1String("QString"))
|
||||
result = QLatin1String("string");
|
||||
return result;
|
||||
}
|
||||
|
||||
static Class *lookupClass(const QString &expression, Scope *scope, TypeOfExpression &typeOf)
|
||||
{
|
||||
QList<LookupItem> results = typeOf(expression, scope);
|
||||
Class *klass = 0;
|
||||
foreach (const LookupItem &item, results) {
|
||||
if (item.declaration()) {
|
||||
klass = item.declaration()->asClass();
|
||||
if (klass)
|
||||
return klass;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void populate(LanguageUtils::FakeMetaObject::Ptr fmo, Class *klass,
|
||||
QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> *classes,
|
||||
TypeOfExpression &typeOf)
|
||||
{
|
||||
using namespace LanguageUtils;
|
||||
|
||||
Overview namePrinter;
|
||||
|
||||
classes->insert(klass, fmo);
|
||||
|
||||
for (unsigned i = 0; i < klass->memberCount(); ++i) {
|
||||
Symbol *member = klass->memberAt(i);
|
||||
if (!member->name())
|
||||
continue;
|
||||
if (Function *func = member->type()->asFunctionType()) {
|
||||
if (!func->isSlot() && !func->isInvokable() && !func->isSignal())
|
||||
continue;
|
||||
FakeMetaMethod method(namePrinter(func->name()), toQmlType(func->returnType()));
|
||||
if (func->isSignal())
|
||||
method.setMethodType(FakeMetaMethod::Signal);
|
||||
else
|
||||
method.setMethodType(FakeMetaMethod::Slot);
|
||||
for (unsigned a = 0; a < func->argumentCount(); ++a) {
|
||||
Symbol *arg = func->argumentAt(a);
|
||||
QString name(CppModelManager::tr("unnamed"));
|
||||
if (arg->name())
|
||||
name = namePrinter(arg->name());
|
||||
method.addParameter(name, toQmlType(arg->type()));
|
||||
}
|
||||
fmo->addMethod(method);
|
||||
}
|
||||
if (QtPropertyDeclaration *propDecl = member->asQtPropertyDeclaration()) {
|
||||
const FullySpecifiedType &type = propDecl->type();
|
||||
const bool isList = false; // ### fixme
|
||||
const bool isWritable = propDecl->flags() & QtPropertyDeclaration::WriteFunction;
|
||||
const bool isPointer = type.type() && type.type()->isPointerType();
|
||||
const int revision = 0; // ### fixme
|
||||
FakeMetaProperty property(
|
||||
namePrinter(propDecl->name()),
|
||||
toQmlType(type),
|
||||
isList, isWritable, isPointer,
|
||||
revision);
|
||||
fmo->addProperty(property);
|
||||
}
|
||||
if (QtEnum *qtEnum = member->asQtEnum()) {
|
||||
// find the matching enum
|
||||
Enum *e = 0;
|
||||
QList<LookupItem> result = typeOf(namePrinter(qtEnum->name()), klass);
|
||||
foreach (const LookupItem &item, result) {
|
||||
if (item.declaration()) {
|
||||
e = item.declaration()->asEnum();
|
||||
if (e)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!e)
|
||||
continue;
|
||||
|
||||
FakeMetaEnum metaEnum(namePrinter(e->name()));
|
||||
for (unsigned j = 0; j < e->memberCount(); ++j) {
|
||||
Symbol *enumMember = e->memberAt(j);
|
||||
if (!enumMember->name())
|
||||
continue;
|
||||
metaEnum.addKey(namePrinter(enumMember->name()), 0);
|
||||
}
|
||||
fmo->addEnum(metaEnum);
|
||||
}
|
||||
}
|
||||
|
||||
// only single inheritance is supported
|
||||
if (klass->baseClassCount() > 0) {
|
||||
BaseClass *base = klass->baseClassAt(0);
|
||||
if (!base->name())
|
||||
return;
|
||||
|
||||
const QString baseClassName = namePrinter(base->name());
|
||||
fmo->setSuperclassName(baseClassName);
|
||||
|
||||
Class *baseClass = lookupClass(baseClassName, klass, typeOf);
|
||||
if (!baseClass)
|
||||
return;
|
||||
|
||||
FakeMetaObject::Ptr baseFmo = classes->value(baseClass);
|
||||
if (!baseFmo) {
|
||||
baseFmo = FakeMetaObject::Ptr(new FakeMetaObject);
|
||||
populate(baseFmo, baseClass, classes, typeOf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QList<LanguageUtils::FakeMetaObject::ConstPtr> CppModelManager::exportedQmlObjects(const Document::Ptr &doc) const
|
||||
{
|
||||
using namespace LanguageUtils;
|
||||
QList<FakeMetaObject::ConstPtr> exportedObjects;
|
||||
QHash<Class *, FakeMetaObject::Ptr> classes;
|
||||
|
||||
const QList<CPlusPlus::Document::ExportedQmlType> exported = doc->exportedQmlTypes();
|
||||
if (exported.isEmpty())
|
||||
return exportedObjects;
|
||||
|
||||
TypeOfExpression typeOf;
|
||||
const Snapshot currentSnapshot = snapshot();
|
||||
typeOf.init(doc, currentSnapshot);
|
||||
foreach (const Document::ExportedQmlType &exportedType, exported) {
|
||||
FakeMetaObject::Ptr fmo(new FakeMetaObject);
|
||||
fmo->addExport(exportedType.typeName, exportedType.packageName,
|
||||
ComponentVersion(exportedType.majorVersion, exportedType.minorVersion));
|
||||
exportedObjects += fmo;
|
||||
|
||||
Class *klass = lookupClass(exportedType.typeExpression, exportedType.scope, typeOf);
|
||||
if (!klass)
|
||||
continue;
|
||||
|
||||
// add the no-package export, so the cpp name can be used in properties
|
||||
Overview overview;
|
||||
fmo->addExport(overview(klass->name()), QString(), ComponentVersion());
|
||||
|
||||
populate(fmo, klass, &classes, typeOf);
|
||||
}
|
||||
|
||||
return exportedObjects;
|
||||
}
|
||||
|
||||
void CppModelManager::finishedRefreshingSourceFiles(const QStringList &files)
|
||||
{
|
||||
emit sourceFilesRefreshed(files);
|
||||
|
@@ -128,8 +128,6 @@ public:
|
||||
|
||||
virtual void findMacroUsages(const CPlusPlus::Macro ¯o);
|
||||
|
||||
virtual QList<LanguageUtils::FakeMetaObject::ConstPtr> exportedQmlObjects(const CPlusPlus::Document::Ptr &doc) const;
|
||||
|
||||
void finishedRefreshingSourceFiles(const QStringList &files);
|
||||
|
||||
Q_SIGNALS:
|
||||
|
486
src/plugins/qmljstools/qmljsfindexportedcpptypes.cpp
Normal file
486
src/plugins/qmljstools/qmljsfindexportedcpptypes.cpp
Normal file
@@ -0,0 +1,486 @@
|
||||
/**************************************************************************
|
||||
**
|
||||
** This file is part of Qt Creator
|
||||
**
|
||||
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
**
|
||||
** Contact: Nokia Corporation (info@qt.nokia.com)
|
||||
**
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
** In addition, as a special exception, Nokia gives you certain additional
|
||||
** rights. These rights are described in the Nokia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** Other Usage
|
||||
**
|
||||
** Alternatively, this file may be used in accordance with the terms and
|
||||
** conditions contained in a signed written agreement between you and Nokia.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please contact
|
||||
** Nokia at info@qt.nokia.com.
|
||||
**
|
||||
**************************************************************************/
|
||||
|
||||
#include "qmljsfindexportedcpptypes.h"
|
||||
|
||||
#include <qmljs/qmljsinterpreter.h>
|
||||
#include <cplusplus/AST.h>
|
||||
#include <cplusplus/TranslationUnit.h>
|
||||
#include <cplusplus/ASTVisitor.h>
|
||||
#include <cplusplus/ASTMatcher.h>
|
||||
#include <cplusplus/ASTPatternBuilder.h>
|
||||
#include <cplusplus/Overview.h>
|
||||
#include <cplusplus/TypeOfExpression.h>
|
||||
#include <cplusplus/Names.h>
|
||||
#include <cplusplus/Literals.h>
|
||||
#include <cplusplus/CoreTypes.h>
|
||||
#include <cplusplus/Symbols.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
using namespace QmlJSTools;
|
||||
|
||||
namespace {
|
||||
using namespace CPlusPlus;
|
||||
|
||||
class ExportedQmlType {
|
||||
public:
|
||||
QString packageName;
|
||||
QString typeName;
|
||||
LanguageUtils::ComponentVersion version;
|
||||
Scope *scope;
|
||||
QString typeExpression;
|
||||
};
|
||||
|
||||
class FindExportsVisitor : protected ASTVisitor
|
||||
{
|
||||
CPlusPlus::Document::Ptr _doc;
|
||||
QList<ExportedQmlType> _exportedTypes;
|
||||
CompoundStatementAST *_compound;
|
||||
ASTMatcher _matcher;
|
||||
ASTPatternBuilder _builder;
|
||||
Overview _overview;
|
||||
|
||||
public:
|
||||
FindExportsVisitor(CPlusPlus::Document::Ptr doc)
|
||||
: ASTVisitor(doc->translationUnit())
|
||||
, _doc(doc)
|
||||
, _compound(0)
|
||||
{}
|
||||
|
||||
QList<ExportedQmlType> operator()()
|
||||
{
|
||||
_exportedTypes.clear();
|
||||
accept(translationUnit()->ast());
|
||||
return _exportedTypes;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool visit(CompoundStatementAST *ast)
|
||||
{
|
||||
CompoundStatementAST *old = _compound;
|
||||
_compound = ast;
|
||||
accept(ast->statement_list);
|
||||
_compound = old;
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool visit(CallAST *ast)
|
||||
{
|
||||
IdExpressionAST *idExp = ast->base_expression->asIdExpression();
|
||||
if (!idExp || !idExp->name)
|
||||
return false;
|
||||
TemplateIdAST *templateId = idExp->name->asTemplateId();
|
||||
if (!templateId || !templateId->identifier_token)
|
||||
return false;
|
||||
|
||||
// check the name
|
||||
const Identifier *templateIdentifier = translationUnit()->identifier(templateId->identifier_token);
|
||||
if (!templateIdentifier)
|
||||
return false;
|
||||
const QString callName = QString::fromUtf8(templateIdentifier->chars());
|
||||
if (callName != QLatin1String("qmlRegisterType"))
|
||||
return false;
|
||||
|
||||
// must have a single typeid template argument
|
||||
if (!templateId->template_argument_list || !templateId->template_argument_list->value
|
||||
|| templateId->template_argument_list->next)
|
||||
return false;
|
||||
TypeIdAST *typeId = templateId->template_argument_list->value->asTypeId();
|
||||
if (!typeId)
|
||||
return false;
|
||||
|
||||
// must have four arguments
|
||||
if (!ast->expression_list
|
||||
|| !ast->expression_list->value || !ast->expression_list->next
|
||||
|| !ast->expression_list->next->value || !ast->expression_list->next->next
|
||||
|| !ast->expression_list->next->next->value || !ast->expression_list->next->next->next
|
||||
|| !ast->expression_list->next->next->next->value
|
||||
|| ast->expression_list->next->next->next->next)
|
||||
return false;
|
||||
|
||||
// last argument must be a string literal
|
||||
const StringLiteral *nameLit = 0;
|
||||
if (StringLiteralAST *nameAst = ast->expression_list->next->next->next->value->asStringLiteral())
|
||||
nameLit = translationUnit()->stringLiteral(nameAst->literal_token);
|
||||
if (!nameLit) {
|
||||
// disable this warning for now, we don't want to encourage using string literals if they don't mean to
|
||||
// in the future, we will also accept annotations for the qmlRegisterType arguments in comments
|
||||
// translationUnit()->warning(ast->expression_list->next->next->next->value->firstToken(),
|
||||
// "The type will only be available in Qt Creator's QML editors when the type name is a string literal");
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the first argument is a string literal, things are easy
|
||||
QString packageName;
|
||||
if (StringLiteralAST *packageAst = ast->expression_list->value->asStringLiteral()) {
|
||||
const StringLiteral *packageLit = translationUnit()->stringLiteral(packageAst->literal_token);
|
||||
packageName = QString::fromUtf8(packageLit->chars(), packageLit->size());
|
||||
}
|
||||
// as a special case, allow an identifier package argument if there's a
|
||||
// Q_ASSERT(QLatin1String(uri) == QLatin1String("actual uri"));
|
||||
// in the enclosing compound statement
|
||||
IdExpressionAST *uriName = ast->expression_list->value->asIdExpression();
|
||||
if (packageName.isEmpty() && uriName && _compound) {
|
||||
for (StatementListAST *it = _compound->statement_list; it; it = it->next) {
|
||||
StatementAST *stmt = it->value;
|
||||
|
||||
packageName = nameOfUriAssert(stmt, uriName);
|
||||
if (!packageName.isEmpty())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// second and third argument must be integer literals
|
||||
const NumericLiteral *majorLit = 0;
|
||||
const NumericLiteral *minorLit = 0;
|
||||
if (NumericLiteralAST *majorAst = ast->expression_list->next->value->asNumericLiteral())
|
||||
majorLit = translationUnit()->numericLiteral(majorAst->literal_token);
|
||||
if (NumericLiteralAST *minorAst = ast->expression_list->next->next->value->asNumericLiteral())
|
||||
minorLit = translationUnit()->numericLiteral(minorAst->literal_token);
|
||||
|
||||
// build the descriptor
|
||||
ExportedQmlType exportedType;
|
||||
exportedType.typeName = QString::fromUtf8(nameLit->chars(), nameLit->size());
|
||||
if (!packageName.isEmpty() && majorLit && minorLit && majorLit->isInt() && minorLit->isInt()) {
|
||||
exportedType.packageName = packageName;
|
||||
exportedType.version = LanguageUtils::ComponentVersion(
|
||||
QString::fromUtf8(majorLit->chars(), majorLit->size()).toInt(),
|
||||
QString::fromUtf8(minorLit->chars(), minorLit->size()).toInt());
|
||||
} else {
|
||||
// disable this warning, see above for details
|
||||
// translationUnit()->warning(ast->base_expression->firstToken(),
|
||||
// "The module will not be available in Qt Creator's QML editors because the uri and version numbers\n"
|
||||
// "cannot be determined by static analysis. The type will still be available globally.");
|
||||
exportedType.packageName = QmlJS::CppQmlTypes::defaultPackage;
|
||||
}
|
||||
|
||||
// we want to do lookup later, so also store the surrounding scope
|
||||
unsigned line, column;
|
||||
translationUnit()->getTokenStartPosition(ast->firstToken(), &line, &column);
|
||||
exportedType.scope = _doc->scopeAt(line, column);
|
||||
|
||||
// and the expression
|
||||
const Token begin = translationUnit()->tokenAt(typeId->firstToken());
|
||||
const Token last = translationUnit()->tokenAt(typeId->lastToken() - 1);
|
||||
exportedType.typeExpression = _doc->source().mid(begin.begin(), last.end() - begin.begin());
|
||||
|
||||
_exportedTypes += exportedType;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
QString stringOf(AST *ast)
|
||||
{
|
||||
const Token begin = translationUnit()->tokenAt(ast->firstToken());
|
||||
const Token last = translationUnit()->tokenAt(ast->lastToken() - 1);
|
||||
return _doc->source().mid(begin.begin(), last.end() - begin.begin());
|
||||
}
|
||||
|
||||
ExpressionAST *skipStringCall(ExpressionAST *exp)
|
||||
{
|
||||
if (!exp)
|
||||
return 0;
|
||||
|
||||
IdExpressionAST *callName = _builder.IdExpression();
|
||||
CallAST *call = _builder.Call(callName);
|
||||
if (!exp->match(call, &_matcher))
|
||||
return exp;
|
||||
|
||||
const QString name = stringOf(callName);
|
||||
if (name != QLatin1String("QLatin1String")
|
||||
&& name != QLatin1String("QString"))
|
||||
return exp;
|
||||
|
||||
if (!call->expression_list || call->expression_list->next)
|
||||
return exp;
|
||||
|
||||
return call->expression_list->value;
|
||||
}
|
||||
|
||||
QString nameOfUriAssert(StatementAST *stmt, IdExpressionAST *uriName)
|
||||
{
|
||||
QString null;
|
||||
|
||||
IdExpressionAST *outerCallName = _builder.IdExpression();
|
||||
BinaryExpressionAST *binary = _builder.BinaryExpression();
|
||||
// assert(... == ...);
|
||||
ExpressionStatementAST *pattern = _builder.ExpressionStatement(
|
||||
_builder.Call(outerCallName, _builder.ExpressionList(
|
||||
binary)));
|
||||
|
||||
if (!stmt->match(pattern, &_matcher)) {
|
||||
outerCallName = _builder.IdExpression();
|
||||
binary = _builder.BinaryExpression();
|
||||
// the expansion of Q_ASSERT(...),
|
||||
// ((!(... == ...)) ? qt_assert(...) : ...);
|
||||
pattern = _builder.ExpressionStatement(
|
||||
_builder.NestedExpression(
|
||||
_builder.ConditionalExpression(
|
||||
_builder.NestedExpression(
|
||||
_builder.UnaryExpression(
|
||||
_builder.NestedExpression(
|
||||
binary))),
|
||||
_builder.Call(outerCallName))));
|
||||
|
||||
if (!stmt->match(pattern, &_matcher))
|
||||
return null;
|
||||
}
|
||||
|
||||
const QString outerCall = stringOf(outerCallName);
|
||||
if (outerCall != QLatin1String("qt_assert")
|
||||
&& outerCall != QLatin1String("assert")
|
||||
&& outerCall != QLatin1String("Q_ASSERT"))
|
||||
return null;
|
||||
|
||||
if (translationUnit()->tokenAt(binary->binary_op_token).kind() != T_EQUAL_EQUAL)
|
||||
return null;
|
||||
|
||||
ExpressionAST *lhsExp = skipStringCall(binary->left_expression);
|
||||
ExpressionAST *rhsExp = skipStringCall(binary->right_expression);
|
||||
if (!lhsExp || !rhsExp)
|
||||
return null;
|
||||
|
||||
StringLiteralAST *uriString = lhsExp->asStringLiteral();
|
||||
IdExpressionAST *uriArgName = lhsExp->asIdExpression();
|
||||
if (!uriString)
|
||||
uriString = rhsExp->asStringLiteral();
|
||||
if (!uriArgName)
|
||||
uriArgName = rhsExp->asIdExpression();
|
||||
if (!uriString || !uriArgName)
|
||||
return null;
|
||||
|
||||
if (stringOf(uriArgName) != stringOf(uriName))
|
||||
return null;
|
||||
|
||||
const StringLiteral *packageLit = translationUnit()->stringLiteral(uriString->literal_token);
|
||||
return QString::fromUtf8(packageLit->chars(), packageLit->size());
|
||||
}
|
||||
};
|
||||
|
||||
static FullySpecifiedType stripPointerAndReference(const FullySpecifiedType &type)
|
||||
{
|
||||
Type *t = type.type();
|
||||
while (t) {
|
||||
if (PointerType *ptr = t->asPointerType())
|
||||
t = ptr->elementType().type();
|
||||
else if (ReferenceType *ref = t->asReferenceType())
|
||||
t = ref->elementType().type();
|
||||
else
|
||||
break;
|
||||
}
|
||||
return FullySpecifiedType(t);
|
||||
}
|
||||
|
||||
static QString toQmlType(const FullySpecifiedType &type)
|
||||
{
|
||||
Overview overview;
|
||||
QString result = overview(stripPointerAndReference(type));
|
||||
if (result == QLatin1String("QString"))
|
||||
result = QLatin1String("string");
|
||||
return result;
|
||||
}
|
||||
|
||||
static Class *lookupClass(const QString &expression, Scope *scope, TypeOfExpression &typeOf)
|
||||
{
|
||||
QList<LookupItem> results = typeOf(expression, scope);
|
||||
Class *klass = 0;
|
||||
foreach (const LookupItem &item, results) {
|
||||
if (item.declaration()) {
|
||||
klass = item.declaration()->asClass();
|
||||
if (klass)
|
||||
return klass;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void populate(LanguageUtils::FakeMetaObject::Ptr fmo, Class *klass,
|
||||
QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> *classes,
|
||||
TypeOfExpression &typeOf)
|
||||
{
|
||||
using namespace LanguageUtils;
|
||||
|
||||
Overview namePrinter;
|
||||
|
||||
classes->insert(klass, fmo);
|
||||
|
||||
for (unsigned i = 0; i < klass->memberCount(); ++i) {
|
||||
Symbol *member = klass->memberAt(i);
|
||||
if (!member->name())
|
||||
continue;
|
||||
if (Function *func = member->type()->asFunctionType()) {
|
||||
if (!func->isSlot() && !func->isInvokable() && !func->isSignal())
|
||||
continue;
|
||||
FakeMetaMethod method(namePrinter(func->name()), toQmlType(func->returnType()));
|
||||
if (func->isSignal())
|
||||
method.setMethodType(FakeMetaMethod::Signal);
|
||||
else
|
||||
method.setMethodType(FakeMetaMethod::Slot);
|
||||
for (unsigned a = 0; a < func->argumentCount(); ++a) {
|
||||
Symbol *arg = func->argumentAt(a);
|
||||
QString name;
|
||||
if (arg->name())
|
||||
name = namePrinter(arg->name());
|
||||
method.addParameter(name, toQmlType(arg->type()));
|
||||
}
|
||||
fmo->addMethod(method);
|
||||
}
|
||||
if (QtPropertyDeclaration *propDecl = member->asQtPropertyDeclaration()) {
|
||||
const FullySpecifiedType &type = propDecl->type();
|
||||
const bool isList = false; // ### fixme
|
||||
const bool isWritable = propDecl->flags() & QtPropertyDeclaration::WriteFunction;
|
||||
const bool isPointer = type.type() && type.type()->isPointerType();
|
||||
const int revision = 0; // ### fixme
|
||||
FakeMetaProperty property(
|
||||
namePrinter(propDecl->name()),
|
||||
toQmlType(type),
|
||||
isList, isWritable, isPointer,
|
||||
revision);
|
||||
fmo->addProperty(property);
|
||||
}
|
||||
if (QtEnum *qtEnum = member->asQtEnum()) {
|
||||
// find the matching enum
|
||||
Enum *e = 0;
|
||||
QList<LookupItem> result = typeOf(namePrinter(qtEnum->name()), klass);
|
||||
foreach (const LookupItem &item, result) {
|
||||
if (item.declaration()) {
|
||||
e = item.declaration()->asEnum();
|
||||
if (e)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!e)
|
||||
continue;
|
||||
|
||||
FakeMetaEnum metaEnum(namePrinter(e->name()));
|
||||
for (unsigned j = 0; j < e->memberCount(); ++j) {
|
||||
Symbol *enumMember = e->memberAt(j);
|
||||
if (!enumMember->name())
|
||||
continue;
|
||||
metaEnum.addKey(namePrinter(enumMember->name()), 0);
|
||||
}
|
||||
fmo->addEnum(metaEnum);
|
||||
}
|
||||
}
|
||||
|
||||
// only single inheritance is supported
|
||||
if (klass->baseClassCount() > 0) {
|
||||
BaseClass *base = klass->baseClassAt(0);
|
||||
if (!base->name())
|
||||
return;
|
||||
|
||||
const QString baseClassName = namePrinter(base->name());
|
||||
fmo->setSuperclassName(baseClassName);
|
||||
|
||||
Class *baseClass = lookupClass(baseClassName, klass, typeOf);
|
||||
if (!baseClass)
|
||||
return;
|
||||
|
||||
FakeMetaObject::Ptr baseFmo = classes->value(baseClass);
|
||||
if (!baseFmo) {
|
||||
baseFmo = FakeMetaObject::Ptr(new FakeMetaObject);
|
||||
populate(baseFmo, baseClass, classes, typeOf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QList<LanguageUtils::FakeMetaObject::ConstPtr> exportedQmlObjects(
|
||||
const Document::Ptr &doc,
|
||||
const Snapshot &snapshot,
|
||||
const QList<ExportedQmlType> &exportedTypes)
|
||||
{
|
||||
using namespace LanguageUtils;
|
||||
QList<FakeMetaObject::ConstPtr> exportedObjects;
|
||||
QHash<Class *, FakeMetaObject::Ptr> classes;
|
||||
|
||||
if (exportedTypes.isEmpty())
|
||||
return exportedObjects;
|
||||
|
||||
TypeOfExpression typeOf;
|
||||
typeOf.init(doc, snapshot);
|
||||
foreach (const ExportedQmlType &exportedType, exportedTypes) {
|
||||
FakeMetaObject::Ptr fmo(new FakeMetaObject);
|
||||
fmo->addExport(exportedType.typeName,
|
||||
exportedType.packageName,
|
||||
exportedType.version);
|
||||
exportedObjects += fmo;
|
||||
|
||||
Class *klass = lookupClass(exportedType.typeExpression, exportedType.scope, typeOf);
|
||||
if (!klass)
|
||||
continue;
|
||||
|
||||
// add the no-package export, so the cpp name can be used in properties
|
||||
Overview overview;
|
||||
fmo->addExport(overview(klass->name()), QmlJS::CppQmlTypes::cppPackage, ComponentVersion());
|
||||
|
||||
populate(fmo, klass, &classes, typeOf);
|
||||
}
|
||||
|
||||
return exportedObjects;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
FindExportedCppTypes::FindExportedCppTypes(const CPlusPlus::Snapshot &snapshot)
|
||||
: m_snapshot(snapshot)
|
||||
{
|
||||
}
|
||||
|
||||
QList<LanguageUtils::FakeMetaObject::ConstPtr> FindExportedCppTypes::operator()(const CPlusPlus::Document::Ptr &document)
|
||||
{
|
||||
QList<LanguageUtils::FakeMetaObject::ConstPtr> noResults;
|
||||
if (document->source().isEmpty()
|
||||
|| !document->translationUnit()->ast())
|
||||
return noResults;
|
||||
|
||||
FindExportsVisitor finder(document);
|
||||
QList<ExportedQmlType> exports = finder();
|
||||
if (exports.isEmpty())
|
||||
return noResults;
|
||||
|
||||
return exportedQmlObjects(document, m_snapshot, exports);
|
||||
}
|
||||
|
||||
bool FindExportedCppTypes::maybeExportsTypes(const Document::Ptr &document)
|
||||
{
|
||||
if (!document->control())
|
||||
return false;
|
||||
const QByteArray qmlRegisterTypeToken("qmlRegisterType");
|
||||
if (document->control()->findIdentifier(
|
||||
qmlRegisterTypeToken.constData(), qmlRegisterTypeToken.size())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
55
src/plugins/qmljstools/qmljsfindexportedcpptypes.h
Normal file
55
src/plugins/qmljstools/qmljsfindexportedcpptypes.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/**************************************************************************
|
||||
**
|
||||
** This file is part of Qt Creator
|
||||
**
|
||||
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
**
|
||||
** Contact: Nokia Corporation (info@qt.nokia.com)
|
||||
**
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
** In addition, as a special exception, Nokia gives you certain additional
|
||||
** rights. These rights are described in the Nokia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** Other Usage
|
||||
**
|
||||
** Alternatively, this file may be used in accordance with the terms and
|
||||
** conditions contained in a signed written agreement between you and Nokia.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please contact
|
||||
** Nokia at info@qt.nokia.com.
|
||||
**
|
||||
**************************************************************************/
|
||||
|
||||
#ifndef QMLJSTOOLS_QMLJSFINDEXPORTEDCPPTYPES_H
|
||||
#define QMLJSTOOLS_QMLJSFINDEXPORTEDCPPTYPES_H
|
||||
|
||||
#include <cplusplus/CppDocument.h>
|
||||
#include <languageutils/fakemetaobject.h>
|
||||
|
||||
namespace QmlJSTools {
|
||||
|
||||
class FindExportedCppTypes
|
||||
{
|
||||
public:
|
||||
FindExportedCppTypes(const CPlusPlus::Snapshot &snapshot);
|
||||
QList<LanguageUtils::FakeMetaObject::ConstPtr> operator()(const CPlusPlus::Document::Ptr &document);
|
||||
|
||||
static bool maybeExportsTypes(const CPlusPlus::Document::Ptr &document);
|
||||
|
||||
private:
|
||||
CPlusPlus::Snapshot m_snapshot;
|
||||
};
|
||||
|
||||
} // namespace QmlJSTools
|
||||
|
||||
#endif // QMLJSTOOLS_QMLJSFINDEXPORTEDCPPTYPES_H
|
@@ -33,6 +33,7 @@
|
||||
#include "qmljsmodelmanager.h"
|
||||
#include "qmljstoolsconstants.h"
|
||||
#include "qmljsplugindumper.h"
|
||||
#include "qmljsfindexportedcpptypes.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
@@ -41,8 +42,6 @@
|
||||
#include <coreplugin/messagemanager.h>
|
||||
#include <cplusplus/ModelManagerInterface.h>
|
||||
#include <cplusplus/CppDocument.h>
|
||||
#include <cplusplus/TypeOfExpression.h>
|
||||
#include <cplusplus/Overview.h>
|
||||
#include <qmljs/qmljscontext.h>
|
||||
#include <qmljs/qmljsbind.h>
|
||||
#include <qmljs/parser/qmldirparser_p.h>
|
||||
@@ -98,8 +97,10 @@ void ModelManager::delayedInitialization()
|
||||
CPlusPlus::CppModelManagerInterface *cppModelManager =
|
||||
CPlusPlus::CppModelManagerInterface::instance();
|
||||
if (cppModelManager) {
|
||||
// It's important to have a direct connection here so we can prevent
|
||||
// the source and AST of the cpp document being cleaned away.
|
||||
connect(cppModelManager, SIGNAL(documentUpdated(CPlusPlus::Document::Ptr)),
|
||||
this, SLOT(queueCppQmlTypeUpdate(CPlusPlus::Document::Ptr)));
|
||||
this, SLOT(maybeQueueCppQmlTypeUpdate(CPlusPlus::Document::Ptr)), Qt::DirectConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,9 +674,25 @@ void ModelManager::loadPluginTypes(const QString &libraryPath, const QString &im
|
||||
m_pluginDumper->loadPluginTypes(libraryPath, importPath, importUri, importVersion);
|
||||
}
|
||||
|
||||
void ModelManager::queueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc)
|
||||
// is called *inside a c++ parsing thread*, to allow hanging on to source and ast
|
||||
void ModelManager::maybeQueueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc)
|
||||
{
|
||||
m_queuedCppDocuments.insert(doc->fileName());
|
||||
// keep source and AST alive if we want to scan for register calls
|
||||
const bool scan = FindExportedCppTypes::maybeExportsTypes(doc);
|
||||
if (scan)
|
||||
doc->keepSourceAndAST();
|
||||
|
||||
// delegate actual queuing to the gui thread
|
||||
QMetaObject::invokeMethod(this, "queueCppQmlTypeUpdate",
|
||||
Q_ARG(CPlusPlus::Document::Ptr, doc), Q_ARG(bool, scan));
|
||||
}
|
||||
|
||||
void ModelManager::queueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc, bool scan)
|
||||
{
|
||||
QPair<CPlusPlus::Document::Ptr, bool> prev = m_queuedCppDocuments.value(doc->fileName());
|
||||
if (prev.first && prev.second)
|
||||
prev.first->releaseSourceAndAST();
|
||||
m_queuedCppDocuments.insert(doc->fileName(), qMakePair(doc, scan));
|
||||
m_updateCppQmlTypesTimer->start();
|
||||
}
|
||||
|
||||
@@ -691,20 +708,32 @@ void ModelManager::startCppQmlTypeUpdate()
|
||||
m_queuedCppDocuments.clear();
|
||||
}
|
||||
|
||||
void ModelManager::updateCppQmlTypes(ModelManager *qmlModelManager, CPlusPlus::CppModelManagerInterface *cppModelManager, QSet<QString> files)
|
||||
void ModelManager::updateCppQmlTypes(ModelManager *qmlModelManager,
|
||||
CPlusPlus::CppModelManagerInterface *cppModelManager,
|
||||
QMap<QString, QPair<CPlusPlus::Document::Ptr, bool> > documents)
|
||||
{
|
||||
CppQmlTypeHash newCppTypes = qmlModelManager->cppQmlTypes();
|
||||
CPlusPlus::Snapshot snapshot = cppModelManager->snapshot();
|
||||
FindExportedCppTypes finder(snapshot);
|
||||
|
||||
typedef QPair<CPlusPlus::Document::Ptr, bool> DocScanPair;
|
||||
foreach (const DocScanPair &pair, documents) {
|
||||
CPlusPlus::Document::Ptr doc = pair.first;
|
||||
const bool scan = pair.second;
|
||||
const QString fileName = doc->fileName();
|
||||
if (!scan) {
|
||||
newCppTypes.remove(fileName);
|
||||
continue;
|
||||
}
|
||||
|
||||
QList<LanguageUtils::FakeMetaObject::ConstPtr> exported = finder(doc);
|
||||
|
||||
foreach (const QString &fileName, files) {
|
||||
CPlusPlus::Document::Ptr doc = snapshot.document(fileName);
|
||||
QList<LanguageUtils::FakeMetaObject::ConstPtr> exported;
|
||||
if (doc)
|
||||
exported = cppModelManager->exportedQmlObjects(doc);
|
||||
if (!exported.isEmpty())
|
||||
newCppTypes[fileName] = exported;
|
||||
else
|
||||
newCppTypes.remove(fileName);
|
||||
|
||||
doc->releaseSourceAndAST();
|
||||
}
|
||||
|
||||
QMutexLocker locker(&qmlModelManager->m_cppTypesMutex);
|
||||
|
@@ -117,12 +117,15 @@ protected:
|
||||
void updateImportPaths();
|
||||
|
||||
private slots:
|
||||
void queueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc);
|
||||
void maybeQueueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc);
|
||||
void queueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc, bool scan);
|
||||
void startCppQmlTypeUpdate();
|
||||
|
||||
private:
|
||||
static bool matchesMimeType(const Core::MimeType &fileMimeType, const Core::MimeType &knownMimeType);
|
||||
static void updateCppQmlTypes(ModelManager *qmlModelManager, CPlusPlus::CppModelManagerInterface *cppModelManager, QSet<QString> files);
|
||||
static void updateCppQmlTypes(ModelManager *qmlModelManager,
|
||||
CPlusPlus::CppModelManagerInterface *cppModelManager,
|
||||
QMap<QString, QPair<CPlusPlus::Document::Ptr, bool> > documents);
|
||||
|
||||
mutable QMutex m_mutex;
|
||||
Core::ICore *m_core;
|
||||
@@ -134,7 +137,7 @@ private:
|
||||
QFutureSynchronizer<void> m_synchronizer;
|
||||
|
||||
QTimer *m_updateCppQmlTypesTimer;
|
||||
QSet<QString> m_queuedCppDocuments;
|
||||
QMap<QString, QPair<CPlusPlus::Document::Ptr, bool> > m_queuedCppDocuments;
|
||||
CppQmlTypeHash m_cppTypes;
|
||||
mutable QMutex m_cppTypesMutex;
|
||||
|
||||
|
@@ -17,7 +17,8 @@ HEADERS += \
|
||||
$$PWD/qmljsfunctionfilter.h \
|
||||
$$PWD/qmljslocatordata.h \
|
||||
$$PWD/qmljsindenter.h \
|
||||
$$PWD/qmljscodestylesettingspage.h
|
||||
$$PWD/qmljscodestylesettingspage.h \
|
||||
$$PWD/qmljsfindexportedcpptypes.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/qmljstoolsplugin.cpp \
|
||||
@@ -30,7 +31,8 @@ SOURCES += \
|
||||
$$PWD/qmljsfunctionfilter.cpp \
|
||||
$$PWD/qmljslocatordata.cpp \
|
||||
$$PWD/qmljsindenter.cpp \
|
||||
$$PWD/qmljscodestylesettingspage.cpp
|
||||
$$PWD/qmljscodestylesettingspage.cpp \
|
||||
$$PWD/qmljsfindexportedcpptypes.cpp
|
||||
|
||||
FORMS += \
|
||||
$$PWD/qmljscodestylesettingspage.ui
|
||||
|
Reference in New Issue
Block a user