blob: f371193070df5e68738576b00fe4fbdf15eb4249 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 NumberFour AG
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* NumberFour AG - initial API and Implementation (Alex Panchenko)
*******************************************************************************/
package org.eclipse.dltk.javascript.internal.search;
import static org.eclipse.dltk.internal.javascript.parser.structure.StructureReporter3.isCallExpression;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.search.matching2.MatchingCollector;
import org.eclipse.dltk.internal.javascript.parser.structure.StructureRequestor;
import org.eclipse.dltk.internal.javascript.ti.JSDocSupport;
import org.eclipse.dltk.internal.javascript.ti.JSDocSupport.ParameterNode;
import org.eclipse.dltk.internal.javascript.ti.JSMethod;
import org.eclipse.dltk.internal.javascript.ti.JSVariable;
import org.eclipse.dltk.internal.javascript.ti.TypeInferencer2;
import org.eclipse.dltk.javascript.ast.AbstractNavigationVisitor;
import org.eclipse.dltk.javascript.ast.Argument;
import org.eclipse.dltk.javascript.ast.Comment;
import org.eclipse.dltk.javascript.ast.Expression;
import org.eclipse.dltk.javascript.ast.FunctionStatement;
import org.eclipse.dltk.javascript.ast.Identifier;
import org.eclipse.dltk.javascript.ast.JSDeclaration;
import org.eclipse.dltk.javascript.ast.JSScope;
import org.eclipse.dltk.javascript.ast.Method;
import org.eclipse.dltk.javascript.ast.ObjectInitializer;
import org.eclipse.dltk.javascript.ast.ObjectInitializerPart;
import org.eclipse.dltk.javascript.ast.PropertyExpression;
import org.eclipse.dltk.javascript.ast.PropertyInitializer;
import org.eclipse.dltk.javascript.ast.Script;
import org.eclipse.dltk.javascript.ast.VariableDeclaration;
import org.eclipse.dltk.javascript.core.JSBindings;
import org.eclipse.dltk.javascript.internal.core.TemporaryBindings;
import org.eclipse.dltk.javascript.parser.JSProblemReporter;
import org.eclipse.dltk.javascript.parser.jsdoc.JSDocTag;
import org.eclipse.dltk.javascript.parser.jsdoc.JSDocTags;
import org.eclipse.dltk.javascript.search.IMatchLocatorHandler;
import org.eclipse.dltk.javascript.search.IMatchLocatorValue;
import org.eclipse.dltk.javascript.search.IMatchLocatorVisitor;
import org.eclipse.dltk.javascript.structure.FunctionDeclaration;
import org.eclipse.dltk.javascript.structure.FunctionExpression;
import org.eclipse.dltk.javascript.structure.FunctionNode;
import org.eclipse.dltk.javascript.structure.IDeclaration;
import org.eclipse.dltk.javascript.structure.IScope;
import org.eclipse.dltk.javascript.structure.ScriptScope;
import org.eclipse.dltk.javascript.structure.VariableNode;
import org.eclipse.dltk.javascript.typeinference.ReferenceLocation;
import org.eclipse.dltk.javascript.typeinfo.IModelBuilder.IMethod;
import org.eclipse.dltk.javascript.typeinfo.IModelBuilder.IParameter;
import org.eclipse.dltk.javascript.typeinfo.ITypeChecker;
import org.eclipse.dltk.javascript.typeinfo.ReferenceSource;
import org.eclipse.dltk.javascript.typeinfo.TypeInfoManager;
import org.eclipse.dltk.javascript.typeinfo.model.JSType;
public class JavaScriptMatchLocatorVisitor extends
AbstractNavigationVisitor<IMatchLocatorValue> implements
IMatchLocatorVisitor {
private final ReferenceSource referenceSource;
private final IMatchLocatorHandler[] handlers;
private JSDocSupport jsdocSupport = new JSDocSupport();
private final JSProblemReporter fReporter = null;
private final ITypeChecker fTypeChecker = null;
private final Stack<IScope> scopes = new Stack<IScope>();
private final List<MatchingNode> nodes = new ArrayList<MatchingNode>();
public JavaScriptMatchLocatorVisitor(ReferenceSource referenceSource) {
this.referenceSource = referenceSource;
final List<IMatchLocatorHandler> extensions = TypeInfoManager
.createExtensions(referenceSource, IMatchLocatorHandler.class,
this);
this.handlers = extensions.toArray(new IMatchLocatorHandler[extensions
.size()]);
}
public void resolveMatchingNodes(TypeInferencer2 inferencer2,
Script script, ISourceModule module) {
if (needsResolve()) {
final List<MatchingNode> tmpNodes = new ArrayList<MatchingNode>();
final JSBindings bindings = TemporaryBindings.build(inferencer2,
module, script);
bindings.run(new Runnable() {
public void run() {
for (MatchingNode node : nodes) {
tmpNodes.add(node.resolvePotentialMatch(bindings));
}
}
});
nodes.clear();
nodes.addAll(tmpNodes);
}
}
private boolean needsResolve() {
for (MatchingNode node : nodes) {
if (node.needsResolve()) {
return true;
}
}
return false;
}
public void report(MatchingCollector<MatchingNode> matchingCollector) {
for (MatchingNode node : nodes) {
matchingCollector.report(node);
}
nodes.clear();
}
@Override
public IMatchLocatorValue visit(ASTNode node) {
for (IMatchLocatorHandler handler : handlers) {
final IMatchLocatorValue value = handler.handle(node);
if (value != IMatchLocatorHandler.CONTINUE) {
return value;
}
}
return super.visit(node);
}
private void push(IScope scope) {
scopes.push(scope);
}
private IScope pop() {
return scopes.pop();
}
private IScope peek() {
return scopes.peek();
}
@Override
public IMatchLocatorValue visitScript(Script node) {
scopes.clear();
push(new ScriptScope());
handleScopeDeclarations(node);
super.visitScript(node);
pop();
return null;
}
@Override
public IMatchLocatorValue visitFunctionStatement(FunctionStatement node) {
return createFunctionDeclaration(node, node.getName());
}
/**
* @param node
* @return
*/
private IMatchLocatorValue createFunctionDeclaration(
FunctionStatement node, Identifier name) {
final JSMethod method = new JSMethod(node, referenceSource);
jsdocSupport.processMethod(node, method, fReporter, fTypeChecker);
final FunctionNode functionNode;
if (node.isDeclaration()) {
functionNode = new FunctionDeclaration(peek(), node, method);
} else {
functionNode = new FunctionExpression(peek(), node, method);
}
method.setLocation(ReferenceLocation.create(referenceSource,
node.start(), node.end(), functionNode.getNameNode()));
functionNode.buildArgumentNodes();
push(functionNode);
addFunctionDeclaration(name, node, method);
visitFunctionBody(node);
pop();
return null;
}
public void visitFunctionBody(FunctionStatement function) {
handleScopeDeclarations(function);
super.visitFunctionStatement(function);
}
public void addFunctionDeclaration(Expression identifier,
FunctionStatement function, IMethod method) {
nodes.add(new MethodDeclarationNode(identifier != null ? identifier
: function, method));
Map<String, Argument> arguments = new HashMap<String, Argument>();
for (Argument argument : function.getArguments()) {
arguments.put(argument.getIdentifier().getName(), argument);
}
for (IParameter parameter : method.getParameters()) {
final Argument argument = arguments.get(parameter.getName());
if (argument != null) {
nodes.add(new ArgumentDeclarationNode(argument, method
.getLocation().getSourceModule(), parameter.getType()));
}
}
final Comment comment = JSDocSupport.getComment(function);
if (comment != null) {
final JSDocTags tags = JSDocSupport.parse(comment);
for (JSDocTag tag : tags.list(JSDocTag.PARAM)) {
final ParameterNode node = JSDocSupport.parseParameter(tag);
if (node != null) {
final IParameter parameter = method.getParameter(node.name);
if (parameter != null) {
final Identifier ref = new Identifier(null);
ref.setName(node.name);
final int start = tag.fromValueOffset(node.offset);
ref.setStart(start);
ref.setEnd(start + node.name.length());
nodes.add(new LocalVariableReferenceNode(ref, parameter
.getLocation(), true));
}
}
}
}
}
public void addFieldDeclaration(Expression identifier, JSType type) {
nodes.add(new FieldDeclarationNode(identifier, referenceSource
.getSourceModule(), type));
}
private void handleScopeDeclarations(JSScope scope) {
for (JSDeclaration declaration : scope.getDeclarations()) {
if (declaration instanceof VariableDeclaration) {
createVariable((VariableDeclaration) declaration);
} else if (declaration instanceof FunctionStatement) {
// TODO Auto-generated method stub
}
}
}
private void createVariable(VariableDeclaration declaration) {
final JSVariable variable = new JSVariable(
declaration.getVariableName());
variable.setLocation(declaration.getInitializer() != null ? ReferenceLocation
.create(referenceSource, declaration.start(),
declaration.end(), declaration.getIdentifier())
: ReferenceLocation.create(referenceSource,
declaration.start(), declaration.end()));
jsdocSupport.processVariable(declaration, variable, fReporter,
fTypeChecker);
final VariableNode variableNode = new VariableNode(peek(), declaration,
variable);
peek().addChild(variableNode);
if (scopes.size() == 1) {
// TODO (alex) option to treat it as field or local
addFieldDeclaration(declaration.getIdentifier(), variable.getType());
} else {
nodes.add(new LocalVariableDeclarationNode(declaration
.getIdentifier(), referenceSource.getSourceModule(),
variable.getType()));
}
}
@Override
public IMatchLocatorValue visitPropertyExpression(PropertyExpression node) {
visit(node.getObject());
final Expression property = node.getProperty();
if (property instanceof Identifier) {
final Identifier identifier = (Identifier) property;
if (isCallExpression(node)) {
nodes.add(new MethodReferenceNode(identifier));
} else {
nodes.add(new FieldReferenceNode(identifier));
}
} else {
visit(property);
}
return null;
}
@Override
public IMatchLocatorValue visitIdentifier(Identifier node) {
final String name = node.getName();
final IDeclaration resolved = peek().resolve(name);
if (resolved != null) {
if (resolved.getParent() instanceof ScriptScope) {
// TODO (alex) option to treat it always as local or not
if (resolved instanceof FunctionNode) {
nodes.add(new MethodReferenceNode(node, resolved
.getLocation()));
} else {
nodes.add(new FieldReferenceNode(node, resolved
.getLocation()));
}
} else {
nodes.add(new LocalVariableReferenceNode(node, resolved
.getLocation()));
}
} else {
if (isCallExpression(node)) {
nodes.add(new MethodReferenceNode(node));
} else {
nodes.add(new FieldReferenceNode(node));
}
}
return null;
}
@Override
public IMatchLocatorValue visitObjectInitializer(ObjectInitializer node) {
for (ObjectInitializerPart part : node.getInitializers()) {
if (part instanceof Method) {
// TODO (alex) handle get & set methods
visitMethod((Method) part);
} else if (part instanceof PropertyInitializer) {
final PropertyInitializer pi = (PropertyInitializer) part;
// TODO (alex) handle jsdoc
if (pi.getValue() instanceof FunctionStatement
&& pi.getName() instanceof Identifier) {
createFunctionDeclaration(
(FunctionStatement) pi.getValue(),
(Identifier) pi.getName());
} else {
addFieldDeclaration(pi.getName(), null /* declaredType */);
visit(pi.getValue());
}
}
}
return null;
}
public void acceptTypeReference(ASTNode node, String typeName) {
nodes.add(new TypeReferenceNode(node, typeName));
}
public void acceptTypeReference(ASTNode node, JSType type) {
nodes.add(new TypeReferenceNode(node, StructureRequestor
.collectContainedTypeNames(type)));
}
}