Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(416)

Side by Side Diff: pkg/analysis_server/lib/src/services/completion/keyword_contributor.dart

Issue 1471173003: hook new DartCompletionContributor API into existing framework (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: merge Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2
3 // for details. All rights reserved. Use of this source code is governed by a
4 // BSD-style license that can be found in the LICENSE file.
5
6 library services.completion.contributor.dart.keyword;
7
8 import 'dart:async';
9
10 import 'package:analysis_server/plugin/protocol/protocol.dart';
11 import 'package:analysis_server/src/services/completion/dart_completion_manager. dart';
12 import 'package:analyzer/src/generated/ast.dart';
13 import 'package:analyzer/src/generated/scanner.dart';
14
15 const ASYNC = 'async';
16 const AWAIT = 'await';
17
18 /**
19 * A contributor for calculating `completion.getSuggestions` request results
20 * for the local library in which the completion is requested.
21 */
22 class KeywordContributor extends DartCompletionContributor {
23 @override
24 bool computeFast(DartCompletionRequest request) {
25 if (!request.target.isCommentText) {
26 request.target.containingNode.accept(new _KeywordVisitor(request));
27 }
28 return true;
29 }
30
31 @override
32 Future<bool> computeFull(DartCompletionRequest request) {
33 return new Future.value(false);
34 }
35 }
36
37 /**
38 * A visitor for generating keyword suggestions.
39 */
40 class _KeywordVisitor extends GeneralizingAstVisitor {
41 final DartCompletionRequest request;
42 final Object entity;
43
44 _KeywordVisitor(DartCompletionRequest request)
45 : this.request = request,
46 this.entity = request.target.entity;
47
48 @override
49 visitArgumentList(ArgumentList node) {
50 if (entity == node.rightParenthesis ||
51 (entity is SimpleIdentifier && node.arguments.contains(entity))) {
52 _addExpressionKeywords(node);
53 }
54 }
55
56 @override
57 visitBlock(Block node) {
58 if (entity is ExpressionStatement) {
59 Expression expression = (entity as ExpressionStatement).expression;
60 if (expression is SimpleIdentifier) {
61 Token token = expression.token;
62 Token previous = token.previous;
63 if (previous.isSynthetic) {
64 previous = previous.previous;
65 }
66 Token next = token.next;
67 if (next.isSynthetic) {
68 next = next.next;
69 }
70 if (previous.lexeme == ')' && next.lexeme == '{') {
71 _addSuggestion2(ASYNC);
72 }
73 }
74 }
75 _addStatementKeywords(node);
76 if (_inCatchClause(node)) {
77 _addSuggestion(Keyword.RETHROW, DART_RELEVANCE_KEYWORD - 1);
78 }
79 }
80
81 @override
82 visitClassDeclaration(ClassDeclaration node) {
83 // Don't suggest class name
84 if (entity == node.name) {
85 return;
86 }
87 if (entity == node.rightBracket) {
88 _addClassBodyKeywords();
89 } else if (entity is ClassMember) {
90 _addClassBodyKeywords();
91 int index = node.members.indexOf(entity);
92 ClassMember previous = index > 0 ? node.members[index - 1] : null;
93 if (previous is MethodDeclaration && previous.body is EmptyFunctionBody) {
94 _addSuggestion2(ASYNC);
95 }
96 } else {
97 _addClassDeclarationKeywords(node);
98 }
99 }
100
101 @override
102 visitCompilationUnit(CompilationUnit node) {
103 var previousMember = null;
104 for (var member in node.childEntities) {
105 if (entity == member) {
106 break;
107 }
108 previousMember = member;
109 }
110 if (previousMember is ClassDeclaration) {
111 if (previousMember.leftBracket == null ||
112 previousMember.leftBracket.isSynthetic) {
113 // If the prior member is an unfinished class declaration
114 // then the user is probably finishing that
115 _addClassDeclarationKeywords(previousMember);
116 return;
117 }
118 }
119 if (previousMember is ImportDirective) {
120 if (previousMember.semicolon == null ||
121 previousMember.semicolon.isSynthetic) {
122 // If the prior member is an unfinished import directive
123 // then the user is probably finishing that
124 _addImportDirectiveKeywords(previousMember);
125 return;
126 }
127 }
128 if (previousMember == null || previousMember is Directive) {
129 if (previousMember == null &&
130 !node.directives.any((d) => d is LibraryDirective)) {
131 _addSuggestions([Keyword.LIBRARY], DART_RELEVANCE_HIGH);
132 }
133 _addSuggestions(
134 [Keyword.IMPORT, Keyword.EXPORT, Keyword.PART], DART_RELEVANCE_HIGH);
135 }
136 if (entity == null || entity is Declaration) {
137 if (previousMember is FunctionDeclaration &&
138 previousMember.functionExpression is FunctionExpression &&
139 previousMember.functionExpression.body is EmptyFunctionBody) {
140 _addSuggestion2(ASYNC, relevance: DART_RELEVANCE_HIGH);
141 }
142 _addCompilationUnitKeywords();
143 }
144 }
145
146 @override
147 visitExpression(Expression node) {
148 _addExpressionKeywords(node);
149 }
150
151 @override
152 visitExpressionFunctionBody(ExpressionFunctionBody node) {
153 if (entity == node.expression) {
154 _addExpressionKeywords(node);
155 }
156 }
157
158 @override
159 visitForEachStatement(ForEachStatement node) {
160 if (entity == node.inKeyword) {
161 Token previous = node.inKeyword.previous;
162 if (previous is SyntheticStringToken && previous.lexeme == 'in') {
163 previous = previous.previous;
164 }
165 if (previous != null && previous.type == TokenType.EQ) {
166 _addSuggestions([
167 Keyword.CONST,
168 Keyword.FALSE,
169 Keyword.NEW,
170 Keyword.NULL,
171 Keyword.TRUE
172 ]);
173 } else {
174 _addSuggestion(Keyword.IN, DART_RELEVANCE_HIGH);
175 }
176 }
177 }
178
179 @override
180 visitFormalParameterList(FormalParameterList node) {
181 AstNode constructorDeclaration =
182 node.getAncestor((p) => p is ConstructorDeclaration);
183 if (constructorDeclaration != null) {
184 _addSuggestions([Keyword.THIS]);
185 }
186 }
187
188 @override
189 visitForStatement(ForStatement node) {
190 // Handle the degenerate case while typing - for (int x i^)
191 if (node.condition == entity && entity is SimpleIdentifier) {
192 Token entityToken = (entity as SimpleIdentifier).beginToken;
193 if (entityToken.previous.isSynthetic &&
194 entityToken.previous.type == TokenType.SEMICOLON) {
195 _addSuggestion(Keyword.IN, DART_RELEVANCE_HIGH);
196 }
197 }
198 }
199
200 @override
201 visitFunctionExpression(FunctionExpression node) {
202 if (entity == node.body) {
203 if (!node.body.isAsynchronous) {
204 _addSuggestion2(ASYNC, relevance: DART_RELEVANCE_HIGH);
205 }
206 if (node.body is EmptyFunctionBody &&
207 node.parent is FunctionDeclaration &&
208 node.parent.parent is CompilationUnit) {
209 _addCompilationUnitKeywords();
210 }
211 }
212 }
213
214 @override
215 visitIfStatement(IfStatement node) {
216 if (entity == node.thenStatement) {
217 _addStatementKeywords(node);
218 } else if (entity == node.condition) {
219 _addExpressionKeywords(node);
220 }
221 }
222
223 @override
224 visitImportDirective(ImportDirective node) {
225 if (entity == node.asKeyword) {
226 if (node.deferredKeyword == null) {
227 _addSuggestion(Keyword.DEFERRED, DART_RELEVANCE_HIGH);
228 }
229 }
230 // Handle degenerate case where import statement does not have a semicolon
231 // and the cursor is in the uri string
232 if ((entity == node.semicolon &&
233 node.uri != null &&
234 node.uri.offset + 1 != request.offset) ||
235 node.combinators.contains(entity)) {
236 _addImportDirectiveKeywords(node);
237 }
238 }
239
240 @override
241 visitInstanceCreationExpression(InstanceCreationExpression node) {
242 if (entity == node.constructorName) {
243 // no keywords in 'new ^' expression
244 } else {
245 super.visitInstanceCreationExpression(node);
246 }
247 }
248
249 @override
250 visitIsExpression(IsExpression node) {
251 if (entity == node.isOperator) {
252 _addSuggestion(Keyword.IS, DART_RELEVANCE_HIGH);
253 } else {
254 _addExpressionKeywords(node);
255 }
256 }
257
258 @override
259 visitLibraryIdentifier(LibraryIdentifier node) {
260 // no suggestions
261 }
262
263 @override
264 visitMethodDeclaration(MethodDeclaration node) {
265 if (entity == node.body) {
266 if (node.body is EmptyFunctionBody) {
267 _addClassBodyKeywords();
268 _addSuggestion2(ASYNC);
269 } else {
270 _addSuggestion2(ASYNC, relevance: DART_RELEVANCE_HIGH);
271 }
272 }
273 }
274
275 @override
276 visitMethodInvocation(MethodInvocation node) {
277 if (entity == node.methodName) {
278 // no keywords in '.' expression
279 } else {
280 super.visitMethodInvocation(node);
281 }
282 }
283
284 @override
285 visitNamedExpression(NamedExpression node) {
286 if (entity is SimpleIdentifier && entity == node.expression) {
287 _addExpressionKeywords(node);
288 }
289 }
290
291 @override
292 visitNode(AstNode node) {
293 // ignored
294 }
295
296 @override
297 visitPrefixedIdentifier(PrefixedIdentifier node) {
298 if (entity != node.identifier) {
299 _addExpressionKeywords(node);
300 }
301 }
302
303 @override
304 visitPropertyAccess(PropertyAccess node) {
305 // suggestions before '.' but not after
306 if (entity != node.propertyName) {
307 super.visitPropertyAccess(node);
308 }
309 }
310
311 @override
312 visitReturnStatement(ReturnStatement node) {
313 if (entity == node.expression) {
314 _addExpressionKeywords(node);
315 }
316 }
317
318 @override
319 visitStringLiteral(StringLiteral node) {
320 // ignored
321 }
322
323 @override
324 visitSwitchStatement(SwitchStatement node) {
325 if (entity == node.expression) {
326 _addExpressionKeywords(node);
327 } else if (entity == node.rightBracket) {
328 if (node.members.isEmpty) {
329 _addSuggestions([Keyword.CASE, Keyword.DEFAULT], DART_RELEVANCE_HIGH);
330 } else {
331 _addSuggestions([Keyword.CASE, Keyword.DEFAULT]);
332 _addStatementKeywords(node);
333 }
334 }
335 if (node.members.contains(entity)) {
336 if (entity == node.members.first) {
337 _addSuggestions([Keyword.CASE, Keyword.DEFAULT], DART_RELEVANCE_HIGH);
338 } else {
339 _addSuggestions([Keyword.CASE, Keyword.DEFAULT]);
340 _addStatementKeywords(node);
341 }
342 }
343 }
344
345 @override
346 visitVariableDeclaration(VariableDeclaration node) {
347 if (entity == node.initializer) {
348 _addExpressionKeywords(node);
349 }
350 }
351
352 void _addClassBodyKeywords() {
353 _addSuggestions([
354 Keyword.CONST,
355 Keyword.DYNAMIC,
356 Keyword.FACTORY,
357 Keyword.FINAL,
358 Keyword.GET,
359 Keyword.OPERATOR,
360 Keyword.SET,
361 Keyword.STATIC,
362 Keyword.VAR,
363 Keyword.VOID
364 ]);
365 }
366
367 void _addClassDeclarationKeywords(ClassDeclaration node) {
368 // Very simplistic suggestion because analyzer will warn if
369 // the extends / with / implements keywords are out of order
370 if (node.extendsClause == null) {
371 _addSuggestion(Keyword.EXTENDS, DART_RELEVANCE_HIGH);
372 } else if (node.withClause == null) {
373 _addSuggestion(Keyword.WITH, DART_RELEVANCE_HIGH);
374 }
375 if (node.implementsClause == null) {
376 _addSuggestion(Keyword.IMPLEMENTS, DART_RELEVANCE_HIGH);
377 }
378 }
379
380 void _addCompilationUnitKeywords() {
381 _addSuggestions([
382 Keyword.ABSTRACT,
383 Keyword.CLASS,
384 Keyword.CONST,
385 Keyword.DYNAMIC,
386 Keyword.FINAL,
387 Keyword.TYPEDEF,
388 Keyword.VAR,
389 Keyword.VOID
390 ], DART_RELEVANCE_HIGH);
391 }
392
393 void _addExpressionKeywords(AstNode node) {
394 _addSuggestions([
395 Keyword.CONST,
396 Keyword.FALSE,
397 Keyword.NEW,
398 Keyword.NULL,
399 Keyword.TRUE,
400 ]);
401 if (_inClassMemberBody(node)) {
402 _addSuggestions([Keyword.SUPER, Keyword.THIS,]);
403 }
404 if (_inAsyncMethodOrFunction(node)) {
405 _addSuggestion2(AWAIT);
406 }
407 }
408
409 void _addImportDirectiveKeywords(ImportDirective node) {
410 bool hasDeferredKeyword = node.deferredKeyword != null;
411 bool hasAsKeyword = node.asKeyword != null;
412 if (!hasAsKeyword) {
413 _addSuggestion(Keyword.AS, DART_RELEVANCE_HIGH);
414 }
415 if (!hasDeferredKeyword) {
416 if (!hasAsKeyword) {
417 _addSuggestion2('deferred as', relevance: DART_RELEVANCE_HIGH);
418 } else if (entity == node.asKeyword) {
419 _addSuggestion(Keyword.DEFERRED, DART_RELEVANCE_HIGH);
420 }
421 }
422 if (!hasDeferredKeyword || hasAsKeyword) {
423 if (node.combinators.isEmpty) {
424 _addSuggestion2('show', relevance: DART_RELEVANCE_HIGH);
425 _addSuggestion2('hide', relevance: DART_RELEVANCE_HIGH);
426 }
427 }
428 }
429
430 void _addStatementKeywords(AstNode node) {
431 if (_inClassMemberBody(node)) {
432 _addSuggestions([Keyword.SUPER, Keyword.THIS,]);
433 }
434 if (_inAsyncMethodOrFunction(node)) {
435 _addSuggestion2(AWAIT);
436 }
437 if (_inLoop(node)) {
438 _addSuggestions([Keyword.BREAK, Keyword.CONTINUE]);
439 }
440 if (_inSwitch(node)) {
441 _addSuggestions([Keyword.BREAK]);
442 }
443 _addSuggestions([
444 Keyword.ASSERT,
445 Keyword.CONST,
446 Keyword.DO,
447 Keyword.FINAL,
448 Keyword.FOR,
449 Keyword.IF,
450 Keyword.NEW,
451 Keyword.RETURN,
452 Keyword.SWITCH,
453 Keyword.THROW,
454 Keyword.TRY,
455 Keyword.VAR,
456 Keyword.VOID,
457 Keyword.WHILE
458 ]);
459 }
460
461 void _addSuggestion(Keyword keyword,
462 [int relevance = DART_RELEVANCE_KEYWORD]) {
463 _addSuggestion2(keyword.syntax, relevance: relevance);
464 }
465
466 void _addSuggestion2(String completion,
467 {int offset, int relevance: DART_RELEVANCE_KEYWORD}) {
468 if (offset == null) {
469 offset = completion.length;
470 }
471 request.addSuggestion(new CompletionSuggestion(
472 CompletionSuggestionKind.KEYWORD,
473 relevance,
474 completion,
475 offset,
476 0,
477 false,
478 false));
479 }
480
481 void _addSuggestions(List<Keyword> keywords,
482 [int relevance = DART_RELEVANCE_KEYWORD]) {
483 keywords.forEach((Keyword keyword) {
484 _addSuggestion(keyword, relevance);
485 });
486 }
487
488 bool _inAsyncMethodOrFunction(AstNode node) {
489 FunctionBody body = node.getAncestor((n) => n is FunctionBody);
490 return body != null && body.isAsynchronous;
491 }
492
493 bool _inCatchClause(Block node) =>
494 node.getAncestor((p) => p is CatchClause) != null;
495
496 bool _inClassMemberBody(AstNode node) {
497 while (true) {
498 AstNode body = node.getAncestor((n) => n is FunctionBody);
499 if (body == null) {
500 return false;
501 }
502 AstNode parent = body.parent;
503 if (parent is ConstructorDeclaration || parent is MethodDeclaration) {
504 return true;
505 }
506 node = parent;
507 }
508 }
509
510 bool _inDoLoop(AstNode node) =>
511 node.getAncestor((p) => p is DoStatement) != null;
512
513 bool _inForLoop(AstNode node) =>
514 node.getAncestor((p) => p is ForStatement || p is ForEachStatement) !=
515 null;
516
517 bool _inLoop(AstNode node) =>
518 _inDoLoop(node) || _inForLoop(node) || _inWhileLoop(node);
519
520 bool _inSwitch(AstNode node) =>
521 node.getAncestor((p) => p is SwitchStatement) != null;
522
523 bool _inWhileLoop(AstNode node) =>
524 node.getAncestor((p) => p is WhileStatement) != null;
525 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698