| OLD | NEW |
| (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 } | |
| OLD | NEW |