1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "qmljsdocument.h"
27 #include "qmljsbind.h"
28 #include "qmljsconstants.h"
29 #include "qmljsimportdependencies.h"
30 #include <qmljs/parser/qmljslexer_p.h>
31 #include <qmljs/parser/qmljsparser_p.h>
32
33 #include <utils/qtcassert.h>
34
35 #include <QCryptographicHash>
36 #include <QDir>
37 #include <QFileInfo>
38 #include <QRegExp>
39
40 #include <algorithm>
41
42 using namespace QmlJS;
43 using namespace QmlJS::AST;
44
45 /*!
46 \class QmlJS::Document
47 \brief The Document class creates a QML or JavaScript document.
48 \sa Snapshot
49
50 Documents are usually created by the ModelManagerInterface
51 and stored in a Snapshot. They allow access to data such as
52 the file path, source code, abstract syntax tree and the Bind
53 instance for the document.
54
55 To make sure unused and outdated documents are removed correctly, Document
56 instances are usually accessed through a shared pointer, see Document::Ptr.
57
58 Documents in a Snapshot are immutable: They, or anything reachable through them,
59 must not be changed. This allows Documents to be shared freely among threads
60 without extra synchronization.
61 */
62
63 /*!
64 \class QmlJS::LibraryInfo
65 \brief The LibraryInfo class creates a QML library.
66 \sa Snapshot
67
68 A LibraryInfo is created when the ModelManagerInterface finds
69 a QML library and parses the qmldir file. The instance holds information about
70 which Components the library provides and which plugins to load.
71
72 The ModelManager will try to extract detailed information about the types
73 defined in the plugins this library loads. Once it is done, the data will
74 be available through the metaObjects() function.
75 */
76
77 /*!
78 \class QmlJS::Snapshot
79 \brief The Snapshot class holds and offers access to a set of
80 Document::Ptr and LibraryInfo instances.
81 \sa Document LibraryInfo
82
83 Usually Snapshots are copies of the snapshot maintained and updated by the
84 ModelManagerInterface that updates its instance as parsing
85 threads finish and new information becomes available.
86 */
87
Document(const QString & fileName,Dialect language)88 Document::Document(const QString &fileName, Dialect language)
89 : _engine(0)
90 , _ast(0)
91 , _bind(0)
92 , _fileName(QDir::cleanPath(fileName))
93 , _editorRevision(0)
94 , _language(language)
95 , _parsedCorrectly(false)
96 {
97 QFileInfo fileInfo(fileName);
98 _path = QDir::cleanPath(fileInfo.absolutePath());
99
100 if (language.isQmlLikeLanguage()) {
101 _componentName = fileInfo.baseName();
102
103 if (! _componentName.isEmpty()) {
104 // ### TODO: check the component name.
105
106 if (! _componentName.at(0).isUpper())
107 _componentName.clear();
108 }
109 }
110 }
111
~Document()112 Document::~Document()
113 {
114 if (_bind)
115 delete _bind;
116
117 if (_engine)
118 delete _engine;
119 }
120
create(const QString & fileName,Dialect language)121 Document::MutablePtr Document::create(const QString &fileName, Dialect language)
122 {
123 Document::MutablePtr doc(new Document(fileName, language));
124 doc->_ptr = doc;
125 return doc;
126 }
127
ptr() const128 Document::Ptr Document::ptr() const
129 {
130 return _ptr.toStrongRef();
131 }
132
isQmlDocument() const133 bool Document::isQmlDocument() const
134 {
135 return _language.isQmlLikeLanguage();
136 }
137
language() const138 Dialect Document::language() const
139 {
140 return _language;
141 }
142
setLanguage(Dialect l)143 void Document::setLanguage(Dialect l)
144 {
145 _language = l;
146 }
147
importId() const148 QString Document::importId() const
149 {
150 return _fileName;
151 }
152
fingerprint() const153 QByteArray Document::fingerprint() const
154 {
155 return _fingerprint;
156 }
157
qmlProgram() const158 AST::UiProgram *Document::qmlProgram() const
159 {
160 return cast<UiProgram *>(_ast);
161 }
162
jsProgram() const163 AST::Program *Document::jsProgram() const
164 {
165 return cast<Program *>(_ast);
166 }
167
expression() const168 AST::ExpressionNode *Document::expression() const
169 {
170 if (_ast)
171 return _ast->expressionCast();
172
173 return 0;
174 }
175
ast() const176 AST::Node *Document::ast() const
177 {
178 return _ast;
179 }
180
engine() const181 const QmlJS::Engine *Document::engine() const
182 {
183 return _engine;
184 }
185
diagnosticMessages() const186 QList<DiagnosticMessage> Document::diagnosticMessages() const
187 {
188 return _diagnosticMessages;
189 }
190
source() const191 QString Document::source() const
192 {
193 return _source;
194 }
195
setSource(const QString & source)196 void Document::setSource(const QString &source)
197 {
198 _source = source;
199 QCryptographicHash sha(QCryptographicHash::Sha1);
200 sha.addData(source.toUtf8());
201 _fingerprint = sha.result();
202 }
203
editorRevision() const204 int Document::editorRevision() const
205 {
206 return _editorRevision;
207 }
208
setEditorRevision(int revision)209 void Document::setEditorRevision(int revision)
210 {
211 _editorRevision = revision;
212 }
213
fileName() const214 QString Document::fileName() const
215 {
216 return _fileName;
217
218 }
219
path() const220 QString Document::path() const
221 {
222 return _path;
223 }
224
componentName() const225 QString Document::componentName() const
226 {
227 return _componentName;
228 }
229
230 namespace {
231 class CollectDirectives : public Directives
232 {
addLocation(int line,int column)233 void addLocation(int line, int column) {
234 const SourceLocation loc = SourceLocation(
235 0, // placeholder
236 0, // placeholder
237 static_cast<quint32>(line),
238 static_cast<quint32>(column));
239 _locations += loc;
240 }
241
242 QList<SourceLocation> _locations;
243
244 public:
CollectDirectives(const QString & documentPath)245 CollectDirectives(const QString &documentPath)
246 : documentPath(documentPath)
247 , isLibrary(false)
248
249 {}
250
pragmaLibrary(int line,int column)251 void pragmaLibrary(int line, int column) override
252 {
253 isLibrary = true;
254 addLocation(line, column);
255 }
256
importFile(const QString & jsfile,const QString & module,int line,int column)257 void importFile(const QString &jsfile, const QString &module,
258 int line, int column) override
259 {
260 imports += ImportInfo::pathImport(
261 documentPath, jsfile, LanguageUtils::ComponentVersion(), module);
262 addLocation(line, column);
263 }
264
importModule(const QString & uri,const QString & version,const QString & module,int line,int column)265 void importModule(const QString &uri, const QString &version, const QString &module,
266 int line, int column) override
267 {
268 imports += ImportInfo::moduleImport(uri, LanguageUtils::ComponentVersion(version), module);
269 addLocation(line, column);
270 }
271
locations()272 virtual QList<SourceLocation> locations() { return _locations; }
273
274 const QString documentPath;
275 bool isLibrary;
276 QList<ImportInfo> imports;
277 };
278
279 } // anonymous namespace
280
281
jsDirectives() const282 QList<SourceLocation> Document::jsDirectives() const
283 {
284 return _jsdirectives;
285 }
286
parse_helper(int startToken)287 bool Document::parse_helper(int startToken)
288 {
289 Q_ASSERT(! _engine);
290 Q_ASSERT(! _ast);
291 Q_ASSERT(! _bind);
292
293 _engine = new Engine();
294
295 Lexer lexer(_engine);
296 Parser parser(_engine);
297
298 QString source = _source;
299 lexer.setCode(source, /*line = */ 1, /*qmlMode = */_language.isQmlLikeLanguage());
300
301 CollectDirectives directives = CollectDirectives(path());
302 _engine->setDirectives(&directives);
303
304 switch (startToken) {
305 case QmlJSGrammar::T_FEED_UI_PROGRAM:
306 _parsedCorrectly = parser.parse();
307 break;
308 case QmlJSGrammar::T_FEED_JS_PROGRAM:
309 _parsedCorrectly = parser.parseProgram();
310 for (const auto &d: directives.locations()) {
311 _jsdirectives << d;
312 }
313 break;
314 case QmlJSGrammar::T_FEED_JS_EXPRESSION:
315 _parsedCorrectly = parser.parseExpression();
316 break;
317 default:
318 Q_ASSERT(0);
319 }
320
321 _ast = parser.rootNode();
322 _diagnosticMessages = parser.diagnosticMessages();
323
324 _bind = new Bind(this, &_diagnosticMessages, directives.isLibrary, directives.imports);
325
326 return _parsedCorrectly;
327 }
328
parse()329 bool Document::parse()
330 {
331 if (isQmlDocument())
332 return parseQml();
333
334 return parseJavaScript();
335 }
336
parseQml()337 bool Document::parseQml()
338 {
339 return parse_helper(QmlJSGrammar::T_FEED_UI_PROGRAM);
340 }
341
parseJavaScript()342 bool Document::parseJavaScript()
343 {
344 return parse_helper(QmlJSGrammar::T_FEED_JS_PROGRAM);
345 }
346
parseExpression()347 bool Document::parseExpression()
348 {
349 return parse_helper(QmlJSGrammar::T_FEED_JS_EXPRESSION);
350 }
351
bind() const352 Bind *Document::bind() const
353 {
354 return _bind;
355 }
356
LibraryInfo(Status status)357 LibraryInfo::LibraryInfo(Status status)
358 : _status(status)
359 , _dumpStatus(NoTypeInfo)
360 {
361 updateFingerprint();
362 }
363
LibraryInfo(const QmlDirParser & parser,const QByteArray & fingerprint)364 LibraryInfo::LibraryInfo(const QmlDirParser &parser, const QByteArray &fingerprint)
365 : _status(Found)
366 , _components(parser.components().values())
367 , _plugins(parser.plugins())
368 , _typeinfos(parser.typeInfos())
369 , _fingerprint(fingerprint)
370 , _dumpStatus(NoTypeInfo)
371 {
372 if (_fingerprint.isEmpty())
373 updateFingerprint();
374 }
375
~LibraryInfo()376 LibraryInfo::~LibraryInfo()
377 {
378 }
379
calculateFingerprint() const380 QByteArray LibraryInfo::calculateFingerprint() const
381 {
382 QCryptographicHash hash(QCryptographicHash::Sha1);
383 hash.addData(reinterpret_cast<const char *>(&_status), sizeof(_status));
384 int len = _components.size();
385 hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
386 foreach (const QmlDirParser::Component &component, _components) {
387 len = component.fileName.size();
388 hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
389 hash.addData(reinterpret_cast<const char *>(component.fileName.constData()), len * sizeof(QChar));
390 hash.addData(reinterpret_cast<const char *>(&component.majorVersion), sizeof(component.majorVersion));
391 hash.addData(reinterpret_cast<const char *>(&component.minorVersion), sizeof(component.minorVersion));
392 len = component.typeName.size();
393 hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
394 hash.addData(reinterpret_cast<const char *>(component.typeName.constData()), component.typeName.size() * sizeof(QChar));
395 int flags = (component.singleton ? (1 << 0) : 0) + (component.internal ? (1 << 1) : 0);
396 hash.addData(reinterpret_cast<const char *>(&flags), sizeof(flags));
397 }
398 len = _plugins.size();
399 hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
400 foreach (const QmlDirParser::Plugin &plugin, _plugins) {
401 len = plugin.path.size();
402 hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
403 hash.addData(reinterpret_cast<const char *>(plugin.path.constData()), len * sizeof(QChar));
404 len = plugin.name.size();
405 hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
406 hash.addData(reinterpret_cast<const char *>(plugin.name.constData()), len * sizeof(QChar));
407 }
408 len = _typeinfos.size();
409 hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
410 foreach (const QmlDirParser::TypeInfo &typeinfo, _typeinfos) {
411 len = typeinfo.fileName.size();
412 hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
413 hash.addData(reinterpret_cast<const char *>(typeinfo.fileName.constData()), len * sizeof(QChar));
414 }
415 len = _metaObjects.size();
416 hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
417 QList<QByteArray> metaFingerprints;
418 foreach (const LanguageUtils::FakeMetaObject::ConstPtr &metaObject, _metaObjects)
419 metaFingerprints.append(metaObject->fingerprint());
420 std::sort(metaFingerprints.begin(), metaFingerprints.end());
421 foreach (const QByteArray &fp, metaFingerprints)
422 hash.addData(fp);
423 hash.addData(reinterpret_cast<const char *>(&_dumpStatus), sizeof(_dumpStatus));
424 len = _dumpError.size(); // localization dependent (avoid?)
425 hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
426 hash.addData(reinterpret_cast<const char *>(_dumpError.constData()), len * sizeof(QChar));
427
428 len = _moduleApis.size();
429 hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
430 foreach (const ModuleApiInfo &moduleInfo, _moduleApis)
431 moduleInfo.addToHash(hash); // make it order independent?
432
433 QByteArray res(hash.result());
434 res.append('L');
435 return res;
436 }
437
updateFingerprint()438 void LibraryInfo::updateFingerprint()
439 {
440 _fingerprint = calculateFingerprint();
441 }
442
Snapshot()443 Snapshot::Snapshot()
444 {
445 }
446
~Snapshot()447 Snapshot::~Snapshot()
448 {
449 }
450
Snapshot(const Snapshot & o)451 Snapshot::Snapshot(const Snapshot &o)
452 : _documents(o._documents),
453 _documentsByPath(o._documentsByPath),
454 _libraries(o._libraries),
455 _dependencies(o._dependencies)
456 {
457 }
458
insert(const Document::Ptr & document,bool allowInvalid)459 void Snapshot::insert(const Document::Ptr &document, bool allowInvalid)
460 {
461 if (document && (allowInvalid || document->qmlProgram() || document->jsProgram())) {
462 const QString fileName = document->fileName();
463 const QString path = document->path();
464 remove(fileName);
465 _documentsByPath[path].append(document);
466 _documents.insert(fileName, document);
467 CoreImport cImport;
468 cImport.importId = document->importId();
469 cImport.language = document->language();
470 cImport.possibleExports << Export(ImportKey(ImportType::File, fileName),
471 QString(), true, QFileInfo(fileName).baseName());
472 cImport.fingerprint = document->fingerprint();
473 _dependencies.addCoreImport(cImport);
474 }
475 }
476
insertLibraryInfo(const QString & path,const LibraryInfo & info)477 void Snapshot::insertLibraryInfo(const QString &path, const LibraryInfo &info)
478 {
479 QTC_CHECK(!path.isEmpty());
480 QTC_CHECK(info.fingerprint() == info.calculateFingerprint());
481 _libraries.insert(QDir::cleanPath(path), info);
482 if (!info.wasFound()) return;
483 CoreImport cImport;
484 cImport.importId = path;
485 cImport.language = Dialect::AnyLanguage;
486 QSet<ImportKey> packages;
487 foreach (const ModuleApiInfo &moduleInfo, info.moduleApis()) {
488 ImportKey iKey(ImportType::Library, moduleInfo.uri, moduleInfo.version.majorVersion(),
489 moduleInfo.version.minorVersion());
490 packages.insert(iKey);
491 }
492 foreach (const LanguageUtils::FakeMetaObject::ConstPtr &metaO, info.metaObjects()) {
493 foreach (const LanguageUtils::FakeMetaObject::Export &e, metaO->exports()) {
494 ImportKey iKey(ImportType::Library, e.package, e.version.majorVersion(),
495 e.version.minorVersion());
496 packages.insert(iKey);
497 }
498 }
499
500 QStringList splitPath = path.split(QLatin1Char('/'));
501 QRegExp vNr(QLatin1String("^(.+)\\.([0-9]+)(?:\\.([0-9]+))?$"));
502 QRegExp safeName(QLatin1String("^[a-zA-Z_][[a-zA-Z0-9_]*$"));
503 foreach (const ImportKey &importKey, packages) {
504 if (importKey.splitPath.size() == 1 && importKey.splitPath.at(0).isEmpty() && splitPath.length() > 0) {
505 // relocatable
506 QStringList myPath = splitPath;
507 if (vNr.indexIn(myPath.last()) == 0)
508 myPath.last() = vNr.cap(1);
509 for (int iPath = myPath.size(); iPath != 1; ) {
510 --iPath;
511 if (safeName.indexIn(myPath.at(iPath)) != 0)
512 break;
513 ImportKey iKey(ImportType::Library, QStringList(myPath.mid(iPath)).join(QLatin1Char('.')),
514 importKey.majorVersion, importKey.minorVersion);
515 cImport.possibleExports.append(Export(iKey, (iPath == 1) ? QLatin1String("/") :
516 QStringList(myPath.mid(0, iPath)).join(QLatin1Char('/')), true));
517 }
518 } else {
519 QString requiredPath = QStringList(splitPath.mid(0, splitPath.size() - importKey.splitPath.size()))
520 .join(QLatin1String("/"));
521 cImport.possibleExports << Export(importKey, requiredPath, true);
522 }
523 }
524 if (cImport.possibleExports.isEmpty() && splitPath.size() > 0) {
525 QRegExp vNr(QLatin1String("^(.+)\\.([0-9]+)(?:\\.([0-9]+))?$"));
526 QRegExp safeName(QLatin1String("^[a-zA-Z_][[a-zA-Z0-9_]*$"));
527 int majorVersion = LanguageUtils::ComponentVersion::NoVersion;
528 int minorVersion = LanguageUtils::ComponentVersion::NoVersion;
529
530 foreach (const QmlDirParser::Component &component, info.components()) {
531 if (component.majorVersion > majorVersion)
532 majorVersion = component.majorVersion;
533 if (component.minorVersion > minorVersion)
534 minorVersion = component.minorVersion;
535 }
536
537 if (vNr.indexIn(splitPath.last()) == 0) {
538 splitPath.last() = vNr.cap(1);
539 bool ok;
540 majorVersion = vNr.cap(2).toInt(&ok);
541 if (!ok)
542 majorVersion = LanguageUtils::ComponentVersion::NoVersion;
543 minorVersion = vNr.cap(3).toInt(&ok);
544 if (vNr.cap(3).isEmpty() || !ok)
545 minorVersion = LanguageUtils::ComponentVersion::NoVersion;
546 }
547
548 for (int iPath = splitPath.size(); iPath != 1; ) {
549 --iPath;
550 if (safeName.indexIn(splitPath.at(iPath)) != 0)
551 break;
552 ImportKey iKey(ImportType::Library, QStringList(splitPath.mid(iPath)).join(QLatin1Char('.')),
553 majorVersion, minorVersion);
554 cImport.possibleExports.append(Export(iKey, (iPath == 1) ? QLatin1String("/") :
555 QStringList(splitPath.mid(0, iPath)).join(QLatin1Char('/')), true));
556 }
557 }
558 foreach (const QmlDirParser::Component &component, info.components()) {
559 foreach (const Export &e, cImport.possibleExports)
560 _dependencies.addExport(component.fileName, e.exportName, e.pathRequired, e.typeName);
561 }
562
563 cImport.fingerprint = info.fingerprint();
564 _dependencies.addCoreImport(cImport);
565 }
566
remove(const QString & fileName)567 void Snapshot::remove(const QString &fileName)
568 {
569 Document::Ptr doc = _documents.value(fileName);
570 if (!doc.isNull()) {
571 const QString &path = doc->path();
572
573 QList<Document::Ptr> docs = _documentsByPath.value(path);
574 docs.removeAll(doc);
575 _documentsByPath[path] = docs;
576
577 _documents.remove(fileName);
578 }
579 }
580
importDependencies() const581 const QmlJS::ImportDependencies *Snapshot::importDependencies() const
582 {
583 return &_dependencies;
584 }
585
importDependencies()586 QmlJS::ImportDependencies *Snapshot::importDependencies()
587 {
588 return &_dependencies;
589 }
590
documentFromSource(const QString & code,const QString & fileName,Dialect language) const591 Document::MutablePtr Snapshot::documentFromSource(
592 const QString &code, const QString &fileName,
593 Dialect language) const
594 {
595 Document::MutablePtr newDoc = Document::create(fileName, language);
596
597 if (Document::Ptr thisDocument = document(fileName))
598 newDoc->_editorRevision = thisDocument->_editorRevision;
599
600 newDoc->setSource(code);
601 return newDoc;
602 }
603
document(const QString & fileName) const604 Document::Ptr Snapshot::document(const QString &fileName) const
605 {
606 return _documents.value(QDir::cleanPath(fileName));
607 }
608
documentsInDirectory(const QString & path) const609 QList<Document::Ptr> Snapshot::documentsInDirectory(const QString &path) const
610 {
611 return _documentsByPath.value(QDir::cleanPath(path));
612 }
613
libraryInfo(const QString & path) const614 LibraryInfo Snapshot::libraryInfo(const QString &path) const
615 {
616 return _libraries.value(QDir::cleanPath(path));
617 }
618
619
addToHash(QCryptographicHash & hash) const620 void ModuleApiInfo::addToHash(QCryptographicHash &hash) const
621 {
622 int len = uri.length();
623 hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
624 hash.addData(reinterpret_cast<const char *>(uri.constData()), len * sizeof(QChar));
625 version.addToHash(hash);
626 len = cppName.length();
627 hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
628 hash.addData(reinterpret_cast<const char *>(cppName.constData()), len * sizeof(QChar));
629 }
630