| /* |
| * 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 org.netbeans.modules.javascript2.model.api.ModelUtils; |
| import com.oracle.js.parser.Token; |
| import com.oracle.js.parser.ir.AccessNode; |
| import com.oracle.js.parser.ir.BinaryNode; |
| import com.oracle.js.parser.ir.Block; |
| import com.oracle.js.parser.ir.BlockStatement; |
| import com.oracle.js.parser.ir.CallNode; |
| import com.oracle.js.parser.ir.CatchNode; |
| import com.oracle.js.parser.ir.ClassNode; |
| import com.oracle.js.parser.ir.ExpressionStatement; |
| import com.oracle.js.parser.ir.ForNode; |
| 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.JoinPredecessorExpression; |
| import com.oracle.js.parser.ir.LabelNode; |
| 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.PropertyNode; |
| import com.oracle.js.parser.ir.ReturnNode; |
| import com.oracle.js.parser.ir.Statement; |
| import com.oracle.js.parser.ir.Symbol; |
| import com.oracle.js.parser.ir.TernaryNode; |
| import com.oracle.js.parser.ir.TryNode; |
| import com.oracle.js.parser.ir.UnaryNode; |
| import com.oracle.js.parser.ir.VarNode; |
| import com.oracle.js.parser.ir.WithNode; |
| import com.oracle.js.parser.ir.visitor.NodeVisitor; |
| import com.oracle.js.parser.TokenType; |
| import com.oracle.js.parser.ir.ExportClauseNode; |
| import com.oracle.js.parser.ir.ExportNode; |
| import com.oracle.js.parser.ir.ExportSpecifierNode; |
| import com.oracle.js.parser.ir.Expression; |
| import com.oracle.js.parser.ir.FromNode; |
| import com.oracle.js.parser.ir.ImportClauseNode; |
| import com.oracle.js.parser.ir.ImportNode; |
| import com.oracle.js.parser.ir.ImportSpecifierNode; |
| import com.oracle.js.parser.ir.NameSpaceImportNode; |
| import com.oracle.js.parser.ir.NamedImportsNode; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.Hashtable; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Stack; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.netbeans.modules.csl.api.Documentation; |
| import org.netbeans.modules.csl.api.ElementKind; |
| import org.netbeans.modules.csl.api.Modifier; |
| import org.netbeans.modules.csl.api.OffsetRange; |
| import org.netbeans.modules.javascript2.doc.api.JsDocumentationSupport; |
| import org.netbeans.modules.javascript2.lexer.api.JsTokenId; |
| import org.netbeans.modules.javascript2.doc.spi.DocParameter; |
| import org.netbeans.modules.javascript2.doc.spi.JsComment; |
| import org.netbeans.modules.javascript2.doc.spi.JsDocumentationHolder; |
| import org.netbeans.modules.javascript2.doc.spi.JsModifier; |
| import org.netbeans.modules.javascript2.types.api.DeclarationScope; |
| import org.netbeans.modules.javascript2.types.api.Identifier; |
| import org.netbeans.modules.javascript2.types.api.Type; |
| import org.netbeans.modules.javascript2.types.api.TypeUsage; |
| import static org.netbeans.modules.javascript2.model.ModelElementFactory.create; |
| import org.netbeans.modules.javascript2.model.api.JsArray; |
| import org.netbeans.modules.javascript2.model.api.JsElement; |
| import org.netbeans.modules.javascript2.model.api.JsFunction; |
| import org.netbeans.modules.javascript2.model.api.JsObject; |
| import org.netbeans.modules.javascript2.model.api.JsWith; |
| import org.netbeans.modules.javascript2.model.api.Model; |
| import org.netbeans.modules.javascript2.model.api.Occurrence; |
| import org.netbeans.modules.javascript2.model.spi.FunctionArgument; |
| import org.netbeans.modules.javascript2.model.spi.FunctionInterceptor; |
| import org.netbeans.modules.javascript2.types.spi.ParserResult; |
| import org.openide.filesystems.FileObject; |
| import org.openide.util.lookup.ServiceProvider; |
| |
| /** |
| * |
| * @author Petr Pisl |
| */ |
| public class ModelVisitor extends PathNodeVisitor implements ModelResolver { |
| |
| private static final Logger LOGGER = Logger.getLogger(ModelVisitor.class.getName()); |
| private static final boolean log = true; |
| |
| private final ModelBuilder modelBuilder; |
| private final OccurrenceBuilder occurrenceBuilder; |
| /** |
| * Keeps the name of the visited properties |
| */ |
| private final ParserResult parserResult; |
| |
| // keeps objects that are created as arguments of a function call |
| private final Stack<Collection<JsObjectImpl>> functionArgumentStack = new Stack<Collection<JsObjectImpl>>(); |
| private Map<FunctionInterceptor, Collection<FunctionCall>> functionCalls = null; |
| private final String scriptName; |
| private LexicalContext lc; |
| |
| private final static String BLOCK_OBJECT_NAME_PREFIX = "block-"; //NOI18N |
| |
| public ModelVisitor(ParserResult parserResult, OccurrenceBuilder occurrenceBuilder) { |
| super(); |
| FileObject fileObject = parserResult.getSnapshot().getSource().getFileObject(); |
| this.modelBuilder = new ModelBuilder(JsFunctionImpl.createGlobal( |
| fileObject, Integer.MAX_VALUE, parserResult.getSnapshot().getMimeType())); |
| this.occurrenceBuilder = occurrenceBuilder; |
| this.parserResult = parserResult; |
| this.scriptName = fileObject != null ? fileObject.getName().replace('.', '_') : ""; |
| lc = getLexicalContext(); |
| } |
| |
| @Override |
| public void init() { |
| final FunctionNode root = parserResult.getLookup().lookup(FunctionNode.class); |
| if (root != null) { |
| root.accept(this); |
| } |
| } |
| |
| public JsObject getGlobalObject() { |
| return modelBuilder.getGlobal(); |
| } |
| |
| @Override |
| public void processCalls( |
| org.netbeans.modules.javascript2.model.spi.ModelElementFactory elementFactory, |
| Map<String, Map<Integer, List<TypeUsage>>> returnTypesFromFrameworks) { |
| final Map<FunctionInterceptor, Collection<ModelVisitor.FunctionCall>> calls = getCallsForProcessing(); |
| if (calls != null && !calls.isEmpty()) { |
| for (Map.Entry<FunctionInterceptor, Collection<ModelVisitor.FunctionCall>> entry : calls.entrySet()) { |
| Collection<ModelVisitor.FunctionCall> fncCalls = entry.getValue(); |
| if (fncCalls != null && !fncCalls.isEmpty()) { |
| for (ModelVisitor.FunctionCall call : fncCalls) { |
| Collection<TypeUsage> returnTypes = entry.getKey().intercept(parserResult.getSnapshot(), call.getName(), |
| getGlobalObject(), call.getScope(), elementFactory, call.getArguments()); |
| if (returnTypes != null) { |
| Map<Integer, List<TypeUsage>> functionCalls = returnTypesFromFrameworks.get(call.getName()); |
| if (functionCalls == null) { |
| functionCalls = new HashMap<Integer, List<TypeUsage>>(); |
| returnTypesFromFrameworks.put(call.getName(), functionCalls); |
| } |
| List<TypeUsage> types = functionCalls.get(call.getCallOffset()); |
| if (types == null) { |
| types = new ArrayList<>(); |
| functionCalls.put(new Integer(call.getCallOffset()), types); |
| } |
| for (TypeUsage type: returnTypes) { |
| if (!types.contains(type)) { |
| types.add(type); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public List<Identifier> getASTNodeName(final Object astNode) { |
| if (astNode instanceof Node) { |
| return getNodeName((Node)astNode, parserResult); |
| } |
| return Collections.emptyList(); |
| } |
| |
| |
| @Override |
| public boolean enterAccessNode(AccessNode accessNode) { |
| BinaryNode node = getPath().get(getPath().size() - 1) instanceof BinaryNode |
| ? (BinaryNode)getPath().get(getPath().size() - 1) : null; |
| if (!(node != null && node.tokenType() == TokenType.ASSIGN)) { |
| if (accessNode.getBase() instanceof IdentNode && ModelUtils.THIS.equals(((IdentNode)accessNode.getBase()).getName())) { //NOI18N |
| String iNode = accessNode.getProperty(); |
| JsObject current = modelBuilder.getCurrentDeclarationFunction(); |
| JsObject property = current.getProperty(iNode); |
| if (property == null && current.getParent() != null && (current.getParent().getJSKind() == JsElement.Kind.CONSTRUCTOR |
| || current.getParent().getJSKind() == JsElement.Kind.OBJECT)) { |
| current = current.getParent(); |
| property = current.getProperty(iNode); |
| if (property == null && ModelUtils.PROTOTYPE.equals(current.getName())) { |
| current = current.getParent(); |
| property = current.getProperty(iNode); |
| } |
| } |
| if (property == null && current.getParent() == null) { |
| // probably we are in global space and there is used this |
| property = modelBuilder.getGlobal().getProperty(iNode); |
| } |
| if (property != null && !property.getModifiers().contains(Modifier.PRIVATE)) { |
| // we don't want to add occurrences for cases like var buf = this.buf. See issue #267694 |
| ((JsObjectImpl)property).addOccurrence(new OffsetRange(accessNode.getFinish() - iNode.length(), accessNode.getFinish())); |
| } |
| } |
| } |
| return super.enterAccessNode(accessNode); |
| } |
| |
| @Override |
| public Node leaveAccessNode(AccessNode accessNode) { |
| createJsObject(accessNode, parserResult, modelBuilder); |
| return super.leaveAccessNode(accessNode); |
| } |
| |
| @Override |
| public boolean enterBlock(Block block) { |
| DeclarationScopeImpl blockScope = (DeclarationScopeImpl)modelBuilder.getCurrentDeclarationScope().getProperty(BLOCK_OBJECT_NAME_PREFIX + block.getStart()); |
| if ( blockScope!= null) { |
| // in this block there are a declarations that we are interested in. |
| modelBuilder.setCurrentObject(blockScope); |
| } |
| return super.enterBlock(block); |
| } |
| |
| @Override |
| public Node leaveBlock(Block block) { |
| DeclarationScopeImpl currentScope = modelBuilder.getCurrentDeclarationScope(); |
| if (currentScope.getJSKind() == JsElement.Kind.BLOCK && currentScope.getName().equals(BLOCK_OBJECT_NAME_PREFIX + block.getStart())) { |
| // removing the block as declaration scope from |
| modelBuilder.reset(); |
| } |
| return super.leaveBlock(block); |
| } |
| |
| @Override |
| public boolean enterBinaryNode(BinaryNode binaryNode) { |
| Node lhs = binaryNode.lhs(); |
| Node rhs = binaryNode.rhs(); |
| if (lhs instanceof LiteralNode.ArrayLiteralNode && binaryNode.tokenType() == TokenType.ASSIGN) { |
| // case of destructuring assgnment like [a,b] = .... |
| LiteralNode.ArrayLiteralNode lan = (LiteralNode.ArrayLiteralNode)lhs; |
| if (rhs instanceof LiteralNode.ArrayLiteralNode) { |
| // case [a, b] = [1, 2] |
| LiteralNode.ArrayLiteralNode ran = (LiteralNode.ArrayLiteralNode)rhs; |
| List<Expression> lExpressions = lan.getElementExpressions(); |
| List<Expression> rExpressions = ran.getElementExpressions(); |
| for (int i = 0; i < lExpressions.size(); i++) { |
| Expression lExpression = lExpressions.get(i); |
| if (i < rExpressions.size()) { |
| Expression rExpression = rExpressions.get(i); |
| processBinaryNode(lExpression, rExpression, TokenType.ASSIGN); |
| } else { |
| break; |
| } |
| } |
| } else { |
| // other cases |
| rhs.accept(this); |
| } |
| } else if (lhs instanceof ObjectNode && binaryNode.tokenType() == TokenType.ASSIGN) { |
| // cases {a, b} = ... |
| ObjectNode lObjectNode = (ObjectNode)lhs; |
| JsObjectImpl rObject = null; |
| |
| // prepare variables that are available in the current scope for later usage |
| DeclarationScopeImpl scope = modelBuilder.getCurrentDeclarationScope(); |
| Collection<? extends JsObject> variables = ModelUtils.getVariables(scope); |
| |
| if (rhs instanceof ObjectNode) { |
| // case {a, b} = {a:1, b:2} |
| // the rhs object we have to put to the model as anonymous object. At least will be colored in the right way |
| ObjectNode rObjectNode = (ObjectNode)rhs; |
| rObject = ModelElementFactory.createAnonymousObject(parserResult, rObjectNode, modelBuilder); |
| modelBuilder.setCurrentObject(rObject); |
| rObject.setJsKind(JsElement.Kind.OBJECT_LITERAL); |
| if (!functionArgumentStack.isEmpty()) { |
| functionArgumentStack.peek().add(rObject); |
| } |
| for (PropertyNode rPropertyNode : rObjectNode.getElements()) { |
| rPropertyNode.accept(this); |
| } |
| modelBuilder.reset(); |
| } else { |
| rhs.accept(this); |
| if (rhs instanceof IdentNode) { |
| // we will try to find the right object literal |
| rObject = (JsObjectImpl)ModelUtils.getScopeVariable(scope, ((IdentNode)rhs).getName()); |
| } |
| } |
| if (rObject != null) { |
| // find variables that are mentioned on the left site and assign the types from |
| // property with the same name from the right site |
| for (PropertyNode lPropertyNode : lObjectNode.getElements()) { |
| String variableName = null; |
| if (isKeyAndValueEquals(lPropertyNode)) { |
| // case {p:var1, q:var2} = {p:1, q:2} or {p:var1, q:var2} = objectLiteral |
| variableName = lPropertyNode.getKeyName(); |
| lPropertyNode.accept(this); |
| } else if (lPropertyNode.getValue() instanceof IdentNode) { |
| variableName = ((IdentNode)lPropertyNode.getValue()).getName(); |
| } else if (lPropertyNode.getValue() instanceof BinaryNode) { |
| BinaryNode bNode = (BinaryNode)lPropertyNode.getValue(); |
| if (bNode.tokenType() == TokenType.ASSIGN && bNode.lhs() instanceof IdentNode) { |
| // the default parameter {a=10, b=20} = .... |
| variableName = ((IdentNode)bNode.lhs()).getName(); |
| } |
| } |
| if (variableName != null) { |
| JsObject variable = ModelUtils.getScopeVariable(scope, variableName); |
| JsObject rProperty = rObject.getProperty(lPropertyNode.getKeyName()); |
| if (variable != null && rProperty != null) { |
| // copy types from the properties |
| for (TypeUsage assignment : rProperty.getAssignments()){ |
| variable.addAssignment(assignment, rProperty.getOffset()); |
| } |
| if (!isKeyAndValueEquals(lPropertyNode)) { |
| // mark occurrences in case {p:var1, q:var2} = {p:1, q:2} or {p:var1, q:var2} = objectLiteral |
| rProperty.addOccurrence(new OffsetRange(lPropertyNode.getStart(), lPropertyNode.getStart() + lPropertyNode.getKeyName().length())); |
| } |
| } |
| } |
| } |
| return false; |
| } |
| }else { |
| processBinaryNode(lhs, rhs, binaryNode.tokenType()); |
| } |
| return super.enterBinaryNode(binaryNode); |
| } |
| |
| private boolean isKeyAndValueEquals(PropertyNode pNode) { |
| if (pNode.getKey() instanceof IdentNode && pNode.getValue() instanceof IdentNode) { |
| IdentNode key = (IdentNode)pNode.getKey(); |
| IdentNode value = (IdentNode)pNode.getValue(); |
| return key.getName().equals(value.getName()) && key.getStart() == value.getStart(); |
| } |
| if (pNode.getKey() instanceof IdentNode && pNode.getValue() instanceof BinaryNode |
| && ((BinaryNode)pNode.getValue()).tokenType() == TokenType.ASSIGN && ((BinaryNode)pNode.getValue()).lhs() instanceof IdentNode) { |
| IdentNode key = (IdentNode)pNode.getKey(); |
| IdentNode value = (IdentNode)((BinaryNode)pNode.getValue()).lhs(); |
| return key.getName().equals(value.getName()) && key.getStart() == value.getStart(); |
| } |
| return false; |
| } |
| |
| private void processBinaryNode(Node lhs, Node rhs, TokenType tokenType) { |
| if (tokenType == TokenType.ASSIGN |
| && !(/*rhs instanceof ReferenceNode ||*/ rhs instanceof ObjectNode) |
| && (lhs instanceof AccessNode || lhs instanceof IdentNode || lhs instanceof IndexNode)) { |
| // TODO probably not only assign |
| JsObjectImpl parent = modelBuilder.getCurrentDeclarationFunction(); |
| if (parent == null) { |
| // should not happened |
| return; |
| } |
| String fieldName = null; |
| if (lhs instanceof AccessNode) { |
| AccessNode aNode = (AccessNode)lhs; |
| JsObjectImpl property = null; |
| List<Identifier> fqName = getName(aNode, parserResult); |
| if (fqName != null && ModelUtils.THIS.equals(fqName.get(0).getName())) { //NOI18N |
| // a usage of field |
| fieldName = aNode.getProperty(); |
| if (rhs instanceof IdentNode) { |
| // resolve occurrence of the indent node sooner, then is created the field. |
| addOccurrence((IdentNode)rhs, fieldName); |
| } |
| property = (JsObjectImpl)createJsObject(aNode, parserResult, modelBuilder); |
| } else { |
| // probably a property of an object |
| if (fqName != null) { |
| property = ModelUtils.getJsObject(modelBuilder, fqName, true); |
| if (property.getParent().getJSKind().isFunction() && !property.getModifiers().contains(Modifier.STATIC)) { |
| property.getModifiers().add(Modifier.STATIC); |
| } |
| } |
| } |
| if (property != null) { |
| String parameter = null; |
| JsFunction function = (JsFunction)modelBuilder.getCurrentDeclarationFunction(); |
| if(rhs instanceof IdentNode) { |
| IdentNode iNode = (IdentNode)rhs; |
| if(/*function.getProperty(rhs.getName()) == null &&*/ function.getParameter(iNode.getName()) != null) { |
| parameter = "@param;" + function.getFullyQualifiedName() + ":" + iNode.getName(); //NOI18N |
| } |
| } |
| Collection<TypeUsage> types; |
| if (parameter == null) { |
| types = ModelUtils.resolveSemiTypeOfExpression(modelBuilder, rhs); |
| Collection<TypeUsage> correctedTypes = new ArrayList<TypeUsage>(types.size()); |
| for (TypeUsage type : types) { |
| String typeName = type.getType(); |
| // we have to check, whether a variable comming from resolvedr is not a parameter of function where the binary node is |
| if (typeName.startsWith(SemiTypeResolverVisitor.ST_VAR)) { |
| String varName = typeName.substring(SemiTypeResolverVisitor.ST_VAR.length()); |
| if (function.getParameter(varName) != null) { |
| correctedTypes.add(new TypeUsage("@param;" + function.getFullyQualifiedName() + ":" + varName, type.getOffset(), false)); |
| } else { |
| correctedTypes.add(type); |
| } |
| } else { |
| correctedTypes.add(type); |
| } |
| } |
| types = correctedTypes; |
| } else { |
| types = new ArrayList<TypeUsage>(); |
| types.add(new TypeUsage(parameter, rhs.getStart(), false)); |
| } |
| |
| for (TypeUsage type : types) { |
| // plus 5 due to the this. |
| property.addAssignment(type, lhs.getStart() + 5); |
| } |
| } |
| |
| } else { |
| JsObject lObject = null; |
| boolean indexNodeReferProperty = false; |
| int assignmentOffset = lhs.getFinish(); |
| if (lhs instanceof IndexNode) { |
| IndexNode iNode = (IndexNode)lhs; |
| if (iNode.getBase() instanceof IdentNode) { |
| lObject = processLhs(ModelElementFactory.create(parserResult, (IdentNode)iNode.getBase()), parent, false); |
| assignmentOffset = iNode.getFinish(); |
| } |
| if (lObject != null && iNode.getIndex() instanceof LiteralNode) { |
| LiteralNode lNode = (LiteralNode)iNode.getIndex(); |
| if (lNode.isString()) { |
| Identifier newPropName = ModelElementFactory.create(parserResult, lNode); |
| if (newPropName != null) { |
| indexNodeReferProperty = true; |
| if (lObject.getProperty(lNode.getString()) == null) { |
| JsObject newProperty = new JsObjectImpl(lObject, newPropName, newPropName.getOffsetRange(), true, parserResult.getSnapshot().getMimeType(), null); |
| lObject.addProperty(newPropName.getName(), newProperty); |
| assignmentOffset = lNode.getFinish(); |
| } |
| lObject = processLhs(newPropName, lObject, true); |
| } |
| } |
| } |
| } else if (lhs instanceof IdentNode) { |
| lObject = processLhs(ModelElementFactory.create(parserResult, (IdentNode)lhs), parent, true); |
| } |
| |
| if (lObject != null && !(rhs instanceof FunctionNode)) { |
| Collection<TypeUsage> types = ModelUtils.resolveSemiTypeOfExpression(modelBuilder, rhs); |
| if (lhs instanceof IndexNode && lObject instanceof JsArrayImpl) { |
| ((JsArrayImpl)lObject).addTypesInArray(types); |
| } else { |
| boolean isIndexNode = lhs instanceof IndexNode; |
| if (!isIndexNode || (isIndexNode && indexNodeReferProperty)) { |
| for (TypeUsage type : types) { |
| lObject.addAssignment(type, assignmentOffset); |
| } |
| } |
| } |
| } |
| } |
| if (fieldName == null && rhs instanceof IdentNode) { |
| addOccurence((IdentNode)rhs, false); |
| } |
| } else if(tokenType != TokenType.ASSIGN |
| || (tokenType == TokenType.ASSIGN && lhs instanceof IndexNode)) { |
| if (lhs instanceof IdentNode) { |
| addOccurence((IdentNode)lhs, tokenType == TokenType.ASSIGN); |
| } |
| if (rhs instanceof IdentNode) { |
| addOccurence((IdentNode)rhs, false); |
| } |
| } |
| } |
| |
| @Override |
| public Node leaveBinaryNode(BinaryNode binaryNode) { |
| Node lhs = binaryNode.lhs(); |
| Node rhs = binaryNode.rhs(); |
| if (lhs instanceof IdentNode && rhs instanceof BinaryNode) { |
| Node rlhs = ((BinaryNode)rhs).lhs(); |
| if (rlhs instanceof IdentNode) { |
| JsObject origFunction = modelBuilder.getCurrentDeclarationFunction().getProperty(((IdentNode)rlhs).getName()); |
| if (origFunction != null && origFunction.getJSKind().isFunction()) { |
| JsObject refFunction = modelBuilder.getCurrentDeclarationFunction().getProperty(((IdentNode)lhs).getName()); |
| if (refFunction != null && !refFunction.getJSKind().isFunction()) { |
| JsFunctionReference newReference = new JsFunctionReference(refFunction.getParent(), refFunction.getDeclarationName(), (JsFunction)origFunction, true, origFunction.getModifiers()); |
| refFunction.getParent().addProperty(newReference.getName(), newReference); |
| } |
| } |
| } |
| } |
| return super.leaveBinaryNode(binaryNode); |
| } |
| |
| @Override |
| public boolean enterCallNode(CallNode callNode) { |
| functionArgumentStack.push(new ArrayList<JsObjectImpl>(3)); |
| if (callNode.getFunction() instanceof IdentNode) { |
| IdentNode iNode = (IdentNode)callNode.getFunction(); |
| addOccurence(iNode, false, true); |
| } |
| for (Node argument : callNode.getArgs()) { |
| if (argument instanceof IdentNode) { |
| addOccurence((IdentNode) argument, false); |
| } |
| } |
| processObjectPropertyAssignment(callNode); |
| return super.enterCallNode(callNode); |
| } |
| |
| @Override |
| public Node leaveCallNode(CallNode callNode) { |
| Collection<JsObjectImpl> functionArguments = functionArgumentStack.pop(); |
| |
| Node function = callNode.getFunction(); |
| if (function instanceof AccessNode || function instanceof IdentNode) { |
| List<Identifier> funcName; |
| if (function instanceof AccessNode) { |
| funcName = getName((AccessNode) function, parserResult); |
| } else { |
| funcName = new ArrayList<Identifier>(); |
| funcName.add(new Identifier(((IdentNode) function).getName(), ((IdentNode) function).getStart())); |
| } |
| if (funcName != null) { |
| StringBuilder sb = new StringBuilder(); |
| for (Identifier identifier : funcName) { |
| sb.append(identifier.getName()); |
| sb.append("."); |
| } |
| if (functionCalls == null) { |
| functionCalls = new LinkedHashMap<FunctionInterceptor, Collection<FunctionCall>>(); |
| } |
| |
| String name = sb.substring(0, sb.length() - 1); |
| List<FunctionInterceptor> interceptorsToUse = new ArrayList<FunctionInterceptor>(); |
| for (FunctionInterceptor interceptor : ModelExtender.getDefault().getFunctionInterceptors()) { |
| if (interceptor.getNamePattern().matcher(name).matches()) { |
| interceptorsToUse.add(interceptor); |
| } |
| } |
| |
| |
| for (FunctionInterceptor interceptor : interceptorsToUse) { |
| Collection<FunctionArgument> funcArg = new ArrayList<FunctionArgument>(); |
| for (int i = 0; i < callNode.getArgs().size(); i++) { |
| Node argument = callNode.getArgs().get(i); |
| createFunctionArgument(argument, i, functionArguments, funcArg); |
| } |
| Collection<FunctionCall> calls = functionCalls.get(interceptor); |
| if (calls == null) { |
| calls = new ArrayList<FunctionCall>(); |
| functionCalls.put(interceptor, calls); |
| } |
| int callOffset = callNode.getFunction().getStart(); |
| if (callNode.getFunction() instanceof AccessNode) { |
| AccessNode anode = (AccessNode)callNode.getFunction(); |
| callOffset = anode.getFinish() - anode.getProperty().length(); |
| } |
| calls.add(new FunctionCall(name, modelBuilder.getCurrentDeclarationScope(), funcArg, callOffset)); |
| } |
| } |
| } |
| return super.leaveCallNode(callNode); |
| } |
| |
| private void createFunctionArgument(Node argument, int position, Collection<JsObjectImpl> functionArguments, |
| Collection<FunctionArgument> result) { |
| |
| if (argument instanceof LiteralNode) { |
| LiteralNode ln = (LiteralNode)argument; |
| if (ln.isString()) { |
| result.add(FunctionArgumentAccessor.getDefault().createForString( |
| position, argument.getStart(), ln.getString())); |
| } else if (ln instanceof LiteralNode.ArrayLiteralNode) { |
| for (JsObjectImpl jsObject: functionArguments) { |
| if (jsObject.getOffset() == argument.getStart()) { |
| result.add(FunctionArgumentAccessor.getDefault().createForArray(position, jsObject.getOffset(), jsObject)); |
| break; |
| } |
| } |
| } |
| } else if (argument instanceof ObjectNode) { |
| for (JsObjectImpl jsObject: functionArguments) { |
| if (jsObject.getOffset() == argument.getStart()) { |
| result.add(FunctionArgumentAccessor.getDefault().createForAnonymousObject(position, jsObject.getOffset(), jsObject)); |
| break; |
| } |
| } |
| } else if (argument instanceof AccessNode) { |
| List<String> strFqn = new ArrayList<String>(); |
| if(fillName((AccessNode) argument, strFqn)) { |
| result.add(FunctionArgumentAccessor.getDefault().createForReference( |
| position, argument.getStart(), strFqn)); |
| } else { |
| result.add(FunctionArgumentAccessor.getDefault().createForUnknown(position)); |
| } |
| } else if (argument instanceof IndexNode) { |
| List<String> strFqn = new ArrayList<String>(); |
| if(fillName((IndexNode) argument, strFqn)) { |
| result.add(FunctionArgumentAccessor.getDefault().createForReference( |
| position, argument.getStart(), strFqn)); |
| } else { |
| result.add(FunctionArgumentAccessor.getDefault().createForUnknown(position)); |
| } |
| } else if (argument instanceof IdentNode) { |
| IdentNode in = (IdentNode) argument; |
| String inName = in.getName(); |
| result.add(FunctionArgumentAccessor.getDefault().createForReference( |
| position, argument.getStart(), |
| Collections.singletonList(inName))); |
| } else if (argument instanceof UnaryNode) { |
| // we are handling foo(new Something()) |
| UnaryNode un = (UnaryNode) argument; |
| if (un.tokenType() == TokenType.NEW) { |
| CallNode constructor = (CallNode) un.getExpression(); |
| createFunctionArgument(constructor.getFunction(), position, functionArguments, result); |
| } |
| } else if (argument instanceof FunctionNode) { |
| FunctionNode reference = (FunctionNode) argument; |
| result.add(FunctionArgumentAccessor.getDefault().createForReference( |
| position, argument.getStart(), |
| Collections.singletonList(modelBuilder.getFunctionName(reference)))); |
| } else { |
| result.add(FunctionArgumentAccessor.getDefault().createForUnknown(position)); |
| } |
| } |
| |
| @Override |
| public boolean enterCatchNode(CatchNode catchNode) { |
| Identifier exception = ModelElementFactory.create(parserResult, catchNode.getException()); |
| if (exception != null) { |
| DeclarationScopeImpl inScope = modelBuilder.getCurrentDeclarationScope(); |
| CatchBlockImpl catchBlock = new CatchBlockImpl(inScope, exception, |
| new OffsetRange(catchNode.getStart(), catchNode.getFinish()), parserResult.getSnapshot().getMimeType()); |
| inScope.addDeclaredScope(catchBlock); |
| modelBuilder.setCurrentObject(catchBlock); |
| } |
| return super.enterCatchNode(catchNode); |
| } |
| |
| @Override |
| public Node leaveCatchNode(CatchNode catchNode) { |
| if (!EmbeddingHelper.containsGeneratedIdentifier(catchNode.getException().getName())) { |
| modelBuilder.reset(); |
| } |
| return super.leaveCatchNode(catchNode); |
| } |
| |
| |
| |
| @Override |
| public boolean enterClassNode(ClassNode node) { |
| IdentNode cnIdent = node.getIdent(); |
| Node lastNode = getPreviousFromPath(1); |
| VarNode varNode = (lastNode instanceof VarNode) ? (VarNode)lastNode : null; |
| JsObject parent = modelBuilder.getCurrentObject(); |
| JsObjectImpl classObject = null; |
| Identifier className = null; |
| Identifier refName = null; |
| if ((varNode != null && cnIdent != null && varNode.getName().getName().equals(cnIdent.getName())) |
| // case1: var Polygon = class Polygon {} |
| // case2: class Polygon {} |
| || (varNode != null && !varNode.isExport() && cnIdent == null) ) { |
| // case 3: var Polygon = class{} |
| // we create just one object |
| className = ModelElementFactory.create(parserResult, varNode.getName()); |
| } else if (varNode != null && cnIdent != null && !varNode.getName().getName().equals(cnIdent.getName())) { |
| // case 4: var Polygon = class PolygonOther{} |
| // The PolygonOther is available just for the inside the class. |
| className = ModelElementFactory.create(parserResult, varNode.getName()); |
| refName = ModelElementFactory.create(parserResult, cnIdent); |
| } else if (varNode == null && cnIdent != null) { |
| className = ModelElementFactory.create(parserResult, cnIdent); |
| } |
| |
| if (className != null) { |
| classObject = new JsObjectImpl(parent, className, new OffsetRange(node.getStart(), node.getFinish()), true, parent.getMimeType(), parent.getSourceLabel()); |
| parent.addProperty(className.getName(), classObject); |
| classObject.setJsKind(JsElement.Kind.CLASS); |
| if (refName != null) { |
| JsObjectReference reference = new JsObjectReference(classObject, refName, classObject, true, EnumSet.of(Modifier.PRIVATE)); |
| classObject.addProperty(refName.getName(), reference); |
| reference.addOccurrence(refName.getOffsetRange()); |
| } |
| } |
| if (classObject != null) { |
| if (node.getClassHeritage() != null) { |
| Expression classHeritage = node.getClassHeritage(); |
| if (classHeritage instanceof IdentNode) { |
| JsObjectImpl proto = new JsObjectImpl(classObject, ModelUtils.PROTOTYPE, true, OffsetRange.NONE, EnumSet.of(Modifier.PUBLIC), classObject.getMimeType(), classObject.getSourceLabel()); |
| classObject.addProperty(ModelUtils.PROTOTYPE, proto); |
| IdentNode type = (IdentNode)classHeritage; |
| proto.addAssignment(new TypeUsage(type.getName(), type.getStart(), true), type.getStart()); |
| } |
| |
| } |
| modelBuilder.setCurrentObject(classObject); |
| // visit constructor |
| node.getConstructor().accept(this); |
| // visit rest of declaration |
| for (PropertyNode element : node.getClassElements()) { |
| element.accept(this); |
| } |
| modelBuilder.reset(); |
| } |
| return false; |
| } |
| |
| |
| |
| @Override |
| public boolean enterIdentNode(IdentNode identNode) { |
| Node previousVisited = getPath().get(getPath().size() - 1); |
| if(!(previousVisited instanceof AccessNode |
| || previousVisited instanceof VarNode |
| || previousVisited instanceof BinaryNode |
| || previousVisited instanceof PropertyNode |
| || previousVisited instanceof CatchNode |
| || previousVisited instanceof LabelNode)) { |
| //boolean declared = previousVisited instanceof CatchNode; |
| addOccurence(identNode, false); |
| } |
| return super.enterIdentNode(identNode); |
| } |
| |
| @Override |
| public Node leaveIndexNode(IndexNode indexNode) { |
| if (indexNode.getIndex() instanceof LiteralNode) { |
| Node base = indexNode.getBase(); |
| JsObjectImpl parent = null; |
| if (base instanceof AccessNode) { |
| parent = (JsObjectImpl)createJsObject((AccessNode)base, parserResult, modelBuilder); |
| } else if (base instanceof IdentNode) { |
| IdentNode iNode = (IdentNode)base; |
| if (!ModelUtils.THIS.equals(iNode.getName())) { |
| Identifier parentName = ModelElementFactory.create(parserResult, iNode); |
| if (parentName != null) { |
| List<Identifier> fqName = new ArrayList<Identifier>(); |
| fqName.add(parentName); |
| parent = ModelUtils.getJsObject(modelBuilder, fqName, false); |
| parent.addOccurrence(parentName.getOffsetRange()); |
| } |
| }/* else { |
| JsObject current = modelBuilder.getCurrentDeclarationFunction(); |
| fromAN = (JsObjectImpl)resolveThis(current); |
| }*/ |
| } |
| if (parent != null && indexNode.getIndex() instanceof LiteralNode) { |
| LiteralNode literal = (LiteralNode)indexNode.getIndex(); |
| if (literal.isString()) { |
| String index = literal.getPropertyName(); |
| JsObjectImpl property = (JsObjectImpl)parent.getProperty(index); |
| if (property != null) { |
| property.addOccurrence(new OffsetRange(indexNode.getIndex().getStart(), indexNode.getIndex().getFinish())); |
| } else { |
| Identifier name = ModelElementFactory.create(parserResult, (LiteralNode)indexNode.getIndex()); |
| if (name != null) { |
| property = new JsObjectImpl(parent, name, name.getOffsetRange(), parserResult.getSnapshot().getMimeType(), null); |
| parent.addProperty(name.getName(), property); |
| } |
| } |
| } |
| } |
| } |
| return super.leaveIndexNode(indexNode); |
| } |
| |
| @Override |
| public boolean enterImportNode(ImportNode iNode) { |
| ImportClauseNode iClause = iNode.getImportClause(); |
| FromNode from = iNode.getFrom(); |
| if (iClause != null) { |
| IdentNode defaultBinding = iClause.getDefaultBinding(); |
| NameSpaceImportNode nameSpaceImport = iClause.getNameSpaceImport(); |
| NamedImportsNode namedImports = iClause.getNamedImports(); |
| if (defaultBinding != null) { |
| Identifier importedAs = create(parserResult, defaultBinding); |
| // create a variable, which have assignment the same variable name from FromNode |
| JsObjectImpl property = createVariableFromImport(importedAs); |
| property.addAssignment(new TypeUsage(importedAs.getName()), importedAs.getOffsetRange().getEnd()); |
| } |
| if (nameSpaceImport != null) { |
| Identifier importedAs = create(parserResult, nameSpaceImport.getBindingIdentifier()); |
| // create a variable, which has all properties from FromNode module |
| createVariableFromImport(importedAs); |
| } |
| if (namedImports != null && namedImports.getImportSpecifiers() != null) { |
| List<ImportSpecifierNode> importSpecifiers = namedImports.getImportSpecifiers(); |
| for (ImportSpecifierNode importSpecifier : importSpecifiers) { |
| Identifier importedAs = create(parserResult, importSpecifier.getBindingIdentifier()); |
| JsObjectImpl property; |
| property = createVariableFromImport(importedAs); |
| property.addOccurrence(importedAs.getOffsetRange()); |
| if (importSpecifier.getIdentifier() != null) { |
| Identifier inModuleName = create(parserResult, importSpecifier.getIdentifier()); |
| property.addAssignment(new TypeUsage(inModuleName.getName()), inModuleName.getOffsetRange().getEnd()); |
| } |
| |
| } |
| } |
| } |
| return false; |
| } |
| |
| private JsObjectImpl createVariableFromImport(Identifier name) { |
| JsFunctionImpl scope = modelBuilder.getCurrentDeclarationFunction(); |
| JsObject existingProp = scope.getProperty(name.getName()); |
| JsObjectImpl property = new JsObjectImpl(scope, name, name.getOffsetRange(), true, scope.getMimeType(), scope.getSourceLabel()); |
| if (existingProp != null) { |
| ModelUtils.copyOccurrences(existingProp, property); |
| } |
| scope.addProperty(name.getName(), property); |
| return property; |
| } |
| |
| @Override |
| public boolean enterExportNode(ExportNode exportNode) { |
| final ExportClauseNode exportClause = exportNode.getExportClause(); |
| final FromNode from = exportNode.getFrom(); |
| final Expression expression = exportNode.getExpression(); |
| |
| if (exportClause != null) { |
| for (ExportSpecifierNode esNode :exportClause.getExportSpecifiers()) { |
| IdentNode exported = esNode.getExportIdentifier(); |
| IdentNode local = esNode.getIdentifier(); |
| JsObjectImpl property = (JsObjectImpl)modelBuilder.getCurrentDeclarationFunction().getProperty(local.getName()); |
| if (exported == null) { |
| if (property == null) { |
| property = createVariableFromImport(create(parserResult, local)); |
| } |
| property.addOccurrence(getOffsetRange(local)); |
| } else { |
| addOccurence (local, false); |
| property = createVariableFromImport(create(parserResult, exported)); |
| } |
| if (from != null && property != null) { |
| TypeUsage type = new TypeUsage(local.getName(), local.getFinish()); |
| property.addAssignment(type, local.getFinish()); |
| } |
| } |
| } |
| if (expression != null) { |
| addToPath(exportNode); |
| expression.accept(this); |
| removeFromPathTheLast(); |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean enterForNode(ForNode forNode) { |
| if (forNode.getInit() instanceof IdentNode) { |
| JsObject parent = modelBuilder.getCurrentObject(); |
| while (parent instanceof JsWith) { |
| parent = parent.getParent(); |
| } |
| IdentNode name = (IdentNode)forNode.getInit(); |
| JsObjectImpl variable = (JsObjectImpl)parent.getProperty(name.getName()); |
| if (variable != null) { |
| Collection<TypeUsage> types = ModelUtils.resolveSemiTypeOfExpression(modelBuilder, forNode.getModify()); |
| for (TypeUsage type : types) { |
| if (type.getType().contains(SemiTypeResolverVisitor.ST_VAR)) { |
| int index = type.getType().lastIndexOf(SemiTypeResolverVisitor.ST_VAR); |
| String newType = type.getType().substring(0, index) + SemiTypeResolverVisitor.ST_ARR + type.getType().substring(index + SemiTypeResolverVisitor.ST_VAR.length()); |
| type = new TypeUsage(newType, type.getOffset(), false); |
| } else if (type.getType().contains(SemiTypeResolverVisitor.ST_PRO)) { |
| int index = type.getType().lastIndexOf(SemiTypeResolverVisitor.ST_PRO); |
| String newType = type.getType().substring(0, index) + SemiTypeResolverVisitor.ST_ARR + type.getType().substring(index + SemiTypeResolverVisitor.ST_PRO.length()); |
| type = new TypeUsage(newType, type.getOffset(), false); |
| } |
| variable.addAssignment(type, forNode.getModify().getStart()); |
| } |
| } |
| } |
| return super.enterForNode(forNode); |
| } |
| |
| @Override |
| public boolean enterFunctionNode(FunctionNode functionNode) { |
| if (functionNode.isClassConstructor() && !(ModelUtils.CONSTRUCTOR.equals(functionNode.getName()) || functionNode.getName().startsWith(ModelUtils.CONSTRUCTOR))) { |
| // don't process artificail constructors. |
| return false; |
| } |
| addToPath(functionNode); |
| // Find the function in the model. It's has to be already there |
| JsFunctionImpl fncParent = modelBuilder.getCurrentDeclarationFunction(); |
| JsFunctionImpl fncScope = null; |
| if (functionNode.isProgram()) { |
| fncScope = fncParent; |
| if (this.parserResult.getSnapshot().getSource().getFileObject() != null) { |
| LOGGER.log(Level.FINE, "Creating model for: {0}", this.parserResult.getSnapshot().getSource().getFileObject().getPath()); //NOI18N |
| } |
| } else { |
| JsObject property = fncParent.getProperty(modelBuilder.getFunctionName(functionNode)); |
| if (property == null && functionNode.isStrict()) { |
| property = modelBuilder.getCurrentDeclarationScope().getProperty(modelBuilder.getFunctionName(functionNode)); |
| } |
| if(!(property instanceof JsFunction)) { |
| property = fncParent.getProperty(modelBuilder.getGlobal().getName() + modelBuilder.getFunctionName(functionNode)); |
| } |
| if (property != null && property instanceof JsFunction) { |
| if (property instanceof JsFunctionReference) { |
| fncScope = (JsFunctionImpl)((JsFunctionReference)property).getOriginal(); |
| } else { |
| fncScope = (JsFunctionImpl)property; |
| } |
| } |
| if (property == null) { |
| LOGGER.log(Level.FINE, "FunctionNode: " + functionNode.toString() + " is not processed, because parent function " + fncParent.toString() + " doesn't contain such property."); //NOI18N |
| return false; |
| } |
| } |
| |
| // add to the model functions and variables declared in this scope |
| // this is needed, to handle usege before declaration |
| processDeclarations(fncScope, functionNode); |
| fncScope.setStrict(functionNode.isStrict()); |
| |
| if (!functionNode.isProgram() && !functionNode.isModule()) { |
| correctNameAndOffsets(fncScope, functionNode); |
| setParent(fncScope, functionNode); |
| // set modifiers for the processed function |
| setModifiers(fncScope, functionNode); |
| modelBuilder.setCurrentObject(fncScope); |
| } |
| |
| processJsDoc(fncScope, functionNode, JsDocumentationSupport.getDocumentationHolder(parserResult)); |
| |
| if (functionNode.isModule()) { |
| // visit all imports and exports |
| List<ImportNode> imports = functionNode.getModule().getImports(); |
| for (ImportNode moduleImport : imports) { |
| moduleImport.accept(this); |
| } |
| List<ExportNode> exports = functionNode.getModule().getExports(); |
| for (ExportNode moduleExport : exports) { |
| moduleExport.accept(this); |
| } |
| } |
| |
| // visit all statements of the function |
| functionNode.getBody().accept(this); |
| |
| if (functionNode.getKind() == FunctionNode.Kind.GENERATOR) { |
| // set the return type as Generator object |
| fncScope.addReturnType(new TypeUsage("Generator", 1, true)); |
| } else { |
| // seting undefinded return type |
| if (fncScope.areReturnTypesEmpty()) { |
| // the function doesn't have return statement -> returns undefined |
| fncScope.addReturnType(new TypeUsage(Type.UNDEFINED, -1, false)); |
| } |
| } |
| |
| if (!functionNode.isProgram() && !functionNode.isModule()) { |
| processModifiersFromJsDoc(fncScope, functionNode, JsDocumentationSupport.getDocumentationHolder(parserResult)); |
| if (canBeSingletonPattern(1)) { |
| // move all properties to the parent |
| JsObject singleton = resolveThisInSingletonPattern(fncScope); |
| if (singleton != null) { |
| fncScope.setJsKind(JsElement.Kind.CONSTRUCTOR); |
| if (fncScope.isAnonymous()) { |
| // TODO we probably should not move the properties, or at least increase offset range |
| // of the singleton to fit offsets of these methods in the singleton object |
| List<JsObject> properties = new ArrayList<>(fncScope.getProperties().values()); |
| for (JsObject property : properties) { |
| ModelUtils.moveProperty(singleton, property); |
| } |
| } |
| } |
| } |
| modelBuilder.reset(); |
| } |
| removeFromPathTheLast(); |
| return false; |
| } |
| |
| private void correctNameAndOffsets(JsFunctionImpl jsFunction, FunctionNode fn) { |
| OffsetRange decNameOffset = jsFunction.getDeclarationName().getOffsetRange(); |
| Node lastVisited = getPreviousFromPath(2); |
| Identifier newIdentifier = null; |
| if (decNameOffset.getLength() == 0) { |
| // the function name is not between function and ( |
| if (lastVisited instanceof PropertyNode && fn.getKind() != FunctionNode.Kind.ARROW) { |
| PropertyNode pNode = (PropertyNode)lastVisited; |
| newIdentifier = new Identifier(pNode.getKeyName(), getOffsetRange(pNode.getKey())); |
| } else if ((lastVisited instanceof VarNode) && fn.isAnonymous()) { |
| VarNode vNode = (VarNode)lastVisited; |
| newIdentifier = new Identifier(vNode.getName().getName(), getOffsetRange(vNode.getName())); |
| } else if (fn.isAnonymous() && lastVisited instanceof JoinPredecessorExpression |
| && getPreviousFromPath(3) instanceof BinaryNode |
| && getPreviousFromPath(4) instanceof VarNode) { |
| // case var f1 = xxx || function () {} |
| VarNode vNode = (VarNode)getPreviousFromPath(4); |
| newIdentifier = new Identifier(vNode.getName().getName(), getOffsetRange(vNode.getName())); |
| } |
| } |
| if (newIdentifier != null) { |
| // if (fn.getKind() == FunctionNode.Kind.ARROW) { |
| // jsFunction.getParent().getProperties().remove(jsFunction.getName()); |
| // jsFunction.getParent().addProperty(newIdentifier.getName(), jsFunction); |
| // } |
| jsFunction.setDeclarationName(newIdentifier); |
| jsFunction.addOccurrence(newIdentifier.getOffsetRange()); |
| |
| } |
| } |
| |
| private void setModifiers(JsFunctionImpl jsFunction, FunctionNode fn) { |
| //Node lastVisited = getPreviousFromPath(2); |
| boolean isPrivate = false; |
| boolean isPrivilage = false; |
| boolean isStatic = false; |
| |
| Node lastVisited = getPreviousFromPath(2); |
| |
| if (!lc.getParentFunction(fn).isProgram() |
| && !(lastVisited instanceof PropertyNode || lastVisited instanceof BinaryNode)) { |
| // it can be a part of anonymous object |
| isPrivate = true; |
| } |
| if (lastVisited instanceof PropertyNode) { |
| PropertyNode pNode = (PropertyNode)lastVisited; |
| isStatic = pNode.isStatic(); |
| if (fn.isClassConstructor() || fn.isSubclassConstructor()) { |
| jsFunction.setJsKind(JsElement.Kind.CONSTRUCTOR); |
| } else if (fn.isMethod()) { |
| if (fn.equals(pNode.getGetter())) { |
| jsFunction.setJsKind(JsElement.Kind.PROPERTY_GETTER); |
| } else if (fn.equals(pNode.getSetter())) { |
| jsFunction.setJsKind(JsElement.Kind.PROPERTY_SETTER); |
| } else { |
| jsFunction.setJsKind(JsElement.Kind.METHOD); |
| } |
| } |
| } else if (lastVisited instanceof BinaryNode) { |
| BinaryNode bNode = (BinaryNode)lastVisited; |
| if (bNode.getAssignmentDest() instanceof AccessNode) { |
| // case like A.f1 = function (){} -> f1 is a public static property |
| AccessNode aNode = (AccessNode)bNode.getAssignmentDest(); |
| List<Identifier> name = getName(aNode, parserResult); |
| if (name != null && ModelUtils.THIS.equals(name.get(0).getName())) { |
| isPrivilage = true; |
| } else { |
| if (!ModelUtils.PROTOTYPE.equals(aNode.getProperty()) && jsFunction.getParent().getJSKind().isFunction()) { |
| if (aNode.getBase() instanceof AccessNode) { |
| if (!ModelUtils.PROTOTYPE.equals(((AccessNode)aNode.getBase()).getProperty())) { |
| // case like A.B.f1 = function () {} |
| isStatic = true; |
| } |
| } else { |
| isStatic = true; |
| } |
| } |
| } |
| } |
| } else if (lastVisited instanceof CallNode) { |
| if (getPreviousFromPath(3) instanceof UnaryNode) { |
| if (getPreviousFromPath(4) instanceof VarNode) { |
| isPrivate = true; |
| } |
| } |
| } |
| |
| if (fn.getKind() == FunctionNode.Kind.GENERATOR) { |
| // marking the function as generator |
| jsFunction.setJsKind(JsElement.Kind.GENERATOR); |
| } |
| |
| Set<Modifier> modifiers = jsFunction.getModifiers(); |
| if (isPrivate || isPrivilage) { |
| modifiers.remove(Modifier.PUBLIC); |
| if (isPrivate) { |
| modifiers.add(Modifier.PRIVATE); |
| } else { |
| modifiers.add(Modifier.PROTECTED); |
| } |
| } |
| if (isStatic) { |
| modifiers.add(Modifier.STATIC); |
| } |
| // setting whether the function is anonymous |
| if (isFunctionAnonymous(fn)) { |
| jsFunction.setAnonymous(true); |
| } |
| } |
| |
| private void setParent(JsFunctionImpl jsFunction, FunctionNode fn) { |
| Node lastVisited = getPreviousFromPath(2); |
| JsObject parent = jsFunction.getParent(); |
| if (lastVisited instanceof JoinPredecessorExpression |
| && getPreviousFromPath(3) instanceof BinaryNode |
| && getPreviousFromPath(4) instanceof VarNode) { |
| // this handle case var f1 = xxx || function () {} |
| // just skip the binary node and continue like in case var f1 = function (){} |
| lastVisited = getPreviousFromPath(4); |
| } |
| if (lastVisited instanceof PropertyNode) { |
| // the parent of the function is the literal object |
| parent = modelBuilder.getCurrentObject(); |
| } else if (lastVisited instanceof VarNode) { |
| VarNode varNode = (VarNode)lastVisited; |
| if (fn.isNamedFunctionExpression()) { |
| // case: var f1 = function fx() {} |
| // the fx can be used only in fx, in other cases is unaccessible -> basically private function of f1 |
| // fx will be feference of f1 |
| parent.getProperties().remove(modelBuilder.getFunctionName(fn)); |
| JsObject variable = parent.getProperty(varNode.getName().getName()); |
| Identifier refName = new Identifier(fn.getIdent().getName(), new OffsetRange(fn.getIdent().getStart(), fn.getIdent().getFinish())); |
| JsFunctionReference jsRef = new JsFunctionReference(jsFunction, refName, jsFunction, true, EnumSet.of(Modifier.PRIVATE)); |
| jsRef.addOccurrence(jsRef.getDeclarationName().getOffsetRange()); |
| jsFunction.setDeclarationName(new Identifier(varNode.getName().getName(), getOffsetRange(varNode.getName()))); |
| if (variable != null) { |
| ModelUtils.copyOccurrences(variable, jsFunction); |
| } |
| parent.addProperty(jsFunction.getName(), jsFunction); |
| jsFunction.addProperty(jsRef.getName(), jsRef); |
| } else if ((varNode.isFunctionDeclaration() || fn.isAnonymous())) { |
| // correct key name of properties in cases |
| // var f1 = function () {} |
| // var f1 = function f1() {} |
| parent.getProperties().remove(modelBuilder.getFunctionName(fn)); |
| parent.addProperty(varNode.getName().getName(), jsFunction); |
| } |
| } else if (lastVisited instanceof BinaryNode) { |
| // case like A.f1 = function () {} |
| BinaryNode bNode = (BinaryNode)lastVisited; |
| List<Identifier> name = getName(bNode, parserResult); |
| boolean isPriviliged = false; |
| |
| if (name != null && !name.isEmpty()) { |
| if (ModelUtils.THIS.equals(name.get(0).getName())) { |
| name.remove(0); |
| isPriviliged = true; |
| parent = (JsObjectImpl)resolveThis(parent); |
| JsObject hParent = parent; |
| while(hParent != null && hParent.getKind() != ElementKind.FILE && hParent.getDeclarationName() != null) { |
| name.add(0, hParent.getDeclarationName()); |
| hParent = hParent.getParent(); |
| } |
| } |
| |
| boolean parentHasSameName = false; |
| if (name.size() > 1 && name.get(0).getName().equals(jsFunction.getName())) { |
| JsObject property = parent.getProperty(modelBuilder.getFunctionName(fn)); |
| if (property != null && property.equals(jsFunction)) { |
| // this handles case like: |
| // theSameName.theSameName = function theSameName () {} |
| parent.getProperties().remove(modelBuilder.getFunctionName(fn)); |
| ((JsObjectImpl)property).clearOccurrences(); |
| parentHasSameName = true; |
| } |
| } |
| JsObjectImpl jsObject = ModelUtils.getJsObject(modelBuilder, name, !parentHasSameName); |
| if (!isPriviliged) { |
| parent = jsObject.getParent(); |
| } |
| if (fn.isNamedFunctionExpression()) { |
| // case like A.f1 = function f1(){} |
| Identifier refName = new Identifier(fn.getIdent().getName(), new OffsetRange(fn.getIdent().getStart(), fn.getIdent().getFinish())); |
| JsFunctionReference jsRef = new JsFunctionReference(jsFunction, refName, jsFunction, true, EnumSet.of(Modifier.PRIVATE)); |
| jsRef.addOccurrence(jsRef.getDeclarationName().getOffsetRange()); |
| jsFunction.addProperty(jsRef.getName(), jsRef); |
| } |
| jsFunction.setDeclarationName(jsObject.getDeclarationName()); |
| ModelUtils.copyOccurrences(jsObject, jsFunction); |
| if (!parentHasSameName) { |
| String builderName = modelBuilder.getFunctionName(fn); |
| if (jsFunction.getParent().getProperties().remove(builderName) == null) { |
| // we need to check, whether is not declared in a block of the parent for handling cases like: |
| // onreadystatechange = function() { |
| // if (true) { |
| // onreadystatechange = function () {console.log("true");}; |
| // } else { |
| // onreadystatechange = function () {console.log("false");}; |
| // } |
| // }; |
| for (JsObject property : jsFunction.getParent().getProperties().values()) { |
| if (property.getJSKind() == JsElement.Kind.BLOCK && property.getProperties().remove(builderName) != null) { |
| if (property.getProperties().size() == 0) { |
| // remove the empty block from model |
| property.getParent().getProperties().remove(property.getName()); |
| } |
| break; |
| } |
| } |
| } |
| } |
| if (parent == null) { |
| parent = jsObject.getParent(); |
| } |
| parent.addProperty(jsObject.getName(), jsFunction); |
| jsFunction.setParent(parent); |
| } |
| } else if (lastVisited instanceof CallNode) { |
| if (getPreviousFromPath(3) instanceof UnaryNode) { |
| if (getPreviousFromPath(4) instanceof VarNode) { |
| // case var MyLib = new function XXX? () {} |
| VarNode varNode = (VarNode) getPreviousFromPath(4); |
| Expression init = varNode.getInit(); |
| Identifier varName = new Identifier(varNode.getName().getName(), getOffsetRange(varNode.getName())); |
| OffsetRange range = varNode.getInit() instanceof ObjectNode ? new OffsetRange(varNode.getName().getStart(), ((ObjectNode)varNode.getInit()).getFinish()) |
| : varName.getOffsetRange(); |
| |
| JsObject variable = handleArrayCreation(varNode.getInit(), parent, varName); |
| if (variable == null) { |
| JsObjectImpl newObject = new JsObjectImpl(parent, varName, range, jsFunction.getMimeType(), jsFunction.getSourceLabel()); |
| newObject.setDeclared(true); |
| variable = newObject; |
| } |
| variable.addOccurrence(varName.getOffsetRange()); |
| parent.getProperties().remove(jsFunction.getName()); |
| parent.addProperty(varName.getName(), variable); |
| variable.addProperty(jsFunction.getName(), jsFunction); |
| jsFunction.setParent(variable); |
| // Collection<TypeUsage> returns = ModelUtils.resolveSemiTypeOfExpression(modelBuilder, init); |
| // for (TypeUsage type : returns) { |
| variable.addAssignment(new TypeUsage(SemiTypeResolverVisitor.ST_NEW + variable.getName() + '.' + jsFunction.getName(), jsFunction.getDeclarationName().getOffsetRange().getStart()), init.getStart()); |
| // } |
| if (fn.isNamedFunctionExpression() && fn.getName().equals(varName.getName())) { |
| // the name of function is the same as the variable |
| // var MyLib = new function MyLib() {}; |
| ModelUtils.copyOccurrences(jsFunction, variable); |
| } |
| parent = variable; |
| int index = getPath().size() - 5; |
| while ( index > -1 && !(getPath().get(index) instanceof FunctionNode)) { |
| index--; |
| } |
| if(index > 0) { |
| // the variable is defined in a function -> the object is private |
| variable.getModifiers().remove(Modifier.PUBLIC); |
| variable.getModifiers().add(Modifier.PRIVATE); |
| } |
| } |
| } |
| } |
| |
| if (!parent.equals(jsFunction.getParent())) { |
| jsFunction.getParent().getProperties().remove(modelBuilder.getFunctionName(fn)); |
| jsFunction.setParent(parent); |
| JsObject property = parent.getProperty(jsFunction.getName()); |
| if (property != null) { |
| ModelUtils.copyOccurrences(property, jsFunction); |
| } |
| parent.addProperty(jsFunction.getName(), jsFunction); |
| } |
| |
| DeclarationScopeImpl fnScope = (DeclarationScopeImpl)jsFunction; |
| DeclarationScope parentScope = fnScope.getParentScope(); |
| if (parentScope != null) { |
| parentScope.addDeclaredScope(fnScope); |
| } |
| } |
| |
| private boolean isFunctionAnonymous(FunctionNode fn) { |
| boolean result = false; |
| if (fn.isAnonymous() ) { |
| Node lastVisited = getPreviousFromPath(2); |
| if (fn.getIdent().getName().startsWith("L:") && !(lastVisited instanceof PropertyNode)) { //NOI18N |
| // XXX this depends on the implemenation of parser. Find the better way |
| result = true; |
| } else if (fn.getIdent().getStart() == fn.getIdent().getFinish()) { |
| if (lastVisited instanceof CallNode) { |
| result = true; |
| } |
| } |
| } |
| return result; |
| } |
| |
| private void processJsDoc(JsFunctionImpl jsFunction, FunctionNode fn, JsDocumentationHolder docHolder) { |
| if (!fn.isProgram()) { |
| // the documentation for the function |
| Documentation documentation = docHolder.getDocumentation(fn); |
| jsFunction.setDocumentation(documentation); |
| // parameters |
| List<DocParameter> docParams = docHolder.getParameters(fn); |
| for (DocParameter docParameter : docParams) { |
| Identifier paramName = docParameter.getParamName(); |
| if (paramName != null) { |
| String sParamName = paramName.getName(); |
| if(sParamName != null && !sParamName.isEmpty()) { |
| JsObjectImpl param = (JsObjectImpl) jsFunction.getParameter(sParamName); |
| if (param != null) { |
| for (Type type : docParameter.getParamTypes()) { |
| param.addAssignment(new TypeUsage(type.getType(), type.getOffset(), true), param.getOffset()); |
| } |
| // param occurence in the doc |
| addDocNameOccurence(param); |
| } |
| } |
| } |
| } |
| // mark constructors |
| if (docHolder.isClass(fn)) { |
| // needs to be marked before going through the nodes |
| jsFunction.setJsKind(JsElement.Kind.CONSTRUCTOR); |
| } |
| |
| jsFunction.setDeprecated(docHolder.isDeprecated(fn)); |
| |
| // process @extends tag |
| List<Type> extendTypes = docHolder.getExtends(fn); |
| if (!extendTypes.isEmpty()) { |
| JsObject prototype = jsFunction.getProperty(ModelUtils.PROTOTYPE); |
| if (prototype == null) { |
| prototype = new JsObjectImpl(jsFunction, ModelUtils.PROTOTYPE, true, OffsetRange.NONE, EnumSet.of(Modifier.PUBLIC), parserResult.getSnapshot().getMimeType(), null); |
| jsFunction.addProperty(ModelUtils.PROTOTYPE, prototype); |
| } |
| for (Type type : extendTypes) { |
| prototype.addAssignment(new TypeUsage(type.getType(), type.getOffset(), true), type.getOffset()); |
| } |
| } |
| |
| // process @returns tag |
| List<Type> types = docHolder.getReturnType(fn); |
| if (types != null && !types.isEmpty()) { |
| for(Type type : types) { |
| jsFunction.addReturnType(new TypeUsage(type.getType(), type.getOffset(), true /*ModelUtils.isKnownGLobalType(type.getType())*/)); |
| } |
| } |
| } |
| // look for the type defined through comment like @typedef |
| Map<Integer, ? extends JsComment> commentBlocks = docHolder.getCommentBlocks(); |
| for (JsComment comment : commentBlocks.values()) { |
| DocParameter definedType = comment.getDefinedType(); |
| if (definedType != null) { |
| // XXX the param name now can contains names with dot. |
| // it would be better if the getParamName returns list of identifiers |
| String typeName = definedType.getParamName().getName(); |
| List<Identifier> fqn = new ArrayList<Identifier>(); |
| JsObject whereOccurrence = getGlobalObject(); |
| if (typeName.indexOf('.') > -1) { |
| String[] parts = typeName.split("\\."); |
| int offset = definedType.getParamName().getOffsetRange().getStart(); |
| int delta = 0; |
| for (int i = 0; i < parts.length; i++) { |
| fqn.add(new Identifier(parts[i], offset + delta)); |
| if (whereOccurrence != null) { |
| whereOccurrence = whereOccurrence.getProperty(parts[i]); |
| if (whereOccurrence != null) { |
| whereOccurrence.addOccurrence(new OffsetRange(offset + delta, offset + delta + parts[i].length())); |
| } |
| } |
| delta = delta + parts[i].length() + 1; |
| } |
| } else { |
| fqn.add(definedType.getParamName()); |
| } |
| JsObject object = ModelUtils.getJsObject(modelBuilder, fqn, true); |
| int assignOffset = definedType.getParamName().getOffsetRange().getEnd(); |
| List<Type> types = definedType.getParamTypes(); |
| |
| for (Type type : types) { |
| object.addAssignment(new TypeUsage(type.getType(), type.getOffset()), assignOffset); |
| } |
| List<Type> assignedTypes = comment.getTypes(); |
| for (Type type : assignedTypes) { |
| object.addAssignment(new TypeUsage(type.getType(), type.getOffset()), assignOffset); |
| } |
| List<DocParameter> properties = comment.getProperties(); |
| for (DocParameter docProperty : properties) { |
| JsObject jsProperty = new JsObjectImpl(object, docProperty.getParamName(), docProperty.getParamName().getOffsetRange(), true, JsTokenId.JAVASCRIPT_MIME_TYPE, null); |
| object.addProperty(jsProperty.getName(), jsProperty); |
| types = docProperty.getParamTypes(); |
| jsProperty.setDocumentation(Documentation.create(docProperty.getParamDescription())); |
| assignOffset = docProperty.getParamName().getOffsetRange().getEnd(); |
| for (Type type : types) { |
| jsProperty.addAssignment(new TypeUsage(type.getType(), type.getOffset()), assignOffset); |
| } |
| } |
| } |
| Type callBack = comment.getCallBack(); |
| if (callBack != null) { |
| List<Identifier> fqn = fqnFromType(callBack); |
| markOccurrences(fqn); |
| List<Identifier> parentFqn = new ArrayList<Identifier>(); |
| for (int i = 0; i < fqn.size() - 1; i++) { |
| parentFqn.add(fqn.get(i)); |
| } |
| JsObject parentObject = parentFqn.isEmpty() ? getGlobalObject() : ModelUtils.getJsObject(modelBuilder, parentFqn, true); |
| JsFunctionImpl callBackFunction = new JsFunctionImpl( |
| parentObject instanceof DeclarationScope ? (DeclarationScope) parentObject : ModelUtils.getDeclarationScope(parentObject), |
| parentObject, fqn.get(fqn.size() - 1), Collections.EMPTY_LIST, |
| callBack.getOffset() > -1 ? new OffsetRange(callBack.getOffset(), callBack.getOffset() + callBack.getType().length()) : OffsetRange.NONE, |
| JsTokenId.JAVASCRIPT_MIME_TYPE, null); |
| parentObject.addProperty(callBackFunction.getName(), callBackFunction); |
| callBackFunction.setDocumentation(Documentation.create(comment.getDocumentation())); |
| callBackFunction.setJsKind(JsElement.Kind.CALLBACK); |
| List<DocParameter> docParameters = comment.getParameters(); |
| for (DocParameter docParameter : docParameters) { |
| ParameterObject parameter = new ParameterObject(callBackFunction, docParameter.getParamName(), JsTokenId.JAVASCRIPT_MIME_TYPE, null); |
| for (Type type : docParameter.getParamTypes()) { |
| parameter.addAssignment(new TypeUsage(type.getType(), type.getOffset(), true), parameter.getOffset()); |
| } |
| addDocNameOccurence(parameter); |
| callBackFunction.addParameter(parameter); |
| } |
| } |
| } |
| } |
| |
| private void processModifiersFromJsDoc(JsFunctionImpl jsFunction, FunctionNode fn, JsDocumentationHolder docHolder) { |
| if (!fn.isProgram() && !fn.isModule() && docHolder != null) { |
| Set<JsModifier> modifiers = docHolder.getModifiers(fn); |
| if (modifiers != null && !modifiers.isEmpty()) { |
| Set<Modifier> fnModifiers = jsFunction.getModifiers(); |
| if (modifiers.contains(JsModifier.PRIVATE)) { |
| fnModifiers.remove(Modifier.PUBLIC); |
| fnModifiers.remove(Modifier.PROTECTED); |
| fnModifiers.add(Modifier.PRIVATE); |
| } |
| if (modifiers.contains(JsModifier.PUBLIC)) { |
| fnModifiers.remove(Modifier.PRIVATE); |
| fnModifiers.remove(Modifier.PROTECTED); |
| fnModifiers.add(Modifier.PUBLIC); |
| } |
| if (modifiers.contains(JsModifier.STATIC)) { |
| fnModifiers.add(Modifier.STATIC); |
| } |
| } |
| } |
| } |
| |
| private void processDeclarations(final JsFunctionImpl parentFn, final FunctionNode inNode) { |
| LOGGER.log(Level.FINEST, "in function: " + inNode.getName() + ", ident: " + inNode.getIdent()); |
| final JsDocumentationHolder docHolder = JsDocumentationSupport.getDocumentationHolder(parserResult); |
| |
| Block block = inNode.getBody(); |
| PathNodeVisitor visitor = new PathNodeVisitor(lc) { |
| |
| DeclarationScopeImpl currentBlockScope = parentFn; |
| private boolean isParameterBlock = false; |
| private List<FunctionNode> declaredFunctions = new ArrayList<>(); |
| private List<VarNode> declaredVars = new ArrayList<>(); |
| |
| private void handleDeclarations() { |
| if (!declaredFunctions.isEmpty() || !declaredVars.isEmpty()) { |
| for (FunctionNode fnNode : declaredFunctions) { |
| handleDeclaredFunction(currentBlockScope, parentFn, fnNode); |
| } |
| for (VarNode varNode : declaredVars) { |
| if (varNode.isLet()) { |
| // block scope variable |
| handleDeclaredVariable(currentBlockScope, parentFn, varNode, docHolder); |
| } else { |
| handleDeclaredVariable(parentFn, parentFn, varNode, docHolder); |
| } |
| } |
| declaredFunctions.clear(); |
| declaredVars.clear(); |
| } |
| } |
| |
| @Override |
| public boolean enterBlock(Block block) { |
| handleDeclarations(); |
| if (inNode.isStrict()) { |
| if (getPath().size() > 0) { |
| // we are in strict mode -> possible block scope declaration |
| currentBlockScope = new DeclarationScopeImpl(currentBlockScope, currentBlockScope, |
| new Identifier(BLOCK_OBJECT_NAME_PREFIX + block.getStart(), OffsetRange.NONE), new OffsetRange(block.getStart(), block.getFinish()), currentBlockScope.getMimeType() , currentBlockScope.getSourceLabel()); |
| currentBlockScope.setJsKind(JsElement.Kind.BLOCK); |
| } |
| } |
| isParameterBlock = block.isParameterBlock(); |
| return super.enterBlock(block); |
| } |
| |
| @Override |
| public Node leaveBlock(Block block) { |
| if (inNode.isStrict() || getPath().size() == 1) { |
| handleDeclarations(); |
| if (getPath().size() > 1) { |
| DeclarationScopeImpl parentScope = (DeclarationScopeImpl)currentBlockScope.getParentScope(); |
| if (!((JsObject)currentBlockScope).getProperties().isEmpty()) { |
| // don't keep this empty scope in model |
| parentScope.addDeclaredScope(currentBlockScope); |
| parentScope.addProperty(currentBlockScope.getName(), currentBlockScope); |
| } |
| currentBlockScope = parentScope; |
| } |
| } |
| return super.leaveBlock(block); |
| } |
| |
| @Override |
| public Node leaveExportNode(ExportNode exportNode) { |
| handleDeclarations(); |
| return super.leaveExportNode(exportNode); |
| } |
| |
| |
| @Override |
| public boolean enterClassNode(ClassNode classNode) { |
| if (classNode.getConstructor() != null) { |
| classNode.getConstructor().accept(this); |
| } |
| if (classNode.getClassElements() != null) { |
| for (PropertyNode pn : classNode.getClassElements()) { |
| pn.accept(this); |
| } |
| } |
| return false; |
| } |
| |
| |
| @Override |
| public boolean enterFunctionNode(FunctionNode fnNode) { |
| declaredFunctions.add(fnNode); |
| return false; |
| } |
| |
| @Override |
| public boolean enterVarNode(VarNode varNode) { |
| if (!isParameterBlock) { |
| declaredVars.add(varNode); |
| } |
| return super.enterVarNode(varNode); |
| } |
| |
| }; |
| if (inNode.isModule() && inNode.getModule().getExports() != null) { |
| for(ExportNode export :inNode.getModule().getExports()) { |
| if (!export.isDefault()) { |
| // don't go through the default export node, it appears also as *default* varible node |
| export.accept(visitor); |
| } else { |
| Expression expression = export.getExpression(); |
| if ((expression instanceof ClassNode && ((ClassNode)expression).getIdent() != null) |
| || (expression instanceof FunctionNode && ((FunctionNode)expression).getIdent() != null)) { |
| export.accept(visitor); |
| } |
| } |
| } |
| } |
| block.accept(visitor); |
| } |
| |
| private void handleDeclaredFunction(DeclarationScopeImpl inScope, JsObject parent, FunctionNode fnNode) { |
| LOGGER.log(Level.FINEST, " function: " + debugInfo(fnNode)); // NOI18N |
| String name = fnNode.isAnonymous() ? modelBuilder.getFunctionName(fnNode) : fnNode.getIdent().getName(); |
| Identifier fnName = new Identifier(name, new OffsetRange(fnNode.getIdent().getStart(), fnNode.getIdent().getFinish())); |
| if (fnNode.isClassConstructor() && !ModelUtils.CONSTRUCTOR.equals(fnName.getName())) { |
| // skip artifical/ syntetic constructor nodes, that are created |
| // when a class extends different class |
| return; |
| } |
| // process parameters |
| List<Identifier> parameters = new ArrayList<>(fnNode.getParameters().size()); |
| for(IdentNode node: fnNode.getParameters()) { |
| Identifier param = create(parserResult, node); |
| if (param != null && !node.isDestructuredParameter()) { |
| // can be null, if it's a generated embeding. |
| parameters.add(param); |
| } |
| } |
| // The parent can be changed in the later processing |
| JsFunctionImpl declaredFn = new JsFunctionImpl(inScope, parent, fnName, parameters, getOffsetRange(fnNode), inScope.getMimeType(), inScope.getSourceLabel()); |
| inScope.addProperty(modelBuilder.getFunctionName(fnNode), declaredFn); |
| if (fnName.getOffsetRange().getLength() > 0 && !fnNode.isNamedFunctionExpression()) { |
| declaredFn.addOccurrence(fnName.getOffsetRange()); |
| } |
| } |
| |
| private void handleDeclaredVariable(DeclarationScopeImpl parentFn, JsObject parent, VarNode varNode, JsDocumentationHolder docHolder) { |
| LOGGER.log(Level.FINEST, " variable: " + debugInfo(varNode)); // NOI18N |
| Expression init = varNode.getInit(); |
| boolean createVariable = true; |
| if (!varNode.isFunctionDeclaration() // we skip syntetic variables created from case: function f1(){} |
| && !varNode.isExport()) { // we skip syntetic variables created from export expression |
| if (init instanceof FunctionNode && !((FunctionNode) init).isNamedFunctionExpression()) { |
| // case: var f1 = function () {} |
| // the function here is already, need to be just fixed the name offsets |
| createVariable = false; |
| } else if (init instanceof BinaryNode) { |
| BinaryNode bNode = (BinaryNode) init; |
| if (bNode.isLogical() |
| && ((bNode.rhs() instanceof JoinPredecessorExpression && ((JoinPredecessorExpression) bNode.rhs()).getExpression() instanceof FunctionNode) |
| || (bNode.lhs() instanceof JoinPredecessorExpression && ((JoinPredecessorExpression) bNode.lhs()).getExpression() instanceof FunctionNode))) { |
| // case: var f1 = xxx || function () {} |
| // the function here is already, need to be just fixed the name offsets |
| createVariable = false; |
| } else if (bNode.isAssignment()) { |
| createVariable = false; |
| if (parentFn.getProperty(varNode.getName().getName()) == null) { |
| while (bNode.rhs() instanceof BinaryNode && bNode.rhs().isAssignment()) { |
| // the cycle is trying to find out a FunctionNode at the end of assignements |
| // case var f1 = f2 = f3 = f4 = function () {} |
| bNode = (BinaryNode) bNode.rhs(); |
| } |
| if (bNode.rhs() instanceof FunctionNode) { |
| // case var f1 = f2 = function (){}; |
| // -> the variable will be reference fo the function |
| FunctionNode fNode = (FunctionNode) bNode.rhs(); |
| JsObject original = parentFn.getProperty(modelBuilder.getFunctionName(fNode)); |
| if (original != null) { |
| Identifier varName = new Identifier(varNode.getName().getName(), getOffsetRange(varNode.getName())); |
| OffsetRange range = varName.getOffsetRange(); |
| JsFunctionReference variable = new JsFunctionReference(parentFn, varName, (JsFunction) original, true, original.getModifiers()); |
| variable.addOccurrence(varName.getOffsetRange()); |
| parentFn.addProperty(varName.getName(), variable); |
| } |
| } |
| } |
| } |
| } else if (parentFn.getProperty(varNode.getName().getName()) != null) { |
| // the name is already used by a function. |
| if (!(init instanceof CallNode) && !(init instanceof UnaryNode)) { |
| // we skip the var declaration basically, but has to be added occuerences for the existing property |
| // with the same name |
| parentFn.getProperty(varNode.getName().getName()).addOccurrence(getOffsetRange(varNode.getName())); |
| } |
| createVariable = false; |
| } |
| if (createVariable) { |
| // skip the variables that are syntetic |
| Identifier varName = new Identifier(varNode.getName().getName(), getOffsetRange(varNode.getName())); |
| OffsetRange range = varNode.getInit() instanceof ObjectNode ? new OffsetRange(varNode.getName().getStart(), ((ObjectNode) varNode.getInit()).getFinish()) |
| : varName.getOffsetRange(); |
| JsObject variable = handleArrayCreation(varNode.getInit(), parentFn, varName); |
| if (variable == null) { |
| JsObjectImpl newObject = new JsObjectImpl(parentFn, varName, range, parserResult.getSnapshot().getMimeType(), null); |
| variable = newObject; |
| } |
| variable.addOccurrence(varName.getOffsetRange()); |
| parentFn.addProperty(varName.getName(), variable); |
| variable.addOccurrence(varName.getOffsetRange()); |
| |
| if (docHolder != null) { |
| ((JsObjectImpl) variable).setDocumentation(docHolder.getDocumentation(varNode)); |
| ((JsObjectImpl) variable).setDeprecated(docHolder.isDeprecated(varNode)); |
| } |
| } |
| } |
| } |
| |
| |
| private List<Identifier> fqnFromType (final Type type) { |
| List<Identifier> fqn = new ArrayList<Identifier>(); |
| String typeName = type.getType(); |
| int offset = type.getOffset(); |
| if (typeName.indexOf('.') > -1) { |
| String[] parts = typeName.split("\\."); |
| int delta = 0; |
| for (int i = 0; i < parts.length; i++) { |
| fqn.add(new Identifier(parts[i], offset + delta)); |
| delta = delta + parts[i].length() + 1; |
| } |
| } else { |
| fqn.add(new Identifier(typeName, offset)); |
| } |
| return fqn; |
| } |
| |
| private void markOccurrences (List<Identifier> fqn) { |
| JsObject whereOccurrence = getGlobalObject(); |
| for (Identifier iden: fqn) { |
| whereOccurrence = whereOccurrence.getProperty(iden.getName()); |
| if (whereOccurrence != null) { |
| whereOccurrence.addOccurrence(iden.getOffsetRange()); |
| } else { |
| break; |
| } |
| } |
| } |
| |
| private JsArray handleArrayCreation(Node initNode, JsObject parent, Identifier name) { |
| if (initNode instanceof UnaryNode && parent != null) { |
| UnaryNode uNode = (UnaryNode)initNode; |
| if (uNode.tokenType() == TokenType.NEW && uNode.getExpression() instanceof CallNode) { |
| CallNode cNode = (CallNode)uNode.getExpression(); |
| if (cNode.getFunction() instanceof IdentNode && "Array".equals(((IdentNode)cNode.getFunction()).getName())) { |
| List<TypeUsage> itemTypes = new ArrayList<TypeUsage>(); |
| for (Node node : cNode.getArgs()) { |
| itemTypes.addAll(ModelUtils.resolveSemiTypeOfExpression(modelBuilder, node)); |
| } |
| EnumSet<Modifier> modifiers = parent.getJSKind() != JsElement.Kind.FILE ? EnumSet.of(Modifier.PRIVATE) : EnumSet.of(Modifier.PUBLIC); |
| JsArrayImpl result = new JsArrayImpl(parent, name, name.getOffsetRange(), true, modifiers, parserResult.getSnapshot().getMimeType(), null); |
| result.addTypesInArray(itemTypes); |
| return result; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public boolean enterLiteralNode(LiteralNode lNode) { |
| Node lastVisited = getPreviousFromPath(1); |
| if (lNode instanceof LiteralNode.ArrayLiteralNode) { |
| LiteralNode.ArrayLiteralNode aNode = (LiteralNode.ArrayLiteralNode)lNode; |
| List<Identifier> fqName = null; |
| int pathSize = getPath().size(); |
| boolean isDeclaredInParent = false; |
| boolean isPrivate = false; |
| boolean treatAsAnonymous = false; |
| JsObject parent = null; |
| |
| if (lastVisited instanceof TernaryNode && pathSize > 1) { |
| lastVisited = getPath().get(pathSize - 2); |
| } |
| int pathIndex = 1; |
| |
| while(lastVisited instanceof BinaryNode |
| && (pathSize > pathIndex) |
| && ((BinaryNode)lastVisited).tokenType() != TokenType.ASSIGN) { |
| pathIndex++; |
| lastVisited = getPath().get(pathSize - pathIndex); |
| } |
| if ( lastVisited instanceof VarNode) { |
| fqName = getName((VarNode)lastVisited, parserResult); |
| isDeclaredInParent = true; |
| JsObject declarationScope = ((VarNode)lastVisited).isLet() ? modelBuilder.getCurrentDeclarationScope() : modelBuilder.getCurrentDeclarationFunction(); |
| parent = declarationScope; |
| if (fqName.size() == 1 && !ModelUtils.isGlobal(declarationScope)) { |
| isPrivate = true; |
| } |
| } else if (lastVisited instanceof PropertyNode) { |
| fqName = getName((PropertyNode) lastVisited); |
| isDeclaredInParent = true; |
| } else if (lastVisited instanceof BinaryNode) { |
| BinaryNode binNode = (BinaryNode) lastVisited; |
| if (binNode.lhs() instanceof IndexNode) { |
| Node index = ((IndexNode)binNode.lhs()).getIndex(); |
| if (!(index instanceof LiteralNode && ((LiteralNode)index).isString())) { |
| treatAsAnonymous = true; |
| } |
| } |
| if (!treatAsAnonymous) { |
| if (getPath().size() > 1) { |
| lastVisited = getPath().get(getPath().size() - pathIndex - 1); |
| } |
| fqName = getName(binNode, parserResult); |
| if ((binNode.lhs() instanceof IdentNode) |
| || (binNode.lhs() instanceof AccessNode |
| && ((AccessNode) binNode.lhs()).getBase() instanceof IdentNode |
| && ((IdentNode) ((AccessNode) binNode.lhs()).getBase()).getName().equals(ModelUtils.THIS))) { //NOI18N |
| if (lastVisited instanceof ExpressionStatement && !fqName.get(0).getName().equals(ModelUtils.THIS)) { //NOI18N |
| // try to catch the case: pool = []; |
| List<Identifier> objectName = fqName.size() > 1 ? fqName.subList(0, fqName.size() - 1) : fqName; |
| JsObject existingArray = ModelUtils.getJsObject(modelBuilder, objectName, false); |
| if (existingArray != null) { |
| existingArray.addOccurrence(fqName.get(fqName.size() - 1).getOffsetRange()); |
| return super.enterLiteralNode(lNode); |
| } |
| } else { |
| isDeclaredInParent = true; |
| if (!(binNode.lhs() instanceof IdentNode)) { |
| parent = resolveThis(modelBuilder.getCurrentObject()); |
| } |
| } |
| } |
| } |
| } else if (lastVisited instanceof CallNode || lastVisited instanceof LiteralNode.ArrayLiteralNode |
| || lastVisited instanceof ReturnNode || lastVisited instanceof AccessNode) { |
| // probably an anonymous array as a parameter of a function call |
| // or array in an array: var a = [['a', 10], ['b', 20]]; |
| // or [1,2,3].join(); |
| treatAsAnonymous = true; |
| } |
| if (!isDeclaredInParent) { |
| if (lastVisited instanceof FunctionNode) { |
| isDeclaredInParent = ((FunctionNode) lastVisited).getKind() == FunctionNode.Kind.SCRIPT; |
| } |
| } |
| JsArrayImpl array = null; |
| if (!treatAsAnonymous) { |
| // if (fqName == null || fqName.isEmpty()) { |
| // fqName = new ArrayList<Identifier>(1); |
| // fqName.add(new Identifier("UNKNOWN", //NOI18N |
| // new OffsetRange(lNode.getStart(), lNode.getFinish()))); |
| // } |
| |
| if (fqName != null && !fqName.isEmpty() && fqName.get(0) != null) { |
| if (ModelUtils.THIS.equals(fqName.get(0).getName())) { |
| parent = resolveThis(modelBuilder.getCurrentObject()); |
| fqName.remove(0); |
| JsObject tmpObject = parent; |
| while (tmpObject.getParent() != null) { |
| Identifier dName = tmpObject.getDeclarationName(); |
| fqName.add(0, dName != null ? tmpObject.getDeclarationName() : new Identifier(tmpObject.getName(), OffsetRange.NONE)); |
| tmpObject = tmpObject.getParent(); |
| } |
| } |
| array = ModelElementFactory.create(parserResult, aNode, fqName, modelBuilder, isDeclaredInParent, parent); |
| if (array != null && isPrivate) { |
| array.getModifiers().remove(Modifier.PUBLIC); |
| array.getModifiers().add(Modifier.PRIVATE); |
| } |
| } |
| } else { |
| array = ModelElementFactory.createAnonymousObject(parserResult, aNode, modelBuilder); |
| } |
| if (array != null) { |
| int aOffset = fqName == null ? lastVisited.getStart() : fqName.get(fqName.size() - 1).getOffsetRange().getEnd(); |
| array.addAssignment(ModelUtils.resolveSemiTypeOfExpression(modelBuilder, lNode), aOffset); |
| for (Node item : aNode.getElementExpressions()) { |
| array.addTypesInArray(ModelUtils.resolveSemiTypeOfExpression(modelBuilder, item)); |
| } |
| if (!functionArgumentStack.isEmpty()) { |
| functionArgumentStack.peek().add(array); |
| } |
| } |
| } |
| return super.enterLiteralNode(lNode); |
| } |
| |
| @Override |
| public boolean enterObjectNode(ObjectNode objectNode) { |
| Node previousVisited = getPath().get(getPath().size() - 1); |
| if(previousVisited instanceof CallNode |
| || previousVisited instanceof LiteralNode.ArrayLiteralNode |
| || previousVisited instanceof ExpressionStatement) { |
| // TODO there should be handled anonymous object that are going as parameter to a funciton |
| //create anonymous object |
| JsObjectImpl object = ModelElementFactory.createAnonymousObject(parserResult, objectNode, modelBuilder); |
| modelBuilder.setCurrentObject(object); |
| object.setJsKind(JsElement.Kind.OBJECT_LITERAL); |
| if (!functionArgumentStack.isEmpty()) { |
| functionArgumentStack.peek().add(object); |
| } |
| return super.enterObjectNode(objectNode); |
| } else if (previousVisited instanceof ReturnNode |
| || (previousVisited instanceof BinaryNode && ((BinaryNode)previousVisited).tokenType() == TokenType.COMMARIGHT)) { |
| JsObjectImpl objectScope = ModelElementFactory.createAnonymousObject(parserResult, objectNode, modelBuilder); |
| modelBuilder.setCurrentObject(objectScope); |
| objectScope.setJsKind(JsElement.Kind.OBJECT_LITERAL); |
| } else if (previousVisited instanceof ExportNode && ((ExportNode)previousVisited).isDefault()) { |
| // we are handling case: export default {} |
| // the node should be visible in navigator |
| List<Identifier> fqName = new ArrayList<>(1); |
| fqName.add(new Identifier("default", OffsetRange.NONE)); // NOI18N |
| JsObjectImpl objectScope = ModelElementFactory.create(parserResult, objectNode, fqName, modelBuilder, true); //ModelElementFactory.createAnonymousObject(parserResult, objectNode, modelBuilder); |
| modelBuilder.setCurrentObject(objectScope); |
| objectScope.setJsKind(JsElement.Kind.OBJECT_LITERAL); |
| } else { |
| List<Identifier> fqName = null; |
| int pathSize = getPath().size(); |
| boolean isDeclaredInParent = false; |
| boolean isDeclaredThroughThis = false; |
| boolean isPrivate = false; |
| boolean treatAsAnonymous = false; |
| |
| int pathIndex = 1; |
| Node lastVisited = getPath().get(pathSize - pathIndex); |
| VarNode varNode = null; |
| |
| if (lastVisited instanceof JoinPredecessorExpression) { |
| pathIndex++; |
| lastVisited = getPath().get(pathSize - pathIndex); |
| } |
| if (lastVisited instanceof TernaryNode && pathSize > 1) { |
| TernaryNode tNode = (TernaryNode)lastVisited; |
| lastVisited = getPath().get(pathSize - pathIndex - 1); |
| if (lastVisited instanceof ExpressionStatement || lastVisited instanceof BinaryNode) { |
| JoinPredecessorExpression trueExpression = tNode.getTrueExpression(); |
| JoinPredecessorExpression falseExpression = tNode.getFalseExpression(); |
| if (trueExpression.getExpression().equals(objectNode) || falseExpression.getExpression().equals(objectNode)) { |
| // now we have to find out, whether we are in parameter block |
| int blockIndex = pathIndex + 1; |
| Block block = null; |
| while (blockIndex < getPath().size() && block == null) { |
| if (getPreviousFromPath(++blockIndex) instanceof Block) { |
| block = (Block)getPreviousFromPath(blockIndex); |
| } |
| } |
| // this is can be case when the object literal is a part of destructure assignman pattern used as parameter |
| // function drawES6Chart({size = 'big', cords = { x: 0, y: 0 , z: 0}, radius = 25} = {}) {} |
| treatAsAnonymous = block != null && block.isParameterBlock(); |
| } |
| } |
| } |
| if (lastVisited instanceof BinaryNode) { |
| BinaryNode bNode = (BinaryNode)lastVisited; |
| if (bNode.lhs().equals(objectNode)) { |
| // case of destructuring assignment { a, b} = .... |
| // we should not create object in the model. |
| return super.enterObjectNode(objectNode); |
| } else if (bNode.lhs() instanceof ObjectNode && bNode.rhs().equals(objectNode)) { |
| // case of destructuring assignment {a, b} = {a:1, b:2} |
| // do nothing/ already processed in binary node. |
| return false; |
| } |
| } |
| |
| while(lastVisited instanceof BinaryNode |
| && (pathSize > pathIndex) |
| && ((BinaryNode)lastVisited).tokenType() != TokenType.ASSIGN) { |
| pathIndex++; |
| lastVisited = getPath().get(pathSize - pathIndex); |
| } |
| if ( lastVisited instanceof VarNode) { |
| fqName = getName((VarNode)lastVisited, parserResult); |
| isDeclaredInParent = true; |
| JsObject declarationScope = modelBuilder.getCurrentDeclarationFunction(); |
| varNode = (VarNode)lastVisited; |
| if (fqName.size() == 1 && !ModelUtils.isGlobal(declarationScope)) { |
| isPrivate = true; |
| } |
| } else if (lastVisited instanceof PropertyNode) { |
| fqName = getName((PropertyNode) lastVisited); |
| isDeclaredInParent = true; |
| } else if (lastVisited instanceof AccessNode) { |
| treatAsAnonymous = true; |
| } else if (lastVisited instanceof BinaryNode) { |
| BinaryNode binNode = (BinaryNode) lastVisited; |
| Node binLhs = binNode.lhs(); |
| if (binLhs instanceof IndexNode) { |
| Node index = ((IndexNode)binLhs).getIndex(); |
| if (!(index instanceof LiteralNode && ((LiteralNode)index).isString())) { |
| treatAsAnonymous = true; |
| } |
| } |
| if (!treatAsAnonymous) { |
| if (getPath().size() > 1) { |
| lastVisited = getPath().get(getPath().size() - pathIndex - 1); |
| if (lastVisited instanceof VarNode) { |
| varNode = (VarNode) lastVisited; |
| } |
| } |
| fqName = getName(binNode, parserResult); |
| if (binLhs instanceof IdentNode || (binLhs instanceof AccessNode |
| && ((AccessNode) binLhs).getBase() instanceof IdentNode |
| && ((IdentNode) ((AccessNode) binLhs).getBase()).getName().equals(ModelUtils.THIS))) { |
| // if it's not declared throgh the var node, then the variable doesn't have to be declared here |
| isDeclaredInParent = (binLhs instanceof IdentNode && varNode != null); |
| if (binLhs instanceof AccessNode) { |
| isDeclaredInParent = true; |
| isDeclaredThroughThis = true; |
| } |
| } |
| } |
| } |
| if (!isDeclaredInParent) { |
| if (lastVisited instanceof FunctionNode) { |
| isDeclaredInParent = ((FunctionNode) lastVisited).getKind() == FunctionNode.Kind.SCRIPT; |
| } |
| } |
| if (!treatAsAnonymous) { |
| if (fqName == null || fqName.isEmpty()) { |
| fqName = new ArrayList<Identifier>(1); |
| fqName.add(new Identifier("UNKNOWN", //NOI18N |
| new OffsetRange(objectNode.getStart(), objectNode.getFinish()))); |
| } |
| JsObjectImpl objectScope; |
| if (varNode != null) { |
| objectScope = modelBuilder.getCurrentObject(); |
| } else { |
| Identifier name = fqName.get(fqName.size() - 1); |
| JsObject alreadyThere = null; |
| if (isDeclaredThroughThis) { |
| JsObject thisIs = resolveThis(modelBuilder.getCurrentObject()); |
| alreadyThere = thisIs.getProperty(name.getName()); |
| } else { |
| if (isDeclaredInParent) { |
| if (lastVisited instanceof PropertyNode) { |
| alreadyThere = modelBuilder.getCurrentObject().getProperty(name.getName()); |
| } else { |
| alreadyThere = ModelUtils.getJsObjectByName(modelBuilder.getCurrentDeclarationFunction(), name.getName()); |
| } |
| } else { |
| if (fqName.size() == 1) { |
| alreadyThere = ModelUtils.getScopeVariable(modelBuilder.getCurrentDeclarationScope(), name.getName()); |
| } |
| if (alreadyThere == null) { |
| alreadyThere = ModelUtils.getJsObject(modelBuilder, fqName, true); |
| } |
| } |
| } |
| |
| objectScope = (alreadyThere == null) |
| ? ModelElementFactory.create(parserResult, objectNode, fqName, modelBuilder, isDeclaredInParent) |
| : (JsObjectImpl)alreadyThere; |
| if (alreadyThere != null) { |
| ((JsObjectImpl)alreadyThere).addOccurrence(name.getOffsetRange()); |
| } |
| } |
| if (objectScope != null) { |
| objectScope.setJsKind(JsElement.Kind.OBJECT_LITERAL); |
| if (!objectScope.isDeclared()) { |
| // the objec literal is always declared |
| objectScope.setDeclared(true); |
| } |
| modelBuilder.setCurrentObject(objectScope); |
| if (isPrivate) { |
| objectScope.getModifiers().remove(Modifier.PUBLIC); |
| objectScope.getModifiers().add(Modifier.PRIVATE); |
| } |
| } |
| } else { |
| JsObjectImpl objectScope = ModelElementFactory.createAnonymousObject(parserResult, objectNode, modelBuilder); |
| modelBuilder.setCurrentObject(objectScope); |
| } |
| |
| } |
| |
| return super.enterObjectNode(objectNode); |
| } |
| |
| @Override |
| public Node leaveObjectNode(ObjectNode objectNode) { |
| Node lastVisited = getPreviousFromPath(2); |
| |
| if (lastVisited instanceof BinaryNode) { |
| BinaryNode bNode = (BinaryNode)lastVisited; |
| if (bNode.lhs().equals(objectNode)) { |
| // case of destructuring assignment { a, b} = .... |
| // we dob't create object in the model, but process the property nodes -> skip reseting modelBuilder |
| return super.leaveObjectNode(objectNode); |
| } |
| } |
| modelBuilder.reset(); |
| return super.leaveObjectNode(objectNode); |
| } |
| |
| @Override |
| public boolean enterPropertyNode(PropertyNode propertyNode) { |
| final Expression key = propertyNode.getKey(); |
| final Expression value = propertyNode.getValue(); |
| List<Expression> decorators = propertyNode.getDecorators(); |
| if (decorators != null && !decorators.isEmpty()) { |
| for (Expression decorator : decorators) { |
| if (decorator instanceof IdentNode) { |
| // in such case, this is probaly a function |
| addOccurence((IdentNode)decorator, false, true); |
| } else { |
| decorator.accept(this); |
| } |
| } |
| } |
| if ((key instanceof IdentNode || key instanceof LiteralNode) |
| && !(value instanceof ObjectNode |
| || value instanceof FunctionNode) |
| && !propertyNode.isComputed()) { |
| final JsObjectImpl parent = modelBuilder.getCurrentObject(); |
| Identifier name = null; |
| if (key instanceof IdentNode) { |
| name = ModelElementFactory.create(parserResult, (IdentNode)key); |
| } else if (key instanceof LiteralNode) { |
| name = ModelElementFactory.create(parserResult, (LiteralNode)key); |
| } |
| if (name != null) { |
| if (key instanceof IdentNode && value instanceof IdentNode) { |
| IdentNode iKey = (IdentNode)key; |
| IdentNode iValue = (IdentNode)value; |
| if (iKey.getName().equals(iValue.getName()) && iKey.getStart() == iValue.getStart() && iKey.getFinish() == iValue.getFinish()) { |
| // it's object initializer shorthand property names |
| // (ES6) var o = { a, b, c }; The variables a, b and c has to exists and properties are references to the orig var |
| JsObject variable = ModelUtils.getScopeVariable(modelBuilder.getCurrentDeclarationScope(), name.getName()); |
| if (variable != null) { |
| parent.addProperty(variable.getName(), variable); |
| variable.addOccurrence(name.getOffsetRange()); |
| // don't continue. |
| return false; |
| } |
| } |
| } |
| JsObjectImpl property = (JsObjectImpl)parent.getProperty(name.getName()); |
| if (property == null) { |
| if (parent.getJSKind() == JsElement.Kind.OBJECT_LITERAL || parent.getJSKind() == JsElement.Kind.ANONYMOUS_OBJECT) { |
| property = ModelElementFactory.create(parserResult, propertyNode, name, modelBuilder, true); |
| } else { |
| // the object literal was not created before property node, |
| // so it can be destructive assignment on the left side |
| |
| // find the block node to decide whether's the property node are not |
| // parameters defined via destructive assignment |
| // case function drawES6Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25} = test) {} |
| int index = 1; |
| Node node = getPreviousFromPath(index); |
| BinaryNode bNode = null; |
| ObjectNode oNode = null; |
| while (index < getPath().size() && !(node instanceof Block)) { |
| if (bNode == null && node instanceof BinaryNode) { |
| bNode = (BinaryNode)node; |
| } else if (oNode == null && node instanceof ObjectNode) { |
| oNode = (ObjectNode)node; |
| } |
| node = getPreviousFromPath(++index); |
| } |
| boolean isDestructiveParam = false; |
| if (node instanceof Block) { |
| Block block = (Block)node; |
| if (block.isParameterBlock() && oNode != null && bNode != null && bNode.lhs().equals(oNode) ) { |
| // we are in parameters defined via destructive assignment |
| JsFunction currentFnImpl = modelBuilder.getCurrentDeclarationFunction(); |
| property = (JsObjectImpl)currentFnImpl.getParameter(name.getName()); |
| isDestructiveParam = true; |
| } |
| } |
| if (!isDestructiveParam) { |
| property = ModelElementFactory.create(parserResult, propertyNode, name, modelBuilder, true); |
| } |
| } |
| } else { |
| // The property can be already defined, via a usage before declaration (see testfiles/model/simpleObject.js - called property) |
| JsObjectImpl newProperty = ModelElementFactory.create(parserResult, propertyNode, name, modelBuilder, true); |
| if (newProperty != null) { |
| newProperty.addOccurrence(property.getDeclarationName().getOffsetRange()); |
| for(Occurrence occurrence : property.getOccurrences()) { |
| newProperty.addOccurrence(occurrence.getOffsetRange()); |
| } |
| property = newProperty; |
| } |
| } |
| |
| if (property != null) { |
| // if (propertyNode.getGetter() != null) { |
| // FunctionNode getter = ((FunctionNode)((ReferenceNode)propertyNode.getGetter()).getReference()); |
| // property.addOccurrence(new OffsetRange(getter.getIdent().getStart(), getter.getIdent().getFinish())); |
| // } |
| // |
| // if (propertyNode.getSetter() != null) { |
| // FunctionNode setter = ((FunctionNode)((ReferenceNode)propertyNode.getSetter()).getReference()); |
| // property.addOccurrence(new OffsetRange(setter.getIdent().getStart(), setter.getIdent().getFinish())); |
| // } |
| property.getParent().addProperty(name.getName(), property); |
| property.setDeclared(true); |
| if(value instanceof CallNode) { |
| // TODO for now, don't continue. There shoudl be handled cases liek |
| // in the testFiles/model/property02.js file |
| //return null; |
| } else { |
| Collection<TypeUsage> types = ModelUtils.resolveSemiTypeOfExpression(modelBuilder, value); |
| if (!types.isEmpty()) { |
| property.addAssignment(types, name.getOffsetRange().getStart()); |
| } |
| if (value instanceof IdentNode) { |
| IdentNode iNode = (IdentNode)value; |
| if (!iNode.getPropertyName().equals(name.getName())) { |
| addOccurence((IdentNode)value, false); |
| } else { |
| // handling case like property: property |
| if (parent.getParent() != null) { |
| occurrenceBuilder.addOccurrence(name.getName(), getOffsetRange(iNode), modelBuilder.getCurrentDeclarationScope(), parent.getParent(), modelBuilder.getCurrentWith(), false, false); |
| } |
| } |
| } |
| } |
| } |
| } |
| } if (propertyNode.isComputed()) { |
| propertyNode.getKey().accept(this); |
| } |
| return super.enterPropertyNode(propertyNode); |
| } |
| // |
| // @Override |
| // public Node enter(ReferenceNode referenceNode) { |
| // FunctionNode reference = referenceNode.getReference(); |
| // if (reference != null) { |
| // Node lastNode = getPreviousFromPath(1); |
| // if (!((lastNode instanceof VarNode) && !reference.isAnonymous())) { |
| // if (lastNode instanceof BinaryNode && !reference.isAnonymous()) { |
| // Node lhs = ((BinaryNode)lastNode).lhs(); |
| // List<Identifier> nodeName = getNodeName(lhs, parserResult); |
| // if (nodeName != null && !nodeName.isEmpty()) { |
| // JsObject jsObject = null; |
| // if (ModelUtils.THIS.equals(nodeName.get(0).getName())) { //NOI18N |
| // jsObject = resolveThis(modelBuilder.getCurrentObject()); |
| // for (int i = 1; jsObject != null && i < nodeName.size(); i++ ) { |
| // jsObject = jsObject.getProperty(nodeName.get(i).getName()); |
| // } |
| // } else { |
| // jsObject = ModelUtils.getJsObject(modelBuilder, nodeName, true); |
| // } |
| // if (jsObject != null) { |
| // Identifier name = nodeName.get(nodeName.size() - 1); |
| // DeclarationScopeImpl ds = modelBuilder.getCurrentDeclarationScope(); |
| // String referenceName = reference.getIdent().getName(); |
| // JsObject originalFnc = ds.getProperty(referenceName); |
| // while (originalFnc != null && !(originalFnc instanceof JsFunction)) { |
| // if (ds.getParentScope() != null) { |
| // ds = (DeclarationScopeImpl)ds.getParentScope(); |
| // originalFnc = ds.getProperty(referenceName); |
| // } else { |
| // originalFnc = null; |
| // } |
| // } |
| // if (originalFnc != null && originalFnc instanceof JsFunction) { |
| // //property contains the definition of the function |
| // JsObject newRef = new JsFunctionReference(jsObject.getParent(), name, (JsFunction)originalFnc, true, jsObject.getModifiers()); |
| // jsObject.getParent().addProperty(jsObject.getName(), newRef); |
| // for (Occurrence occurence : jsObject.getOccurrences()) { |
| // newRef.addOccurrence(occurence.getOffsetRange()); |
| // } |
| // if (originalFnc instanceof JsFunctionImpl) { |
| //// ((JsFunctionImpl)originalFnc).setAnonymous(true); |
| // JsObject parent = jsObject.getParent(); |
| // if (ModelUtils.PROTOTYPE.equals(parent.getName())) { |
| // parent = parent.getParent(); |
| // } |
| // if (parent != null) { |
| // Collection<JsObject> propertiesCopy = new ArrayList(originalFnc.getProperties().values()); |
| // for (JsObject property : propertiesCopy) { |
| // if (!property.getModifiers().contains(Modifier.PRIVATE)) { |
| // ModelUtils.moveProperty(parent, property); |
| // } |
| // } |
| // } |
| // } |
| // |
| // } |
| // |
| // } |
| // } |
| // } else { |
| // addToPath(referenceNode); |
| // reference.accept(this); |
| // removeFromPathTheLast(); |
| // } |
| // } |
| // return null; |
| // } |
| // return super.enter(referenceNode); |
| // } |
| // |
| @Override |
| public boolean enterReturnNode(ReturnNode returnNode) { |
| Node expression = returnNode.getExpression(); |
| Collection<TypeUsage> types = ModelUtils.resolveSemiTypeOfExpression(modelBuilder, expression); |
| if (expression == null) { |
| types.add(new TypeUsage(Type.UNDEFINED, returnNode.getStart(), true)); |
| } else { |
| if (expression instanceof IdentNode) { |
| addOccurence((IdentNode)expression, false); |
| } |
| if(types.isEmpty()) { |
| types.add(new TypeUsage(Type.UNRESOLVED, returnNode.getStart(), true)); |
| } |
| } |
| JsFunctionImpl function = modelBuilder.getCurrentDeclarationFunction(); |
| function.addReturnType(types); |
| return super.enterReturnNode(returnNode); |
| } |
| |
| @Override |
| public boolean enterTernaryNode(TernaryNode ternaryNode) { |
| if (ternaryNode.getTest() instanceof IdentNode) { |
| addOccurence((IdentNode)ternaryNode.getTest(), false); |
| } |
| if (ternaryNode.getTrueExpression().getExpression() instanceof IdentNode) { |
| addOccurence((IdentNode)ternaryNode.getTrueExpression().getExpression(), false); |
| } |
| if (ternaryNode.getFalseExpression().getExpression() instanceof IdentNode) { |
| addOccurence((IdentNode)ternaryNode.getFalseExpression().getExpression(), false); |
| } |
| return super.enterTernaryNode(ternaryNode); |
| } |
| |
| @Override |
| public boolean enterUnaryNode(UnaryNode unaryNode) { |
| if (unaryNode.getExpression() instanceof IdentNode) { |
| addOccurence((IdentNode) unaryNode.getExpression(), false); |
| } |
| return super.enterUnaryNode(unaryNode); |
| } |
| |
| @Override |
| public boolean enterVarNode(VarNode varNode) { |
| Node init = varNode.getInit(); |
| FunctionNode rNode = null; |
| if (init instanceof FunctionNode) { |
| rNode = (FunctionNode)init; |
| } else if (init instanceof BinaryNode) { |
| // this should handle cases like |
| // var prom = another.prom = function prom() {} |
| BinaryNode bNode = (BinaryNode)init; |
| while (bNode.rhs() instanceof BinaryNode ) { |
| bNode = (BinaryNode)bNode.rhs(); |
| } |
| if (bNode.rhs() instanceof FunctionNode) { |
| rNode = (FunctionNode) bNode.rhs(); |
| } |
| } else if (init instanceof UnaryNode && ((UnaryNode)init).getExpression() instanceof CallNode |
| && ((CallNode)((UnaryNode)init).getExpression()).getFunction() instanceof FunctionNode) { |
| rNode = (FunctionNode)((CallNode)((UnaryNode)init).getExpression()).getFunction(); |
| // Identifier varName = new Identifier(varNode.getName().getName(), getOffsetRange(varNode.getName())); |
| // OffsetRange range = varNode.getInit() instanceof ObjectNode ? new OffsetRange(varNode.getName().getStart(), ((ObjectNode)varNode.getInit()).getFinish()) |
| // : varName.getOffsetRange(); |
| // JsObjectImpl parentFn = modelBuilder.getCurrentDeclarationFunction(); |
| // JsObject variable = handleArrayCreation(varNode.getInit(), parentFn, varName); |
| // if (variable == null) { |
| // JsObjectImpl newObject = new JsObjectImpl(parentFn, varName, range, parserResult.getSnapshot().getMimeType(), null); |
| // variable = newObject; |
| // } |
| // variable.addOccurrence(varName.getOffsetRange()); |
| // JsObject property = parentFn.getProperty(varName.getName()); |
| // parentFn.addProperty(varName.getName(), variable); |
| // variable.addProperty(property.getName(), property); |
| // Collection<TypeUsage> returns = ModelUtils.resolveSemiTypeOfExpression(modelBuilder, init); |
| // for (TypeUsage type : returns) { |
| // variable.addAssignment(type, init.getStart()); |
| // } |
| // if (rNode.isNamedFunctionExpression() && rNode.getName().equals(varName.getName())) { |
| // // the name of function is the same as the variable |
| // // var MyLib = new function MyLib() {}; |
| // ModelUtils.copyOccurrences(property, variable); |
| // } |
| |
| } |
| if (!(init instanceof ObjectNode || rNode != null |
| || init instanceof LiteralNode.ArrayLiteralNode |
| || init instanceof ClassNode |
| || varNode.isExport())) { |
| JsObject parent = modelBuilder.getCurrentObject(); |
| //parent = canBeSingletonPattern(1) ? resolveThis(parent) : parent; |
| if (parent instanceof CatchBlockImpl) { |
| parent = parent.getParent(); |
| } |
| while (parent instanceof JsWith) { |
| parent = parent.getParent(); |
| } |
| JsObjectImpl variable = (JsObjectImpl)parent.getProperty(varNode.getName().getName()); |
| Identifier name = ModelElementFactory.create(parserResult, varNode.getName()); |
| if (name != null) { |
| if (variable == null) { |
| // variable si not defined, so it has to be from global scope |
| // or from a code structure like for cycle |
| // or it can be from parameter block |
| |
| Node lastVisited = getPreviousFromPath(1); |
| if (parent instanceof JsFunctionImpl && lastVisited instanceof Block && ((Block)lastVisited).isParameterBlock()) { |
| // it's a parameter definition |
| variable = new ParameterObject(parent, name, parent.getMimeType(), parent.getSourceLabel()); |
| ((JsFunctionImpl)parent).addParameter(variable); |
| } else { |
| variable = new JsObjectImpl(parent, name, name.getOffsetRange(), |
| true, parserResult.getSnapshot().getMimeType(), null); |
| parent.addProperty(name.getName(), variable); |
| } |
| if (parent.getJSKind() != JsElement.Kind.FILE) { |
| variable.getModifiers().remove(Modifier.PUBLIC); |
| variable.getModifiers().add(Modifier.PRIVATE); |
| } |
| variable.addOccurrence(name.getOffsetRange()); |
| } else if (!variable.isDeclared()){ |
| // the variable was probably created as temporary before, now we |
| // need to replace it with the real one |
| JsObjectImpl newVariable = new JsObjectImpl(parent, name, name.getOffsetRange(), |
| true, parserResult.getSnapshot().getMimeType(), null); |
| newVariable.addOccurrence(name.getOffsetRange()); |
| for(String propertyName: variable.getProperties().keySet()) { |
| JsObject property = variable.getProperty(propertyName); |
| if (property instanceof JsObjectImpl) { |
| ((JsObjectImpl)property).setParent(newVariable); |
| } |
| newVariable.addProperty(propertyName, property); |
| } |
| if (parent.getJSKind() != JsElement.Kind.FILE) { |
| newVariable.getModifiers().remove(Modifier.PUBLIC); |
| newVariable.getModifiers().add(Modifier.PRIVATE); |
| } |
| for(TypeUsage type : variable.getAssignments()) { |
| newVariable.addAssignment(type, type.getOffset()); |
| } |
| for(Occurrence occurrence: variable.getOccurrences()){ |
| newVariable.addOccurrence(occurrence.getOffsetRange()); |
| } |
| parent.addProperty(name.getName(), newVariable); |
| variable = newVariable; |
| } |
| JsDocumentationHolder docHolder = JsDocumentationSupport.getDocumentationHolder(parserResult); |
| variable.setDeprecated(docHolder.isDeprecated(varNode)); |
| variable.setDocumentation(docHolder.getDocumentation(varNode)); |
| if (docHolder.isConstant(varNode)) { |
| variable.setJsKind(JsElement.Kind.CONSTANT); |
| } |
| if (init instanceof IdentNode) { |
| IdentNode iNode = (IdentNode)init; |
| if (!iNode.getName().equals(variable.getName())) { |
| addOccurrence((IdentNode)init, variable.getName()); |
| } else { |
| // the name of variable is the same as already existing function or var or parameter |
| JsFunctionImpl currentFunction = modelBuilder.getCurrentDeclarationFunction(); |
| if (currentFunction != null && currentFunction.getParameter(variable.getName()) != null) { |
| // it's a parameter |
| addOccurrence((IdentNode)init, variable.getName()); |
| } else { |
| variable.addOccurrence(getOffsetRange(iNode)); |
| } |
| } |
| |
| } |
| modelBuilder.setCurrentObject(variable); |
| Collection<TypeUsage> types = ModelUtils.resolveSemiTypeOfExpression(modelBuilder, init); |
| if (modelBuilder.getCurrentWith() != null) { |
| ((JsWithObjectImpl)modelBuilder.getCurrentWith()).addObjectWithAssignment(variable); |
| } |
| for (TypeUsage type : types) { |
| variable.addAssignment(type, varNode.getName().getFinish()); |
| } |
| List<Type> returnTypes = docHolder.getReturnType(varNode); |
| if (returnTypes != null && !returnTypes.isEmpty()) { |
| for (Type type : returnTypes) { |
| variable.addAssignment(new TypeUsage(type.getType(), type.getOffset(), true), varNode.getName().getFinish()); |
| } |
| } |
| if (varNode.isConst()) { |
| variable.setJsKind(JsElement.Kind.CONSTANT); |
| } |
| } |
| } else if(init instanceof ObjectNode && !varNode.isExport()) { |
| JsObjectImpl function = modelBuilder.getCurrentDeclarationFunction(); |
| Identifier name = ModelElementFactory.create(parserResult, varNode.getName()); |
| if (name != null) { |
| JsObjectImpl variable = (JsObjectImpl)function.getProperty(name.getName()); |
| if (variable != null) { |
| variable.setDeclared(true); |
| } else { |
| List<Identifier> fqName = getName(varNode, parserResult); |
| variable = ModelElementFactory.create(parserResult, (ObjectNode)varNode.getInit(), fqName, modelBuilder, true); |
| } |
| if (variable != null) { |
| variable.setJsKind(JsElement.Kind.OBJECT_LITERAL); |
| modelBuilder.setCurrentObject(variable); |
| } |
| } |
| } else if(init instanceof ObjectNode && varNode.isExport()) { |
| // we are expecting here that the var node is artificial and is created due to: export default {} |
| return false; |
| } else if (init instanceof ClassNode) { |
| ClassNode cNode = (ClassNode) init; |
| // process decorators |
| List<Expression> decorators = cNode.getDecorators(); |
| if (decorators != null && !decorators.isEmpty()) { |
| for (Expression decorator : decorators) { |
| if (decorator instanceof IdentNode) { |
| // in such case, this is probaly a function |
| addOccurence((IdentNode)decorator, false, true); |
| } else { |
| decorator.accept(this); |
| } |
| } |
| } |
| } |
| return super.enterVarNode(varNode); |
| } |
| |
| @Override |
| public Node leaveVarNode(VarNode varNode) { |
| Node init = varNode.getInit(); |
| FunctionNode rNode = null; |
| if (init instanceof BinaryNode) { |
| // this should handle cases like |
| // var prom = another.prom = function prom() {} |
| BinaryNode bNode = (BinaryNode)init; |
| while (bNode.rhs() instanceof BinaryNode ) { |
| bNode = (BinaryNode)bNode.rhs(); |
| } |
| if (bNode.rhs() instanceof FunctionNode) { |
| // this should handle cases like |
| // var prom = another.prom = function prom() {} |
| rNode = (FunctionNode) bNode.rhs(); |
| List<Identifier> name = getNodeName(bNode.lhs(), parserResult); |
| if (name != null && !name.isEmpty()) { |
| boolean isPriviliged = name.get(0).getName().equals(ModelUtils.THIS); |
| JsObject parent = isPriviliged ? resolveThis(modelBuilder.getCurrentObject()) : modelBuilder.getCurrentObject(); |
| for (int i = isPriviliged ? 1 : 0; parent != null && i < name.size(); i++) { |
| parent = parent.getProperty(name.get(i).getName()); |
| } |
| if (parent!= null && parent instanceof JsFunction) { |
| Identifier propertyName = create(parserResult, varNode.getName()); |
| Set<Modifier> modifiers; |
| if (isPriviliged) { |
| modifiers = EnumSet.of(Modifier.PROTECTED); |
| } else if (modelBuilder.getCurrentDeclarationFunction().getJSKind() == JsElement.Kind.FILE) { |
| modifiers = EnumSet.of(Modifier.PUBLIC); |
| } else { |
| modifiers = EnumSet.of(Modifier.PRIVATE); |
| } |
| JsObject property = new JsFunctionReference(modelBuilder.getCurrentObject(), propertyName, (JsFunction)parent, true, modifiers); |
| modelBuilder.getCurrentObject().addProperty(propertyName.getName(), property); |
| property.addOccurrence(propertyName.getOffsetRange()); |
| } |
| } |
| |
| } |
| // if (bNode.rhs() instanceof ReferenceNode /*&& bNode.tokenType() == TokenType.ASSIGN*/) { |
| // init = (ReferenceNode) bNode.rhs(); |
| // } |
| } else if (init instanceof FunctionNode) { |
| rNode = (FunctionNode)init; |
| } else if (init instanceof UnaryNode && ((UnaryNode)init).getExpression() instanceof CallNode |
| && ((CallNode)((UnaryNode)init).getExpression()).getFunction() instanceof FunctionNode) { |
| rNode = (FunctionNode)((CallNode)((UnaryNode)init).getExpression()).getFunction(); |
| } |
| if (!(rNode != null || init instanceof LiteralNode.ArrayLiteralNode) |
| // XXX can we avoid creation of object ? |
| && ModelElementFactory.create(parserResult, varNode.getName()) != null) { |
| JsDocumentationHolder docHolder = JsDocumentationSupport.getDocumentationHolder(parserResult); |
| List<DocParameter> properties = docHolder.getProperties(varNode); |
| for (DocParameter docProperty : properties) { |
| String propertyName = docProperty.getParamName().getName(); |
| String names[]; |
| int delta = 0; |
| if (propertyName.indexOf('.') > 0) { |
| names = propertyName.split("\\."); |
| } else { |
| names = new String[]{propertyName}; |
| } |
| JsObject parent = modelBuilder.getCurrentObject(); |
| for (int i = 0; i < names.length; i++) { |
| String name = names[i]; |
| JsObject property = parent.getProperty(name); |
| int startOffset = docProperty.getParamName().getOffsetRange().getStart() + delta; |
| int endOffset = startOffset + name.length(); |
| OffsetRange offsetRange = new OffsetRange(startOffset, endOffset); |
| if (property == null) { |
| Identifier iden = new Identifier(name, offsetRange); |
| property = new JsObjectImpl(parent, iden, offsetRange, true, JsTokenId.JAVASCRIPT_MIME_TYPE, null); |
| parent.addProperty(name, property); |
| } |
| property.addOccurrence(offsetRange); |
| if (i == names.length - 1) { |
| for (Type type : docProperty.getParamTypes()) { |
| property.addAssignment(new TypeUsage(type.getType(), endOffset), endOffset); |
| } |
| } |
| delta = delta + name.length() + 1; |
| parent = property; |
| |
| } |
| |
| |
| } |
| modelBuilder.reset(); |
| } |
| return super.leaveVarNode(varNode); |
| } |
| |
| @Override |
| public boolean enterWithNode(WithNode withNode) { |
| JsObjectImpl currentObject = modelBuilder.getCurrentObject(); |
| Collection<TypeUsage> types = ModelUtils.resolveSemiTypeOfExpression(modelBuilder, withNode.getExpression()); |
| JsWithObjectImpl withObject = new JsWithObjectImpl(currentObject, modelBuilder.getUnigueNameForWithObject(), types, new OffsetRange(withNode.getStart(), withNode.getFinish()), |
| new OffsetRange(withNode.getExpression().getStart(), withNode.getExpression().getFinish()), modelBuilder.getCurrentWith(), parserResult.getSnapshot().getMimeType(), null); |
| currentObject.addProperty(withObject.getName(), withObject); |
| // withNode.getExpression().accept(this); // expression should be visted when the with object is the current object. |
| modelBuilder.setCurrentObject(withObject); |
| withNode.getBody().accept(this); |
| modelBuilder.reset(); |
| return false; |
| } |
| |
| //--------------------------------End of visit methods-------------------------------------- |
| |
| public Map<FunctionInterceptor, Collection<FunctionCall>> getCallsForProcessing() { |
| return functionCalls; |
| } |
| |
| private boolean fillName(AccessNode node, List<String> result) { |
| List<Identifier> fqn = getName(node, parserResult); |
| if (fqn != null) { |
| for (int i = fqn.size() - 1; i >= 0; i--) { |
| result.add(0, fqn.get(i).getName()); |
| } |
| } |
| |
| JsObject current = modelBuilder.getCurrentObject(); |
| while (current != null && current.getDeclarationName() != null) { |
| if (current != modelBuilder.getGlobal()) { |
| result.add(0, current.getDeclarationName().getName()); |
| } |
| current = current.getParent(); |
| } |
| return true; |
| } |
| |
| private boolean fillName(IndexNode node, List<String> result) { |
| Node index = node.getIndex(); |
| Node base = node.getBase(); |
| if (index instanceof LiteralNode && base instanceof AccessNode) { |
| LiteralNode literal = (LiteralNode) index; |
| if (literal.isString()) { |
| result.add(0, literal.getString()); |
| List<Identifier> fqn = getName((AccessNode) base, parserResult); |
| for (int i = fqn.size() - 1; i >= 0; i--) { |
| result.add(0, fqn.get(i).getName()); |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private List<Identifier> getName(PropertyNode propertyNode) { |
| List<Identifier> name = new ArrayList<>(1); |
| if (propertyNode.getGetter() != null || propertyNode.getSetter() != null) { |
| // check whether this is not defining getter or setter of a property. |
| Node previousNode = getPreviousFromPath(1); |
| if (previousNode instanceof FunctionNode) { |
| FunctionNode fNode = (FunctionNode)previousNode; |
| String fName = fNode.getIdent().getName(); |
| if (fName.startsWith("get ") || fName.startsWith("set ")) { //NOI18N |
| name.add(new Identifier(fName, |
| new OffsetRange(fNode.getIdent().getStart(), fNode.getIdent().getFinish()))); |
| return name; |
| } |
| } |
| } |
| return getName(propertyNode, parserResult); |
| } |
| |
| private static List<Identifier> getName(PropertyNode propertyNode, ParserResult parserResult) { |
| List<Identifier> name = new ArrayList<>(1); |
| if (propertyNode.getKey() instanceof IdentNode) { |
| IdentNode ident = (IdentNode) propertyNode.getKey(); |
| name.add(new Identifier(ident.getName(), getOffsetRange(ident))); |
| } else if (propertyNode.getKey() instanceof LiteralNode){ |
| LiteralNode lNode = (LiteralNode)propertyNode.getKey(); |
| name.add(new Identifier(lNode.getString(), |
| new OffsetRange(lNode.getStart(), lNode.getFinish()))); |
| } |
| return name; |
| } |
| |
| private static List<Identifier> getName(VarNode varNode, ParserResult parserResult) { |
| List<Identifier> name = new ArrayList<>(); |
| name.add(new Identifier(varNode.getName().getName(), |
| new OffsetRange(varNode.getName().getStart(), varNode.getName().getFinish()))); |
| return name; |
| } |
| |
| private static List<Identifier> getName(BinaryNode binaryNode, ParserResult parserResult) { |
| List<Identifier> name = new ArrayList<>(); |
| Node lhs = binaryNode.lhs(); |
| if (lhs instanceof AccessNode) { |
| name = getName((AccessNode)lhs, parserResult); |
| } else if (lhs instanceof IdentNode) { |
| IdentNode ident = (IdentNode) lhs; |
| name.add(new Identifier(ident.getName(), getOffsetRange(ident))); |
| } else if (lhs instanceof IndexNode) { |
| IndexNode indexNode = (IndexNode)lhs; |
| if (indexNode.getBase() instanceof AccessNode) { |
| List<Identifier> aName = getName((AccessNode)indexNode.getBase(), parserResult); |
| if (aName != null) { |
| name.addAll(getName((AccessNode)indexNode.getBase(), parserResult)); |
| } |
| else { |
| return null; |
| } |
| } else if (indexNode.getBase() instanceof IdentNode) { |
| name.add(create(parserResult, (IdentNode)indexNode.getBase())); |
| } |
| if (indexNode.getIndex() instanceof LiteralNode) { |
| LiteralNode lNode = (LiteralNode)indexNode.getIndex(); |
| name.add(new Identifier(lNode.getPropertyName(), |
| new OffsetRange(lNode.getStart(), lNode.getFinish()))); |
| } else if (indexNode.getIndex() instanceof IdentNode) { |
| // this is case test[variable] where we don't know the name -> return null |
| return null; |
| } |
| } |
| return name; |
| } |
| |
| private static List<Identifier> getName(AccessNode aNode, ParserResult parserResult) { |
| List<Identifier> name = new ArrayList<>(); |
| name.add(new Identifier(aNode.getProperty(), |
| new OffsetRange(aNode.getFinish() - aNode.getProperty().length(), aNode.getFinish()))); |
| Node base = aNode.getBase(); |
| while (base instanceof AccessNode || base instanceof CallNode || base instanceof IndexNode) { |
| if (base instanceof CallNode) { |
| CallNode cNode = (CallNode)base; |
| base = cNode.getFunction(); |
| } else if (base instanceof IndexNode) { |
| IndexNode iNode = (IndexNode) base; |
| if (iNode.getIndex() instanceof LiteralNode) { |
| LiteralNode lNode = (LiteralNode)iNode.getIndex(); |
| if (lNode.isString()) { |
| name.add(new Identifier(lNode.getPropertyName(), |
| new OffsetRange(lNode.getStart(), lNode.getFinish()))); |
| } |
| } else { |
| return null; |
| } |
| base = iNode.getBase(); |
| } |
| if (base instanceof AccessNode) { |
| AccessNode aaNode = (AccessNode)base; |
| base = aaNode.getBase(); |
| name.add(new Identifier(aaNode.getProperty(), |
| new OffsetRange(aaNode.getFinish() - aaNode.getProperty().length(), aaNode.getFinish()))); |
| } |
| } |
| Identifier baseIdent = null; |
| if (base instanceof IdentNode) { |
| IdentNode ident = (IdentNode) base; |
| baseIdent = new Identifier(ident.getName(), getOffsetRange(ident)); |
| } else if (base instanceof LiteralNode) { |
| // we fake Number object to handel mark occurrences |
| LiteralNode lNode= (LiteralNode) base; |
| if (lNode.isNumeric()) { |
| baseIdent = new Identifier("Number", OffsetRange.NONE); //NOI8N |
| } |
| } |
| |
| |
| if (baseIdent != null) { |
| name.add(baseIdent); |
| Collections.reverse(name); |
| return name; |
| } else { |
| return null; |
| } |
| } |
| |
| private JsObject createJsObject(AccessNode accessNode, ParserResult parserResult, ModelBuilder modelBuilder) { |
| List<Identifier> fqn = getName(accessNode, parserResult); |
| if (fqn == null) { |
| return null; |
| } |
| JsObject object = null; |
| |
| Identifier name = fqn.get(0); |
| if (!ModelUtils.THIS.equals(fqn.get(0).getName())) { |
| if (modelBuilder.getCurrentWith() == null) { |
| DeclarationScopeImpl currentDS = modelBuilder.getCurrentDeclarationScope(); |
| JsObject variable = ModelUtils.getScopeVariable(currentDS, name.getName()); |
| if (variable != null) { |
| if (variable instanceof ParameterObject || variable.getModifiers().contains(Modifier.PRIVATE)) { |
| object = (JsObjectImpl) variable; |
| } else { |
| DeclarationScope variableDS = ModelUtils.getDeclarationScope(variable); |
| if (!variableDS.equals(currentDS)) { |
| object = (JsObjectImpl) variable; |
| } else if (currentDS.getProperty(name.getName()) != null) { |
| Node lastNode = getPreviousFromPath(2); |
| if (lastNode instanceof BinaryNode) { |
| BinaryNode bNode = (BinaryNode) lastNode; |
| if (bNode.lhs().equals(accessNode)) { |
| object = (JsObjectImpl) variable; |
| } |
| } |
| } |
| } |
| } |
| if (object == null) { |
| JsObject global = modelBuilder.getGlobal(); |
| object = (JsObjectImpl)global.getProperty(name.getName()); |
| if (object == null) { |
| object = new JsObjectImpl(global, name, name.getOffsetRange(), false, global.getMimeType(), global.getSourceLabel()); |
| global.addProperty(name.getName(), object); |
| } |
| } |
| } else { |
| JsObject withObject = modelBuilder.getCurrentWith(); |
| object = (JsObjectImpl)withObject.getProperty(name.getName()); |
| if (object == null) { |
| object = new JsObjectImpl(withObject, name, name.getOffsetRange(), false, parserResult.getSnapshot().getMimeType(), null); |
| withObject.addProperty(name.getName(), object); |
| } |
| } |
| object.addOccurrence(name.getOffsetRange()); |
| } else { |
| JsObject current = modelBuilder.getCurrentDeclarationFunction(); |
| object = (JsObjectImpl)resolveThis(current); |
| if (object != null) { |
| // find out, whether is not defined in prototype |
| if (object.getProperty(fqn.get(1).getName()) == null) { |
| JsObject prototype = object.getProperty(ModelUtils.PROTOTYPE); |
| if (prototype != null && prototype.getProperty(fqn.get(1).getName()) != null) { |
| object = prototype; |
| } |
| } |
| } |
| if (object != null && fqn.size() == 2) { |
| // try to handle case |
| // function MyF() { |
| // this.f1 = f1; |
| // function f1() {}; |
| // } |
| // in such case the name after this has to be equal to the declared function. |
| // -> in the model, just change the f1 from private to privilaged. |
| String lastName = fqn.get(1).getName(); |
| JsObjectImpl property = (JsObjectImpl)object.getProperty(lastName); |
| Node lastVisited = getPreviousFromPath(2); |
| if (property != null && lastName.equals(property.getName()) && (property.getModifiers().contains(Modifier.PRIVATE) && property.getModifiers().size() == 1) |
| && !(lastVisited instanceof CallNode)) { |
| // if there is CallNode, then it like this.xxx() -> don't change modifiers |
| property.getModifiers().remove(Modifier.PRIVATE); |
| property.getModifiers().add(Modifier.PROTECTED); |
| } |
| } |
| } |
| |
| if (object != null) { |
| JsObjectImpl property = null; |
| for (int i = 1; i < fqn.size(); i++) { |
| property = (JsObjectImpl)object.getProperty(fqn.get(i).getName()); |
| if (property != null) { |
| object = property; |
| } |
| } |
| int pathSize = getPath().size(); |
| Node lastVisited = pathSize > 1 ? getPath().get(pathSize - 2) : getPath().get(0); |
| boolean onLeftSite = false; |
| if (lastVisited instanceof BinaryNode) { |
| BinaryNode bNode = (BinaryNode)lastVisited; |
| onLeftSite = bNode.tokenType() == TokenType.ASSIGN && bNode.lhs().equals(accessNode); |
| } |
| String propertyName = accessNode.getProperty(); |
| // there is a problem in the parser. When there is a line comment after access node, then the finish of access node is finish of the line comment |
| int propertyOffsetStart = accessNode.getBase().getFinish() + 1; |
| int propertyOffsetEnd = propertyOffsetStart + propertyName.length(); |
| if (property != null) { |
| OffsetRange range = new OffsetRange(propertyOffsetStart, propertyOffsetEnd); |
| if(onLeftSite && !property.isDeclared()) { |
| property.setDeclared(true); |
| property.setDeclarationName(new Identifier(property.getName(), range)); |
| } |
| property.addOccurrence(range); |
| } else { |
| name = ModelElementFactory.create(parserResult, propertyName, propertyOffsetStart, propertyOffsetEnd); |
| if (name != null) { |
| if (pathSize > 1 && getPath().get(pathSize - 2) instanceof CallNode) { |
| CallNode cNode = (CallNode)getPath().get(pathSize - 2); |
| if (!cNode.getArgs().contains(accessNode)) { |
| property = ModelElementFactory.createVirtualFunction(parserResult, object, name, cNode.getArgs().size()); |
| //property.addOccurrence(name.getOffsetRange()); |
| } else { |
| property = new JsObjectImpl(object, name, name.getOffsetRange(), onLeftSite, parserResult.getSnapshot().getMimeType(), null); |
| property.addOccurrence(name.getOffsetRange()); |
| } |
| } else { |
| boolean setDocumentation = false; |
| if (isPriviliged(accessNode) && getPath().size() > 1 && (getPreviousFromPath(2) instanceof ExpressionStatement || getPreviousFromPath(1) instanceof ExpressionStatement)) { |
| // google style declaration of properties: this.buildingID; |
| onLeftSite = true; |
| setDocumentation = true; |
| } |
| property = new JsObjectImpl(object, name, name.getOffsetRange(), onLeftSite, parserResult.getSnapshot().getMimeType(), null); |
| property.addOccurrence(name.getOffsetRange()); |
| if (setDocumentation) { |
| JsDocumentationHolder docHolder = JsDocumentationSupport.getDocumentationHolder(parserResult); |
| if (docHolder != null) { |
| property.setDocumentation(docHolder.getDocumentation(accessNode)); |
| property.setDeprecated(docHolder.isDeprecated(accessNode)); |
| List<Type> returnTypes = docHolder.getReturnType(accessNode); |
| if (!returnTypes.isEmpty()) { |
| for (Type type : returnTypes) { |
| property.addAssignment(new TypeUsage(type.getType(), type.getOffset(), true), accessNode.getFinish()); |
| } |
| } |
| setModifiersFromDoc(property, docHolder.getModifiers(accessNode)); |
| } |
| } |
| } |
| object.addProperty(name.getName(), property); |
| object = property; |
| } |
| } |
| } |
| return object; |
| } |
| |
| /** |
| * Gets the node name if it has any (case of AccessNode, BinaryNode, VarNode, PropertyNode). |
| * |
| * @param node examined node for getting its name |
| * @return name of the node if it supports it |
| */ |
| public static List<Identifier> getNodeName(Node node, ParserResult parserResult) { |
| if (node instanceof AccessNode) { |
| return getName((AccessNode) node, parserResult); |
| } else if (node instanceof BinaryNode) { |
| return getName((BinaryNode) node, parserResult); |
| } else if (node instanceof VarNode) { |
| return getName((VarNode) node, parserResult); |
| } else if (node instanceof PropertyNode) { |
| return getName((PropertyNode) node, parserResult); |
| } else if (node instanceof IdentNode) { |
| IdentNode ident = ((IdentNode) node); |
| return Arrays.<Identifier>asList(new Identifier( |
| ident.getName(), getOffsetRange(ident))); |
| } else if (node instanceof FunctionNode) { |
| if (((FunctionNode) node).getKind() == FunctionNode.Kind.SCRIPT) { |
| return Collections.<Identifier>emptyList(); |
| } |
| IdentNode ident = ((FunctionNode) node).getIdent(); |
| return Arrays.<Identifier>asList(new Identifier( |
| ident.getName(), getOffsetRange(ident))); |
| } else { |
| return Collections.<Identifier>emptyList(); |
| } |
| } |
| // |
| //// private Variable findVarWithName(final Scope scope, final String name) { |
| //// Variable result = null; |
| //// Collection<Variable> variables = ScopeImpl.filter(scope.getElements(), new ScopeImpl.ElementFilter() { |
| //// |
| //// @Override |
| //// public boolean isAccepted(ModelElement element) { |
| //// return element.getJSKind().equals(JsElement.Kind.VARIABLE) |
| //// && element.getName().equals(name); |
| //// } |
| //// }); |
| //// |
| //// if (!variables.isEmpty()) { |
| //// result = variables.iterator().next(); |
| //// } else { |
| //// if (!(scope instanceof FileScope)) { |
| //// result = findVarWithName((Scope)scope.getInElement(), name); |
| //// } |
| //// } |
| //// |
| //// return result; |
| //// } |
| //// |
| //// private Field findFieldWithName(FunctionScope function, final String name) { |
| //// Field result = null; |
| //// Collection<? extends Field> fields = function.getFields(); |
| //// result = ModelUtils.getFirst(ModelUtils.getFirst(fields, name)); |
| //// if (result == null && function.getInElement() instanceof FunctionScope) { |
| //// FunctionScope parent = (FunctionScope)function.getInElement(); |
| //// fields = parent.getFields(); |
| //// result = ModelUtils.getFirst(ModelUtils.getFirst(fields, name)); |
| //// } |
| //// return result; |
| //// } |
| // |
| private boolean isInPropertyNode() { |
| boolean inFunction = false; |
| for (int i = getPath().size() - 1; i > 0 ; i--) { |
| final Node node = getPath().get(i); |
| if(node instanceof FunctionNode) { |
| if (!inFunction) { |
| inFunction = true; |
| } else { |
| return false; |
| } |
| } else if (node instanceof PropertyNode) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void addOccurence(IdentNode iNode, boolean leftSite) { |
| addOccurence(iNode, leftSite, false); |
| } |
| |
| private void addOccurence(IdentNode iNode, boolean leftSite, boolean isFunction) { |
| if (!iNode.isDestructuredParameter()) { |
| // skip names of destructured param (it's syntetic) |
| addOccurrence(iNode.getName(), getOffsetRange(iNode), leftSite, isFunction); |
| } |
| } |
| |
| private void addOccurrence(String name, OffsetRange range, boolean leftSite, boolean isFunction) { |
| if (ModelUtils.THIS.equals(name) || Type.UNDEFINED.equals(name)) { |
| // don't process this node and undefined |
| return; |
| } |
| occurrenceBuilder.addOccurrence(name, range, modelBuilder.getCurrentDeclarationScope(), modelBuilder.getCurrentObject(), modelBuilder.getCurrentWith(), isFunction, leftSite); |
| // DeclarationScope scope = modelBuilder.getCurrentDeclarationScope(); |
| // JsObject property = null; |
| // JsObject parameter = null; |
| // JsObject parent = modelBuilder.getCurrentObject(); |
| // if (!(parent instanceof JsWith || (parent.getParent() != null && parent.getParent() instanceof JsWith))) { |
| // while (scope != null && property == null && parameter == null) { |
| // JsFunction function = (JsFunction)scope; |
| // property = function.getProperty(name); |
| // parameter = function.getParameter(name); |
| // scope = scope.getParentScope(); |
| // } |
| // if(parameter != null) { |
| // if (property == null) { |
| // property = parameter; |
| // } else { |
| // if(property.getJSKind() != JsElement.Kind.VARIABLE) { |
| // property = parameter; |
| // } |
| // } |
| // } |
| // } else { |
| // if (!(parent instanceof JsWith) && (parent.getParent() != null && parent.getParent() instanceof JsWith)) { |
| // parent = parent.getParent(); |
| // } |
| // property = parent.getProperty(name); |
| // } |
| // |
| // if (property != null) { |
| // |
| // // occurence in the doc |
| // addDocNameOccurence(((JsObjectImpl)property)); |
| // addDocTypesOccurence(((JsObjectImpl)property)); |
| // |
| // ((JsObjectImpl)property).addOccurrence(range); |
| // } else { |
| // // it's a new global variable? |
| // Identifier nameIden = ModelElementFactory.create(parserResult, name, range.getStart(), range.getEnd()); |
| // if (nameIden != null) { |
| // JsObjectImpl newObject; |
| // if (!(parent instanceof JsWith)) { |
| // parent = modelBuilder.getGlobal(); |
| // } |
| // if (!isFunction) { |
| // newObject = new JsObjectImpl(parent, nameIden, nameIden.getOffsetRange(), |
| // leftSite, parserResult.getSnapshot().getMimeType(), null); |
| // } else { |
| // FileObject fo = parserResult.getSnapshot().getSource().getFileObject(); |
| // newObject = new JsFunctionImpl(fo, parent, nameIden, Collections.EMPTY_LIST, |
| // parserResult.getSnapshot().getMimeType(), null); |
| // } |
| // newObject.addOccurrence(nameIden.getOffsetRange()); |
| // parent.addProperty(nameIden.getName(), newObject); |
| // } |
| // } |
| } |
| |
| /** |
| * Handles adding occurrences in expression like var xxx = xxx or this.xxx = xxx; |
| * @param iNode |
| * @param name |
| */ |
| private void addOccurrence(IdentNode iNode, String name) { |
| String valueName = iNode.getName(); |
| if (!name.equals(valueName)) { |
| addOccurence(iNode, false); |
| } else { |
| DeclarationScope scope = modelBuilder.getCurrentDeclarationScope(); |
| JsObject parameter = null; |
| if (scope instanceof JsFunction) { |
| JsFunction function = (JsFunction)scope; |
| parameter = function.getParameter(iNode.getName()); |
| } |
| if (parameter != null) { |
| parameter.addOccurrence(getOffsetRange(iNode)); |
| } else { |
| boolean found = false; |
| JsObject jsProperty = ((JsObject)scope).getProperty(valueName); |
| if (jsProperty != null && jsProperty.isDeclared()) { |
| found = true; |
| jsProperty.addOccurrence(new OffsetRange(iNode.getStart(), iNode.getFinish())); |
| } else { |
| JsObject jsObject = ModelUtils.getScopeVariable(scope.getParentScope(), valueName); |
| if (jsObject != null) { |
| jsObject.addOccurrence(new OffsetRange(iNode.getStart(), iNode.getFinish())); |
| found = true; |
| } |
| } |
| if (!found) { |
| // new global var? |
| Identifier nameI = ModelElementFactory.create(parserResult, iNode); |
| if (nameI != null) { |
| JsObjectImpl newObject; |
| newObject = new JsObjectImpl(modelBuilder.getGlobal(), nameI, nameI.getOffsetRange(), |
| false, parserResult.getSnapshot().getMimeType(), null); |
| newObject.addOccurrence(nameI.getOffsetRange()); |
| modelBuilder.getGlobal().addProperty(nameI.getName(), newObject); |
| } |
| } |
| } |
| } |
| } |
| |
| private void addDocNameOccurence(JsObjectImpl jsObject) { |
| JsDocumentationHolder holder = JsDocumentationSupport.getDocumentationHolder(parserResult); |
| JsComment comment = holder.getCommentForOffset(jsObject.getOffset(), holder.getCommentBlocks()); |
| if (comment != null) { |
| for (DocParameter docParameter : comment.getParameters()) { |
| Identifier paramName = docParameter.getParamName(); |
| String name = (docParameter.getParamName() == null) ? "" : docParameter.getParamName().getName(); //NOI18N |
| if (name.equals(jsObject.getName())) { |
| jsObject.addOccurrence(paramName.getOffsetRange()); |
| } |
| } |
| } |
| } |
| |
| private void addDocTypesOccurence(JsObjectImpl jsObject) { |
| JsDocumentationHolder holder = JsDocumentationSupport.getDocumentationHolder(parserResult); |
| if (holder.getOccurencesMap().containsKey(jsObject.getName())) { |
| for (OffsetRange offsetRange : holder.getOccurencesMap().get(jsObject.getName())) { |
| ((JsObjectImpl)jsObject).addOccurrence(offsetRange); |
| } |
| } |
| } |
| |
| private boolean belongsTo(JsObject parent, String property) { |
| boolean result = parent.getProperty(property) != null; |
| if (!result && parent instanceof JsFunction) { |
| result = ((JsFunction)parent).getParameter(property) != null; |
| } |
| |
| return result; |
| } |
| |
| private JsObject processLhs(Identifier name, JsObject parent, boolean lastOnLeft) { |
| JsObject lObject = null; |
| if (name != null) { |
| if (ModelUtils.THIS.equals(name.getName())) { |
| return null; |
| } |
| final String newVarName = name.getName(); |
| boolean hasParent = belongsTo(parent, newVarName); |
| boolean hasGrandParent = parent.getJSKind() == JsElement.Kind.METHOD && belongsTo(parent.getParent(), newVarName); |
| if (!hasParent && !hasGrandParent && modelBuilder.getGlobal().getProperty(newVarName) == null) { |
| addOccurrence(name.getName(), name.getOffsetRange(), lastOnLeft, false); |
| } else { |
| if (hasParent) { |
| lObject = parent.getProperty(newVarName); |
| if(lObject == null && parent instanceof JsFunction) { |
| lObject = ((JsFunction)parent).getParameter(newVarName); |
| } |
| } else if (hasGrandParent) { |
| lObject = parent.getParent().getProperty(newVarName); |
| if(lObject == null && parent.getParent() instanceof JsFunction) { |
| lObject = ((JsFunction)parent.getParent()).getParameter(newVarName); |
| } |
| } |
| if (lObject != null) { |
| ((JsObjectImpl)lObject).addOccurrence(name.getOffsetRange()); |
| } else { |
| addOccurrence(name.getName(), name.getOffsetRange(), lastOnLeft, false); |
| } |
| } |
| // lObject = (JsObjectImpl)parent.getProperty(newVarName); |
| if (lObject == null) { |
| // it's not a property of the parent -> try to find in different context |
| // FIXME why is model visitor requesting model from PR |
| Model model = Model.getModel(parserResult, false); |
| Collection<? extends JsObject> variables = model.getVariables(name.getOffsetRange().getStart()); |
| for(JsObject variable : variables) { |
| if(variable.getName().equals(newVarName)) { |
| lObject = (JsObjectImpl)variable; |
| break; |
| } |
| } |
| if (lObject == null) { |
| // the object with the name wasn't find yet -> create in global scope |
| JsObject where = modelBuilder.getCurrentWith() == null ? model.getGlobalObject() : modelBuilder.getCurrentWith(); |
| lObject = new JsObjectImpl( where, name, |
| name.getOffsetRange(), lastOnLeft, parserResult.getSnapshot().getMimeType(), null); |
| where.addProperty(name.getName(), lObject); |
| } |
| } |
| } |
| return lObject; |
| } |
| |
| public static OffsetRange getOffsetRange(IdentNode node) { |
| // because the truffle parser doesn't set correctly the finish offset, when there are comments after the indent node |
| return new OffsetRange(node.getStart(), node.getStart() + node.getName().length()); |
| } |
| |
| public static OffsetRange getOffsetRange(Node node) { |
| return new OffsetRange(node.getStart(), node.getFinish()); |
| } |
| |
| public static OffsetRange getOffsetRange(FunctionNode node) { |
| return new OffsetRange(Token.descPosition(node.getFirstToken()), |
| Token.descPosition(node.getLastToken()) + Token.descLength(node.getLastToken())); |
| } |
| |
| // TODO move this method to the ModelUtils |
| /** |
| * |
| * @param where the declaration context, where this is used |
| * @return JsObject that should represent this. |
| */ |
| public JsObject resolveThis(JsObject where) { |
| JsElement.Kind whereKind = where.getJSKind(); |
| // if (canBeSingletonPattern()) { |
| // JsObject result = resolveThisInSingletonPattern(where); |
| // if (result != null) { |
| // return result; |
| // } |
| // } |
| if (whereKind == JsElement.Kind.FILE) { |
| // this is used in global context |
| return where; |
| } |
| if (whereKind.isFunction() && where.getModifiers().contains(Modifier.PRIVATE)) { |
| // the case where is defined private function in another function |
| return where; |
| } |
| JsObject parent = where.getParent(); |
| if (parent == null) { |
| return where; |
| } |
| JsElement.Kind parentKind = parent.getJSKind(); |
| if (parentKind == JsElement.Kind.FILE && !where.isAnonymous()) { |
| // this is used in a function that is in the global context |
| return where; |
| } |
| if (ModelUtils.PROTOTYPE.equals(parent.getName())) { |
| // this is used in a function defined in prototype object |
| return where.getParent().getParent(); |
| } |
| if (whereKind == JsElement.Kind.CONSTRUCTOR) { |
| if (parentKind == JsElement.Kind.CLASS) { |
| return parent; |
| } else { |
| return where; |
| } |
| } |
| if (whereKind.isFunction() && !where.getModifiers().contains(Modifier.PRIVATE) && !where.isAnonymous()) { |
| // public or protected method |
| if (parent.getJSKind() == JsElement.Kind.OBJECT_LITERAL) { |
| if (Character.isUpperCase(where.getName().charAt(0))) { |
| return where; |
| } |
| if (Character.isUpperCase(parent.getName().charAt(0))) { |
| return parent; |
| } |
| } else { |
| if (parent.isDeclared() || modelBuilder.getCurrentWith() != null) { |
| return parent; |
| } else { |
| return where; |
| } |
| } |
| } |
| if (isInPropertyNode()) { |
| // this is used in a method of an object -> this is the object |
| return parent; |
| } |
| // if (where.isAnonymous()) { |
| // JsObject result = resolveThisInSingletonPattern(where); |
| // if (result != null) { |
| // return result; |
| // } |
| // } |
| return where; |
| } |
| |
| private JsObject resolveThisInSingletonPattern(JsObject where) { |
| int pathIndex = 1; |
| Node lastNode = getPreviousFromPath(1); |
| if (lastNode instanceof FunctionNode && !canBeSingletonPattern(pathIndex)) { |
| pathIndex++; |
| } |
| while (pathIndex < getPath().size() && !(getPreviousFromPath(pathIndex) instanceof FunctionNode)) { |
| pathIndex++; |
| } |
| // trying to find out that it corresponds with patter, where an object is defined via new function: |
| // exp: this.pro = new function () { this.field = "";} |
| if (canBeSingletonPattern(pathIndex)) { |
| UnaryNode uNode = (UnaryNode) getPreviousFromPath(pathIndex + 2); |
| if (uNode.tokenType() == TokenType.NEW) { |
| |
| String name = null; |
| boolean simpleName = true; |
| if (getPreviousFromPath(pathIndex + 3) instanceof BinaryNode) { |
| BinaryNode bNode = (BinaryNode) getPreviousFromPath(pathIndex + 3); |
| if (bNode.tokenType() == TokenType.ASSIGN) { |
| if (bNode.lhs() instanceof AccessNode) { |
| List<Identifier> identifier = getName((AccessNode) bNode.lhs(), parserResult); |
| if (identifier != null) { |
| if (!identifier.isEmpty() && ModelUtils.THIS.equals(identifier.get(0).getName())) { |
| identifier.remove(0); |
| } |
| if (identifier.size() == 1) { |
| name = identifier.get(0).getName(); |
| } else { |
| StringBuilder sb = new StringBuilder(); |
| for (Identifier part : identifier) { |
| sb.append(part.getName()).append('.'); |
| } |
| name = sb.toString().substring(0, sb.length() - 1); |
| simpleName = false; |
| } |
| } |
| } else if (bNode.lhs() instanceof IdentNode) { |
| name = ((IdentNode) bNode.lhs()).getName(); |
| } |
| } |
| } else if (getPreviousFromPath(pathIndex + 3) instanceof VarNode) { |
| VarNode vNode = (VarNode)getPreviousFromPath(pathIndex + 3); |
| name = vNode.getName().getName(); |
| } |
| |
| JsObject parent = where.getParent() == null ? where : where.getParent(); |
| if (name != null) { |
| if (simpleName) { |
| parent = where; |
| while (parent != null && parent.getProperty(name) == null) { |
| parent = parent.getParent(); |
| } |
| if (parent != null && parent.getProperty(name) != null) { |
| if (parent.getName().equals(name) && parent.getProperty(name).getJSKind().isFunction()) { |
| return parent; |
| } |
| return parent.getProperty(name); |
| } |
| } else { |
| JsObject property = ModelUtils.findJsObjectByName(ModelUtils.getGlobalObject(parent), name); |
| if (property != null) { |
| return property; |
| } |
| } |
| |
| } |
| } |
| } |
| return null; |
| } |
| |
| private boolean canBeSingletonPattern() { |
| int pathIndex = 1; |
| Node lastNode = getPreviousFromPath(1); |
| if (lastNode instanceof FunctionNode && !canBeSingletonPattern(pathIndex)) { |
| pathIndex++; |
| } |
| while (pathIndex < getPath().size() && !(getPreviousFromPath(pathIndex) instanceof FunctionNode)) { |
| pathIndex++; |
| } |
| return canBeSingletonPattern(pathIndex); |
| } |
| |
| private boolean canBeSingletonPattern(int pathIndex) { |
| return (getPath().size() > pathIndex + 3 && getPreviousFromPath(pathIndex) instanceof FunctionNode |
| && getPreviousFromPath(pathIndex + 1) instanceof CallNode |
| && ((CallNode)getPreviousFromPath(pathIndex + 1)).getFunction().equals(getPreviousFromPath(pathIndex)) |
| && getPreviousFromPath(pathIndex + 2) instanceof UnaryNode |
| && (getPreviousFromPath(pathIndex + 3) instanceof BinaryNode |
| || getPreviousFromPath(pathIndex + 3) instanceof VarNode)); |
| } |
| |
| private boolean isPriviliged(AccessNode aNode) { |
| Node node = aNode.getBase(); |
| while (node instanceof AccessNode) { |
| node = ((AccessNode)node).getBase(); |
| } |
| if (node instanceof IdentNode && ModelUtils.THIS.endsWith(((IdentNode)node).getName())) { |
| return true; |
| } |
| return false; |
| } |
| |
| private void setModifiersFromDoc(JsObject object, Set<JsModifier> modifiers) { |
| if (modifiers != null && !modifiers.isEmpty()) { |
| for (JsModifier jsModifier : modifiers) { |
| switch (jsModifier) { |
| case PRIVATE: |
| // if the modifier from doc is PRIVATE, keep information about the privilaged or public method anyway. |
| object.getModifiers().add(Modifier.PRIVATE); |
| break; |
| case PUBLIC: |
| object.getModifiers().remove(Modifier.PROTECTED); |
| object.getModifiers().remove(Modifier.PRIVATE); |
| object.getModifiers().add(Modifier.PUBLIC); |
| break; |
| case STATIC: |
| object.getModifiers().add(Modifier.STATIC); |
| break; |
| } |
| } |
| } |
| } |
| |
| private void processObjectPropertyAssignment(CallNode cNode) { |
| if (!(cNode.getFunction() instanceof AccessNode)) { |
| return; |
| } |
| |
| AccessNode aNode = (AccessNode)cNode.getFunction(); |
| if ("assign".equals(aNode.getProperty()) |
| && aNode.getBase() instanceof IdentNode |
| && "Object".equals(((IdentNode)aNode.getBase()).getName())) { |
| // the function call is Object.assign ... |
| final List<Expression> args = cNode.getArgs(); |
| if (args != null && !args.isEmpty()) { |
| // first param is the target object |
| List<Identifier> targetName = getNodeName(args.get(0), parserResult); |
| if (targetName != null && !targetName.isEmpty()) { |
| JsObjectImpl targetObject = ModelUtils.getJsObject(modelBuilder, targetName, false); |
| if (targetObject != null) { |
| for (int i = 1; i < args.size(); i++) { |
| Expression expression = args.get(i); |
| List<Identifier> argName = getNodeName(expression, parserResult); |
| if (argName != null && !argName.isEmpty()) { |
| JsObjectImpl argObject = ModelUtils.getJsObject(modelBuilder, argName, false); |
| if (argObject != null) { |
| for(JsObject property : argObject.getProperties().values()) { |
| JsObject copyProperty; |
| if (property.getJSKind().isFunction()) { |
| copyProperty = new JsFunctionReference(targetObject, property.getDeclarationName(), (JsFunction)property, true, property.getModifiers()); |
| } else { |
| copyProperty = new JsObjectReference(targetObject, property.getDeclarationName(), property, true, property.getModifiers()); |
| } |
| targetObject.addProperty(copyProperty.getName(), copyProperty); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| } |
| } |
| |
| public static class FunctionCall { |
| |
| private final String name; |
| |
| private final DeclarationScope scope; |
| |
| private final Collection<FunctionArgument> arguments; |
| |
| private final int callOffset; |
| |
| public FunctionCall(String name, DeclarationScope scope, |
| Collection<FunctionArgument> arguments, int callOffset) { |
| this.name = name; |
| this.scope = scope; |
| this.arguments = arguments; |
| this.callOffset = callOffset; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| public DeclarationScope getScope() { |
| return scope; |
| } |
| |
| public Collection<FunctionArgument> getArguments() { |
| return arguments; |
| } |
| |
| public int getCallOffset() { |
| return callOffset; |
| } |
| |
| |
| } |
| |
| // for loging purposes |
| private int indent = 0; |
| private String createSpaces(int indent) { |
| StringBuilder sb = new StringBuilder(); |
| for(int i = 0; i < indent; i++) { |
| sb.append(' '); |
| } |
| return sb.toString(); |
| } |
| |
| private void log(String text) { |
| System.out.print(createSpaces(indent)); |
| System.out.println(text); |
| } |
| |
| private String debugInfo(Node node) { |
| StringBuilder sb = new StringBuilder(); |
| if (node instanceof FunctionNode) { |
| FunctionNode fn = (FunctionNode)node; |
| sb.append("FunctionNode name: ").append(fn.getName()); |
| sb.append(", Ident: ").append(fn.getIdent()); |
| if (fn.allVarsInScope()) sb.append(", allVarsInScope"); |
| if (fn.isAnonymous()) sb.append(", isAnonymous"); |
| if (fn.isDeclared()) sb.append(", isDeclared"); |
| if (fn.isMethod()) sb.append(", isMethod"); |
| if (fn.isNamedFunctionExpression()) sb.append(", isNamedFunctionExpression"); |
| if (fn.isVarArg()) sb.append(", isVarArg"); |
| if (fn.hasDeclaredFunctions()) sb.append(", hasDeclaredFunctions"); |
| if (fn.hasDirectSuper()) sb.append(", hasDirectSuper"); |
| // if (fn.hasScopeBlock()) sb.append(", hasScoprBlock"); |
| } else if (node instanceof VarNode) { |
| VarNode vn = (VarNode)node; |
| sb.append("VarNode ").append(vn.getName()); |
| if (vn.isBlockScoped()) sb.append(", isBlockScoped"); |
| if (vn.isConst()) sb.append(", isConst"); |
| if (vn.isFunctionDeclaration()) sb.append(", isFunctionDeclaration"); |
| if (vn.isLet()) sb.append(", isLet"); |
| } else { |
| sb.append(node.getClass().getName()); |
| } |
| return sb.toString(); |
| } |
| |
| |
| @ServiceProvider(service = ModelResolver.Provider.class, position = 10_000) |
| public static final class Provider implements ModelResolver.Provider { |
| |
| @Override |
| public ModelResolver create(ParserResult result, OccurrenceBuilder occurrenceBuilder) { |
| final ModelVisitor visitor = new ModelVisitor(result, occurrenceBuilder); |
| return visitor; |
| } |
| |
| } |
| } |