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