8600df906e
This will allow diagnostic reporting code in the analyzer to be more easily converted to the new literate diagnostic reporting API, because it will make it possible to use `SourceRange` as the common denominator for situations where the location where a diagnostic needs to be reported might come from either a `SyntacticEntity` or a `SourceRange`. Change-Id: I6a6a6964f3ec2ea33e8900d93b5abe714c9e1dcc Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/467682 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
894 lines
27 KiB
Dart
894 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 _asyncStar = 'async*';
|
|
|
|
static const _defaultColon = 'default:';
|
|
|
|
static const _deferredAs = 'deferred as';
|
|
|
|
static const _exportStatement = "export '';";
|
|
|
|
static const _importStatement = "import '';";
|
|
|
|
static const _partStatement = "part '';";
|
|
static const _syncStar = 'sync*';
|
|
static const _yieldStar = '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,
|
|
_asyncStar,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else if (lexeme == 'default') {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
_defaultColon,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else if (lexeme == 'deferred') {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
_deferredAs,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else if (lexeme == 'export' && isKeyword()) {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
_exportStatement,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else if (lexeme == 'import' && isKeyword()) {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
_importStatement,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else if (lexeme == 'part' && isKeyword()) {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
_partStatement,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else if (lexeme == 'sync') {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
_syncStar,
|
|
kind,
|
|
elementKind,
|
|
),
|
|
);
|
|
} else if (lexeme == 'yield') {
|
|
expectedCompletions.add(
|
|
ExpectedCompletion.specialCompletionString(
|
|
filePath,
|
|
entity,
|
|
lineNumber,
|
|
columnNumber,
|
|
_yieldStar,
|
|
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 extends SyntacticEntity {
|
|
@override
|
|
final int offset;
|
|
|
|
_SyntacticEntity(this.offset);
|
|
|
|
@override
|
|
int get end => offset + length;
|
|
|
|
@override
|
|
int get length => 0;
|
|
}
|