blob: dca7a96b4b6e27dd43ffcfcc150ea9e45b4e0925 [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';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/utilities/extensions/object.dart';
extension AstNodeExtension on AstNode {
/// Returns all tokens, from [beginToken] to [endToken] including.
List<Token> get allTokens {
var result = <Token>[];
var token = beginToken;
while (true) {
result.add(token);
if (token == endToken) {
break;
}
if (token.next case var next?) {
token = next;
} else {
break;
}
}
return result;
}
/// The [FunctionExpression] that encloses this node directly or `null` if
/// there is another enclosing executable element.
FunctionExpression? get enclosingClosure {
for (var node in withAncestors) {
switch (node) {
case FunctionExpression(:var parent)
when parent is! FunctionDeclaration:
return node;
case FunctionDeclaration() ||
ConstructorDeclaration() ||
MethodDeclaration():
break;
}
}
return null;
}
/// The [ExecutableElement] of the enclosing executable [AstNode].
ExecutableElement? get enclosingExecutableElement {
for (var node in withAncestors) {
if (node is FunctionDeclaration) {
return node.declaredFragment?.element;
}
if (node is ConstructorDeclaration) {
return node.declaredFragment?.element;
}
if (node is MethodDeclaration) {
return node.declaredFragment?.element;
}
}
return null;
}
/// The [InstanceElement] of the enclosing executable [AstNode].
InstanceElement? get enclosingInstanceElement {
for (var node in withAncestors) {
var element = switch (node) {
ClassDeclaration(:var declaredFragment?) => declaredFragment.element,
EnumDeclaration(:var declaredFragment?) => declaredFragment.element,
ExtensionDeclaration(:var declaredFragment?) =>
declaredFragment.element,
ExtensionTypeDeclaration(:var declaredFragment?) =>
declaredFragment.element,
MixinDeclaration(:var declaredFragment?) => declaredFragment.element,
_ => null,
};
if (element != null) {
return element;
}
}
return null;
}
InterfaceElement? get enclosingInterfaceElement =>
enclosingInstanceElement.ifTypeOrNull();
AstNode? get enclosingUnitChild {
for (var node in withAncestors) {
if (node.parent is CompilationUnit) {
return node;
}
}
return null;
}
/// This node and all of its ancestors.
Iterable<AstNode> get withAncestors sync* {
AstNode? current = this;
while (current != null) {
yield current;
current = current.parent;
}
}
/// Returns the comment token that covers the [offset].
Token? commentTokenCovering(int offset) {
for (var token in allTokens) {
for (
Token? comment = token.precedingComments;
comment is Token;
comment = comment.next
) {
if (comment.offset <= offset && offset <= comment.end) {
return comment;
}
}
}
return null;
}
}
extension AstNodeNullableExtension on AstNode? {
List<ClassMember> get classMembers {
var self = this;
return switch (self) {
ClassDeclaration() => self.members,
EnumDeclaration() => self.members,
ExtensionDeclaration() => self.members,
ExtensionTypeDeclaration() => self.members,
MixinDeclaration() => self.members,
_ => throw UnimplementedError('(${self.runtimeType}) $self'),
};
}
}
extension CompilationUnitExtension on CompilationUnit {
/// Whether this [CompilationUnit] is found in a "test" directory.
bool get inTestDir {
var declaredFragment = this.declaredFragment;
if (declaredFragment == null) return false;
var pathContext =
declaredFragment.element.session.resourceProvider.pathContext;
var path = declaredFragment.source.fullName;
return switch (pathContext.separator) {
'/' => const [
'/test/',
'/integration_test/',
'/test_driver/',
'/testing/',
].any(path.contains),
r'\' => const [
r'\test\',
r'\integration_test\',
r'\test_driver\',
r'\testing\',
].any(path.contains),
_ => false,
};
}
}
extension ExtensionElementExtension on ExtensionElement {
InterfaceElement? get extendedInterfaceElement =>
extendedType.ifTypeOrNull<InterfaceType>()?.element;
}
extension FieldDeclarationExtension on FieldDeclaration {
Element get firstVariableElement =>
fields.variables.first.declaredFragment!.element;
}
extension TopLevelVariableDeclarationExtension on TopLevelVariableDeclaration {
Element get firstVariableElement =>
variables.variables.first.declaredFragment!.element;
}
extension VariableDeclarationExtension on VariableDeclaration {
FieldElementImpl get declaredFieldElement {
return declaredFragment!.element as FieldElementImpl;
}
TopLevelVariableElementImpl get declaredTopLevelVariableElement {
return declaredFragment!.element as TopLevelVariableElementImpl;
}
}