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/util.dart'; | 13 import 'package:analysis_server/src/services/correction/util.dart'; |
13 import 'package:analyzer/dart/ast/ast.dart'; | 14 import 'package:analyzer/dart/ast/ast.dart'; |
14 import 'package:analyzer/dart/ast/token.dart'; | 15 import 'package:analyzer/dart/ast/token.dart'; |
15 import 'package:analyzer/dart/element/element.dart'; | 16 import 'package:analyzer/dart/element/element.dart'; |
16 import 'package:analyzer/error/error.dart'; | 17 import 'package:analyzer/error/error.dart'; |
17 import 'package:analyzer/error/error.dart' as engine; | 18 import 'package:analyzer/error/error.dart' as engine; |
18 import 'package:analyzer/src/dart/ast/utilities.dart'; | 19 import 'package:analyzer/src/dart/ast/utilities.dart'; |
19 import 'package:analyzer/src/dart/error/hint_codes.dart'; | 20 import 'package:analyzer/src/dart/error/hint_codes.dart'; |
20 import 'package:analyzer/src/dart/error/syntactic_errors.dart'; | 21 import 'package:analyzer/src/dart/error/syntactic_errors.dart'; |
| 22 import 'package:analyzer/src/error/codes.dart'; |
21 import 'package:analyzer/src/generated/engine.dart'; | 23 import 'package:analyzer/src/generated/engine.dart'; |
22 import 'package:analyzer/src/generated/java_core.dart'; | 24 import 'package:analyzer/src/generated/java_core.dart'; |
23 import 'package:analyzer/src/generated/source.dart'; | 25 import 'package:analyzer/src/generated/source.dart'; |
24 import 'package:analyzer_plugin/utilities/range_factory.dart'; | 26 import 'package:analyzer_plugin/utilities/range_factory.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 = |
(...skipping 163 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 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
479 _setCompletion(DartStatementCompletion.COMPLETE_FOR_EACH_STMT); | 579 _setCompletion(DartStatementCompletion.COMPLETE_FOR_EACH_STMT); |
480 return true; | 580 return true; |
481 } | 581 } |
482 | 582 |
483 bool _complete_forStatement() { | 583 bool _complete_forStatement() { |
484 if (node is! ForStatement) { | 584 if (node is! ForStatement) { |
485 return false; | 585 return false; |
486 } | 586 } |
487 ForStatement forNode = node; | 587 ForStatement forNode = node; |
488 SourceBuilder sb; | 588 SourceBuilder sb; |
489 int delta = 0; | 589 int replacementLength = 0; |
490 if (forNode.leftParenthesis.isSynthetic) { | 590 if (forNode.leftParenthesis.isSynthetic) { |
491 if (!forNode.rightParenthesis.isSynthetic) { | 591 if (!forNode.rightParenthesis.isSynthetic) { |
492 return false; | 592 return false; |
493 } | 593 } |
494 // keywordOnly (unit test name suffix that exercises this branch) | 594 // keywordOnly (unit test name suffix that exercises this branch) |
495 sb = _sourceBuilderAfterKeyword(forNode.forKeyword); | 595 sb = _sourceBuilderAfterKeyword(forNode.forKeyword); |
496 sb.append('('); | 596 sb.append('('); |
497 sb.setExitOffset(); | 597 sb.setExitOffset(); |
498 sb.append(')'); | 598 sb.append(')'); |
499 } else { | 599 } else { |
500 if (!forNode.rightSeparator.isSynthetic) { | 600 if (!forNode.rightSeparator.isSynthetic) { |
501 // Fully-defined init, cond, updaters so nothing more needed here. | 601 // Fully-defined init, cond, updaters so nothing more needed here. |
502 // emptyParts | 602 // emptyParts, noError |
503 sb = new SourceBuilder(file, forNode.rightParenthesis.offset + 1); | 603 sb = new SourceBuilder(file, forNode.rightParenthesis.offset + 1); |
504 } else if (!forNode.leftSeparator.isSynthetic) { | 604 } else if (!forNode.leftSeparator.isSynthetic) { |
505 if (_isSyntheticExpression(forNode.condition)) { | 605 if (_isSyntheticExpression(forNode.condition)) { |
506 exitPosition = _newPosition(forNode.leftSeparator.offset + 1); | |
507 String text = utils | 606 String text = utils |
508 .getNodeText(forNode) | 607 .getNodeText(forNode) |
509 .substring(forNode.leftSeparator.offset - forNode.offset); | 608 .substring(forNode.leftSeparator.offset - forNode.offset); |
510 if (text.startsWith(new RegExp(r';\s*\)'))) { | 609 Match match = |
511 // emptyCondition | 610 new RegExp(r';\s*(/\*.*\*/\s*)?\)[ \t]*').matchAsPrefix(text); |
512 int end = text.indexOf(')'); | 611 if (match != null) { |
| 612 // emptyCondition, emptyInitializersEmptyCondition |
| 613 replacementLength = match.end - match.start; |
513 sb = new SourceBuilder(file, forNode.leftSeparator.offset); | 614 sb = new SourceBuilder(file, forNode.leftSeparator.offset); |
514 _addReplaceEdit(new SourceRange(sb.offset, end), '; ; '); | 615 sb.append('; ${match.group(1) == null ? '' : match.group(1)}; )'); |
515 delta = end - '; '.length; | 616 String suffix = text.substring(match.end); |
| 617 if (suffix.trim().isNotEmpty) { |
| 618 sb.append(' '); |
| 619 sb.append(suffix.trim()); |
| 620 replacementLength += suffix.length; |
| 621 if (suffix.endsWith(eol)) { |
| 622 // emptyCondition |
| 623 replacementLength -= eol.length; |
| 624 } |
| 625 } |
| 626 exitPosition = _newPosition(forNode.leftSeparator.offset + 2); |
516 } else { | 627 } else { |
517 // emptyInitializersEmptyCondition | 628 return false; // Line comment in condition |
518 exitPosition = _newPosition(forNode.rightParenthesis.offset); | |
519 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); | |
520 } | 629 } |
521 } else { | 630 } else { |
522 // emptyUpdaters | 631 // emptyUpdaters |
523 exitPosition = _newPosition(forNode.rightSeparator.offset); | 632 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); |
524 sb = new SourceBuilder(file, forNode.rightSeparator.offset); | 633 replacementLength = 1; |
525 _addReplaceEdit(new SourceRange(sb.offset, 0), '; '); | 634 sb.append('; )'); |
526 delta = -'; '.length; | 635 exitPosition = _newPosition(forNode.rightSeparator.offset + 2); |
527 } | 636 } |
528 } else if (_isSyntheticExpression(forNode.initialization)) { | 637 } else if (_isSyntheticExpression(forNode.initialization)) { |
529 // emptyInitializers | 638 // emptyInitializers |
530 exitPosition = _newPosition(forNode.rightParenthesis.offset); | 639 exitPosition = _newPosition(forNode.rightParenthesis.offset); |
531 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); | 640 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); |
532 } else { | 641 } else { |
533 int start = forNode.condition.offset + forNode.condition.length; | 642 int start = forNode.condition.offset + forNode.condition.length; |
534 String text = | 643 String text = |
535 utils.getNodeText(forNode).substring(start - forNode.offset); | 644 utils.getNodeText(forNode).substring(start - forNode.offset); |
536 if (text.startsWith(new RegExp(r'\s*\)'))) { | 645 if (text.startsWith(new RegExp(r'\s*\)'))) { |
537 // missingLeftSeparator | 646 // missingLeftSeparator |
538 int end = text.indexOf(')'); | 647 int end = text.indexOf(')'); |
539 sb = new SourceBuilder(file, start); | 648 sb = new SourceBuilder(file, start); |
540 _addReplaceEdit(new SourceRange(start, end), '; '); | 649 _addReplaceEdit(new SourceRange(start, end), '; ; '); |
541 delta = end - '; '.length; | 650 exitPosition = new Position(file, start - (end - '; '.length)); |
542 exitPosition = new Position(file, start); | |
543 } else { | 651 } else { |
544 // Not possible; any comment following init is attached to init. | 652 // Not possible; any comment following init is attached to init. |
545 exitPosition = _newPosition(forNode.rightParenthesis.offset); | 653 exitPosition = _newPosition(forNode.rightParenthesis.offset); |
546 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); | 654 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); |
547 } | 655 } |
548 } | 656 } |
549 } | 657 } |
550 if (!_statementHasValidBody(forNode.forKeyword, forNode.body)) { | 658 if (!_statementHasValidBody(forNode.forKeyword, forNode.body)) { |
551 // keywordOnly, noError | 659 // keywordOnly, noError |
552 sb.append(' '); | 660 sb.append(' '); |
553 _appendEmptyBraces(sb, exitPosition == null); | 661 _appendEmptyBraces(sb, true /*exitPosition == null*/); |
| 662 } else if (forNode.body is Block) { |
| 663 Block body = forNode.body; |
| 664 if (body.rightBracket.end <= selectionOffset) { |
| 665 // emptyInitializersAfterBody |
| 666 errors = []; // Ignore errors; they are for previous statement. |
| 667 return false; // If cursor is after closing brace just add newline. |
| 668 } |
554 } | 669 } |
555 if (delta != 0 && exitPosition != null) { | 670 _insertBuilder(sb, replacementLength); |
556 // missingLeftSeparator | |
557 exitPosition = new Position(file, exitPosition.offset - delta); | |
558 } | |
559 _insertBuilder(sb); | |
560 _setCompletion(DartStatementCompletion.COMPLETE_FOR_STMT); | 671 _setCompletion(DartStatementCompletion.COMPLETE_FOR_STMT); |
561 return true; | 672 return true; |
562 } | 673 } |
563 | 674 |
564 bool _complete_functionDeclaration() { | 675 bool _complete_functionDeclaration() { |
565 if (node is! MethodDeclaration && node is! FunctionDeclaration) { | 676 if (node is! MethodDeclaration && node is! FunctionDeclaration) { |
566 return false; | 677 return false; |
567 } | 678 } |
568 SourceBuilder sb = new SourceBuilder(file, node.end - 1); | 679 bool needsParen = false; |
| 680 int computeExitPos(FormalParameterList parameters) { |
| 681 if (needsParen = parameters.rightParenthesis.isSynthetic) { |
| 682 var error = _findError(ParserErrorCode.MISSING_CLOSING_PARENTHESIS); |
| 683 if (error != null) { |
| 684 return error.offset - 1; |
| 685 } |
| 686 } |
| 687 return node.end - 1; |
| 688 } |
| 689 |
| 690 int paramListEnd; |
| 691 if (node is FunctionDeclaration) { |
| 692 FunctionDeclaration func = node; |
| 693 paramListEnd = computeExitPos(func.functionExpression.parameters); |
| 694 } else { |
| 695 MethodDeclaration meth = node; |
| 696 paramListEnd = computeExitPos(meth.parameters); |
| 697 } |
| 698 SourceBuilder sb = new SourceBuilder(file, paramListEnd); |
| 699 if (needsParen) { |
| 700 sb.append(')'); |
| 701 } |
569 sb.append(' '); | 702 sb.append(' '); |
570 _appendEmptyBraces(sb, true); | 703 _appendEmptyBraces(sb, true); |
571 _insertBuilder(sb); | 704 _insertBuilder(sb); |
572 _setCompletion(DartStatementCompletion.COMPLETE_FUNCTION_DECLARATION); | 705 _setCompletion(DartStatementCompletion.COMPLETE_FUNCTION_DECLARATION); |
573 return true; | 706 return true; |
574 } | 707 } |
575 | 708 |
576 bool _complete_functionDeclarationStatement() { | 709 bool _complete_functionDeclarationStatement() { |
577 if (node is! FunctionDeclarationStatement) { | 710 if (node is! FunctionDeclarationStatement) { |
578 return false; | 711 return false; |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
614 | 747 |
615 bool _complete_ifOrWhileStatement( | 748 bool _complete_ifOrWhileStatement( |
616 _KeywordConditionBlockStructure statement, StatementCompletionKind kind) { | 749 _KeywordConditionBlockStructure statement, StatementCompletionKind kind) { |
617 if (_statementHasValidBody(statement.keyword, statement.block)) { | 750 if (_statementHasValidBody(statement.keyword, statement.block)) { |
618 return false; | 751 return false; |
619 } | 752 } |
620 SourceBuilder sb = _complete_keywordCondition(statement); | 753 SourceBuilder sb = _complete_keywordCondition(statement); |
621 if (sb == null) { | 754 if (sb == null) { |
622 return false; | 755 return false; |
623 } | 756 } |
| 757 int overshoot = _lengthOfDeletions(); |
624 sb.append(' '); | 758 sb.append(' '); |
625 _appendEmptyBraces(sb, exitPosition == null); | 759 _appendEmptyBraces(sb, exitPosition == null); |
626 _insertBuilder(sb); | 760 _insertBuilder(sb); |
| 761 if (overshoot != 0) { |
| 762 exitPosition = _newPosition(exitPosition.offset - overshoot); |
| 763 } |
627 _setCompletion(kind); | 764 _setCompletion(kind); |
628 return true; | 765 return true; |
629 } | 766 } |
630 | 767 |
631 bool _complete_ifStatement() { | 768 bool _complete_ifStatement() { |
632 if (node is! IfStatement) { | 769 if (node is! IfStatement) { |
633 return false; | 770 return false; |
634 } | 771 } |
635 IfStatement ifNode = node; | 772 IfStatement ifNode = node; |
636 if (ifNode.elseKeyword != null) { | 773 if (ifNode.elseKeyword != null) { |
| 774 if (selectionOffset >= ifNode.elseKeyword.end && |
| 775 ifNode.elseStatement is EmptyStatement) { |
| 776 SourceBuilder sb = new SourceBuilder(file, selectionOffset); |
| 777 String src = utils.getNodeText(ifNode); |
| 778 if (!src |
| 779 .substring(ifNode.elseKeyword.end - node.offset) |
| 780 .startsWith(new RegExp(r'[ \t]'))) { |
| 781 sb.append(' '); |
| 782 } |
| 783 _appendEmptyBraces(sb, true); |
| 784 _insertBuilder(sb); |
| 785 _setCompletion(DartStatementCompletion.COMPLETE_IF_STMT); |
| 786 return true; |
| 787 } |
637 return false; | 788 return false; |
638 } | 789 } |
639 var stmt = new _KeywordConditionBlockStructure( | 790 var stmt = new _KeywordConditionBlockStructure( |
640 ifNode.ifKeyword, | 791 ifNode.ifKeyword, |
641 ifNode.leftParenthesis, | 792 ifNode.leftParenthesis, |
642 ifNode.condition, | 793 ifNode.condition, |
643 ifNode.rightParenthesis, | 794 ifNode.rightParenthesis, |
644 ifNode.thenStatement); | 795 ifNode.thenStatement); |
645 return _complete_ifOrWhileStatement( | 796 return _complete_ifOrWhileStatement( |
646 stmt, DartStatementCompletion.COMPLETE_IF_STMT); | 797 stmt, DartStatementCompletion.COMPLETE_IF_STMT); |
647 } | 798 } |
648 | 799 |
649 SourceBuilder _complete_keywordCondition( | 800 SourceBuilder _complete_keywordCondition( |
650 _KeywordConditionBlockStructure statement) { | 801 _KeywordConditionBlockStructure statement) { |
651 SourceBuilder sb; | 802 SourceBuilder sb; |
652 if (statement.leftParenthesis.isSynthetic) { | 803 if (statement.leftParenthesis.isSynthetic) { |
653 if (!statement.rightParenthesis.isSynthetic) { | 804 if (!statement.rightParenthesis.isSynthetic) { |
654 // Quite unlikely to see this so don't try to fix it. | 805 // Quite unlikely to see this so don't try to fix it. |
655 return null; | 806 return null; |
656 } | 807 } |
657 sb = _sourceBuilderAfterKeyword(statement.keyword); | 808 sb = _sourceBuilderAfterKeyword(statement.keyword); |
658 sb.append('('); | 809 sb.append('('); |
659 sb.setExitOffset(); | 810 sb.setExitOffset(); |
660 sb.append(')'); | 811 sb.append(')'); |
661 } else { | 812 } else { |
662 if (_isSyntheticExpression(statement.condition)) { | 813 if (_isSyntheticExpression(statement.condition)) { |
663 exitPosition = _newPosition(statement.leftParenthesis.offset + 1); | 814 exitPosition = _newPosition(statement.leftParenthesis.offset + 1); |
664 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); | 815 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); |
665 } else { | 816 } else { |
666 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); | 817 int afterParen = statement.rightParenthesis.offset + 1; |
| 818 if (utils |
| 819 .getNodeText(node) |
| 820 .substring(afterParen - node.offset) |
| 821 .startsWith(new RegExp(r'[ \t]'))) { |
| 822 _addReplaceEdit(new SourceRange(afterParen, 1), ''); |
| 823 sb = new SourceBuilder(file, afterParen + 1); |
| 824 } else { |
| 825 sb = new SourceBuilder(file, afterParen); |
| 826 } |
667 } | 827 } |
668 } | 828 } |
669 return sb; | 829 return sb; |
670 } | 830 } |
671 | 831 |
672 bool _complete_methodCall() { | 832 bool _complete_methodCall() { |
673 var parenError = | 833 var parenError = |
674 _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "')'"); | 834 _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "')'"); |
675 if (parenError == null) { | 835 if (parenError == null) { |
676 return false; | 836 return false; |
677 } | 837 } |
678 AstNode argList = _selectedNode(at: parenError.offset - 1) | 838 AstNode argList = _selectedNode(at: selectionOffset) |
679 .getAncestor((n) => n is ArgumentList); | 839 .getAncestor((n) => n is ArgumentList); |
| 840 if (argList == null) { |
| 841 argList = _selectedNode(at: parenError.offset) |
| 842 .getAncestor((n) => n is ArgumentList); |
| 843 } |
680 if (argList?.getAncestor((n) => n == node) == null) { | 844 if (argList?.getAncestor((n) => n == node) == null) { |
681 return false; | 845 return false; |
682 } | 846 } |
683 int loc = argList.end - 1; | 847 int previousInsertions = _lengthOfInsertions(); |
| 848 int loc = min(selectionOffset, argList.end - 1); |
684 int delta = 1; | 849 int delta = 1; |
685 var semicolonError = | 850 var semicolonError = |
686 _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'"); | 851 _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'"); |
687 if (semicolonError == null) { | 852 if (semicolonError == null) { |
688 loc += 1; | 853 loc += 1; |
689 delta = 0; | 854 delta = 0; |
690 } | 855 } |
691 _addInsertEdit(loc, ')'); | 856 _addInsertEdit(loc, ')'); |
692 if (semicolonError != null) { | 857 if (semicolonError != null) { |
693 _addInsertEdit(loc, ';'); | 858 _addInsertEdit(loc, ';'); |
694 } | 859 } |
695 String indent = utils.getLinePrefix(selectionOffset); | 860 String indent = utils.getLinePrefix(selectionOffset); |
696 int exit = utils.getLineNext(selectionOffset); | 861 int exit = utils.getLineNext(selectionOffset); |
697 _addInsertEdit(exit, indent + eol); | 862 _addInsertEdit(exit, indent + eol); |
698 exit += indent.length + eol.length; | 863 exit += indent.length + eol.length + previousInsertions; |
699 | 864 |
700 _setCompletionAt(DartStatementCompletion.SIMPLE_ENTER, exit + delta); | 865 _setCompletionAt(DartStatementCompletion.SIMPLE_ENTER, exit + delta); |
701 return true; | 866 return true; |
702 } | 867 } |
703 | 868 |
704 bool _complete_simpleEnter() { | 869 bool _complete_simpleEnter() { |
705 int offset; | 870 int offset; |
706 if (!errors.isEmpty) { | 871 if (!errors.isEmpty) { |
707 offset = selectionOffset; | 872 offset = selectionOffset; |
708 } else { | 873 } else { |
709 String indent = utils.getLinePrefix(selectionOffset); | 874 String indent = utils.getLinePrefix(selectionOffset); |
710 int loc = utils.getLineNext(selectionOffset); | 875 int loc = utils.getLineNext(selectionOffset); |
711 _addInsertEdit(loc, indent + eol); | 876 _addInsertEdit(loc, indent + eol); |
712 offset = loc + indent.length + eol.length; | 877 offset = loc + indent.length; |
713 } | 878 } |
714 _setCompletionAt(DartStatementCompletion.SIMPLE_ENTER, offset); | 879 _setCompletionAt(DartStatementCompletion.SIMPLE_ENTER, offset); |
715 return true; | 880 return true; |
716 } | 881 } |
717 | 882 |
718 bool _complete_simpleSemicolon() { | 883 bool _complete_simpleSemicolon() { |
719 if (errors.length != 1) { | 884 if (errors.length != 1) { |
720 return false; | 885 return false; |
721 } | 886 } |
722 var error = _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'"); | 887 var error = _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'"); |
723 if (error != null) { | 888 if (error != null) { |
| 889 int previousInsertions = _lengthOfInsertions(); |
724 // TODO(messick) Fix this to find the correct place in all cases. | 890 // TODO(messick) Fix this to find the correct place in all cases. |
725 int insertOffset = error.offset + error.length; | 891 int insertOffset = error.offset + error.length; |
726 _addInsertEdit(insertOffset, ';'); | 892 _addInsertEdit(insertOffset, ';'); |
727 int offset = _appendNewlinePlusIndent() + 1 /* ';' */; | 893 int offset = _appendNewlinePlusIndent() + 1 /*';'*/ + previousInsertions; |
728 _setCompletionAt(DartStatementCompletion.SIMPLE_SEMICOLON, offset); | 894 _setCompletionAt(DartStatementCompletion.SIMPLE_SEMICOLON, offset); |
729 return true; | 895 return true; |
730 } | 896 } |
731 return false; | 897 return false; |
732 } | 898 } |
733 | 899 |
734 bool _complete_switchStatement() { | 900 bool _complete_switchStatement() { |
735 if (node is! SwitchStatement) { | 901 if (node is! SwitchStatement) { |
736 return false; | 902 return false; |
737 } | 903 } |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
801 // keywordOnly | 967 // keywordOnly |
802 sb = new SourceBuilder(file, tryNode.tryKeyword.end); | 968 sb = new SourceBuilder(file, tryNode.tryKeyword.end); |
803 sb.append(' '); | 969 sb.append(' '); |
804 } | 970 } |
805 _appendEmptyBraces(sb, true); | 971 _appendEmptyBraces(sb, true); |
806 _insertBuilder(sb); | 972 _insertBuilder(sb); |
807 sb = null; | 973 sb = null; |
808 } else if ((catchNode = _findInvalidElement(tryNode.catchClauses)) != | 974 } else if ((catchNode = _findInvalidElement(tryNode.catchClauses)) != |
809 null) { | 975 null) { |
810 if (catchNode.onKeyword != null) { | 976 if (catchNode.onKeyword != null) { |
811 if (catchNode.exceptionType.length == 0) { | 977 if (catchNode.exceptionType.length == 0 || |
| 978 null != |
| 979 _findError(StaticWarningCode.NON_TYPE_IN_CATCH_CLAUSE, |
| 980 partialMatch: "name 'catch")) { |
812 String src = utils.getNodeText(catchNode); | 981 String src = utils.getNodeText(catchNode); |
813 if (src.startsWith(new RegExp(r'on[ \t]+'))) { | 982 if (src.startsWith(new RegExp(r'on[ \t]+'))) { |
814 if (src.startsWith(new RegExp(r'on[ \t][ \t]+'))) { | 983 if (src.startsWith(new RegExp(r'on[ \t][ \t]+'))) { |
815 // onSpaces | 984 // onSpaces |
816 exitPosition = new Position(file, catchNode.onKeyword.end + 1); | 985 exitPosition = new Position(file, catchNode.onKeyword.end + 1); |
817 sb = new SourceBuilder(file, catchNode.onKeyword.end + 2); | 986 sb = new SourceBuilder(file, catchNode.onKeyword.end + 2); |
818 addSpace = false; | 987 addSpace = false; |
819 } else { | 988 } else { |
820 // onSpace | 989 // onSpace |
821 sb = new SourceBuilder(file, catchNode.onKeyword.end + 1); | 990 sb = new SourceBuilder(file, catchNode.onKeyword.end + 1); |
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
962 return true; | 1131 return true; |
963 } | 1132 } |
964 AstNode p = n.parent; | 1133 AstNode p = n.parent; |
965 return p is! Statement && p?.parent is! Statement; | 1134 return p is! Statement && p?.parent is! Statement; |
966 } | 1135 } |
967 | 1136 |
968 bool _isSyntheticExpression(Expression expr) { | 1137 bool _isSyntheticExpression(Expression expr) { |
969 return expr is SimpleIdentifier && expr.isSynthetic; | 1138 return expr is SimpleIdentifier && expr.isSynthetic; |
970 } | 1139 } |
971 | 1140 |
| 1141 int _lengthOfDeletions() { |
| 1142 if (change.edits.isEmpty) { |
| 1143 return 0; |
| 1144 } |
| 1145 int length = 0; |
| 1146 for (SourceFileEdit edit in change.edits) { |
| 1147 for (SourceEdit srcEdit in edit.edits) { |
| 1148 if (srcEdit.length > 0) { |
| 1149 length += srcEdit.length - srcEdit.replacement.length; |
| 1150 } |
| 1151 } |
| 1152 } |
| 1153 return length; |
| 1154 } |
| 1155 |
| 1156 int _lengthOfInsertions() { |
| 1157 // Any _complete_*() that may follow changes made by _checkExpressions() |
| 1158 // must cache the result of this method and add that value to its |
| 1159 // exit position. That's assuming all edits are done in increasing position. |
| 1160 // There are currently no editing sequences that produce both insertions and |
| 1161 // deletions, but if there were this approach would have to be generalized. |
| 1162 if (change.edits.isEmpty) { |
| 1163 return 0; |
| 1164 } |
| 1165 int length = 0; |
| 1166 for (SourceFileEdit edit in change.edits) { |
| 1167 for (SourceEdit srcEdit in edit.edits) { |
| 1168 if (srcEdit.length == 0) { |
| 1169 length += srcEdit.replacement.length; |
| 1170 } |
| 1171 } |
| 1172 } |
| 1173 return length; |
| 1174 } |
| 1175 |
972 Position _newPosition(int offset) { | 1176 Position _newPosition(int offset) { |
973 return new Position(file, offset); | 1177 return new Position(file, offset); |
974 } | 1178 } |
975 | 1179 |
| 1180 void _removeError(errorCode, {partialMatch = null}) { |
| 1181 var error = _findError(errorCode, partialMatch: partialMatch); |
| 1182 if (error != null) { |
| 1183 errors.remove(error); |
| 1184 } |
| 1185 } |
| 1186 |
976 AstNode _selectedNode({int at: null}) => | 1187 AstNode _selectedNode({int at: null}) => |
977 new NodeLocator(at == null ? selectionOffset : at).searchWithin(unit); | 1188 new NodeLocator(at == null ? selectionOffset : at).searchWithin(unit); |
978 | 1189 |
979 void _setCompletion(StatementCompletionKind kind, [List args]) { | 1190 void _setCompletion(StatementCompletionKind kind, [List args]) { |
980 assert(exitPosition != null); | 1191 assert(exitPosition != null); |
981 change.selection = exitPosition; | 1192 change.selection = exitPosition; |
982 change.message = formatList(kind.message, args); | 1193 change.message = formatList(kind.message, args); |
983 linkedPositionGroups.values | 1194 linkedPositionGroups.values |
984 .forEach((group) => change.addLinkedEditGroup(group)); | 1195 .forEach((group) => change.addLinkedEditGroup(group)); |
985 completion = new StatementCompletion(kind, change); | 1196 completion = new StatementCompletion(kind, change); |
986 } | 1197 } |
987 | 1198 |
988 void _setCompletionAt(StatementCompletionKind kind, int offset, [List args]) { | 1199 void _setCompletionAt(StatementCompletionKind kind, int offset, [List args]) { |
989 exitPosition = _newPosition(offset); | 1200 exitPosition = _newPosition(offset); |
990 _setCompletion(kind, args); | 1201 _setCompletion(kind, args); |
991 } | 1202 } |
992 | 1203 |
993 SourceBuilder _sourceBuilderAfterKeyword(Token keyword) { | 1204 SourceBuilder _sourceBuilderAfterKeyword(Token keyword) { |
994 SourceBuilder sb; | 1205 SourceBuilder sb; |
995 String text = _baseNodeText(node); | 1206 String text = _baseNodeText(node); |
996 text = text.substring(keyword.offset - node.offset); | 1207 text = text.substring(keyword.offset - node.offset); |
997 int len = keyword.length; | 1208 int len = keyword.length; |
998 if (text.length == len || | 1209 if (text.length == len || // onCatchComment |
999 !text.substring(len, len + 1).contains(new RegExp(r'\s'))) { | 1210 !text.substring(len, len + 1).contains(new RegExp(r'[ \t]'))) { |
1000 sb = new SourceBuilder(file, keyword.offset + len); | 1211 sb = new SourceBuilder(file, keyword.offset + len); |
1001 sb.append(' '); | 1212 sb.append(' '); |
1002 } else { | 1213 } else { |
1003 sb = new SourceBuilder(file, keyword.offset + len + 1); | 1214 sb = new SourceBuilder(file, keyword.offset + len + 1); |
1004 } | 1215 } |
1005 return sb; | 1216 return sb; |
1006 } | 1217 } |
1007 | 1218 |
1008 bool _statementHasValidBody(Token keyword, Statement body) { | 1219 bool _statementHasValidBody(Token keyword, Statement body) { |
1009 // A "valid" body is either a non-synthetic block or a single statement | 1220 // A "valid" body is either a non-synthetic block or a single statement |
(...skipping 15 matching lines...) Expand all Loading... |
1025 final Token keyword; | 1236 final Token keyword; |
1026 final Token leftParenthesis, rightParenthesis; | 1237 final Token leftParenthesis, rightParenthesis; |
1027 final Expression condition; | 1238 final Expression condition; |
1028 final Statement block; | 1239 final Statement block; |
1029 | 1240 |
1030 _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis, | 1241 _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis, |
1031 this.condition, this.rightParenthesis, this.block); | 1242 this.condition, this.rightParenthesis, this.block); |
1032 | 1243 |
1033 int get offset => keyword.offset; | 1244 int get offset => keyword.offset; |
1034 } | 1245 } |
OLD | NEW |