blob: 57e0d0ea7a91ba7779c7e7f8a73b0be4dbc0f6e4 [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;
import org.netbeans.modules.javascript2.model.api.ModelUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.model.api.JsElement;
import org.netbeans.modules.javascript2.model.api.JsFunction;
import org.netbeans.modules.javascript2.model.api.JsObject;
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.openide.filesystems.FileObject;
/**
*
* @author Petr Pisl
*/
public class JsFunctionImpl extends DeclarationScopeImpl implements JsFunction {
final private HashMap <String, JsObject> parametersByName;
final private List<JsObject> parameters;
final private Set<TypeUsage> returnTypes;
private boolean isStrict;
public JsFunctionImpl(DeclarationScope scope, JsObject parentObject, Identifier name,
List<Identifier> parameters, OffsetRange offsetRange, String mimeType, String sourceLabel) {
super(scope, parentObject, name, offsetRange, mimeType, sourceLabel);
this.parametersByName = new HashMap<String, JsObject>(parameters.size());
this.parameters = new ArrayList<JsObject>(parameters.size());
for (Identifier identifier : parameters) {
JsObject parameter = new ParameterObject(this, identifier, mimeType, sourceLabel);
addParameter(parameter);
}
setAnonymous(false);
this.returnTypes = new HashSet<TypeUsage>();
setDeclared(true);
if (parentObject != null) {
// creating arguments variable
JsObjectImpl arguments = new JsObjectImpl(this,
new Identifier(ModelUtils.ARGUMENTS, new OffsetRange(name.getOffsetRange().getStart(), name.getOffsetRange().getStart())),
name.getOffsetRange(), false, EnumSet.of(Modifier.PRIVATE), mimeType, sourceLabel);
arguments.addAssignment(new TypeUsage("Arguments", getOffset(), true), getOffset()); // NOI18N
this.addProperty(arguments.getName(), arguments);
}
}
protected JsFunctionImpl(FileObject file, JsObject parentObject, Identifier name,
List<Identifier> parameters, String mimeType, String sourceLabel) {
this(null, parentObject, name, parameters, name.getOffsetRange(), mimeType, sourceLabel);
this.setFileObject(file);
this.setDeclared(false);
}
private JsFunctionImpl(FileObject file, Identifier name, String mimeType, String sourceLabel) {
this(null, null, name, Collections.emptyList(), name.getOffsetRange(), mimeType, sourceLabel);
this.setFileObject(file);
}
public static JsFunctionImpl createGlobal(FileObject fileObject, int length, String mimeType) {
String name = fileObject != null ? fileObject.getName() : "VirtualSource"; //NOI18N
Identifier ident = new Identifier(name, new OffsetRange(0, length));
return new JsFunctionImpl(fileObject, ident, mimeType, null);
}
@Override
public final Collection<? extends JsObject> getParameters() {
return this.parameters;
}
public final void addParameter(JsObject object) {
assert object.getParent() == this;
this.parametersByName.put(object.getName(), object);
this.parameters.add(object);
}
@Override
public Kind getJSKind() {
if (kind != null) {
return kind;
}
if (getParent() == null) {
// global function
return JsElement.Kind.FILE;
}
String name = getName();
if (name != null && name.startsWith("get ")) { //NOI18N
return JsElement.Kind.PROPERTY_GETTER;
}
if (name != null && name.startsWith("set ")) { //NOI18N
return JsElement.Kind.PROPERTY_SETTER;
}
if (getParent() != null /*&& getParent() instanceof JsFunction*/) {
JsObject prototype = null;
for (JsObject property : getProperties().values()) {
if (property.isDeclared()
&& (property.getModifiers().contains(Modifier.PROTECTED)
|| (property.getModifiers().contains(Modifier.PUBLIC) && !property.getModifiers().contains(Modifier.STATIC)))
&& !isAnonymous() && !property.isAnonymous()
&& (property.getDeclarationName() != null && property.getDeclarationName().getOffsetRange().getStart() < property.getDeclarationName().getOffsetRange().getEnd())) {
if(!ModelUtils.PROTOTYPE.equals(getParent().getName())) {
return JsElement.Kind.CONSTRUCTOR;
}
}
if (ModelUtils.PROTOTYPE.equals(property.getName())) {
prototype = property;
}
}
if (prototype != null /*&& !prototype.getProperties().isEmpty()*/) {
return JsElement.Kind.CONSTRUCTOR;
}
}
// if (getParent() != null && !getParent().isDeclared()) {
//
// }
JsElement.Kind result = JsElement.Kind.FUNCTION;
if (!(getParent() instanceof JsObjectReference) && getParent().getJSKind() != JsElement.Kind.FILE) {
result = JsElement.Kind.METHOD;
}
return result;
}
@Override
public JsObject getParameter(String name) {
JsObject result = parametersByName.get(name);
return result;
}
private boolean areReturnTypesResolved = false;
@Override
public Collection<? extends TypeUsage> getReturnTypes() {
if (areReturnTypesResolved) {
return Collections.emptyList();
}
Collection<TypeUsage> returns = new HashSet<>();
HashSet<String> nameReturnTypes = new HashSet<>();
areReturnTypesResolved = true;
for(TypeUsage type : returnTypes) {
if (type.isResolved()) {
if (!nameReturnTypes.contains(type.getType())){
returns.add(type);
nameReturnTypes.add(type.getType());
}
} else {
if (type.getType().startsWith("@")) {
String typeName = type.getType();
if (!(typeName.endsWith(getName()) && typeName.startsWith("@call"))) {
Collection<TypeUsage> resolved = ModelUtils.resolveTypeFromSemiType(this, type);
for (TypeUsage typeResolved : resolved) {
if (!nameReturnTypes.contains(type.getType())) {
returns.add(typeResolved);
nameReturnTypes.add(typeResolved.getType());
}
}
}
} else {
JsObject jsObject = ModelUtils.getJsObjectByName(this,type.getType());
if (jsObject == null) {
// try to find according the fqn
JsObject global = ModelUtils.getGlobalObject(this);
jsObject = ModelUtils.findJsObjectByName(global, type.getType());
}
if(jsObject != null) {
Collection<TypeUsage> resolveAssignments = resolveAssignments(jsObject, type.getOffset());
for (TypeUsage typeResolved: resolveAssignments) {
if (!nameReturnTypes.contains(typeResolved.getType())){
returns.add(typeResolved);
nameReturnTypes.add(typeResolved.getType());
}
}
} else {
returns.add(type);
nameReturnTypes.add(type.getType());
}
}
}
}
areReturnTypesResolved = false;
return returns;
}
@Override
public void addReturnType(TypeUsage type) {
boolean isThere = false;
for (TypeUsage typeUsage : this.returnTypes) {
if (type.getType().equals(typeUsage.getType())) {
isThere = true;
}
}
if (!isThere){
this.returnTypes.add(type);
}
}
public void addReturnType(Collection<TypeUsage> types) {
for (TypeUsage typeUsage : types) {
addReturnType(typeUsage);
}
}
public boolean areReturnTypesEmpty() {
return returnTypes.isEmpty();
}
@Override
public boolean moveProperty(String name, JsObject newParent) {
JsObject property = getProperty(name);
if (property != null && (newParent instanceof DeclarationScope)) {
ModelUtils.changeDeclarationScope(property, (DeclarationScope)newParent);
}
return super.moveProperty(name, newParent);
}
@Override
public void resolveTypes(JsDocumentationHolder docHolder) {
super.resolveTypes(docHolder);
if (!(returnTypes.size() == 1 && Type.UNDEFINED.equals(returnTypes.iterator().next().getType()))) {
HashSet<String> nameReturnTypes = new HashSet<String>();
Collection<TypeUsage> resolved = new ArrayList<>();
for (TypeUsage type : returnTypes) {
if (!(type.getType().equals(Type.UNRESOLVED) && returnTypes.size() > 1)) {
if (!type.isResolved()) {
for (TypeUsage rType : ModelUtils.resolveTypeFromSemiType(this, type)) {
if (!nameReturnTypes.contains(rType.getType())) {
if ("@this;".equals(type.getType())) { // NOI18N
rType = new TypeUsage(rType.getType(), -1, rType.isResolved());
}
resolved.add(rType);
nameReturnTypes.add(rType.getType());
}
}
// resolved.addAll(ModelUtils.resolveTypeFromSemiType(this, type));
} else {
if (!nameReturnTypes.contains(type.getType())) {
resolved.add(type);
nameReturnTypes.add(type.getType());
}
}
}
}
for (TypeUsage type : resolved) {
if (type.getOffset() > 0) {
String typeName = type.getType();
JsObject jsObject = null;
// at first check whether is not a parameter
if (typeName.indexOf('.') == -1) {
JsObject parameter = null;
DeclarationScope scope = this;
while (scope != null && parameter == null && jsObject == null) {
if (scope instanceof JsFunction) {
parameter = ((JsFunction) scope).getParameter(typeName);
}
jsObject = ((JsObject) scope).getProperty(typeName);
scope = scope.getParentScope();
}
if (jsObject == null && parameter != null) {
jsObject = parameter;
}
if (jsObject != null) {
jsObject.addOccurrence(new OffsetRange(type.getOffset(), type.getOffset() + typeName.length()));
}
}
if (jsObject == null) {
jsObject = ModelUtils.findJsObjectByName(this, typeName);
if (jsObject == null) {
JsObject global = ModelUtils.getGlobalObject(this);
jsObject = ModelUtils.findJsObjectByName(global, typeName);
}
if (jsObject != null && containsOffset(type.getOffset()) && !getJSKind().equals(JsElement.Kind.FILE)) {
int index = typeName.lastIndexOf('.');
int typeLength = (index > -1) ? typeName.length() - index - 1 : typeName.length();
((JsObjectImpl)jsObject).addOccurrence(new OffsetRange(type.getOffset(), jsObject.isAnonymous() ? type.getOffset() : type.getOffset() + typeLength));
}
}
}
}
returnTypes.clear();
returnTypes.addAll(resolved);
} else if (getJSKind() == JsElement.Kind.CONSTRUCTOR) {
Collection<TypeUsage> resolved = ModelUtils.resolveTypeFromSemiType(this, returnTypes.iterator().next());
returnTypes.clear();
returnTypes.addAll(resolved);
} else if (returnTypes.size() == 1) {
TypeUsage type = returnTypes.iterator().next();
if (Type.UNDEFINED.equals(type.getType()) && !type.isResolved()) {
returnTypes.clear();
returnTypes.add(new TypeUsage(type.getType(), type.getOffset(), true));
}
}
// parameters and type type resolving for occurrences
JsObject global = ModelUtils.getGlobalObject(this);
for(JsObject param : parameters) {
Collection<? extends TypeUsage> types = param.getAssignmentForOffset(param.getDeclarationName().getOffsetRange().getStart());
for(TypeUsage type: types) {
JsObject jsObject = ModelUtils.findJsObjectByName(global, type.getType());//getJsObjectByName(this, type.getType());
if (jsObject != null) {
ModelUtils.addDocTypesOccurence(jsObject, docHolder);
moveOccurrenceOfProperties((JsObjectImpl)jsObject, param);
if (type.getType().indexOf('.') > -1) {
// mark occurrences also for the parent if the type is like Contex.Object
String[] typeParts = type.getType().split("\\.");
JsObject parent = jsObject.getParent();
for (int i = (typeParts.length - 2); i > -1 && parent != null; i--) {
if (parent.getName().equals(typeParts[i])) {
ModelUtils.addDocTypesOccurence(parent, docHolder);
}
parent = parent.getParent();
}
}
}
}
List<JsObject> paramProperties = new ArrayList<>(param.getProperties().values());
for(JsObject paramProperty: paramProperties) {
((JsObjectImpl)paramProperty).resolveTypes(docHolder);
}
}
}
// @Override
// public String toString() {
// return "JsFunctionImpl{" + "declarationName=" + getDeclarationName() + ", parent=" + getParent() + ", kind=" + kind + ", parameters=" + parameters + ", returnTypes=" + returnTypes + '}';
// }
@Override
protected void correctTypes(String fromType, String toType) {
super.correctTypes(fromType, toType);
String typeR;
String typeFQN;
Set<TypeUsage> copy = new HashSet<TypeUsage>(returnTypes);
for (TypeUsage type : copy) {
typeFQN = type.getType();
typeR = replaceTypeInFQN(typeFQN, fromType, toType);
if (typeR != null) {
returnTypes.remove(type);
returnTypes.add(new TypeUsage(typeR, type.getOffset(), type.isResolved()));
}
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getDeclarationName().getName()).append("()");
return sb.toString();
}
public boolean isStrict() {
return isStrict;
}
public void setStrict(boolean isStrict) {
this.isStrict = isStrict;
}
}