/** * Copyright (c) 2013-2016 Angelo ZERR. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation */ package tern.server.protocol.completions; import java.util.ArrayList; import java.util.List; import tern.ITernProject; import tern.server.ITernModule; import tern.utils.StringUtils; /** * Tern completion item. * */ public class TernCompletionItem { private final TernCompletionProposalRec proposal; private final String displayName; private final String signature; private final boolean function; private boolean array; private String jsType; private List<Parameter> parameters; private String[] allTypes; private boolean hasDisplayName; private ITernProject ternProject; public TernCompletionItem(TernCompletionProposalRec proposal) { this.proposal = proposal; // we consider that we are inside string when display name is defined. this.hasDisplayName = !StringUtils.isEmpty(proposal.displayName); this.displayName = hasDisplayName ? proposal.displayName : proposal.name; this.parameters = null; String signature = proposal.name; this.jsType = proposal.type; if (!hasDisplayName && !StringUtils.isEmpty(proposal.type)) { this.function = TernTypeHelper.isFunction(proposal.type); if (function) { FunctionInfo functionInfo = TernTypeHelper.parseFunction( proposal.name, proposal.type); this.parameters = functionInfo.getParameters(); signature = functionInfo.getSignature(); this.jsType = functionInfo.getReturnType(); } else { this.array = proposal.type.indexOf("[") != -1; } } else { this.function = false; this.array = false; } this.signature = signature; } /** * Expand an array of expanded functions if this function contains optional * parameters. * * <p> * The expansion of "fn(selector: string, context?: frameElement)" returns * an array of functions : * * <ul> * <li>fn(selector: string) -> jQuery.fn</li> * </ul> * * </p> * * @return */ public String[] expand() { if (allTypes == null) { // not computed, compute it. if (parameters == null) { // no parameters. allTypes = StringUtils.EMPTY_ARRAY; } else { // have parameters, retrieve optional parameters. List<Parameter> optionalParameters = null; for (int i = 0; i < parameters.size(); i++) { Parameter parameter = parameters.get(i); if (!parameter.isRequired()) { if (optionalParameters == null) { optionalParameters = new ArrayList<Parameter>(); } optionalParameters.add(parameter); } } if (optionalParameters == null) { // no optional parameters allTypes = StringUtils.EMPTY_ARRAY; } else { // optional parameters, expand it. List<String> types = new ArrayList<String>(); // Loop for each number of optional parameters (0 to // optional parameters size). for (int nbMaxOptionalParams = 0; nbMaxOptionalParams < optionalParameters .size(); nbMaxOptionalParams++) { // loop for each optional parameters. for (Parameter optional : optionalParameters) { addType(types, nbMaxOptionalParams, optional, null); } } allTypes = types.toArray(StringUtils.EMPTY_ARRAY); } } } return allTypes; } /** * Add function type to the given list types. * * @param types * list of function type. * @param nbMaxOptional * nb max of optional parameters. * @param optional * the current optional parameter. * @param index * not null if it must starts with the optional parameter which * matches this index. */ public void addType(List<String> types, int nbMaxOptional, Parameter optional, Integer index) { Integer newIndex = null; int nbOptionalAdded = -1; StringBuilder newType = new StringBuilder("fn("); // Loop for each parameters. for (int i = 0; i < parameters.size(); i++) { Parameter parameter = parameters.get(i); if (parameter.isRequired()) { // required parameter, add it. addParam(newType, parameter); } else { // optional parameter if (nbOptionalAdded == -1) { // none optional parameter was added, check if the current // optional parameter is the given optional parameter. if (optional.equals(parameter)) { nbOptionalAdded = 0; } } if (nbOptionalAdded != -1) { // the given optional was or must be added. if (nbOptionalAdded < nbMaxOptional) { // the current number of optional which was added is < // to the nb max of optional parameters which can be // added. boolean add = false; if (nbOptionalAdded == 0) { // given optional parameter, add it add = true; } else { if (index == null) { // the start index is null, add it add = true; } else if (i == index) { // the start index matches the current parameter // index, add it add = true; } } // Update newIndex with the current index + 1 if : // * if newIndex was not already set // * previous optional param which was added is the // given optional param // * the next index param is a optional param if (newIndex == null && nbOptionalAdded == 1) { newIndex = getNexOptionalIndex(parameters, i, index); } if (add) { // optional parameter, add it. addParam(newType, parameter); nbOptionalAdded++; } } } } } newType.append(")"); if (jsType != null) { newType.append(" -> "); newType.append(jsType); } if (!types.contains(newType.toString())) { // type must be added if it doesn't exists. types.add(newType.toString()); } if (newIndex != null) { // next optional parameter must be treat, do it. addType(types, nbMaxOptional, optional, newIndex); } } /** * Returns the next optional parameter index. * * @param parameters * @param i * @param index * @return */ private Integer getNexOptionalIndex(List<Parameter> parameters, int i, Integer index) { Parameter parameter = null; for (int j = i + 1; j < parameters.size(); j++) { parameter = parameters.get(j); if (!parameter.isRequired() && index == null || (index != null && index < j)) { return j; } } return null; } public void addParam(StringBuilder newType, Parameter parameter) { if (newType.length() > 3) { newType.append(", "); } newType.append(parameter.getName()); if (!parameter.isRequired()) { newType.append("?"); } if (parameter.getType() != null) { newType.append(": "); newType.append(parameter.getType()); } } public String getText() { if (StringUtils.isEmpty(proposal.origin) && StringUtils.isEmpty(jsType)) { return hasDisplayName ? displayName : signature; } StringBuilder text = new StringBuilder(hasDisplayName ? displayName : signature); if (!StringUtils.isEmpty(jsType)) { text.append(" : "); text.append(jsType); } if (!StringUtils.isEmpty(proposal.origin)) { text.append(" - "); text.append(proposal.origin); } return text.toString(); } public List<Parameter> getParameters() { return parameters; } public boolean isFunction() { return function; } public boolean isArray() { return array; } public String getName() { return proposal.name; } public String getDoc() { return proposal.doc; } public String getURL() { return proposal.url; } public String getOrigin() { return proposal.origin; } public String getSignature() { return signature; } public String getType() { return proposal.type; } public TernCompletionProposalRec getProposal() { return proposal; } public String getJsType() { return jsType; } public boolean isProperty() { return proposal.isProperty; } public boolean isObjectKey() { return proposal.isObjectKey; } public boolean isSpecifier() { return proposal.isSpecifier; } public boolean hasDisplayName() { return hasDisplayName; } public String getDisplayName() { return displayName; } public boolean isStringType() { return "string".equals(getType()); } public boolean isStringReturnType() { return "string".equals(getJsType()); } public ITernProject getTernProject() { return ternProject; } public void setTernProject(ITernProject ternProject) { this.ternProject = ternProject; } /** * Returns the origin type. * * @return the origin type. */ // public String getOriginType() { // ITernModule module = getModule(); // // Use tern repository to retrieve the real module type (ex : yui for // // yui3). // return module != null ? module.getType() : null; // } /** * Returns the tern module and null otherwise. * * @return the tern module and null otherwise. */ public ITernModule getTernModule() { String origin = getOrigin(); if (origin == null) { return null; } if (ternProject == null) { return null; } return this.ternProject.getRepository().getModuleByOrigin(origin); } }