OSDN Git Service

Update license.
[qt-creator-jp/qt-creator-jp.git] / src / libs / qmljs / qmljslink.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
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.
18 **
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.
22 **
23 ** Other Usage
24 **
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.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **************************************************************************/
32
33 #include "qmljslink.h"
34
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"
41
42 #include <QtCore/QFileInfo>
43 #include <QtCore/QDir>
44 #include <QtCore/QDebug>
45
46 #include <limits>
47
48 using namespace LanguageUtils;
49 using namespace QmlJS;
50 using namespace QmlJS::Interpreter;
51 using namespace QmlJS::AST;
52
53 namespace {
54 class ImportCacheKey
55 {
56 public:
57     explicit ImportCacheKey(const Interpreter::ImportInfo &info)
58         : type(info.type())
59         , name(info.name())
60         , majorVersion(info.version().majorVersion())
61         , minorVersion(info.version().minorVersion())
62     {}
63
64     int type;
65     QString name;
66     int majorVersion;
67     int minorVersion;
68 };
69
70 uint qHash(const ImportCacheKey &info)
71 {
72     return ::qHash(info.type) ^ ::qHash(info.name) ^
73             ::qHash(info.majorVersion) ^ ::qHash(info.minorVersion);
74 }
75
76 bool operator==(const ImportCacheKey &i1, const ImportCacheKey &i2)
77 {
78     return i1.type == i2.type
79             && i1.name == i2.name
80             && i1.majorVersion == i2.majorVersion
81             && i1.minorVersion == i2.minorVersion;
82 }
83 }
84
85
86 class QmlJS::LinkPrivate
87 {
88 public:
89     Document::Ptr doc;
90     Snapshot snapshot;
91     Interpreter::Context *context;
92     QStringList importPaths;
93
94     QHash<ImportCacheKey, Interpreter::ObjectValue *> importCache;
95
96     QList<DiagnosticMessage> diagnosticMessages;
97 };
98
99 /*!
100     \class QmlJS::Link
101     \brief Initializes the Context for a Document.
102     \sa QmlJS::Document QmlJS::Interpreter::Context
103
104     Initializes a context by resolving imports and building the root scope
105     chain. Currently, this is a expensive operation.
106
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}.
110 */
111
112 Link::Link(Context *context, const Document::Ptr &doc, const Snapshot &snapshot,
113            const QStringList &importPaths)
114     : d_ptr(new LinkPrivate)
115 {
116     Q_D(Link);
117     d->context = context;
118     d->doc = doc;
119     d->snapshot = snapshot;
120     d->importPaths = importPaths;
121
122     // populate engine with types from C++
123     ModelManagerInterface *modelManager = ModelManagerInterface::instance();
124     if (modelManager) {
125         foreach (const QList<FakeMetaObject::ConstPtr> &cppTypes, modelManager->cppQmlTypes()) {
126             engine()->cppQmlTypes().load(engine(), cppTypes);
127         }
128     }
129
130     linkImports();
131 }
132
133 Link::~Link()
134 {
135 }
136
137 Interpreter::Engine *Link::engine()
138 {
139     Q_D(Link);
140     return d->context->engine();
141 }
142
143 QList<DiagnosticMessage> Link::diagnosticMessages() const
144 {
145     Q_D(const Link);
146     return d->diagnosticMessages;
147 }
148
149 void Link::linkImports()
150 {
151     Q_D(Link);
152
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);
157
158     foreach (Document::Ptr doc, d->snapshot) {
159         if (doc == d->doc)
160             continue;
161
162         TypeEnvironment *typeEnv = new TypeEnvironment(engine());
163         populateImportedTypes(typeEnv, doc);
164         d->context->setTypeEnvironment(doc.data(), typeEnv);
165     }
166 }
167
168 void Link::populateImportedTypes(TypeEnvironment *typeEnv, Document::Ptr doc)
169 {
170     Q_D(Link);
171
172     if (! doc->qmlProgram())
173         return;
174
175     // implicit imports: the <default> package is always available
176     loadImplicitDefaultImports(typeEnv);
177
178     // implicit imports:
179     // qml files in the same directory are available without explicit imports
180     loadImplicitDirectoryImports(typeEnv, doc);
181
182     // explicit imports, whether directories, files or libraries
183     foreach (const ImportInfo &info, doc->bind()->imports()) {
184         ObjectValue *import = d->importCache.value(ImportCacheKey(info));
185
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)) {
192                         // Got it.
193
194                         const QString cleanPath = QFileInfo(importInfoName).canonicalFilePath();
195                         const QString forcedPackageName = cleanPath.mid(importPath.size() + 1).replace('/', '.').replace('\\', '.');
196                         import = importNonFile(doc, info, forcedPackageName);
197                         if (import)
198                             d->importCache.insert(ImportCacheKey(info), import);
199
200                         break;
201                     }
202                 }
203             }
204         }
205         //### End of hack.
206
207         if (!import) {
208             switch (info.type()) {
209             case ImportInfo::FileImport:
210             case ImportInfo::DirectoryImport:
211                 import = importFileOrDirectory(doc, info);
212                 break;
213             case ImportInfo::LibraryImport:
214                 import = importNonFile(doc, info);
215                 break;
216             default:
217                 break;
218             }
219             if (import)
220                 d->importCache.insert(ImportCacheKey(info), import);
221         }
222         if (import)
223             typeEnv->addImport(import, info);
224     }
225 }
226
227 /*
228     import "content"
229     import "content" as Xxx
230     import "content" 4.6
231     import "content" 4.6 as Xxx
232
233     import "http://www.ovi.com/" as Ovi
234 */
235 ObjectValue *Link::importFileOrDirectory(Document::Ptr doc, const ImportInfo &importInfo)
236 {
237     Q_D(Link);
238
239     ObjectValue *import = 0;
240     const QString &path = importInfo.name();
241
242     if (importInfo.type() == ImportInfo::DirectoryImport
243             || importInfo.type() == ImportInfo::ImplicitDirectoryImport) {
244         import = new ObjectValue(engine());
245
246         importLibrary(doc, import, path, importInfo);
247
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());
253             }
254         }
255     } else if (importInfo.type() == ImportInfo::FileImport) {
256         Document::Ptr importedDoc = d->snapshot.document(path);
257         if (importedDoc)
258             import = importedDoc->bind()->rootObjectValue();
259     }
260
261     return import;
262 }
263
264 /*
265   import Qt 4.6
266   import Qt 4.6 as Xxx
267   (import com.nokia.qt is the same as the ones above)
268 */
269 ObjectValue *Link::importNonFile(Document::Ptr doc, const ImportInfo &importInfo, const QString &forcedPackageName)
270 {
271     Q_D(Link);
272
273     ObjectValue *import = new ObjectValue(engine());
274     const QString packageName = forcedPackageName.isEmpty() ? Bind::toString(importInfo.ast()->importUri, '.') : forcedPackageName;
275     const ComponentVersion version = importInfo.version();
276
277     bool importFound = false;
278
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;
285
286         if (importLibrary(doc, import, libraryPath, importInfo, importPath)) {
287             importFound = true;
288             break;
289         }
290     }
291
292     // if there are cpp-based types for this package, use them too
293     if (engine()->cppQmlTypes().hasPackage(packageName)) {
294         importFound = true;
295         foreach (QmlObjectValue *object,
296                  engine()->cppQmlTypes().typesForImport(packageName, version)) {
297             import->setProperty(object->className(), object);
298         }
299     }
300
301     if (!importFound && importInfo.ast()) {
302         error(doc, locationFromRange(importInfo.ast()->firstSourceLocation(),
303                                      importInfo.ast()->lastSourceLocation()),
304               tr("package not found"));
305     }
306
307     return import;
308 }
309
310 bool Link::importLibrary(Document::Ptr doc, Interpreter::ObjectValue *import,
311                          const QString &libraryPath,
312                          const Interpreter::ImportInfo &importInfo,
313                          const QString &importPath)
314 {
315     Q_D(Link);
316
317     const LibraryInfo libraryInfo = d->snapshot.libraryInfo(libraryPath);
318     if (!libraryInfo.isValid())
319         return false;
320
321     const ComponentVersion version = importInfo.version();
322     const UiImport *ast = importInfo.ast();
323     SourceLocation errorLoc;
324     if (ast)
325         errorLoc = locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation());
326
327     if (!libraryInfo.plugins().isEmpty()) {
328         if (libraryInfo.dumpStatus() == LibraryInfo::DumpNotStartedOrRunning) {
329             ModelManagerInterface *modelManager = ModelManagerInterface::instance();
330             if (modelManager) {
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());
337                     }
338                 } else {
339                     modelManager->loadPluginTypes(
340                                 libraryPath, libraryPath,
341                                 QString(), version.toString());
342                 }
343             }
344             if (errorLoc.isValid()) {
345                 warning(doc, errorLoc,
346                         tr("Library contains C++ plugins, type dump is in progress."));
347             }
348         } else if (libraryInfo.dumpStatus() == LibraryInfo::DumpError) {
349             ModelManagerInterface *modelManager = ModelManagerInterface::instance();
350
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());
358                 }
359             }
360         } else {
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);
366                 }
367             }
368         }
369     }
370
371     loadQmldirComponents(import, version, libraryInfo, libraryPath);
372
373     return true;
374 }
375
376 UiQualifiedId *Link::qualifiedTypeNameId(Node *node)
377 {
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;
382     else
383         return 0;
384 }
385
386 void Link::error(const Document::Ptr &doc, const AST::SourceLocation &loc, const QString &message)
387 {
388     Q_D(Link);
389
390     if (doc->fileName() == d->doc->fileName())
391         d->diagnosticMessages.append(DiagnosticMessage(DiagnosticMessage::Error, loc, message));
392 }
393
394 void Link::warning(const Document::Ptr &doc, const AST::SourceLocation &loc, const QString &message)
395 {
396     Q_D(Link);
397
398     if (doc->fileName() == d->doc->fileName())
399         d->diagnosticMessages.append(DiagnosticMessage(DiagnosticMessage::Warning, loc, message));
400 }
401
402 void Link::loadQmldirComponents(Interpreter::ObjectValue *import, ComponentVersion version,
403                                 const LibraryInfo &libraryInfo, const QString &libraryPath)
404 {
405     Q_D(Link);
406
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);
411     }
412
413
414     QSet<QString> importedTypes;
415     foreach (const QmlDirParser::Component &component, libraryInfo.components()) {
416         if (importedTypes.contains(component.typeName))
417             continue;
418
419         ComponentVersion componentVersion(component.majorVersion,
420                                           component.minorVersion);
421         if (version < componentVersion)
422             continue;
423
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);
429         }
430     }
431 }
432
433 void Link::loadImplicitDirectoryImports(TypeEnvironment *typeEnv, Document::Ptr doc)
434 {
435     Q_D(Link);
436
437     ImportInfo implcitDirectoryImportInfo(
438                 ImportInfo::ImplicitDirectoryImport, doc->path());
439
440     ObjectValue *directoryImport = d->importCache.value(ImportCacheKey(implcitDirectoryImportInfo));
441     if (!directoryImport) {
442         directoryImport = importFileOrDirectory(doc, implcitDirectoryImportInfo);
443         if (directoryImport)
444             d->importCache.insert(ImportCacheKey(implcitDirectoryImportInfo), directoryImport);
445     }
446     if (directoryImport)
447         typeEnv->addImport(directoryImport, implcitDirectoryImportInfo);
448 }
449
450 void Link::loadImplicitDefaultImports(TypeEnvironment *typeEnv)
451 {
452     Q_D(Link);
453
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));
458         if (!import) {
459             import = new ObjectValue(engine());
460             foreach (QmlObjectValue *object,
461                      engine()->cppQmlTypes().typesForImport(defaultPackage, ComponentVersion())) {
462                 import->setProperty(object->className(), object);
463             }
464             d->importCache.insert(ImportCacheKey(info), import);
465         }
466         typeEnv->addImport(import, info);
467     }
468 }