1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
31 **************************************************************************/
33 #include "qmljslink.h"
35 #include "parser/qmljsast_p.h"
36 #include "qmljsdocument.h"
37 #include "qmljsbind.h"
38 #include "qmljscheck.h"
39 #include "qmljsscopebuilder.h"
40 #include "qmljsmodelmanagerinterface.h"
42 #include <QtCore/QFileInfo>
43 #include <QtCore/QDir>
44 #include <QtCore/QDebug>
48 using namespace LanguageUtils;
49 using namespace QmlJS;
50 using namespace QmlJS::Interpreter;
51 using namespace QmlJS::AST;
57 explicit ImportCacheKey(const Interpreter::ImportInfo &info)
60 , majorVersion(info.version().majorVersion())
61 , minorVersion(info.version().minorVersion())
70 uint qHash(const ImportCacheKey &info)
72 return ::qHash(info.type) ^ ::qHash(info.name) ^
73 ::qHash(info.majorVersion) ^ ::qHash(info.minorVersion);
76 bool operator==(const ImportCacheKey &i1, const ImportCacheKey &i2)
78 return i1.type == i2.type
80 && i1.majorVersion == i2.majorVersion
81 && i1.minorVersion == i2.minorVersion;
86 class QmlJS::LinkPrivate
91 Interpreter::Context *context;
92 QStringList importPaths;
94 QHash<ImportCacheKey, Interpreter::ObjectValue *> importCache;
96 QList<DiagnosticMessage> diagnosticMessages;
101 \brief Initializes the Context for a Document.
102 \sa QmlJS::Document QmlJS::Interpreter::Context
104 Initializes a context by resolving imports and building the root scope
105 chain. Currently, this is a expensive operation.
107 It's recommended to use a the \l{LookupContext} returned by
108 \l{QmlJSEditor::SemanticInfo::lookupContext()} instead of building a new
109 \l{Context} with \l{Link}.
112 Link::Link(Context *context, const Document::Ptr &doc, const Snapshot &snapshot,
113 const QStringList &importPaths)
114 : d_ptr(new LinkPrivate)
117 d->context = context;
119 d->snapshot = snapshot;
120 d->importPaths = importPaths;
122 // populate engine with types from C++
123 ModelManagerInterface *modelManager = ModelManagerInterface::instance();
125 foreach (const QList<FakeMetaObject::ConstPtr> &cppTypes, modelManager->cppQmlTypes()) {
126 engine()->cppQmlTypes().load(engine(), cppTypes);
137 Interpreter::Engine *Link::engine()
140 return d->context->engine();
143 QList<DiagnosticMessage> Link::diagnosticMessages() const
146 return d->diagnosticMessages;
149 void Link::linkImports()
153 // do it on d->doc first, to make sure import errors are shown
154 TypeEnvironment *typeEnv = new TypeEnvironment(engine());
155 populateImportedTypes(typeEnv, d->doc);
156 d->context->setTypeEnvironment(d->doc.data(), typeEnv);
158 foreach (Document::Ptr doc, d->snapshot) {
162 TypeEnvironment *typeEnv = new TypeEnvironment(engine());
163 populateImportedTypes(typeEnv, doc);
164 d->context->setTypeEnvironment(doc.data(), typeEnv);
168 void Link::populateImportedTypes(TypeEnvironment *typeEnv, Document::Ptr doc)
172 if (! doc->qmlProgram())
175 // implicit imports: the <default> package is always available
176 loadImplicitDefaultImports(typeEnv);
179 // qml files in the same directory are available without explicit imports
180 loadImplicitDirectoryImports(typeEnv, doc);
182 // explicit imports, whether directories, files or libraries
183 foreach (const ImportInfo &info, doc->bind()->imports()) {
184 ObjectValue *import = d->importCache.value(ImportCacheKey(info));
186 //### Hack: if this document is in a library, and if there is an qmldir file in the same directory, and if the prefix is an import-path, the import means to import everything in this library.
187 if (info.ast() && info.ast()->fileName && info.ast()->fileName->asString() == QLatin1String(".")) {
188 const QString importInfoName(info.name());
189 if (QFileInfo(QDir(importInfoName), QLatin1String("qmldir")).exists()) {
190 foreach (const QString &importPath, d->importPaths) {
191 if (importInfoName.startsWith(importPath)) {
194 const QString cleanPath = QFileInfo(importInfoName).canonicalFilePath();
195 const QString forcedPackageName = cleanPath.mid(importPath.size() + 1).replace('/', '.').replace('\\', '.');
196 import = importNonFile(doc, info, forcedPackageName);
198 d->importCache.insert(ImportCacheKey(info), import);
208 switch (info.type()) {
209 case ImportInfo::FileImport:
210 case ImportInfo::DirectoryImport:
211 import = importFileOrDirectory(doc, info);
213 case ImportInfo::LibraryImport:
214 import = importNonFile(doc, info);
220 d->importCache.insert(ImportCacheKey(info), import);
223 typeEnv->addImport(import, info);
229 import "content" as Xxx
231 import "content" 4.6 as Xxx
233 import "http://www.ovi.com/" as Ovi
235 ObjectValue *Link::importFileOrDirectory(Document::Ptr doc, const ImportInfo &importInfo)
239 ObjectValue *import = 0;
240 const QString &path = importInfo.name();
242 if (importInfo.type() == ImportInfo::DirectoryImport
243 || importInfo.type() == ImportInfo::ImplicitDirectoryImport) {
244 import = new ObjectValue(engine());
246 importLibrary(doc, import, path, importInfo);
248 const QList<Document::Ptr> &documentsInDirectory = d->snapshot.documentsInDirectory(path);
249 foreach (Document::Ptr importedDoc, documentsInDirectory) {
250 if (importedDoc->bind()->rootObjectValue()) {
251 const QString targetName = importedDoc->componentName();
252 import->setProperty(targetName, importedDoc->bind()->rootObjectValue());
255 } else if (importInfo.type() == ImportInfo::FileImport) {
256 Document::Ptr importedDoc = d->snapshot.document(path);
258 import = importedDoc->bind()->rootObjectValue();
267 (import com.nokia.qt is the same as the ones above)
269 ObjectValue *Link::importNonFile(Document::Ptr doc, const ImportInfo &importInfo, const QString &forcedPackageName)
273 ObjectValue *import = new ObjectValue(engine());
274 const QString packageName = forcedPackageName.isEmpty() ? Bind::toString(importInfo.ast()->importUri, '.') : forcedPackageName;
275 const ComponentVersion version = importInfo.version();
277 bool importFound = false;
279 // check the filesystem
280 const QString &packagePath = importInfo.name();
281 foreach (const QString &importPath, d->importPaths) {
282 QString libraryPath = importPath;
283 libraryPath += QDir::separator();
284 libraryPath += packagePath;
286 if (importLibrary(doc, import, libraryPath, importInfo, importPath)) {
292 // if there are cpp-based types for this package, use them too
293 if (engine()->cppQmlTypes().hasPackage(packageName)) {
295 foreach (QmlObjectValue *object,
296 engine()->cppQmlTypes().typesForImport(packageName, version)) {
297 import->setProperty(object->className(), object);
301 if (!importFound && importInfo.ast()) {
302 error(doc, locationFromRange(importInfo.ast()->firstSourceLocation(),
303 importInfo.ast()->lastSourceLocation()),
304 tr("package not found"));
310 bool Link::importLibrary(Document::Ptr doc, Interpreter::ObjectValue *import,
311 const QString &libraryPath,
312 const Interpreter::ImportInfo &importInfo,
313 const QString &importPath)
317 const LibraryInfo libraryInfo = d->snapshot.libraryInfo(libraryPath);
318 if (!libraryInfo.isValid())
321 const ComponentVersion version = importInfo.version();
322 const UiImport *ast = importInfo.ast();
323 SourceLocation errorLoc;
325 errorLoc = locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation());
327 if (!libraryInfo.plugins().isEmpty()) {
328 if (libraryInfo.dumpStatus() == LibraryInfo::DumpNotStartedOrRunning) {
329 ModelManagerInterface *modelManager = ModelManagerInterface::instance();
331 if (importInfo.type() == ImportInfo::LibraryImport) {
332 if (importInfo.version().isValid()) {
333 const QString uri = importInfo.name().replace(QDir::separator(), QLatin1Char('.'));
334 modelManager->loadPluginTypes(
335 libraryPath, importPath,
336 uri, version.toString());
339 modelManager->loadPluginTypes(
340 libraryPath, libraryPath,
341 QString(), version.toString());
344 if (errorLoc.isValid()) {
345 warning(doc, errorLoc,
346 tr("Library contains C++ plugins, type dump is in progress."));
348 } else if (libraryInfo.dumpStatus() == LibraryInfo::DumpError) {
349 ModelManagerInterface *modelManager = ModelManagerInterface::instance();
351 // Only underline import if package/version isn't described in .qmltypes anyway
352 const QmlJS::ModelManagerInterface::BuiltinPackagesHash builtinPackages
353 = modelManager->builtinPackages();
354 const QString packageName = importInfo.name().replace(QDir::separator(), QLatin1Char('.'));
355 if (!builtinPackages.value(packageName).contains(importInfo.version())) {
356 if (errorLoc.isValid()) {
357 error(doc, errorLoc, libraryInfo.dumpError());
361 QList<QmlObjectValue *> loadedObjects =
362 engine()->cppQmlTypes().load(engine(), libraryInfo.metaObjects());
363 foreach (QmlObjectValue *object, loadedObjects) {
364 if (object->packageName().isEmpty()) {
365 import->setProperty(object->className(), object);
371 loadQmldirComponents(import, version, libraryInfo, libraryPath);
376 UiQualifiedId *Link::qualifiedTypeNameId(Node *node)
378 if (UiObjectBinding *binding = AST::cast<UiObjectBinding *>(node))
379 return binding->qualifiedTypeNameId;
380 else if (UiObjectDefinition *binding = AST::cast<UiObjectDefinition *>(node))
381 return binding->qualifiedTypeNameId;
386 void Link::error(const Document::Ptr &doc, const AST::SourceLocation &loc, const QString &message)
390 if (doc->fileName() == d->doc->fileName())
391 d->diagnosticMessages.append(DiagnosticMessage(DiagnosticMessage::Error, loc, message));
394 void Link::warning(const Document::Ptr &doc, const AST::SourceLocation &loc, const QString &message)
398 if (doc->fileName() == d->doc->fileName())
399 d->diagnosticMessages.append(DiagnosticMessage(DiagnosticMessage::Warning, loc, message));
402 void Link::loadQmldirComponents(Interpreter::ObjectValue *import, ComponentVersion version,
403 const LibraryInfo &libraryInfo, const QString &libraryPath)
407 // if the version isn't valid, import the latest
408 if (!version.isValid()) {
409 const int maxVersion = std::numeric_limits<int>::max();
410 version = ComponentVersion(maxVersion, maxVersion);
414 QSet<QString> importedTypes;
415 foreach (const QmlDirParser::Component &component, libraryInfo.components()) {
416 if (importedTypes.contains(component.typeName))
419 ComponentVersion componentVersion(component.majorVersion,
420 component.minorVersion);
421 if (version < componentVersion)
424 importedTypes.insert(component.typeName);
425 if (Document::Ptr importedDoc = d->snapshot.document(
426 libraryPath + QDir::separator() + component.fileName)) {
427 if (ObjectValue *v = importedDoc->bind()->rootObjectValue())
428 import->setProperty(component.typeName, v);
433 void Link::loadImplicitDirectoryImports(TypeEnvironment *typeEnv, Document::Ptr doc)
437 ImportInfo implcitDirectoryImportInfo(
438 ImportInfo::ImplicitDirectoryImport, doc->path());
440 ObjectValue *directoryImport = d->importCache.value(ImportCacheKey(implcitDirectoryImportInfo));
441 if (!directoryImport) {
442 directoryImport = importFileOrDirectory(doc, implcitDirectoryImportInfo);
444 d->importCache.insert(ImportCacheKey(implcitDirectoryImportInfo), directoryImport);
447 typeEnv->addImport(directoryImport, implcitDirectoryImportInfo);
450 void Link::loadImplicitDefaultImports(TypeEnvironment *typeEnv)
454 const QString defaultPackage = CppQmlTypes::defaultPackage;
455 if (engine()->cppQmlTypes().hasPackage(defaultPackage)) {
456 ImportInfo info(ImportInfo::LibraryImport, defaultPackage);
457 ObjectValue *import = d->importCache.value(ImportCacheKey(info));
459 import = new ObjectValue(engine());
460 foreach (QmlObjectValue *object,
461 engine()->cppQmlTypes().typesForImport(defaultPackage, ComponentVersion())) {
462 import->setProperty(object->className(), object);
464 d->importCache.insert(ImportCacheKey(info), import);
466 typeEnv->addImport(import, info);