| // Copyright (c) 2022, 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:analyzer/dart/ast/ast.dart'; | |
| import 'package:analyzer/dart/ast/token.dart'; | |
| import 'package:analyzer/dart/ast/visitor.dart'; | |
| import 'package:analyzer/source/source_range.dart'; | |
| /// A selection within a compilation unit. | |
| class Selection { | |
| /// The offset of the selection. | |
| final int offset; | |
| /// The length of the selection. | |
| final int length; | |
| /// The most deeply nested node whose range completely includes the selected | |
| /// range of characters. | |
| final AstNode coveringNode; | |
| /// Initialize a newly created selection to include the characters starting at | |
| /// the [offset] and including [length] characters, all of which fall within | |
| /// the [coveringNode]. | |
| Selection({ | |
| required this.offset, | |
| required this.length, | |
| required this.coveringNode, | |
| }); | |
| bool isCoveredByNode(AstNode node) { | |
| return node.offset <= offset && offset + length <= node.end; | |
| } | |
| bool isCoveredByToken(Token token) { | |
| return token.offset <= offset && offset + length <= token.end; | |
| } | |
| /// Returns the contiguous subset of [coveringNode] children that are at | |
| /// least partially covered by the selection. Touching is not enough. | |
| /// | |
| /// A list of nodes is defined to be _contiguous_ if they are syntactically | |
| /// adjacent with no intervening tokens other than comments or commas. For | |
| /// example, the nodes might be a sequence of members in a compilation unit, a | |
| /// sequence of statements in a block, or a sequence of parameters in a | |
| /// parameter list that don't cross a separator such as `{` or `[`. | |
| List<AstNode> nodesInRange() { | |
| var rangeFinder = _ChildrenFinder(SourceRange(offset, length)); | |
| coveringNode.accept(rangeFinder); | |
| return rangeFinder.nodes; | |
| } | |
| } | |
| /// A visitor used to find a sequence of nodes within the node being visited | |
| /// that together cover the range. | |
| class _ChildrenFinder extends SimpleAstVisitor<void> { | |
| /// The range that the sequence of nodes must cover. | |
| final SourceRange range; | |
| /// The nodes within the range. | |
| List<AstNode> nodes = []; | |
| /// Initialize a newly created visitor. | |
| _ChildrenFinder(this.range); | |
| @override | |
| void visitAdjacentStrings(AdjacentStrings node) { | |
| _fromList(node.strings); | |
| } | |
| @override | |
| void visitArgumentList(ArgumentList node) { | |
| _fromList(node.arguments); | |
| } | |
| @override | |
| void visitBlock(Block node) { | |
| _fromList(node.statements); | |
| } | |
| @override | |
| void visitBlockClassBody(BlockClassBody node) { | |
| _fromList(node.members); | |
| } | |
| @override | |
| void visitBlockEnumBody(BlockEnumBody node) { | |
| _fromList(node.constants) || _fromList(node.members); | |
| } | |
| @override | |
| void visitCascadeExpression(CascadeExpression node) { | |
| _fromList(node.cascadeSections); | |
| } | |
| @override | |
| void visitClassDeclaration(ClassDeclaration node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitClassTypeAlias(ClassTypeAlias node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitCompilationUnit(CompilationUnit node) { | |
| // TODO(brianwilkerson): Support selecting both directives and declarations. | |
| _fromList(node.directives) || _fromList(node.declarations); | |
| } | |
| @override | |
| void visitConstructorDeclaration(ConstructorDeclaration node) { | |
| _fromList(node.metadata) || _fromList(node.initializers); | |
| } | |
| @override | |
| void visitDeclaredIdentifier(DeclaredIdentifier node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitEnumConstantDeclaration(EnumConstantDeclaration node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitEnumDeclaration(EnumDeclaration node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitExportDirective(ExportDirective node) { | |
| _fromList(node.metadata) || | |
| _fromList(node.configurations) || | |
| _fromList(node.combinators); | |
| } | |
| @override | |
| void visitExtensionDeclaration(ExtensionDeclaration node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitFieldDeclaration(FieldDeclaration node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitFieldFormalParameter(FieldFormalParameter node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitForEachPartsWithPattern(ForEachPartsWithPattern node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitFormalParameterList(FormalParameterList node) { | |
| var delimiter = node.leftDelimiter; | |
| if (delimiter == null || !range.contains(delimiter.offset)) { | |
| _fromList(node.parameters); | |
| } | |
| } | |
| @override | |
| void visitForPartsWithDeclarations(ForPartsWithDeclarations node) { | |
| _fromList(node.updaters); | |
| } | |
| @override | |
| void visitForPartsWithExpression(ForPartsWithExpression node) { | |
| _fromList(node.updaters); | |
| } | |
| @override | |
| void visitForPartsWithPattern(ForPartsWithPattern node) { | |
| _fromList(node.updaters); | |
| } | |
| @override | |
| void visitFunctionDeclaration(FunctionDeclaration node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitFunctionTypeAlias(FunctionTypeAlias node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitGenericTypeAlias(GenericTypeAlias node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitHideCombinator(HideCombinator node) { | |
| _fromList(node.hiddenNames); | |
| } | |
| @override | |
| void visitImplementsClause(ImplementsClause node) { | |
| _fromList(node.interfaces); | |
| } | |
| @override | |
| void visitImportDirective(ImportDirective node) { | |
| _fromList(node.metadata) || | |
| _fromList(node.configurations) || | |
| _fromList(node.combinators); | |
| } | |
| @override | |
| void visitLabeledStatement(LabeledStatement node) { | |
| _fromList(node.labels); | |
| } | |
| @override | |
| void visitLibraryDirective(LibraryDirective node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitListLiteral(ListLiteral node) { | |
| _fromList(node.elements); | |
| } | |
| @override | |
| void visitListPattern(ListPattern node) { | |
| _fromList(node.elements); | |
| } | |
| @override | |
| void visitMapPattern(MapPattern node) { | |
| _fromList(node.elements); | |
| } | |
| @override | |
| void visitMethodDeclaration(MethodDeclaration node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitMixinDeclaration(MixinDeclaration node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitMixinOnClause(MixinOnClause node) { | |
| _fromList(node.superclassConstraints); | |
| } | |
| @override | |
| void visitObjectPattern(ObjectPattern node) { | |
| _fromList(node.fields); | |
| } | |
| @override | |
| void visitPartDirective(PartDirective node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitPartOfDirective(PartOfDirective node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitPatternVariableDeclaration(PatternVariableDeclaration node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitRecordLiteral(RecordLiteral node) { | |
| _fromList(node.fields); | |
| } | |
| @override | |
| void visitRecordPattern(RecordPattern node) { | |
| _fromList(node.fields); | |
| } | |
| @override | |
| void visitRecordTypeAnnotation(RecordTypeAnnotation node) { | |
| _fromList(node.positionalFields); | |
| } | |
| @override | |
| void visitRecordTypeAnnotationNamedField( | |
| RecordTypeAnnotationNamedField node, | |
| ) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitRecordTypeAnnotationNamedFields( | |
| RecordTypeAnnotationNamedFields node, | |
| ) { | |
| _fromList(node.fields); | |
| } | |
| @override | |
| void visitRecordTypeAnnotationPositionalField( | |
| RecordTypeAnnotationPositionalField node, | |
| ) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitSetOrMapLiteral(SetOrMapLiteral node) { | |
| _fromList(node.elements); | |
| } | |
| @override | |
| void visitShowCombinator(ShowCombinator node) { | |
| _fromList(node.shownNames); | |
| } | |
| @override | |
| void visitRegularFormalParameter(RegularFormalParameter node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitStringInterpolation(StringInterpolation node) { | |
| _fromList(node.elements); | |
| } | |
| @override | |
| void visitSuperFormalParameter(SuperFormalParameter node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitSwitchCase(SwitchCase node) { | |
| _fromList(node.labels) || _fromList(node.statements); | |
| } | |
| @override | |
| void visitSwitchDefault(SwitchDefault node) { | |
| _fromList(node.labels) || _fromList(node.statements); | |
| } | |
| @override | |
| void visitSwitchExpression(SwitchExpression node) { | |
| _fromList(node.cases); | |
| } | |
| @override | |
| void visitSwitchPatternCase(SwitchPatternCase node) { | |
| _fromList(node.labels) || _fromList(node.statements); | |
| } | |
| @override | |
| void visitSwitchStatement(SwitchStatement node) { | |
| _fromList(node.members); | |
| } | |
| @override | |
| void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitTryStatement(TryStatement node) { | |
| _fromList(node.catchClauses); | |
| } | |
| @override | |
| void visitTypeArgumentList(TypeArgumentList node) { | |
| _fromList(node.arguments); | |
| } | |
| @override | |
| void visitTypeParameter(TypeParameter node) { | |
| _fromList(node.metadata); | |
| } | |
| @override | |
| void visitTypeParameterList(TypeParameterList node) { | |
| _fromList(node.typeParameters); | |
| } | |
| @override | |
| void visitVariableDeclarationList(VariableDeclarationList node) { | |
| _fromList(node.metadata) || _fromList(node.variables); | |
| } | |
| @override | |
| void visitWithClause(WithClause node) { | |
| _fromList(node.mixinTypes); | |
| } | |
| /// If one or more of the [elements] in the list can cover the [range], then | |
| /// add the elements to the list of [nodes] and return `true`. | |
| bool _fromList(List<AstNode> elements) { | |
| var first = elements.length; | |
| for (; first > 0; first--) { | |
| var element = elements[first - 1]; | |
| if (element.end <= range.offset) { | |
| break; | |
| } | |
| } | |
| var last = first; | |
| for (; last < elements.length; last++) { | |
| var element = elements[last]; | |
| if (element.offset >= range.end) { | |
| break; | |
| } | |
| nodes.add(element); | |
| } | |
| return nodes.isNotEmpty; | |
| } | |
| } | |
| extension CompilationUnitExtension on CompilationUnit { | |
| /// Return the selection that includes the characters starting at the [offset] | |
| /// with the given [length]. | |
| Selection? select({required int offset, required int length}) { | |
| var coveringNode = nodeCovering(offset: offset, length: length); | |
| if (coveringNode == null) { | |
| return null; | |
| } | |
| return Selection( | |
| offset: offset, | |
| length: length, | |
| coveringNode: coveringNode, | |
| ); | |
| } | |
| } |