OSDN Git Service

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