blob: 581dd2a26ee22600f1e63e5afc524a0644661e47 [file] [log] [blame]
// 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';
extension AstNodeExtension on AstNode {
/// Return the minimal cover node for the range of characters beginning at the
/// [offset] with the given [length], or `null` if the range is outside the
/// range covered by the receiver.
///
/// The minimal covering node is the node, rooted at the receiver, with the
/// shortest length whose range completely includes the given range.
AstNode? nodeCovering({required int offset, int length = 0}) {
var end = offset + length;
/// Return `true` if the [node] contains the range.
bool containsOffset(AstNode node) {
if (length == 0) {
// The selection range is an insertion point. It is considered to be
// outside the node if it's
// - both immediately before the first token in the node and immediately
// after the previous token, or
// - both immediately after the last token in the node and immediately
// before the next token.
if (offset == node.offset && offset == node.beginToken.previous!.end) {
return false;
}
if (offset == node.end && offset == node.endToken.next!.offset) {
return false;
}
}
return node.offset <= offset && node.end >= end;
}
/// Return the child of the [node] that completely contains the [range], or
/// `null` if none of the children contain the range (which means that the
/// [node] is the covering node).
AstNode? childContainingRange(AstNode node) {
for (var entity in node.childEntities) {
if (entity is AstNode && containsOffset(entity)) {
return entity;
}
}
return null;
}
if (this is CompilationUnit) {
if (offset < 0 || end >= this.end) {
return null;
}
} else if (!containsOffset(this)) {
return null;
}
var previousNode = this;
var currentNode = childContainingRange(previousNode);
while (currentNode != null) {
previousNode = currentNode;
currentNode = childContainingRange(previousNode);
}
return previousNode;
}
}