/******************************************************************************* * Copyright (c) 2010 xored software, Inc. * * 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: * xored software, Inc. - initial API and Implementation (Alex Panchenko) *******************************************************************************/ package org.eclipse.dltk.javascript.internal.core.codeassist; import static org.eclipse.dltk.javascript.ast.Keywords.THIS; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.Platform; import org.eclipse.dltk.annotations.Internal; import org.eclipse.dltk.ast.ASTNode; import org.eclipse.dltk.codeassist.ScriptCompletionEngine; import org.eclipse.dltk.compiler.CharOperation; import org.eclipse.dltk.compiler.env.IModuleSource; import org.eclipse.dltk.compiler.problem.IValidationStatus; import org.eclipse.dltk.compiler.problem.ValidationStatus; import org.eclipse.dltk.core.CompletionContext; import org.eclipse.dltk.core.CompletionProposal; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.IAccessRule; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.internal.javascript.ti.FunctionMethod; import org.eclipse.dltk.internal.javascript.ti.IReferenceAttributes; import org.eclipse.dltk.internal.javascript.ti.ITypeInferenceContext; import org.eclipse.dltk.internal.javascript.ti.PositionReachedException; import org.eclipse.dltk.internal.javascript.ti.TypeInferencer2; import org.eclipse.dltk.internal.javascript.typeinference.CompletionPath; import org.eclipse.dltk.internal.javascript.validation.MemberValidationEvent; import org.eclipse.dltk.javascript.ast.Identifier; import org.eclipse.dltk.javascript.ast.Script; import org.eclipse.dltk.javascript.ast.StringLiteral; import org.eclipse.dltk.javascript.core.JavaScriptKeywords; import org.eclipse.dltk.javascript.core.JavaScriptPlugin; import org.eclipse.dltk.javascript.core.NodeFinder; import org.eclipse.dltk.javascript.core.Types; import org.eclipse.dltk.javascript.internal.core.codeassist.JavaScriptCompletionUtil.ExpressionContext; import org.eclipse.dltk.javascript.internal.core.codeassist.JavaScriptCompletionUtil.ExpressionType; import org.eclipse.dltk.javascript.parser.JavaScriptParserUtil; import org.eclipse.dltk.javascript.typeinference.IValueCollection; import org.eclipse.dltk.javascript.typeinference.IValueParent; import org.eclipse.dltk.javascript.typeinference.IValueReference; import org.eclipse.dltk.javascript.typeinference.ReferenceKind; import org.eclipse.dltk.javascript.typeinfo.IRClassType; import org.eclipse.dltk.javascript.typeinfo.IRElement; import org.eclipse.dltk.javascript.typeinfo.IRFunctionType; import org.eclipse.dltk.javascript.typeinfo.IRMember; import org.eclipse.dltk.javascript.typeinfo.IRMethod; import org.eclipse.dltk.javascript.typeinfo.IRParameter; import org.eclipse.dltk.javascript.typeinfo.IRRecordMember; import org.eclipse.dltk.javascript.typeinfo.IRRecordType; import org.eclipse.dltk.javascript.typeinfo.IRSimpleType; import org.eclipse.dltk.javascript.typeinfo.IRType; import org.eclipse.dltk.javascript.typeinfo.IRTypeDeclaration; import org.eclipse.dltk.javascript.typeinfo.ITypeNames; import org.eclipse.dltk.javascript.typeinfo.ITypeSystem; import org.eclipse.dltk.javascript.typeinfo.JSTypeSet; import org.eclipse.dltk.javascript.typeinfo.MemberPredicate; import org.eclipse.dltk.javascript.typeinfo.MemberPredicates; import org.eclipse.dltk.javascript.typeinfo.RTypeMemberQuery; import org.eclipse.dltk.javascript.typeinfo.RTypes; import org.eclipse.dltk.javascript.typeinfo.TypeInfoManager; import org.eclipse.dltk.javascript.typeinfo.TypeMode; import org.eclipse.dltk.javascript.typeinfo.model.Member; import org.eclipse.dltk.javascript.typeinfo.model.ParameterKind; import org.eclipse.dltk.javascript.typeinfo.model.Type; import org.eclipse.dltk.javascript.typeinfo.model.TypeKind; import org.eclipse.dltk.javascript.validation.IValidatorExtension; public class JavaScriptCompletionEngine2 extends ScriptCompletionEngine implements JSCompletionEngine { private int globalOptions = JSCompletionEngine.OPTION_ALL; public int getGlobalOptions() { return globalOptions; } public void setGlobalOptions(int value) { this.globalOptions = value; } public void complete(final IModuleSource cu, final int position, int i) { this.requestor.beginReporting(); final String content = cu.getSourceContents(); if (position < 0 || position > content.length()) { return; } final TypeInferencer2 inferencer2 = new TypeInferencer2(); inferencer2.setModelElement(cu.getModelElement()); final Script script = JavaScriptParserUtil.parse(cu, null); final NodeFinder nodeFinder = new NodeFinder(position, position); nodeFinder.locate(script); if (nodeFinder.getNode() instanceof StringLiteral) { // don't complete inside string literals return; } final PositionCalculator calculator = new PositionCalculator(content, position, false); final CompletionVisitor visitor = new CompletionVisitor(inferencer2, position); inferencer2.setVisitor(visitor); if (cu instanceof org.eclipse.dltk.core.ISourceModule) { inferencer2 .setModelElement((org.eclipse.dltk.core.ISourceModule) cu); } try { inferencer2.doInferencing(script); } catch (PositionReachedException e) { // e.printStackTrace(); } ITypeSystem.CURRENT.runWith(inferencer2, new Runnable() { public void run() { final CompletionPath path = new CompletionPath(calculator .getCompletion()); final ASTNode node = nodeFinder.getNode(); if (node instanceof Identifier) { setSourceRange(node.start(), node.end()); } else { String lastSegment = path.lastSegment(); if (lastSegment == null) lastSegment = ""; setSourceRange(position - lastSegment.length(), position); } final Reporter reporter = new Reporter(inferencer2, path .lastSegment(), position, TypeInfoManager .createExtensions(inferencer2, IValidatorExtension.class, null)); if (calculator.isMember() && !path.isEmpty() && path.lastSegment() != null) { doCompletionOnMember(inferencer2, visitor.getCollection(), path, reporter); } else { doGlobalCompletion(visitor.getCollection(), reporter, JavaScriptCompletionUtil.evaluateExpressionContext( script, content, position)); } } }); this.requestor.endReporting(); } public void completeTypes(ISourceModule module, TypeMode mode, String prefix, int offset) { final TypeInferencer2 inferencer2 = new TypeInferencer2(); inferencer2.setModelElement(module); final Script script = JavaScriptParserUtil.parse(module, null); inferencer2.doInferencing(script); setSourceRange(offset - prefix.length(), offset); doCompletionOnType(mode, new Reporter(inferencer2, prefix, offset, Collections.<IValidatorExtension> emptyList())); } public void completeGlobals(ISourceModule module, final String prefix, final int offset, boolean jsdoc) { final CompletionContext completionContext = new CompletionContext(); completionContext.setOffset(offset); completionContext.setDoc(jsdoc); requestor.acceptContext(completionContext); setSourceRange(offset - prefix.length(), offset); final TypeInferencer2 inferencer2 = new TypeInferencer2(); inferencer2.setModelElement(module); final CompletionVisitor visitor = new CompletionVisitor(inferencer2, Integer.MAX_VALUE); inferencer2.setVisitor(visitor); final Script script = JavaScriptParserUtil.parse(module, null); try { inferencer2.doInferencing(script); } catch (PositionReachedException e) { // e.printStackTrace(); } ITypeSystem.CURRENT.runWith(inferencer2, new Runnable() { public void run() { final Reporter reporter = new Reporter(inferencer2, prefix, offset, TypeInfoManager.createExtensions(inferencer2, IValidatorExtension.class, null)); doGlobalCompletion(visitor.getCollection(), reporter, null); } }); } /** * Generate completion proposals for the matching members from the specified * {@link Iterable}. */ public void completeMembers(ISourceModule module, String prefix, int offset, boolean jsdoc, Iterable<IRMember> memers) { final CompletionContext completionContext = new CompletionContext(); completionContext.setOffset(offset); completionContext.setDoc(jsdoc); requestor.acceptContext(completionContext); setSourceRange(offset - prefix.length(), offset); final TypeInferencer2 inferencer2 = new TypeInferencer2(); inferencer2.setModelElement(module); final Reporter reporter = new Reporter(inferencer2, prefix, offset, TypeInfoManager.createExtensions(inferencer2, IValidatorExtension.class, null)); for (IRMember member : memers) { final String name = member.getName(); if (reporter.matches(name)) { reporter.report(name, member); } } } private void doCompletionOnType(TypeMode mode, Reporter reporter) { final ITypeInferenceContext context = reporter.context; Set<String> typeNames = context.listTypes(mode, reporter.getPrefix()); for (String typeName : typeNames) { final Type type = context.getType(typeName); if (type != null && type.isVisible()) { reporter.reportTypeRef(type); } } } private static boolean exists(IValueParent item) { if (item instanceof IValueReference) { return ((IValueReference) item).exists(); } else { return true; } } /** * @param context * @param collection * @param startPart */ @Internal void doCompletionOnMember(ITypeInferenceContext context, IValueCollection collection, CompletionPath path, Reporter reporter) { IValueParent item = collection; for (int i = 0; i < path.segmentCount() - 1; ++i) { if (path.isName(i)) { final String segment = path.segment(i); if (THIS.equals(segment) && item instanceof IValueCollection) { item = ((IValueCollection) item).getThis(); } else { item = item.getChild(segment); } if (!exists(item)) break; } else if (path.isFunction(i)) { item = item.getChild(IValueReference.FUNCTION_OP); if (!exists(item)) break; } else if (path.isObject(i)) { item = item.getChild(ITypeNames.OBJECT).getChild( IValueReference.FUNCTION_OP); break; } else { assert path.isArray(i); item = item.getChild(IValueReference.ARRAY_OP); if (!exists(item)) break; } } if (item != null && exists(item)) { reportItems(reporter, item); } } protected void reportItems(Reporter reporter, IValueParent item) { reporter.report(item); if (item instanceof IValueCollection) { IValueCollection coll = (IValueCollection) item; for (;;) { coll = coll.getParent(); if (coll == null) break; reporter.report(coll); } } else if (item instanceof IValueReference) { reporter.reportValueTypeMembers((IValueReference) item); } } protected void reportGlobals(Reporter reporter) { final ITypeInferenceContext context = reporter.context; final Set<String> globals = context.listGlobals(reporter.getPrefix()); for (String global : globals) { if (reporter.canReport(global)) { IRMember element = context.resolve(global); if (element != null && element.isVisible()) { reporter.report(global, element); } } else if (global.lastIndexOf('.') != -1) { Type type = context.getType(global); if (type != null && type.isVisible() && type.getKind() != TypeKind.UNKNOWN) { reporter.reportTypeRef(type); } } } } private class Reporter { final ITypeInferenceContext context; final char[] prefix; private final String prefixStr; final int position; final Set<String> processed = new HashSet<String>(); final boolean camelCase = DLTKCore.ENABLED.equals(DLTKCore .getOption(DLTKCore.CODEASSIST_CAMEL_CASE_MATCH)); final boolean visibilityCheck = DLTKCore.ENABLED.equals(Platform .getPreferencesService().getString(JavaScriptPlugin.PLUGIN_ID, DLTKCore.CODEASSIST_VISIBILITY_CHECK, null, null)); final IValidatorExtension[] extensions; public Reporter(ITypeInferenceContext context, String prefix, int position, List<IValidatorExtension> extensions) { this.context = context; this.prefixStr = prefix != null ? prefix : ""; this.prefix = prefixStr.toCharArray(); this.position = position; if (!extensions.isEmpty()) { this.extensions = extensions .toArray(new IValidatorExtension[extensions.size()]); } else { this.extensions = null; } } @SuppressWarnings("unused") public void ignore(String generatedIdentifier) { processed.add(generatedIdentifier); } public String getPrefix() { return prefixStr; } public int getPosition() { return position; } public void report(String name, IRElement element) { if (element instanceof IRMember && processed.add(name)) { reportMember((IRMember) element, name, false); } } public boolean canReport(String name) { return matches(name) && !processed.contains(name); } boolean matches(String name) { return CharOperation.prefixEquals(prefix, name, false) || camelCase && CharOperation.camelCaseMatch(prefix, name.toCharArray()); } private MemberValidationEvent memberValidationEvent; public void report(IValueParent item) { final Set<String> deleted = item.getDeletedChildren(); CHILDREN: for (String childName : item.getDirectChildren()) { if (childName.equals(IValueReference.FUNCTION_OP)) continue; if (!deleted.contains(childName) && matches(childName) && processed.add(childName)) { IValueReference child = item.getChild(childName); if (child.exists()) { if (visibilityCheck && extensions != null) { if (memberValidationEvent == null) { memberValidationEvent = new MemberValidationEvent(); } memberValidationEvent.set(child, null); for (IValidatorExtension extension : extensions) { final IValidationStatus status = extension .validateAccessibility(memberValidationEvent); if (status != null) { if (status == ValidationStatus.OK) { break; } else { continue CHILDREN; } } } } reportReference(child); } } } } public void reportValueTypeMembers(IValueReference valueRef) { final RTypeMemberQuery typeQuery = new RTypeMemberQuery(); final Set<IRMember> members = new HashSet<IRMember>(); collectTypes(valueRef.getDeclaredTypes(), typeQuery, members, valueRef); collectTypes(valueRef.getTypes(), typeQuery, members, valueRef); for (IRMember member : members) { if (member.isVisible() && matches(member.getName())) { reportMember(member, member.getName(), true); } } for (IRMember member : typeQuery.ignoreDuplicates(processed)) { if (member.isVisible() && matches(member.getName())) { reportMember(member, member.getName(), typeQuery.contains(member.getDeclaringType())); } } } protected void collectTypes(final JSTypeSet types, final RTypeMemberQuery typeQuery, final Collection<IRMember> members, IValueReference valueRef) { for (IRType type : types) { if (type instanceof IRClassType) { final IRTypeDeclaration t = ((IRClassType) type) .getDeclaration(); if (t != null) { final MemberPredicate predicate = t.getSource() .memberPredicateFor(type, MemberPredicates.STATIC); typeQuery.add(t, predicate); if (t.getSource().hasPrototype() && predicate .isCompatibleWith(MemberPredicates.STATIC)) { Type prototypeType = t.getSource() .getPrototypeType(); if (prototypeType == Types.FUNCTION && !canInstantiate(t.getSource(), valueRef)) { prototypeType = Types.OBJECT; } typeQuery.add(context.convert(prototypeType), MemberPredicates.NON_STATIC); } } else { typeQuery.add(RTypes.FUNCTION.getDeclaration(), MemberPredicates.NON_STATIC); } } else if (type instanceof IRSimpleType) { final IRTypeDeclaration t = ((IRSimpleType) type) .getDeclaration(); typeQuery.add( t, t.getSource().memberPredicateFor(type, MemberPredicates.NON_STATIC)); } else if (type instanceof IRRecordType) { members.addAll(((IRRecordType) type).getMembers()); typeQuery.add(RTypes.OBJECT.getDeclaration(), MemberPredicates.NON_STATIC); } else if (type instanceof IRFunctionType) { final IRFunctionType functionType = (IRFunctionType) type; members.add(FunctionMethod.apply.create(functionType)); members.add(FunctionMethod.call.create(functionType)); typeQuery.add(RTypes.FUNCTION.getDeclaration(), new MemberPredicate() { public boolean isCompatibleWith( MemberPredicate predicate) { return MemberPredicates.NON_STATIC == predicate; } public boolean evaluate(Member member) { return MemberPredicates.NON_STATIC .evaluate(member) && !FunctionMethod.apply .test(member.getName()) && !FunctionMethod.call.test(member .getName()); } public boolean evaluate(IRMember member) { return member.getSource() instanceof Member && evaluate((Member) member .getSource()); } }); } else if (type.isJavaScriptObject()) { typeQuery.add(RTypes.OBJECT.getDeclaration(), MemberPredicates.NON_STATIC); } } } private boolean canInstantiate(Type type, IValueReference ref) { if (extensions != null) { for (IValidatorExtension extension : extensions) { final IValidationStatus status = extension.canInstantiate( type, ref); if (status != null && status != ValidationStatus.OK) { return false; } } } return true; } /** * @param member */ private void reportMember(IRMember member, String memberName, boolean important) { if (visibilityCheck && extensions != null && member.getSource() instanceof Member) { final Member source = (Member) member.getSource(); for (IValidatorExtension extension : extensions) { final IValidationStatus status = extension .validateAccessibility(source); if (status != null) { if (status == ValidationStatus.OK) { break; } else { return; } } } } boolean isFunction = member instanceof IRMethod || (member instanceof IRRecordMember && member.getType() instanceof IRFunctionType); CompletionProposal proposal = CompletionProposal.create( isFunction ? CompletionProposal.METHOD_REF : CompletionProposal.FIELD_REF, position); int relevance = computeBaseRelevance(); // if (important) { // relevance += RelevanceConstants.R_NON_INHERITED; // } relevance += computeRelevanceForInterestingProposal(); relevance += computeRelevanceForCaseMatching(prefix, memberName); relevance += computeRelevanceForRestrictions(IAccessRule.K_ACCESSIBLE); proposal.setRelevance(relevance); proposal.setCompletion(isFunction ? memberName + "()" : memberName); proposal.setName(memberName); proposal.setExtraInfo(member.getSource()); if (isFunction) { List<IRParameter> parameters = null; if (member.getType() instanceof IRFunctionType) { parameters = ((IRFunctionType) member.getType()) .getParameters(); } else { parameters = ((IRMethod) member).getParameters(); } int paramCount = parameters.size(); if (paramCount > 0) { final String[] params = new String[paramCount]; for (int i = 0; i < paramCount; ++i) { params[i] = parameters.get(i).getName(); if (params[i] == null && parameters.get(i).getType() != null) params[i] = parameters.get(i).getType().getName(); if (params[i] == null) params[i] = "anon_" + i; } proposal.setParameterNames(params); if (parameters.get(paramCount - 1).getKind() != ParameterKind.NORMAL) { int requiredCount = parameters.size(); while (requiredCount > 0 && parameters.get(requiredCount - 1).getKind() != ParameterKind.NORMAL) { --requiredCount; } if (requiredCount == 0 && parameters.get(requiredCount).getKind() == ParameterKind.VARARGS) { ++requiredCount; // heuristic... } if (requiredCount != paramCount) { proposal.setAttribute( CompletionProposal.ATTR_REQUIRED_PARAM_COUNT, requiredCount); } } } } accept(proposal); } /** * @param reference */ private void reportReference(IValueReference reference) { Object element = reference .getAttribute(IReferenceAttributes.ELEMENT); if (element instanceof IRMember) { reportMember((IRMember) element, ((IRMember) element).getName(), true); } else { int proposalKind = CompletionProposal.FIELD_REF; final ReferenceKind kind = reference.getKind(); if (reference.getAttribute(IReferenceAttributes.PHANTOM, true) == null && (kind == ReferenceKind.FUNCTION || reference .hasChild(IValueReference.FUNCTION_OP))) { proposalKind = CompletionProposal.METHOD_REF; } else if (kind == ReferenceKind.LOCAL) { proposalKind = CompletionProposal.LOCAL_VARIABLE_REF; } CompletionProposal proposal = CompletionProposal.create( proposalKind, position); int relevance = computeBaseRelevance(); relevance += computeRelevanceForInterestingProposal(); relevance += computeRelevanceForCaseMatching(prefix, reference.getName()); relevance += computeRelevanceForRestrictions(IAccessRule.K_ACCESSIBLE); proposal.setRelevance(relevance); proposal.setCompletion(proposalKind == CompletionProposal.METHOD_REF ? reference .getName() + "()" : reference.getName()); proposal.setName(reference.getName()); proposal.setExtraInfo(reference); if (proposalKind == CompletionProposal.METHOD_REF) { final IRMethod method = (IRMethod) reference.getAttribute( IReferenceAttributes.R_METHOD, true); if (method != null) { int paramCount = method.getParameterCount(); if (paramCount > 0) { final String[] params = new String[paramCount]; for (int i = 0; i < paramCount; ++i) { params[i] = method.getParameters().get(i) .getName(); } proposal.setParameterNames(params); if (method.getParameters().get(paramCount - 1) .getKind() != ParameterKind.NORMAL) { int requiredCount = method.getParameters() .size(); while (requiredCount > 0 && method.getParameters() .get(requiredCount - 1) .getKind() != ParameterKind.NORMAL) { --requiredCount; } if (requiredCount == 0 && method.getParameters() .get(requiredCount).getKind() == ParameterKind.VARARGS) { ++requiredCount; // heuristic... } if (requiredCount != paramCount) { proposal.setAttribute( CompletionProposal.ATTR_REQUIRED_PARAM_COUNT, requiredCount); } } } } } accept(proposal); } } public void reportTypeRef(Type type) { if (!processed.add(type.getName())) { return; } CompletionProposal proposal = CompletionProposal.create( CompletionProposal.TYPE_REF, position); int relevance = computeBaseRelevance(); relevance += computeRelevanceForInterestingProposal(); relevance += computeRelevanceForCaseMatching(prefix, type.getName()); relevance += computeRelevanceForRestrictions(IAccessRule.K_ACCESSIBLE); proposal.setRelevance(relevance); proposal.setCompletion(type.getName()); proposal.setName(type.getName()); proposal.setExtraInfo(type); accept(proposal); } } /** * @param context * @param collection * @param reporter * @param expressionContext */ @Internal void doGlobalCompletion(IValueCollection collection, Reporter reporter, ExpressionContext expressionContext) { if (expressionContext != null && expressionContext.expressionType == ExpressionType.OBJECT_INITIALIZER) { return; } reportItems(reporter, collection); if ((globalOptions & OPTION_GLOBALS) != 0) { if (!requestor.isIgnored(CompletionProposal.TYPE_REF)) { doCompletionOnType(TypeMode.CODE, reporter); } reportGlobals(reporter); } if ((globalOptions & OPTION_KEYWORDS) != 0) { if (!requestor.isIgnored(CompletionProposal.KEYWORD)) { doCompletionOnKeyword(reporter.getPrefix(), reporter.getPosition(), expressionContext); } } } private void doCompletionOnKeyword(String prefix, int position, ExpressionContext expressionContext) { final String[] keywords; if (expressionContext != null) { if (expressionContext.expressionType == ExpressionType.PROPERTY_INITIALIZER_VALUE) { keywords = JavaScriptKeywords.getJavaScriptValueKeywords(); } else { keywords = JavaScriptKeywords.getJavaScriptExpressionKeywords(); } } else { keywords = JavaScriptKeywords.getJavaScriptKeywords(); } findKeywords(prefix.toCharArray(), keywords, true); } }