| // 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/keyword_contributor.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; |
| |
| 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(protocol.CompletionSuggestion completionSuggestion) { |
| if (completionSuggestion.completion == completion) { |
| if (kind != null && completionSuggestion.kind != kind) { |
| return false; |
| } |
| if (elementKind != null && |
| completionSuggestion.element?.kind != null && |
| completionSuggestion.element?.kind != elementKind) { |
| return false; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /// 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> { |
| final List<ExpectedCompletion> expectedCompletions; |
| |
| final String filePath; |
| |
| late CompilationUnit _enclosingCompilationUnit; |
| |
| /// 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.filePath) |
| : expectedCompletions = <ExpectedCompletion>[]; |
| |
| 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) { |
| // Compute the line number at this offset |
| var lineNumber = _enclosingCompilationUnit.lineInfo! |
| .getLocation(entity.offset) |
| .lineNumber; |
| |
| var columnNumber = _enclosingCompilationUnit.lineInfo! |
| .getLocation(entity.offset) |
| .columnNumber; |
| |
| // Some special cases in the if and if-else blocks, 'import' from the |
| // DAS is "import '';" which we want to be sure to match. |
| if (entity.toString() == 'async') { |
| expectedCompletions.add(ExpectedCompletion.specialCompletionString( |
| filePath, |
| entity, |
| lineNumber, |
| columnNumber, |
| ASYNC_STAR, |
| kind, |
| elementKind)); |
| } else if (entity.toString() == 'default') { |
| expectedCompletions.add(ExpectedCompletion.specialCompletionString( |
| filePath, |
| entity, |
| lineNumber, |
| columnNumber, |
| DEFAULT_COLON, |
| kind, |
| elementKind)); |
| } else if (entity.toString() == 'deferred') { |
| expectedCompletions.add(ExpectedCompletion.specialCompletionString( |
| filePath, |
| entity, |
| lineNumber, |
| columnNumber, |
| DEFERRED_AS, |
| kind, |
| elementKind)); |
| } else if (entity.toString() == 'export') { |
| expectedCompletions.add(ExpectedCompletion.specialCompletionString( |
| filePath, |
| entity, |
| lineNumber, |
| columnNumber, |
| EXPORT_STATEMENT, |
| kind, |
| elementKind)); |
| } else if (entity.toString() == 'import') { |
| expectedCompletions.add(ExpectedCompletion.specialCompletionString( |
| filePath, |
| entity, |
| lineNumber, |
| columnNumber, |
| IMPORT_STATEMENT, |
| kind, |
| elementKind)); |
| } else if (entity.toString() == 'part') { |
| expectedCompletions.add(ExpectedCompletion.specialCompletionString( |
| filePath, |
| entity, |
| lineNumber, |
| columnNumber, |
| PART_STATEMENT, |
| kind, |
| elementKind)); |
| } else if (entity.toString() == 'sync') { |
| expectedCompletions.add(ExpectedCompletion.specialCompletionString( |
| filePath, |
| entity, |
| lineNumber, |
| columnNumber, |
| SYNC_STAR, |
| kind, |
| elementKind)); |
| } else if (entity.toString() == '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 visitCompilationUnit(CompilationUnit node) { |
| _enclosingCompilationUnit = node; |
| super.visitCompilationUnit(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.keyword); |
| 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.onKeyword); |
| 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.keyword); |
| 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 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 visitOnClause(OnClause node) { |
| safelyRecordKeywordCompletion(node.onKeyword); |
| super.visitOnClause(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.staticElement?.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) { |
| var instanceCreationExpression = constructorName.parent; |
| if (instanceCreationExpression is InstanceCreationExpression && |
| constructorName.type.name == node) { |
| if (instanceCreationExpression.keyword != null || |
| constructorName.name == null) { |
| 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.isDynamic) { |
| 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; |
| } |
| |
| // 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.staticElement == null || |
| node.staticElement?.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; |
| } |