blob: d5fd2ab58f2c9e64703e195f7d74bf03a21c4e6c [file] [log] [blame]
// Copyright (c) 2014, 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 'dart:math';
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
import 'package:analyzer/dart/ast/precedence.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/utilities/extensions/ast.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
import 'package:path/path.dart' as path;
/// Climbs up [PrefixedIdentifier] and [PropertyAccess] nodes that include
/// [node].
Expression climbPropertyAccess(Expression node) {
while (true) {
var parent = node.parent;
if (parent is PrefixedIdentifier && parent.identifier == node) {
node = parent;
continue;
}
if (parent is PropertyAccess && parent.propertyName == node) {
node = parent;
continue;
}
return node;
}
}
/// Return references to the [element] inside the [root] node.
List<SimpleIdentifier> findLocalElementReferences(
AstNode root, LocalElement element) {
var collector = _ElementReferenceCollector(element);
root.accept(collector);
return collector.references;
}
/// Return references to the [element] inside the [root] node.
List<SimpleIdentifier> findPrefixElementReferences(
AstNode root, PrefixElement element) {
var collector = _ElementReferenceCollector(element);
root.accept(collector);
return collector.references;
}
// TODO(scheglov): replace with nodes once there will be
// [CompilationUnit.getComments].
/// Returns [SourceRange]s of all comments in [unit].
List<SourceRange> getCommentRanges(CompilationUnit unit) {
var ranges = <SourceRange>[];
var token = unit.beginToken;
while (!token.isEof) {
var commentToken = token.precedingComments;
while (commentToken != null) {
ranges.add(range.token(commentToken));
commentToken = commentToken.next as CommentToken?;
}
token = token.next!;
}
return ranges;
}
/// Return all [LocalElement]s defined in the given [node].
List<LocalElement> getDefinedLocalElements(AstNode node) {
var collector = _LocalElementsCollector();
node.accept(collector);
return collector.elements;
}
/// Return the name of the [Element] kind.
String getElementKindName(Element element) {
return element.kind.displayName;
}
/// Returns the name to display in the UI for the given [Element].
String getElementQualifiedName(Element element) {
var kind = element.kind;
if (kind == ElementKind.FIELD || kind == ElementKind.METHOD) {
return '${element.enclosingElement!.displayName}.${element.displayName}';
} else if (kind == ElementKind.LIBRARY) {
// Libraries may not have names, so use a path relative to the context root.
var session = element.session!;
var pathContext = session.resourceProvider.pathContext;
var rootPath = session.analysisContext.contextRoot.root.path;
var library = element as LibraryElement;
return pathContext.relative(library.source.fullName, from: rootPath);
} else {
return element.displayName;
}
}
/// Returns a class or an unit member enclosing the given [input].
AstNode? getEnclosingClassOrUnitMember(AstNode input) {
var member = input;
for (var node in input.withParents) {
switch (node) {
case ClassDeclaration _:
case CompilationUnit _:
case EnumDeclaration _:
case ExtensionDeclaration _:
case ExtensionTypeDeclaration _:
case MixinDeclaration _:
return member;
}
member = node;
}
return null;
}
/// Return the enclosing executable [AstNode].
AstNode? getEnclosingExecutableNode(AstNode input) {
for (var node in input.withParents) {
if (node is FunctionDeclaration) {
return node;
}
if (node is ConstructorDeclaration) {
return node;
}
if (node is MethodDeclaration) {
return node;
}
}
return null;
}
/// Returns [getExpressionPrecedence] for the parent of [node], or
/// ASSIGNMENT_PRECEDENCE if the parent node is a [ParenthesizedExpression].
///
/// The reason is that `(expr)` is always executed after `expr`.
Precedence getExpressionParentPrecedence(AstNode node) {
var parent = node.parent!;
if (parent is ParenthesizedExpression) {
return Precedence.assignment;
} else if (parent is IndexExpression && parent.index == node) {
return Precedence.assignment;
} else if (parent is AssignmentExpression &&
node == parent.rightHandSide &&
parent.parent is CascadeExpression) {
// This is a hack to allow nesting of cascade expressions within other
// cascade expressions. The problem is that if the precedence of two
// expressions are equal it sometimes means that we don't need parentheses
// (such as replacing the `b` in `a + b` with `c + d`) and sometimes do
// (such as replacing the `v` in `..f = v` with `a..b`).
return Precedence.conditional;
}
return getExpressionPrecedence(parent);
}
/// Returns the precedence of [node] it is an [Expression], NO_PRECEDENCE
/// otherwise.
Precedence getExpressionPrecedence(AstNode node) {
if (node is Expression) {
return node.precedence;
}
return Precedence.none;
}
/// Returns the namespace of the given [LibraryImportElement].
Map<String, Element> getImportNamespace(LibraryImportElement imp) {
return imp.namespace.definedNames;
}
/// Computes the best URI to import [what] into [from].
String getLibrarySourceUri(
path.Context pathContext, LibraryElement from, Uri what) {
if (what.isScheme('file')) {
var fromFolder = pathContext.dirname(from.source.fullName);
var relativeFile = pathContext.relative(what.path, from: fromFolder);
return pathContext.split(relativeFile).join('/');
}
return what.toString();
}
/// Return the [LocalVariableElement] if given [node] is a reference to a local
/// variable, or `null` in the other case.
LocalVariableElement? getLocalVariableElement(SimpleIdentifier node) {
var element = node.staticElement;
if (element is LocalVariableElement) {
return element;
}
return null;
}
/// Return the nearest common ancestor of the given [nodes].
AstNode? getNearestCommonAncestor(List<AstNode> nodes) {
// may be no nodes
if (nodes.isEmpty) {
return null;
}
// prepare parents
var parents = <List<AstNode>>[];
for (var node in nodes) {
parents.add(getParents(node));
}
// find min length
var minLength = 1 << 20;
for (var parentList in parents) {
minLength = min(minLength, parentList.length);
}
// find deepest parent
var i = 0;
for (; i < minLength; i++) {
if (!_allListsIdentical(parents, i)) {
break;
}
}
return parents[0][i - 1];
}
/// Returns the [Expression] qualifier if given [node] is the name part of a
/// [PropertyAccess] or a [PrefixedIdentifier]. Maybe `null`.
Expression? getNodeQualifier(SimpleIdentifier node) {
var parent = node.parent;
if (parent is MethodInvocation && identical(parent.methodName, node)) {
return parent.target;
}
if (parent is PropertyAccess && identical(parent.propertyName, node)) {
return parent.target;
}
if (parent is PrefixedIdentifier && identical(parent.identifier, node)) {
return parent.prefix;
}
return null;
}
/// Returns the [ParameterElement] if the given [node] is a reference to a
/// parameter, or `null` in the other case.
ParameterElement? getParameterElement(SimpleIdentifier node) {
var element = node.staticElement;
if (element is ParameterElement) {
return element;
}
return null;
}
/// Return parent [AstNode]s from compilation unit (at index "0") to the given
/// [node].
List<AstNode> getParents(AstNode node) {
return node.withParents.toList().reversed.toList();
}
/// If given [node] is name of qualified property extraction, returns target
/// from which this property is extracted, otherwise `null`.
Expression? getQualifiedPropertyTarget(AstNode node) {
var parent = node.parent;
if (parent is PrefixedIdentifier) {
var prefixed = parent;
if (prefixed.identifier == node) {
return parent.prefix;
}
}
if (parent is PropertyAccess) {
var access = parent;
if (access.propertyName == node) {
return access.realTarget;
}
}
return null;
}
/// Returns the given [statement] if not a block, or the first child statement
/// if a block, or `null` if more than one child.
Statement? getSingleStatement(Statement? statement) {
if (statement is Block) {
List<Statement> blockStatements = statement.statements;
if (blockStatements.length != 1) {
return null;
}
return blockStatements[0];
}
return statement;
}
/// Returns the given [statement] if not a block, or all the children statements
/// if a block.
List<Statement> getStatements(Statement statement) {
if (statement is Block) {
return statement.statements;
}
return [statement];
}
/// Checks if the given [element]'s display name equals to the given [name].
bool hasDisplayName(Element? element, String name) {
return element?.displayName == name;
}
/// Return whether the specified [name] is declared inside the [root] node
/// or not.
bool isDeclaredIn(AstNode root, String name) {
bool isDeclaredIn(FormalParameterList? parameters) {
if (parameters != null) {
for (var parameter in parameters.parameters) {
if (parameter.name?.lexeme == name) {
return true;
}
}
}
return false;
}
if (root is MethodDeclaration && isDeclaredIn(root.parameters)) {
return true;
}
if (root is FunctionDeclaration &&
isDeclaredIn(root.functionExpression.parameters)) {
return true;
}
var collector = _DeclarationCollector(name);
root.accept(collector);
return collector.isDeclared;
}
/// Checks if given [DartNode] is the left hand side of an assignment, or a
/// declaration of a variable.
bool isLeftHandOfAssignment(SimpleIdentifier node) {
if (node.inSetterContext()) {
return true;
}
return node.parent is VariableDeclaration &&
(node.parent as VariableDeclaration).name == node.token;
}
/// Return `true` if the given [node] is the name of a [NamedExpression].
bool isNamedExpressionName(SimpleIdentifier node) {
var parent = node.parent;
if (parent is Label) {
var label = parent;
if (identical(label.label, node)) {
var parent2 = label.parent;
if (parent2 is NamedExpression) {
return identical(parent2.name, label);
}
}
}
return false;
}
/// If the given [expression] is the `expression` property of a
/// [NamedExpression] then returns this [NamedExpression], otherwise returns
/// [expression].
Expression stepUpNamedExpression(Expression expression) {
var parent = expression.parent;
return parent is NamedExpression ? parent : expression;
}
/// Return `true` if the given [lists] are identical at the given [position].
bool _allListsIdentical(List<List<Object>> lists, int position) {
var element = lists[0][position];
for (var list in lists) {
if (list[position] != element) {
return false;
}
}
return true;
}
class _DeclarationCollector extends RecursiveAstVisitor<void> {
final String name;
bool isDeclared = false;
_DeclarationCollector(this.name);
@override
void visitVariableDeclaration(VariableDeclaration node) {
if (node.name.lexeme == name) {
isDeclared = true;
}
}
}
class _ElementReferenceCollector extends RecursiveAstVisitor<void> {
final Element element;
final List<SimpleIdentifier> references = [];
_ElementReferenceCollector(this.element);
@override
void visitImportPrefixReference(ImportPrefixReference node) {
if (node.element == element) {
references.add(SimpleIdentifierImpl(node.name));
}
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (node.staticElement == element) {
references.add(node);
}
}
}
/// Visitor that collects defined [LocalElement]s.
class _LocalElementsCollector extends RecursiveAstVisitor<void> {
final elements = <LocalElement>[];
@override
void visitVariableDeclaration(VariableDeclaration node) {
var element = node.declaredElement;
if (element is LocalVariableElement) {
elements.add(element);
}
super.visitVariableDeclaration(node);
}
}