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

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

Issue 2867123003: Follow a consistent user model (Closed)
Patch Set: Address review comments Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698