All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fife.rsta.ac.js.resolver.JavaScriptCompletionResolver Maven / Gradle / Ivy

package org.fife.rsta.ac.js.resolver;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;

import org.fife.rsta.ac.java.classreader.ClassFile;
import org.fife.rsta.ac.js.JavaScriptHelper;
import org.fife.rsta.ac.js.JavaScriptParser;
import org.fife.rsta.ac.js.Logger;
import org.fife.rsta.ac.js.SourceCompletionProvider;
import org.fife.rsta.ac.js.ast.JavaScriptFunctionDeclaration;
import org.fife.rsta.ac.js.ast.jsType.JavaScriptType;
import org.fife.rsta.ac.js.ast.type.TypeDeclaration;
import org.fife.rsta.ac.js.completion.JSCompletion;
import org.fife.rsta.ac.js.completion.JSMethodData;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Parser;
import org.mozilla.javascript.Token;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.ExpressionStatement;
import org.mozilla.javascript.ast.FunctionCall;
import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.NodeVisitor;
import org.mozilla.javascript.ast.PropertyGet;


/**
 * Compiles the entered text using Rhino and tries to resolve the JavaScriptType
 * from the AstRoot e.g.
 * 
 * var a = ""; "" --> String JavaScriptType var b =
 * a.toString() a.toString --> String JavaScriptType
 * 
 * etc.
 * 

* Note, will resolve any type added to JavaScriptTypesFactory * */ public class JavaScriptCompletionResolver extends JavaScriptResolver { protected JavaScriptType lastJavaScriptType; protected String lastLookupName = null; /** * Standard ECMA JavaScript resolver * @param provider */ public JavaScriptCompletionResolver(SourceCompletionProvider provider) { super(provider); } /** * Compiles Text and resolves the type. * e.g. * "Hello World".length; //resolve as a Number * * @param text to compile and resolve */ @Override public JavaScriptType compileText(String text) { CompilerEnvirons env = JavaScriptParser.createCompilerEnvironment(new JavaScriptParser.JSErrorReporter(), provider.getLanguageSupport()); String parseText = JavaScriptHelper.removeLastDotFromText(text); int charIndex = JavaScriptHelper.findIndexOfFirstOpeningBracket(parseText); env.setRecoverFromErrors(true); Parser parser = new Parser(env); AstRoot root = parser.parse(parseText, null, 0); CompilerNodeVisitor visitor = new CompilerNodeVisitor(charIndex == 0); root.visitAll(visitor); return lastJavaScriptType; } /** * Resolve node type to TypeDeclaration. Called instead of #compileText(String text) when document is already parsed * @param text The node to resolve * @return TypeDeclaration for node or null if not found. */ @Override public TypeDeclaration resolveParamNode(String text) { if(text != null) { CompilerEnvirons env = JavaScriptParser.createCompilerEnvironment(new JavaScriptParser.JSErrorReporter(), provider.getLanguageSupport()); int charIndex = JavaScriptHelper.findIndexOfFirstOpeningBracket(text); env.setRecoverFromErrors(true); Parser parser = new Parser(env); AstRoot root = parser.parse(text, null, 0); CompilerNodeVisitor visitor = new CompilerNodeVisitor(charIndex == 0); root.visitAll(visitor); } return lastJavaScriptType != null ? lastJavaScriptType.getType() : provider.getTypesFactory().getDefaultTypeDeclaration(); } /** * Resolve node type to TypeDeclaration. Called instead of #compileText(String text) when document is already parsed * @param node AstNode to resolve * @return TypeDeclaration for node or null if not found. */ @Override public TypeDeclaration resolveNode(AstNode node) { if(node == null) return provider.getTypesFactory().getDefaultTypeDeclaration(); CompilerNodeVisitor visitor = new CompilerNodeVisitor(true); node.visit(visitor); return lastJavaScriptType != null ? lastJavaScriptType.getType() : provider.getTypesFactory().getDefaultTypeDeclaration(); } /** * Resolve node type to TypeDeclaration * N.B called from CompilerNodeVisitor.visit() * * @param node AstNode to resolve * @return TypeDeclaration for node or null if not found. */ @Override protected TypeDeclaration resolveNativeType(AstNode node) { TypeDeclaration dec = JavaScriptHelper.tokenToNativeTypeDeclaration(node, provider); if(dec == null) { dec = testJavaStaticType(node); } return dec; } /** * Test whether the node can be resolved as a static Java class. * Only looks for Token.NAME nodes to test * @param node node to test * @return The type declaration. */ protected TypeDeclaration testJavaStaticType(AstNode node) { switch (node.getType()) { case Token.NAME: return findJavaStaticType(node); } return null; } /** * Try to resolve the Token.NAME AstNode and return a TypeDeclaration * @param node node to resolve * @return TypeDeclaration if the name can be resolved as a Java Class else null */ protected TypeDeclaration findJavaStaticType(AstNode node) { // check parent is of type property get String testName = node.toSource(); if (testName != null) { TypeDeclaration dec = JavaScriptHelper.getTypeDeclaration(testName, provider); if(dec != null) { ClassFile cf = provider.getJavaScriptTypesFactory().getClassFile( provider.getJarManager(), dec); if (cf != null) { return provider.getJavaScriptTypesFactory() .createNewTypeDeclaration(cf, true, false); } } } return null; } // TODO not sure how right this is, but is very tricky problem resolving // complex completions private class CompilerNodeVisitor implements NodeVisitor { private boolean ignoreParams; private HashSet paramNodes = new HashSet<>(); private CompilerNodeVisitor(boolean ignoreParams) { this.ignoreParams = ignoreParams; } @Override public boolean visit(AstNode node) { Logger.log(JavaScriptHelper.convertNodeToSource(node)); Logger.log(node.shortName()); if(!validNode(node)) { //invalid node found, set last completion invalid and stop processing lastJavaScriptType = null; return false; } if (ignore(node, ignoreParams)) return true; JavaScriptType jsType; TypeDeclaration dec; //only resolve native type if last type is null //otherwise it can be assumed that this is part of multi depth - e.g. "".length.toString() if(lastJavaScriptType == null) { dec = resolveNativeType(node); if(dec == null && node.getType() == Token.NAME) { lastJavaScriptType = null; return false; } } else { dec = resolveTypeFromLastJavaScriptType(node); } if (dec != null) { // lookup JavaScript completions type jsType = provider.getJavaScriptTypesFactory().getCachedType( dec, provider.getJarManager(), provider, JavaScriptHelper.convertNodeToSource(node)); if (jsType != null) { lastJavaScriptType = jsType; // stop here return false; } } else if (lastJavaScriptType != null) { if (node.getType() == Token.NAME) { // lookup from source name jsType = lookupFromName(node, lastJavaScriptType); if (jsType == null) { // lookup name through the functions of // lastJavaScriptType jsType = lookupFunctionCompletion(node, lastJavaScriptType); } lastJavaScriptType = jsType; } } else if(node instanceof FunctionCall) { FunctionCall fn = (FunctionCall) node; String lookupText = createLookupString(fn); JavaScriptFunctionDeclaration funcDec = provider.getVariableResolver().findFunctionDeclaration(lookupText); if(funcDec != null) { jsType = provider.getJavaScriptTypesFactory().getCachedType( funcDec.getTypeDeclaration(), provider.getJarManager(), provider, JavaScriptHelper.convertNodeToSource(node)); if (jsType != null) { lastJavaScriptType = jsType; // stop here return false; } } } return true; } private boolean validNode(AstNode node) { switch(node.getType()) { case Token.NAME: return ((Name) node).getIdentifier() != null && ((Name) node).getIdentifier().length() > 0; } return true; } private String createLookupString(FunctionCall fn) { StringBuilder sb = new StringBuilder(); String name = ""; switch(fn.getTarget().getType()) { case Token.NAME : name = ((Name) fn.getTarget()).getIdentifier(); break; } sb.append(name); sb.append("("); Iterator i = fn.getArguments().iterator(); while (i.hasNext()) { i.next(); sb.append("p"); if(i.hasNext()) sb.append(","); } sb.append(")"); return sb.toString(); } /** * Test node to check whether to ignore resolving, this is for * parameters * * @param node node to test * @return true to ignore */ private boolean ignore(AstNode node, boolean ignoreParams) { switch (node.getType()) { // ignore errors e.g. if statement - if(a. //no closing brace case Token.EXPR_VOID: case Token.EXPR_RESULT: return ((ExpressionStatement) node).getExpression() .getType() == Token.ERROR; case Token.ERROR: case Token.GETPROP: case Token.SCRIPT: return true; default: { if (isParameter(node)) { collectAllNodes(node); // everything within this node // is a parameter return ignoreParams; } break; } } //if (JavaScriptHelper.isInfixOnly(node)) // return true; return false; } /** * Get all nodes within AstNode and add to an ArrayList * * @param node */ private void collectAllNodes(AstNode node) { if (node.getType() == Token.CALL) { // collect all argument nodes FunctionCall call = (FunctionCall) node; for (AstNode arg : call.getArguments()) { VisitorAll all = new VisitorAll(); arg.visit(all); paramNodes.addAll(all.getAllNodes()); } } } /** * Check the function that a name may belong to contains this actual * parameter * * @param node Node to check * @return true if the function contains the parameter */ private boolean isParameter(AstNode node) { if (paramNodes.contains(node)) return true; // get all params from this function too FunctionCall fc = JavaScriptHelper.findFunctionCallFromNode(node); if (fc != null && !(node == fc)) { collectAllNodes(fc); if (paramNodes.contains(node)) { return true; } } return false; } } /** * Lookup the name of the node within the last JavaScript type. e.g. var a = * 1; var b = a.MAX_VALUE; looks up MAX_VALUE within NumberLiteral a where a * is resolve before as a JavaScript Number; * * @param node * @param lastJavaScriptType * @return The type. */ protected JavaScriptType lookupFromName(AstNode node, JavaScriptType lastJavaScriptType) { JavaScriptType javaScriptType = null; if (lastJavaScriptType != null) { String lookupText = null; switch (node.getType()) { case Token.NAME: lookupText = ((Name) node).getIdentifier(); break; } if (lookupText == null) { // just try the source lookupText = node.toSource(); } javaScriptType = lookupJavaScriptType(lastJavaScriptType, lookupText); } return javaScriptType; } /** * Lookup the function name of the node within the last JavaScript type. e.g. * var a = ""; var b = a.toString(); looks up toString() within * StringLiteral a where a is resolve before as a JavaScript String; * * @param node * @param lastJavaScriptType * @return The type. */ protected JavaScriptType lookupFunctionCompletion(AstNode node, JavaScriptType lastJavaScriptType) { JavaScriptType javaScriptType = null; if (lastJavaScriptType != null) { String lookupText = JavaScriptHelper.getFunctionNameLookup(node, provider); javaScriptType = lookupJavaScriptType(lastJavaScriptType, lookupText); } // return last type return javaScriptType; } @Override public String getLookupText(JSMethodData method, String name) { StringBuilder sb = new StringBuilder(name); sb.append('('); int count = method.getParameterCount(); for (int i = 0; i < count; i++) { sb.append("p"); if (i < count - 1) { sb.append(","); } } sb.append(')'); return sb.toString(); } @Override public String getFunctionNameLookup(FunctionCall call, SourceCompletionProvider provider) { if (call != null) { StringBuilder sb = new StringBuilder(); if (call.getTarget() instanceof PropertyGet) { PropertyGet get = (PropertyGet) call.getTarget(); sb.append(get.getProperty().getIdentifier()); } sb.append("("); int count = call.getArguments().size(); for (int i = 0; i < count; i++) { sb.append("p"); if (i < count - 1) { sb.append(","); } } sb.append(")"); return sb.toString(); } return null; } private JavaScriptType lookupJavaScriptType( JavaScriptType lastJavaScriptType, String lookupText) { JavaScriptType javaScriptType = null; if (lookupText != null && !lookupText.equals(lastLookupName)) { // look up JSCompletion JSCompletion completion = lastJavaScriptType .getCompletion(lookupText, provider); if (completion != null) { String type = completion.getType(true); if (type != null) { TypeDeclaration newType = provider.getTypesFactory() .getTypeDeclaration(type); if (newType != null) { javaScriptType = provider.getJavaScriptTypesFactory() .getCachedType(newType, provider.getJarManager(), provider, lookupText); } else { javaScriptType = createNewTypeDeclaration(provider, type, lookupText); } } } } lastLookupName = lookupText; return javaScriptType; } /** * Creates a new JavaScriptType based on the String type * @param provider SourceCompletionProvider * @param type type of JavaScript type to create e.g. java.sql.Connection * @param text Text entered from the user to resolve the node. This will be null if resolveNode(AstNode node) is called * @return */ private JavaScriptType createNewTypeDeclaration( SourceCompletionProvider provider, String type, String text) { if (provider.getJavaScriptTypesFactory() != null) { ClassFile cf = provider.getJarManager().getClassEntry(type); TypeDeclaration newType; if (cf != null) { newType = provider.getJavaScriptTypesFactory() .createNewTypeDeclaration(cf, false); return provider.getJavaScriptTypesFactory() .getCachedType(newType, provider.getJarManager(), provider, text); } } return null; } /** * Method called if the lastJavaScriptType is not null. i.e. has gone through one iteration at least. * Resolves TypeDeclaration for parts of a variable past the first part. e.g. "".toString() //resolve toString() * In some circumstances this is useful to resolve this. e.g. for Custom Object completions * @param node Node to resolve * @return Type Declaration * */ protected TypeDeclaration resolveTypeFromLastJavaScriptType(AstNode node) { return null; } /** * Visit all nodes in the AstNode tree and all to a single list */ private static class VisitorAll implements NodeVisitor { private ArrayList all = new ArrayList<>(); @Override public boolean visit(AstNode node) { all.add(node); return true; } public ArrayList getAllNodes() { return all; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy