| OLD | NEW |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library services.src.completion.statement; | 5 library services.src.completion.statement; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:math'; |
| 8 | 9 |
| 9 import 'package:analysis_server/protocol/protocol_generated.dart'; | 10 import 'package:analysis_server/protocol/protocol_generated.dart'; |
| 10 import 'package:analysis_server/src/protocol_server.dart' hide Element; | 11 import 'package:analysis_server/src/protocol_server.dart' hide Element; |
| 11 import 'package:analysis_server/src/services/correction/source_buffer.dart'; | 12 import 'package:analysis_server/src/services/correction/source_buffer.dart'; |
| 12 import 'package:analysis_server/src/services/correction/source_range.dart'; | 13 import 'package:analysis_server/src/services/correction/source_range.dart'; |
| 13 import 'package:analysis_server/src/services/correction/util.dart'; | 14 import 'package:analysis_server/src/services/correction/util.dart'; |
| 14 import 'package:analyzer/dart/ast/ast.dart'; | 15 import 'package:analyzer/dart/ast/ast.dart'; |
| 15 import 'package:analyzer/dart/ast/token.dart'; | 16 import 'package:analyzer/dart/ast/token.dart'; |
| 16 import 'package:analyzer/dart/element/element.dart'; | 17 import 'package:analyzer/dart/element/element.dart'; |
| 17 import 'package:analyzer/error/error.dart'; | 18 import 'package:analyzer/error/error.dart'; |
| 18 import 'package:analyzer/error/error.dart' as engine; | 19 import 'package:analyzer/error/error.dart' as engine; |
| 19 import 'package:analyzer/src/dart/ast/utilities.dart'; | 20 import 'package:analyzer/src/dart/ast/utilities.dart'; |
| 20 import 'package:analyzer/src/dart/error/hint_codes.dart'; | 21 import 'package:analyzer/src/dart/error/hint_codes.dart'; |
| 21 import 'package:analyzer/src/dart/error/syntactic_errors.dart'; | 22 import 'package:analyzer/src/dart/error/syntactic_errors.dart'; |
| 23 import 'package:analyzer/src/error/codes.dart'; |
| 22 import 'package:analyzer/src/generated/engine.dart'; | 24 import 'package:analyzer/src/generated/engine.dart'; |
| 23 import 'package:analyzer/src/generated/java_core.dart'; | 25 import 'package:analyzer/src/generated/java_core.dart'; |
| 24 import 'package:analyzer/src/generated/source.dart'; | 26 import 'package:analyzer/src/generated/source.dart'; |
| 25 | 27 |
| 26 /** | 28 /** |
| 27 * An enumeration of possible statement completion kinds. | 29 * An enumeration of possible statement completion kinds. |
| 28 */ | 30 */ |
| 29 class DartStatementCompletion { | 31 class DartStatementCompletion { |
| 30 static const NO_COMPLETION = | 32 static const NO_COMPLETION = |
| 31 const StatementCompletionKind('No_COMPLETION', 'No completion available'); | 33 const StatementCompletionKind('No_COMPLETION', 'No completion available'); |
| (...skipping 162 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 194 } | 196 } |
| 195 for (engine.AnalysisError error in statementContext.errors) { | 197 for (engine.AnalysisError error in statementContext.errors) { |
| 196 if (error.offset >= node.offset && | 198 if (error.offset >= node.offset && |
| 197 error.offset <= node.offset + node.length) { | 199 error.offset <= node.offset + node.length) { |
| 198 if (error.errorCode is! HintCode) { | 200 if (error.errorCode is! HintCode) { |
| 199 errors.add(error); | 201 errors.add(error); |
| 200 } | 202 } |
| 201 } | 203 } |
| 202 } | 204 } |
| 203 | 205 |
| 206 _checkExpressions(); |
| 204 if (node is Statement) { | 207 if (node is Statement) { |
| 205 if (errors.isEmpty) { | 208 if (errors.isEmpty) { |
| 206 if (_complete_ifStatement() || | 209 if (_complete_ifStatement() || |
| 207 _complete_forStatement() || | 210 _complete_forStatement() || |
| 208 _complete_forEachStatement() || | 211 _complete_forEachStatement() || |
| 209 _complete_whileStatement() || | 212 _complete_whileStatement() || |
| 210 _complete_controlFlowBlock()) { | 213 _complete_controlFlowBlock()) { |
| 211 return completion; | 214 return completion; |
| 212 } | 215 } |
| 213 } else { | 216 } else { |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 278 } | 281 } |
| 279 | 282 |
| 280 String _baseNodeText(AstNode astNode) { | 283 String _baseNodeText(AstNode astNode) { |
| 281 String text = utils.getNodeText(astNode); | 284 String text = utils.getNodeText(astNode); |
| 282 if (text.endsWith(eol)) { | 285 if (text.endsWith(eol)) { |
| 283 text = text.substring(0, text.length - eol.length); | 286 text = text.substring(0, text.length - eol.length); |
| 284 } | 287 } |
| 285 return text; | 288 return text; |
| 286 } | 289 } |
| 287 | 290 |
| 291 void _checkExpressions() { |
| 292 // Note: This may queue edits that have to be accounted for later. |
| 293 // See _lengthOfInsertions(). |
| 294 AstNode errorMatching(errorCode, {partialMatch = null}) { |
| 295 var error = _findError(errorCode, partialMatch: partialMatch); |
| 296 if (error == null) { |
| 297 return null; |
| 298 } |
| 299 AstNode expr = _selectedNode(); |
| 300 return (expr.getAncestor((n) => n is StringInterpolation) == null) |
| 301 ? expr |
| 302 : null; |
| 303 } |
| 304 |
| 305 var expr = errorMatching(ScannerErrorCode.UNTERMINATED_STRING_LITERAL); |
| 306 if (expr != null) { |
| 307 String source = utils.getNodeText(expr); |
| 308 String content = source; |
| 309 int char = content.codeUnitAt(0); |
| 310 if (char == 'r'.codeUnitAt(0)) { |
| 311 content = source.substring(1); |
| 312 char = content.codeUnitAt(0); |
| 313 } |
| 314 String delimiter; |
| 315 int loc; |
| 316 if (content.length >= 3 && |
| 317 char == content.codeUnitAt(1) && |
| 318 char == content.codeUnitAt(2)) { |
| 319 // multi-line string |
| 320 delimiter = content.substring(0, 3); |
| 321 int newlineLoc = source.indexOf(eol, selectionOffset - expr.offset); |
| 322 if (newlineLoc < 0) { |
| 323 newlineLoc = source.length; |
| 324 } |
| 325 loc = newlineLoc + expr.offset; |
| 326 } else { |
| 327 // add first char of src |
| 328 delimiter = content.substring(0, 1); |
| 329 loc = expr.offset + source.length; |
| 330 } |
| 331 _removeError(ScannerErrorCode.UNTERMINATED_STRING_LITERAL); |
| 332 _addInsertEdit(loc, delimiter); |
| 333 } |
| 334 expr = errorMatching(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "']'"); |
| 335 if (expr != null) { |
| 336 expr = expr.getAncestor((n) => n is ListLiteral); |
| 337 if (expr != null) { |
| 338 ListLiteral lit = expr; |
| 339 if (lit.rightBracket.isSynthetic) { |
| 340 String src = utils.getNodeText(expr).trim(); |
| 341 int loc = expr.offset + src.length; |
| 342 if (src.contains(eol)) { |
| 343 String indent = utils.getNodePrefix(node); |
| 344 _addInsertEdit(loc, ',' + eol + indent + ']'); |
| 345 } else { |
| 346 _addInsertEdit(loc, ']'); |
| 347 } |
| 348 _removeError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "']'"); |
| 349 var ms = |
| 350 _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'"); |
| 351 if (ms != null) { |
| 352 // Ensure the semicolon gets inserted in the correct location. |
| 353 ms.offset = loc - 1; |
| 354 } |
| 355 } |
| 356 } |
| 357 } |
| 358 // The following code is similar to the code for ']' but does not work well. |
| 359 // A closing brace is recognized as belong to the map even if it is intended |
| 360 // to close a block of code. |
| 361 /* |
| 362 expr = errorMatching(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "'}'"); |
| 363 if (expr != null) { |
| 364 expr = expr.getAncestor((n) => n is MapLiteral); |
| 365 if (expr != null) { |
| 366 MapLiteral lit = expr; |
| 367 String src = utils.getNodeText(expr).trim(); |
| 368 int loc = expr.offset + src.length; |
| 369 if (lit.entries.last.separator.isSynthetic) { |
| 370 _addInsertEdit(loc, ': '); |
| 371 } |
| 372 if (!src.endsWith('}')/*lit.rightBracket.isSynthetic*/) { |
| 373 _addInsertEdit(loc, '}'); |
| 374 } |
| 375 _removeError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "'}'"); |
| 376 var ms = |
| 377 _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'"); |
| 378 if (ms != null) { |
| 379 // Ensure the semicolon gets inserted in the correct location. |
| 380 ms.offset = loc - 1; |
| 381 } |
| 382 } |
| 383 } |
| 384 */ |
| 385 } |
| 386 |
| 288 bool _complete_classDeclaration() { | 387 bool _complete_classDeclaration() { |
| 289 if (node is! ClassDeclaration) { | 388 if (node is! ClassDeclaration) { |
| 290 return false; | 389 return false; |
| 291 } | 390 } |
| 292 ClassDeclaration decl = node; | 391 ClassDeclaration decl = node; |
| 293 if (decl.leftBracket.isSynthetic && errors.length == 1) { | 392 if (decl.leftBracket.isSynthetic && errors.length == 1) { |
| 294 // The space before the left brace is assumed to exist, even if it does no
t. | 393 // The space before the left brace is assumed to exist, even if it does no
t. |
| 295 SourceBuilder sb = new SourceBuilder(file, decl.end - 1); | 394 SourceBuilder sb = new SourceBuilder(file, decl.end - 1); |
| 296 sb.append(' '); | 395 sb.append(' '); |
| 297 _appendEmptyBraces(sb, true); | 396 _appendEmptyBraces(sb, true); |
| (...skipping 17 matching lines...) Expand all Loading... |
| 315 return false; | 414 return false; |
| 316 } | 415 } |
| 317 AstNode outer = node.parent.parent; | 416 AstNode outer = node.parent.parent; |
| 318 if (!(outer is DoStatement || | 417 if (!(outer is DoStatement || |
| 319 outer is ForStatement || | 418 outer is ForStatement || |
| 320 outer is ForEachStatement || | 419 outer is ForEachStatement || |
| 321 outer is IfStatement || | 420 outer is IfStatement || |
| 322 outer is WhileStatement)) { | 421 outer is WhileStatement)) { |
| 323 return false; | 422 return false; |
| 324 } | 423 } |
| 424 int previousInsertions = _lengthOfInsertions(); |
| 325 int delta = 0; | 425 int delta = 0; |
| 326 if (errors.isNotEmpty) { | 426 if (errors.isNotEmpty) { |
| 327 var error = | 427 var error = |
| 328 _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'"); | 428 _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'"); |
| 329 if (error != null) { | 429 if (error != null) { |
| 330 int insertOffset; | 430 int insertOffset; |
| 331 if (expr == null || expr.isSynthetic) { | 431 if (expr == null || expr.isSynthetic) { |
| 332 if (node is ReturnStatement) { | 432 if (node is ReturnStatement) { |
| 333 insertOffset = (node as ReturnStatement).returnKeyword.end; | 433 insertOffset = (node as ReturnStatement).returnKeyword.end; |
| 334 } else if (node is ExpressionStatement) { | 434 } else if (node is ExpressionStatement) { |
| 335 insertOffset = | 435 insertOffset = |
| 336 ((node as ExpressionStatement).expression as ThrowExpression) | 436 ((node as ExpressionStatement).expression as ThrowExpression) |
| 337 .throwKeyword | 437 .throwKeyword |
| 338 .end; | 438 .end; |
| 339 } else { | 439 } else { |
| 340 insertOffset = node.end; // Not reached. | 440 insertOffset = node.end; // Not reached. |
| 341 } | 441 } |
| 342 } else { | 442 } else { |
| 343 insertOffset = expr.end; | 443 insertOffset = expr.end; |
| 344 } | 444 } |
| 345 //TODO(messick) Uncomment the following line when error location is fixe
d. | 445 //TODO(messick) Uncomment the following line when error location is fixe
d. |
| 346 //insertOffset = error.offset + error.length; | 446 //insertOffset = error.offset + error.length; |
| 347 _addInsertEdit(insertOffset, ';'); | 447 _addInsertEdit(insertOffset, ';'); |
| 348 delta = 1; | 448 delta = 1; |
| 349 } | 449 } |
| 350 } | 450 } |
| 351 int offset = _appendNewlinePlusIndentAt(node.parent.end); | 451 int offset = _appendNewlinePlusIndentAt(node.parent.end); |
| 352 exitPosition = new Position(file, offset + delta); | 452 exitPosition = new Position(file, offset + delta + previousInsertions); |
| 353 _setCompletion(DartStatementCompletion.COMPLETE_CONTROL_FLOW_BLOCK); | 453 _setCompletion(DartStatementCompletion.COMPLETE_CONTROL_FLOW_BLOCK); |
| 354 return true; | 454 return true; |
| 355 } | 455 } |
| 356 | 456 |
| 357 bool _complete_doStatement() { | 457 bool _complete_doStatement() { |
| 358 if (node is! DoStatement) { | 458 if (node is! DoStatement) { |
| 359 return false; | 459 return false; |
| 360 } | 460 } |
| 361 DoStatement statement = node; | 461 DoStatement statement = node; |
| 362 SourceBuilder sb = _sourceBuilderAfterKeyword(statement.doKeyword); | 462 SourceBuilder sb = _sourceBuilderAfterKeyword(statement.doKeyword); |
| (...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 478 _setCompletion(DartStatementCompletion.COMPLETE_FOR_EACH_STMT); | 578 _setCompletion(DartStatementCompletion.COMPLETE_FOR_EACH_STMT); |
| 479 return true; | 579 return true; |
| 480 } | 580 } |
| 481 | 581 |
| 482 bool _complete_forStatement() { | 582 bool _complete_forStatement() { |
| 483 if (node is! ForStatement) { | 583 if (node is! ForStatement) { |
| 484 return false; | 584 return false; |
| 485 } | 585 } |
| 486 ForStatement forNode = node; | 586 ForStatement forNode = node; |
| 487 SourceBuilder sb; | 587 SourceBuilder sb; |
| 488 int delta = 0; | 588 int replacementLength = 0; |
| 489 if (forNode.leftParenthesis.isSynthetic) { | 589 if (forNode.leftParenthesis.isSynthetic) { |
| 490 if (!forNode.rightParenthesis.isSynthetic) { | 590 if (!forNode.rightParenthesis.isSynthetic) { |
| 491 return false; | 591 return false; |
| 492 } | 592 } |
| 493 // keywordOnly (unit test name suffix that exercises this branch) | 593 // keywordOnly (unit test name suffix that exercises this branch) |
| 494 sb = _sourceBuilderAfterKeyword(forNode.forKeyword); | 594 sb = _sourceBuilderAfterKeyword(forNode.forKeyword); |
| 495 sb.append('('); | 595 sb.append('('); |
| 496 sb.setExitOffset(); | 596 sb.setExitOffset(); |
| 497 sb.append(')'); | 597 sb.append(')'); |
| 498 } else { | 598 } else { |
| 499 if (!forNode.rightSeparator.isSynthetic) { | 599 if (!forNode.rightSeparator.isSynthetic) { |
| 500 // Fully-defined init, cond, updaters so nothing more needed here. | 600 // Fully-defined init, cond, updaters so nothing more needed here. |
| 501 // emptyParts | 601 // emptyParts, noError |
| 502 sb = new SourceBuilder(file, forNode.rightParenthesis.offset + 1); | 602 sb = new SourceBuilder(file, forNode.rightParenthesis.offset + 1); |
| 503 } else if (!forNode.leftSeparator.isSynthetic) { | 603 } else if (!forNode.leftSeparator.isSynthetic) { |
| 504 if (_isSyntheticExpression(forNode.condition)) { | 604 if (_isSyntheticExpression(forNode.condition)) { |
| 505 exitPosition = _newPosition(forNode.leftSeparator.offset + 1); | |
| 506 String text = utils | 605 String text = utils |
| 507 .getNodeText(forNode) | 606 .getNodeText(forNode) |
| 508 .substring(forNode.leftSeparator.offset - forNode.offset); | 607 .substring(forNode.leftSeparator.offset - forNode.offset); |
| 509 if (text.startsWith(new RegExp(r';\s*\)'))) { | 608 Match match = |
| 510 // emptyCondition | 609 new RegExp(r';\s*(/\*.*\*/\s*)?\)[ \t]*').matchAsPrefix(text); |
| 511 int end = text.indexOf(')'); | 610 if (match != null) { |
| 611 // emptyCondition, emptyInitializersEmptyCondition |
| 612 replacementLength = match.end - match.start; |
| 512 sb = new SourceBuilder(file, forNode.leftSeparator.offset); | 613 sb = new SourceBuilder(file, forNode.leftSeparator.offset); |
| 513 _addReplaceEdit(rangeStartLength(sb.offset, end), '; ; '); | 614 sb.append('; ${match.group(1) == null ? '' : match.group(1)}; )'); |
| 514 delta = end - '; '.length; | 615 String suffix = text.substring(match.end); |
| 616 if (suffix.trim().isNotEmpty) { |
| 617 sb.append(' '); |
| 618 sb.append(suffix.trim()); |
| 619 replacementLength += suffix.length; |
| 620 if (suffix.endsWith(eol)) { |
| 621 // emptyCondition |
| 622 replacementLength -= eol.length; |
| 623 } |
| 624 } |
| 625 exitPosition = _newPosition(forNode.leftSeparator.offset + 2); |
| 515 } else { | 626 } else { |
| 516 // emptyInitializersEmptyCondition | 627 return false; // Line comment in condition |
| 517 exitPosition = _newPosition(forNode.rightParenthesis.offset); | |
| 518 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); | |
| 519 } | 628 } |
| 520 } else { | 629 } else { |
| 521 // emptyUpdaters | 630 // emptyUpdaters |
| 522 exitPosition = _newPosition(forNode.rightSeparator.offset); | 631 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); |
| 523 sb = new SourceBuilder(file, forNode.rightSeparator.offset); | 632 replacementLength = 1; |
| 524 _addReplaceEdit(rangeStartLength(sb.offset, 0), '; '); | 633 sb.append('; )'); |
| 525 delta = -'; '.length; | 634 exitPosition = _newPosition(forNode.rightSeparator.offset + 2); |
| 526 } | 635 } |
| 527 } else if (_isSyntheticExpression(forNode.initialization)) { | 636 } else if (_isSyntheticExpression(forNode.initialization)) { |
| 528 // emptyInitializers | 637 // emptyInitializers |
| 529 exitPosition = _newPosition(forNode.rightParenthesis.offset); | 638 exitPosition = _newPosition(forNode.rightParenthesis.offset); |
| 530 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); | 639 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); |
| 531 } else { | 640 } else { |
| 532 int start = forNode.condition.offset + forNode.condition.length; | 641 int start = forNode.condition.offset + forNode.condition.length; |
| 533 String text = | 642 String text = |
| 534 utils.getNodeText(forNode).substring(start - forNode.offset); | 643 utils.getNodeText(forNode).substring(start - forNode.offset); |
| 535 if (text.startsWith(new RegExp(r'\s*\)'))) { | 644 if (text.startsWith(new RegExp(r'\s*\)'))) { |
| 536 // missingLeftSeparator | 645 // missingLeftSeparator |
| 537 int end = text.indexOf(')'); | 646 int end = text.indexOf(')'); |
| 538 sb = new SourceBuilder(file, start); | 647 sb = new SourceBuilder(file, start); |
| 539 _addReplaceEdit(rangeStartLength(start, end), '; '); | 648 _addReplaceEdit(rangeStartLength(start, end), '; ; '); |
| 540 delta = end - '; '.length; | 649 exitPosition = new Position(file, start - (end - '; '.length)); |
| 541 exitPosition = new Position(file, start); | |
| 542 } else { | 650 } else { |
| 543 // Not possible; any comment following init is attached to init. | 651 // Not possible; any comment following init is attached to init. |
| 544 exitPosition = _newPosition(forNode.rightParenthesis.offset); | 652 exitPosition = _newPosition(forNode.rightParenthesis.offset); |
| 545 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); | 653 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); |
| 546 } | 654 } |
| 547 } | 655 } |
| 548 } | 656 } |
| 549 if (!_statementHasValidBody(forNode.forKeyword, forNode.body)) { | 657 if (!_statementHasValidBody(forNode.forKeyword, forNode.body)) { |
| 550 // keywordOnly, noError | 658 // keywordOnly, noError |
| 551 sb.append(' '); | 659 sb.append(' '); |
| 552 _appendEmptyBraces(sb, exitPosition == null); | 660 _appendEmptyBraces(sb, true /*exitPosition == null*/); |
| 661 } else if (forNode.body is Block) { |
| 662 Block body = forNode.body; |
| 663 if (body.rightBracket.end <= selectionOffset) { |
| 664 // emptyInitializersAfterBody |
| 665 errors = []; // Ignore errors; they are for previous statement. |
| 666 return false; // If cursor is after closing brace just add newline. |
| 667 } |
| 553 } | 668 } |
| 554 if (delta != 0 && exitPosition != null) { | 669 _insertBuilder(sb, replacementLength); |
| 555 // missingLeftSeparator | |
| 556 exitPosition = new Position(file, exitPosition.offset - delta); | |
| 557 } | |
| 558 _insertBuilder(sb); | |
| 559 _setCompletion(DartStatementCompletion.COMPLETE_FOR_STMT); | 670 _setCompletion(DartStatementCompletion.COMPLETE_FOR_STMT); |
| 560 return true; | 671 return true; |
| 561 } | 672 } |
| 562 | 673 |
| 563 bool _complete_functionDeclaration() { | 674 bool _complete_functionDeclaration() { |
| 564 if (node is! MethodDeclaration && node is! FunctionDeclaration) { | 675 if (node is! MethodDeclaration && node is! FunctionDeclaration) { |
| 565 return false; | 676 return false; |
| 566 } | 677 } |
| 567 SourceBuilder sb = new SourceBuilder(file, node.end - 1); | 678 bool needsParen = false; |
| 679 int computeExitPos(FormalParameterList parameters) { |
| 680 if (needsParen = parameters.rightParenthesis.isSynthetic) { |
| 681 var error = _findError(ParserErrorCode.MISSING_CLOSING_PARENTHESIS); |
| 682 if (error != null) { |
| 683 return error.offset - 1; |
| 684 } |
| 685 } |
| 686 return node.end - 1; |
| 687 } |
| 688 |
| 689 int paramListEnd; |
| 690 if (node is FunctionDeclaration) { |
| 691 FunctionDeclaration func = node; |
| 692 paramListEnd = computeExitPos(func.functionExpression.parameters); |
| 693 } else { |
| 694 MethodDeclaration meth = node; |
| 695 paramListEnd = computeExitPos(meth.parameters); |
| 696 } |
| 697 SourceBuilder sb = new SourceBuilder(file, paramListEnd); |
| 698 if (needsParen) { |
| 699 sb.append(')'); |
| 700 } |
| 568 sb.append(' '); | 701 sb.append(' '); |
| 569 _appendEmptyBraces(sb, true); | 702 _appendEmptyBraces(sb, true); |
| 570 _insertBuilder(sb); | 703 _insertBuilder(sb); |
| 571 _setCompletion(DartStatementCompletion.COMPLETE_FUNCTION_DECLARATION); | 704 _setCompletion(DartStatementCompletion.COMPLETE_FUNCTION_DECLARATION); |
| 572 return true; | 705 return true; |
| 573 } | 706 } |
| 574 | 707 |
| 575 bool _complete_functionDeclarationStatement() { | 708 bool _complete_functionDeclarationStatement() { |
| 576 if (node is! FunctionDeclarationStatement) { | 709 if (node is! FunctionDeclarationStatement) { |
| 577 return false; | 710 return false; |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 613 | 746 |
| 614 bool _complete_ifOrWhileStatement( | 747 bool _complete_ifOrWhileStatement( |
| 615 _KeywordConditionBlockStructure statement, StatementCompletionKind kind) { | 748 _KeywordConditionBlockStructure statement, StatementCompletionKind kind) { |
| 616 if (_statementHasValidBody(statement.keyword, statement.block)) { | 749 if (_statementHasValidBody(statement.keyword, statement.block)) { |
| 617 return false; | 750 return false; |
| 618 } | 751 } |
| 619 SourceBuilder sb = _complete_keywordCondition(statement); | 752 SourceBuilder sb = _complete_keywordCondition(statement); |
| 620 if (sb == null) { | 753 if (sb == null) { |
| 621 return false; | 754 return false; |
| 622 } | 755 } |
| 756 int overshoot = _lengthOfDeletions(); |
| 623 sb.append(' '); | 757 sb.append(' '); |
| 624 _appendEmptyBraces(sb, exitPosition == null); | 758 _appendEmptyBraces(sb, exitPosition == null); |
| 625 _insertBuilder(sb); | 759 _insertBuilder(sb); |
| 760 if (overshoot != 0) { |
| 761 exitPosition = _newPosition(exitPosition.offset - overshoot); |
| 762 } |
| 626 _setCompletion(kind); | 763 _setCompletion(kind); |
| 627 return true; | 764 return true; |
| 628 } | 765 } |
| 629 | 766 |
| 630 bool _complete_ifStatement() { | 767 bool _complete_ifStatement() { |
| 631 if (node is! IfStatement) { | 768 if (node is! IfStatement) { |
| 632 return false; | 769 return false; |
| 633 } | 770 } |
| 634 IfStatement ifNode = node; | 771 IfStatement ifNode = node; |
| 635 if (ifNode.elseKeyword != null) { | 772 if (ifNode.elseKeyword != null) { |
| 773 if (selectionOffset >= ifNode.elseKeyword.end && |
| 774 ifNode.elseStatement is EmptyStatement) { |
| 775 SourceBuilder sb = new SourceBuilder(file, selectionOffset); |
| 776 String src = utils.getNodeText(ifNode); |
| 777 if (!src |
| 778 .substring(ifNode.elseKeyword.end - node.offset) |
| 779 .startsWith(new RegExp(r'[ \t]'))) { |
| 780 sb.append(' '); |
| 781 } |
| 782 _appendEmptyBraces(sb, true); |
| 783 _insertBuilder(sb); |
| 784 _setCompletion(DartStatementCompletion.COMPLETE_IF_STMT); |
| 785 return true; |
| 786 } |
| 636 return false; | 787 return false; |
| 637 } | 788 } |
| 638 var stmt = new _KeywordConditionBlockStructure( | 789 var stmt = new _KeywordConditionBlockStructure( |
| 639 ifNode.ifKeyword, | 790 ifNode.ifKeyword, |
| 640 ifNode.leftParenthesis, | 791 ifNode.leftParenthesis, |
| 641 ifNode.condition, | 792 ifNode.condition, |
| 642 ifNode.rightParenthesis, | 793 ifNode.rightParenthesis, |
| 643 ifNode.thenStatement); | 794 ifNode.thenStatement); |
| 644 return _complete_ifOrWhileStatement( | 795 return _complete_ifOrWhileStatement( |
| 645 stmt, DartStatementCompletion.COMPLETE_IF_STMT); | 796 stmt, DartStatementCompletion.COMPLETE_IF_STMT); |
| 646 } | 797 } |
| 647 | 798 |
| 648 SourceBuilder _complete_keywordCondition( | 799 SourceBuilder _complete_keywordCondition( |
| 649 _KeywordConditionBlockStructure statement) { | 800 _KeywordConditionBlockStructure statement) { |
| 650 SourceBuilder sb; | 801 SourceBuilder sb; |
| 651 if (statement.leftParenthesis.isSynthetic) { | 802 if (statement.leftParenthesis.isSynthetic) { |
| 652 if (!statement.rightParenthesis.isSynthetic) { | 803 if (!statement.rightParenthesis.isSynthetic) { |
| 653 // Quite unlikely to see this so don't try to fix it. | 804 // Quite unlikely to see this so don't try to fix it. |
| 654 return null; | 805 return null; |
| 655 } | 806 } |
| 656 sb = _sourceBuilderAfterKeyword(statement.keyword); | 807 sb = _sourceBuilderAfterKeyword(statement.keyword); |
| 657 sb.append('('); | 808 sb.append('('); |
| 658 sb.setExitOffset(); | 809 sb.setExitOffset(); |
| 659 sb.append(')'); | 810 sb.append(')'); |
| 660 } else { | 811 } else { |
| 661 if (_isSyntheticExpression(statement.condition)) { | 812 if (_isSyntheticExpression(statement.condition)) { |
| 662 exitPosition = _newPosition(statement.leftParenthesis.offset + 1); | 813 exitPosition = _newPosition(statement.leftParenthesis.offset + 1); |
| 663 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); | 814 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); |
| 664 } else { | 815 } else { |
| 665 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); | 816 int afterParen = statement.rightParenthesis.offset + 1; |
| 817 if (utils |
| 818 .getNodeText(node) |
| 819 .substring(afterParen - node.offset) |
| 820 .startsWith(new RegExp(r'[ \t]'))) { |
| 821 _addReplaceEdit(rangeStartLength(afterParen, 1), ''); |
| 822 sb = new SourceBuilder(file, afterParen + 1); |
| 823 } else { |
| 824 sb = new SourceBuilder(file, afterParen); |
| 825 } |
| 666 } | 826 } |
| 667 } | 827 } |
| 668 return sb; | 828 return sb; |
| 669 } | 829 } |
| 670 | 830 |
| 671 bool _complete_methodCall() { | 831 bool _complete_methodCall() { |
| 672 var parenError = | 832 var parenError = |
| 673 _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "')'"); | 833 _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "')'"); |
| 674 if (parenError == null) { | 834 if (parenError == null) { |
| 675 return false; | 835 return false; |
| 676 } | 836 } |
| 677 AstNode argList = _selectedNode(at: parenError.offset - 1) | 837 AstNode argList = _selectedNode(at: selectionOffset) |
| 678 .getAncestor((n) => n is ArgumentList); | 838 .getAncestor((n) => n is ArgumentList); |
| 839 if (argList == null) { |
| 840 argList = _selectedNode(at: parenError.offset) |
| 841 .getAncestor((n) => n is ArgumentList); |
| 842 } |
| 679 if (argList?.getAncestor((n) => n == node) == null) { | 843 if (argList?.getAncestor((n) => n == node) == null) { |
| 680 return false; | 844 return false; |
| 681 } | 845 } |
| 682 int loc = argList.end - 1; | 846 int previousInsertions = _lengthOfInsertions(); |
| 847 int loc = min(selectionOffset, argList.end - 1); |
| 683 int delta = 1; | 848 int delta = 1; |
| 684 var semicolonError = | 849 var semicolonError = |
| 685 _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'"); | 850 _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'"); |
| 686 if (semicolonError == null) { | 851 if (semicolonError == null) { |
| 687 loc += 1; | 852 loc += 1; |
| 688 delta = 0; | 853 delta = 0; |
| 689 } | 854 } |
| 690 _addInsertEdit(loc, ')'); | 855 _addInsertEdit(loc, ')'); |
| 691 if (semicolonError != null) { | 856 if (semicolonError != null) { |
| 692 _addInsertEdit(loc, ';'); | 857 _addInsertEdit(loc, ';'); |
| 693 } | 858 } |
| 694 String indent = utils.getLinePrefix(selectionOffset); | 859 String indent = utils.getLinePrefix(selectionOffset); |
| 695 int exit = utils.getLineNext(selectionOffset); | 860 int exit = utils.getLineNext(selectionOffset); |
| 696 _addInsertEdit(exit, indent + eol); | 861 _addInsertEdit(exit, indent + eol); |
| 697 exit += indent.length + eol.length; | 862 exit += indent.length + eol.length + previousInsertions; |
| 698 | 863 |
| 699 _setCompletionAt(DartStatementCompletion.SIMPLE_ENTER, exit + delta); | 864 _setCompletionAt(DartStatementCompletion.SIMPLE_ENTER, exit + delta); |
| 700 return true; | 865 return true; |
| 701 } | 866 } |
| 702 | 867 |
| 703 bool _complete_simpleEnter() { | 868 bool _complete_simpleEnter() { |
| 704 int offset; | 869 int offset; |
| 705 if (!errors.isEmpty) { | 870 if (!errors.isEmpty) { |
| 706 offset = selectionOffset; | 871 offset = selectionOffset; |
| 707 } else { | 872 } else { |
| 708 String indent = utils.getLinePrefix(selectionOffset); | 873 String indent = utils.getLinePrefix(selectionOffset); |
| 709 int loc = utils.getLineNext(selectionOffset); | 874 int loc = utils.getLineNext(selectionOffset); |
| 710 _addInsertEdit(loc, indent + eol); | 875 _addInsertEdit(loc, indent + eol); |
| 711 offset = loc + indent.length + eol.length; | 876 offset = loc + indent.length; |
| 712 } | 877 } |
| 713 _setCompletionAt(DartStatementCompletion.SIMPLE_ENTER, offset); | 878 _setCompletionAt(DartStatementCompletion.SIMPLE_ENTER, offset); |
| 714 return true; | 879 return true; |
| 715 } | 880 } |
| 716 | 881 |
| 717 bool _complete_simpleSemicolon() { | 882 bool _complete_simpleSemicolon() { |
| 718 if (errors.length != 1) { | 883 if (errors.length != 1) { |
| 719 return false; | 884 return false; |
| 720 } | 885 } |
| 721 var error = _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'"); | 886 var error = _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'"); |
| 722 if (error != null) { | 887 if (error != null) { |
| 888 int previousInsertions = _lengthOfInsertions(); |
| 723 // TODO(messick) Fix this to find the correct place in all cases. | 889 // TODO(messick) Fix this to find the correct place in all cases. |
| 724 int insertOffset = error.offset + error.length; | 890 int insertOffset = error.offset + error.length; |
| 725 _addInsertEdit(insertOffset, ';'); | 891 _addInsertEdit(insertOffset, ';'); |
| 726 int offset = _appendNewlinePlusIndent() + 1 /* ';' */; | 892 int offset = _appendNewlinePlusIndent() + 1 /*';'*/ + previousInsertions; |
| 727 _setCompletionAt(DartStatementCompletion.SIMPLE_SEMICOLON, offset); | 893 _setCompletionAt(DartStatementCompletion.SIMPLE_SEMICOLON, offset); |
| 728 return true; | 894 return true; |
| 729 } | 895 } |
| 730 return false; | 896 return false; |
| 731 } | 897 } |
| 732 | 898 |
| 733 bool _complete_switchStatement() { | 899 bool _complete_switchStatement() { |
| 734 if (node is! SwitchStatement) { | 900 if (node is! SwitchStatement) { |
| 735 return false; | 901 return false; |
| 736 } | 902 } |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 800 // keywordOnly | 966 // keywordOnly |
| 801 sb = new SourceBuilder(file, tryNode.tryKeyword.end); | 967 sb = new SourceBuilder(file, tryNode.tryKeyword.end); |
| 802 sb.append(' '); | 968 sb.append(' '); |
| 803 } | 969 } |
| 804 _appendEmptyBraces(sb, true); | 970 _appendEmptyBraces(sb, true); |
| 805 _insertBuilder(sb); | 971 _insertBuilder(sb); |
| 806 sb = null; | 972 sb = null; |
| 807 } else if ((catchNode = _findInvalidElement(tryNode.catchClauses)) != | 973 } else if ((catchNode = _findInvalidElement(tryNode.catchClauses)) != |
| 808 null) { | 974 null) { |
| 809 if (catchNode.onKeyword != null) { | 975 if (catchNode.onKeyword != null) { |
| 810 if (catchNode.exceptionType.length == 0) { | 976 if (catchNode.exceptionType.length == 0 || |
| 977 null != |
| 978 _findError(StaticWarningCode.NON_TYPE_IN_CATCH_CLAUSE, |
| 979 partialMatch: "name 'catch")) { |
| 811 String src = utils.getNodeText(catchNode); | 980 String src = utils.getNodeText(catchNode); |
| 812 if (src.startsWith(new RegExp(r'on[ \t]+'))) { | 981 if (src.startsWith(new RegExp(r'on[ \t]+'))) { |
| 813 if (src.startsWith(new RegExp(r'on[ \t][ \t]+'))) { | 982 if (src.startsWith(new RegExp(r'on[ \t][ \t]+'))) { |
| 814 // onSpaces | 983 // onSpaces |
| 815 exitPosition = new Position(file, catchNode.onKeyword.end + 1); | 984 exitPosition = new Position(file, catchNode.onKeyword.end + 1); |
| 816 sb = new SourceBuilder(file, catchNode.onKeyword.end + 2); | 985 sb = new SourceBuilder(file, catchNode.onKeyword.end + 2); |
| 817 addSpace = false; | 986 addSpace = false; |
| 818 } else { | 987 } else { |
| 819 // onSpace | 988 // onSpace |
| 820 sb = new SourceBuilder(file, catchNode.onKeyword.end + 1); | 989 sb = new SourceBuilder(file, catchNode.onKeyword.end + 1); |
| (...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 961 return true; | 1130 return true; |
| 962 } | 1131 } |
| 963 AstNode p = n.parent; | 1132 AstNode p = n.parent; |
| 964 return p is! Statement && p?.parent is! Statement; | 1133 return p is! Statement && p?.parent is! Statement; |
| 965 } | 1134 } |
| 966 | 1135 |
| 967 bool _isSyntheticExpression(Expression expr) { | 1136 bool _isSyntheticExpression(Expression expr) { |
| 968 return expr is SimpleIdentifier && expr.isSynthetic; | 1137 return expr is SimpleIdentifier && expr.isSynthetic; |
| 969 } | 1138 } |
| 970 | 1139 |
| 1140 int _lengthOfDeletions() { |
| 1141 if (change.edits.isEmpty) { |
| 1142 return 0; |
| 1143 } |
| 1144 int length = 0; |
| 1145 for (SourceFileEdit edit in change.edits) { |
| 1146 for (SourceEdit srcEdit in edit.edits) { |
| 1147 if (srcEdit.length > 0) { |
| 1148 length += srcEdit.length - srcEdit.replacement.length; |
| 1149 } |
| 1150 } |
| 1151 } |
| 1152 return length; |
| 1153 } |
| 1154 |
| 1155 int _lengthOfInsertions() { |
| 1156 // Any _complete_*() that may follow changes made by _checkExpressions() |
| 1157 // must cache the result of this method and add that value to its |
| 1158 // exit position. That's assuming all edits are done in increasing position. |
| 1159 // There are currently no editing sequences that produce both insertions and |
| 1160 // deletions, but if there were this approach would have to be generalized. |
| 1161 if (change.edits.isEmpty) { |
| 1162 return 0; |
| 1163 } |
| 1164 int length = 0; |
| 1165 for (SourceFileEdit edit in change.edits) { |
| 1166 for (SourceEdit srcEdit in edit.edits) { |
| 1167 if (srcEdit.length == 0) { |
| 1168 length += srcEdit.replacement.length; |
| 1169 } |
| 1170 } |
| 1171 } |
| 1172 return length; |
| 1173 } |
| 1174 |
| 971 Position _newPosition(int offset) { | 1175 Position _newPosition(int offset) { |
| 972 return new Position(file, offset); | 1176 return new Position(file, offset); |
| 973 } | 1177 } |
| 974 | 1178 |
| 1179 void _removeError(errorCode, {partialMatch = null}) { |
| 1180 var error = _findError(errorCode, partialMatch: partialMatch); |
| 1181 if (error != null) { |
| 1182 errors.remove(error); |
| 1183 } |
| 1184 } |
| 1185 |
| 975 AstNode _selectedNode({int at: null}) => | 1186 AstNode _selectedNode({int at: null}) => |
| 976 new NodeLocator(at == null ? selectionOffset : at).searchWithin(unit); | 1187 new NodeLocator(at == null ? selectionOffset : at).searchWithin(unit); |
| 977 | 1188 |
| 978 void _setCompletion(StatementCompletionKind kind, [List args]) { | 1189 void _setCompletion(StatementCompletionKind kind, [List args]) { |
| 979 assert(exitPosition != null); | 1190 assert(exitPosition != null); |
| 980 change.selection = exitPosition; | 1191 change.selection = exitPosition; |
| 981 change.message = formatList(kind.message, args); | 1192 change.message = formatList(kind.message, args); |
| 982 linkedPositionGroups.values | 1193 linkedPositionGroups.values |
| 983 .forEach((group) => change.addLinkedEditGroup(group)); | 1194 .forEach((group) => change.addLinkedEditGroup(group)); |
| 984 completion = new StatementCompletion(kind, change); | 1195 completion = new StatementCompletion(kind, change); |
| 985 } | 1196 } |
| 986 | 1197 |
| 987 void _setCompletionAt(StatementCompletionKind kind, int offset, [List args]) { | 1198 void _setCompletionAt(StatementCompletionKind kind, int offset, [List args]) { |
| 988 exitPosition = _newPosition(offset); | 1199 exitPosition = _newPosition(offset); |
| 989 _setCompletion(kind, args); | 1200 _setCompletion(kind, args); |
| 990 } | 1201 } |
| 991 | 1202 |
| 992 SourceBuilder _sourceBuilderAfterKeyword(Token keyword) { | 1203 SourceBuilder _sourceBuilderAfterKeyword(Token keyword) { |
| 993 SourceBuilder sb; | 1204 SourceBuilder sb; |
| 994 String text = _baseNodeText(node); | 1205 String text = _baseNodeText(node); |
| 995 text = text.substring(keyword.offset - node.offset); | 1206 text = text.substring(keyword.offset - node.offset); |
| 996 int len = keyword.length; | 1207 int len = keyword.length; |
| 997 if (text.length == len || | 1208 if (text.length == len || // onCatchComment |
| 998 !text.substring(len, len + 1).contains(new RegExp(r'\s'))) { | 1209 !text.substring(len, len + 1).contains(new RegExp(r'[ \t]'))) { |
| 999 sb = new SourceBuilder(file, keyword.offset + len); | 1210 sb = new SourceBuilder(file, keyword.offset + len); |
| 1000 sb.append(' '); | 1211 sb.append(' '); |
| 1001 } else { | 1212 } else { |
| 1002 sb = new SourceBuilder(file, keyword.offset + len + 1); | 1213 sb = new SourceBuilder(file, keyword.offset + len + 1); |
| 1003 } | 1214 } |
| 1004 return sb; | 1215 return sb; |
| 1005 } | 1216 } |
| 1006 | 1217 |
| 1007 bool _statementHasValidBody(Token keyword, Statement body) { | 1218 bool _statementHasValidBody(Token keyword, Statement body) { |
| 1008 // A "valid" body is either a non-synthetic block or a single statement | 1219 // A "valid" body is either a non-synthetic block or a single statement |
| (...skipping 15 matching lines...) Expand all Loading... |
| 1024 final Token keyword; | 1235 final Token keyword; |
| 1025 final Token leftParenthesis, rightParenthesis; | 1236 final Token leftParenthesis, rightParenthesis; |
| 1026 final Expression condition; | 1237 final Expression condition; |
| 1027 final Statement block; | 1238 final Statement block; |
| 1028 | 1239 |
| 1029 _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis, | 1240 _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis, |
| 1030 this.condition, this.rightParenthesis, this.block); | 1241 this.condition, this.rightParenthesis, this.block); |
| 1031 | 1242 |
| 1032 int get offset => keyword.offset; | 1243 int get offset => keyword.offset; |
| 1033 } | 1244 } |
| OLD | NEW |