blob: 2f99ee29b1158b817476417650311f448676c67e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 NumberFour AG
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* NumberFour AG - initial API and Implementation (Alex Panchenko)
*******************************************************************************/
package org.eclipse.dltk.javascript.internal.core.codeassist;
import org.eclipse.dltk.annotations.NonNull;
import org.eclipse.dltk.annotations.Nullable;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.compiler.env.ModuleSource;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.javascript.ast.ErrorExpression;
import org.eclipse.dltk.javascript.ast.Identifier;
import org.eclipse.dltk.javascript.ast.JSNode;
import org.eclipse.dltk.javascript.ast.ObjectInitializer;
import org.eclipse.dltk.javascript.ast.PropertyInitializer;
import org.eclipse.dltk.javascript.ast.Script;
import org.eclipse.dltk.javascript.ast.StatementBlock;
import org.eclipse.dltk.javascript.ast.UserExpression;
import org.eclipse.dltk.javascript.core.NodeFinder;
import org.eclipse.dltk.javascript.parser.JavaScriptParserUtil;
/**
* Static functions for evaluating the context at the specified position, to
* limit which proposals should be offered, e.g. exclude keywords in the
* expression context.
*/
public class JavaScriptCompletionUtil {
public enum ExpressionType {
PROPERTY_INITIALIZER_VALUE,
/**
* Indicates position within an {@link ObjectInitializer} but not within
* any nested {@link PropertyInitializer}.
*
* E.g.
*
* <pre>
* var object = { name : "Aleksander " , surname : "Kosicki" };
* ^^^^^ ^^^^^^^ ^^^^^
* </pre>
*
* Where ^ means between the given and the precedent column
*/
OBJECT_INITIALIZER, OTHER
}
public static class ExpressionContext {
public final ExpressionType expressionType;
public final JSNode node;
public ExpressionContext(ExpressionType expressionType, JSNode node) {
this.expressionType = expressionType;
this.node = node;
}
}
public static ExpressionType evaluateExpressionType(
@Nullable ISourceModule module, @NonNull CharSequence document,
int position) {
final ExpressionContext context = evaluateExpressionContext(module,
document, position);
return context != null ? context.expressionType : null;
}
public static ExpressionContext evaluateExpressionContext(
ISourceModule module, CharSequence document, int position) {
final Script script = module != null ? JavaScriptParserUtil
.parse(module) : JavaScriptParserUtil.parse(new ModuleSource(
document.toString()), null);
return evaluateExpressionContext(script, document, position);
}
/**
* @param node
* @param position
* @return <code>true</code> if given position is within the source range of
* some {@link ObjectInitializer} but now within the source range of
* any of its nested {@link PropertyInitializer}s
*
* @see {@link ExpressionType#OBJECT_INITIALIZER}
*/
private static boolean isDirectlyInObjectInitializer(@NonNull ASTNode node,
int position) {
if (node instanceof UserExpression) {
node = ((UserExpression) node).getOriginal();
}
for (ASTNode child : node.getChilds()) {
if (child.start() < position && child.end() > position) {
return isDirectlyInObjectInitializer(child, position);
}
}
return node instanceof ObjectInitializer;
}
public static ExpressionContext evaluateExpressionContext(Script script,
CharSequence document, int position) {
if (script != null) {
ASTNode node = new NodeFinder(true, position, position).locate(
script).getNode();
if (node instanceof Identifier) {
final Identifier identifier = (Identifier) node;
final ASTNode parent = identifier.getParent();
if (parent instanceof Script
|| parent instanceof StatementBlock) {
// fall thru
} else if (parent instanceof PropertyInitializer
&& ((PropertyInitializer) parent).getValue() == node) {
return new ExpressionContext(
ExpressionType.PROPERTY_INITIALIZER_VALUE,
identifier);
} else {
return new ExpressionContext(ExpressionType.OTHER,
identifier);
}
} else if (node instanceof ErrorExpression) {
final ErrorExpression error = (ErrorExpression) node;
if (error.getParent() instanceof PropertyInitializer) {
return new ExpressionContext(
ExpressionType.PROPERTY_INITIALIZER_VALUE, error);
} else {
return new ExpressionContext(ExpressionType.OTHER, error);
}
}
int begin = position;
while (begin > 0
&& Character.isWhitespace(document.charAt(begin - 1))) {
--begin;
}
node = new NodeFinder(true, begin, begin).locate(script).getNode();
if (node instanceof ErrorExpression) {
final ErrorExpression error = (ErrorExpression) node;
if (error.getParent() instanceof PropertyInitializer) {
return new ExpressionContext(
ExpressionType.PROPERTY_INITIALIZER_VALUE, error);
} else {
return new ExpressionContext(ExpressionType.OTHER, error);
}
}
// TODO CallExpression: setTimeout(<Ctrl-Space>)
if (isDirectlyInObjectInitializer(script, position)) {
return new ExpressionContext(ExpressionType.OBJECT_INITIALIZER,
null);
}
}
return null;
}
}