1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "qmljslink.h"
36 #include "parser/qmljsast_p.h"
37 #include "qmljsdocument.h"
38 #include "qmljsbind.h"
39 #include "qmljscheck.h"
40 #include "qmljsscopebuilder.h"
41 #include "qmljsmodelmanagerinterface.h"
43 #include <QtCore/QFileInfo>
44 #include <QtCore/QDir>
45 #include <QtCore/QDebug>
49 using namespace LanguageUtils;
50 using namespace QmlJS;
51 using namespace QmlJS::Interpreter;
52 using namespace QmlJS::AST;
58 explicit ImportCacheKey(const Interpreter::ImportInfo &info)
61 , majorVersion(info.version().majorVersion())
62 , minorVersion(info.version().minorVersion())
71 uint qHash(const ImportCacheKey &info)
73 return ::qHash(info.type) ^ ::qHash(info.name) ^
74 ::qHash(info.majorVersion) ^ ::qHash(info.minorVersion);
77 bool operator==(const ImportCacheKey &i1, const ImportCacheKey &i2)
79 return i1.type == i2.type
81 && i1.majorVersion == i2.majorVersion
82 && i1.minorVersion == i2.minorVersion;
87 class QmlJS::LinkPrivate
92 Interpreter::Context *context;
93 QStringList importPaths;
95 QHash<ImportCacheKey, Interpreter::ObjectValue *> importCache;
97 QList<DiagnosticMessage> diagnosticMessages;
102 \brief Initializes the Context for a Document.
103 \sa QmlJS::Document QmlJS::Interpreter::Context
105 Initializes a context by resolving imports and building the root scope
106 chain. Currently, this is a expensive operation.
108 It's recommended to use a the \l{LookupContext} returned by
109 \l{QmlJSEditor::SemanticInfo::lookupContext()} instead of building a new
110 \l{Context} with \l{Link}.
113 Link::Link(Context *context, const Document::Ptr &doc, const Snapshot &snapshot,
114 const QStringList &importPaths)
115 : d_ptr(new LinkPrivate)
118 d->context = context;
120 d->snapshot = snapshot;
121 d->importPaths = importPaths;
123 // populate engine with types from C++
124 ModelManagerInterface *modelManager = ModelManagerInterface::instance();
126 foreach (const QList<FakeMetaObject::ConstPtr> &cppTypes, modelManager->cppQmlTypes()) {
127 engine()->cppQmlTypes().load(engine(), cppTypes);
138 Interpreter::Engine *Link::engine()
141 return d->context->engine();
144 QList<DiagnosticMessage> Link::diagnosticMessages() const
147 return d->diagnosticMessages;
150 void Link::linkImports()
154 // do it on d->doc first, to make sure import errors are shown
155 TypeEnvironment *typeEnv = new TypeEnvironment(engine());
156 populateImportedTypes(typeEnv, d->doc);
157 d->context->setTypeEnvironment(d->doc.data(), typeEnv);
159 foreach (Document::Ptr doc, d->snapshot) {
163 TypeEnvironment *typeEnv = new TypeEnvironment(engine());
164 populateImportedTypes(typeEnv, doc);
165 d->context->setTypeEnvironment(doc.data(), typeEnv);
169 void Link::populateImportedTypes(TypeEnvironment *typeEnv, Document::Ptr doc)
173 if (! doc->qmlProgram())
176 // implicit imports: the <default> package is always available
177 loadImplicitDefaultImports(typeEnv);
180 // qml files in the same directory are available without explicit imports
181 loadImplicitDirectoryImports(typeEnv, doc);
183 // explicit imports, whether directories, files or libraries
184 foreach (const ImportInfo &info, doc->bind()->imports()) {
185 ObjectValue *import = d->importCache.value(ImportCacheKey(info));
187 //### 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.
188 if (info.ast() && info.ast()->fileName && info.ast()->fileName->asString() == QLatin1String(".")) {
189 const QString importInfoName(info.name());
190 if (QFileInfo(QDir(importInfoName), QLatin1String("qmldir")).exists()) {
191 foreach (const QString &importPath, d->importPaths) {
192 if (importInfoName.startsWith(importPath)) {
195 const QString cleanPath = QFileInfo(importInfoName).canonicalFilePath();
196 const QString forcedPackageName = cleanPath.mid(importPath.size() + 1).replace('/', '.').replace('\\', '.');
197 import = importNonFile(doc, info, forcedPackageName);
199 d->importCache.insert(ImportCacheKey(info), import);
209 switch (info.type()) {
210 case ImportInfo::FileImport:
211 case ImportInfo::DirectoryImport:
212 import = importFileOrDirectory(doc, info);
214 case ImportInfo::LibraryImport:
215 import = importNonFile(doc, info);
221 d->importCache.insert(ImportCacheKey(info), import);
224 typeEnv->addImport(import, info);
230 import "content" as Xxx
232 import "content" 4.6 as Xxx
234 import "http://www.ovi.com/" as Ovi
236 ObjectValue *Link::importFileOrDirectory(Document::Ptr doc, const ImportInfo &importInfo)
240 ObjectValue *import = 0;
241 const QString &path = importInfo.name();
243 if (importInfo.type() == ImportInfo::DirectoryImport
244 || importInfo.type() == ImportInfo::ImplicitDirectoryImport) {
245 import = new ObjectValue(engine());
247 importLibrary(doc, import, path, importInfo);
249 const QList<Document::Ptr> &documentsInDirectory = d->snapshot.documentsInDirectory(path);
250 foreach (Document::Ptr importedDoc, documentsInDirectory) {
251 if (importedDoc->bind()->rootObjectValue()) {
252 const QString targetName = importedDoc->componentName();
253 import->setProperty(targetName, importedDoc->bind()->rootObjectValue());
256 } else if (importInfo.type() == ImportInfo::FileImport) {
257 Document::Ptr importedDoc = d->snapshot.document(path);
259 import = importedDoc->bind()->rootObjectValue();
268 (import com.nokia.qt is the same as the ones above)
270 ObjectValue *Link::importNonFile(Document::Ptr doc, const ImportInfo &importInfo, const QString &forcedPackageName)
274 ObjectValue *import = new ObjectValue(engine());
275 const QString packageName = forcedPackageName.isEmpty() ? Bind::toString(importInfo.ast()->importUri, '.') : forcedPackageName;
276 const ComponentVersion version = importInfo.version();
278 bool importFound = false;
280 // check the filesystem
281 const QString &packagePath = importInfo.name();
282 foreach (const QString &importPath, d->importPaths) {
283 QString libraryPath = importPath;
284 libraryPath += QDir::separator();
285 libraryPath += packagePath;
287 if (importLibrary(doc, import, libraryPath, importInfo, importPath)) {
293 // if there are cpp-based types for this package, use them too
294 if (engine()->cppQmlTypes().hasPackage(packageName)) {
296 foreach (QmlObjectValue *object,
297 engine()->cppQmlTypes().typesForImport(packageName, version)) {
298 import->setProperty(object->className(), object);
302 if (!importFound && importInfo.ast()) {
303 error(doc, locationFromRange(importInfo.ast()->firstSourceLocation(),
304 importInfo.ast()->lastSourceLocation()),
305 tr("package not found"));
311 bool Link::importLibrary(Document::Ptr doc, Interpreter::ObjectValue *import,
312 const QString &libraryPath,
313 const Interpreter::ImportInfo &importInfo,
314 const QString &importPath)
318 const LibraryInfo libraryInfo = d->snapshot.libraryInfo(libraryPath);
319 if (!libraryInfo.isValid())
322 const ComponentVersion version = importInfo.version();
323 const UiImport *ast = importInfo.ast();
324 SourceLocation errorLoc;
326 errorLoc = locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation());
328 if (!libraryInfo.plugins().isEmpty()) {
329 if (libraryInfo.dumpStatus() == LibraryInfo::DumpNotStartedOrRunning) {
330 ModelManagerInterface *modelManager = ModelManagerInterface::instance();
332 if (importInfo.type() == ImportInfo::LibraryImport) {
333 if (importInfo.version().isValid()) {
334 const QString uri = importInfo.name().replace(QDir::separator(), QLatin1Char('.'));
335 modelManager->loadPluginTypes(
336 libraryPath, importPath,
337 uri, version.toString());
340 modelManager->loadPluginTypes(
341 libraryPath, libraryPath,
342 QString(), version.toString());
345 if (errorLoc.isValid()) {
346 warning(doc, errorLoc,
347 tr("Library contains C++ plugins, type dump is in progress."));
349 } else if (libraryInfo.dumpStatus() == LibraryInfo::DumpError) {
350 ModelManagerInterface *modelManager = ModelManagerInterface::instance();
352 // Only underline import if package/version isn't described in .qmltypes anyway
353 const QmlJS::ModelManagerInterface::BuiltinPackagesHash builtinPackages
354 = modelManager->builtinPackages();
355 const QString packageName = importInfo.name().replace(QDir::separator(), QLatin1Char('.'));
356 if (!builtinPackages.value(packageName).contains(importInfo.version())) {
357 if (errorLoc.isValid()) {
358 error(doc, errorLoc, libraryInfo.dumpError());
362 QList<QmlObjectValue *> loadedObjects =
363 engine()->cppQmlTypes().load(engine(), libraryInfo.metaObjects());
364 foreach (QmlObjectValue *object, loadedObjects) {
365 if (object->packageName().isEmpty()) {
366 import->setProperty(object->className(), object);
372 loadQmldirComponents(import, version, libraryInfo, libraryPath);
377 UiQualifiedId *Link::qualifiedTypeNameId(Node *node)
379 if (UiObjectBinding *binding = AST::cast<UiObjectBinding *>(node))
380 return binding->qualifiedTypeNameId;
381 else if (UiObjectDefinition *binding = AST::cast<UiObjectDefinition *>(node))
382 return binding->qualifiedTypeNameId;
387 void Link::error(const Document::Ptr &doc, const AST::SourceLocation &loc, const QString &message)
391 if (doc->fileName() == d->doc->fileName())
392 d->diagnosticMessages.append(DiagnosticMessage(DiagnosticMessage::Error, loc, message));
395 void Link::warning(const Document::Ptr &doc, const AST::SourceLocation &loc, const QString &message)
399 if (doc->fileName() == d->doc->fileName())
400 d->diagnosticMessages.append(DiagnosticMessage(DiagnosticMessage::Warning, loc, message));
403 void Link::loadQmldirComponents(Interpreter::ObjectValue *import, ComponentVersion version,
404 const LibraryInfo &libraryInfo, const QString &libraryPath)
408 // if the version isn't valid, import the latest
409 if (!version.isValid()) {
410 const int maxVersion = std::numeric_limits<int>::max();
411 version = ComponentVersion(maxVersion, maxVersion);
415 QSet<QString> importedTypes;
416 foreach (const QmlDirParser::Component &component, libraryInfo.components()) {
417 if (importedTypes.contains(component.typeName))
420 ComponentVersion componentVersion(component.majorVersion,
421 component.minorVersion);
422 if (version < componentVersion)
425 importedTypes.insert(component.typeName);
426 if (Document::Ptr importedDoc = d->snapshot.document(
427 libraryPath + QDir::separator() + component.fileName)) {
428 if (ObjectValue *v = importedDoc->bind()->rootObjectValue())
429 import->setProperty(component.typeName, v);
434 void Link::loadImplicitDirectoryImports(TypeEnvironment *typeEnv, Document::Ptr doc)
438 ImportInfo implcitDirectoryImportInfo(
439 ImportInfo::ImplicitDirectoryImport, doc->path());
441 ObjectValue *directoryImport = d->importCache.value(ImportCacheKey(implcitDirectoryImportInfo));
442 if (!directoryImport) {
443 directoryImport = importFileOrDirectory(doc, implcitDirectoryImportInfo);
445 d->importCache.insert(ImportCacheKey(implcitDirectoryImportInfo), directoryImport);
448 typeEnv->addImport(directoryImport, implcitDirectoryImportInfo);
451 void Link::loadImplicitDefaultImports(TypeEnvironment *typeEnv)
455 const QString defaultPackage = CppQmlTypes::defaultPackage;
456 if (engine()->cppQmlTypes().hasPackage(defaultPackage)) {
457 ImportInfo info(ImportInfo::LibraryImport, defaultPackage);
458 ObjectValue *import = d->importCache.value(ImportCacheKey(info));
460 import = new ObjectValue(engine());
461 foreach (QmlObjectValue *object,
462 engine()->cppQmlTypes().typesForImport(defaultPackage, ComponentVersion())) {
463 import->setProperty(object->className(), object);
465 d->importCache.insert(ImportCacheKey(info), import);
467 typeEnv->addImport(import, info);