blob: 5a0c4f4784effc6c8958ce28afa40c731168d4ac [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Eugene Melekhov and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eugene Melekhov - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.jsdt.internal.core.dom.binding;
import java.util.List;
import org.eclipse.wst.jsdt.core.dom.ASTNode;
import org.eclipse.wst.jsdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.wst.jsdt.core.dom.Block;
import org.eclipse.wst.jsdt.core.dom.CatchClause;
import org.eclipse.wst.jsdt.core.dom.DoStatement;
import org.eclipse.wst.jsdt.core.dom.ForOfStatement;
import org.eclipse.wst.jsdt.core.dom.ForStatement;
import org.eclipse.wst.jsdt.core.dom.FunctionDeclaration;
import org.eclipse.wst.jsdt.core.dom.FunctionDeclarationStatement;
import org.eclipse.wst.jsdt.core.dom.FunctionExpression;
import org.eclipse.wst.jsdt.core.dom.IfStatement;
import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
import org.eclipse.wst.jsdt.core.dom.LabeledStatement;
import org.eclipse.wst.jsdt.core.dom.SimpleName;
import org.eclipse.wst.jsdt.core.dom.SingleVariableDeclaration;
import org.eclipse.wst.jsdt.core.dom.SwitchCase;
import org.eclipse.wst.jsdt.core.dom.SwitchStatement;
import org.eclipse.wst.jsdt.core.dom.TryStatement;
import org.eclipse.wst.jsdt.core.dom.TypeDeclaration;
import org.eclipse.wst.jsdt.core.dom.TypeDeclarationExpression;
import org.eclipse.wst.jsdt.core.dom.TypeDeclarationStatement;
import org.eclipse.wst.jsdt.core.dom.VariableDeclarationExpression;
import org.eclipse.wst.jsdt.core.dom.VariableDeclarationFragment;
import org.eclipse.wst.jsdt.core.dom.VariableDeclarationStatement;
import org.eclipse.wst.jsdt.core.dom.WhileStatement;
import org.eclipse.wst.jsdt.core.dom.WithStatement;
/**
* Declarations scanner. Scans given scope and adds all found symbol
* declarations to it.
*
* Note. It scans only declarations. Reference scan is done in
* <code>SymbolCollector</code> which creates scopes and invokes this scanner.
*
* @since 2.0
*
*/
public class ScopeDeclarationScanner {
/**
* Scans given scope and adds all found symbol declarations to it.
*
* @param scope
*/
public static void process(Scope scope) {
new Visitor().process(scope);
}
/**
* Scope tree traversal that walks given scope and adds all found symbol
* declarations to it.
*
*/
private static class Visitor {
private static final String ARGUMENTS = "arguments"; //$NON-NLS-1$
private Scope rootScope;
/**
* Scans given scope and adds all found symbol declarations to it.
*
* @param scope
*/
void process(Scope scope) {
this.rootScope = scope;
scanRoot(scope.getRootNode());
}
/**
* Scan scope root AST Node. The scopes are created for Classes,
* Functions and JavaScriptUnits
*
* @param n
* node to scan
*/
private void scanRoot(ASTNode n) {
switch (n.getNodeType()) {
case ASTNode.FUNCTION_EXPRESSION : {
FunctionDeclaration fd = ((FunctionExpression) n).getMethod();
SimpleName fNameNode = ((SimpleName) fd.getMethodName());
if (fNameNode != null) {
declareFunction(fNameNode);
}
declareExpressions(rootScope, fd.parameters());
}
break;
case ASTNode.FUNCTION_DECLARATION_STATEMENT : {
FunctionDeclaration fd = ((FunctionDeclarationStatement) n).getDeclaration();
declareExpressions(rootScope, fd.parameters());
}
break;
case ASTNode.FUNCTION_DECLARATION : {
FunctionDeclaration fd = ((FunctionDeclaration) n);
SimpleName fNameNode = ((SimpleName) fd.getMethodName());
if (fNameNode != null) {
declareFunction(fNameNode);
}
declareExpressions(rootScope, fd.parameters());
}
break;
case ASTNode.TYPE_DECLARATION_EXPRESSION : {
AbstractTypeDeclaration cd = ((TypeDeclarationExpression) n).getDeclaration();
if (cd != null) {
declareClass(cd.getName());
}
}
break;
case ASTNode.TYPE_DECLARATION :
break;
default :
scanDeclarations(n);
break;
}
}
/**
* Scan declarations in the given node
*
* @param n
* node to scan
*/
private void scanDeclarations(ASTNode n) {
if (n == null) {
return;
}
switch (n.getNodeType()) {
case ASTNode.VARIABLE_DECLARATION_STATEMENT : {
VariableDeclarationStatement vd = ((VariableDeclarationStatement) n);
switch (vd.getKind()) {
case VAR :
Scope s = rootScope.getHoistScope();
for (VariableDeclarationFragment f : (List<VariableDeclarationFragment>) vd.fragments()) {
declareExpression(s, f);
}
break;
case LET :
case CONST :
if (!isAtCurrentLexicalScope(n)) {
return;
}
for (VariableDeclarationFragment f : (List<VariableDeclarationFragment>) vd.fragments()) {
declareExpression(rootScope, f);
}
break;
}
}
return;
case ASTNode.VARIABLE_DECLARATION_EXPRESSION : {
VariableDeclarationExpression vd = ((VariableDeclarationExpression) n);
switch (vd.getKind()) {
case VAR :
Scope s = rootScope.getHoistScope();
for (VariableDeclarationFragment f : (List<VariableDeclarationFragment>) vd.fragments()) {
declareExpression(s, f);
}
break;
case LET :
case CONST :
if (!isAtCurrentLexicalScope(n)) {
return;
}
for (VariableDeclarationFragment f : (List<VariableDeclarationFragment>) vd.fragments()) {
declareExpression(rootScope, f);
}
break;
}
}
return;
case ASTNode.FUNCTION_EXPRESSION :
// Intentional
return;
case ASTNode.FUNCTION_DECLARATION_STATEMENT : {
FunctionDeclaration fd = ((FunctionDeclarationStatement) n).getDeclaration();
if (fd != null) {
declareFunction(((SimpleName) fd.getMethodName()));
}
}
return;
case ASTNode.FUNCTION_DECLARATION : {
declareFunction(((SimpleName) ((FunctionDeclaration) n).getMethodName()));
}
break;
case ASTNode.TYPE_DECLARATION_EXPRESSION :
return;
case ASTNode.TYPE_DECLARATION_STATEMENT : {
TypeDeclaration td = (TypeDeclaration) ((TypeDeclarationStatement) n).getDeclaration();
if (td != null) {
declareClass(td.getName());
}
}
return;
case ASTNode.CATCH_CLAUSE : {
CatchClause cc = ((CatchClause) n);
if (isAtCurrentLexicalScope(n)) {
declareExpression(rootScope, cc.getException());
}
scanDeclarations(cc.getBody());
}
return;
}
switch (n.getNodeType()) {
case ASTNode.FOR_STATEMENT : {
ForStatement fs = ((ForStatement) n);
scanDeclarations(fs.initializers());
scanDeclarations(fs.getExpression());
scanDeclarations(fs.updaters());
scanDeclarations(fs.getBody());
}
break;
case ASTNode.FOR_OF_STATEMENT : {
ForOfStatement fs = ((ForOfStatement) n);
scanDeclarations(fs.getIterationVariable());
scanDeclarations(fs.getCollection());
scanDeclarations(fs.getBody());
}
break;
case ASTNode.DO_STATEMENT : {
DoStatement fs = ((DoStatement) n);
scanDeclarations(fs.getBody());
scanDeclarations(fs.getExpression());
}
break;
case ASTNode.WHILE_STATEMENT : {
WhileStatement fs = ((WhileStatement) n);
scanDeclarations(fs.getExpression());
scanDeclarations(fs.getBody());
}
break;
case ASTNode.WITH_STATEMENT : {
WithStatement fs = ((WithStatement) n);
scanDeclarations(fs.getExpression());
scanDeclarations(fs.getBody());
}
break;
case ASTNode.IF_STATEMENT : {
IfStatement fs = ((IfStatement) n);
scanDeclarations(fs.getExpression());
scanDeclarations(fs.getThenStatement());
scanDeclarations(fs.getElseStatement());
}
break;
case ASTNode.LABELED_STATEMENT : {
LabeledStatement fs = ((LabeledStatement) n);
scanDeclarations(fs.getBody());
}
break;
case ASTNode.TRY_STATEMENT : {
TryStatement ts = ((TryStatement) n);
scanDeclarations(ts.getBody());
scanDeclarations(ts.catchClauses());
scanDeclarations(ts.getFinally());
}
break;
case ASTNode.SWITCH_STATEMENT : {
SwitchStatement ss = ((SwitchStatement) n);
scanDeclarations(ss.getExpression());
scanDeclarations(ss.statements());
}
break;
case ASTNode.SWITCH_CASE : {
SwitchCase ss = ((SwitchCase) n);
scanDeclarations(ss.getExpression());
}
break;
case ASTNode.BLOCK : {
Block ss = ((Block) n);
scanDeclarations(ss.statements());
}
break;
case ASTNode.JAVASCRIPT_UNIT : {
JavaScriptUnit ss = ((JavaScriptUnit) n);
scanDeclarations(ss.statements());
}
}
}
/**
* Scan Declarations in the list of nodes
*
* @param nodes
*/
private void scanDeclarations(List<ASTNode> nodes) {
if (nodes != null) {
for (ASTNode node : nodes) {
scanDeclarations(node);
}
}
}
/**
* Declare list of given expressions if possible
*
* @param scope
* scope to place declaration to
* @param lhs
* expression
*/
private void declareExpressions(Scope scope, List<ASTNode> lhs) {
if (lhs != null) {
for (ASTNode p : lhs) {
declareExpression(scope, p);
}
}
}
/**
* Declare given expressions if possible
*
* @param scope
* scope to place declaration to
* @param lhs
* expression
*/
private void declareExpression(Scope scope, ASTNode lhs) {
if (lhs != null) {
switch (lhs.getNodeType()) {
case ASTNode.SIMPLE_NAME :
declareVariable(scope, (SimpleName) lhs);
break;
case ASTNode.SINGLE_VARIABLE_DECLARATION :
declareVariable(scope, ((SingleVariableDeclaration) lhs).getName());
break;
case ASTNode.VARIABLE_DECLARATION_FRAGMENT :
declareVariable(scope, ((VariableDeclarationFragment) lhs).getName());
break;
case ASTNode.OBJECT_LITERAL :
break;
}
}
}
/**
* Check if given node is at current lexical scope
*
* @param n
* node to check
* @return
*/
private boolean isAtCurrentLexicalScope(ASTNode n) {
ASTNode parent = n.getParent();
ASTNode grandparent = NodeUtil.grandParent(n);
if (grandparent != null && (grandparent.getNodeType() == ASTNode.SWITCH_CASE)) {
ASTNode switchNode = grandparent.getParent();
return rootScope.getRootNode() == switchNode;
}
if (parent == rootScope.getRootNode() || parent.getNodeType() == ASTNode.JAVASCRIPT_UNIT || (grandparent != null && grandparent.getNodeType() == ASTNode.CATCH_CLAUSE && (parent.getParent() != null ? parent.getParent() : null) == rootScope.getRootNode())) {
return true;
}
while (parent.getNodeType() == ASTNode.LABELED_STATEMENT) {
if (parent.getParent() == rootScope.getRootNode()) {
return true;
}
parent = parent.getParent();
}
return false;
}
/**
* Declare variable at given scope
*
* @param s
* scope
* @param n
* variable name node
*/
private void declareVariable(Scope s, SimpleName n) {
if (n != null) {
String name = n.getIdentifier();
if (checkRedeclaration(s, n, name)) {
s.declareVariable(name, n, org.eclipse.wst.jsdt.internal.core.dom.binding.VariableDeclaration.VariableKind.VAR);
}
}
}
/**
* Declare function at given scope
*
* @param s
* scope
* @param n
* function name node
*/
private void declareFunction(Scope s, SimpleName n) {
if (n != null) {
String name = n.getIdentifier();
if (checkRedeclaration(s, n, name)) {
s.declareFunction(name, n);
}
}
}
/**
* Declare function at default scope
*
* @param n
* function name node
*/
private void declareFunction(SimpleName n) {
declareFunction(rootScope, n);
}
/**
* Declare class at given scope
*
* @param s
* scope
* @param n
* class name node
*/
private void declareClass(Scope s, SimpleName n) {
if (n != null) {
String name = n.getIdentifier();
if (checkRedeclaration(s, n, name)) {
s.declareClass(name, n);
}
}
}
/**
* Declare class at default scope
*
* @param n
* function name node
*/
private void declareClass(SimpleName n) {
declareClass(rootScope, n);
}
private boolean checkRedeclaration(Scope s, SimpleName n, String name) {
if (s.isDeclared(name, false) || (s.isLocal() && ARGUMENTS.equals(name))) {
// TODO Should we handle it somehow?
return false;
}
return true;
}
}
}