| // 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; | |
| new( | |
| this._filePath, | |
| this._entity, | |
| this._lineNumber, | |
| this._columnNumber, | |
| this._kind, | |
| this._elementKind, | |
| ) : _completionString = null; | |
| /// Return an instance extracted from the decoded JSON [map]. | |
| factory 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, | |
| ); | |
| } | |
| new 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; | |
| new(this.result, {required this._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.constFinalOrVarKeyword); | |
| 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 visitRegularFormalParameter(RegularFormalParameter node) { | |
| // 'final', 'const' or 'var' | |
| safelyRecordKeywordCompletion(node.constFinalOrVarKeyword); | |
| safelyRecordKeywordCompletion(node.covariantKeyword); | |
| super.visitRegularFormalParameter(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 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; | |
| new(this.offset); | |
| @override | |
| int get end => offset + length; | |
| @override | |
| int get length => 0; | |
| } |