| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 |
| * |
| * 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 org.netbeans.modules.javascript2.model; |
| |
| import org.netbeans.modules.javascript2.model.spi.PathNodeVisitor; |
| import com.oracle.js.parser.ir.AccessNode; |
| import com.oracle.js.parser.ir.BinaryNode; |
| import com.oracle.js.parser.ir.CallNode; |
| import com.oracle.js.parser.ir.FunctionNode; |
| import com.oracle.js.parser.ir.IdentNode; |
| import com.oracle.js.parser.ir.IndexNode; |
| import com.oracle.js.parser.ir.LexicalContext; |
| import com.oracle.js.parser.ir.LiteralNode; |
| import com.oracle.js.parser.ir.Node; |
| import com.oracle.js.parser.ir.ObjectNode; |
| import com.oracle.js.parser.ir.TernaryNode; |
| import com.oracle.js.parser.ir.UnaryNode; |
| import com.oracle.js.parser.ir.visitor.NodeVisitor; |
| import com.oracle.js.parser.Lexer; |
| import com.oracle.js.parser.Token; |
| import com.oracle.js.parser.TokenType; |
| import static com.oracle.js.parser.TokenType.ADD; |
| import static com.oracle.js.parser.TokenType.DECPOSTFIX; |
| import static com.oracle.js.parser.TokenType.DECPREFIX; |
| import static com.oracle.js.parser.TokenType.INCPOSTFIX; |
| import static com.oracle.js.parser.TokenType.INCPREFIX; |
| import static com.oracle.js.parser.TokenType.NEW; |
| import static com.oracle.js.parser.TokenType.NOT; |
| import static com.oracle.js.parser.TokenType.SUB; |
| import com.oracle.js.parser.ir.JoinPredecessorExpression; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.netbeans.modules.javascript2.model.api.ModelUtils; |
| import org.netbeans.modules.javascript2.types.api.Type; |
| import org.netbeans.modules.javascript2.types.api.TypeUsage; |
| |
| /** |
| * |
| * @author Petr Pisl |
| */ |
| public class SemiTypeResolverVisitor extends PathNodeVisitor { |
| |
| private static final Logger LOGGER = Logger.getLogger(SemiTypeResolverVisitor.class.getName()); |
| |
| public static final String ST_START_DELIMITER = "@"; //NOI18N |
| public static final String ST_THIS = "@this;"; //NOI18N |
| public static final String ST_VAR = "@var;"; //NOI18N |
| public static final String ST_EXP = "@exp;"; //NOI18N |
| public static final String ST_PRO = "@pro;"; //NOI18N |
| public static final String ST_CALL = "@call;"; //NOI18N |
| public static final String ST_NEW = "@new;"; //NOI18N |
| public static final String ST_ARR = "@arr;"; //NOI18N |
| public static final String ST_ANONYM = "@anonym;"; //NOI18N |
| public static final String ST_WITH = "@with;"; //NOI18N |
| |
| private static final TypeUsage BOOLEAN_TYPE = new TypeUsage(Type.BOOLEAN, -1, true); |
| private static final TypeUsage STRING_TYPE = new TypeUsage(Type.STRING, -1, true); |
| private static final TypeUsage NUMBER_TYPE = new TypeUsage(Type.NUMBER, -1, true); |
| private static final TypeUsage ARRAY_TYPE = new TypeUsage(Type.ARRAY, -1, true); |
| private static final TypeUsage REGEXP_TYPE = new TypeUsage(Type.REGEXP, -1, true); |
| private static final TypeUsage UNDEFINED_TYPE = new TypeUsage(Type.UNDEFINED, -1, true); |
| |
| private Map<String, TypeUsage> result; |
| |
| private List<String> exp; |
| |
| private int typeOffset; |
| |
| private final FinderOffsetTypeVisitor offsetVisitor; |
| private ModelBuilder builder; |
| |
| public SemiTypeResolverVisitor() { |
| offsetVisitor = new FinderOffsetTypeVisitor(); |
| } |
| |
| public Set<TypeUsage> getSemiTypes(Node expression, ModelBuilder builder) { |
| this.builder = builder; |
| exp = new ArrayList<String>(); |
| result = new HashMap<String, TypeUsage>(); |
| reset(); |
| expression.accept(this); |
| add(exp, typeOffset == -1 ? offsetVisitor.findOffset(expression) : typeOffset, false); |
| return new HashSet<TypeUsage>(result.values()); |
| } |
| |
| private void reset() { |
| exp.clear(); |
| typeOffset = -1; |
| //visitedIndexNode = false; // we are not able to count arrays now |
| } |
| |
| private void add(List<String> exp, int offset, boolean resolved) { |
| if (/*visitedIndexNode ||*/ exp.isEmpty() |
| || (exp.size() == 1 && exp.get(0).startsWith(ST_START_DELIMITER) && !exp.get(0).startsWith(ST_ANONYM) |
| && !ST_THIS.equals(exp.get(0)))) { |
| return; |
| } |
| StringBuilder sb = new StringBuilder(); |
| if (!exp.get(0).startsWith(ST_START_DELIMITER)) { |
| if (exp.size() == 1) { |
| sb.append(ST_VAR); |
| } else { |
| sb.append(ST_EXP); |
| } |
| } |
| for (String part : exp) { |
| sb.append(part); |
| } |
| String type = sb.toString(); |
| if (!result.containsKey(type)) { |
| result.put(type, new TypeUsage(type, offset, resolved)); |
| } |
| } |
| |
| private void add(TypeUsage type) { |
| if (!result.containsKey(type.getType())) { |
| result.put(type.getType(), type); |
| } |
| } |
| |
| @Override |
| public Node leaveAccessNode(AccessNode accessNode) { |
| if (!exp.isEmpty()) { |
| String type = exp.get(exp.size() - 1); |
| String preType = exp.size() > 1 ? exp.get(exp.size() - 2) : ""; |
| if (!ST_THIS.equals(type) && !(preType.startsWith(ST_START_DELIMITER)) && !type.startsWith(ST_ANONYM)) { |
| exp.add(exp.size() - 1, ST_PRO); |
| } |
| } |
| exp.add(ST_PRO); |
| exp.add(accessNode.getProperty()); |
| return super.leaveAccessNode(accessNode); |
| } |
| |
| @Override |
| public boolean enterCallNode(CallNode callNode) { |
| addToPath(callNode); |
| if (!(callNode.getFunction() instanceof FunctionNode)) { |
| callNode.getFunction().accept(this); |
| } |
| if (exp.size() == 2 && ST_NEW.equals(exp.get(0))) { |
| return false; |
| } |
| if (callNode.getFunction() instanceof AccessNode) { |
| int size = exp.size(); |
| if (size > 1 && ST_PRO.equals(exp.get(size - 2))) { |
| exp.remove(size - 2); |
| } |
| } |
| else if (callNode.getFunction() instanceof FunctionNode) { |
| FunctionNode function = (FunctionNode) callNode.getFunction(); |
| String name = builder.getFunctionName(function); |
| // String name = function.getIdent().getName(); |
| add(new TypeUsage(ST_CALL + name, function.getStart(), false)); |
| return false; |
| } |
| if (exp.isEmpty()) { |
| exp.add(ST_CALL); |
| } else { |
| exp.add(exp.size() - 1, ST_CALL); |
| } |
| return false; |
| } |
| |
| @Override |
| public Node leaveCallNode(CallNode callNode) { |
| if (callNode.getFunction() instanceof AccessNode) { |
| int size = exp.size(); |
| if (size > 1 && ST_PRO.equals(exp.get(size - 2))) { |
| exp.remove(size - 2); |
| } |
| } |
| exp.add(exp.size() - 1, ST_CALL); |
| return super.leaveCallNode(callNode); |
| } |
| |
| @Override |
| public boolean enterUnaryNode(UnaryNode unaryNode) { |
| switch (Token.descType(unaryNode.getToken())) { |
| case NEW: |
| exp.add(ST_NEW); |
| SimpleNameResolver snr = new SimpleNameResolver(); |
| exp.add(snr.getFQN(unaryNode.getExpression(), builder)); |
| typeOffset = snr.getTypeOffset(); |
| return false; |
| case NOT: |
| add(BOOLEAN_TYPE); |
| return false; |
| case ADD: |
| case SUB: |
| case DECPREFIX: |
| case DECPOSTFIX: |
| case INCPREFIX: |
| case INCPOSTFIX: |
| add(NUMBER_TYPE); |
| return false; |
| default: |
| return super.enterUnaryNode(unaryNode); |
| } |
| } |
| |
| |
| @Override |
| public Node leaveUnaryNode(UnaryNode uNode) { |
| if (Token.descType(uNode.getToken()) == TokenType.NEW) { |
| int size = exp.size(); |
| if (size > 1 && ST_CALL.equals(exp.get(size - 2))) { |
| exp.remove(size - 2); |
| } |
| typeOffset = uNode.getExpression().getStart(); |
| if (exp.size() > 0) { |
| exp.add(exp.size() - 1, ST_NEW); |
| } else { |
| exp.add(ST_NEW); |
| } |
| } |
| return super.leaveUnaryNode(uNode); |
| } |
| |
| @Override |
| public boolean enterIdentNode(IdentNode iNode) { |
| String name = iNode.getPropertyName(); |
| if (ModelUtils.THIS.equals(name)) { //NOI18N |
| exp.add(ST_THIS); |
| } else if (Type.UNDEFINED.equals(name)){ |
| add(UNDEFINED_TYPE); |
| } else { |
| if (getPath().isEmpty()) { |
| exp.add(ST_VAR); |
| } |
| exp.add(name); |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean enterLiteralNode(LiteralNode lNode) { |
| Object value = lNode.getObject(); |
| TypeUsage type = null; |
| if (value instanceof Boolean) { |
| type = BOOLEAN_TYPE; |
| } else if (value instanceof String) { |
| type = STRING_TYPE; |
| } else if (value instanceof Integer |
| || value instanceof Float |
| || value instanceof Double) { |
| type = NUMBER_TYPE; |
| } else if (lNode instanceof LiteralNode.ArrayLiteralNode) { |
| type = ARRAY_TYPE; |
| } else if (value instanceof Lexer.RegexToken) { |
| type = REGEXP_TYPE; |
| } |
| |
| if (type != null) { |
| if (getPath().size() > 1 && getPreviousFromPath(2) instanceof CallNode) { |
| exp.add(type.getType()); |
| } else { |
| add(type); |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean enterTernaryNode(TernaryNode ternaryNode) { |
| ternaryNode.getTrueExpression().accept(this); |
| add(exp, offsetVisitor.findOffset(ternaryNode.getTrueExpression()), false); |
| reset(); |
| Node third = ternaryNode.getFalseExpression(); |
| third.accept(this); |
| int typeStart = offsetVisitor.findOffset(third); |
| add(exp, typeStart, false); |
| reset(); |
| return false; |
| } |
| |
| @Override |
| public boolean enterObjectNode(ObjectNode objectNode) { |
| int size = getPath().size(); |
| if (size > 0 && getPath().get(size - 1) instanceof AccessNode) { |
| exp.add(ST_ANONYM + objectNode.getStart()); |
| } else { |
| add(new TypeUsage(ST_ANONYM + objectNode.getStart(), objectNode.getStart(), false)); |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean enterIndexNode(IndexNode indexNode) { |
| addToPath(indexNode); |
| indexNode.getBase().accept(this); |
| int size = exp.size(); |
| if (size > 1 && ST_PRO.equals(exp.get(size - 2))) { |
| exp.remove(size - 2); |
| } |
| if (exp.isEmpty()) { |
| exp.add(ST_ARR); |
| } else { |
| boolean propertyAccess = false; |
| if (indexNode.getIndex() instanceof LiteralNode) { |
| LiteralNode lNode = (LiteralNode)indexNode.getIndex(); |
| if (lNode.isString()) { |
| exp.add(ST_PRO); |
| exp.add(lNode.getPropertyName()); |
| propertyAccess = true; |
| } |
| } |
| if (!propertyAccess) { |
| exp.add(exp.size() - 1, ST_ARR); |
| } |
| } |
| //add(exp, indexNode.getStart(), false); |
| //reset(); |
| return false; |
| } |
| |
| @Override |
| public boolean enterBinaryNode(BinaryNode binaryNode) { |
| if (!binaryNode.isAssignment()) { |
| if (isResultString(binaryNode)) { |
| add(STRING_TYPE); |
| return false; |
| } |
| if (isResultNumber(binaryNode)) { |
| add(NUMBER_TYPE); |
| return false; |
| } |
| TokenType tokenType = binaryNode.tokenType(); |
| if (tokenType == TokenType.EQ || tokenType == TokenType.EQ_STRICT |
| || tokenType == TokenType.NE || tokenType == TokenType.NE_STRICT |
| || tokenType == TokenType.GE || tokenType == TokenType.GT |
| || tokenType == TokenType.LE || tokenType == TokenType.LT |
| || tokenType == TokenType.AND ) { |
| if (getPath().isEmpty()) { |
| add(BOOLEAN_TYPE); |
| } |
| return false; |
| } |
| binaryNode.lhs().accept(this); |
| add(exp, offsetVisitor.findOffset(binaryNode.lhs()), false); |
| reset(); |
| binaryNode.rhs().accept(this); |
| add(exp, offsetVisitor.findOffset(binaryNode.rhs()), false); |
| reset(); |
| return false; |
| } |
| if (binaryNode.rhs() instanceof FunctionNode) { |
| binaryNode.lhs().accept(this); |
| return false; |
| } |
| if (binaryNode.isAssignment()) { |
| binaryNode.rhs().accept(this); |
| return false; |
| } |
| return super.enterBinaryNode(binaryNode); |
| } |
| |
| @Override |
| public boolean enterFunctionNode(FunctionNode functionNode) { |
| List<? extends Node> path = getPath(); |
| boolean functionType = true; |
| if (!path.isEmpty()) { |
| Node lastNode = path.get(path.size() - 1); |
| functionType = !(lastNode instanceof CallNode); |
| } |
| if (functionType) { |
| add(new TypeUsage(Type.FUNCTION, functionNode.getStart(), true)); |
| } |
| return false; |
| } |
| |
| private boolean isResultString(BinaryNode binaryNode) { |
| boolean bResult = false; |
| TokenType tokenType = binaryNode.tokenType(); |
| Node lhs = binaryNode.lhs(); |
| Node rhs = binaryNode.rhs(); |
| if (tokenType == TokenType.ADD |
| && ((lhs instanceof LiteralNode && ((LiteralNode) lhs).isString()) |
| || (rhs instanceof LiteralNode && ((LiteralNode) rhs).isString()))) { |
| bResult = true; |
| } else { |
| if (lhs instanceof JoinPredecessorExpression) { |
| lhs = ((JoinPredecessorExpression)lhs).getExpression(); |
| } |
| if (rhs instanceof JoinPredecessorExpression) { |
| rhs = ((JoinPredecessorExpression)rhs).getExpression(); |
| } |
| if (lhs instanceof BinaryNode) { |
| bResult = isResultString((BinaryNode) lhs); |
| } else if (rhs instanceof BinaryNode) { |
| bResult = isResultString((BinaryNode) rhs); |
| } |
| } |
| return bResult; |
| } |
| |
| private boolean isResultNumber(BinaryNode binaryNode) { |
| boolean bResult = false; |
| TokenType tokenType = binaryNode.tokenType(); |
| Node lhs = binaryNode.lhs(); |
| Node rhs = binaryNode.rhs(); |
| if ((tokenType == TokenType.BIT_OR || tokenType == TokenType.BIT_AND) |
| && ((lhs instanceof LiteralNode && ((LiteralNode) lhs).isNumeric()) |
| || (rhs instanceof LiteralNode && ((LiteralNode) rhs).isNumeric()))) { |
| bResult = true; |
| } else if (tokenType == TokenType.DIV || tokenType == TokenType.MUL |
| || tokenType == TokenType.SUB ){ |
| bResult = true; |
| } else { |
| if (lhs instanceof BinaryNode) { |
| bResult = isResultNumber((BinaryNode) lhs); |
| } else if (rhs instanceof BinaryNode) { |
| bResult = isResultNumber((BinaryNode) rhs); |
| } |
| } |
| return bResult; |
| } |
| |
| private static class SimpleNameResolver extends PathNodeVisitor { |
| private List<String> exp = new ArrayList<String>(); |
| private int typeOffset = -1; |
| private ModelBuilder builder; |
| |
| public String getFQN(Node expression, ModelBuilder builder) { |
| exp.clear(); |
| this.builder = builder; |
| expression.accept(this); |
| StringBuilder sb = new StringBuilder(); |
| for(String part : exp){ |
| sb.append(part); |
| sb.append('.'); |
| } |
| if (sb.length() == 0) { |
| LOGGER.log(Level.FINE, "New operator withouth name: {0}", expression.toString()); //NOI18N |
| return null; |
| } |
| return sb.toString().substring(0, sb.length() - 1); |
| } |
| |
| public int getTypeOffset() { |
| return typeOffset; |
| } |
| |
| @Override |
| public boolean enterAccessNode(AccessNode accessNode) { |
| if (typeOffset == -1) { |
| typeOffset = accessNode.getFinish() - accessNode.getProperty().length(); |
| } |
| accessNode.getBase().accept(this); |
| exp.add(accessNode.getProperty()); |
| return false; |
| } |
| |
| |
| @Override |
| public boolean enterCallNode(CallNode callNode) { |
| callNode.getFunction().accept(this); |
| return false; |
| } |
| |
| @Override |
| public boolean enterFunctionNode(FunctionNode functionNode) { |
| String name = builder.getFunctionName(functionNode); |
| exp.add(name); |
| if (typeOffset == -1) { |
| typeOffset = functionNode.getIdent().getStart(); |
| } |
| return false; |
| } |
| |
| |
| @Override |
| public boolean enterIndexNode(IndexNode indexNode) { |
| indexNode.getBase().accept(this); |
| return false; |
| } |
| |
| |
| |
| @Override |
| public boolean enterIdentNode(IdentNode identNode) { |
| exp.add(identNode.getName()); |
| if (typeOffset == -1) { |
| typeOffset = identNode.getStart(); |
| } |
| return super.enterIdentNode(identNode); |
| } |
| |
| // TRUFFLE |
| // @Override |
| // public Node enter(ReferenceNode referenceNode) { |
| // referenceNode.getReference().accept(this); |
| // return null; |
| // } |
| } |
| |
| private static class FinderOffsetTypeVisitor extends NodeVisitor { |
| private int typeOffset = -1; |
| |
| public FinderOffsetTypeVisitor() { |
| super(new LexicalContext()); |
| } |
| |
| int findOffset (Node expression) { |
| expression.accept(this); |
| return typeOffset; |
| } |
| |
| @Override |
| public boolean enterIdentNode(IdentNode identNode) { |
| typeOffset = identNode.getStart(); |
| return false; |
| } |
| |
| @Override |
| public boolean enterAccessNode(AccessNode accessNode) { |
| typeOffset = accessNode.getStart(); |
| return false; |
| } |
| } |
| } |