mirror of
https://github.com/dart-lang/sdk
synced 2024-10-03 21:21:32 +00:00
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:
parent
720b6c963c
commit
5b4d67b22e
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue