| // 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 visitCascadeExpression(CascadeExpression node) { |
| _fromList(node.cascadeSections); |
| } |
| |
| @override |
| void visitClassDeclaration(ClassDeclaration node) { |
| _fromList(node.metadata) || _fromList(node.members); |
| } |
| |
| @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 visitDefaultFormalParameter(DefaultFormalParameter node) { |
| _fromList(node.metadata); |
| } |
| |
| @override |
| void visitDottedName(DottedName node) { |
| _fromList(node.components); |
| } |
| |
| @override |
| void visitEnumConstantDeclaration(EnumConstantDeclaration node) { |
| _fromList(node.metadata); |
| } |
| |
| @override |
| void visitEnumDeclaration(EnumDeclaration node) { |
| _fromList(node.metadata) || |
| _fromList(node.constants) || |
| _fromList(node.members); |
| } |
| |
| @override |
| void visitExportDirective(ExportDirective node) { |
| _fromList(node.metadata) || |
| _fromList(node.configurations) || |
| _fromList(node.combinators); |
| } |
| |
| @override |
| void visitExtensionDeclaration(ExtensionDeclaration node) { |
| _fromList(node.metadata) || _fromList(node.members); |
| } |
| |
| @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 visitFunctionTypedFormalParameter(FunctionTypedFormalParameter 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 visitLibraryIdentifier(LibraryIdentifier node) { |
| _fromList(node.components); |
| } |
| |
| @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) || _fromList(node.members); |
| } |
| |
| @override |
| void visitMixinOnClause(MixinOnClause node) { |
| _fromList(node.superclassConstraints); |
| } |
| |
| @override |
| void visitObjectPattern(ObjectPattern node) { |
| _fromList(node.fields); |
| } |
| |
| @override |
| void visitPartDirective(PartDirective node) { |
| _fromList(node.metadata) || _fromList(node.configurations); |
| } |
| |
| @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 visitSimpleFormalParameter(SimpleFormalParameter 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); |
| } |
| } |