blob: 1882a8728ca71c22e249c7b4b1649642a634fafb [file] [log] [blame]
/*
* 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.api;
import com.oracle.js.parser.ir.Node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.csl.api.Modifier;
import org.netbeans.modules.csl.api.OffsetRange;
import org.netbeans.modules.javascript2.doc.spi.JsDocumentationHolder;
import org.netbeans.modules.javascript2.lexer.api.JsTokenId;
import org.netbeans.modules.javascript2.lexer.api.LexUtilities;
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 org.netbeans.modules.javascript2.model.DeclarationScopeImpl;
import org.netbeans.modules.javascript2.model.EmbeddingHelper;
import org.netbeans.modules.javascript2.model.JsFunctionImpl;
import org.netbeans.modules.javascript2.model.JsFunctionReference;
import org.netbeans.modules.javascript2.model.JsObjectImpl;
import org.netbeans.modules.javascript2.model.JsObjectReference;
import org.netbeans.modules.javascript2.model.ModelBuilder;
import org.netbeans.modules.javascript2.model.ModelExtender;
import org.netbeans.modules.javascript2.model.ParameterObject;
import org.netbeans.modules.javascript2.model.SemiTypeResolverVisitor;
import org.netbeans.modules.javascript2.model.spi.TypeNameConvertor;
import org.netbeans.modules.javascript2.types.spi.ParserResult;
import org.netbeans.modules.parsing.api.Snapshot;
import org.netbeans.modules.parsing.spi.indexing.support.IndexResult;
import org.openide.filesystems.FileObject;
/**
*
* @author Petr Pisl
*/
public class ModelUtils {
public static final String PROTOTYPE = "prototype"; //NOI18N
public static final String CONSTRUCTOR = "constructor"; //NOI18N
public static final String THIS = "this"; //NOI18N
public static final String ARGUMENTS = "arguments"; //NOI18N
private static final String GENERATED_FUNCTION_PREFIX = "L#"; //NOI18N
private static final String GENERATED_ANONYM_PREFIX = "Anonym#"; //NOI18N
private static final List<String> KNOWN_TYPES = Arrays.asList(Type.ARRAY, Type.STRING, Type.BOOLEAN, Type.NUMBER, Type.UNDEFINED);
private static final int MAX_RECURSION_DEEP_RESOLVING_ASSIGNMENTS = 10;
private static String GLOBAL_DIRECTIVE = "global"; //NOI18N
private static final Logger LOG = Logger.getLogger(ModelUtils.class.getName());
public static JsObjectImpl getJsObject (ModelBuilder builder, List<Identifier> fqName, boolean isLHS) {
if (fqName == null || fqName.isEmpty()) {
return null;
}
JsObject result = builder.getCurrentObject();
JsObject tmpObject = null;
String firstName = fqName.get(0).getName();
while (tmpObject == null && result != null && result.getParent() != null) {
if (result instanceof JsFunctionImpl) {
tmpObject = ((JsFunctionImpl)result).getParameter(firstName);
}
if (tmpObject == null) {
if (result.getProperty(firstName) != null) {
tmpObject = result;
}
result = result.getParent();
} else {
result = tmpObject;
}
}
if (tmpObject == null) {
JsObject current = builder.getCurrentObject();
if (current instanceof JsWith) {
tmpObject = current;
} else {
DeclarationScope scope = builder.getCurrentDeclarationFunction();
while (scope != null && tmpObject == null && scope.getParentScope() != null) {
if (scope instanceof JsFunction) {
tmpObject = ((JsFunction)scope).getParameter(firstName);
}
if (tmpObject == null) {
tmpObject = ((JsObject)scope).getProperty(firstName);
}
scope = scope.getParentScope();
}
if (tmpObject == null) {
tmpObject = builder.getGlobal();
} else {
result = tmpObject;
}
}
}
for (int index = (tmpObject instanceof ParameterObject ? 1 : 0); index < fqName.size() ; index++) {
Identifier name = fqName.get(index);
if (name != null) {
result = tmpObject.getProperty(name.getName());
if (result == null) {
result = new JsObjectImpl(tmpObject, name, name.getOffsetRange(),
(index < (fqName.size() - 1)) ? false : isLHS, tmpObject.getMimeType(), tmpObject.getSourceLabel());
tmpObject.addProperty(name.getName(), result);
}
}
tmpObject = result;
}
return (JsObjectImpl)result;
}
public static boolean isGlobal(JsObject object) {
return object != null && object.getJSKind() == JsElement.Kind.FILE;
}
public static boolean isDescendant(JsObject possibleDescendant, JsObject possibleAncestor) {
JsObject parent = possibleDescendant;
while (parent != null && !parent.equals(possibleAncestor)) {
parent = parent.getParent();
}
return parent != null;
}
public static JsObject findJsObject(Model model, int offset) {
JsObject result = null;
JsObject global = model.getGlobalObject();
result = findJsObject(global, offset);
if (result == null) {
result = global;
}
return result;
}
public static JsObject findJsObject(JsObject object, int offset) {
HashSet<String> visited = new HashSet<String>();
return findJsObject(object, offset, visited);
}
public static void copyOccurrences(JsObject from, JsObject to) {
for (Occurrence oc : from.getOccurrences()) {
to.addOccurrence(oc.getOffsetRange());
}
}
public static JsObject findJsObject(JsObject object, int offset, Set<String> visited) {
JsObjectImpl jsObject = (JsObjectImpl)object;
visited.add(jsObject.getFullyQualifiedName());
JsObject result = null;
JsObject tmpObject = null;
if (jsObject.containsOffset(offset)) {
result = jsObject;
for (JsObject property : jsObject.getProperties().values()) {
JsElement.Kind kind = property.getJSKind();
if (kind == JsElement.Kind.OBJECT || kind == JsElement.Kind.ANONYMOUS_OBJECT || kind == JsElement.Kind.OBJECT_LITERAL
|| kind == JsElement.Kind.FUNCTION || kind == JsElement.Kind.METHOD || kind == JsElement.Kind.CONSTRUCTOR
|| kind == JsElement.Kind.WITH_OBJECT) {
if (!visited.contains(property.getFullyQualifiedName())) {
tmpObject = findJsObject(property, offset, visited);
}
}
if (tmpObject != null) {
result = tmpObject;
break;
}
}
if ( object.getJSKind() == JsElement.Kind.WITH_OBJECT) {
for (JsWith innerWith :((JsWith)object).getInnerWiths()){
if (!visited.contains(innerWith.getFullyQualifiedName())) {
tmpObject = findJsObject(innerWith, offset, visited);
}
if (tmpObject != null) {
result = tmpObject;
break;
}
}
}
if (object instanceof JsArray) {
JsArray array = (JsArray)object;
for (TypeUsage type : array.getTypesInArray()) {
if (type.getType().startsWith(SemiTypeResolverVisitor.ST_ANONYM)) {
int anonymOffset = Integer.parseInt(type.getType().substring(SemiTypeResolverVisitor.ST_ANONYM.length()));
if (anonymOffset > 0) {
DeclarationScope scope = getDeclarationScope(array);
for (JsObject property : ((JsObject)scope).getProperties().values()) {
JsElement.Kind kind = property.getJSKind();
if (kind == JsElement.Kind.ANONYMOUS_OBJECT && !visited.contains(property.getFullyQualifiedName())) {
tmpObject = findJsObject(property, offset, visited);
}
if (tmpObject != null) {
result = tmpObject;
break;
}
}
}
}
}
}
}
return result;
}
public static JsObject findJsObjectByName(JsObject global, String fqName) {
JsObject result = global;
JsObject property = result;
for (StringTokenizer stringTokenizer = new StringTokenizer(fqName, "."); stringTokenizer.hasMoreTokens() && result != null;) {
String token = stringTokenizer.nextToken();
property = result.getProperty(token);
if (property == null) {
result = (result instanceof JsFunction)
? ((JsFunction)result).getParameter(token)
: null;
if (result == null) {
break;
}
} else {
result = property;
}
}
return result;
}
public static JsObject findJsObjectByName(Model model, String fqName) {
return findJsObjectByName(model.getGlobalObject(), fqName);
}
public static JsObject getGlobalObject(JsObject jsObject) {
JsObject result = jsObject;
while(result.getParent() != null) {
result = result.getParent();
}
return result;
}
public static DeclarationScope getDeclarationScope(JsObject object) {
assert object != null;
JsObject result = object;
while (result.getParent() != null && !(result.getParent() instanceof DeclarationScope)) {
result = result.getParent();
}
if (result.getParent() != null && result.getParent() instanceof DeclarationScope) {
result = result.getParent();
}
if (!(result instanceof DeclarationScope)) {
// this shouldn't happened, basically it means that the model is broken and has an object without parent
result = getGlobalObject(object);
}
return (DeclarationScope)result;
}
public static DeclarationScope getDeclarationScope(Model model, int offset) {
DeclarationScope result = null;
JsObject global = model.getGlobalObject();
result = getDeclarationScope((DeclarationScope)global, offset);
if (result == null) {
result = (DeclarationScope)global;
}
return result;
}
public static DeclarationScope getDeclarationScope(DeclarationScope scope, int offset) {
DeclarationScopeImpl dScope = (DeclarationScopeImpl)scope;
DeclarationScope result = null;
if (result == null) {
if (dScope.getOffsetRange().containsInclusive(offset)) {
result = dScope;
boolean deep = true;
while (deep) {
deep = false;
for (DeclarationScope innerScope : result.getChildrenScopes()) {
if ((innerScope instanceof DeclarationScopeImpl)
&& ((DeclarationScopeImpl)innerScope).getOffsetRange().containsInclusive(offset)) {
result = innerScope;
deep = true;
break;
}
}
}
}
}
return result;
}
public static OffsetRange documentOffsetRange(ParserResult result, int start, int end) {
int lStart = LexUtilities.getLexerOffset(result, start);
int lEnd = LexUtilities.getLexerOffset(result, end);
if (lStart == -1 || lEnd == -1) {
return OffsetRange.NONE;
}
if (lEnd < lStart) {
// TODO this is a workaround for bug in nashorn, when sometime the start and end are not crorrect
int length = lStart - lEnd;
lEnd = lStart + length;
}
return new OffsetRange(lStart, lEnd);
}
/**
* Returns all variables that are available in the scope
* @param inScope
* @return
*/
public static Collection<? extends JsObject> getVariables(DeclarationScope inScope) {
HashMap<String, JsObject> result = new HashMap<String, JsObject>();
while (inScope != null) {
for (JsObject object : ((JsObject)inScope).getProperties().values()) {
if (!result.containsKey(object.getName()) && object.getModifiers().contains(Modifier.PRIVATE)) {
result.put(object.getName(), object);
}
}
if (inScope instanceof JsFunction) {
for (JsObject object : ((JsFunction)inScope).getParameters()) {
if (!result.containsKey(object.getName())) {
result.put(object.getName(), object);
}
}
}
for (JsObject object : ((JsObject)inScope).getProperties().values()) {
if (!result.containsKey(object.getName())) {
result.put(object.getName(), object);
}
}
if (inScope.getParentScope() != null && !result.containsKey(((JsObject)inScope).getName())) {
result.put(((JsObject)inScope).getName(), (JsObject)inScope);
}
inScope = inScope.getParentScope();
}
return result.values();
}
public static JsObject getScopeVariable(DeclarationScope inScope, String name) {
for( DeclarationScope curScope = inScope; curScope != null; curScope = curScope.getParentScope()) {
JsObject prop = ((JsObject)curScope).getProperty(name);
if( prop != null && prop.getModifiers().contains(Modifier.PRIVATE) )
return prop;
if( curScope instanceof JsFunction ) {
JsObject param = ((JsFunction)curScope).getParameter(name);
if( param != null )
return param;
}
if( prop != null )
return prop;
if( name.equals(((JsObject)inScope).getName()) )
return (JsObject)inScope;
}
return null;
}
public static Collection<? extends JsObject> getVariables(Model model, int offset) {
DeclarationScope scope = ModelUtils.getDeclarationScope(model, offset);
return getVariables(scope);
}
public static JsObject getJsObjectByName(DeclarationScope inScope, String simpleName) {
Collection<? extends JsObject> variables = ModelUtils.getVariables(inScope);
for (JsObject jsObject : variables) {
if (simpleName.equals(jsObject.getName())) {
return jsObject;
}
}
return null;
}
private static final Collection<JsTokenId> CTX_DELIMITERS = Arrays.asList(
JsTokenId.BRACKET_LEFT_CURLY, JsTokenId.BRACKET_RIGHT_CURLY,
JsTokenId.OPERATOR_SEMICOLON);
private static Collection<TypeUsage> tryResolveWindowProperty(Model model, Index jsIndex, String name) {
// since issue #215863
String fqn = null;
int offset = -1;
for (IndexedElement indexedElement : jsIndex.getProperties("window")) { //NOI18N
if (indexedElement.getName().equals(name)) {
offset = indexedElement.getOffset();
fqn = "window." + name;
break;
}
}
if (fqn == null) {
for (IndexedElement indexedElement : jsIndex.getProperties("Window.prototype")) { //NOI18N
if (indexedElement.getName().equals(name)) {
offset = indexedElement.getOffset();
fqn = "Window.prototype." + name; //NOI18N
break;
}
}
}
if (fqn != null) {
List<TypeUsage> fromAssignment = new ArrayList<TypeUsage>();
resolveAssignments(model, jsIndex, fqn, offset, fromAssignment);
if (fromAssignment.isEmpty()) {
fromAssignment.add(new TypeUsage(fqn));
}
return fromAssignment;
}
return null;
}
private enum State {
INIT
}
public static Collection<TypeUsage> resolveSemiTypeOfExpression(ModelBuilder builder, Node expression) {
Collection<TypeUsage> result = new HashSet<TypeUsage>();
SemiTypeResolverVisitor visitor = new SemiTypeResolverVisitor();
if (expression != null) {
result = visitor.getSemiTypes(expression, builder);
}
if (builder.getCurrentWith()!= null) {
Collection<TypeUsage> withResult = new HashSet<TypeUsage>();
String withSemi = SemiTypeResolverVisitor.ST_WITH + builder.getCurrentWith().getFullyQualifiedName();
for(TypeUsage type : result) {
if (!KNOWN_TYPES.contains(type.getType())) {
withResult.add(new TypeUsage(withSemi + type.getType(), type.getOffset(), type.isResolved()));
} else {
withResult.add(type);
}
}
result = withResult;
}
return result;
}
public static Collection<TypeUsage> resolveTypeFromSemiType(JsObject object, TypeUsage type) {
Set<TypeUsage> result = new HashSet<TypeUsage>();
if (type.isResolved()) {
result.add(type);
} else if (Type.UNDEFINED.equals(type.getType())) {
if (object.getJSKind() == JsElement.Kind.CONSTRUCTOR) {
if (object.getParent().getJSKind() == JsElement.Kind.CLASS) {
result.add(new TypeUsage(object.getParent().getFullyQualifiedName(), type.getOffset(), true));
} else {
result.add(new TypeUsage(object.getFullyQualifiedName(), type.getOffset(), true));
}
} else {
result.add(new TypeUsage(Type.UNDEFINED, type.getOffset(), true));
}
} else if (EmbeddingHelper.containsGeneratedIdentifier(type.getType())) {
result.add(new TypeUsage(Type.UNDEFINED, type.getOffset(), true));
} else if (SemiTypeResolverVisitor.ST_THIS.equals(type.getType())) {
JsObject parent = resolveThis(object);
if (parent != null) {
result.add(new TypeUsage(parent.getFullyQualifiedName(), type.getOffset(), true));
}
} else if (type.getType().startsWith(SemiTypeResolverVisitor.ST_THIS)) {
JsObject parent = resolveThis(object);
if (parent != null) {
Collection<TypeUsage> locally = resolveSemiTypeChain(parent, type.getType().substring(6));
if (locally.isEmpty()) {
result.add(new TypeUsage(type.getType().replace(SemiTypeResolverVisitor.ST_THIS, parent.getFullyQualifiedName()), type.getOffset(), false));
} else {
if (locally.size() == 1) {
TypeUsage localType = locally.iterator().next();
if (localType.isResolved()) {
JsObject rObject = ModelUtils.findJsObjectByName(ModelUtils.getGlobalObject(object), localType.getType());
JsFunction function = rObject instanceof JsFunctionImpl
? (JsFunctionImpl) rObject
: rObject instanceof JsFunctionReference ? ((JsFunctionReference) rObject).getOriginal() : null;
if (function != null && function.getParent() != null && object != null
&& function.getParent().equals(object.getParent())
&& object.getDeclarationName() != null) {
// creates reference to the original function
object.getParent().addProperty(object.getName(), new JsFunctionReference(
object.getParent(), object.getDeclarationName(), function, true, null));
}
}
}
result.addAll(locally);
}
}
} else if (type.getType().startsWith(SemiTypeResolverVisitor.ST_NEW)) {
result.addAll(resolveSemiTypeCallChain(object, type));
} else if (type.getType().startsWith(SemiTypeResolverVisitor.ST_CALL)) {
result.addAll(resolveSemiTypeCallChain(object, type));
} else if(type.getType().startsWith(SemiTypeResolverVisitor.ST_ANONYM)){
String offsetPart = type.getType().substring(8);
String rest = "";
int index = offsetPart.indexOf(SemiTypeResolverVisitor.ST_START_DELIMITER);
if (index > -1) {
rest = offsetPart.substring(index);
offsetPart = offsetPart.substring(0, index);
}
int start = Integer.parseInt(offsetPart);
// JsObject globalObject = ModelUtils.getGlobalObject(object);
JsObject byOffset = ModelUtils.findJsObject(object, start);
if (byOffset == null) {
JsObject globalObject = ModelUtils.getGlobalObject(object);
byOffset = ModelUtils.findJsObject(globalObject, start);
}
if(byOffset != null && byOffset.isAnonymous()) {
if (rest.isEmpty()) {
result.add(new TypeUsage(byOffset.getFullyQualifiedName(), byOffset.getOffset(), true));
} else {
String newType= SemiTypeResolverVisitor.ST_EXP + byOffset.getFullyQualifiedName().replace(".", SemiTypeResolverVisitor.ST_PRO);
newType = newType + rest;
result.add(new TypeUsage(newType, byOffset.getOffset(), false));
}
}
// for(JsObject children : globalObject.getProperties().values()) {
// if(children.getOffset() == start && children.getName().startsWith("Anonym$")) {
// result.add(new TypeUsage(ModelUtils.createFQN(children), children.getOffset(), true));
// break;
// }
//
// }
} else if(type.getType().startsWith(SemiTypeResolverVisitor.ST_VAR)){
String name = type.getType().substring(5);
DeclarationScope declarationScope = object instanceof DeclarationScope ? (DeclarationScope)object : getDeclarationScope(object);
List<JsObject> variables = new ArrayList(ModelUtils.getVariables(declarationScope));
if (!(object instanceof DeclarationScope) && object.getParent() != null && !(object.getParent() instanceof DeclarationScope)) {
variables.addAll(object.getParent().getProperties().values());
}
if (declarationScope != null) {
boolean resolved = false;
for (JsObject variable : variables) {
if (variable.getName().equals(name)) {
String newVarType;
if (!variable.getAssignments().isEmpty()) {
newVarType= SemiTypeResolverVisitor.ST_EXP + variable.getFullyQualifiedName().replace(".", SemiTypeResolverVisitor.ST_PRO);
result.add(new TypeUsage(newVarType, type.getOffset(), false));
resolved = true;
break;
} else {
if (variable.getJSKind() != JsElement.Kind.PARAMETER) {
if (variable.getJSKind().isFunction() && object.getAssignments().size() == 1
&& object.getParent() != null && object.getDeclarationName() != null) {
JsObject oldProperty = object.getParent().getProperty(object.getName());
JsObject newProperty = new JsFunctionReference(object.getParent(), object.getDeclarationName(), (JsFunction)variable, true, oldProperty.getModifiers());
for (Occurrence occurrence : oldProperty.getOccurrences()) {
newProperty.addOccurrence(occurrence.getOffsetRange());
}
object.getParent().addProperty(object.getName(), newProperty);
} else {
newVarType = variable.getFullyQualifiedName();
result.add(new TypeUsage(newVarType, type.getOffset(), false));
}
resolved = true;
break;
}
}
}
}
if (!resolved) {
if (declarationScope instanceof JsFunction) {
Collection<? extends JsObject> parameters = ((JsFunction)declarationScope).getParameters();
boolean isParameter = false;
for (JsObject parameter : parameters) {
if (name.equals(parameter.getName())) {
Collection<? extends TypeUsage> assignments = parameter.getAssignmentForOffset(parameter.getOffset());
result.addAll(assignments);
isParameter = true;
break;
}
}
if (!isParameter) {
result.add(new TypeUsage(name, type.getOffset(), false));
}
}
}
}
} else if(type.getType().startsWith("@param;")) {
String functionName = type.getType().substring(7);
int index = functionName.indexOf(":");
if (index > 0) {
String fqn = functionName.substring(0, index);
JsObject globalObject = ModelUtils.getGlobalObject(object);
JsObject function = ModelUtils.findJsObjectByName(globalObject, fqn);
if(function instanceof JsFunction) {
JsObject param = ((JsFunction)function).getParameter(functionName.substring(index + 1));
if(param != null) {
result.addAll(param.getAssignments());
}
}
}
} else {
result.add(type);
}
return result;
}
private static JsObject resolveThis(JsObject object) {
JsObject parent = null;
if (object.getJSKind() == JsElement.Kind.CONSTRUCTOR) {
parent = object;
} else {
if (object.getParent() != null && object.getParent().getJSKind() != JsElement.Kind.FILE) {
parent = object.getParent();
} else {
parent = object;
}
}
if (parent != null && (parent.getJSKind() == JsElement.Kind.FUNCTION || parent.getJSKind() == JsElement.Kind.METHOD)) {
if (parent.getParent().getJSKind() != JsElement.Kind.FILE) {
JsObject grandParent = parent.getParent();
if (grandParent != null
&& (grandParent.getJSKind() == JsElement.Kind.OBJECT_LITERAL || PROTOTYPE.equals(grandParent.getName()))) {
parent = grandParent;
if (PROTOTYPE.equals(parent.getName()) && parent.getParent() != null) {
parent = parent.getParent();
}
}
}
}
// if the parent is priviliged the this refers the constructor => find the constructor
while (parent != null && parent.getParent() != null && parent.getModifiers().contains(Modifier.PROTECTED)) {
parent = parent.getParent();
}
return parent;
}
private static Collection<TypeUsage> resolveSemiTypeCallChain(JsObject object, TypeUsage type) {
Set<TypeUsage> result = new HashSet<TypeUsage>();
DeclarationScope declarationScope = ModelUtils.getDeclarationScope(object);
JsObject function = null;
boolean calledNew = false;
int index = -1;
int dotIndex = -1;
if (type.getType().startsWith(SemiTypeResolverVisitor.ST_CALL)) {
index = 6;
} else if (type.getType().startsWith(SemiTypeResolverVisitor.ST_NEW)) {
index = 5;
calledNew = true;
}
String name = type.getType().substring(index);
if (declarationScope != null) {
index = name.indexOf(SemiTypeResolverVisitor.ST_START_DELIMITER);
if (index > -1) {
name = name.substring(0, index);
}
Collection<? extends JsObject> variables = ModelUtils.getVariables(declarationScope);
dotIndex = name.indexOf('.');
String firstSpace = dotIndex == -1 ? name : name.substring(0, name.indexOf('.'));
for (JsObject variable : variables) {
if (variable.getName().equals(firstSpace)) {
function = variable;
break;
}
}
}
if (dotIndex != -1 && function != null) {
function = ModelUtils.findJsObjectByName(function, name.substring(dotIndex + 1));
}
if (function != null) {
if (index == -1) {
if (function instanceof JsFunction) {
if (calledNew) {
result.add(new TypeUsage(function.getFullyQualifiedName(), type.getOffset(), true));
} else {
result.addAll(((JsFunction) function).getReturnTypes());
}
} else {
if (calledNew) {
result.add(new TypeUsage(function.getFullyQualifiedName(), type.getOffset(), true));
} else {
result.add(type);
}
}
} else {
result.add(new TypeUsage(type.getType().replace(name, function.getFullyQualifiedName()), type.getOffset(), false));
}
} else {
result.add(type);
}
return result;
}
/**
*
* @param object
* @param chain
* @return
*/
private static Collection<TypeUsage> resolveSemiTypeChain(JsObject object, String chain) {
Collection<TypeUsage> result = new HashSet<TypeUsage>();
if (chain.isEmpty()) {
return result;
}
if (PROTOTYPE.equals(object.getName())) {
object = object.getParent();
if (object == null) {
return result;
}
}
String[] parts = chain.substring(1).split(SemiTypeResolverVisitor.ST_START_DELIMITER);
JsObject resultObject = null;
JsObject testObject = object;
String kind = ""; //NOI18N
String name;
for (String part : parts) {
int index = part.indexOf(";"); //NOI18N
if (index > 0) {
kind = part.substring(0, index);
name = part.substring(index + 1);
resultObject = testObject.getProperty(name);
if (resultObject == null) {
JsObject prototype = testObject.getProperty(PROTOTYPE);
if (prototype != null) {
resultObject = prototype.getProperty(name);
}
}
if (resultObject == null) {
break;
}
testObject = resultObject;
}
else {
break;
}
}
if (resultObject != null) {
if (resultObject instanceof JsFunction) {
if ("call".endsWith(kind)) {
ModelUtils.addUniqueType(result, (Collection<TypeUsage>)((JsFunction)resultObject).getReturnTypes());
} else {
ModelUtils.addUniqueType(result, new TypeUsage(resultObject.getFullyQualifiedName(), -1, true));
}
}else {
Collection<? extends TypeUsage> assignments = resultObject.getAssignments();
if (assignments.isEmpty()) {
ModelUtils.addUniqueType(result, new TypeUsage(resultObject.getFullyQualifiedName(), -1, true));
} else {
ModelUtils.addUniqueType(result, (Collection<TypeUsage>)resultObject.getAssignments());
}
}
}
return result;
}
public static Collection<TypeUsage> resolveTypeFromExpression (Model model, @NullAllowed Index jsIndex, List<String> exp, int offset, boolean includeAllPossible) {
List<JsObject> localObjects = new ArrayList<JsObject>();
List<JsObject> lastResolvedObjects = new ArrayList<JsObject>();
List<TypeUsage> lastResolvedTypes = new ArrayList<TypeUsage>();
for (int i = exp.size() - 1; i > -1; i--) {
String kind = exp.get(i);
String name = exp.get(--i);
if (name.startsWith("@ano:")){
String[] parts = name.split(":");
int anoOffset = Integer.parseInt(parts[1]);
JsObject anonym = ModelUtils.findJsObject(model, anoOffset);
lastResolvedObjects.add(anonym);
continue;
}
if (ModelUtils.THIS.equals(name)) {
JsObject thisObject = ModelUtils.findJsObject(model, offset);
JsObject first = thisObject;
while (thisObject != null && thisObject.getParent() != null && thisObject.getParent().getJSKind() != JsElement.Kind.FILE
&& thisObject.getJSKind() != JsElement.Kind.CONSTRUCTOR
&& thisObject.getJSKind() != JsElement.Kind.ANONYMOUS_OBJECT
&& thisObject.getJSKind() != JsElement.Kind.OBJECT_LITERAL) {
thisObject = thisObject.getParent();
}
if ((thisObject == null || thisObject.getParent() == null) && first != null) {
thisObject = first;
}
if (thisObject != null) {
name = thisObject.getName();
}
}
if (i == (exp.size() - 2)) {
JsObject localObject = null;
// resolving the first part of expression
// find possible variables from local context, index contains only
// public definition, we are interested in the private here as well
int index = name.lastIndexOf('.');
// needs to look, whether the expression is in a with statement
Collection<? extends TypeUsage> typeFromWith = getTypeFromWith(model, offset);
if (!typeFromWith.isEmpty()) {
String firstNamePart = index == -1 ? name : name.substring(0, index);
String changedName = name;
for (TypeUsage type : typeFromWith) {
//Collection<TypeUsage> resolveTypeFromSemiType = ModelUtils.resolveTypeFromSemiType(model.getGlobalObject(), type);
String sType = type.getType();
// if (sType.startsWith("@exp;")) {
// sType = sType.substring(5);
// sType = sType.replace("@pro;", ".");
// }
localObject = ModelUtils.findJsObjectByName(model, sType);
if (localObject != null && localObject.getProperty(firstNamePart) != null) {
changedName = localObject.getFullyQualifiedName() + "." + name;
} else {
lastResolvedTypes.add(new TypeUsage(sType + "." + name, -1, true));
}
}
name = changedName;
}
if (index > -1) { // the first part is a fqn
localObject = ModelUtils.findJsObjectByName(model, name);
if (localObject != null) {
localObjects.add(localObject);
}
} else {
boolean canBeWindowsProp = true;
for (JsObject object : model.getVariables(offset)) {
if (object.getName().equals(name)) {
localObjects.add(object);
localObject = object;
break;
}
}
if (localObject != null && localObject.getJSKind().isFunction() && i - 2 > -1) {
JsFunction localFunc = (JsFunction)localObject;
String paramName = exp.get(i - 2);
if (localFunc.getParameter(paramName) != null) {
canBeWindowsProp = false;
}
}
for (JsObject libGlobal : ModelExtender.getDefault().getExtendingGlobalObjects(model.getGlobalObject().getFileObject())) {
assert libGlobal != null;
for (JsObject object : libGlobal.getProperties().values()) {
if (object.getName().equals(name)) {
//localObjects.add(object);
lastResolvedTypes.add(new TypeUsage(object.getName(), -1, true));
break;
}
}
}
if (jsIndex != null && canBeWindowsProp) {
Collection<TypeUsage> windowProperty = tryResolveWindowProperty(model, jsIndex, name);
if (windowProperty != null && !windowProperty.isEmpty()) {
lastResolvedTypes.addAll(windowProperty);
}
}
}
if(localObject == null || (localObject.getJSKind() != JsElement.Kind.PARAMETER
&& (ModelUtils.isGlobal(localObject.getParent()) || localObject.getJSKind() != JsElement.Kind.VARIABLE))) {
// Add global variables from index
// Collection<IndexedElement> globalVars = jsIndex.getGlobalVar(name);
// for (IndexedElement globalVar : globalVars) {
// if(name.equals(globalVar.getName())) {
// Collection<TypeUsage> assignments = globalVar.getAssignments();
// if (assignments.isEmpty()) {
// lastResolvedTypes.add(new TypeUsage(name, -1, true));
// } else {
// lastResolvedTypes.addAll(assignments);
// }
// }
// }
List<TypeUsage> fromAssignments = new ArrayList<TypeUsage>();
// if (localObject != null) {
// //make it only for the right offset
// for(TypeUsage type: localObject.getAssignmentForOffset(offset)) {
// resolveAssignments(jsIndex, type.getType(), fromAssignments);
// }
// } else {
if ("@pro".equals(kind) && jsIndex != null) { //NOI18N
resolveAssignments(model, jsIndex, name, -1, fromAssignments);
}
// }
lastResolvedTypes.addAll(fromAssignments);
if (!typeFromWith.isEmpty()) {
// Collection<TypeUsage> resolveTypes = ModelUtils.resolveTypes(typeFromWith, parserRestult);
for (TypeUsage typeUsage : typeFromWith) {
String sType = typeUsage.getType();
if (sType.startsWith("@exp;")) {
sType = sType.substring(5);
sType = sType.replace("@pro;", ".");
}
ModelUtils.resolveAssignments(model, jsIndex, sType, typeUsage.getOffset(), fromAssignments);
for (TypeUsage typeUsage1 : fromAssignments) {
String localFqn = localObject != null ? localObject.getFullyQualifiedName() : null;
if (localFqn != null && name.startsWith(localFqn) && name.length() > localFqn.length() ) {
lastResolvedTypes.add(new TypeUsage(typeUsage1.getType() + kind + ";" + name.substring(localFqn.length() + 1), typeUsage.getOffset(), false));
} else {
if (!typeUsage1.getType().equals(name)) {
lastResolvedTypes.add(new TypeUsage(typeUsage1.getType() + kind + ";" + name, typeUsage.getOffset(), false));
} else {
lastResolvedTypes.add(typeUsage1);
}
}
}
}
}
}
if(!localObjects.isEmpty()){
for(JsObject lObject : localObjects) {
if(lObject.getAssignmentForOffset(offset).isEmpty()) {
boolean addAsType = lObject.getJSKind() == JsElement.Kind.OBJECT_LITERAL;
if (lObject instanceof JsObjectReference) {
// translate reference objects to the original objects / type
JsObject original = ((JsObjectReference)lObject).getOriginal();
if (original != null){
name = original.getDeclarationName() != null ? original.getDeclarationName().getName() : original.getName();
}
}
if(addAsType) {
// here it doesn't have to be real type, it's possible that it's just an object name
lastResolvedTypes.add(new TypeUsage(name, -1, true));
}
}
if ("@mtd".equals(kind)) { //NOI18N
if (lObject.getJSKind().isFunction()) {
// if it's a method call, add all retuturn types
lastResolvedTypes.addAll(((JsFunction) lObject).getReturnTypes());
}
int lastCallOffset = -1;
for (Occurrence occurrence : lObject.getOccurrences()) {
if (lastCallOffset < occurrence.getOffsetRange().getStart() && occurrence.getOffsetRange().getStart() <= offset) {
lastCallOffset = occurrence.getOffsetRange().getStart();
}
}
Collection<TypeUsage> returnTypesFromFrameworks = model.getReturnTypesFromFrameworks(lObject.getName(), lastCallOffset);
if (returnTypesFromFrameworks != null && !returnTypesFromFrameworks.isEmpty()) {
lastResolvedTypes.addAll(returnTypesFromFrameworks);
}
if (jsIndex != null) {
Collection<? extends IndexResult> findByFqn = jsIndex.findByFqn(name, Index.TERMS_BASIC_INFO);
for (Iterator<? extends IndexResult> iterator = findByFqn.iterator(); iterator.hasNext();) {
IndexedElement indexElement = IndexedElement.create(iterator.next());
if(indexElement instanceof IndexedElement.FunctionIndexedElement) {
IndexedElement.FunctionIndexedElement iFunction = (IndexedElement.FunctionIndexedElement)indexElement;
for (String type : iFunction.getReturnTypes()) {
lastResolvedTypes.add(new TypeUsage(type, -1, false));
}
}
}
}
} else if ("@arr".equals(kind) && lObject instanceof JsArray) {
lastResolvedTypes.addAll(((JsArray) lObject).getTypesInArray());
} else {
// just property
Collection<? extends Type> lastTypeAssignment = lObject.getAssignmentForOffset(offset);
// we need to process the object later anyway. To get learning cc, see issue #224453
lastResolvedObjects.add(lObject);
if (!lastTypeAssignment.isEmpty()) {
// go through the assignments and find the last object / type in the assignment chain
// it solve assignements like a = b; b = c; c = d;. the result for a should be d.
resolveAssignments(model, lObject, offset, lastResolvedObjects, lastResolvedTypes);
break;
}
}
}
}
// now we should have collected possible local objects
// also objects from index, that fits the first part of the expression
} else {
List<JsObject> newResolvedObjects = new ArrayList<JsObject>();
List<TypeUsage> newResolvedTypes = new ArrayList<TypeUsage>();
for (JsObject localObject : lastResolvedObjects) {
// go through the loca object and try find the method / property from the next expression part
JsObject property = ((JsObject) localObject).getProperty(name);
if (property != null) {
if ("@mtd".equals(kind)) { //NOI18N
if (property.getJSKind().isFunction()) {
//Collection<TypeUsage> resovledTypes = resolveTypeFromSemiType(model, property, ((JsFunction) property).getReturnTypes());
Collection<? extends TypeUsage> resovledTypes = ((JsFunction) property).getReturnTypes();
newResolvedTypes.addAll(resovledTypes);
}
} else if ("@arr".equals(kind)) {
if (property instanceof JsArray) {
newResolvedTypes.addAll(((JsArray) property).getTypesInArray());
}
} else {
Collection<? extends TypeUsage> lastTypeAssignment = property.getAssignmentForOffset(offset);
if (lastTypeAssignment.isEmpty()) {
newResolvedObjects.add(property);
} else {
newResolvedTypes.addAll(lastTypeAssignment);
if(!property.getProperties().isEmpty()) {
newResolvedObjects.add(property);
}
}
}
}
}
for (TypeUsage typeUsage : lastResolvedTypes) {
if (jsIndex != null) {
// for the type build the prototype chain.
Collection<String> prototypeChain = new ArrayList<String>();
String typeName = typeUsage.getType();
if (typeName.contains(SemiTypeResolverVisitor.ST_EXP)) {
typeName = typeName.substring(typeName.indexOf(SemiTypeResolverVisitor.ST_EXP) + SemiTypeResolverVisitor.ST_EXP.length());
}
if (typeName.contains(SemiTypeResolverVisitor.ST_PRO)) {
typeName = typeName.replace(SemiTypeResolverVisitor.ST_PRO, ".");
}
prototypeChain.add(typeName);
prototypeChain.addAll(findPrototypeChain(typeName, jsIndex));
Collection<? extends IndexResult> indexResults = null;
String propertyToCheck = null;
for (String fqn : prototypeChain) {
// at first look at the properties of the object
propertyToCheck = fqn + "." + name;
indexResults = jsIndex.findByFqn(propertyToCheck,
Index.FIELD_FLAG, Index.FIELD_RETURN_TYPES, Index.FIELD_ARRAY_TYPES, Index.FIELD_ASSIGNMENTS); //NOI18N
if (indexResults.isEmpty() && !fqn.endsWith(".prototype")) {
// if the property was not found, try to look at the prototype of the object
propertyToCheck = fqn + ".prototype." + name;
indexResults = jsIndex.findByFqn(propertyToCheck,
Index.FIELD_FLAG, Index.FIELD_RETURN_TYPES, Index.FIELD_ARRAY_TYPES, Index.FIELD_ASSIGNMENTS); //NOI18N
}
if(!indexResults.isEmpty()) {
// if the property / method was already found, we don't need to continue.
// in the runtime is also used the first one that is found in the prototype chain
break;
}
propertyToCheck = null;
}
boolean checkProperty = (indexResults == null || indexResults.isEmpty()) && !"@mtd".equals(kind);
if (indexResults != null) {
for (IndexResult indexResult : indexResults) {
// go through the resul from index and add appropriate types to the new resolved
JsElement.Kind jsKind = IndexedElement.Flag.getJsKind(Integer.parseInt(indexResult.getValue(Index.FIELD_FLAG)));
if ("@mtd".equals(kind) && jsKind.isFunction()) {
//Collection<TypeUsage> resolved = resolveTypeFromSemiType(model, ModelUtils.findJsObject(model, offset), IndexedElement.getReturnTypes(indexResult));
Collection<TypeUsage> resolvedTypes = IndexedElement.getReturnTypes(indexResult);
ModelUtils.addUniqueType(newResolvedTypes, resolvedTypes);
} else if ("@arr".equals(kind)) { // NOI18N
Collection<TypeUsage> resolvedTypes = IndexedElement.getArrayTypes(indexResult);
ModelUtils.addUniqueType(newResolvedTypes, resolvedTypes);
} else {
checkProperty = true;
}
}
}
if (checkProperty) {
String propertyFQN = propertyToCheck != null ? propertyToCheck : typeName + "." + name;
List<TypeUsage> fromAssignment = new ArrayList<TypeUsage>();
resolveAssignments(model, jsIndex, propertyFQN, -1, fromAssignment);
if (fromAssignment.isEmpty()) {
ModelUtils.addUniqueType(newResolvedTypes, new TypeUsage(propertyFQN));
} else {
ModelUtils.addUniqueType(newResolvedTypes, fromAssignment);
}
}
}
// from libraries look for top level types
for (JsObject libGlobal : ModelExtender.getDefault().getExtendingGlobalObjects(model.getGlobalObject().getFileObject())) {
for (JsObject object : libGlobal.getProperties().values()) {
if (object.getName().equals(typeUsage.getType())) {
JsObject property = object.getProperty(name);
if (property != null) {
JsElement.Kind jsKind = property.getJSKind();
if ("@mtd".equals(kind) && jsKind.isFunction()) {
newResolvedTypes.addAll(((JsFunction) property).getReturnTypes());
} else {
newResolvedObjects.add(property);
}
}
break;
}
}
}
}
lastResolvedObjects = newResolvedObjects;
lastResolvedTypes = newResolvedTypes;
}
}
HashMap<String, TypeUsage> resultTypes = new HashMap<String, TypeUsage> ();
for (TypeUsage typeUsage : lastResolvedTypes) {
if(!resultTypes.containsKey(typeUsage.getType())) {
resultTypes.put(typeUsage.getType(), typeUsage);
}
}
for (JsObject jsObject : lastResolvedObjects) {
if (jsObject.isDeclared()) {
String fqn = jsObject.getFullyQualifiedName();
if (!resultTypes.containsKey(fqn)) {
if (includeAllPossible || hasDeclaredProperty(jsObject)) {
resultTypes.put(fqn, new TypeUsage(fqn, offset));
}
}
}
}
return resultTypes.values();
}
public static boolean hasDeclaredProperty(JsObject jsObject) {
boolean result = false;
Iterator<? extends JsObject> it = jsObject.getProperties().values().iterator();
while (!result && it.hasNext()) {
JsObject property = it.next();
result = property.isDeclared();
if (!result) {
result = hasDeclaredProperty(property);
}
}
return result;
}
public static List<String> expressionFromType(TypeUsage type) {
String sexp = type.getType();
if ((sexp.startsWith("@exp;") || sexp.startsWith("@new;") || sexp.startsWith("@arr;") || sexp.contains("@pro;")
|| sexp.startsWith("@call;") || sexp.startsWith(SemiTypeResolverVisitor.ST_WITH)) && (sexp.length() > 5)) {
if (sexp.charAt(0) == '@') {
int start = sexp.startsWith("@call;") || sexp.startsWith("@arr;") || sexp.startsWith(SemiTypeResolverVisitor.ST_WITH) ? 1 : sexp.charAt(5) == '@' ? 6 : 5;
sexp = sexp.substring(start);
}
List<String> nExp = new ArrayList<String>();
String[] split = sexp.split("@");
for (int i = split.length - 1; i > -1; i--) {
nExp.add(split[i].substring(split[i].indexOf(';') + 1));
if (split[i].startsWith("arr;")) {
nExp.add("@arr");
} else if (split[i].startsWith("call;")) {
nExp.add("@mtd");
} else if (split[i].startsWith("with;")) {
nExp.add("@with");
}else {
nExp.add("@pro");
}
}
return nExp;
} else {
return Collections.singletonList(type.getType());
}
}
/**
* Resolve assignments to return a resolved typeUsage
*
* @param parent Parent Object
* @param typeHere TypeUsage for the assignment
* @return
*/
public static TypeUsage createResolvedType(JsObject parent, TypeUsage typeHere) {
int invokeCount = 0;
String fqn = getFQNFromType(typeHere);
List<TypeUsage> alreadyResolved = new ArrayList<>();
return resolveTypes(parent, fqn, typeHere.getOffset(), alreadyResolved, invokeCount);
}
/* @return TypeUsage with generated typename string
*/
private static TypeUsage resolveTypes(JsObject parent, String fqn, int offset, List<TypeUsage> alreadyResolved, int invokeCount) {
invokeCount++;
String name = fqn;
StringBuilder props = new StringBuilder();
int indx = fqn.indexOf(".");
if (indx != -1) {
name = fqn.substring(0, indx);
props.append(fqn.substring(indx + 1));
}
List<TypeUsage> localResolved = new ArrayList<>();
resolveAssignments(parent, name, offset, localResolved, props);
List<TypeUsage> diff = localResolved.stream().filter(type -> !alreadyResolved.contains(type)).collect(Collectors.toList());
if (diff.size() > 0) {
alreadyResolved.addAll(diff);
boolean typeResolved = false;
for (TypeUsage type : localResolved) {
if (type.isResolved()) {
String newObjectName = type.getType();
JsObject object = ModelUtils.searchJsObjectByName(parent, newObjectName);
if ((object != null) && (object != parent)) {
String partfqn = props.toString();
if (!partfqn.trim().equals("")) {
String[] tokens = partfqn.split("\\.");
for (int i = 0; i < tokens.length; i++) {
object = ModelUtils.searchJsObjectByName(parent, newObjectName);
for (JsObject prop : object.getProperties().values()) {
if ((prop.getName().equals(tokens[i])) && (prop.isDeclared())) {
if (prop.getAssignmentCount() > 0) {
if (invokeCount == MAX_RECURSION_DEEP_RESOLVING_ASSIGNMENTS) {
LOG.log(Level.WARNING, "StackOverFlowError : {0} : {1}",
new Object[]{object.getFullyQualifiedName(),
object.getFileObject()});
}
return resolveTypes(object, String.join(".", Arrays.copyOfRange(tokens, i, tokens.length)), offset, alreadyResolved, invokeCount);
} else {
object = prop;
newObjectName = newObjectName + "." + prop.getName();
if (i == tokens.length - 1) {
typeResolved = true;
}
break;
}
}
}
}
if (typeResolved) {
return new TypeUsage(newObjectName, type.getOffset(), true);
}
}
}
}
}
}
return new TypeUsage(fqn, offset, false);
}
private static void resolveAssignments(JsObject jsObject, String fqn, int offset, List<TypeUsage> resolved, StringBuilder nestedProperties) {
int invokeCount = 0;
Set<String> alreadyProcessed = new HashSet<String>();
for (TypeUsage type : resolved) {
alreadyProcessed.add(type.getType());
}
resolveAssignments(jsObject, fqn, offset, resolved, alreadyProcessed, nestedProperties, invokeCount);
}
/**
* Resolves assignments for the top level object in a given fqn
*
* @param parent parent JsObject
* @param fqn assignment Type name string
* @param offset assignment Type offset
* @param resolved list of resolved TypeUsages
* @param alreadyProcessed List of FQNs already resolved
* @param nestedProperties "." separated nested property names string part
* of FQN
*/
private static void resolveAssignments(JsObject parent, String fqn, int offset, List<TypeUsage> resolved, Set<String> alreadyProcessed, StringBuilder nestedProperties, int invokeCount) {
if (!alreadyProcessed.contains(fqn)) {
invokeCount++;
alreadyProcessed.add(fqn);
String fqnCorrected = ModelUtils.getFQNFromType(new TypeUsage(fqn, offset, false));
//resolve the parent object in fqn
int index = fqnCorrected.indexOf(".");//NOI18N
if (index != -1) {
//save the remaining fqn part to locate in object properties later
nestedProperties.insert(0, fqnCorrected.substring(index + 1) + ".");
fqnCorrected = fqnCorrected.substring(0, index);
}
if (!fqnCorrected.startsWith("@")) {
List<TypeUsage> toProcess = new ArrayList<TypeUsage>();
JsObject object = ModelUtils.searchJsObjectByName(parent, fqnCorrected);
if ((object != null) && (((JsObjectImpl) object).getAssignmentCount() > 0)) {
for (TypeUsage type : ((JsObjectImpl) object).getAssignments()) {
if (!type.isResolved()) {
for (TypeUsage resolvedType : resolveTypeFromSemiType(object, type)) {
toProcess.add(resolvedType);
}
} else {
toProcess.add(type);
}
}
for (TypeUsage type : toProcess) {
if (!alreadyProcessed.contains(type.getType())) {
if (invokeCount == MAX_RECURSION_DEEP_RESOLVING_ASSIGNMENTS) {
LOG.log(Level.WARNING, "StackOverFlowError : {0} : {1}",
new Object[]{object.getFullyQualifiedName(),
object.getFileObject()});
}
resolveAssignments(parent, type.getType(), type.getOffset(), resolved, alreadyProcessed, nestedProperties, invokeCount);
}
}
} else {
ModelUtils.addUniqueType(resolved, new TypeUsage(fqnCorrected, offset, true));
}
} else {
ModelUtils.addUniqueType(resolved, new TypeUsage(fqnCorrected, offset, false));
}
}
}
private static JsObject searchJsObjectByName(JsObject parent, String fqn) {
JsObject object = ModelUtils.findJsObjectByName(parent, fqn);
if (object == null) {
if (!isGlobal(parent)) {
if (parent.getParent() != null) {
parent = parent.getParent();
return searchJsObjectByName(parent, fqn);
}
}
}
return object;
}
public static Collection<TypeUsage> resolveTypes(Collection<? extends TypeUsage> unresolved, Model model, Index jsIndex, boolean includeAllPossible) {
//assert !SwingUtilities.isEventDispatchThread() : "Type resolution may block AWT due to index search";
Collection<TypeUsage> types = new ArrayList<TypeUsage>(unresolved);
if (types.size() == 1 && types.iterator().next().isResolved()) {
return types;
}
Set<String> original = null;
int cycle = 0;
boolean resolvedAll = false;
while (!resolvedAll && cycle < 10) {
cycle++;
resolvedAll = true;
Collection<TypeUsage> resolved = new ArrayList<TypeUsage>();
for (TypeUsage typeUsage : types) {
if (!typeUsage.isResolved()) {
if (original == null) {
original = new HashSet<String>(unresolved.size());
for (TypeUsage t : unresolved) {
original.add(t.getType());
}
}
resolvedAll = false;
List<String> nExp = expressionFromType(typeUsage);
if (nExp.size() > 1) {
// passing original prevents the unresolved return types
// when recursion in place
ModelUtils.addUniqueType(resolved, original, ModelUtils.resolveTypeFromExpression(model, jsIndex, nExp, typeUsage.getOffset(), includeAllPossible));
} else {
ModelUtils.addUniqueType(resolved, new TypeUsage(typeUsage.getType(), typeUsage.getOffset(), true));
}
} else {
ModelUtils.addUniqueType(resolved, (TypeUsage) typeUsage);
}
}
types.clear();
types = new ArrayList<TypeUsage>(resolved);
}
return types;
}
private static void resolveAssignments(Model model, JsObject jsObject, int offset, List<JsObject> resolvedObjects, List<TypeUsage> resolvedTypes) {
Collection<? extends TypeUsage> assignments = jsObject.getAssignmentForOffset(offset);
for (TypeUsage typeName : assignments) {
if (typeName.isResolved()) {
resolvedTypes.add(typeName);
continue;
}
String type = typeName.getType();
if (type.startsWith(SemiTypeResolverVisitor.ST_WITH)) {
List<String> expression = expressionFromType((TypeUsage)typeName);
Collection<? extends TypeUsage> typesFromWith = ModelUtils.getTypeFromWith(model, typeName.getOffset());
expression.remove(expression.size() - 1);
expression.remove(expression.size() - 1);
StringBuilder sb = new StringBuilder();
for (int i = expression.size() - 1; i > 0; i--) {
sb.append(expression.get(i--));
sb.append(";");
sb.append(expression.get(i));
}
for (TypeUsage typeWith: typesFromWith) {
resolvedTypes.add(new TypeUsage(SemiTypeResolverVisitor.ST_EXP + typeWith.getType() + sb.toString(), typeName.getOffset(), false));
}
resolvedTypes.add(new TypeUsage(sb.toString(), typeName.getOffset(), false));
} else {
JsObject byOffset = findObjectForOffset(typeName.getType(), offset, model);
if (byOffset != null) {
if(!jsObject.getName().equals(byOffset.getName())) {
resolvedObjects.add(byOffset);
resolveAssignments(model, byOffset, offset, resolvedObjects, resolvedTypes);
}
} else {
resolvedTypes.add((TypeUsage)typeName);
}
}
}
}
private static int deepRA = 0;
private static void resolveAssignments(Model model, Index jsIndex, String fqn, int offset, List<TypeUsage> resolved) {
Set<String> alreadyProcessed = new HashSet<String>();
deepRA = 0;
for(TypeUsage type : resolved) {
alreadyProcessed.add(type.getType());
}
resolveAssignments(model, jsIndex, fqn, offset, resolved, alreadyProcessed);
}
private static void resolveAssignments(Model model, Index jsIndex, String fqn, int offset, List<TypeUsage> resolved, Set<String> alreadyProcessed) {
if (!alreadyProcessed.contains(fqn)) {
alreadyProcessed.add(fqn);
deepRA++;
String fqnCorrected = fqn;
if (fqnCorrected.startsWith(SemiTypeResolverVisitor.ST_EXP) && !fqnCorrected.contains(SemiTypeResolverVisitor.ST_CALL)) {
fqnCorrected = fqnCorrected.substring(fqnCorrected.indexOf(SemiTypeResolverVisitor.ST_EXP) + SemiTypeResolverVisitor.ST_EXP.length());
fqnCorrected = fqnCorrected.replace(SemiTypeResolverVisitor.ST_PRO, "."); //NOI18N
}
if (!fqnCorrected.startsWith("@")) {
if (jsIndex != null) {
Collection<? extends IndexResult> indexResults = jsIndex.findByFqn(fqnCorrected, Index.FIELD_ASSIGNMENTS);
boolean hasAssignments = false;
boolean isType = false;
for (IndexResult indexResult: indexResults) {
Collection<TypeUsage> assignments = IndexedElement.getAssignments(indexResult);
if (!assignments.isEmpty()) {
hasAssignments = true;
for (TypeUsage type : assignments) {
if (resolved.size() > 10) {
resolved.clear();
break;
}
if (!alreadyProcessed.contains(type.getType()) && deepRA < MAX_RECURSION_DEEP_RESOLVING_ASSIGNMENTS) {
resolveAssignments(model, jsIndex, type.getType(), type.getOffset(), resolved, alreadyProcessed);
}
}
}
}
if (indexResults.isEmpty()) {
JsObject found = ModelUtils.findJsObjectByName(model.getGlobalObject(), fqnCorrected);
if (found != null) {
Collection<? extends TypeUsage> assignments = found.getAssignments();
if (!assignments.isEmpty()) {
hasAssignments = true;
List<TypeUsage> toProcess = new ArrayList<TypeUsage>();
for (TypeUsage type : assignments) {
if (!type.isResolved()) {
for (TypeUsage resolvedType : resolveTypeFromSemiType(found, type)) {
toProcess.add(resolvedType);
}
} else {
toProcess.add(type);
}
}
for (TypeUsage type : toProcess) {
if (!alreadyProcessed.contains(type.getType()) && deepRA < MAX_RECURSION_DEEP_RESOLVING_ASSIGNMENTS) {
resolveAssignments(model, jsIndex, type.getType(), type.getOffset(), resolved, alreadyProcessed);
}
}
}
}
}
Collection<IndexedElement> properties = jsIndex.getProperties(fqnCorrected);
for (IndexedElement property : properties) {
if (property.getFQN().startsWith(fqnCorrected) && (property.isDeclared() || ModelUtils.PROTOTYPE.equals(property.getName()))) {
isType = true;
break;
}
}
if(!hasAssignments || isType) {
ModelUtils.addUniqueType(resolved, new TypeUsage(fqnCorrected, offset, true));
}
}
} else {
ModelUtils.addUniqueType(resolved, new TypeUsage(fqn, offset, false));
}
}
}
public static JsObject findObjectForOffset(String name, int offset, Model model) {
for (JsObject object : model.getVariables(offset)) {
if (object.getName().equals(name)) {
return object;
}
}
return null;
}
public static Collection<String> findPrototypeChain(String fqn, Index jsIndex) {
Collection<String> chain = findPrototypeChain(fqn, jsIndex, new HashSet<String>());
return chain;
}
private static Collection<String> findPrototypeChain(String fqn, Index jsIndex, Set<String> alreadyCheck) {
Collection<String> result = new HashSet<>();
if (!alreadyCheck.contains(fqn)) {
alreadyCheck.add(fqn);
Collection<? extends IndexResult> indexResults = jsIndex.findByFqn(fqn + "." + ModelUtils.PROTOTYPE, Index.FIELD_ASSIGNMENTS); //NOI18N
for (IndexResult indexResult : indexResults) {
Collection<TypeUsage> assignments = IndexedElement.getAssignments(indexResult);
for (TypeUsage typeUsage : assignments) {
result.add(typeUsage.getType());
}
for (TypeUsage typeUsage : assignments) {
result.addAll(findPrototypeChain(typeUsage.getType(), jsIndex, alreadyCheck));
}
}
}
if (result.isEmpty()) {
result.add("Object"); //NOI18N
}
return result;
}
/**
*
* @param model
* @param offset
* @return types from with expressions. The collection has the order of items from most inner with to
* the outer with.
*/
public static Collection <? extends TypeUsage> getTypeFromWith(Model model, int offset) {
JsObject jsObject = ModelUtils.findJsObject(model, offset);
JsObject previous = jsObject;
while (jsObject != null && jsObject.isAnonymous() && jsObject.getJSKind() != JsElement.Kind.WITH_OBJECT) {
jsObject = ModelUtils.findJsObject(model, jsObject.getOffset() - 1);
if (jsObject.getFullyQualifiedName().endsWith(previous.getFullyQualifiedName())) {
break;
} else {
previous = jsObject;
}
}
while(jsObject != null && jsObject.getJSKind() != JsElement.Kind.WITH_OBJECT) {
jsObject = jsObject.getParent();
}
if (jsObject != null && jsObject.getJSKind() == JsElement.Kind.WITH_OBJECT) {
List<TypeUsage> types = new ArrayList<TypeUsage>();
JsWith wObject = (JsWith)jsObject;
Collection<? extends TypeUsage> withTypes = wObject.getTypes();
types.addAll(withTypes);
while (wObject.getOuterWith() != null) {
wObject = wObject.getOuterWith();
withTypes = wObject.getTypes();
types.addAll(withTypes);
}
return types;
}
return Collections.EMPTY_LIST;
}
public static Collection<Identifier> getDefinedGlobal(final Snapshot snapshot, final int offset) {
ArrayList<Identifier> names = new ArrayList<Identifier>();
List<JsTokenId> findToken = Arrays.asList(JsTokenId.BLOCK_COMMENT);
TokenSequence<? extends JsTokenId> ts = LexUtilities.getJsTokenSequence(snapshot, offset);
if (ts == null) {
return names;
}
ts.move(0);
Token<? extends JsTokenId> token = LexUtilities.findNextIncluding(ts, findToken);
while (token != null && token.id() == JsTokenId.BLOCK_COMMENT) {
int iOffset = ts.offset();
String text = token.text().toString();
// cut /*
text = text.substring(2);
iOffset += 2;
while (!text.isEmpty() && text.charAt(0) == ' ') {
text = text.substring(1);
iOffset++;
}
if (text.startsWith(GLOBAL_DIRECTIVE) && text.length() > GLOBAL_DIRECTIVE.length() && text.charAt(GLOBAL_DIRECTIVE.length()) == ' ') {
text = text.substring(GLOBAL_DIRECTIVE.length() + 1);
iOffset = iOffset + GLOBAL_DIRECTIVE.length() + 1;
String[] parts = text.split(",");
for (String part : parts) {
String name = part;
int nameOffset = iOffset;
while (!name.isEmpty() && name.charAt(0) == ' ') {
name = name.substring(1);
nameOffset++;
}
name = name.trim();
if (name.indexOf('*') > 0) {
name = name.substring(0, name.indexOf('*')).trim();
}
if (name.indexOf(':') > 0) {
name = name.substring(0, name.indexOf(':')).trim();
}
if (!name.isEmpty()) {
names.add(new Identifier(name, new OffsetRange(nameOffset, nameOffset + name.length())));
}
iOffset = iOffset + part.length() + 1;
}
}
if (ts.moveNext()) {
token = LexUtilities.findNextIncluding(ts, findToken);
} else {
break;
}
}
return names;
}
public static List<? extends JsObject> getExtendingGlobalObjects(FileObject fo) {
return ModelExtender.getDefault().getExtendingGlobalObjects(fo);
}
public static void addUniqueType(Collection <TypeUsage> where, Set<String> forbidden, TypeUsage type) {
String typeName = type.getType();
if (forbidden.contains(typeName)) {
return;
}
for (TypeUsage utype : where) {
if (utype.getType().equals(typeName)) {
return;
}
}
where.add(type);
}
public static void addUniqueType(Collection <TypeUsage> where, TypeUsage type) {
addUniqueType(where, Collections.<String>emptySet(), type);
}
public static void addUniqueType(Collection <TypeUsage> where, Set<String> forbidden, Collection <TypeUsage> what) {
for (TypeUsage type: what) {
addUniqueType(where, forbidden, type);
}
}
public static void addUniqueType(Collection <TypeUsage> where, Collection <TypeUsage> what) {
addUniqueType(where, Collections.<String>emptySet(), what);
}
public static void addDocTypesOccurence(JsObject jsObject, JsDocumentationHolder docHolder) {
if (docHolder.getOccurencesMap().containsKey(jsObject.getFullyQualifiedName())) {
for (OffsetRange offsetRange : docHolder.getOccurencesMap().get(jsObject.getFullyQualifiedName())) {
((JsObjectImpl)jsObject).addOccurrence(offsetRange);
}
}
}
public static String getDisplayName(String typeName) {
String displayName = typeName;
if (displayName.startsWith("@param;") || displayName.contains(ModelBuilder.WITH_OBJECT_NAME_START)
|| displayName.contains(ModelBuilder.ANONYMOUS_OBJECT_NAME_START)) {
displayName = "";
} else {
if (displayName.contains(GENERATED_FUNCTION_PREFIX)) {
displayName = removeGeneratedFromFQN(displayName, GENERATED_FUNCTION_PREFIX);
}
if (displayName.contains(GENERATED_ANONYM_PREFIX)) {
displayName = removeGeneratedFromFQN(displayName, GENERATED_ANONYM_PREFIX);
}
}
return displayName;
}
public static String getDisplayName(Type type) {
List<TypeNameConvertor> convertors = ModelExtender.getDefault().getTypeNameConvertors();
String displayName = null;
for (TypeNameConvertor convertor: convertors) {
displayName = convertor.getDisplayName(type);
if (displayName != null && !displayName.isEmpty() && !displayName.equals(type.getType())) {
break;
}
}
if (displayName == null || displayName.isEmpty() || displayName.equals(type.getType())) {
displayName = ModelUtils.getDisplayName(type.getType());
}
return displayName;
}
/**
*
* @param fqn fully qualified name of the type
* @param generated the generated prefix
* @return the fully qualified name without the generated part or empty string if the generated name is the last one.
*
*/
private static String removeGeneratedFromFQN(String fqn, String generated) {
String[] parts = fqn.split("\\."); //NOI18N
String part = parts[parts.length - 1];
if(part.contains(generated)) {
try {
Integer.parseInt(part.substring(generated.length()));
return ""; // return empty name if the last name is generated
} catch (NumberFormatException nfe) {
// do nothing
}
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < parts.length; i++) {
part = parts[i];
boolean add = true;
if (part.startsWith(generated) || (i == 0 && part.contains(generated))) {
// if (part.startsWith(generated)) {
try {
Integer.parseInt(part.substring(part.indexOf(generated) + generated.length()));
// Integer.parseInt(part.substring(generated.length()));
add = false;
} catch (NumberFormatException nfe) {
// do nothing
}
}
if (add) {
sb.append(part);
if (i < (parts.length - 1)) {
sb.append(".");
}
}
}
return sb.toString();
}
private static List<String> knownGlobalObjects = Arrays.asList("window", "document", "console",
"clearInterval", "clearTimeout", "event", "frames", "history",
"Image", "location", "name", "navigator", "Option", "parent", "screen", "setInterval", "setTimeout",
"XMLHttpRequest", "JSON", "Date", Type.UNDEFINED, "Math", //NOI18N
Type.ARRAY, Type.OBJECT, Type.BOOLEAN, Type.NULL, Type.NUMBER, Type.REGEXP, Type.STRING, Type.UNDEFINED, Type.UNRESOLVED,
Type.NAN, Type.INFINITY);
public static boolean isKnownGLobalType(String type) {
return knownGlobalObjects.contains(type);
}
/**
*
* @param snapshot
* @param offset offset where the expression should be resolved
* @param lookBefore if yes, looks for the beginning of the expression before the offset,
* if no, it can be in a middle of expression
* @return
*/
public static List<String> resolveExpressionChain(Snapshot snapshot, int offset, boolean lookBefore) {
TokenHierarchy<?> th = snapshot.getTokenHierarchy();
TokenSequence<? extends JsTokenId> ts = LexUtilities.getJsTokenSequence(th, offset);
if (ts == null) {
return Collections.<String>emptyList();
}
ts.move(offset);
if (ts.movePrevious() && (ts.moveNext() || ((ts.offset() + ts.token().length()) == snapshot.getText().length()))) {
if (!lookBefore && ts.token().id() != JsTokenId.OPERATOR_DOT) {
ts.movePrevious();
}
Token<? extends JsTokenId> token = lookBefore ? LexUtilities.findPrevious(ts, Arrays.asList(JsTokenId.WHITESPACE, JsTokenId.BLOCK_COMMENT, JsTokenId.EOL)) : ts.token();
int parenBalancer = 0;
// 1 - method call, 0 - property, 2 - array
int partType = 0;
boolean wasLastDot = lookBefore;
int offsetFirstRightParen = -1;
List<String> exp = new ArrayList();
while (token.id() != JsTokenId.OPERATOR_SEMICOLON
&& token.id() != JsTokenId.BRACKET_RIGHT_CURLY && token.id() != JsTokenId.BRACKET_LEFT_CURLY
&& token.id() != JsTokenId.BRACKET_LEFT_PAREN
&& token.id() != JsTokenId.BLOCK_COMMENT
&& token.id() != JsTokenId.LINE_COMMENT
&& token.id() != JsTokenId.OPERATOR_ASSIGNMENT
&& token.id() != JsTokenId.OPERATOR_PLUS) {
if (token.id() == JsTokenId.WHITESPACE) {
// we need to find out, whether this is a continual expression on the new line
int helpOffset = ts.offset();
if (ts.movePrevious()) {
token = LexUtilities.findPrevious(ts, Arrays.asList(JsTokenId.WHITESPACE, JsTokenId.BLOCK_COMMENT, JsTokenId.LINE_COMMENT, JsTokenId.EOL));
if (token.id() != JsTokenId.BRACKET_RIGHT_PAREN && token.id() != JsTokenId.IDENTIFIER
&& token.id() != JsTokenId.OPERATOR_DOT) {
ts.move(helpOffset);
ts.moveNext();
token = ts.token();
break;
}
}
}
if (token.id() != JsTokenId.EOL) {
if (token.id() != JsTokenId.OPERATOR_DOT) {
if (token.id() == JsTokenId.BRACKET_RIGHT_PAREN) {
parenBalancer++;
partType = 1;
if (offsetFirstRightParen == -1) {
offsetFirstRightParen = ts.offset();
}
while (parenBalancer > 0 && ts.movePrevious()) {
token = ts.token();
if (token.id() == JsTokenId.BRACKET_RIGHT_PAREN) {
parenBalancer++;
} else {
if (token.id() == JsTokenId.BRACKET_LEFT_PAREN) {
parenBalancer--;
}
}
}
} else if (token.id() == JsTokenId.BRACKET_RIGHT_BRACKET) {
parenBalancer++;
partType = 2;
while (parenBalancer > 0 && ts.movePrevious()) {
token = ts.token();
if (token.id() == JsTokenId.BRACKET_RIGHT_BRACKET) {
parenBalancer++;
} else {
if (token.id() == JsTokenId.BRACKET_LEFT_BRACKET) {
parenBalancer--;
}
}
}
} else if (parenBalancer == 0 && "operator".equals(token.id().primaryCategory())) { // NOI18N
return exp;
} else {
exp.add(token.text().toString());
switch (partType) {
case 0:
exp.add("@pro"); // NOI18N
break;
case 1:
exp.add("@mtd"); // NOI18N
offsetFirstRightParen = -1;
break;
case 2:
exp.add("@arr"); // NOI18N
break;
default:
break;
}
partType = 0;
wasLastDot = false;
}
} else {
wasLastDot = true;
}
} else {
if (!wasLastDot && ts.movePrevious()) {
// check whether it's continuatino of previous line
token = LexUtilities.findPrevious(ts, Arrays.asList(JsTokenId.WHITESPACE, JsTokenId.BLOCK_COMMENT, JsTokenId.LINE_COMMENT));
if (token.id() != JsTokenId.OPERATOR_DOT) {
// the dot was not found => it's not continuation of expression
break;
}
}
}
if (!ts.movePrevious()) {
break;
}
token = ts.token();
}
if (token.id() == JsTokenId.WHITESPACE) {
if (ts.movePrevious()) {
token = LexUtilities.findPrevious(ts, Arrays.asList(JsTokenId.WHITESPACE, JsTokenId.BLOCK_COMMENT, JsTokenId.EOL));
if (token.id() == JsTokenId.KEYWORD_NEW && !exp.isEmpty()) {
exp.remove(exp.size() - 1);
exp.add("@pro"); // NOI18N
} else if (!lookBefore && offsetFirstRightParen > -1) {
// in the case when the expression is like ( new Object()).someMethod
exp.addAll(resolveExpressionChain(snapshot, offsetFirstRightParen - 1, true));
}
}
} else if (exp.isEmpty() && !lookBefore && offsetFirstRightParen > -1) {
// in the case when the expression is like ( new Object()).someMethod
exp.addAll(resolveExpressionChain(snapshot, offsetFirstRightParen - 1, true));
} else if (wasLastDot && !lookBefore && token.id() == JsTokenId.BRACKET_RIGHT_CURLY) {
int balancer = 1;
while (balancer > 0 && ts.movePrevious()) {
token = ts.token();
if (token.id() == JsTokenId.BRACKET_RIGHT_CURLY) {
balancer++;
} else {
if (token.id() == JsTokenId.BRACKET_LEFT_CURLY) {
balancer--;
}
}
}
exp.add("@ano:" + ts.offset());
exp.add("@pro");
}
return exp;
}
return Collections.<String>emptyList();
}
public static void moveProperty (JsObject newParent, JsObject property) {
JsObject newProperty = newParent.getProperty(property.getName());
if (property.getParent() != null) {
property.getParent().getProperties().remove(property.getName());
}
if (newProperty == null) {
((JsObjectImpl)property).setParent(newParent);
newParent.addProperty(property.getName(), property);
} else {
if (property.isDeclared() && !newProperty.isDeclared()) {
JsObject tmpProperty = newProperty;
newParent.addProperty(property.getName(), property);
((JsObjectImpl)property).setParent(newParent);
newProperty = property;
property = tmpProperty;
}
JsObjectImpl.moveOccurrenceOfProperties((JsObjectImpl) newProperty, property);
for (Occurrence occurrence : property.getOccurrences()) {
newProperty.addOccurrence(occurrence.getOffsetRange());
}
List<JsObject>propertiesToMove = new ArrayList(property.getProperties().values());
for (JsObject propOfProperty: propertiesToMove) {
moveProperty(newProperty, propOfProperty);
}
}
}
/**
* It change the declaration scope of the input object to the new scope.
* If the where object is not a function (Declaration Scope), then it's all the properties are
* scanned recursively to change the declaration scope to the new one. It doesn't change the parents
* of the objects, just the declaration scope. Usually is used, when you need wrap the object to the
* new virtual function.
* @param where the object which is moved from one declaration scope to another one.
* @param newScope new declaration scope
*/
public static void changeDeclarationScope(JsObject where, DeclarationScope newScope) {
changeDeclarationScope(where, newScope, new HashSet<String>());
}
private static void changeDeclarationScope(JsObject where, DeclarationScope newScope, Set<String> done) {
if (!done.contains(where.getFullyQualifiedName())) {
done.add(where.getFullyQualifiedName());
if (where instanceof DeclarationScope) {
if (where.isDeclared()) {
DeclarationScope scope = (DeclarationScope)where;
DeclarationScope oldScope = scope.getParentScope();
if (oldScope != null) {
oldScope.getChildrenScopes().remove(scope);
if (scope instanceof DeclarationScopeImpl) {
((DeclarationScopeImpl)scope).setParentScope(newScope);
}
}
newScope.addDeclaredScope(scope);
}
} else {
for (JsObject property : where.getProperties().values()) {
changeDeclarationScope(property, newScope, done);
}
}
}
}
/**
* This method is useful, when you need to go through the model and be sure that
* the algorithm will not run into endless cycle. In the model there can be references
* to an object that can caused endless cycle. This method check whether the object has an reference or
* the original of the reference object was already processed. Also adds the fully qualified names of the object
* and the references to the list of processed objects.
* @param object object that should be processed
* @param processedObjects list of already processed object
* @return true if the object full qualified name or his reference full qualified name is in the processedObjects list.
*/
public static boolean wasProcessed (JsObject object, Set<String> processedObjects) {
if (processedObjects.contains(object.getFullyQualifiedName())) {
return true;
} else if (object instanceof JsReference) {
JsObject original = ((JsReference) object).getOriginal();
boolean isOrginalReachable = !original.isAnonymous() && !original.getName().equals(object.getName());
JsObject origParent = original.getParent();
while (origParent != null && isOrginalReachable) {
if (origParent.isAnonymous() && !(origParent.getParent() != null && origParent.getParent().getParent() == null)) {
isOrginalReachable = false;
}
origParent = origParent.getParent();
}
if (isOrginalReachable) {
processedObjects.add(object.getFullyQualifiedName());
return true;
}
if (processedObjects.contains(original.getFullyQualifiedName())) {
return true;
} else {
processedObjects.add(object.getFullyQualifiedName());
processedObjects.add(original.getFullyQualifiedName());
}
} else {
if (object.getJSKind() != JsElement.Kind.FILE) {
processedObjects.add(object.getFullyQualifiedName());
}
}
return false;
}
public static String getFQNFromType(Type type) {
String fqn = type.getType();
if (fqn.startsWith(SemiTypeResolverVisitor.ST_EXP)) {
fqn = fqn.substring(SemiTypeResolverVisitor.ST_EXP.length());
} else if (fqn.startsWith(SemiTypeResolverVisitor.ST_PRO)) {
fqn = fqn.substring(SemiTypeResolverVisitor.ST_PRO.length());
}
if (fqn.contains(SemiTypeResolverVisitor.ST_PRO)) {
fqn = fqn.replace(SemiTypeResolverVisitor.ST_PRO, "."); //NOI18N
}
return fqn;
}
}