Compute runtime completions in the context file itsef.

So, we support all elements - class fields, top-level functions and
variables, etc. And it is just simpler too.

R=brianwilkerson@google.com

Change-Id: Ifed8f9b8d8ef857750b28c48f851d8611a56ef22
Reviewed-on: https://dart-review.googlesource.com/55990
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov 2018-05-19 17:03:21 +00:00 committed by commit-bot@chromium.org
parent 720b6c963c
commit 5b4d67b22e
2 changed files with 112 additions and 117 deletions

View file

@ -14,13 +14,9 @@ import 'package:analysis_server/src/provisional/completion/completion_core.dart'
import 'package:analysis_server/src/services/completion/completion_core.dart';
import 'package:analysis_server/src/services/completion/completion_performance.dart';
import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
@ -38,9 +34,6 @@ class RuntimeCompletionComputer {
final List<RuntimeCompletionVariable> variables;
final List<RuntimeCompletionExpression> expressions;
AnalysisSession session;
_Context context;
RuntimeCompletionComputer(
this.resourceProvider,
this.fileContentOverlay,
@ -53,58 +46,48 @@ class RuntimeCompletionComputer {
this.expressions);
Future<RuntimeCompletionResult> compute() async {
var pathContext = resourceProvider.pathContext;
var contextDir = pathContext.dirname(contextFile);
var targetPath = pathContext.join(contextDir, '_runtimeCompletion.dart');
var contextResult = await analysisDriver.getResult(contextFile);
var session = contextResult.session;
// TODO(scheglov) Use variables.
await _initContext();
const codeMarker = '__code_\_';
String baseTargetCode = r'''
library _runtimeCompletion;
''';
fileContentOverlay[targetPath] = baseTargetCode;
const codeMarker = '__code__';
// Build the target code that provides the same context.
// Insert the code being completed at the context offset.
var changeBuilder = new DartChangeBuilder(session);
int nextImportPrefixIndex = 0;
await changeBuilder.addFileEdit(targetPath, (builder) {
builder.addInsertion(baseTargetCode.length, (builder) {
builder.writeln('main() {');
// Write all visible local declarations.
for (var local in context.locals.values) {
builder.writeLocalVariableDeclaration(local.name, type: local.type);
builder.writeln();
}
await changeBuilder.addFileEdit(contextFile, (builder) {
builder.addInsertion(contextOffset, (builder) {
builder.writeln('{');
// TODO(scheglov) Use variables.
// Write the marker to insert the code being completed.
builder.write(codeMarker);
// Finalize main().
builder.writeln(';');
builder.writeln('}');
});
}, importPrefixGenerator: (uri) => '__prefix${nextImportPrefixIndex++}');
// Compute the target code.
List<SourceEdit> targetEdits = changeBuilder.sourceChange.edits[0].edits;
String targetCode = SourceEdit.applySequence(baseTargetCode, targetEdits);
// Compute the patched context file content.
String targetCode = SourceEdit.applySequence(
contextResult.content,
changeBuilder.sourceChange.edits[0].edits,
);
// Insert the code being completed.
int targetOffset = targetCode.indexOf(codeMarker) + offset;
targetCode = targetCode.replaceAll(codeMarker, code);
// Resolve the constructed target file.
// Update the context file content to include the code being completed.
// Then resolve it, and restore the file to its initial state.
AnalysisResult targetResult;
String contentFileOverlay = fileContentOverlay[contextFile];
try {
fileContentOverlay[targetPath] = targetCode;
analysisDriver.changeFile(targetPath);
targetResult = await analysisDriver.getResult(targetPath);
fileContentOverlay[contextFile] = targetCode;
analysisDriver.changeFile(contextFile);
targetResult = await analysisDriver.getResult(contextFile);
} finally {
fileContentOverlay[targetPath] = null;
analysisDriver.changeFile(targetPath);
fileContentOverlay[contextFile] = contentFileOverlay;
analysisDriver.changeFile(contextFile);
}
CompletionContributor contributor = new DartCompletionManager();
@ -122,12 +105,6 @@ library _runtimeCompletion;
var expressions = <RuntimeCompletionExpression>[];
return new RuntimeCompletionResult(expressions, suggestions);
}
Future<void> _initContext() async {
var contextResult = await analysisDriver.getResult(contextFile);
session = contextResult.session;
context = new _Context(contextResult.unit, contextOffset);
}
}
/// The result of performing runtime completion.
@ -137,74 +114,3 @@ class RuntimeCompletionResult {
RuntimeCompletionResult(this.expressions, this.suggestions);
}
/// The context in which completion is performed.
class _Context {
final int contextOffset;
ClassElement enclosingClass;
Map<String, VariableElement> locals = {};
_Context(CompilationUnit unit, this.contextOffset) {
var node = new NodeLocator2(contextOffset).searchWithin(unit);
var enclosingClass = node.getAncestor((n) => n is ClassDeclaration);
if (enclosingClass is ClassDeclaration) {
this.enclosingClass = enclosingClass.element;
}
_appendLocals(node);
}
void _appendLocals(AstNode node) {
if (node is Block) {
for (var statement in node.statements) {
if (statement.offset > contextOffset) {
break;
}
if (statement is VariableDeclarationStatement) {
_appendVariables(statement.variables);
}
}
} else if (node is ClassDeclaration) {
// TODO(scheglov) implement, maybe not locals anymore
return;
} else if (node is CompilationUnit) {
return;
} else if (node is ConstructorDeclaration) {
_appendParameters(node.parameters);
} else if (node is FunctionDeclaration) {
_appendParameters(node.functionExpression.parameters);
} else if (node is FunctionExpression) {
_appendParameters(node.parameters);
} else if (node is ForEachStatement) {
LocalVariableElement element = node.loopVariable?.element;
if (element != null) {
locals[element.name] ??= element;
}
} else if (node is ForStatement) {
_appendVariables(node.variables);
} else if (node is MethodDeclaration) {
_appendParameters(node.parameters);
}
_appendLocals(node.parent);
}
void _appendParameters(FormalParameterList parameters) {
if (parameters != null) {
for (var parameter in parameters.parameters) {
VariableElement element = parameter.element;
locals[element.name] ??= element;
}
}
}
void _appendVariables(VariableDeclarationList variables) {
if (variables != null) {
for (var variable in variables.variables) {
VariableElement element = variable.element;
locals[element.name] ??= element;
}
}
}
}

View file

@ -95,6 +95,68 @@ class RuntimeCompletionComputerTest extends AbstractContextTest {
return null;
}
test_class_fields() async {
addContextFile(r'''
class A {
int a;
}
class B extends A {
double b, c;
void foo() {
// context line
}
}
''');
await computeCompletion('^');
assertSuggested('a', returnType: 'int');
assertSuggested('b', returnType: 'double');
assertSuggested('c', returnType: 'double');
}
test_class_methods() async {
addContextFile(r'''
class A {
int a() => null;
}
class B extends A {
double b() => null;
void foo() {
// context line
}
}
''');
await computeCompletion('^');
assertSuggested('a', returnType: 'int');
assertSuggested('b', returnType: 'double');
}
test_inPart() async {
addSource('/test/lib/a.dart', r'''
part 'b.dart';
part 'context.dart';
int a;
''');
addSource('/test/lib/b.dart', r'''
part of 'a.dart';
double b;
''');
addContextFile(r'''
part of 'a.dart';
String c;
void main() {
// context line
}
''');
await computeCompletion('^');
assertSuggested('a', returnType: 'int');
assertSuggested('b', returnType: 'double');
assertSuggested('c', returnType: 'String');
}
test_locals_block() async {
addContextFile(r'''
class A {
@ -287,4 +349,31 @@ main() {
expect(suggestion.completion, isNot(startsWith('__prefix')));
}
}
test_topLevelFunctions() async {
addContextFile(r'''
int a() => null;
double b() => null;
void main() {
// context line
}
''');
await computeCompletion('^');
assertSuggested('a', returnType: 'int');
assertSuggested('b', returnType: 'double');
}
test_topLevelVariables() async {
addContextFile(r'''
int a;
double b;
void main() {
// context line
}
''');
await computeCompletion('^');
assertSuggested('a', returnType: 'int');
assertSuggested('b', returnType: 'double');
}
}