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 "qmljscompletioncontextfinder.h"
27 #include "qmljsscanner.h"
28
29 #include <utils/porting.h>
30
31 #include <QTextDocument>
32 #include <QStringList>
33
34 using namespace QmlJS;
35
36 /*
37 Saves and restores the state of the global linizer. This enables
38 backtracking.
39
40 Identical to the defines in qmljslineinfo.cpp
41 */
42 #define YY_SAVE() LinizerState savedState = yyLinizerState
43 #define YY_RESTORE() yyLinizerState = savedState
44
CompletionContextFinder(const QTextCursor & cursor)45 CompletionContextFinder::CompletionContextFinder(const QTextCursor &cursor)
46 : m_cursor(cursor)
47 , m_colonCount(-1)
48 , m_behaviorBinding(false)
49 , m_inStringLiteral(false)
50 , m_inImport(false)
51 {
52 QTextBlock lastBlock = cursor.block();
53 if (lastBlock.next().isValid())
54 lastBlock = lastBlock.next();
55 initialize(cursor.document()->begin(), lastBlock);
56
57 m_startTokenIndex = yyLinizerState.tokens.size() - 1;
58
59 // Initialize calls readLine - which skips empty lines. We should only adjust
60 // the start token index if the linizer still is in the same block as the cursor.
61 const int cursorPos = cursor.positionInBlock();
62 if (yyLinizerState.iter == cursor.block()) {
63 for (; m_startTokenIndex >= 0; --m_startTokenIndex) {
64 const Token &token = yyLinizerState.tokens.at(m_startTokenIndex);
65 if (token.end() <= cursorPos)
66 break;
67 if (token.begin() < cursorPos && token.is(Token::String))
68 m_inStringLiteral = true;
69 }
70
71 if (m_startTokenIndex == yyLinizerState.tokens.size() - 1 && yyLinizerState.insertedSemicolon)
72 --m_startTokenIndex;
73 }
74
75 getQmlObjectTypeName(m_startTokenIndex);
76 checkBinding();
77 checkImport();
78 }
79
getQmlObjectTypeName(int startTokenIndex)80 void CompletionContextFinder::getQmlObjectTypeName(int startTokenIndex)
81 {
82 YY_SAVE();
83
84 int tokenIndex = findOpeningBrace(startTokenIndex);
85 if (tokenIndex != -1) {
86 --tokenIndex;
87
88 // can be one of
89 // A.B on c.d
90 // A.B
91
92 bool identifierExpected = true;
93 int i = tokenIndex;
94 forever {
95 if (i < 0) {
96 if (!readLine())
97 break;
98 else
99 i = yyLinizerState.tokens.size() - 1;
100 }
101
102 const Token &token = yyLinizerState.tokens.at(i);
103 if (!identifierExpected && token.kind == Token::Dot) {
104 identifierExpected = true;
105 } else if (token.kind == Token::Identifier) {
106 const QString idText = yyLine->mid(token.begin(), token.length);
107 if (identifierExpected) {
108 m_qmlObjectTypeName.prepend(idText);
109 identifierExpected = false;
110 } else if (idText == QLatin1String("on")) {
111 m_qmlObjectTypeName.clear();
112 identifierExpected = true;
113 } else {
114 break;
115 }
116 } else {
117 break;
118 }
119
120 --i;
121 }
122 }
123
124 YY_RESTORE();
125 }
126
checkBinding()127 void CompletionContextFinder::checkBinding()
128 {
129 YY_SAVE();
130
131 //qCDebug(qmljsLog) << "Start line:" << *yyLine << m_startTokenIndex;
132
133 int i = m_startTokenIndex;
134 int colonCount = 0;
135 bool delimiterFound = false;
136 bool identifierExpected = false;
137 bool dotExpected = false;
138 while (!delimiterFound) {
139 if (i < 0) {
140 if (!readLine())
141 break;
142 else
143 i = yyLinizerState.tokens.size() - 1;
144 //qDebug() << "New Line" << *yyLine;
145 }
146
147 const Token &token = yyLinizerState.tokens.at(i);
148 //qDebug() << "Token:" << yyLine->mid(token.begin(), token.length);
149
150 switch (token.kind) {
151 case Token::RightBrace:
152 case Token::LeftBrace:
153 case Token::Semicolon:
154 delimiterFound = true;
155 break;
156
157 case Token::Colon:
158 ++colonCount;
159 identifierExpected = true;
160 dotExpected = false;
161 m_behaviorBinding = false;
162 m_bindingPropertyName.clear();
163 break;
164
165 case Token::Identifier: {
166 const QStringView tokenString = Utils::midView(*yyLine, token.begin(), token.length);
167 dotExpected = false;
168 if (identifierExpected) {
169 m_bindingPropertyName.prepend(tokenString.toString());
170 identifierExpected = false;
171 dotExpected = true;
172 } else if (tokenString == QLatin1String("on")) {
173 m_behaviorBinding = true;
174 }
175 } break;
176
177 case Token::Dot:
178 if (dotExpected) {
179 dotExpected = false;
180 identifierExpected = true;
181 } else {
182 identifierExpected = false;
183 }
184 break;
185
186 default:
187 dotExpected = false;
188 identifierExpected = false;
189 break;
190 }
191
192 --i;
193 }
194
195 YY_RESTORE();
196 if (delimiterFound)
197 m_colonCount = colonCount;
198 }
199
checkImport()200 void CompletionContextFinder::checkImport()
201 {
202 YY_SAVE();
203
204 //qDebug() << "Start line:" << *yyLine << m_startTokenIndex;
205
206 QStringList libVersionImport;
207 int isInLibVersionImport;
208 int i = m_startTokenIndex;
209 bool stop = false;
210 enum State {
211 Unknown,
212 ExpectImport = 1 << 0,
213 ExpectTargetDot = 1 << 1,
214 ExpectTargetIdentifier = 1 << 2,
215 ExpectAnyTarget = 1 << 3,
216 ExpectVersion = 1 << 4,
217 ExpectAs = 1 << 5
218 };
219 State state = Unknown;
220 isInLibVersionImport = -1;
221
222 while (!stop) {
223 if (i < 0) {
224 if (!readLine())
225 break;
226 else
227 i = yyLinizerState.tokens.size() - 1;
228 //qDebug() << "New Line" << *yyLine;
229 }
230
231 const Token &token = yyLinizerState.tokens.at(i);
232 //qDebug() << "Token:" << yyLine->mid(token.begin(), token.length);
233
234 switch (token.kind) {
235 case Token::Identifier: {
236 const QStringView tokenString = Utils::midView(*yyLine, token.begin(), token.length);
237 if (tokenString == QLatin1String("as")) {
238 isInLibVersionImport = 0;
239 if (state == Unknown) {
240 state = State(ExpectAnyTarget | ExpectVersion);
241 break;
242 }
243 } else if (tokenString == QLatin1String("import")) {
244 if (state == Unknown || (state & ExpectImport)) {
245 if (isInLibVersionImport == -1 && token.end() < m_cursor.position())
246 isInLibVersionImport = 1;
247 m_inImport = true;
248 }
249 } else {
250 if (state == Unknown || (state & ExpectAnyTarget)
251 || (state & ExpectTargetIdentifier)) {
252 state = State(ExpectImport | ExpectTargetDot);
253 libVersionImport.prepend(tokenString.toString());
254 if (isInLibVersionImport == -1) {
255 if (token.end() < m_cursor.position())
256 libVersionImport.append(QLatin1String(" "));
257 isInLibVersionImport = 1;
258 }
259 break;
260 }
261 }
262 stop = true;
263 break;
264 }
265 case Token::String:
266 if (state == Unknown || (state & ExpectAnyTarget)) {
267 state = ExpectImport;
268 break;
269 }
270 stop = true;
271 break;
272 case Token::Number:
273 if (state == Unknown || (state & ExpectVersion)) {
274 state = ExpectAnyTarget;
275 libVersionImport.prepend(yyLine->mid(token.begin(), token.length));
276 libVersionImport.prepend(QLatin1String(" "));
277 if (isInLibVersionImport == -1)
278 isInLibVersionImport = 1;
279 break;
280 }
281 stop = true;
282 break;
283 case Token::Dot:
284 if (state == Unknown || (state & ExpectTargetDot)) {
285 state = ExpectTargetIdentifier;
286 libVersionImport.prepend(QLatin1String("."));
287 if (isInLibVersionImport == -1)
288 isInLibVersionImport = 1;
289 break;
290 }
291 stop = true;
292 break;
293
294 default:
295 stop = true;
296 break;
297 }
298 if (isInLibVersionImport == -1)
299 isInLibVersionImport = 0;
300 --i;
301 }
302
303 YY_RESTORE();
304 if (m_inImport && isInLibVersionImport == 1) {
305 m_libVersion = libVersionImport.join(QString());
306 if (m_libVersion.isNull())
307 m_libVersion = QLatin1String("");
308 } else {
309 m_libVersion.clear();
310 }
311 }
312
qmlObjectTypeName() const313 QStringList CompletionContextFinder::qmlObjectTypeName() const
314 {
315 return m_qmlObjectTypeName;
316 }
317
isInQmlContext() const318 bool CompletionContextFinder::isInQmlContext() const
319 {
320 return !qmlObjectTypeName().isEmpty();
321 }
322
isInLhsOfBinding() const323 bool CompletionContextFinder::isInLhsOfBinding() const
324 {
325 return isInQmlContext() && m_colonCount == 0;
326 }
327
isInRhsOfBinding() const328 bool CompletionContextFinder::isInRhsOfBinding() const
329 {
330 return isInQmlContext() && m_colonCount >= 1;
331 }
332
bindingPropertyName() const333 QStringList CompletionContextFinder::bindingPropertyName() const
334 {
335 return m_bindingPropertyName;
336 }
337
338 /*!
339 \return true if the cursor after "Type on" in the left hand side
340 of a binding, false otherwise.
341 */
isAfterOnInLhsOfBinding() const342 bool CompletionContextFinder::isAfterOnInLhsOfBinding() const
343 {
344 return isInLhsOfBinding() && m_behaviorBinding;
345 }
346
isInStringLiteral() const347 bool QmlJS::CompletionContextFinder::isInStringLiteral() const
348 {
349 return m_inStringLiteral;
350 }
351
isInImport() const352 bool QmlJS::CompletionContextFinder::isInImport() const
353 {
354 return m_inImport;
355 }
356
libVersionImport() const357 QString CompletionContextFinder::libVersionImport() const
358 {
359 return m_libVersion;
360 }
361
findOpeningBrace(int startTokenIndex)362 int CompletionContextFinder::findOpeningBrace(int startTokenIndex)
363 {
364 YY_SAVE();
365
366 if (startTokenIndex == -1)
367 readLine();
368
369 Token::Kind nestedClosing = Token::EndOfFile;
370 int nestingCount = 0;
371
372 for (int i = 0; i < BigRoof; ++i) {
373 if (i != 0 || startTokenIndex == -1)
374 startTokenIndex = yyLinizerState.tokens.size() - 1;
375
376 for (int t = startTokenIndex; t >= 0; --t) {
377 const Token &token = yyLinizerState.tokens.at(t);
378 switch (token.kind) {
379 case Token::LeftBrace:
380 if (nestingCount > 0) {
381 if (nestedClosing == Token::RightBrace)
382 --nestingCount;
383 } else {
384 return t;
385 }
386 break;
387 case Token::LeftParenthesis:
388 if (nestingCount > 0) {
389 if (nestedClosing == Token::RightParenthesis)
390 --nestingCount;
391 } else {
392 YY_RESTORE();
393 return -1;
394 }
395 break;
396 case Token::LeftBracket:
397 if (nestingCount > 0) {
398 if (nestedClosing == Token::RightBracket)
399 --nestingCount;
400 } else {
401 YY_RESTORE();
402 return -1;
403 }
404 break;
405
406 case Token::RightBrace:
407 case Token::RightParenthesis:
408 case Token::RightBracket:
409 if (nestingCount == 0) {
410 nestedClosing = token.kind;
411 nestingCount = 1;
412 } else if (nestedClosing == token.kind) {
413 ++nestingCount;
414 }
415 break;
416
417 default: break;
418 }
419 }
420
421 if (! readLine())
422 break;
423 }
424
425 YY_RESTORE();
426 return -1;
427 }
428