| 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 | 8 |
| 9 import 'package:analysis_server/plugin/protocol/protocol.dart'; | 9 import 'package:analysis_server/plugin/protocol/protocol.dart'; |
| 10 import 'package:analysis_server/src/protocol_server.dart' hide Element; | 10 import 'package:analysis_server/src/protocol_server.dart' hide Element; |
| 11 import 'package:analysis_server/src/services/correction/source_buffer.dart'; | 11 import 'package:analysis_server/src/services/correction/source_buffer.dart'; |
| 12 import 'package:analysis_server/src/services/correction/source_range.dart'; | 12 import 'package:analysis_server/src/services/correction/source_range.dart'; |
| 13 import 'package:analysis_server/src/services/correction/util.dart'; | 13 import 'package:analysis_server/src/services/correction/util.dart'; |
| 14 import 'package:analyzer/dart/ast/ast.dart'; | 14 import 'package:analyzer/dart/ast/ast.dart'; |
| 15 import 'package:analyzer/dart/ast/token.dart'; | 15 import 'package:analyzer/dart/ast/token.dart'; |
| 16 import 'package:analyzer/dart/ast/visitor.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'; |
| 22 import 'package:analyzer/src/generated/engine.dart'; | 23 import 'package:analyzer/src/generated/engine.dart'; |
| 23 import 'package:analyzer/src/generated/java_core.dart'; | 24 import 'package:analyzer/src/generated/java_core.dart'; |
| 24 import 'package:analyzer/src/generated/source.dart'; | 25 import 'package:analyzer/src/generated/source.dart'; |
| 25 | 26 |
| (...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 177 } | 178 } |
| 178 for (engine.AnalysisError error in statementContext.errors) { | 179 for (engine.AnalysisError error in statementContext.errors) { |
| 179 if (error.offset >= node.offset && | 180 if (error.offset >= node.offset && |
| 180 error.offset <= node.offset + node.length) { | 181 error.offset <= node.offset + node.length) { |
| 181 if (error.errorCode is! HintCode) { | 182 if (error.errorCode is! HintCode) { |
| 182 errors.add(error); | 183 errors.add(error); |
| 183 } | 184 } |
| 184 } | 185 } |
| 185 } | 186 } |
| 186 | 187 |
| 187 // TODO(messick) Consider changing (some of) this to a visitor. | 188 if (errors.isEmpty) { |
| 188 if (_complete_ifStatement() || | 189 if (_complete_simpleEnter()) { |
| 189 _complete_doStatement() || | 190 return completion; |
| 190 _complete_forStatement() || | 191 } |
| 191 _complete_forEachStatement() || | 192 } else { |
| 192 _complete_switchStatement() || | 193 if (_complete_ifStatement() || |
| 193 _complete_tryStatement() || | 194 _complete_doStatement() || |
| 194 _complete_whileStatement() || | 195 _complete_forStatement() || |
| 195 _complete_simpleSemicolon() || | 196 _complete_forEachStatement() || |
| 196 _complete_controlFlowBlock() || | 197 _complete_switchStatement() || |
| 197 _complete_simpleEnter()) { | 198 _complete_tryStatement() || |
| 198 return completion; | 199 _complete_whileStatement() || |
| 200 _complete_simpleSemicolon() || |
| 201 _complete_controlFlowBlock() || |
| 202 _complete_simpleEnter()) { |
| 203 return completion; |
| 204 } |
| 199 } | 205 } |
| 200 return NO_COMPLETION; | 206 return NO_COMPLETION; |
| 201 } | 207 } |
| 202 | 208 |
| 203 void _addInsertEdit(int offset, String text) { | 209 void _addInsertEdit(int offset, String text) { |
| 204 SourceEdit edit = new SourceEdit(offset, 0, text); | 210 SourceEdit edit = new SourceEdit(offset, 0, text); |
| 205 doSourceChange_addElementEdit(change, unitElement, edit); | 211 doSourceChange_addElementEdit(change, unitElement, edit); |
| 206 } | 212 } |
| 207 | 213 |
| 208 void _addReplaceEdit(SourceRange range, String text) { | 214 void _addReplaceEdit(SourceRange range, String text) { |
| (...skipping 30 matching lines...) Expand all Loading... |
| 239 text = text.substring(0, text.length - eol.length); | 245 text = text.substring(0, text.length - eol.length); |
| 240 } | 246 } |
| 241 return text; | 247 return text; |
| 242 } | 248 } |
| 243 | 249 |
| 244 bool _complete_controlFlowBlock() { | 250 bool _complete_controlFlowBlock() { |
| 245 //TODO(messick) Implement _complete_controlFlowBlock | 251 //TODO(messick) Implement _complete_controlFlowBlock |
| 246 // Use statement completion to move the cursor to a new line outside the | 252 // Use statement completion to move the cursor to a new line outside the |
| 247 // current block. The statement has no errors in this case. Used to jump | 253 // current block. The statement has no errors in this case. Used to jump |
| 248 // out of do/for/if/while blocks. | 254 // out of do/for/if/while blocks. |
| 249 //TODO(messick) Move has-errors checking into dispatch method. | |
| 250 return false; | 255 return false; |
| 251 } | 256 } |
| 252 | 257 |
| 253 bool _complete_doStatement() { | 258 bool _complete_doStatement() { |
| 254 if (errors.isEmpty || node is! DoStatement) { | 259 if (node is! DoStatement) { |
| 255 return false; | 260 return false; |
| 256 } | 261 } |
| 257 DoStatement statement = node; | 262 DoStatement statement = node; |
| 258 SourceBuilder sb = _sourceBuilderAfterKeyword(statement.doKeyword); | 263 SourceBuilder sb = _sourceBuilderAfterKeyword(statement.doKeyword); |
| 259 bool hasWhileKeyword = statement.whileKeyword.lexeme == "while"; | 264 bool hasWhileKeyword = statement.whileKeyword.lexeme == "while"; |
| 260 int exitDelta = 0; | 265 int exitDelta = 0; |
| 261 if (statement.body is EmptyStatement) { | 266 if (statement.body is EmptyStatement) { |
| 262 String text = utils.getNodeText(statement.body); | 267 String text = utils.getNodeText(statement.body); |
| 263 int delta = 0; | 268 int delta = 0; |
| 264 if (text.startsWith(';')) { | 269 if (text.startsWith(';')) { |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 323 _insertBuilder(sb); | 328 _insertBuilder(sb); |
| 324 if (exitDelta != 0) { | 329 if (exitDelta != 0) { |
| 325 exitPosition = | 330 exitPosition = |
| 326 new Position(exitPosition.file, exitPosition.offset + exitDelta); | 331 new Position(exitPosition.file, exitPosition.offset + exitDelta); |
| 327 } | 332 } |
| 328 _setCompletion(DartStatementCompletion.COMPLETE_DO_STMT); | 333 _setCompletion(DartStatementCompletion.COMPLETE_DO_STMT); |
| 329 return true; | 334 return true; |
| 330 } | 335 } |
| 331 | 336 |
| 332 bool _complete_forEachStatement() { | 337 bool _complete_forEachStatement() { |
| 333 if (errors.isEmpty || node is! ForEachStatement) { | 338 if (node is! ForEachStatement) { |
| 334 return false; | 339 return false; |
| 335 } | 340 } |
| 336 ForEachStatement forNode = node; | 341 ForEachStatement forNode = node; |
| 337 if (forNode.inKeyword.isSynthetic) { | 342 if (forNode.inKeyword.isSynthetic) { |
| 338 return false; // Can't happen -- would be parsed as a for-statement. | 343 return false; // Can't happen -- would be parsed as a for-statement. |
| 339 } | 344 } |
| 340 SourceBuilder sb = | 345 SourceBuilder sb = |
| 341 new SourceBuilder(file, forNode.rightParenthesis.offset + 1); | 346 new SourceBuilder(file, forNode.rightParenthesis.offset + 1); |
| 342 AstNode name = forNode.identifier; | 347 AstNode name = forNode.identifier; |
| 343 name ??= forNode.loopVariable; | 348 name ??= forNode.loopVariable; |
| (...skipping 25 matching lines...) Expand all Loading... |
| 369 if (_isEmptyStatement(forNode.body)) { | 374 if (_isEmptyStatement(forNode.body)) { |
| 370 sb.append(' '); | 375 sb.append(' '); |
| 371 _appendEmptyBraces(sb, exitPosition == null); | 376 _appendEmptyBraces(sb, exitPosition == null); |
| 372 } | 377 } |
| 373 _insertBuilder(sb); | 378 _insertBuilder(sb); |
| 374 _setCompletion(DartStatementCompletion.COMPLETE_FOR_EACH_STMT); | 379 _setCompletion(DartStatementCompletion.COMPLETE_FOR_EACH_STMT); |
| 375 return true; | 380 return true; |
| 376 } | 381 } |
| 377 | 382 |
| 378 bool _complete_forStatement() { | 383 bool _complete_forStatement() { |
| 379 if (errors.isEmpty || node is! ForStatement) { | 384 if (node is! ForStatement) { |
| 380 return false; | 385 return false; |
| 381 } | 386 } |
| 382 ForStatement forNode = node; | 387 ForStatement forNode = node; |
| 383 SourceBuilder sb; | 388 SourceBuilder sb; |
| 384 int delta = 0; | 389 int delta = 0; |
| 385 if (forNode.leftParenthesis.isSynthetic) { | 390 if (forNode.leftParenthesis.isSynthetic) { |
| 386 if (!forNode.rightParenthesis.isSynthetic) { | 391 if (!forNode.rightParenthesis.isSynthetic) { |
| 387 return false; | 392 return false; |
| 388 } | 393 } |
| 389 // keywordOnly (unit test name suffix that exercises this branch) | 394 // keywordOnly (unit test name suffix that exercises this branch) |
| 390 sb = _sourceBuilderAfterKeyword(forNode.forKeyword); | 395 sb = _sourceBuilderAfterKeyword(forNode.forKeyword); |
| 391 sb.append('('); | 396 sb.append('('); |
| 392 sb.setExitOffset(); | 397 sb.setExitOffset(); |
| 393 sb.append(')'); | 398 sb.append(')'); |
| 394 } else { | 399 } else { |
| 395 if (!forNode.rightSeparator.isSynthetic) { | 400 if (!forNode.rightSeparator.isSynthetic) { |
| 396 // Fully-defined init, cond, updaters so nothing more needed here. | 401 // Fully-defined init, cond, updaters so nothing more needed here. |
| 397 // emptyParts | 402 // emptyParts |
| 398 sb = new SourceBuilder(file, forNode.rightParenthesis.offset + 1); | 403 sb = new SourceBuilder(file, forNode.rightParenthesis.offset + 1); |
| 399 } else if (!forNode.leftSeparator.isSynthetic) { | 404 } else if (!forNode.leftSeparator.isSynthetic) { |
| 400 if (_isSyntheticExpression(forNode.condition)) { | 405 if (_isSyntheticExpression(forNode.condition)) { |
| 401 exitPosition = _newPosition(forNode.leftSeparator.offset + 1); | 406 exitPosition = _newPosition(forNode.leftSeparator.offset + 1); |
| 402 String text = utils | 407 String text = utils |
| 403 .getNodeText(forNode) | 408 .getNodeText(forNode) |
| 404 .substring(forNode.leftSeparator.offset - forNode.offset); | 409 .substring(forNode.leftSeparator.offset - forNode.offset); |
| 405 if (text.startsWith(new RegExp(r';\s*\)'))) { | 410 if (text.startsWith(new RegExp(r';\s*\)'))) { |
| 406 // emptyCondition | 411 // emptyCondition |
| 407 int end = text.indexOf(')'); | 412 int end = text.indexOf(')'); |
| 408 sb = new SourceBuilder(file, forNode.leftSeparator.offset); | 413 sb = new SourceBuilder(file, forNode.leftSeparator.offset); |
| 409 // TODO(messick) Consider adding two semicolons here. | 414 _addReplaceEdit(rangeStartLength(sb.offset, end), '; ; '); |
| 410 _addReplaceEdit(rangeStartLength(sb.offset, end), '; '); | |
| 411 delta = end - '; '.length; | 415 delta = end - '; '.length; |
| 412 } else { | 416 } else { |
| 413 // emptyInitializersEmptyCondition | 417 // emptyInitializersEmptyCondition |
| 414 exitPosition = _newPosition(forNode.rightParenthesis.offset); | 418 exitPosition = _newPosition(forNode.rightParenthesis.offset); |
| 415 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); | 419 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); |
| 416 } | 420 } |
| 417 } else { | 421 } else { |
| 418 // emptyUpdaters | 422 // emptyUpdaters |
| 419 exitPosition = _newPosition(forNode.rightSeparator.offset); | 423 exitPosition = _newPosition(forNode.rightSeparator.offset); |
| 420 sb = new SourceBuilder(file, forNode.rightSeparator.offset); | 424 sb = new SourceBuilder(file, forNode.rightSeparator.offset); |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 466 if (statement.block is EmptyStatement) { | 470 if (statement.block is EmptyStatement) { |
| 467 sb.append(' '); | 471 sb.append(' '); |
| 468 _appendEmptyBraces(sb, exitPosition == null); | 472 _appendEmptyBraces(sb, exitPosition == null); |
| 469 } | 473 } |
| 470 _insertBuilder(sb); | 474 _insertBuilder(sb); |
| 471 _setCompletion(kind); | 475 _setCompletion(kind); |
| 472 return true; | 476 return true; |
| 473 } | 477 } |
| 474 | 478 |
| 475 bool _complete_ifStatement() { | 479 bool _complete_ifStatement() { |
| 476 if (errors.isEmpty || node is! IfStatement) { | 480 if (node is! IfStatement) { |
| 477 return false; | 481 return false; |
| 478 } | 482 } |
| 479 IfStatement ifNode = node; | 483 IfStatement ifNode = node; |
| 480 if (ifNode != null) { | 484 if (ifNode != null) { |
| 481 if (ifNode.elseKeyword != null) { | 485 if (ifNode.elseKeyword != null) { |
| 482 return false; | 486 return false; |
| 483 } | 487 } |
| 484 var stmt = new _KeywordConditionBlockStructure( | 488 var stmt = new _KeywordConditionBlockStructure( |
| 485 ifNode.ifKeyword, | 489 ifNode.ifKeyword, |
| 486 ifNode.leftParenthesis, | 490 ifNode.leftParenthesis, |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 539 int insertOffset = error.offset + error.length; | 543 int insertOffset = error.offset + error.length; |
| 540 _addInsertEdit(insertOffset, ';'); | 544 _addInsertEdit(insertOffset, ';'); |
| 541 int offset = _appendNewlinePlusIndent() + 1 /* ';' */; | 545 int offset = _appendNewlinePlusIndent() + 1 /* ';' */; |
| 542 _setCompletionAt(DartStatementCompletion.SIMPLE_SEMICOLON, offset); | 546 _setCompletionAt(DartStatementCompletion.SIMPLE_SEMICOLON, offset); |
| 543 return true; | 547 return true; |
| 544 } | 548 } |
| 545 return false; | 549 return false; |
| 546 } | 550 } |
| 547 | 551 |
| 548 bool _complete_switchStatement() { | 552 bool _complete_switchStatement() { |
| 549 if (errors.isEmpty || node is! SwitchStatement) { | 553 if (node is! SwitchStatement) { |
| 550 return false; | 554 return false; |
| 551 } | 555 } |
| 552 SourceBuilder sb; | 556 SourceBuilder sb; |
| 553 SwitchStatement switchNode = node; | 557 SwitchStatement switchNode = node; |
| 554 if (switchNode.leftParenthesis.isSynthetic && | 558 if (switchNode.leftParenthesis.isSynthetic && |
| 555 switchNode.rightParenthesis.isSynthetic) { | 559 switchNode.rightParenthesis.isSynthetic) { |
| 556 exitPosition = new Position(file, switchNode.switchKeyword.end + 2); | 560 exitPosition = new Position(file, switchNode.switchKeyword.end + 2); |
| 557 String src = utils.getNodeText(switchNode); | 561 String src = utils.getNodeText(switchNode); |
| 558 if (src | 562 if (src |
| 559 .substring(switchNode.switchKeyword.end - switchNode.offset) | 563 .substring(switchNode.switchKeyword.end - switchNode.offset) |
| (...skipping 19 matching lines...) Expand all Loading... |
| 579 // See https://github.com/dart-lang/sdk/issues/29391 | 583 // See https://github.com/dart-lang/sdk/issues/29391 |
| 580 sb.append(' '); | 584 sb.append(' '); |
| 581 _appendEmptyBraces(sb, exitPosition == null); | 585 _appendEmptyBraces(sb, exitPosition == null); |
| 582 } | 586 } |
| 583 _insertBuilder(sb); | 587 _insertBuilder(sb); |
| 584 _setCompletion(DartStatementCompletion.COMPLETE_SWITCH_STMT); | 588 _setCompletion(DartStatementCompletion.COMPLETE_SWITCH_STMT); |
| 585 return true; | 589 return true; |
| 586 } | 590 } |
| 587 | 591 |
| 588 bool _complete_tryStatement() { | 592 bool _complete_tryStatement() { |
| 589 if (errors.isEmpty || node is! TryStatement) { | 593 if (node is! TryStatement) { |
| 590 return false; | 594 return false; |
| 591 } | 595 } |
| 592 TryStatement tryNode = node; | 596 TryStatement tryNode = node; |
| 593 SourceBuilder sb; | 597 SourceBuilder sb; |
| 594 CatchClause catchNode; | 598 CatchClause catchNode; |
| 595 bool addSpace = true; | 599 bool addSpace = true; |
| 596 if (tryNode.body.leftBracket.isSynthetic) { | 600 if (tryNode.body.leftBracket.isSynthetic) { |
| 597 String src = utils.getNodeText(tryNode); | 601 String src = utils.getNodeText(tryNode); |
| 598 if (src | 602 if (src |
| 599 .substring(tryNode.tryKeyword.end - tryNode.offset) | 603 .substring(tryNode.tryKeyword.end - tryNode.offset) |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 666 sb.append(' '); | 670 sb.append(' '); |
| 667 _appendEmptyBraces(sb, true); | 671 _appendEmptyBraces(sb, true); |
| 668 _insertBuilder(sb); | 672 _insertBuilder(sb); |
| 669 } | 673 } |
| 670 } | 674 } |
| 671 _setCompletion(DartStatementCompletion.COMPLETE_TRY_STMT); | 675 _setCompletion(DartStatementCompletion.COMPLETE_TRY_STMT); |
| 672 return true; | 676 return true; |
| 673 } | 677 } |
| 674 | 678 |
| 675 bool _complete_whileStatement() { | 679 bool _complete_whileStatement() { |
| 676 if (errors.isEmpty || node is! WhileStatement) { | 680 if (node is! WhileStatement) { |
| 677 return false; | 681 return false; |
| 678 } | 682 } |
| 679 WhileStatement whileNode = node; | 683 WhileStatement whileNode = node; |
| 680 if (whileNode != null) { | 684 if (whileNode != null) { |
| 681 var stmt = new _KeywordConditionBlockStructure( | 685 var stmt = new _KeywordConditionBlockStructure( |
| 682 whileNode.whileKeyword, | 686 whileNode.whileKeyword, |
| 683 whileNode.leftParenthesis, | 687 whileNode.leftParenthesis, |
| 684 whileNode.condition, | 688 whileNode.condition, |
| 685 whileNode.rightParenthesis, | 689 whileNode.rightParenthesis, |
| 686 whileNode.body); | 690 whileNode.body); |
| (...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 802 final Token keyword; | 806 final Token keyword; |
| 803 final Token leftParenthesis, rightParenthesis; | 807 final Token leftParenthesis, rightParenthesis; |
| 804 final Expression condition; | 808 final Expression condition; |
| 805 final Statement block; | 809 final Statement block; |
| 806 | 810 |
| 807 _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis, | 811 _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis, |
| 808 this.condition, this.rightParenthesis, this.block); | 812 this.condition, this.rightParenthesis, this.block); |
| 809 | 813 |
| 810 int get offset => keyword.offset; | 814 int get offset => keyword.offset; |
| 811 } | 815 } |
| OLD | NEW |