1 /*
2     SPDX-FileCopyrightText: 2012 Aleix Pol <aleixpol@kde.org>
3     SPDX-FileCopyrightText: 2012 Milian Wolff <mail@milianw.de>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "qmljsparsejob.h"
9 
10 #include <language/backgroundparser/urlparselock.h>
11 #include <custom-definesandincludes/idefinesandincludesmanager.h>
12 
13 #include <language/duchain/duchainlock.h>
14 #include <language/duchain/duchainutils.h>
15 #include <language/duchain/duchain.h>
16 #include <language/duchain/parsingenvironment.h>
17 #include <language/interfaces/ilanguagesupport.h>
18 #include <interfaces/icore.h>
19 #include <interfaces/iprojectcontroller.h>
20 #include <interfaces/iproject.h>
21 #include <project/projectmodel.h>
22 
23 #include "duchain/cache.h"
24 #include "duchain/declarationbuilder.h"
25 #include "duchain/parsesession.h"
26 #include "duchain/usebuilder.h"
27 
28 #include "debug.h"
29 
30 #include <QReadLocker>
31 
32 using namespace KDevelop;
33 
34 /*
35  * This function has been copied from kdev-clang
36  *
37  * SPDX-FileCopyrightText: 2013 Olivier de Gaalon <olivier.jg@gmail.com> and Milian Wolff <mail@milianw.de>
38  * Licensed under the GPL v2+
39  */
findProjectFileItem(const IndexedString & url)40 ProjectFileItem* findProjectFileItem(const IndexedString& url)
41 {
42     ProjectFileItem* file = nullptr;
43 
44     const auto& projects = ICore::self()->projectController()->projects();
45     for (auto project: projects) {
46         const auto files = project->filesForPath(url);
47         if (files.isEmpty()) {
48             continue;
49         }
50 
51         file = files.last();
52 
53         // A file might be defined in different targets.
54         // Prefer file items defined inside a target with non-empty includes.
55         for (auto f: files) {
56             if (!dynamic_cast<ProjectTargetItem*>(f->parent())) {
57                 continue;
58             }
59             file = f;
60             if (!IDefinesAndIncludesManager::manager()->includes(f, IDefinesAndIncludesManager::ProjectSpecific).isEmpty()) {
61                 break;
62             }
63         }
64     }
65     return file;
66 }
67 
QmlJsParseJob(const IndexedString & url,ILanguageSupport * languageSupport)68 QmlJsParseJob::QmlJsParseJob(const IndexedString& url, ILanguageSupport* languageSupport)
69 : ParseJob(url, languageSupport)
70 {
71     // Tell the cache that this file has custom include directories
72     if (auto file = findProjectFileItem(url)) {
73         QmlJS::Cache::instance().setFileCustomIncludes(
74             url,
75             IDefinesAndIncludesManager::manager()->includes(file,
76                 IDefinesAndIncludesManager::Type(
77                     IDefinesAndIncludesManager::ProjectSpecific | IDefinesAndIncludesManager::UserDefined))
78         );
79     } else {
80         QmlJS::Cache::instance().setFileCustomIncludes(
81             url,
82             IDefinesAndIncludesManager::manager()->includes(url.str(),
83                 IDefinesAndIncludesManager::ProjectSpecific)
84         );
85     }
86 }
87 
run(ThreadWeaver::JobPointer pointer,ThreadWeaver::Thread * thread)88 void QmlJsParseJob::run(ThreadWeaver::JobPointer pointer, ThreadWeaver::Thread* thread)
89 {
90     Q_UNUSED(pointer)
91     Q_UNUSED(thread)
92 
93     UrlParseLock urlLock(document());
94     if (abortRequested() || !isUpdateRequired(ParseSession::languageString())) {
95         return;
96     }
97 
98     // Don't parse this file if one of its dependencies is not up to date
99     const auto& dependencies = QmlJS::Cache::instance().dependencies(document());
100     for (auto& dependency : dependencies) {
101         if (!QmlJS::Cache::instance().isUpToDate(dependency)) {
102             QmlJS::Cache::instance().setUpToDate(document(), false);
103             return;
104         }
105     }
106 
107     qCDebug(KDEV_QMLJS) << "parsing" << document().str();
108 
109     ProblemPointer p = readContents();
110     if (p) {
111         //TODO: associate problem with topducontext
112         return;
113     }
114 
115     ParseSession session(document(), QString::fromUtf8(contents().contents), priority());
116 
117     if (abortRequested()) {
118         return;
119     }
120 
121     ReferencedTopDUContext context;
122     {
123         DUChainReadLocker lock;
124         context = DUChainUtils::standardContextForUrl(document().toUrl());
125     }
126     if (context) {
127         translateDUChainToRevision(context);
128         context->setRange(RangeInRevision(0, 0, INT_MAX, INT_MAX));
129     }
130 
131     if (session.ast()) {
132         QReadLocker parseLock(languageSupport()->parseLock());
133 
134         if (abortRequested()) {
135             abortJob();
136             return;
137         }
138 
139         DeclarationBuilder builder(&session);
140         context = builder.build(document(), session.ast(), context);
141 
142         if (abortRequested()) {
143             abortJob();
144             return;
145         }
146 
147         if ( context && (minimumFeatures() & TopDUContext::AllDeclarationsContextsAndUses) ) {
148             UseBuilder useBuilder(&session);
149             useBuilder.buildUses(session.ast());
150         }
151     }
152 
153     if (abortRequested()) {
154         abortJob();
155         return;
156     }
157 
158     if (!context) {
159         DUChainWriteLocker lock;
160         auto* file = new ParsingEnvironmentFile(document());
161         file->setLanguage(ParseSession::languageString());
162         context = new TopDUContext(document(), RangeInRevision(0, 0, INT_MAX, INT_MAX), file);
163         DUChain::self()->addDocumentChain(context);
164     }
165 
166     setDuChain(context);
167 
168     // If the file has become up to date, reparse its importers
169     bool dependenciesOk = session.allDependenciesSatisfied();
170 
171     QmlJS::Cache::instance().setUpToDate(document(), dependenciesOk);
172 
173     if (dependenciesOk) {
174         session.reparseImporters();
175     }
176 
177     {
178         DUChainWriteLocker lock;
179         context->setProblems(session.problems());
180 
181         context->setFeatures(minimumFeatures());
182         ParsingEnvironmentFilePointer file = context->parsingEnvironmentFile();
183         Q_ASSERT(file);
184         file->setModificationRevision(contents().modification);
185         DUChain::self()->updateContextEnvironment( context->topContext(), file.data() );
186     }
187     highlightDUChain();
188 
189     DUChain::self()->emitUpdateReady(document(), duChain());
190 
191     if (session.isParsedCorrectly()) {
192         qCDebug(KDEV_QMLJS) << "===Success===" << document().str();
193     } else {
194         qCDebug(KDEV_QMLJS) << "===Failed===" << document().str() << session.problems();
195     }
196 }
197 
198