blob: 44f265a7206ea23792a56ef3817f997a45418edc [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.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.eclipse.wst.jsdt.core.dom.ASTNode;
import org.eclipse.wst.jsdt.core.dom.ASTVisitor;
import org.eclipse.wst.jsdt.core.dom.Block;
import org.eclipse.wst.jsdt.core.dom.FieldAccess;
import org.eclipse.wst.jsdt.core.dom.ForInStatement;
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.JavaScriptUnit;
import org.eclipse.wst.jsdt.core.dom.ObjectLiteralField;
import org.eclipse.wst.jsdt.core.dom.SimpleName;
import org.eclipse.wst.jsdt.core.dom.SwitchStatement;
import org.eclipse.wst.jsdt.core.dom.TypeDeclaration;
import org.eclipse.wst.jsdt.core.dom.TypeDeclarationExpression;
import org.eclipse.wst.jsdt.core.dom.TypeDeclarationStatement;
/**
* JS Unit "Indexer". Collects all Class/Function/Variable declarations and
* references
*
* @since 2.0
*
*/
public class SymbolCollector {
Map<IDeclaration, List<IReference>> classReferenceMap = new LinkedHashMap<>();
Map<IDeclaration, List<IReference>> functionReferenceMap = new LinkedHashMap<>();
Map<IDeclaration, List<IReference>> variableReferenceMap = new LinkedHashMap<>();
Map<String, List<IReference>> unresolvedReferences = new LinkedHashMap<>();
JavaScriptUnit unit;
private Stack<Scope> scopes = new Stack<>();
/**
* Creates collector for given JavaScriptUnit
*
* @param unit
* JavaScriptUnit to process
*/
public SymbolCollector(JavaScriptUnit unit) {
this.unit = unit;
}
/**
* Collects all Class/Function/Variable declarations and references
*/
public void process() {
unit.accept(new Visitor());
}
/**
* Returns map of found class declarations and references
*
* @return map of found class declarations and references
*/
public Map<IDeclaration, List<IReference>> getClassReferences() {
return classReferenceMap;
}
/**
* Returns map of found function declarations and references
*
* @return map of found function declarations and references
*/
public Map<IDeclaration, List<IReference>> getFunctionReferences() {
return functionReferenceMap;
}
/**
* Returns map of found variable declarations and references
*
* @return map of found variable declarations and references
*/
public Map<IDeclaration, List<IReference>> getVariableReferences() {
return variableReferenceMap;
}
/**
* Returns map of found unresolved references
*
* @return map of found unresolved references
*/
public Map<String, List<IReference>> getUnresolvedReferences() {
return unresolvedReferences;
}
/**
* Create scope for given AST node and add it to the scope stack
*
* @param node
* AST node to create scope for
* @return newly created scope
*/
private Scope createScope(ASTNode node) {
Scope parent = scopes.empty() ? null : scopes.peek();
Scope result = new Scope(parent, node);
scopes.push(result);
return result;
}
/**
* Remove scope from stack of scopes
*/
private void popScope() {
scopes.pop();
}
/**
* Return current scope
*
* @return current scope
*/
private Scope getScope() {
return scopes.empty() ? null : scopes.peek();
}
/**
* Process scope - find all declarations
*
* @param scope
* scope to process
*/
private void processScope(Scope scope) {
ScopeDeclarationScanner.process(scope);
}
/**
* Register reference
*
* @param node
* AST node that represents reference
*/
private void addReference(SimpleName node) {
IDeclaration v = getScope().getDeclaration(node.getIdentifier());
if (v != null) {
addReference(v, node, getScope());
}
else {
addUnresolvedReference(node, getScope());
}
}
/**
* Select proper map to add declaration to basing on the declaration kind
*
* @param v
* declaration to add
* @return map to add declaration to
*/
private Map<IDeclaration, List<IReference>> selectMap(IDeclaration v) {
switch (v.getKind()) {
case CLASS :
return classReferenceMap;
case FUNCTION :
return functionReferenceMap;
case VARIABLE :
return variableReferenceMap;
default :
return null;
}
}
/**
* Add resolved reference
*
* @param v
* declaration to which reference points
* @param node
* AST node which presents reference symbol
* @param scope
* scope in which reference is contained
*/
private void addReference(IDeclaration v, SimpleName node, Scope scope) {
Map<IDeclaration, List<IReference>> map = selectMap(v);
int index = scope.registerReference();
List<IReference> referenceInfo = map.get(v);
if (referenceInfo == null) {
referenceInfo = new ArrayList<>();
map.put(v, referenceInfo);
}
referenceInfo.add(new Reference(node, scope, index, v));
}
/**
* Add unresolved reference
*
* @param node
* AST node which presents reference symbol
* @param scope
* scope in which reference is contained
*/
private void addUnresolvedReference(SimpleName node, Scope scope) {
int index = scope.registerReference();
String name = node.getIdentifier();
List<IReference> referenceInfo = unresolvedReferences.get(name);
if (referenceInfo == null) {
referenceInfo = new ArrayList<>();
unresolvedReferences.put(name, referenceInfo);
}
referenceInfo.add(new Reference(node, scope, index, null));
}
/**
* Visitor that traverses AST, creates scopes, calls scope scanner detects
* and records references.
*/
private class Visitor extends ASTVisitor {
@Override
public boolean visit(JavaScriptUnit node) {
processScope(createScope(node));
return true;
}
@Override
public boolean visit(FunctionDeclaration node) {
// This is the case of the Class Method declaration
processScope(createScope(node));
internalVisit(node.getMethodName());
internalVisit(node.parameters());
node.getBody().accept(this);
popScope();
return false;
}
@Override
public boolean visit(FunctionExpression node) {
FunctionDeclaration fd = node.getMethod();
processScope(createScope(node));
internalVisit(fd.getMethodName());
internalVisit(fd.parameters());
fd.getBody().accept(this);
popScope();
return false;
}
@Override
public boolean visit(FunctionDeclarationStatement node) {
FunctionDeclaration fd = node.getDeclaration();
internalVisit(fd.getMethodName());
processScope(createScope(node));
internalVisit(fd.parameters());
internalVisit(fd.getBody());
popScope();
return false;
}
@Override
public boolean visit(TypeDeclarationStatement node) {
TypeDeclaration td = (TypeDeclaration) node.getDeclaration();
processScope(createScope(node));
internalVisit(td.getName());
internalVisit(td.getSuperclassExpression());
internalVisit(td.bodyDeclarations());
popScope();
return false;
}
@Override
public boolean visit(TypeDeclarationExpression node) {
TypeDeclaration td = (TypeDeclaration) node.getDeclaration();
processScope(createScope(node));
internalVisit(td.getName());
internalVisit(td.getSuperclassExpression());
internalVisit(td.bodyDeclarations());
popScope();
return false;
}
@Override
public boolean visit(ForStatement node) {
processScope(createScope(node));
return true;
}
@Override
public void endVisit(ForStatement node) {
popScope();
}
@Override
public boolean visit(ForInStatement node) {
processScope(createScope(node));
return true;
}
@Override
public void endVisit(ForInStatement node) {
popScope();
}
@Override
public boolean visit(ForOfStatement node) {
processScope(createScope(node));
return true;
}
@Override
public void endVisit(ForOfStatement node) {
popScope();
}
@Override
public boolean visit(SwitchStatement node) {
processScope(createScope(node));
return true;
}
@Override
public void endVisit(SwitchStatement node) {
popScope();
}
@Override
public boolean visit(Block node) {
processScope(createScope(node));
return true;
}
@Override
public void endVisit(Block node) {
popScope();
}
@Override
public boolean visit(ObjectLiteralField node) {
// Process only initializer. Don't collect property names
// FIXME getInitializer() always returns value. Is it correct? How
// to test if it's real or not?
internalVisit(node.getInitializer());
return false;
}
@Override
public boolean visit(FieldAccess node) {
// In this simplified collector we process only the left hand
// expresion.
// Ignore field name since there is no type inference
internalVisit(node.getExpression());
return false;
}
@Override
public boolean visit(SimpleName node) {
addReference(node);
return false;
}
protected void internalVisit(List<ASTNode> nodes) {
if (nodes != null) {
for (ASTNode node : nodes) {
node.accept(this);
}
}
}
protected void internalVisit(ASTNode node) {
if (node != null) {
node.accept(this);
}
}
}
}