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