blob: 6c9f9f88061acec56b4cd32fa7396cc43020242f [file] [log] [blame]
/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.engine.utilities.ast;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.Block;
import com.google.dart.engine.ast.CatchClause;
import com.google.dart.engine.ast.ConstructorDeclaration;
import com.google.dart.engine.ast.Declaration;
import com.google.dart.engine.ast.DeclaredIdentifier;
import com.google.dart.engine.ast.FieldDeclaration;
import com.google.dart.engine.ast.ForEachStatement;
import com.google.dart.engine.ast.ForStatement;
import com.google.dart.engine.ast.FormalParameter;
import com.google.dart.engine.ast.FunctionDeclaration;
import com.google.dart.engine.ast.FunctionDeclarationStatement;
import com.google.dart.engine.ast.FunctionExpression;
import com.google.dart.engine.ast.MethodDeclaration;
import com.google.dart.engine.ast.NodeList;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.Statement;
import com.google.dart.engine.ast.SwitchMember;
import com.google.dart.engine.ast.TopLevelVariableDeclaration;
import com.google.dart.engine.ast.TypeAlias;
import com.google.dart.engine.ast.VariableDeclaration;
import com.google.dart.engine.ast.VariableDeclarationStatement;
import com.google.dart.engine.ast.visitor.GeneralizingAstVisitor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 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.
* <p>
* Completion test code coverage is 95%. The two basic blocks that are not executed cannot be
* executed. They are included for future reference.
*
* @coverage com.google.dart.engine.services.completion
*/
public class ScopedNameFinder extends GeneralizingAstVisitor<Void> {
private Declaration declarationNode;
private AstNode immediateChild;
private Map<String, SimpleIdentifier> locals = new HashMap<String, SimpleIdentifier>();
private int position;
private boolean referenceIsWithinLocalFunction;
public ScopedNameFinder(int position) {
this.position = position;
}
public Declaration getDeclaration() {
return declarationNode;
}
public Map<String, SimpleIdentifier> getLocals() {
return locals;
}
@Override
public Void visitBlock(Block node) {
checkStatements(node.getStatements());
return super.visitBlock(node);
}
@Override
public Void visitCatchClause(CatchClause node) {
addToScope(node.getExceptionParameter());
addToScope(node.getStackTraceParameter());
return super.visitCatchClause(node);
}
@Override
public Void visitConstructorDeclaration(ConstructorDeclaration node) {
if (immediateChild != node.getParameters()) {
addParameters(node.getParameters().getParameters());
}
declarationNode = node;
return null;
}
@Override
public Void visitFieldDeclaration(FieldDeclaration node) {
declarationNode = node;
return null;
}
@Override
public Void visitForEachStatement(ForEachStatement node) {
DeclaredIdentifier loopVariable = node.getLoopVariable();
if (loopVariable != null) {
addToScope(loopVariable.getIdentifier());
}
return super.visitForEachStatement(node);
}
@Override
public Void visitForStatement(ForStatement node) {
if (immediateChild != node.getVariables() && node.getVariables() != null) {
addVariables(node.getVariables().getVariables());
}
return super.visitForStatement(node);
}
@Override
public Void visitFunctionDeclaration(FunctionDeclaration node) {
if (!(node.getParent() instanceof FunctionDeclarationStatement)) {
declarationNode = node;
return null;
}
return super.visitFunctionDeclaration(node);
}
@Override
public Void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
referenceIsWithinLocalFunction = true;
return super.visitFunctionDeclarationStatement(node);
}
@Override
public Void visitFunctionExpression(FunctionExpression node) {
if (node.getParameters() != null && immediateChild != node.getParameters()) {
addParameters(node.getParameters().getParameters());
}
return super.visitFunctionExpression(node);
}
@Override
public Void visitMethodDeclaration(MethodDeclaration node) {
declarationNode = node;
if (node.getParameters() == null) {
return null;
}
if (immediateChild != node.getParameters()) {
addParameters(node.getParameters().getParameters());
}
return null;
}
@Override
public Void visitNode(AstNode node) {
immediateChild = node;
AstNode parent = node.getParent();
if (parent != null) {
parent.accept(this);
}
return null;
}
@Override
public Void visitSwitchMember(SwitchMember node) {
checkStatements(node.getStatements());
return super.visitSwitchMember(node);
}
@Override
public Void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
declarationNode = node;
return null;
}
@Override
public Void visitTypeAlias(TypeAlias node) {
declarationNode = node; // not reached
return null;
}
private void addParameters(NodeList<FormalParameter> vars) {
for (FormalParameter var : vars) {
addToScope(var.getIdentifier());
}
}
private void addToScope(SimpleIdentifier identifier) {
if (identifier != null && isInRange(identifier)) {
String name = identifier.getName();
if (!locals.containsKey(name)) {
locals.put(name, identifier);
}
}
}
private void addVariables(NodeList<VariableDeclaration> vars) {
for (VariableDeclaration var : vars) {
addToScope(var.getName());
}
}
/**
* Some statements define names that are visible downstream. There aren't many of these.
*
* @param statements the list of statements to check for name definitions
*/
private void checkStatements(List<Statement> statements) {
for (Statement stmt : statements) {
if (stmt == immediateChild) {
return;
}
if (stmt instanceof VariableDeclarationStatement) {
addVariables(((VariableDeclarationStatement) stmt).getVariables().getVariables());
} else if (stmt instanceof FunctionDeclarationStatement && !referenceIsWithinLocalFunction) {
addToScope(((FunctionDeclarationStatement) stmt).getFunctionDeclaration().getName());
}
}
}
private boolean isInRange(AstNode node) {
if (position < 0) {
// if source position is not set then all nodes are in range
return true; // not reached
}
return node.getEnd() < position;
}
}