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

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

Issue 2852073002: Complete declarations; detect invalid blocks (Closed)
Patch Set: Created 3 years, 8 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 side-by-side diff with in-line comments
Download patch
Index: pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart
diff --git a/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart b/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart
index 986ca9500ad365dccff4e005475f955d496a6b8f..fada03424adc4dac6f12cf81ab92466c516468d4 100644
--- a/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart
+++ b/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart
@@ -33,6 +33,8 @@ class DartStatementCompletion {
'SIMPLE_ENTER', "Insert a newline at the end of the current line");
static const SIMPLE_SEMICOLON = const StatementCompletionKind(
'SIMPLE_SEMICOLON', "Add a semicolon and newline");
+ static const COMPLETE_CLASS_DECLARATION = const StatementCompletionKind(
+ 'COMPLETE_CLASS_DECLARATION', "Complete class declaration");
static const COMPLETE_CONTROL_FLOW_BLOCK = const StatementCompletionKind(
'COMPLETE_CONTROL_FLOW_BLOCK', "Complete control flow block");
static const COMPLETE_DO_STMT = const StatementCompletionKind(
@@ -43,10 +45,14 @@ class DartStatementCompletion {
'COMPLETE_FOR_STMT', "Complete for-statement");
static const COMPLETE_FOR_EACH_STMT = const StatementCompletionKind(
'COMPLETE_FOR_EACH_STMT', "Complete for-each-statement");
+ static const COMPLETE_FUNCTION_DECLARATION = const StatementCompletionKind(
+ 'COMPLETE_FUNCTION_DECLARATION', "Complete function declaration");
static const COMPLETE_SWITCH_STMT = const StatementCompletionKind(
'COMPLETE_SWITCH_STMT', "Complete switch-statement");
static const COMPLETE_TRY_STMT = const StatementCompletionKind(
'COMPLETE_TRY_STMT', "Complete try-statement");
+ static const COMPLETE_VARIABLE_DECLARATION = const StatementCompletionKind(
+ 'COMPLETE_VARIABLE_DECLARATION', "Complete variable declaration");
static const COMPLETE_WHILE_STMT = const StatementCompletionKind(
'COMPLETE_WHILE_STMT', "Complete while-statement");
}
@@ -168,21 +174,20 @@ class StatementCompletionProcessor {
if (analysisContext.getModificationStamp(source) != fileStamp) {
return NO_COMPLETION;
}
- node = new NodeLocator(selectionOffset).searchWithin(unit);
+ node = _selectedNode();
if (node == null) {
return NO_COMPLETION;
}
- // TODO(messick): This needs to work for declarations.
- AstNode newNode = node.getAncestor((n) => n is Statement);
- if (newNode is Block) {
- Block blockNode = newNode;
+ node = node
+ .getAncestor((n) => n is Statement || _isNonStatementDeclaration(n));
+ if (node == null) {
+ return _complete_simpleEnter() ? completion : NO_COMPLETION;
+ }
+ if (node is Block) {
+ Block blockNode = node;
if (blockNode.statements.isNotEmpty) {
node = blockNode.statements.last;
- } else {
- node = newNode;
}
- } else {
- node = newNode;
}
if (_isEmptyStatement(node)) {
node = node.parent;
@@ -196,24 +201,42 @@ class StatementCompletionProcessor {
}
}
- if (errors.isEmpty) {
- if (_complete_controlFlowBlock() || _complete_simpleEnter()) {
- return completion;
+ if (node is Statement) {
+ if (errors.isEmpty) {
+ if (_complete_ifStatement() ||
+ _complete_forStatement() ||
+ _complete_forEachStatement() ||
+ _complete_whileStatement() ||
+ _complete_controlFlowBlock()) {
+ return completion;
+ }
+ } else {
+ if (_complete_ifStatement() ||
+ _complete_doStatement() ||
+ _complete_forStatement() ||
+ _complete_forEachStatement() ||
+ _complete_functionDeclarationStatement() ||
+ _complete_switchStatement() ||
+ _complete_tryStatement() ||
+ _complete_whileStatement() ||
+ _complete_controlFlowBlock() ||
+ _complete_simpleSemicolon() ||
+ _complete_methodCall()) {
+ return completion;
+ }
}
- } else {
- if (_complete_ifStatement() ||
- _complete_doStatement() ||
- _complete_forStatement() ||
- _complete_forEachStatement() ||
- _complete_switchStatement() ||
- _complete_tryStatement() ||
- _complete_whileStatement() ||
- _complete_controlFlowBlock() ||
- _complete_simpleSemicolon() ||
- _complete_simpleEnter()) {
- return completion;
+ } else if (node is Declaration) {
+ if (errors.isNotEmpty) {
+ if (_complete_classDeclaration() ||
+ _complete_functionDeclaration() ||
+ _complete_variableDeclaration()) {
+ return completion;
+ }
}
}
+ if (_complete_simpleEnter()) {
+ return completion;
+ }
return NO_COMPLETION;
}
@@ -262,6 +285,23 @@ class StatementCompletionProcessor {
return text;
}
+ bool _complete_classDeclaration() {
+ if (node is! ClassDeclaration) {
+ return false;
+ }
+ ClassDeclaration decl = node;
+ if (decl.leftBracket.isSynthetic && errors.length == 1) {
+ // The space before the left brace is assumed to exist, even if it does not.
+ SourceBuilder sb = new SourceBuilder(file, decl.end - 1);
+ sb.append(' ');
+ _appendEmptyBraces(sb, true);
+ _insertBuilder(sb);
+ _setCompletion(DartStatementCompletion.COMPLETE_CLASS_DECLARATION);
+ return true;
+ }
+ return false;
+ }
+
bool _complete_controlFlowBlock() {
Expression expr = (node is ExpressionStatement)
? (node as ExpressionStatement).expression
@@ -322,7 +362,7 @@ class StatementCompletionProcessor {
SourceBuilder sb = _sourceBuilderAfterKeyword(statement.doKeyword);
bool hasWhileKeyword = statement.whileKeyword.lexeme == "while";
int exitDelta = 0;
- if (statement.body is EmptyStatement) {
+ if (!_statementHasValidBody(statement.doKeyword, statement.body)) {
String text = utils.getNodeText(statement.body);
int delta = 0;
if (text.startsWith(';')) {
@@ -430,7 +470,7 @@ class StatementCompletionProcessor {
' ');
}
}
- if (_isEmptyStatement(forNode.body)) {
+ if (!_statementHasValidBody(forNode.forKeyword, forNode.body)) {
sb.append(' ');
_appendEmptyBraces(sb, exitPosition == null);
}
@@ -506,8 +546,8 @@ class StatementCompletionProcessor {
}
}
}
- if (forNode.body is EmptyStatement) {
- // keywordOnly
+ if (!_statementHasValidBody(forNode.forKeyword, forNode.body)) {
+ // keywordOnly, noError
sb.append(' ');
_appendEmptyBraces(sb, exitPosition == null);
}
@@ -520,16 +560,68 @@ class StatementCompletionProcessor {
return true;
}
+ bool _complete_functionDeclaration() {
+ if (node is! MethodDeclaration && node is! FunctionDeclaration) {
+ return false;
+ }
+ SourceBuilder sb = new SourceBuilder(file, node.end - 1);
+ sb.append(' ');
+ _appendEmptyBraces(sb, true);
+ _insertBuilder(sb);
+ _setCompletion(DartStatementCompletion.COMPLETE_FUNCTION_DECLARATION);
+ return true;
+ }
+
+ bool _complete_functionDeclarationStatement() {
+ if (node is! FunctionDeclarationStatement) {
+ return false;
+ }
+ var error = _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'");
+ if (error != null) {
+ FunctionDeclarationStatement stmt = node;
+ String src = utils.getNodeText(stmt);
+ int insertOffset = stmt.functionDeclaration.end - 1;
+ if (stmt.functionDeclaration.functionExpression.body
+ is ExpressionFunctionBody) {
+ ExpressionFunctionBody fnb =
+ stmt.functionDeclaration.functionExpression.body;
+ int fnbOffset = fnb.functionDefinition.offset;
+ String fnSrc = src.substring(fnbOffset - stmt.offset);
+ if (!fnSrc.startsWith('=>')) {
+ return false;
+ }
+ int delta = 0;
+ if (fnb.expression.isSynthetic) {
+ if (!fnSrc.startsWith('=> ')) {
+ _addInsertEdit(insertOffset, ' ');
+ delta = 1;
+ }
+ _addInsertEdit(insertOffset, ';');
+ _appendNewlinePlusIndentAt(insertOffset);
+ } else {
+ delta = 1;
+ _addInsertEdit(insertOffset, ';');
+ insertOffset = _appendNewlinePlusIndent();
+ }
+ _setCompletionAt(
+ DartStatementCompletion.SIMPLE_SEMICOLON, insertOffset + delta);
+ return true;
+ }
+ }
+ return false;
+ }
+
bool _complete_ifOrWhileStatement(
_KeywordConditionBlockStructure statement, StatementCompletionKind kind) {
+ if (_statementHasValidBody(statement.keyword, statement.block)) {
+ return false;
+ }
SourceBuilder sb = _complete_keywordCondition(statement);
if (sb == null) {
return false;
}
- if (statement.block is EmptyStatement) {
- sb.append(' ');
- _appendEmptyBraces(sb, exitPosition == null);
- }
+ sb.append(' ');
+ _appendEmptyBraces(sb, exitPosition == null);
_insertBuilder(sb);
_setCompletion(kind);
return true;
@@ -540,20 +632,17 @@ class StatementCompletionProcessor {
return false;
}
IfStatement ifNode = node;
- if (ifNode != null) {
- if (ifNode.elseKeyword != null) {
- return false;
- }
- var stmt = new _KeywordConditionBlockStructure(
- ifNode.ifKeyword,
- ifNode.leftParenthesis,
- ifNode.condition,
- ifNode.rightParenthesis,
- ifNode.thenStatement);
- return _complete_ifOrWhileStatement(
- stmt, DartStatementCompletion.COMPLETE_IF_STMT);
+ if (ifNode.elseKeyword != null) {
+ return false;
}
- return false;
+ var stmt = new _KeywordConditionBlockStructure(
+ ifNode.ifKeyword,
+ ifNode.leftParenthesis,
+ ifNode.condition,
+ ifNode.rightParenthesis,
+ ifNode.thenStatement);
+ return _complete_ifOrWhileStatement(
+ stmt, DartStatementCompletion.COMPLETE_IF_STMT);
}
SourceBuilder _complete_keywordCondition(
@@ -579,6 +668,38 @@ class StatementCompletionProcessor {
return sb;
}
+ bool _complete_methodCall() {
+ var parenError =
+ _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "')'");
+ if (parenError == null) {
+ return false;
+ }
+ AstNode argList = _selectedNode(at: parenError.offset - 1)
+ .getAncestor((n) => n is ArgumentList);
+ if (argList?.getAncestor((n) => n == node) == null) {
+ return false;
+ }
+ int loc = argList.end - 1;
+ int delta = 1;
+ var semicolonError =
+ _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'");
+ if (semicolonError == null) {
+ loc += 1;
+ delta = 0;
+ }
+ _addInsertEdit(loc, ')');
+ if (semicolonError != null) {
+ _addInsertEdit(loc, ';');
+ }
+ String indent = utils.getLinePrefix(selectionOffset);
+ int exit = utils.getLineNext(selectionOffset);
+ _addInsertEdit(exit, indent + eol);
+ exit += indent.length + eol.length;
+
+ _setCompletionAt(DartStatementCompletion.SIMPLE_ENTER, exit + delta);
+ return true;
+ }
+
bool _complete_simpleEnter() {
int offset;
if (!errors.isEmpty) {
@@ -748,6 +869,16 @@ class StatementCompletionProcessor {
return true;
}
+ bool _complete_variableDeclaration() {
+ if (node is! VariableDeclaration) {
+ return false;
+ }
+ _addInsertEdit(node.end, ';');
+ exitPosition = new Position(file, _appendNewlinePlusIndentAt(node.end) + 1);
+ _setCompletion(DartStatementCompletion.COMPLETE_VARIABLE_DECLARATION);
+ return true;
+ }
+
bool _complete_whileStatement() {
if (node is! WhileStatement) {
return false;
@@ -767,22 +898,16 @@ class StatementCompletionProcessor {
}
engine.AnalysisError _findError(ErrorCode code, {partialMatch: null}) {
- var error =
- errors.firstWhere((err) => err.errorCode == code, orElse: () => null);
- if (error != null) {
- if (partialMatch != null) {
- return error.message.contains(partialMatch) ? error : null;
- }
- return error;
- }
- return null;
+ return errors.firstWhere(
+ (err) =>
+ err.errorCode == code &&
+ (partialMatch == null ? true : err.message.contains(partialMatch)),
+ orElse: () => null);
}
T _findInvalidElement<T extends AstNode>(NodeList<T> list) {
return list.firstWhere(
- (catchClause) =>
- selectionOffset >= catchClause.offset &&
- selectionOffset <= catchClause.end,
+ (item) => selectionOffset >= item.offset && selectionOffset <= item.end,
orElse: () => null);
}
@@ -828,6 +953,17 @@ class StatementCompletionProcessor {
return stmt is EmptyStatement || _isEmptyBlock(stmt);
}
+ bool _isNonStatementDeclaration(AstNode n) {
+ if (n is! Declaration) {
+ return false;
+ }
+ if (n is! VariableDeclaration && n is! FunctionDeclaration) {
+ return true;
+ }
+ AstNode p = n.parent;
+ return p is! Statement && p?.parent is! Statement;
+ }
+
bool _isSyntheticExpression(Expression expr) {
return expr is SimpleIdentifier && expr.isSynthetic;
}
@@ -836,6 +972,9 @@ class StatementCompletionProcessor {
return new Position(file, offset);
}
+ AstNode _selectedNode({int at: null}) =>
+ new NodeLocator(at == null ? selectionOffset : at).searchWithin(unit);
+
void _setCompletion(StatementCompletionKind kind, [List args]) {
assert(exitPosition != null);
change.selection = exitPosition;
@@ -864,6 +1003,20 @@ class StatementCompletionProcessor {
}
return sb;
}
+
+ bool _statementHasValidBody(Token keyword, Statement body) {
+ // A "valid" body is either a non-synthetic block or a single statement
+ // on the same line as the parent statement, similar to dart_style.
+ if (body.isSynthetic) {
+ return false;
+ }
+ if (body is Block) {
+ Block block = body;
+ return (!(block.leftBracket.isSynthetic));
+ }
+ return (lineInfo.getLocation(keyword.offset) ==
+ lineInfo.getLocation(body.offset));
+ }
}
// Encapsulate common structure of if-statement and while-statement.

Powered by Google App Engine
This is Rietveld 408576698