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

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

Powered by Google App Engine
This is Rietveld 408576698