587cbed231
The tool was previously using the old implementation, which is now only used by the Cider support. As a result, it wasn't providing valid timing measurements for the new implementation. That's been fixed. The map/reduce functionality is not yet supported, but it's also rarely used. There's some information missing, but most of the functionality works as expected, and I'd like to tackle the rest in a separate CL (though I don't know exactly when I'll get to it). Change-Id: I98150c1d5273a8ffaccf7858f9588e52e11636c8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/444183 Reviewed-by: Samuel Rawlins <srawlins@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
896 lines
27 KiB
Dart
896 lines
27 KiB
Dart
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
import 'package:analysis_server/src/protocol/protocol_internal.dart';
|
|
import 'package:analysis_server/src/protocol_server.dart' as protocol;
|
|
import 'package:analysis_server/src/services/completion/dart/candidate_suggestion.dart';
|
|
import 'package:analyzer/dart/analysis/results.dart';
|
|
import 'package:analyzer/dart/ast/ast.dart';
|
|
import 'package:analyzer/dart/ast/syntactic_entity.dart';
|
|
import 'package:analyzer/dart/ast/token.dart';
|
|
import 'package:analyzer/dart/ast/visitor.dart';
|
|
import 'package:analyzer/dart/element/element.dart' as element;
|
|
import 'package:analyzer/dart/element/type.dart';
|
|
import 'package:analyzer/source/line_info.dart';
|
|
|
|
class ExpectedCompletion {
|
|
final String _filePath;
|
|
|
|
final SyntacticEntity _entity;
|
|
|
|
/// Some completions are special cased from the DAS "import" for instance is
|
|
/// suggested as a completion "import '';", the completion string here in this
|
|
/// instance would have the value "import '';".
|
|
final String? _completionString;
|
|
|
|
final protocol.CompletionSuggestionKind? _kind;
|
|
|
|
final int _lineNumber;
|
|
|
|
final int _columnNumber;
|
|
|
|
final protocol.ElementKind? _elementKind;
|
|
|
|
ExpectedCompletion(
|
|
this._filePath,
|
|
this._entity,
|
|
this._lineNumber,
|
|
this._columnNumber,
|
|
this._kind,
|
|
this._elementKind,
|
|
) : _completionString = null;
|
|
|
|
/// Return an instance extracted from the decoded JSON [map].
|
|
factory ExpectedCompletion.fromJson(Map<String, dynamic> map) {
|
|
var jsonDecoder = ResponseDecoder(null);
|
|
var filePath = map['filePath'] as String;
|
|
var offset = map['offset'] as int;
|
|
var lineNumber = map['lineNumber'] as int;
|
|
var columnNumber = map['columnNumber'] as int;
|
|
var completionString = map['completionString'] as String;
|
|
var kind =
|
|
map['kind'] != null
|
|
? protocol.CompletionSuggestionKind.fromJson(
|
|
jsonDecoder,
|
|
'',
|
|
map['kind'] as String,
|
|
)
|
|
: null;
|
|
var elementKind =
|
|
map['elementKind'] != null
|
|
? protocol.ElementKind.fromJson(
|
|
jsonDecoder,
|
|
'',
|
|
map['elementKind'] as String,
|
|
)
|
|
: null;
|
|
return ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
_SyntacticEntity(offset),
|
|
lineNumber,
|
|
columnNumber,
|
|
completionString,
|
|
kind,
|
|
elementKind,
|
|
);
|
|
}
|
|
|
|
ExpectedCompletion.specialCompletionString(
|
|
this._filePath,
|
|
this._entity,
|
|
this._lineNumber,
|
|
this._columnNumber,
|
|
this._completionString,
|
|
this._kind,
|
|
this._elementKind,
|
|
);
|
|
|
|
int get columnNumber => _columnNumber;
|
|
|
|
String get completion => _completionString ?? _entity.toString();
|
|
|
|
protocol.ElementKind? get elementKind => _elementKind;
|
|
|
|
String get filePath => _filePath;
|
|
|
|
protocol.CompletionSuggestionKind? get kind => _kind;
|
|
|
|
int get lineNumber => _lineNumber;
|
|
|
|
String get location => '$filePath:$lineNumber:$columnNumber';
|
|
|
|
int get offset => _entity.offset;
|
|
|
|
SyntacticEntity get syntacticEntity => _entity;
|
|
|
|
bool matches(CandidateSuggestion completionSuggestion) {
|
|
// This method used to use the `_kind` and `_elementKind` to further
|
|
// validate that the right suggestion had been found, but that was removed
|
|
// because candidate suggestions don't have that information.
|
|
return completionSuggestion.completion == completion;
|
|
}
|
|
|
|
/// Return a map used to represent this expected completion in a JSON structure.
|
|
Map<String, dynamic> toJson() {
|
|
var kind = _kind;
|
|
var elementKind = _elementKind;
|
|
return {
|
|
'filePath': _filePath,
|
|
'offset': _entity.offset,
|
|
'lineNumber': _lineNumber,
|
|
'columnNumber': _columnNumber,
|
|
'completionString': _completionString,
|
|
if (kind != null) 'kind': kind.toJson(),
|
|
if (elementKind != null) 'elementKind': elementKind.toJson(),
|
|
};
|
|
}
|
|
|
|
@override
|
|
String toString() =>
|
|
"'$completion', kind = $kind, elementKind = $elementKind, $location";
|
|
}
|
|
|
|
class ExpectedCompletionsVisitor extends RecursiveAstVisitor<void> {
|
|
static const ASYNC_STAR = 'async*';
|
|
|
|
static const DEFAULT_COLON = 'default:';
|
|
|
|
static const DEFERRED_AS = 'deferred as';
|
|
|
|
static const EXPORT_STATEMENT = "export '';";
|
|
|
|
static const IMPORT_STATEMENT = "import '';";
|
|
|
|
static const PART_STATEMENT = "part '';";
|
|
static const SYNC_STAR = 'sync*';
|
|
static const YIELD_STAR = 'yield*';
|
|
|
|
/// The result of resolving the file being visited.
|
|
final ResolvedUnitResult result;
|
|
|
|
/// The offset from the location of each entity passed to
|
|
/// [safelyRecordEntity], to be used as the column for each
|
|
/// [ExpectedCompletion] created.
|
|
final int _caretOffset;
|
|
|
|
/// The completions that are expected to be produced in the file being
|
|
/// visited.
|
|
final List<ExpectedCompletion> expectedCompletions = [];
|
|
|
|
/// This boolean is set to enable whether or not we should assert that some
|
|
/// found keyword in Dart syntax should be in the completion set returned from
|
|
/// the analysis server. This is off by default because with syntax such as
|
|
/// "^get foo() => _foo;", "get" and "set" won't be suggested because this
|
|
/// syntax has specified the "get" modifier, i.e. it would be invalid to
|
|
/// include it again: "get get foo() => _foo;".
|
|
final bool _doExpectKeywordCompletions = false;
|
|
|
|
/// This boolean is set to enable if identifiers in comments should be
|
|
/// expected to be completed. The default is false as typos in a dartdoc
|
|
/// comment don't yield an error like Dart syntax mistakes would yield.
|
|
final bool _doExpectCommentRefs = false;
|
|
|
|
ExpectedCompletionsVisitor(this.result, {required int caretOffset})
|
|
: _caretOffset = caretOffset;
|
|
|
|
/// Return the path of the file that is being visited.
|
|
String get filePath => result.path;
|
|
|
|
/// Return the line info for the file that is being visited.
|
|
LineInfo get lineInfo => result.lineInfo;
|
|
|
|
void safelyRecordEntity(
|
|
SyntacticEntity? entity, {
|
|
protocol.CompletionSuggestionKind? kind,
|
|
protocol.ElementKind? elementKind,
|
|
}) {
|
|
// Only record if this entity is not null, has a length, etc.
|
|
if (entity != null && entity.offset >= 0 && entity.length > 0) {
|
|
var location = lineInfo.getLocation(entity.offset);
|
|
var lineNumber = location.lineNumber;
|
|
var columnNumber = location.columnNumber;
|
|
if (entity.length >= _caretOffset) {
|
|
columnNumber += _caretOffset;
|
|
}
|
|
|
|
bool isKeyword() => kind == protocol.CompletionSuggestionKind.KEYWORD;
|
|
|
|
// Some special cases in the if and if-else blocks, 'import' from the
|
|
// DAS is "import '';" which we want to be sure to match.
|
|
var lexeme = entity.toString();
|
|
if (lexeme == 'async') {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
ASYNC_STAR,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else if (lexeme == 'default') {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
DEFAULT_COLON,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else if (lexeme == 'deferred') {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
DEFERRED_AS,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else if (lexeme == 'export' && isKeyword()) {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
EXPORT_STATEMENT,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else if (lexeme == 'import' && isKeyword()) {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
IMPORT_STATEMENT,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else if (lexeme == 'part' && isKeyword()) {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
PART_STATEMENT,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else if (lexeme == 'sync') {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
SYNC_STAR,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else if (lexeme == 'yield') {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
YIELD_STAR,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
void safelyRecordKeywordCompletion(SyntacticEntity? entity) {
|
|
if (_doExpectKeywordCompletions) {
|
|
safelyRecordEntity(
|
|
entity,
|
|
kind: protocol.CompletionSuggestionKind.KEYWORD,
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitAsExpression(AsExpression node) {
|
|
safelyRecordKeywordCompletion(node.asOperator);
|
|
super.visitAsExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitAssertInitializer(AssertInitializer node) {
|
|
safelyRecordKeywordCompletion(node.assertKeyword);
|
|
super.visitAssertInitializer(node);
|
|
}
|
|
|
|
@override
|
|
void visitAssertStatement(AssertStatement node) {
|
|
safelyRecordKeywordCompletion(node.assertKeyword);
|
|
super.visitAssertStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitAwaitExpression(AwaitExpression node) {
|
|
safelyRecordKeywordCompletion(node.awaitKeyword);
|
|
super.visitAwaitExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitBlockFunctionBody(BlockFunctionBody node) {
|
|
// 'async' | 'async' '*' | 'sync' '*':
|
|
safelyRecordKeywordCompletion(node.keyword);
|
|
super.visitBlockFunctionBody(node);
|
|
}
|
|
|
|
@override
|
|
void visitBooleanLiteral(BooleanLiteral node) {
|
|
// 'false' | 'true'
|
|
safelyRecordKeywordCompletion(node.literal);
|
|
super.visitBooleanLiteral(node);
|
|
}
|
|
|
|
@override
|
|
void visitBreakStatement(BreakStatement node) {
|
|
safelyRecordKeywordCompletion(node.breakKeyword);
|
|
super.visitBreakStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitCatchClause(CatchClause node) {
|
|
safelyRecordKeywordCompletion(node.catchKeyword);
|
|
safelyRecordKeywordCompletion(node.onKeyword);
|
|
super.visitCatchClause(node);
|
|
}
|
|
|
|
@override
|
|
void visitClassDeclaration(ClassDeclaration node) {
|
|
safelyRecordKeywordCompletion(node.abstractKeyword);
|
|
safelyRecordKeywordCompletion(node.classKeyword);
|
|
super.visitClassDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitClassTypeAlias(ClassTypeAlias node) {
|
|
safelyRecordKeywordCompletion(node.abstractKeyword);
|
|
safelyRecordKeywordCompletion(node.typedefKeyword);
|
|
super.visitClassTypeAlias(node);
|
|
}
|
|
|
|
@override
|
|
void visitConfiguration(Configuration node) {
|
|
safelyRecordKeywordCompletion(node.ifKeyword);
|
|
super.visitConfiguration(node);
|
|
}
|
|
|
|
@override
|
|
void visitConstructorDeclaration(ConstructorDeclaration node) {
|
|
safelyRecordKeywordCompletion(node.externalKeyword);
|
|
safelyRecordKeywordCompletion(node.constKeyword);
|
|
safelyRecordKeywordCompletion(node.factoryKeyword);
|
|
super.visitConstructorDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
|
|
safelyRecordKeywordCompletion(node.thisKeyword);
|
|
super.visitConstructorFieldInitializer(node);
|
|
}
|
|
|
|
@override
|
|
void visitContinueStatement(ContinueStatement node) {
|
|
safelyRecordKeywordCompletion(node.continueKeyword);
|
|
super.visitContinueStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitDeclaredIdentifier(DeclaredIdentifier node) {
|
|
// 'final', 'const' or 'var'
|
|
safelyRecordKeywordCompletion(node.keyword);
|
|
super.visitDeclaredIdentifier(node);
|
|
}
|
|
|
|
@override
|
|
void visitDoStatement(DoStatement node) {
|
|
safelyRecordKeywordCompletion(node.doKeyword);
|
|
safelyRecordKeywordCompletion(node.whileKeyword);
|
|
super.visitDoStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitEnumDeclaration(EnumDeclaration node) {
|
|
safelyRecordKeywordCompletion(node.enumKeyword);
|
|
super.visitEnumDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitExportDirective(ExportDirective node) {
|
|
safelyRecordKeywordCompletion(node.exportKeyword);
|
|
super.visitExportDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
|
|
safelyRecordKeywordCompletion(node.keyword);
|
|
super.visitExpressionFunctionBody(node);
|
|
}
|
|
|
|
@override
|
|
void visitExtendsClause(ExtendsClause node) {
|
|
safelyRecordKeywordCompletion(node.extendsKeyword);
|
|
super.visitExtendsClause(node);
|
|
}
|
|
|
|
@override
|
|
void visitExtensionDeclaration(ExtensionDeclaration node) {
|
|
safelyRecordKeywordCompletion(node.extensionKeyword);
|
|
safelyRecordKeywordCompletion(node.onClause);
|
|
super.visitExtensionDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitFieldDeclaration(FieldDeclaration node) {
|
|
safelyRecordKeywordCompletion(node.abstractKeyword);
|
|
safelyRecordKeywordCompletion(node.covariantKeyword);
|
|
safelyRecordKeywordCompletion(node.externalKeyword);
|
|
safelyRecordKeywordCompletion(node.staticKeyword);
|
|
super.visitFieldDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitFieldFormalParameter(FieldFormalParameter node) {
|
|
// 'final', 'const' or 'var'
|
|
safelyRecordKeywordCompletion(node.keyword);
|
|
safelyRecordKeywordCompletion(node.thisKeyword);
|
|
super.visitFieldFormalParameter(node);
|
|
}
|
|
|
|
@override
|
|
void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
|
|
safelyRecordKeywordCompletion(node.inKeyword);
|
|
super.visitForEachPartsWithDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitForEachPartsWithIdentifier(ForEachPartsWithIdentifier node) {
|
|
safelyRecordKeywordCompletion(node.inKeyword);
|
|
super.visitForEachPartsWithIdentifier(node);
|
|
}
|
|
|
|
@override
|
|
void visitForElement(ForElement node) {
|
|
safelyRecordKeywordCompletion(node.awaitKeyword);
|
|
safelyRecordKeywordCompletion(node.forKeyword);
|
|
super.visitForElement(node);
|
|
}
|
|
|
|
@override
|
|
void visitForStatement(ForStatement node) {
|
|
safelyRecordKeywordCompletion(node.awaitKeyword);
|
|
safelyRecordKeywordCompletion(node.forKeyword);
|
|
super.visitForStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitFunctionDeclaration(FunctionDeclaration node) {
|
|
safelyRecordKeywordCompletion(node.externalKeyword);
|
|
// 'get' or 'set':
|
|
safelyRecordKeywordCompletion(node.propertyKeyword);
|
|
super.visitFunctionDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitFunctionTypeAlias(FunctionTypeAlias node) {
|
|
safelyRecordKeywordCompletion(node.typedefKeyword);
|
|
super.visitFunctionTypeAlias(node);
|
|
}
|
|
|
|
@override
|
|
void visitGenericFunctionType(GenericFunctionType node) {
|
|
safelyRecordKeywordCompletion(node.functionKeyword);
|
|
super.visitGenericFunctionType(node);
|
|
}
|
|
|
|
@override
|
|
void visitGenericTypeAlias(GenericTypeAlias node) {
|
|
safelyRecordKeywordCompletion(node.typedefKeyword);
|
|
super.visitGenericTypeAlias(node);
|
|
}
|
|
|
|
@override
|
|
void visitHideCombinator(HideCombinator node) {
|
|
safelyRecordKeywordCompletion(node.keyword);
|
|
super.visitHideCombinator(node);
|
|
}
|
|
|
|
@override
|
|
void visitIfElement(IfElement node) {
|
|
safelyRecordKeywordCompletion(node.ifKeyword);
|
|
safelyRecordKeywordCompletion(node.elseKeyword);
|
|
super.visitIfElement(node);
|
|
}
|
|
|
|
@override
|
|
void visitIfStatement(IfStatement node) {
|
|
safelyRecordKeywordCompletion(node.ifKeyword);
|
|
safelyRecordKeywordCompletion(node.elseKeyword);
|
|
super.visitIfStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitImplementsClause(ImplementsClause node) {
|
|
safelyRecordKeywordCompletion(node.implementsKeyword);
|
|
super.visitImplementsClause(node);
|
|
}
|
|
|
|
@override
|
|
void visitImportDirective(ImportDirective node) {
|
|
safelyRecordKeywordCompletion(node.importKeyword);
|
|
safelyRecordKeywordCompletion(node.asKeyword);
|
|
safelyRecordKeywordCompletion(node.deferredKeyword);
|
|
super.visitImportDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitInstanceCreationExpression(InstanceCreationExpression node) {
|
|
// Here we explicitly do not record 'new' as we don't suggest it in the
|
|
// completion service.
|
|
// https://dart-review.googlesource.com/c/sdk/+/131020
|
|
if (node.keyword?.type == Keyword.CONST) {
|
|
safelyRecordKeywordCompletion(node.keyword);
|
|
}
|
|
super.visitInstanceCreationExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitIsExpression(IsExpression node) {
|
|
safelyRecordKeywordCompletion(node.isOperator);
|
|
super.visitIsExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitLibraryDirective(LibraryDirective node) {
|
|
safelyRecordKeywordCompletion(node.libraryKeyword);
|
|
super.visitLibraryDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitListLiteral(ListLiteral node) {
|
|
safelyRecordKeywordCompletion(node.constKeyword);
|
|
super.visitListLiteral(node);
|
|
}
|
|
|
|
@override
|
|
void visitMethodDeclaration(MethodDeclaration node) {
|
|
safelyRecordKeywordCompletion(node.externalKeyword);
|
|
safelyRecordKeywordCompletion(node.modifierKeyword);
|
|
safelyRecordKeywordCompletion(node.operatorKeyword);
|
|
safelyRecordKeywordCompletion(node.propertyKeyword);
|
|
super.visitMethodDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitMixinDeclaration(MixinDeclaration node) {
|
|
safelyRecordKeywordCompletion(node.mixinKeyword);
|
|
super.visitMixinDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitMixinOnClause(MixinOnClause node) {
|
|
safelyRecordKeywordCompletion(node.onKeyword);
|
|
super.visitMixinOnClause(node);
|
|
}
|
|
|
|
@override
|
|
void visitNativeClause(NativeClause node) {
|
|
safelyRecordKeywordCompletion(node.nativeKeyword);
|
|
super.visitNativeClause(node);
|
|
}
|
|
|
|
@override
|
|
void visitNativeFunctionBody(NativeFunctionBody node) {
|
|
safelyRecordKeywordCompletion(node.nativeKeyword);
|
|
super.visitNativeFunctionBody(node);
|
|
}
|
|
|
|
@override
|
|
void visitNullLiteral(NullLiteral node) {
|
|
safelyRecordKeywordCompletion(node.literal);
|
|
super.visitNullLiteral(node);
|
|
}
|
|
|
|
@override
|
|
void visitPartDirective(PartDirective node) {
|
|
safelyRecordKeywordCompletion(node.partKeyword);
|
|
super.visitPartDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitPartOfDirective(PartOfDirective node) {
|
|
safelyRecordKeywordCompletion(node.partKeyword);
|
|
safelyRecordKeywordCompletion(node.ofKeyword);
|
|
super.visitPartOfDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitRedirectingConstructorInvocation(
|
|
RedirectingConstructorInvocation node,
|
|
) {
|
|
safelyRecordKeywordCompletion(node.thisKeyword);
|
|
super.visitRedirectingConstructorInvocation(node);
|
|
}
|
|
|
|
@override
|
|
void visitRethrowExpression(RethrowExpression node) {
|
|
safelyRecordKeywordCompletion(node.rethrowKeyword);
|
|
return super.visitRethrowExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitReturnStatement(ReturnStatement node) {
|
|
safelyRecordKeywordCompletion(node.returnKeyword);
|
|
super.visitReturnStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitSetOrMapLiteral(SetOrMapLiteral node) {
|
|
safelyRecordKeywordCompletion(node.constKeyword);
|
|
super.visitSetOrMapLiteral(node);
|
|
}
|
|
|
|
@override
|
|
void visitShowCombinator(ShowCombinator node) {
|
|
safelyRecordKeywordCompletion(node.keyword);
|
|
super.visitShowCombinator(node);
|
|
}
|
|
|
|
@override
|
|
void visitSimpleFormalParameter(SimpleFormalParameter node) {
|
|
// 'final', 'const' or 'var'
|
|
safelyRecordKeywordCompletion(node.keyword);
|
|
safelyRecordKeywordCompletion(node.covariantKeyword);
|
|
super.visitSimpleFormalParameter(node);
|
|
}
|
|
|
|
@override
|
|
void visitSimpleIdentifier(SimpleIdentifier node) {
|
|
if (_doIncludeSimpleIdentifier(node)) {
|
|
protocol.ElementKind? elementKind;
|
|
var kind = node.element?.kind;
|
|
if (kind != null) {
|
|
elementKind = protocol.convertElementKind(kind);
|
|
|
|
// If the completed element kind is a getter or setter set the element
|
|
// kind to null as the exact kind from the DAS is unknown at this
|
|
// point.
|
|
if (elementKind == protocol.ElementKind.GETTER ||
|
|
elementKind == protocol.ElementKind.SETTER) {
|
|
elementKind = null;
|
|
}
|
|
|
|
// Map PREFIX element kinds to LIBRARY kinds as this is the ElementKind
|
|
// used for prefixes reported from the completion engine.
|
|
if (elementKind == protocol.ElementKind.PREFIX) {
|
|
elementKind = protocol.ElementKind.LIBRARY;
|
|
}
|
|
|
|
// For identifiers in a FieldFormalParameter, i.e. the 'foo' in some
|
|
// ClassName(this.foo), set the elementKind to FIELD which is what the
|
|
// completion engine does (elementKind before this if statement is a
|
|
// PARAMETER).
|
|
if (node.parent is FieldFormalParameter) {
|
|
elementKind = protocol.ElementKind.FIELD;
|
|
}
|
|
|
|
// Class references that are constructor calls are constructor kinds,
|
|
// unless either:
|
|
// 1) the constructor is a named constructor, i.e. some "Foo.bar()",
|
|
// the "Foo" in this case is a class,
|
|
// 2) or, there is an explicit const or new keyword before the
|
|
// constructor invocation in which case the "Foo" above is
|
|
// considered a constructor still
|
|
if (elementKind == protocol.ElementKind.CLASS) {
|
|
var constructorName = node.parent?.parent;
|
|
if (constructorName is ConstructorName) {
|
|
// TODO(scheglov): Commented out, probably does not work now.
|
|
// var instanceCreationExpression = constructorName.parent;
|
|
// if (instanceCreationExpression is InstanceCreationExpression &&
|
|
// constructorName.type.name == node) {
|
|
// if (instanceCreationExpression.keyword != null ||
|
|
// constructorName.name == null) {
|
|
// elementKind = protocol.ElementKind.CONSTRUCTOR;
|
|
// }
|
|
// }
|
|
}
|
|
}
|
|
|
|
// A class reference followed by parens in an annotation is a
|
|
// constructor reference.
|
|
if (elementKind == protocol.ElementKind.CLASS &&
|
|
node.thisOrAncestorOfType<Annotation>() != null &&
|
|
node.endToken.next?.type == TokenType.OPEN_PAREN) {
|
|
elementKind = protocol.ElementKind.CONSTRUCTOR;
|
|
}
|
|
}
|
|
safelyRecordEntity(node, elementKind: elementKind);
|
|
}
|
|
super.visitSimpleIdentifier(node);
|
|
}
|
|
|
|
@override
|
|
void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
|
|
safelyRecordKeywordCompletion(node.superKeyword);
|
|
super.visitSuperConstructorInvocation(node);
|
|
}
|
|
|
|
@override
|
|
void visitSuperExpression(SuperExpression node) {
|
|
safelyRecordKeywordCompletion(node.superKeyword);
|
|
super.visitSuperExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitSwitchCase(SwitchCase node) {
|
|
safelyRecordKeywordCompletion(node.keyword);
|
|
super.visitSwitchCase(node);
|
|
}
|
|
|
|
@override
|
|
void visitSwitchDefault(SwitchDefault node) {
|
|
safelyRecordKeywordCompletion(node.keyword);
|
|
super.visitSwitchDefault(node);
|
|
}
|
|
|
|
@override
|
|
void visitSwitchStatement(SwitchStatement node) {
|
|
safelyRecordKeywordCompletion(node.switchKeyword);
|
|
super.visitSwitchStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitThisExpression(ThisExpression node) {
|
|
safelyRecordKeywordCompletion(node.thisKeyword);
|
|
super.visitThisExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitThrowExpression(ThrowExpression node) {
|
|
safelyRecordKeywordCompletion(node.throwKeyword);
|
|
super.visitThrowExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
|
|
safelyRecordKeywordCompletion(node.externalKeyword);
|
|
super.visitTopLevelVariableDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitTryStatement(TryStatement node) {
|
|
safelyRecordKeywordCompletion(node.tryKeyword);
|
|
safelyRecordKeywordCompletion(node.finallyKeyword);
|
|
super.visitTryStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitTypeParameter(TypeParameter node) {
|
|
safelyRecordKeywordCompletion(node.extendsKeyword);
|
|
super.visitTypeParameter(node);
|
|
}
|
|
|
|
@override
|
|
void visitVariableDeclarationList(VariableDeclarationList node) {
|
|
// 'final', 'const' or 'var'
|
|
safelyRecordKeywordCompletion(node.keyword);
|
|
safelyRecordKeywordCompletion(node.lateKeyword);
|
|
super.visitVariableDeclarationList(node);
|
|
}
|
|
|
|
@override
|
|
void visitWhileStatement(WhileStatement node) {
|
|
safelyRecordKeywordCompletion(node.whileKeyword);
|
|
super.visitWhileStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitWithClause(WithClause node) {
|
|
safelyRecordKeywordCompletion(node.withKeyword);
|
|
super.visitWithClause(node);
|
|
}
|
|
|
|
@override
|
|
void visitYieldStatement(YieldStatement node) {
|
|
safelyRecordKeywordCompletion(node.yieldKeyword);
|
|
super.visitYieldStatement(node);
|
|
}
|
|
|
|
bool _doIncludeSimpleIdentifier(SimpleIdentifier node) {
|
|
// Do not continue if this node is synthetic, or if the node is in a
|
|
// declaration context
|
|
if (node.isSynthetic || node.inDeclarationContext()) {
|
|
return false;
|
|
}
|
|
|
|
// If the type of the SimpleIdentifier is dynamic, don't include.
|
|
var staticType = node.staticType;
|
|
if (staticType != null && staticType is DynamicType) {
|
|
return false;
|
|
}
|
|
|
|
// If we are in a comment reference context, return if we should include
|
|
// such identifiers.
|
|
if (node.thisOrAncestorOfType<CommentReference>() != null) {
|
|
return _doExpectCommentRefs;
|
|
}
|
|
|
|
// Ignore the SimpleIdentifiers that make up library directives.
|
|
if (node.thisOrAncestorOfType<LibraryDirective>() != null) {
|
|
return false;
|
|
}
|
|
// Ignore identifiers in hide and show combinators because we don't suggest
|
|
// names that are already in the list.
|
|
if (node.parent is HideCombinator || node.parent is ShowCombinator) {
|
|
return false;
|
|
}
|
|
|
|
// TODO(jwren): If there is a mode of completing at a token location where
|
|
// the token is removed before the completion query happens, then this
|
|
// should be disabled in such a case:
|
|
// Named arguments, i.e. the 'foo' in 'method_call(foo: 1)' should not be
|
|
// included, by design, the completion engine won't suggest named arguments
|
|
// already it the source.
|
|
//
|
|
// The null check, node.staticElement == null, handles the cases where the
|
|
// invocation is unknown, i.e. the 'arg' in 'foo.bar(arg: 1)', where foo is
|
|
// dynamic.
|
|
if ((node.element == null ||
|
|
node.element?.kind == element.ElementKind.PARAMETER) &&
|
|
node.parent is Label &&
|
|
node.thisOrAncestorOfType<ArgumentList>() != null) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
class _SyntacticEntity implements SyntacticEntity {
|
|
@override
|
|
final int offset;
|
|
|
|
_SyntacticEntity(this.offset);
|
|
|
|
@override
|
|
int get end => offset + length;
|
|
|
|
@override
|
|
int get length => 0;
|
|
}
|