blob: b4851bc996095fb2ef709f6f1e8f92d17dee9f8e [file]
// 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.
/// @docImport 'package:analyzer/src/lint/linter_visitor.dart';
library;
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/exception/exception.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/generated/engine.dart' show AnalysisEngine;
export 'package:analyzer/src/dart/ast/constant_evaluator.dart';
/// An object used to locate the [AstNode] associated with a source range.
/// More specifically, they will return the deepest [AstNode] which completely
/// encompasses the specified range with some exceptions:
///
/// - Offsets that fall between the name and type/formal parameter list of a
/// declaration will return the declaration node and not the parameter list
/// node.
class NodeLocator2 extends UnifyingAstVisitor<void> {
/// The inclusive start offset of the range used to identify the node.
final int _startOffset;
/// The inclusive end offset of the range used to identify the node.
final int _endOffset;
/// The found node or `null` if there is no such node.
AstNode? _foundNode;
/// Initialize a newly created locator to locate the deepest [AstNode] for
/// which `node.offset <= [startOffset]` and `[endOffset] < node.end`.
///
/// If [endOffset] is not provided, then it is considered the same as the
/// given [startOffset].
NodeLocator2(int startOffset, [int? endOffset])
: _startOffset = startOffset,
_endOffset = endOffset ?? startOffset;
/// Search within the given AST [node] and return the node that was found,
/// or `null` if no node was found.
AstNode? searchWithin(AstNode? node) {
if (node == null) {
return null;
}
try {
node.accept(this);
} catch (exception, stackTrace) {
// TODO(39284): should this exception be silent?
AnalysisEngine.instance.instrumentationService.logException(
SilentException(
'Unable to locate element at offset '
'($_startOffset - $_endOffset)',
exception,
stackTrace,
),
);
return null;
}
return _foundNode;
}
@override
void visitClassDeclaration(ClassDeclaration node) {
// Names do not have AstNodes but offsets at the end should be treated as
// part of the declaration (not parameter list).
if (_startOffset == _endOffset &&
_startOffset == node.namePart.typeName.end) {
_foundNode = node;
return;
}
super.visitClassDeclaration(node);
}
@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
// Names do not have AstNodes but offsets at the end should be treated as
// part of the declaration (not parameter list).
if (_startOffset == _endOffset) {
var end = node.name?.end ?? node.typeName?.end;
if (end != null && _startOffset == end) {
_foundNode = node;
return;
}
}
super.visitConstructorDeclaration(node);
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
// Names do not have AstNodes but offsets at the end should be treated as
// part of the declaration (not parameter list).
if (_startOffset == _endOffset && _startOffset == node.name.end) {
_foundNode = node;
return;
}
super.visitFunctionDeclaration(node);
}
@override
void visitMethodDeclaration(MethodDeclaration node) {
// Names do not have AstNodes but offsets at the end should be treated as
// part of the declaration (not parameter list).
if (_startOffset == _endOffset && _startOffset == node.name.end) {
_foundNode = node;
return;
}
super.visitMethodDeclaration(node);
}
@override
void visitNode(AstNode node) {
// Don't visit a new tree if the result has been already found.
if (_foundNode != null) {
return;
}
// Check whether the current node covers the selection.
Token beginToken = node.beginToken;
Token endToken = node.endToken;
// Don't include synthetic tokens.
while (endToken != beginToken) {
// Fasta scanner reports unterminated string literal errors
// and generates a synthetic string token with non-zero length.
// Because of this, check for length > 0 rather than !isSynthetic.
if (endToken.isEof || endToken.length > 0) {
break;
}
endToken = endToken.previous!;
}
int end = endToken.end;
int start = node.offset;
if (end <= _startOffset || start > _endOffset) {
return;
}
// Check children.
try {
node.visitChildren(this);
} catch (exception, stackTrace) {
// Ignore the exception and proceed in order to visit the rest of the
// structure.
// TODO(39284): should this exception be silent?
AnalysisEngine.instance.instrumentationService.logException(
SilentException(
"Exception caught while traversing an AST structure.",
exception,
stackTrace,
),
);
}
// Found a child.
if (_foundNode != null) {
return;
}
// Check this node.
if (start <= _startOffset && _endOffset < end) {
_foundNode = node;
}
}
}
/// Traverse the AST from initial child node to successive parents, building a
/// collection of local variable and parameter names visible to the initial
/// child node. In case of name shadowing, the first name seen is the most
/// specific one so names are not redefined.
///
/// Completion test code coverage is 95%. The two basic blocks that are not
/// executed cannot be executed. They are included for future reference.
class ScopedNameFinder extends GeneralizingAstVisitor<void> {
Declaration? _declarationNode;
AstNode? _immediateChild;
final Set<String> _locals = {};
final int _position;
bool _referenceIsWithinLocalFunction = false;
ScopedNameFinder(this._position);
Declaration? get declaration => _declarationNode;
Set<String> get locals => _locals;
@override
void visitBlock(Block node) {
_checkStatements(node.statements);
super.visitBlock(node);
}
@override
void visitCatchClause(CatchClause node) {
_addToScope(node.exceptionParameter?.name);
_addToScope(node.stackTraceParameter?.name);
super.visitCatchClause(node);
}
@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
if (!identical(_immediateChild, node.parameters)) {
_addParameters(node.parameters.parameters);
}
_declarationNode = node;
}
@override
void visitFieldDeclaration(FieldDeclaration node) {
_declarationNode = node;
}
@override
void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
_addToScope(node.loopVariable.name);
super.visitForEachPartsWithDeclaration(node);
}
@override
void visitForPartsWithDeclarations(ForPartsWithDeclarations node) {
_addVariables(node.variables.variables);
super.visitForPartsWithDeclarations(node);
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
if (node.parent is! FunctionDeclarationStatement) {
_declarationNode = node;
} else {
super.visitFunctionDeclaration(node);
}
}
@override
void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
_referenceIsWithinLocalFunction = true;
super.visitFunctionDeclarationStatement(node);
}
@override
void visitFunctionExpression(FunctionExpression node) {
var parameters = node.parameters;
if (parameters != null && !identical(_immediateChild, parameters)) {
_addParameters(parameters.parameters);
}
super.visitFunctionExpression(node);
}
@override
void visitMethodDeclaration(MethodDeclaration node) {
_declarationNode = node;
var parameters = node.parameters;
if (parameters != null && !identical(_immediateChild, parameters)) {
_addParameters(parameters.parameters);
}
}
@override
void visitNode(AstNode node) {
_immediateChild = node;
node.parent?.accept(this);
}
@override
void visitSwitchMember(SwitchMember node) {
_checkStatements(node.statements);
super.visitSwitchMember(node);
}
@override
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
_declarationNode = node;
}
@override
void visitTypeAlias(TypeAlias node) {
_declarationNode = node;
}
void _addParameters(NodeList<FormalParameter> vars) {
for (FormalParameter var2 in vars) {
_addToScope(var2.name);
}
}
void _addToScope(Token? identifier) {
if (identifier != null && _isInRange(identifier)) {
_locals.add(identifier.lexeme);
}
}
void _addVariables(NodeList<VariableDeclaration> variables) {
for (VariableDeclaration variable in variables) {
_addToScope(variable.name);
}
}
/// Check the given list of [statements] for any that come before the
/// immediate child and that define a name that would be visible to the
/// immediate child.
void _checkStatements(List<Statement> statements) {
for (Statement statement in statements) {
if (identical(statement, _immediateChild)) {
return;
}
if (statement is VariableDeclarationStatement) {
_addVariables(statement.variables.variables);
} else if (statement is FunctionDeclarationStatement &&
!_referenceIsWithinLocalFunction) {
_addToScope(statement.functionDeclaration.name);
}
}
}
bool _isInRange(Token token) {
if (_position < 0) {
// if source position is not set then all nodes are in range
return true;
// not reached
}
return token.end < _position;
}
}