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 "qmljsimportdependencies.h"
27 #include "qmljsinterpreter.h"
28 #include "qmljsviewercontext.h"
29
30 #include <utils/algorithm.h>
31 #include <utils/qrcparser.h>
32 #include <utils/qtcassert.h>
33
34 #include <QCryptographicHash>
35 #include <QElapsedTimer>
36 #include <QLoggingCategory>
37
38 #include <algorithm>
39
40 static Q_LOGGING_CATEGORY(importsLog, "qtc.qmljs.imports", QtWarningMsg)
41 static Q_LOGGING_CATEGORY(importsBenchmark, "qtc.qmljs.imports.benchmark", QtWarningMsg)
42
43
44 class ImportsBenchmarker
45 {
46 public:
ImportsBenchmarker(const QString & functionName)47 ImportsBenchmarker(const QString &functionName)
48 : m_functionName(functionName)
49 {
50 m_timer.start();
51 }
52
~ImportsBenchmarker()53 ~ImportsBenchmarker()
54 {
55 if (importsBenchmark().isDebugEnabled()) {
56 qCDebug(importsBenchmark).noquote().nospace() << m_functionName << " executed nPossibleExports: " << nPossibleExports << " in " << m_timer.elapsed() << "ms";
57 }
58 }
59 int nPossibleExports = 0;
60
61 QElapsedTimer m_timer;
62 QString m_functionName;
63 };
64
65 namespace QmlJS {
66
67 /*
68 which languages might be imported in this context
69 */
languageIsCompatible(Dialect contextLanguage,Dialect importLanguage)70 static bool languageIsCompatible(Dialect contextLanguage, Dialect importLanguage)
71 {
72 if (importLanguage == Dialect::AnyLanguage && contextLanguage != Dialect::NoLanguage)
73 return true;
74 switch (contextLanguage.dialect()) {
75 case Dialect::JavaScript:
76 case Dialect::Json:
77 case Dialect::QmlProject:
78 case Dialect::QmlQbs:
79 case Dialect::QmlTypeInfo:
80 return contextLanguage == importLanguage;
81 case Dialect::Qml:
82 return importLanguage == Dialect::Qml || importLanguage == Dialect::QmlQtQuick2 || importLanguage == Dialect::JavaScript;
83 case Dialect::QmlQtQuick2:
84 case Dialect::QmlQtQuick2Ui:
85 return importLanguage == Dialect::Qml || importLanguage == Dialect::QmlQtQuick2 || importLanguage == Dialect::QmlQtQuick2Ui
86 || importLanguage == Dialect::JavaScript;
87 case Dialect::AnyLanguage:
88 return true;
89 case Dialect::NoLanguage:
90 break;
91 }
92 return false;
93 }
94
toImportKind(ImportType::Enum type)95 ImportKind::Enum toImportKind(ImportType::Enum type)
96 {
97 switch (type) {
98 case ImportType::Invalid:
99 break;
100 case ImportType::Library:
101 return ImportKind::Library;
102 case ImportType::ImplicitDirectory:
103 case ImportType::File:
104 case ImportType::Directory:
105 case ImportType::UnknownFile:
106 return ImportKind::Path;
107 case ImportType::QrcFile:
108 case ImportType::QrcDirectory:
109 return ImportKind::QrcPath;
110 }
111 return ImportKind::Invalid;
112 }
113
ImportMatchStrength(const QList<int> & match)114 ImportMatchStrength::ImportMatchStrength(const QList<int> &match)
115 : m_match(match)
116 { }
117
compareMatch(const ImportMatchStrength & o) const118 int ImportMatchStrength::compareMatch(const ImportMatchStrength &o) const
119 {
120 int len1 = m_match.size();
121 int len2 = o.m_match.size();
122 int len = ((len1 < len2) ? len1 : len2);
123 for (int i = 0; i < len; ++ i) {
124 int v1 = m_match.at(i);
125 int v2 = o.m_match.at(i);
126 if (v1 < v2)
127 return -1;
128 if (v1 > v2)
129 return 1;
130 }
131 if (len1 < len2)
132 return -1;
133 if (len1 > len2)
134 return 1;
135 return 0;
136 }
137
hasNoMatch()138 bool ImportMatchStrength::hasNoMatch()
139 {
140 return m_match.isEmpty();
141 }
142
hasMatch()143 bool ImportMatchStrength::hasMatch()
144 {
145 return !m_match.isEmpty();
146 }
147
operator ==(const ImportMatchStrength & m1,const ImportMatchStrength & m2)148 bool operator ==(const ImportMatchStrength &m1, const ImportMatchStrength &m2)
149 {
150 return m1.m_match == m2.m_match;
151 }
152
operator !=(const ImportMatchStrength & m1,const ImportMatchStrength & m2)153 bool operator !=(const ImportMatchStrength &m1, const ImportMatchStrength &m2)
154 {
155 return !(m1 == m2);
156 }
157
operator <(const ImportMatchStrength & m1,const ImportMatchStrength & m2)158 bool operator <(const ImportMatchStrength &m1, const ImportMatchStrength &m2)
159 {
160 return m1.compareMatch(m2) < 0;
161 }
162
ImportKey()163 ImportKey::ImportKey()
164 : type(ImportType::Invalid),
165 majorVersion(LanguageUtils::ComponentVersion::NoVersion),
166 minorVersion(LanguageUtils::ComponentVersion::NoVersion)
167 { }
168
ImportKey(const ImportInfo & info)169 ImportKey::ImportKey(const ImportInfo &info)
170 : type(info.type())
171 , majorVersion(info.version().majorVersion())
172 , minorVersion(info.version().minorVersion())
173 {
174 splitPath = QFileInfo(info.path()).canonicalFilePath().split('/');
175 }
176
ImportKey(ImportType::Enum type,const QString & path,int majorVersion,int minorVersion)177 ImportKey::ImportKey(ImportType::Enum type, const QString &path, int majorVersion, int minorVersion)
178 : type(type)
179 , majorVersion(majorVersion)
180 , minorVersion(minorVersion)
181 {
182 switch (type) {
183 case ImportType::Library:
184 splitPath = path.split(QLatin1Char('.'));
185 break;
186 case ImportType::ImplicitDirectory:
187 case ImportType::Directory:
188 splitPath = path.split(QLatin1Char('/'));
189 if (splitPath.length() > 1 && splitPath.last().isEmpty())
190 splitPath.removeLast();
191 break;
192 case ImportType::File:
193 case ImportType::QrcFile:
194 splitPath = Utils::QrcParser::normalizedQrcFilePath(path).split(QLatin1Char('/'));
195 break;
196 case ImportType::QrcDirectory:
197 splitPath = Utils::QrcParser::normalizedQrcDirectoryPath(path).split(QLatin1Char('/'));
198 if (splitPath.length() > 1 && splitPath.last().isEmpty())
199 splitPath.removeLast();
200 break;
201 case ImportType::Invalid:
202 case ImportType::UnknownFile:
203 splitPath = path.split(QLatin1Char('/'));
204 break;
205 }
206 }
207
addToHash(QCryptographicHash & hash) const208 void ImportKey::addToHash(QCryptographicHash &hash) const
209 {
210 hash.addData(reinterpret_cast<const char *>(&type), sizeof(type));
211 hash.addData(reinterpret_cast<const char *>(&majorVersion), sizeof(majorVersion));
212 hash.addData(reinterpret_cast<const char *>(&minorVersion), sizeof(minorVersion));
213 foreach (const QString &s, splitPath) {
214 hash.addData("/", 1);
215 hash.addData(reinterpret_cast<const char *>(s.constData()), sizeof(QChar) * s.size());
216 }
217 hash.addData("/", 1);
218 }
219
flatKey() const220 ImportKey ImportKey::flatKey() const {
221 switch (type) {
222 case ImportType::Invalid:
223 return *this;
224 case ImportType::ImplicitDirectory:
225 case ImportType::Library:
226 case ImportType::File:
227 case ImportType::Directory:
228 case ImportType::QrcFile:
229 case ImportType::QrcDirectory:
230 case ImportType::UnknownFile:
231 break;
232 }
233 QStringList flatPath = splitPath;
234 int i = 0;
235 while (i < flatPath.size()) {
236 if (flatPath.at(i).startsWith(QLatin1Char('+')))
237 flatPath.removeAt(i);
238 else
239 ++i;
240 }
241 if (flatPath.size() == splitPath.size())
242 return *this;
243 ImportKey res = *this;
244 res.splitPath = flatPath;
245 return res;
246 }
247
libraryQualifiedPath() const248 QString ImportKey::libraryQualifiedPath() const
249 {
250 QString res = splitPath.join(QLatin1Char('.'));
251 if (res.isEmpty() && !splitPath.isEmpty())
252 return QLatin1String("");
253 return res;
254 }
255
path() const256 QString ImportKey::path() const
257 {
258 QString res = splitPath.join(QLatin1Char('/'));
259 if (res.isEmpty() && !splitPath.isEmpty())
260 return QLatin1String("/");
261 return res;
262 }
263
matchImport(const ImportKey & o,const ViewerContext & vContext) const264 ImportMatchStrength ImportKey::matchImport(const ImportKey &o, const ViewerContext &vContext) const
265 {
266 if (majorVersion != o.majorVersion || minorVersion > o.minorVersion)
267 return ImportMatchStrength();
268 bool dirToFile = false;
269 switch (o.type) {
270 case ImportType::Invalid:
271 return ImportMatchStrength();
272 case ImportType::ImplicitDirectory:
273 case ImportType::Directory:
274 switch (type) {
275 case ImportType::File:
276 case ImportType::UnknownFile:
277 dirToFile = true;
278 break;
279 case ImportType::ImplicitDirectory:
280 case ImportType::Directory:
281 break;
282 default:
283 return ImportMatchStrength();
284 }
285 break;
286 case ImportType::Library:
287 if (type != ImportType::Library)
288 return ImportMatchStrength();
289 break;
290 case ImportType::QrcDirectory:
291 switch (type) {
292 case ImportType::QrcFile:
293 dirToFile = true;
294 break;
295 case ImportType::QrcDirectory:
296 break;
297 default:
298 return ImportMatchStrength();
299 }
300 break;
301 case ImportType::QrcFile:
302 if (type != ImportType::QrcFile)
303 return ImportMatchStrength();
304 break;
305 case ImportType::UnknownFile:
306 case ImportType::File:
307 switch (type) {
308 case ImportType::UnknownFile:
309 case ImportType::File:
310 break;
311 default:
312 return ImportMatchStrength();
313 }
314 break;
315 }
316
317 QList<int> res;
318 int iPath1 = 0;
319 int lenPath1 = splitPath.size();
320 int iPath2 = 0;
321 int lenPath2 = o.splitPath.size();
322 if (dirToFile)
323 --lenPath1;
324 int iSelector = 0;
325 const int nSelectors = vContext.selectors.size();
326 while (iPath1 < lenPath1) {
327 if (lenPath2 - iPath2 > lenPath1 - iPath1)
328 return ImportMatchStrength();
329 const QString p1 = splitPath.at(iPath1);
330 if (iPath2 < lenPath2) {
331 const QString p2 = o.splitPath.at(iPath2);
332 if (p1 == p2) {
333 ++iPath1;
334 ++iPath2;
335 continue;
336 }
337 }
338 if (!p1.startsWith(QLatin1Char('+')))
339 return ImportMatchStrength();
340 const QStringView selectorAtt(p1.constData() + 1, p1.size() - 1);
341 while (iSelector < nSelectors) {
342 if (selectorAtt == vContext.selectors.at(iSelector))
343 break;
344 ++iSelector;
345 }
346 if (iSelector == nSelectors)
347 return ImportMatchStrength();
348 res << (nSelectors - iSelector);
349 ++iSelector;
350 ++iPath1;
351 }
352 if (iPath2 != lenPath2)
353 return ImportMatchStrength();
354 if (res.isEmpty())
355 res << 0;
356 return ImportMatchStrength(res);
357 }
358
compare(const ImportKey & other) const359 int ImportKey::compare(const ImportKey &other) const
360 {
361 ImportKind::Enum k1 = toImportKind(type);
362 ImportKind::Enum k2 = toImportKind(other.type);
363 if (k1 < k2)
364 return -1;
365 if (k1 > k2)
366 return 1;
367 int len1 = splitPath.size();
368 int len2 = other.splitPath.size();
369 int len = ((len1 < len2) ? len1 : len2);
370 for (int i = 0; i < len; ++ i) {
371 QString v1 = splitPath.at(i);
372 QString v2 = other.splitPath.at(i);
373 if (v1 < v2)
374 return -1;
375 if (v1 > v2)
376 return 1;
377 }
378 if (len1 < len2)
379 return -1;
380 if (len1 > len2)
381 return 1;
382 if (majorVersion < other.majorVersion)
383 return -1;
384 if (majorVersion > other.majorVersion)
385 return 1;
386 if (minorVersion < other.minorVersion)
387 return -1;
388 if (minorVersion > other.minorVersion)
389 return 1;
390 if (type < other.type)
391 return -1;
392 if (type > other.type)
393 return 1;
394 return 0;
395 }
396
isDirectoryLike() const397 bool ImportKey::isDirectoryLike() const
398 {
399 switch (type) {
400 case ImportType::Directory:
401 case ImportType::ImplicitDirectory:
402 case ImportType::QrcDirectory:
403 return true;
404 default:
405 return false;
406 }
407 }
408
compareDir(const ImportKey & superDir) const409 ImportKey::DirCompareInfo ImportKey::compareDir(const ImportKey &superDir) const
410 {
411 // assumes dir/+selectors/file (i.e. no directories inside selectors)
412 switch (superDir.type) {
413 case ImportType::UnknownFile:
414 case ImportType::File:
415 case ImportType::Directory:
416 case ImportType::ImplicitDirectory:
417 if (type != ImportType::File && type != ImportType::ImplicitDirectory
418 && type != ImportType::Directory && type != ImportType::UnknownFile)
419 return Incompatible;
420 break;
421 case ImportType::QrcDirectory:
422 case ImportType::QrcFile:
423 if (type != ImportType::QrcDirectory && type != ImportType::QrcFile)
424 return Incompatible;
425 break;
426 case ImportType::Invalid:
427 case ImportType::Library:
428 return Incompatible;
429 }
430 bool isDir1 = isDirectoryLike();
431 bool isDir2 = superDir.isDirectoryLike();
432 int len1 = splitPath.size();
433 int len2 = superDir.splitPath.size();
434 if (isDir1 && len1 > 0)
435 --len1;
436 if (isDir2 && len2 > 0)
437 --len2;
438
439 int i1 = 0;
440 int i2 = 0;
441 while (i1 < len1 && i2 < len2) {
442 QString p1 = splitPath.at(i1);
443 QString p2 = superDir.splitPath.at(i2);
444 if (p1 == p2) {
445 ++i1;
446 ++i2;
447 continue;
448 }
449 if (p1.startsWith(QLatin1Char('+'))) {
450 if (p2.startsWith(QLatin1Char('+')))
451 return SameDir;
452 return SecondInFirst;
453 }
454 if (p2.startsWith(QLatin1Char('+')))
455 return FirstInSecond;
456 return Different;
457 }
458 if (i1 < len1) {
459 if (splitPath.at(i1).startsWith(QLatin1Char('+')))
460 return SameDir;
461 return SecondInFirst;
462 }
463 if (i2 < len2) {
464 if (superDir.splitPath.at(i2).startsWith(QLatin1Char('+')))
465 return SameDir;
466 return SecondInFirst;
467 }
468 return SameDir;
469 }
470
toString() const471 QString ImportKey::toString() const
472 {
473 QString res;
474 switch (type) {
475 case ImportType::UnknownFile:
476 case ImportType::File:
477 res = path();
478 break;
479 case ImportType::Directory:
480 case ImportType::ImplicitDirectory:
481 res = path() + QLatin1Char('/');
482 break;
483 case ImportType::QrcDirectory:
484 res = QLatin1String("qrc:") + path() + QLatin1Char('/');
485 break;
486 case ImportType::QrcFile:
487 res = QLatin1String("qrc:") + path() + QLatin1Char('/');
488 break;
489 case ImportType::Invalid:
490 res = path();
491 break;
492 case ImportType::Library:
493 res = splitPath.join(QLatin1Char('.'));
494 break;
495 }
496
497 if (majorVersion != LanguageUtils::ComponentVersion::NoVersion
498 || minorVersion != LanguageUtils::ComponentVersion::NoVersion)
499 return res + QLatin1Char(' ') + QString::number(majorVersion)
500 + QLatin1Char('.') + QString::number(minorVersion);
501
502 return res;
503 }
504
qHash(const ImportKey & info)505 uint qHash(const ImportKey &info)
506 {
507 uint res = ::qHash(info.type) ^
508 ::qHash(info.majorVersion) ^ ::qHash(info.minorVersion);
509 foreach (const QString &s, info.splitPath)
510 res = res ^ ::qHash(s);
511 return res;
512 }
513
operator ==(const ImportKey & i1,const ImportKey & i2)514 bool operator==(const ImportKey &i1, const ImportKey &i2)
515 {
516 return i1.type == i2.type
517 && i1.splitPath == i2.splitPath
518 && i1.majorVersion == i2.majorVersion
519 && i1.minorVersion == i2.minorVersion;
520 }
521
operator !=(const ImportKey & i1,const ImportKey & i2)522 bool operator !=(const ImportKey &i1, const ImportKey &i2)
523 {
524 return ! (i1 == i2);
525 }
526
operator <(const ImportKey & i1,const ImportKey & i2)527 bool operator <(const ImportKey &i1, const ImportKey &i2)
528 {
529 return i1.compare(i2) < 0;
530 }
531
libraryTypeName()532 QString Export::libraryTypeName() { return QStringLiteral("%Library%"); }
533
Export()534 Export::Export()
535 : intrinsic(false)
536 { }
537
Export(ImportKey exportName,const QString & pathRequired,bool intrinsic,const QString & typeName)538 Export::Export(ImportKey exportName, const QString &pathRequired, bool intrinsic, const QString &typeName)
539 : exportName(exportName), pathRequired(pathRequired), typeName(typeName), intrinsic(intrinsic)
540 { }
541
visibleInVContext(const ViewerContext & vContext) const542 bool Export::visibleInVContext(const ViewerContext &vContext) const
543 {
544 return pathRequired.isEmpty() || vContext.paths.contains(pathRequired);
545 }
546
CoreImport()547 CoreImport::CoreImport() : language(Dialect::Qml) { }
548
CoreImport(const QString & importId,const QList<Export> & possibleExports,Dialect language,const QByteArray & fingerprint)549 CoreImport::CoreImport(const QString &importId, const QList<Export> &possibleExports,
550 Dialect language, const QByteArray &fingerprint)
551 : importId(importId), possibleExports(possibleExports), language(language),
552 fingerprint(fingerprint)
553 { }
554
valid()555 bool CoreImport::valid() {
556 return !fingerprint.isEmpty();
557 }
558
calculateFingerprint(const ImportDependencies & deps)559 QByteArray DependencyInfo::calculateFingerprint(const ImportDependencies &deps)
560 {
561 QCryptographicHash hash(QCryptographicHash::Sha1);
562 rootImport.addToHash(hash);
563 QStringList coreImports = Utils::toList(allCoreImports);
564 coreImports.sort();
565 foreach (const QString importId, coreImports) {
566 hash.addData(reinterpret_cast<const char*>(importId.constData()), importId.size() * sizeof(QChar));
567 QByteArray coreImportFingerprint = deps.coreImport(importId).fingerprint;
568 hash.addData(coreImportFingerprint);
569 }
570 hash.addData("/", 1);
571 QList<ImportKey> imports = Utils::toList(allImports);
572 std::sort(imports.begin(), imports.end());
573 for (const ImportKey &k : qAsConst(imports))
574 k.addToHash(hash);
575 return hash.result();
576 }
577
MatchedImport()578 MatchedImport::MatchedImport()
579 { }
580
MatchedImport(ImportMatchStrength matchStrength,ImportKey importKey,const QString & coreImportId)581 MatchedImport::MatchedImport(ImportMatchStrength matchStrength, ImportKey importKey,
582 const QString &coreImportId)
583 : matchStrength(matchStrength), importKey(importKey), coreImportId(coreImportId)
584 { }
585
compare(const MatchedImport & o) const586 int MatchedImport::compare(const MatchedImport &o) const {
587 int res = matchStrength.compareMatch(o.matchStrength);
588 if (res != 0)
589 return res;
590 res = importKey.compare(o.importKey);
591 if (res != 0)
592 return res;
593 if (coreImportId < o.coreImportId)
594 return -1;
595 if (coreImportId > o.coreImportId)
596 return 1;
597 return 0;
598 }
599
operator ==(const MatchedImport & m1,const MatchedImport & m2)600 bool operator ==(const MatchedImport &m1, const MatchedImport &m2)
601 {
602 return m1.compare(m2) == 0;
603 }
604
operator !=(const MatchedImport & m1,const MatchedImport & m2)605 bool operator !=(const MatchedImport &m1, const MatchedImport &m2)
606 {
607 return m1.compare(m2) != 0;
608 }
609
operator <(const MatchedImport & m1,const MatchedImport & m2)610 bool operator <(const MatchedImport &m1, const MatchedImport &m2)
611 {
612 return m1.compare(m2) < 0;
613 }
614
ImportDependencies()615 ImportDependencies::ImportDependencies()
616 { }
617
~ImportDependencies()618 ImportDependencies::~ImportDependencies()
619 { }
620
filter(const ViewerContext & vContext)621 void ImportDependencies::filter(const ViewerContext &vContext)
622 {
623 ImportsBenchmarker benchMark("filter()");
624 QMap<QString, CoreImport> newCoreImports;
625 QMap<ImportKey, QStringList> newImportCache;
626 bool hasChanges = false;
627 for (auto j = m_coreImports.cbegin(), end = m_coreImports.cend(); j != end; ++j) {
628 const CoreImport &cImport = j.value();
629 if (languageIsCompatible(vContext.language, cImport.language)) {
630 QList<Export> newExports;
631 foreach (const Export &e, cImport.possibleExports) {
632 ++benchMark.nPossibleExports;
633 if (e.visibleInVContext(vContext)) {
634 newExports.append(e);
635 QStringList &candidateImports = newImportCache[e.exportName];
636 if (!candidateImports.contains(cImport.importId))
637 candidateImports.append(cImport.importId);
638 }
639 }
640 if (newExports.size() == cImport.possibleExports.size()) {
641 newCoreImports.insert(cImport.importId, cImport);
642 } else if (newExports.length() > 0) {
643 CoreImport newCImport = cImport;
644 newCImport.possibleExports = newExports;
645 newCoreImports.insert(newCImport.importId, newCImport);
646 hasChanges = true;
647 } else {
648 hasChanges = true;
649 }
650 } else {
651 hasChanges = true;
652 }
653 }
654 if (!hasChanges)
655 return;
656 m_coreImports = newCoreImports;
657 m_importCache = newImportCache;
658 }
659
coreImport(const QString & importId) const660 CoreImport ImportDependencies::coreImport(const QString &importId) const
661 {
662 return m_coreImports.value(importId);
663 }
664
iterateOnCandidateImports(const ImportKey & key,const ViewerContext & vContext,std::function<bool (const ImportMatchStrength &,const Export &,const CoreImport &)> const & iterF) const665 void ImportDependencies::iterateOnCandidateImports(
666 const ImportKey &key, const ViewerContext &vContext,
667 std::function<bool (const ImportMatchStrength &,const Export &,const CoreImport &)>
668 const &iterF) const
669 {
670 ImportsBenchmarker benchMark("iterateOnCandidateImports()");
671 switch (key.type) {
672 case ImportType::Directory:
673 case ImportType::QrcDirectory:
674 case ImportType::ImplicitDirectory:
675 break;
676 default:
677 {
678 const QStringList imp = m_importCache.value(key.flatKey());
679 foreach (const QString &cImportName, imp) {
680 CoreImport cImport = coreImport(cImportName);
681 if (languageIsCompatible(vContext.language, cImport.language)) {
682 foreach (const Export e, cImport.possibleExports) {
683 ++benchMark.nPossibleExports;
684 if (e.visibleInVContext(vContext)) {
685 ImportMatchStrength m = e.exportName.matchImport(key, vContext);
686 if (m.hasMatch()) {
687 if (!iterF(m, e, cImport))
688 return;
689 }
690 }
691 }
692 }
693 }
694 return;
695 }
696 }
697 QMap<ImportKey, QStringList>::const_iterator lb = m_importCache.lowerBound(key.flatKey());
698 QMap<ImportKey, QStringList>::const_iterator end = m_importCache.constEnd();
699 while (lb != end) {
700 ImportKey::DirCompareInfo c = key.compareDir(lb.key());
701 if (c == ImportKey::SameDir) {
702 foreach (const QString &cImportName, lb.value()) {
703 CoreImport cImport = coreImport(cImportName);
704 if (languageIsCompatible(vContext.language, cImport.language)) {
705 foreach (const Export e, cImport.possibleExports) {
706 ++benchMark.nPossibleExports;
707 if (e.visibleInVContext(vContext)) {
708 ImportMatchStrength m = e.exportName.matchImport(key, vContext);
709 if (m.hasMatch()) {
710 if (!iterF(m, e, cImport))
711 return;
712 }
713 }
714 }
715 }
716 }
717 } else if (c != ImportKey::SecondInFirst) {
718 break;
719 }
720 ++lb;
721 }
722 }
723
724 class CollectCandidateImports
725 {
726 public:
727 ImportDependencies::ImportElements &res;
728
CollectCandidateImports(ImportDependencies::ImportElements & res)729 CollectCandidateImports(ImportDependencies::ImportElements & res)
730 : res(res)
731 { }
732
operator ()(const ImportMatchStrength & m,const Export & e,const CoreImport & cI) const733 bool operator ()(const ImportMatchStrength &m, const Export &e, const CoreImport &cI) const
734 {
735 ImportKey flatName = e.exportName.flatKey();
736 res[flatName].append(MatchedImport(m, e.exportName, cI.importId));
737 return true;
738 }
739 };
740
candidateImports(const ImportKey & key,const ViewerContext & vContext) const741 ImportDependencies::ImportElements ImportDependencies::candidateImports(
742 const ImportKey &key,
743 const ViewerContext &vContext) const
744 {
745 ImportDependencies::ImportElements res;
746 CollectCandidateImports collector(res);
747 iterateOnCandidateImports(key, vContext, collector);
748 typedef QMap<ImportKey, QList<MatchedImport> >::iterator iter_t;
749 iter_t i = res.begin();
750 iter_t end = res.end();
751 while (i != end) {
752 std::sort(i.value().begin(), i.value().end());
753 ++i;
754 }
755 return res;
756 }
757
createDependencyInfos(const ImportKey & mainDoc,const ViewerContext & vContext) const758 QList<DependencyInfo::ConstPtr> ImportDependencies::createDependencyInfos(
759 const ImportKey &mainDoc, const ViewerContext &vContext) const
760 {
761 Q_UNUSED(mainDoc)
762 Q_UNUSED(vContext)
763 QList<DependencyInfo::ConstPtr> res;
764 QTC_CHECK(false);
765 return res;
766 }
767
addCoreImport(const CoreImport & import)768 void ImportDependencies::addCoreImport(const CoreImport &import)
769 {
770 CoreImport newImport = import;
771 if (m_coreImports.contains(import.importId)) {
772 CoreImport oldVal = m_coreImports.value(import.importId);
773 foreach (const Export &e, oldVal.possibleExports) {
774 if (e.intrinsic)
775 removeImportCacheEntry(e.exportName, import.importId);
776 else
777 newImport.possibleExports.append(e);
778 }
779 }
780 foreach (const Export &e, import.possibleExports)
781 m_importCache[e.exportName].append(import.importId);
782 m_coreImports.insert(newImport.importId, newImport);
783 if (importsLog().isDebugEnabled()) {
784 QString msg = QString::fromLatin1("added import %1 for").arg(newImport.importId);
785 foreach (const Export &e, newImport.possibleExports)
786 msg += QString::fromLatin1("\n %1(%2)").arg(e.exportName.toString(), e.pathRequired);
787 qCDebug(importsLog) << msg;
788 }
789 }
790
removeCoreImport(const QString & importId)791 void ImportDependencies::removeCoreImport(const QString &importId)
792 {
793 if (!m_coreImports.contains(importId)) {
794 qCWarning(importsLog) << "missing importId in removeCoreImport(" << importId << ")";
795 return;
796 }
797 CoreImport &cImport = m_coreImports[importId];
798 QList<Export> newExports;
799 foreach (const Export &e, cImport.possibleExports)
800 if (e.intrinsic)
801 removeImportCacheEntry(e.exportName, importId);
802 else
803 newExports.append(e);
804 if (newExports.size()>0)
805 cImport.possibleExports = newExports;
806 else
807 m_coreImports.remove(importId);
808
809 qCDebug(importsLog) << "removed import with id:"<< importId;
810 }
811
removeImportCacheEntry(const ImportKey & importKey,const QString & importId)812 void ImportDependencies::removeImportCacheEntry(const ImportKey &importKey, const QString &importId)
813 {
814 QStringList &cImp = m_importCache[importKey];
815 if (!cImp.removeOne(importId)) {
816 qCWarning(importsLog) << "missing possibleExport backpointer for " << importKey.toString() << " to "
817 << importId;
818 }
819 if (cImp.isEmpty())
820 m_importCache.remove(importKey);
821 }
822
addExport(const QString & importId,const ImportKey & importKey,const QString & requiredPath,const QString & typeName)823 void ImportDependencies::addExport(const QString &importId, const ImportKey &importKey,
824 const QString &requiredPath, const QString &typeName)
825 {
826 if (!m_coreImports.contains(importId)) {
827 CoreImport newImport(importId);
828 newImport.language = Dialect::AnyLanguage;
829 newImport.addPossibleExport(Export(importKey, requiredPath, false, typeName));
830 m_coreImports.insert(newImport.importId, newImport);
831 m_importCache[importKey].append(importId);
832 return;
833 }
834 CoreImport &importValue = m_coreImports[importId];
835 importValue.addPossibleExport(Export(importKey, requiredPath, false, typeName));
836 m_importCache[importKey].append(importId);
837 qCDebug(importsLog) << "added export "<< importKey.toString() << " for id " <<importId
838 << " (" << requiredPath << ")";
839 }
840
removeExport(const QString & importId,const ImportKey & importKey,const QString & requiredPath,const QString & typeName)841 void ImportDependencies::removeExport(const QString &importId, const ImportKey &importKey,
842 const QString &requiredPath, const QString &typeName)
843 {
844 if (!m_coreImports.contains(importId)) {
845 qCWarning(importsLog) << "non existing core import for removeExport(" << importId << ", "
846 << importKey.toString() << ")";
847 } else {
848 CoreImport &importValue = m_coreImports[importId];
849 if (!importValue.possibleExports.removeOne(Export(importKey, requiredPath, false, typeName))) {
850 qCWarning(importsLog) << "non existing export for removeExport(" << importId << ", "
851 << importKey.toString() << ")";
852 }
853 if (importValue.possibleExports.isEmpty() && importValue.fingerprint.isEmpty())
854 m_coreImports.remove(importId);
855 }
856 if (!m_importCache.contains(importKey)) {
857 qCWarning(importsLog) << "missing possibleExport for " << importKey.toString()
858 << " when removing export of " << importId;
859 } else {
860 removeImportCacheEntry(importKey, importId);
861 }
862 qCDebug(importsLog) << "removed export "<< importKey.toString() << " for id " << importId
863 << " (" << requiredPath << ")";
864 }
865
iterateOnLibraryImports(const ViewerContext & vContext,std::function<bool (const ImportMatchStrength &,const Export &,const CoreImport &)> const & iterF) const866 void ImportDependencies::iterateOnLibraryImports(
867 const ViewerContext &vContext,
868 std::function<bool (const ImportMatchStrength &,
869 const Export &,
870 const CoreImport &)> const &iterF) const
871 {
872 ImportsBenchmarker benchMark("iterateOnLibraryImports()");
873
874 typedef QMap<ImportKey, QStringList>::const_iterator iter_t;
875 ImportKey firstLib;
876 firstLib.type = ImportType::Library;
877 iter_t i = m_importCache.lowerBound(firstLib);
878 iter_t end = m_importCache.constEnd();
879 while (i != end && i.key().type == ImportType::Library) {
880 qCDebug(importsLog) << "libloop:" << i.key().toString() << i.value();
881 foreach (const QString &cImportName, i.value()) {
882 CoreImport cImport = coreImport(cImportName);
883 if (languageIsCompatible(vContext.language, cImport.language)) {
884 foreach (const Export &e, cImport.possibleExports) {
885 ++benchMark.nPossibleExports;
886 if (e.visibleInVContext(vContext) && e.exportName.type == ImportType::Library) {
887 ImportMatchStrength m = e.exportName.matchImport(i.key(), vContext);
888 if (m.hasMatch()) {
889 qCDebug(importsLog) << "import iterate:" << e.exportName.toString()
890 << " (" << e.pathRequired << "), id:" << cImport.importId;
891 if (!iterF(m, e, cImport))
892 return;
893 }
894 }
895 }
896 }
897 }
898 ++i;
899 }
900 }
901
iterateOnSubImports(const ImportKey & baseKey,const ViewerContext & vContext,std::function<bool (const ImportMatchStrength &,const Export &,const CoreImport &)> const & iterF) const902 void ImportDependencies::iterateOnSubImports(
903 const ImportKey &baseKey,
904 const ViewerContext &vContext,
905 std::function<bool (const ImportMatchStrength &,
906 const Export &,
907 const CoreImport &)> const &iterF) const
908 {
909 ImportsBenchmarker benchMark("iterateOnSubImports()");
910 typedef QMap<ImportKey, QStringList>::const_iterator iter_t;
911 iter_t i = m_importCache.lowerBound(baseKey);
912 iter_t end = m_importCache.constEnd();
913 while (i != end) {
914 ImportKey::DirCompareInfo c = baseKey.compareDir(i.key());
915 if (c != ImportKey::SameDir && c != ImportKey::SecondInFirst)
916 break;
917 foreach (const QString &cImportName, i.value()) {
918 CoreImport cImport = coreImport(cImportName);
919 if (languageIsCompatible(vContext.language, cImport.language)) {
920 foreach (const Export &e, cImport.possibleExports) {
921 ++benchMark.nPossibleExports;
922 if (e.visibleInVContext(vContext)) {
923 ImportMatchStrength m = e.exportName.matchImport(i.key(), vContext);
924 if (m.hasMatch()) {
925 if (!iterF(m, e, cImport))
926 return;
927 }
928 }
929 }
930 }
931 }
932 ++i;
933 }
934 }
935
936 class CollectImportKeys {
937 public:
938 QSet<ImportKey> &imports;
CollectImportKeys(QSet<ImportKey> & imports)939 CollectImportKeys(QSet<ImportKey> &imports)
940 : imports(imports)
941 { }
operator ()(const ImportMatchStrength & m,const Export & e,const CoreImport & cI) const942 bool operator()(const ImportMatchStrength &m,
943 const Export &e,
944 const CoreImport &cI) const
945 {
946 Q_UNUSED(m)
947 Q_UNUSED(cI)
948 imports.insert(e.exportName.flatKey());
949 return true;
950 }
951 };
952
libraryImports(const ViewerContext & viewContext) const953 QSet<ImportKey> ImportDependencies::libraryImports(const ViewerContext &viewContext) const
954 {
955 QSet<ImportKey> res;
956 CollectImportKeys importCollector(res);
957 iterateOnLibraryImports(viewContext, importCollector);
958 return res;
959 }
960
subdirImports(const ImportKey & baseKey,const ViewerContext & viewContext) const961 QSet<ImportKey> ImportDependencies::subdirImports(
962 const ImportKey &baseKey, const ViewerContext &viewContext) const
963 {
964 QSet<ImportKey> res;
965 CollectImportKeys importCollector(res);
966 iterateOnSubImports(baseKey, viewContext, importCollector);
967 return res;
968 }
969
checkConsistency() const970 void ImportDependencies::checkConsistency() const
971 {
972 for (auto j = m_importCache.cbegin(), end = m_importCache.cend(); j != end; ++j) {
973 for (const QString &s : j.value()) {
974 bool found = false;
975 foreach (const Export &e, m_coreImports.value(s).possibleExports)
976 if (e.exportName == j.key())
977 found = true;
978 Q_ASSERT(found); Q_UNUSED(found)
979 }
980 }
981 for (auto i = m_coreImports.cbegin(), end = m_coreImports.cend(); i != end; ++i) {
982 foreach (const Export &e, i.value().possibleExports) {
983 if (!m_importCache.value(e.exportName).contains(i.key())) {
984 qCWarning(importsLog) << e.exportName.toString();
985 qCWarning(importsLog) << i.key();
986
987 for (auto j = m_importCache.cbegin(), end = m_importCache.cend(); j != end; ++j)
988 qCWarning(importsLog) << j.key().toString() << j.value();
989
990 qCWarning(importsLog) << m_importCache.contains(e.exportName);
991 qCWarning(importsLog) << m_importCache.value(e.exportName);
992 }
993 Q_ASSERT(m_importCache.value(e.exportName).contains(i.key()));
994 }
995 }
996 }
997
998 } // namespace QmlJS
999