| /******************************************************************************* |
| * 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 java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.dltk.annotations.Nullable; |
| import org.eclipse.dltk.ast.ASTNode; |
| import org.eclipse.dltk.ast.utils.ASTUtil; |
| import org.eclipse.dltk.codeassist.ScriptSelectionEngine; |
| import org.eclipse.dltk.compiler.env.IModuleSource; |
| import org.eclipse.dltk.core.DLTKCore; |
| import org.eclipse.dltk.core.IMember; |
| import org.eclipse.dltk.core.IModelElement; |
| import org.eclipse.dltk.core.IModelElementVisitor; |
| import org.eclipse.dltk.core.IParent; |
| import org.eclipse.dltk.core.IProjectFragment; |
| import org.eclipse.dltk.core.IScriptProject; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.ISourceRange; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.core.ScriptModelUtil; |
| import org.eclipse.dltk.core.SourceRange; |
| import org.eclipse.dltk.core.model.LocalVariable; |
| import org.eclipse.dltk.core.model.UnresolvedElement; |
| import org.eclipse.dltk.internal.javascript.ti.IReferenceAttributes; |
| import org.eclipse.dltk.internal.javascript.ti.JSDocSupport; |
| import org.eclipse.dltk.internal.javascript.ti.JSDocSupport.ParameterNode; |
| import org.eclipse.dltk.internal.javascript.ti.JSDocSupport.TypeNode; |
| import org.eclipse.dltk.internal.javascript.ti.JSDocSupport.TypedElementNode; |
| import org.eclipse.dltk.internal.javascript.ti.PositionReachedException; |
| import org.eclipse.dltk.internal.javascript.ti.TypeInferencer2; |
| import org.eclipse.dltk.internal.javascript.validation.JavaScriptValidations; |
| import org.eclipse.dltk.javascript.ast.Argument; |
| import org.eclipse.dltk.javascript.ast.FunctionStatement; |
| import org.eclipse.dltk.javascript.ast.Identifier; |
| import org.eclipse.dltk.javascript.ast.MultiLineComment; |
| import org.eclipse.dltk.javascript.ast.PropertyInitializer; |
| import org.eclipse.dltk.javascript.ast.Script; |
| import org.eclipse.dltk.javascript.ast.StringLiteral; |
| import org.eclipse.dltk.javascript.core.NodeFinder; |
| import org.eclipse.dltk.javascript.parser.JavaScriptParserUtil; |
| import org.eclipse.dltk.javascript.parser.jsdoc.JSDocTag; |
| import org.eclipse.dltk.javascript.typeinference.IValueReference; |
| import org.eclipse.dltk.javascript.typeinference.ReferenceKind; |
| import org.eclipse.dltk.javascript.typeinference.ReferenceLocation; |
| import org.eclipse.dltk.javascript.typeinference.ValueReferenceUtil; |
| import org.eclipse.dltk.javascript.typeinfo.IRMethod; |
| import org.eclipse.dltk.javascript.typeinfo.IRType; |
| import org.eclipse.dltk.javascript.typeinfo.ITypeSystem; |
| import org.eclipse.dltk.javascript.typeinfo.JSDocTypeRegion; |
| import org.eclipse.dltk.javascript.typeinfo.JSDocTypeUtil; |
| import org.eclipse.dltk.javascript.typeinfo.JSTypeSet; |
| import org.eclipse.dltk.javascript.typeinfo.TypeInfoManager; |
| import org.eclipse.dltk.javascript.typeinfo.model.Element; |
| import org.eclipse.dltk.javascript.typeinfo.model.Member; |
| import org.eclipse.dltk.javascript.typeinfo.model.Method; |
| import org.eclipse.dltk.javascript.typeinfo.model.Property; |
| import org.eclipse.dltk.javascript.typeinfo.model.Type; |
| import org.eclipse.dltk.javascript.typeinfo.model.TypeKind; |
| |
| public class JavaScriptSelectionEngine2 extends ScriptSelectionEngine { |
| |
| private static final boolean DEBUG = false; |
| |
| public static boolean isJSDocTypeSelectionEnabled() { |
| return true; |
| } |
| |
| @SuppressWarnings("serial") |
| private static class ModelElementFound extends RuntimeException { |
| final IModelElement element; |
| |
| public ModelElementFound(IModelElement element) { |
| this.element = element; |
| } |
| |
| } |
| |
| private static class Visitor implements IModelElementVisitor { |
| |
| private final int nameStart; |
| private final int nameEnd; |
| |
| public Visitor(int nameStart, int nameEnd) { |
| this.nameStart = nameStart; |
| this.nameEnd = nameEnd; |
| } |
| |
| public boolean visit(IModelElement element) { |
| if (element instanceof IMember) { |
| IMember member = (IMember) element; |
| try { |
| ISourceRange range = member.getNameRange(); |
| if (range.getOffset() == nameStart |
| && range.getLength() == nameEnd - nameStart) { |
| throw new ModelElementFound(element); |
| } |
| } catch (ModelException e) { |
| // |
| } |
| } |
| return true; |
| } |
| |
| } |
| |
| private static boolean isStringLiteralInObjectLiteral(ASTNode node) { |
| if (node instanceof StringLiteral) { |
| final StringLiteral literal = (StringLiteral) node; |
| if (literal.getParent() instanceof PropertyInitializer) { |
| final PropertyInitializer initializer = (PropertyInitializer) literal |
| .getParent(); |
| return initializer.getName() == literal; |
| } |
| } |
| return false; |
| } |
| |
| public IModelElement[] select(final IModuleSource module, int position, |
| int i) { |
| if (!(module.getModelElement() instanceof ISourceModule) || i == -1) { |
| return null; |
| } |
| String content = module.getSourceContents(); |
| if (position < 0 || position > content.length()) { |
| return null; |
| } |
| if (DEBUG) { |
| System.out |
| .println("select in " + module.getFileName() + " at " + position); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| final Script script = JavaScriptParserUtil.parse(module, null); |
| |
| NodeFinder finder = new NodeFinder(position, i + 1); |
| finder.locate(script); |
| ASTNode node = finder.getNode(); |
| if (node != null) { |
| if (DEBUG) { |
| System.out.println(node.getClass().getName() + "=" + node); //$NON-NLS-1$ |
| } |
| if (node instanceof Identifier |
| || isStringLiteralInObjectLiteral(node)) { |
| final TypeInferencer2 inferencer2 = new TypeInferencer2(); |
| final SelectionVisitor visitor = new SelectionVisitor( |
| inferencer2, node); |
| inferencer2.setVisitor(visitor); |
| inferencer2.setModelElement(module.getModelElement()); |
| try { |
| inferencer2.doInferencing(script); |
| } catch (PositionReachedException e) { |
| // |
| } |
| final IValueReference value = visitor.getValue(); |
| if (value == null) { |
| if (DEBUG) { |
| System.out.println("value is null or not found"); //$NON-NLS-1$ |
| } |
| return null; |
| } |
| ITypeSystem.CURRENT.runWith(inferencer2, new Runnable() { |
| public void run() { |
| toModelElements(inferencer2, visitor, module, value); |
| } |
| }); |
| return null; |
| } else if (node instanceof MultiLineComment |
| && isJSDocTypeSelectionEnabled()) { |
| final MultiLineComment comment = (MultiLineComment) node; |
| if (comment.isDocumentation()) { |
| final JSDocTag tag = JSDocSupport.parse(comment).getTagAt( |
| position); |
| if (tag == null || position < tag.valueStart()) { |
| return null; |
| } |
| final int valueOffset = tag.toValueOffset(position); |
| if (JSDocTag.PARAM.equals(tag.name())) { |
| final ParameterNode paramNode = JSDocSupport |
| .parseParameter(tag); |
| if (paramNode != null) { |
| if (paramNode.isInParameter(valueOffset)) { |
| final FunctionStatement function = findFunctionByDocumentation( |
| script, comment); |
| if (function != null) { |
| final Argument argument = function |
| .getArgument(paramNode.name); |
| if (argument != null) { |
| return new IModelElement[] { new LocalVariable( |
| module.getModelElement(), |
| paramNode.name, |
| argument.start(), |
| argument.end(), |
| argument.start(), |
| argument.end() - 1, null) }; |
| } |
| } |
| } else if (paramNode.isInType(valueOffset)) { |
| findTypeInTypeExpression(module, tag, |
| paramNode, valueOffset); |
| } |
| } |
| } else if (JSDocTag.RETURN.equals(tag.name()) |
| || JSDocTag.RETURNS.equals(tag.name())) { |
| final TypedElementNode typedNode = JSDocSupport |
| .parseOptionalType(tag); |
| if (typedNode != null |
| && typedNode.isInType(valueOffset)) { |
| findTypeInTypeExpression(module, tag, typedNode, |
| valueOffset); |
| } |
| } else if (JSDocTag.TYPE.equals(tag.name())) { |
| final TypeNode typeNode = JSDocSupport.parseType(tag); |
| if (typeNode != null && typeNode.isInType(valueOffset)) { |
| findTypeInTypeExpression(module, tag, typeNode, |
| valueOffset); |
| } |
| } |
| } |
| } |
| } |
| |
| // TODO Auto-generated method stub |
| return null; |
| } |
| |
| private void findTypeInTypeExpression(IModuleSource module, JSDocTag tag, |
| TypedElementNode node, int valueOffset) { |
| final ISourceModule m = (ISourceModule) module.getModelElement(); |
| final TypeInferencer2 inferencer2 = new TypeInferencer2(); |
| inferencer2.setModelElement(module.getModelElement()); |
| final JSDocTypeRegion typeRegion = JSDocTypeUtil.findTypeAt( |
| inferencer2, node.getTypeExpression(), |
| valueOffset - node.getTypeExpressionStart()); |
| if (typeRegion != null) { |
| final Type type = inferencer2.getKnownType(typeRegion.name()); |
| if (type != null) { |
| final int typeOffsetInFile = tag.fromValueOffset(typeRegion |
| .start() + node.getTypeExpressionStart()); |
| convertAndReportElement(m, type, new SourceRange( |
| typeOffsetInFile, typeRegion.length())); |
| } |
| } |
| } |
| |
| /** |
| * Finds the function the specified documentation belongs to. |
| */ |
| @Nullable |
| private static FunctionStatement findFunctionByDocumentation(Script script, |
| MultiLineComment comment) { |
| for (FunctionStatement function : ASTUtil.select(script, |
| FunctionStatement.class, true)) { |
| if (function.getDocumentation() == comment |
| || JSDocSupport.getComment(function) == comment) { |
| return function; |
| } |
| } |
| return null; |
| } |
| |
| private void toModelElements(TypeInferencer2 inferencer2, |
| SelectionVisitor visitor, IModuleSource module, |
| IValueReference value) { |
| final ReferenceKind kind = value.getKind(); |
| if (DEBUG) { |
| System.out.println(value + "," + kind); //$NON-NLS-1$ |
| } |
| final ReferenceLocation location = value.getLocation(); |
| if (DEBUG) { |
| System.out.println(location); |
| } |
| ISourceModule m = (ISourceModule) module.getModelElement(); |
| if (kind == ReferenceKind.ARGUMENT || kind == ReferenceKind.LOCAL) { |
| if (location == ReferenceLocation.UNKNOWN) { |
| return; |
| } |
| final IModelElement result = locateModelElement(location); |
| if (result != null |
| && (result.getElementType() == IModelElement.FIELD || result |
| .getElementType() == IModelElement.METHOD)) { |
| reportElement(result); |
| return; |
| } |
| final IRType type = JavaScriptValidations.typeOf(value); |
| reportElement(new LocalVariable(m, value.getName(), |
| location.getDeclarationStart(), |
| location.getDeclarationEnd(), location.getNameStart(), |
| location.getNameEnd() - 1, type == null ? null |
| : type.getName())); |
| return; |
| } else if (kind == ReferenceKind.FUNCTION |
| || kind == ReferenceKind.GLOBAL || kind == ReferenceKind.FIELD) { |
| if (location == ReferenceLocation.UNKNOWN) { |
| return; |
| } |
| final IModelElement result = locateModelElement(location); |
| if (result != null) { |
| reportElement(result); |
| return; |
| } |
| } else if (kind == ReferenceKind.PROPERTY) { |
| final Collection<Property> properties = ValueReferenceUtil |
| .extractElements(value, Property.class); |
| if (properties != null) { |
| convertAndReportElements(m, properties); |
| return; |
| } |
| final IModelElement result = locateModelElement(location); |
| if (result != null) { |
| reportElement(result); |
| return; |
| } |
| } else if (kind == ReferenceKind.METHOD) { |
| final List<IRMethod> methods = ValueReferenceUtil.extractElements( |
| value, IRMethod.class); |
| if (methods != null) { |
| IValueReference[] arguments = visitor.getArguments(); |
| if (arguments == null) { |
| arguments = new IValueReference[0]; |
| } |
| final IRMethod method = JavaScriptValidations.selectMethod( |
| methods, arguments, true); |
| if (method.getSource() instanceof Method) { |
| convertAndReportElement(m, (Method) method.getSource(), |
| null); |
| return; |
| } |
| } |
| } else if (kind == ReferenceKind.TYPE) { |
| final LinkedHashSet<Type> t = new LinkedHashSet<Type>(); |
| JSTypeSet types = value.getDeclaredTypes(); |
| if (types != null) { |
| Collections.addAll(t, types.toArray()); |
| } |
| types = value.getTypes(); |
| if (types != null) { |
| Collections.addAll(t, types.toArray()); |
| } |
| if (!t.isEmpty()) { |
| convertAndReportElements(m, t); |
| return; |
| } |
| } |
| if (location != ReferenceLocation.UNKNOWN |
| && location.getSourceModule() != null) { |
| reportElement(new UnresolvedElement(location.getSourceModule(), |
| value.getName(), location.getNameStart(), |
| location.getNameEnd() - 1)); |
| return; |
| } |
| return; |
| } |
| |
| /** |
| * @param module |
| * @param elements |
| * @return |
| */ |
| private void convertAndReportElements(ISourceModule module, |
| Collection<? extends Element> elements) { |
| for (Element element : elements) { |
| convertAndReportElement(module, element, null); |
| } |
| } |
| |
| private void convertAndReportElement(ISourceModule module, Element element, |
| ISourceRange range) { |
| try { |
| final IModelElement me = convert(module, element); |
| reportElement(me != null ? me : element, range); |
| } catch (ModelException e) { |
| // |
| } |
| } |
| |
| /** |
| * @param module |
| * @param element |
| * @return |
| * @throws ModelException |
| */ |
| private IModelElement convert(ISourceModule module, Element element) |
| throws ModelException { |
| Type type; |
| if (element instanceof Type) { |
| type = (Type) element; |
| } else { |
| type = ((Member) element).getDeclaringType(); |
| } |
| if (type != null && type.getKind() == TypeKind.PREDEFINED) { |
| final List<String> path = new ArrayList<String>(); |
| path.add(type.getName()); |
| if (element != type) { |
| path.add(element.getName()); |
| } |
| return resolveBuiltin(module.getScriptProject(), path); |
| } |
| if (type != null && type.getKind() == TypeKind.JAVASCRIPT) { |
| ReferenceLocation location = (ReferenceLocation) element |
| .getAttribute(IReferenceAttributes.LOCATION); |
| if (location != null && location != ReferenceLocation.UNKNOWN) { |
| if (element instanceof Property) { |
| return new LocalVariable(module, element.getName(), |
| location.getDeclarationStart(), |
| location.getDeclarationEnd(), |
| location.getNameStart(), location.getNameEnd() - 1, |
| null); |
| } |
| final IModelElement result = locateModelElement(location); |
| if (result != null) { |
| return result; |
| } |
| } else { |
| // TODO this only goes 1 deep, need support for nested types.. |
| IModelElement[] children = module.getChildren(); |
| for (IModelElement modelElement : children) { |
| if (modelElement.getElementType() == IModelElement.METHOD |
| && modelElement.getElementName().equals( |
| type.getName())) { |
| IModelElement[] children2 = ((IParent) modelElement) |
| .getChildren(); |
| for (IModelElement child : children2) { |
| if (child.getElementName() |
| .equals(element.getName())) |
| return child; |
| } |
| return modelElement; |
| } |
| } |
| } |
| } |
| return TypeInfoManager.convertElement(module, element); |
| } |
| |
| private IModelElement locateModelElement(final ReferenceLocation location) { |
| ISourceModule module = location.getSourceModule(); |
| if (module != null) { |
| try { |
| ScriptModelUtil.reconcile(module); |
| module.accept(new Visitor(location.getNameStart(), location |
| .getNameEnd())); |
| } catch (ModelException e) { |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| } catch (ModelElementFound e) { |
| return e.element; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @param project |
| * @param segments |
| * @return |
| * @throws ModelException |
| */ |
| private IModelElement resolveBuiltin(IScriptProject project, |
| List<String> segments) throws ModelException { |
| for (IProjectFragment fragment : project.getProjectFragments()) { |
| if (fragment.isBuiltin()) { |
| ISourceModule m = fragment.getScriptFolder(Path.EMPTY) |
| .getSourceModule("builtins.js"); //$NON-NLS-1$ |
| if (!m.exists()) { |
| return null; |
| } |
| IModelElement me = m; |
| SEGMENT_LOOP: for (String segment : segments) { |
| if (me instanceof IParent) { |
| final IModelElement[] children = ((IParent) me) |
| .getChildren(); |
| for (IModelElement child : children) { |
| if (segment.equals(child.getElementName())) { |
| me = child; |
| continue SEGMENT_LOOP; |
| } |
| } |
| } |
| return null; |
| } |
| return me; |
| } |
| } |
| return null; |
| } |
| } |