1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-04-29 19:45:01 +02:00

Bug 481126 - Convert Acorn AST into Java

Added the class QmlASTNodeHandler that creates and manages Proxy
instances of the QML AST node interfaces.  The proxies are created
dynamically based on what methods are called on the interface.  That
way, the AST is loaded as needed rather than all at once.  Added a few
tests to verify behavior (however, they are not comprehensive; there are
a lot more cases that should be verified in the future).

Change-Id: I64038f9668942a67e1f1b7dceac6c7dbed2d46d7
Signed-off-by: Matthew Bastien <mbastien@blackberry.com>
This commit is contained in:
Matthew Bastien 2015-12-16 11:38:33 -05:00
parent 6266df700b
commit a43976b882
20 changed files with 695 additions and 63 deletions

View file

@ -1,34 +1,45 @@
package org.eclipse.cdt.qt.core.tests;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.qt.core.QMLAnalyzer;
import org.eclipse.cdt.qt.core.IQMLAnalyzer;
import org.eclipse.cdt.qt.core.QMLTernCompletion;
import org.eclipse.cdt.qt.core.qmljs.IJSBinaryExpression;
import org.eclipse.cdt.qt.core.qmljs.IJSBinaryExpression.BinaryOperator;
import org.eclipse.cdt.qt.core.qmljs.IQmlASTNode;
import org.eclipse.cdt.qt.core.qmljs.IQmlHeaderItem;
import org.eclipse.cdt.qt.core.qmljs.IQmlImport;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectMember;
import org.eclipse.cdt.qt.core.qmljs.IQmlProgram;
import org.eclipse.cdt.qt.core.qmljs.IQmlPropertyBinding;
import org.eclipse.cdt.qt.core.qmljs.IQmlRootObject;
import org.eclipse.cdt.qt.core.qmljs.IQmlScriptBinding;
import org.junit.BeforeClass;
import org.junit.Test;
@SuppressWarnings("nls")
public class NashornTests {
protected static QMLAnalyzer analyzer;
protected static IQMLAnalyzer analyzer;
@BeforeClass
public static void loadAnalyzer() {
analyzer = Activator.getService(QMLAnalyzer.class);
analyzer = Activator.getService(IQMLAnalyzer.class);
}
protected void getCompletions(String code, QMLTernCompletion... expected) throws Throwable {
int pos = code.indexOf('|');
code = code.substring(0, pos) + code.substring(pos + 1);
Collection<QMLTernCompletion> QMLTernCompletions = analyzer.getCompletions("test1.qml", code, pos);
Collection<QMLTernCompletion> QMLTernCompletions = analyzer.getCompletions("test1.qml", code, pos, false);
Map<String, QMLTernCompletion> set = new HashMap<>();
Set<String> unexpected = new HashSet<>();
@ -55,7 +66,108 @@ public class NashornTests {
}
@Test
public void test1() throws Throwable {
public void testParseFile1() throws Throwable {
IQmlASTNode node = analyzer.parseFile("main.qml", "");
assertEquals("Unexpected program node type", "QMLProgram", node.getType());
}
@Test
public void testParseFile2() throws Throwable {
IQmlASTNode node = analyzer.parseFile("main.qml", "import QtQuick 2.2");
assertThat(node, instanceOf(IQmlProgram.class));
IQmlProgram program = (IQmlProgram) node;
List<IQmlHeaderItem> headerItems = program.getHeaderItemList().getItems();
assertEquals("Unexpected number of header items", 1, headerItems.size());
assertThat(headerItems.get(0), instanceOf(IQmlImport.class));
IQmlImport imp = (IQmlImport) headerItems.get(0);
assertEquals("Unexpected module identifier", "QtQuick", imp.getModule().getIdentifier().getName());
assertEquals("Unexpected module raw version", "2.2", imp.getModule().getVersion().getRaw());
assertEquals("Unexpected module version", 2.2, imp.getModule().getVersion().getValue(), 0.0001d);
}
@Test
public void testParseString1() throws Throwable {
IQmlASTNode node = analyzer.parseString("", "qml", true, true);
assertEquals("Unexpected program node type", "QMLProgram", node.getType());
}
@Test
public void testParseString2() throws Throwable {
IQmlASTNode node = analyzer.parseString("import QtQuick 2.2", "qml", true, true);
assertThat(node, instanceOf(IQmlProgram.class));
IQmlProgram program = (IQmlProgram) node;
List<IQmlHeaderItem> headerItems = program.getHeaderItemList().getItems();
assertEquals("Unexpected number of header items", 1, headerItems.size());
assertThat(headerItems.get(0), instanceOf(IQmlImport.class));
IQmlImport imp = (IQmlImport) headerItems.get(0);
assertEquals("Unexpected module identifier", "QtQuick", imp.getModule().getIdentifier().getName());
assertEquals("Unexpected module raw version", "2.2", imp.getModule().getVersion().getRaw());
assertEquals("Unexpected module version", 2.2, imp.getModule().getVersion().getValue(), 0.0001d);
}
@Test
public void testParseString3() throws Throwable {
IQmlASTNode node = analyzer.parseString("import QtQuick 2.2", "qml", true, true);
assertThat(node, instanceOf(IQmlProgram.class));
IQmlProgram program = (IQmlProgram) node;
List<IQmlHeaderItem> headerItems = program.getHeaderItemList().getItems();
assertEquals("Unexpected number of header items", 1, headerItems.size());
assertThat(headerItems.get(0), instanceOf(IQmlImport.class));
IQmlImport imp = (IQmlImport) headerItems.get(0);
assertEquals("Unexpected start range", 0, imp.getRange()[0]);
assertEquals("Unexpected end range", 18, imp.getRange()[1]);
}
@Test
public void testParseString4() throws Throwable {
IQmlASTNode node = analyzer.parseString("import QtQuick 2.2", "qml", true, true);
assertThat(node, instanceOf(IQmlProgram.class));
IQmlProgram program = (IQmlProgram) node;
List<IQmlHeaderItem> headerItems = program.getHeaderItemList().getItems();
assertEquals("Unexpected number of header items", 1, headerItems.size());
assertThat(headerItems.get(0), instanceOf(IQmlImport.class));
IQmlImport imp = (IQmlImport) headerItems.get(0);
assertEquals("Unexpected start line", 1, imp.getLocation().getStart().getLine());
assertEquals("Unexpected start column", 0, imp.getLocation().getStart().getColumn());
assertEquals("Unexpected start line", 1, imp.getLocation().getEnd().getLine());
assertEquals("Unexpected start column", 18, imp.getLocation().getEnd().getColumn());
}
@Test
public void testParseString5() throws Throwable {
IQmlASTNode node = analyzer.parseString("QtObject {}", "qml", true, true);
assertThat(node, instanceOf(IQmlProgram.class));
IQmlProgram program = (IQmlProgram) node;
List<IQmlHeaderItem> headerItems = program.getHeaderItemList().getItems();
assertEquals("Unexpected number of header items", 0, headerItems.size());
assertNotNull("Root object was null", program.getRootObject());
IQmlRootObject root = program.getRootObject();
assertEquals("Unexpected root object type", "QMLObjectDefinition", root.getType());
assertEquals("Unexpected root object identifier", "QtObject", root.getIdentifier().getName());
}
@Test
public void testParseString6() throws Throwable {
IQmlASTNode node = analyzer.parseString("QtObject {s: 3 + 3}", "qml", true, true);
assertThat(node, instanceOf(IQmlProgram.class));
IQmlProgram program = (IQmlProgram) node;
assertNotNull("Root object was null", program.getRootObject());
IQmlRootObject root = program.getRootObject();
List<IQmlObjectMember> members = root.getBody().getMembers();
assertEquals("Unexpected number of root object members", 1, members.size());
assertThat(members.get(0), instanceOf(IQmlPropertyBinding.class));
IQmlPropertyBinding bind = (IQmlPropertyBinding) members.get(0);
assertThat(bind.getBinding(), instanceOf(IQmlScriptBinding.class));
IQmlScriptBinding scriptBinding = (IQmlScriptBinding) bind.getBinding();
assertFalse("Script binding was not a JavaScript expression", scriptBinding.isBlock());
assertThat(scriptBinding.getScript(), instanceOf(IJSBinaryExpression.class));
assertEquals("Unexpected expression type", "BinaryExpression", scriptBinding.getScript().getType());
IJSBinaryExpression expr = (IJSBinaryExpression) scriptBinding.getScript();
assertEquals("Unexpected binary operator", BinaryOperator.Add, expr.getOperator());
}
@Test
public void testCompletions1() throws Throwable {
if (analyzer == null) {
return;
}
@ -64,7 +176,7 @@ public class NashornTests {
}
@Test
public void test2() throws Throwable {
public void testCompletions2() throws Throwable {
if (analyzer == null) {
return;
}
@ -75,7 +187,7 @@ public class NashornTests {
}
@Test
public void test3() throws Throwable {
public void testCompletions3() throws Throwable {
if (analyzer == null) {
return;
}
@ -83,7 +195,7 @@ public class NashornTests {
}
@Test
public void test4() throws Throwable {
public void testCompletions4() throws Throwable {
if (analyzer == null) {
return;
}
@ -118,7 +230,7 @@ public class NashornTests {
}
@Test
public void test5() throws Throwable {
public void testCompletions5() throws Throwable {
if (analyzer == null) {
return;
}
@ -153,7 +265,7 @@ public class NashornTests {
}
@Test
public void test6() throws Throwable {
public void testCompletions6() throws Throwable {
if (analyzer == null) {
return;
}
@ -188,7 +300,7 @@ public class NashornTests {
}
@Test
public void test7() throws Throwable {
public void testCompletions7() throws Throwable {
if (analyzer == null) {
return;
}

View file

@ -31,4 +31,5 @@ Export-Package: org.eclipse.cdt.internal.qt.core;x-friends:="org.eclipse.cdt.qt.
org.eclipse.cdt.internal.qt.core.qmldir;x-friends:="org.eclipse.cdt.qt.core.tests",
org.eclipse.cdt.qt.core,
org.eclipse.cdt.qt.core.location,
org.eclipse.cdt.qt.core.qmldir
org.eclipse.cdt.qt.core.qmldir,
org.eclipse.cdt.qt.core.qmljs

View file

@ -623,6 +623,7 @@
// replacing JavaScripts top-level. Here we are parsing such things
// as the root object literal and header statements of QML. Eventually,
// these rules will delegate down to JavaScript expressions.
node.mode = this.options.mode;
node.headerItemList = this.qml_parseHeaderItemList();
node.rootObject = null;
if (this.type !== tt.eof) {

View file

@ -656,6 +656,7 @@ var injectQMLLoose;
// as the root object literal and header statements of QML. Eventually,
// these rules will delegate down to JavaScript expressions.
var node = this.startNode();
node.mode = this.options.mode;
node.headerItemList = this.qml_parseHeaderItemList();
node.rootObject = null;
if (this.tok.type !== tt.eof) {

View file

@ -13,8 +13,8 @@ import javax.script.ScriptException;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.internal.qt.core.build.QtBuildConfigurationFactory;
import org.eclipse.cdt.qt.core.IQMLAnalyzer;
import org.eclipse.cdt.qt.core.IQtInstallManager;
import org.eclipse.cdt.qt.core.QMLAnalyzer;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
@ -64,8 +64,8 @@ public class Activator extends Plugin {
context.registerService(IQtInstallManager.class, new QtInstallManager(), null);
QMLAnalyzer qmlAnalyzer = new QMLAnalyzer();
context.registerService(QMLAnalyzer.class, qmlAnalyzer, null);
new Job("Load QML Analyzer") {
context.registerService(IQMLAnalyzer.class, qmlAnalyzer, null);
new Job("Load QML Analyzer") { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
try {

View file

@ -5,7 +5,7 @@
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.cdt.qt.core;
package org.eclipse.cdt.internal.qt.core;
import java.io.BufferedReader;
import java.io.File;
@ -27,10 +27,12 @@ import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.eclipse.cdt.internal.qt.core.Activator;
import org.eclipse.cdt.qt.core.IQMLAnalyzer;
import org.eclipse.cdt.qt.core.QMLTernCompletion;
import org.eclipse.cdt.qt.core.qmljs.IQmlASTNode;
@SuppressWarnings("nls")
public class QMLAnalyzer {
public class QMLAnalyzer implements IQMLAnalyzer {
private ScriptEngine engine;
private Invocable invoke;
@ -72,6 +74,9 @@ public class QMLAnalyzer {
ResolveDirectory resolveDirectory = (file, pathString) -> {
String filename = (String) file.get("name");
String fileDirectory = new File(filename).getParent();
if (fileDirectory == null) {
fileDirectory = "";
}
if (pathString == null) {
return fixPathString(fileDirectory);
}
@ -130,43 +135,134 @@ public class QMLAnalyzer {
return fileName;
}
@Override
public void addFile(String fileName, String code) throws NoSuchMethodException, ScriptException {
waitUntilLoaded();
invoke.invokeMethod(tern, "addFile", fixPathString(fileName), code);
}
@Override
public void deleteFile(String fileName) throws NoSuchMethodException, ScriptException {
waitUntilLoaded();
invoke.invokeMethod(tern, "delFile", fixPathString(fileName));
}
private static class ASTCallback implements RequestCallback {
private IQmlASTNode ast;
@Override
public void callback(Object err, Object data) {
if (err != null) {
throw new RuntimeException(err.toString());
} else {
try {
ast = QmlASTNodeHandler.createQmlASTProxy((Bindings) ((Bindings) data).get("ast"));
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
public IQmlASTNode getAST() {
return ast;
}
}
@Override
public IQmlASTNode parseFile(String fileName, String text) throws NoSuchMethodException, ScriptException {
waitUntilLoaded();
fileName = fixPathString(fileName);
Bindings query = engine.createBindings();
query.put("type", "parseFile");
query.put("file", fileName);
Bindings request = engine.createBindings();
request.put("query", query);
if (text != null) {
Bindings file = engine.createBindings();
file.put("type", "full");
file.put("name", fileName);
file.put("text", text);
Bindings files = (Bindings) engine.eval("new Array()");
invoke.invokeMethod(files, "push", file);
request.put("files", files);
}
ASTCallback callback = new ASTCallback();
invoke.invokeMethod(tern, "request", request, invoke.invokeFunction("requestCallback", callback));
return callback.getAST();
}
@Override
public IQmlASTNode parseString(String text) throws NoSuchMethodException, ScriptException {
return parseString(text, "qml", false, false);
}
@Override
public IQmlASTNode parseString(String text, String mode, boolean locations, boolean ranges)
throws NoSuchMethodException, ScriptException {
waitUntilLoaded();
Bindings query = engine.createBindings();
query.put("type", "parseString");
query.put("text", text);
Bindings options = engine.createBindings();
options.put("mode", mode);
options.put("locations", locations);
options.put("ranges", ranges);
query.put("options", options);
Bindings request = engine.createBindings();
request.put("query", query);
ASTCallback callback = new ASTCallback();
invoke.invokeMethod(tern, "request", request, invoke.invokeFunction("requestCallback", callback));
return callback.getAST();
}
protected <T> T[] toJavaArray(Bindings binding, Class<T[]> clazz) throws NoSuchMethodException, ScriptException {
return clazz.cast(invoke.invokeMethod(engine.get("Java"), "to", binding,
clazz.getCanonicalName() + (clazz.isArray() ? "" : "[]")));
}
@Override
public Collection<QMLTernCompletion> getCompletions(String fileName, String text, int pos)
throws NoSuchMethodException, ScriptException {
return getCompletions(fileName, text, pos, true);
}
@Override
public Collection<QMLTernCompletion> getCompletions(String fileName, String text, int pos, boolean includeKeywords)
throws NoSuchMethodException, ScriptException {
waitUntilLoaded();
fileName = fixPathString(fileName);
Bindings file = engine.createBindings();
file.put("type", "full");
file.put("name", fileName);
file.put("text", text);
Bindings files = (Bindings) engine.eval("new Array()");
invoke.invokeMethod(files, "push", file);
Bindings query = engine.createBindings();
query.put("type", "completions");
query.put("lineCharPositions", true);
query.put("file", fileName);
query.put("end", pos);
query.put("types", true);
query.put("docs", false);
query.put("urls", false);
query.put("origins", true);
query.put("filter", true);
query.put("caseInsensitive", true);
query.put("lineCharPositions", true);
query.put("expandWordForward", false);
query.put("includeKeywords", true);
query.put("guess", false);
query.put("sort", true);
query.put("expandWordForward", false);
query.put("includeKeywords", includeKeywords);
Bindings request = engine.createBindings();
request.put("files", files);
request.put("query", query);
if (text != null) {
Bindings file = engine.createBindings();
file.put("type", "full");
file.put("name", fileName);
file.put("text", text);
Bindings files = (Bindings) engine.eval("new Array()");
invoke.invokeMethod(files, "push", file);
request.put("files", files);
}
List<QMLTernCompletion> completions = new ArrayList<>();
@ -176,8 +272,7 @@ public class QMLAnalyzer {
} else {
try {
Bindings comps = (Bindings) ((Bindings) data).get("completions");
for (Bindings completion : (Bindings[]) invoke.invokeMethod(engine.get("Java"), "to", comps,
"javax.script.Bindings[]")) {
for (Bindings completion : toJavaArray(comps, Bindings[].class)) {
completions.add(new QMLTernCompletion((String) completion.get("name"),
(String) completion.get("type"), (String) completion.get("origin")));
}
@ -192,16 +287,11 @@ public class QMLAnalyzer {
return completions;
}
@Override
public List<Bindings> getDefinition(String identifier, String fileName, String text, int pos)
throws NoSuchMethodException, ScriptException {
waitUntilLoaded();
fileName = fixPathString(fileName);
Bindings file = engine.createBindings();
file.put("type", "full");
file.put("name", fileName);
file.put("text", text);
Bindings files = (Bindings) engine.eval("new Array()");
invoke.invokeMethod(files, "push", file);
Bindings query = engine.createBindings();
query.put("type", "definition");
@ -217,8 +307,16 @@ public class QMLAnalyzer {
query.put("includeKeywords", true);
query.put("guess", false);
Bindings request = engine.createBindings();
request.put("files", files);
request.put("query", query);
if (text != null) {
Bindings file = engine.createBindings();
file.put("type", "full");
file.put("name", fileName);
file.put("text", text);
Bindings files = (Bindings) engine.eval("new Array()");
invoke.invokeMethod(files, "push", file);
request.put("files", files);
}
List<Bindings> definitions = new ArrayList<>();

View file

@ -0,0 +1,250 @@
/*******************************************************************************
* Copyright (c) 2015 QNX Software Systems and others.
* 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:
* QNX Software Systems - Initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.qt.core;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.script.Bindings;
import org.eclipse.cdt.internal.qt.core.location.Position;
import org.eclipse.cdt.internal.qt.core.location.SourceLocation;
import org.eclipse.cdt.qt.core.IQMLAnalyzer;
import org.eclipse.cdt.qt.core.location.ISourceLocation;
import org.eclipse.cdt.qt.core.qmljs.IJSLiteral;
import org.eclipse.cdt.qt.core.qmljs.IJSRegExpLiteral;
import org.eclipse.cdt.qt.core.qmljs.IQmlASTNode;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectDefinition;
import org.eclipse.cdt.qt.core.qmljs.IQmlRootObject;
/**
* Translates a JavaScript {@link Bindings} object into a QML AST. This class employs {@link java.lang.reflect.Proxy} in order to
* dynamically create the AST at runtime.
* <p>
* To begin translation simply call the static method <code>createQmlASTProxy</code>. The AST is translated only when it needs to be
* (i.e. when one of its 'get' methods are called).
*/
public class QmlASTNodeHandler implements InvocationHandler {
private static final String NODE_QML_PREFIX = "QML"; //$NON-NLS-1$
private static final String NODE_TYPE_PROPERTY = "type"; //$NON-NLS-1$
private static final String NODE_REGEX_PROPERTY = "regex"; //$NON-NLS-1$
private static final String CREATE_ENUM_METHOD = "fromObject"; //$NON-NLS-1$
private static final String AST_PACKAGE = "org.eclipse.cdt.qt.core.qmljs."; //$NON-NLS-1$
private static final String AST_QML_PREFIX = "IQml"; //$NON-NLS-1$
private static final String AST_JS_PREFIX = "IJS"; //$NON-NLS-1$
private static String getPropertyName(String method) {
String name = ""; //$NON-NLS-1$
if (method.startsWith("is")) { //$NON-NLS-1$
name = method.substring(2, 3).toLowerCase() + method.substring(3);
} else if (method.startsWith("get")) { //$NON-NLS-1$
name = method.substring(3, 4).toLowerCase() + method.substring(4);
}
if (name.equalsIgnoreCase("identifier")) { //$NON-NLS-1$
return "id"; //$NON-NLS-1$
} else if (name.equalsIgnoreCase("location")) { //$NON-NLS-1$
return "loc"; //$NON-NLS-1$
}
return name;
}
/**
* Constructs a new {@link IQmlASTNode} from the given {@link Bindings}. This is a helper method equivalent to
* <code>createQmlASTProxy(node, null)</code>
*
* @param node
* the AST node as retrieved from Nashorn
* @return a Proxy representing the given node
* @throws ClassNotFoundException
* if the node does not represent a valid QML AST Node
* @see {@link QmlASTNodeHandler#createQmlASTProxy(Bindings, Class)}
*/
public static IQmlASTNode createQmlASTProxy(Bindings node) throws ClassNotFoundException {
return createQmlASTProxy(node, null);
}
/**
* Constructs a new {@link IQmlASTNode} from the given {@link Bindings}. If a return type is specified, it will take precedence
* over the type retrieved from the binding. This is useful for nodes that extend, but do not add functionality to, an acorn AST
* element. A good example of this is {@link IQmlRootObject} which extends {@link IQmlObjectDefinition}. We can easily determine
* the location in the AST at which we want an IQmlRootObject over an IQmlObjectDefinition and set the returnType accordingly.
*
* @param node
* the node as retrieved from acorn
* @param returnType
* the expected node to return or null
* @return a Proxy representing the given node
* @throws ClassNotFoundException
* if the node does not represent a valid QML AST Node
*/
public static IQmlASTNode createQmlASTProxy(Bindings node, Class<?> returnType) throws ClassNotFoundException {
String type = (String) node.getOrDefault(NODE_TYPE_PROPERTY, ""); //$NON-NLS-1$
if (type.startsWith(NODE_QML_PREFIX)) {
type = AST_QML_PREFIX + type.substring(3);
} else {
type = AST_JS_PREFIX + type;
}
Class<?> astClass = Class.forName(AST_PACKAGE + type);
if (astClass.equals(IJSLiteral.class)) {
// If this is a Literal, we have to distinguish it between a RegExp Literal using the 'regex' property
if (node.get(NODE_REGEX_PROPERTY) != null) {
astClass = IJSRegExpLiteral.class;
}
}
if (returnType != null) {
if (!IQmlASTNode.class.isAssignableFrom(astClass)) {
throw new ClassCastException(astClass + " cannot be cast to " + IQmlASTNode.class); //$NON-NLS-1$
}
if (astClass.isAssignableFrom(returnType)) {
astClass = returnType;
}
}
return (IQmlASTNode) Proxy.newProxyInstance(QmlASTNodeHandler.class.getClassLoader(),
new Class<?>[] { astClass },
new QmlASTNodeHandler(node));
}
private final QMLAnalyzer analyzer;
private final Bindings node;
private final Map<String, Object> methodResults;
private QmlASTNodeHandler(Bindings node) {
this.analyzer = (QMLAnalyzer) Activator.getService(IQMLAnalyzer.class);
this.node = node;
this.methodResults = new HashMap<>();
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String mName = method.getName();
if (!methodResults.containsKey(method.getName())) {
// Invoke the default implementation of the method if possible
if (method.isDefault()) {
final Class<?> declaringClass = method.getDeclaringClass();
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class,
int.class);
constructor.setAccessible(true);
methodResults.put(mName, constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
.unreflectSpecial(method, declaringClass)
.bindTo(proxy)
.invokeWithArguments(args));
} else {
// Use the return type of the method as well as its contents of the node to get the Object to return
String pName = getPropertyName(mName);
methodResults.put(mName, handleObject(node.get(pName), method.getReturnType()));
}
}
return methodResults.get(method.getName());
}
private Object handleObject(Object value, Class<?> expectedType) throws Throwable {
if (expectedType.isAssignableFrom(ISourceLocation.class)) {
// ISourceLocation doesn't correspond to an AST Node and needs to be created manually from
// the given Bindings.
if (value instanceof Bindings) {
Bindings bind = (Bindings) value;
SourceLocation loc = new SourceLocation();
loc.setSource((String) bind.get("source")); //$NON-NLS-1$
Bindings start = (Bindings) bind.get("start"); //$NON-NLS-1$
loc.setStart(new Position(((Number) start.get("line")).intValue(), //$NON-NLS-1$
((Number) start.get("column")).intValue())); //$NON-NLS-1$
Bindings end = (Bindings) bind.get("end"); //$NON-NLS-1$
loc.setEnd(new Position(((Number) end.get("line")).intValue(), //$NON-NLS-1$
((Number) end.get("column")).intValue())); //$NON-NLS-1$
return loc;
}
return new SourceLocation();
} else if (expectedType.isArray()) {
Object arr = Array.newInstance(expectedType.getComponentType(), ((Bindings) value).size());
int ctr = 0;
for (Object obj : ((Bindings) value).values()) {
Array.set(arr, ctr++, handleObject(obj, expectedType.getComponentType()));
}
return arr;
} else if (expectedType.isAssignableFrom(List.class)) {
if (value instanceof Bindings) {
List<Object> list = new ArrayList<>();
for (Bindings object : analyzer.toJavaArray((Bindings) value, Bindings[].class)) {
list.add(QmlASTNodeHandler.createQmlASTProxy(object));
}
return list;
}
return null;
} else if (expectedType.isPrimitive()) {
return handlePrimitive(value, expectedType);
} else if (expectedType.isAssignableFrom(Number.class)) {
if (value instanceof Number) {
return value;
}
return new Integer(0);
} else if (expectedType.isEnum()) {
return expectedType.getMethod(CREATE_ENUM_METHOD, Object.class).invoke(null, value);
} else if (value instanceof Bindings) {
return QmlASTNodeHandler.createQmlASTProxy((Bindings) value, expectedType);
}
return value;
}
private Object handlePrimitive(Object value, Class<?> expectedType) throws Throwable {
if (expectedType.isPrimitive()) {
if (expectedType.equals(Boolean.TYPE)) {
if (value instanceof Boolean) {
return value;
}
return false;
} else if (expectedType.equals(Character.TYPE)) {
if (value instanceof Character) {
return value;
}
return '\0';
} else if (expectedType.equals(Byte.TYPE)) {
if (value instanceof Number) {
return ((Number) value).byteValue();
}
return (byte) 0;
} else if (expectedType.equals(Short.TYPE)) {
if (value instanceof Number) {
return ((Number) value).shortValue();
}
return (short) 0;
} else if (expectedType.equals(Integer.TYPE)) {
if (value instanceof Number) {
return ((Number) value).intValue();
}
return 0;
} else if (expectedType.equals(Long.TYPE)) {
if (value instanceof Number) {
return ((Number) value).longValue();
}
return 0l;
} else if (expectedType.equals(Float.TYPE)) {
if (value instanceof Number) {
return ((Number) value).floatValue();
}
return 0.0f;
} else if (expectedType.equals(Double.TYPE)) {
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
return 0.0d;
}
}
throw new IllegalArgumentException("expectedType was not a primitive type"); //$NON-NLS-1$
}
}

View file

@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright (c) 2015 QNX Software Systems and others.
* 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:
* QNX Software Systems - Initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.qt.core;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import javax.script.Bindings;
import javax.script.ScriptException;
import org.eclipse.cdt.qt.core.qmljs.IQmlASTNode;
public interface IQMLAnalyzer {
void addFile(String fileName, String code) throws NoSuchMethodException, ScriptException;
void deleteFile(String fileName) throws NoSuchMethodException, ScriptException;
IQmlASTNode parseFile(String fileName, String text) throws NoSuchMethodException, ScriptException;
IQmlASTNode parseString(String text) throws NoSuchMethodException, ScriptException;
IQmlASTNode parseString(String text, String mode, boolean locations, boolean ranges)
throws NoSuchMethodException, ScriptException;
Collection<QMLTernCompletion> getCompletions(String fileName, String text, int pos)
throws NoSuchMethodException, ScriptException;
Collection<QMLTernCompletion> getCompletions(String fileName, String text, int pos, boolean includeKeywords)
throws NoSuchMethodException, ScriptException;
List<Bindings> getDefinition(String identifier, String fileName, String text, int pos)
throws NoSuchMethodException, ScriptException;
void load() throws ScriptException, IOException, NoSuchMethodException;
}

View file

@ -32,6 +32,17 @@ public interface IJSAssignmentExpression extends IJSExpression {
AssignExclusiveOr("^"), //$NON-NLS-1$
AssignAnd("&="); //$NON-NLS-1$
public static AssignmentOperator fromObject(Object obj) {
if (obj instanceof String) {
for (AssignmentOperator op : AssignmentOperator.values()) {
if (obj.equals(op.toString())) {
return op;
}
}
}
return null;
}
private final String op;
private AssignmentOperator(String op) {

View file

@ -40,6 +40,17 @@ public interface IJSBinaryExpression extends IJSExpression {
In("in"), //$NON-NLS-1$
Instanceof("instanceof"); //$NON-NLS-1$
public static BinaryOperator fromObject(Object obj) {
if (obj instanceof String) {
for (BinaryOperator op : BinaryOperator.values()) {
if (obj.equals(op.toString())) {
return op;
}
}
}
return null;
}
private final String op;
private BinaryOperator(String op) {
@ -54,7 +65,7 @@ public interface IJSBinaryExpression extends IJSExpression {
@Override
default String getType() {
return "UnaryExpression"; //$NON-NLS-1$
return "BinaryExpression"; //$NON-NLS-1$
}
public BinaryOperator getOperator();

View file

@ -22,6 +22,17 @@ public interface IJSLogicalExpression extends IJSExpression {
Or("||"), //$NON-NLS-1$
And("&&"); //$NON-NLS-1$
public static LogicalOperator fromObject(Object obj) {
if (obj instanceof String) {
for (LogicalOperator op : LogicalOperator.values()) {
if (obj.equals(op.toString())) {
return op;
}
}
}
return null;
}
private final String op;
private LogicalOperator(String op) {

View file

@ -27,6 +27,17 @@ public interface IJSUnaryExpression extends IJSExpression {
Void("void"), //$NON-NLS-1$
Delete("delete"); //$NON-NLS-1$
public static UnaryOperator fromObject(Object obj) {
if (obj instanceof String) {
for (UnaryOperator op : UnaryOperator.values()) {
if (obj.equals(op.toString())) {
return op;
}
}
}
return null;
}
private final String op;
private UnaryOperator(String op) {

View file

@ -22,6 +22,17 @@ public interface IJSUpdateExpression extends IQmlASTNode {
Decrement("--"), //$NON-NLS-1$
Increment("++"); //$NON-NLS-1$
public static UpdateOperator fromObject(Object obj) {
if (obj instanceof String) {
for (UpdateOperator op : UpdateOperator.values()) {
if (obj.equals(op.toString())) {
return op;
}
}
}
return null;
}
private final String op;
private UpdateOperator(String op) {

View file

@ -969,20 +969,20 @@
if (query.file) {
// Get the file's AST. It should have been parsed already by the server.
ast = file.ast;
} else if (query.text) {
} else {
// Parse the file manually and get the AST.
var text = query.text;
var options = query.options || {
var options = {
directSourceFile: file,
allowReturnOutsideFunction: true,
allowImportExportEverywhere: true,
ecmaVersion: srv.options.ecmaVersion
};
srv.signalReturnFirst("preParse", text, options);
try {
ast = acorn.parse(text, options);
} catch (e) {
ast = acorn.parse_dammit(text, options);
for (var opt in query.options) {
options[opt] = query.options[opt];
}
query.text = query.text || "";
var text = srv.signalReturnFirst("preParse", query.text, options) || query.text;
ast = infer.parse(text, options);
srv.signal("postParse", ast, text);
}
return {

View file

@ -29,7 +29,7 @@ test("{Parse existing file}", function (server, callback, name) {
if (err) {
throw err;
}
if (!resp.ast && resp.ast.type === "QMLProgram") {
if (!resp.ast || resp.ast.type !== "QMLProgram") {
return callback("fail", name, "AST could not be found in response");
}
return callback("ok", name);
@ -53,14 +53,33 @@ test("{Parse given file}", function (server, callback, name) {
if (err) {
throw err;
}
if (!resp.ast && resp.ast.type === "QMLProgram") {
if (!resp.ast || resp.ast.type !== "QMLProgram") {
return callback("fail", name, "AST could not be found in response");
}
return callback("ok", name);
});
});
test("{Parse text}", function (server, callback, name) {
test("{Parse empty text}", function (server, callback, name) {
server.request({
query: {
type: "parseString",
text: ""
}
}, function (err, resp) {
if (err) {
throw err;
}
if (!resp.ast) {
return callback("fail", name, "AST could not be found in response");
} else if (resp.ast.type !== "QMLProgram" || resp.ast.mode !== "qml") {
return callback("fail", name, "AST was not a QMLProgram with mode 'qml'");
}
return callback("ok", name);
});
});
test("{Parse text no mode}", function (server, callback, name) {
server.request({
query: {
type: "parseString",
@ -70,8 +89,57 @@ test("{Parse text}", function (server, callback, name) {
if (err) {
throw err;
}
if (!resp.ast && resp.ast.type === "QMLProgram") {
if (!resp.ast) {
return callback("fail", name, "AST could not be found in response");
} else if (resp.ast.type !== "QMLProgram" || resp.ast.mode !== "qml") {
return callback("fail", name, "AST was not a QMLProgram with mode 'qml'");
}
return callback("ok", name);
});
});
test("{Parse text (mode: qmltypes)}", function (server, callback, name) {
server.request({
query: {
type: "parseString",
text: "QtObject {\n\tobj: {\n\t\tprop1: 1,\n\t\tprop2: 2\n\t}\n}",
options: {
mode: "qmltypes"
}
}
}, function (err, resp) {
if (err) {
throw err;
}
if (!resp.ast) {
return callback("fail", name, "AST could not be found in response");
} else if (resp.ast.type !== "QMLProgram" || resp.ast.mode !== "qmltypes") {
return callback("fail", name, "AST was not a QMLProgram with mode 'qmltypes'");
}
return callback("ok", name);
});
});
test("{Parse text with locations}", function (server, callback, name) {
server.request({
query: {
type: "parseString",
text: "var w = 3",
options: {
mode: "js",
locations: true
}
}
}, function (err, resp) {
if (err) {
throw err;
}
if (!resp.ast) {
return callback("fail", name, "AST could not be found in response");
} else if (resp.ast.type !== "Program") {
return callback("fail", name, "AST was not a JavaScript Program");
} else if (!resp.ast.loc) {
return callback("fail", name, "AST had no loc object");
}
return callback("ok", name);
});

View file

@ -13,7 +13,7 @@ import java.util.Collection;
import javax.script.ScriptException;
import org.eclipse.cdt.internal.qt.ui.Activator;
import org.eclipse.cdt.qt.core.QMLAnalyzer;
import org.eclipse.cdt.qt.core.IQMLAnalyzer;
import org.eclipse.cdt.qt.core.QMLTernCompletion;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
@ -29,7 +29,7 @@ public class QMLContentAssistProcessor implements IContentAssistProcessor {
private static final IContextInformation[] NO_CONTEXTS = {};
private static final ICompletionProposal[] NO_COMPLETIONS = {};
private final QMLAnalyzer analyzer = Activator.getService(QMLAnalyzer.class);
private final IQMLAnalyzer analyzer = Activator.getService(IQMLAnalyzer.class);
private final QMLEditor editor;
public QMLContentAssistProcessor(QMLEditor editor) {

View file

@ -16,7 +16,7 @@ import javax.script.ScriptException;
import org.eclipse.cdt.internal.qt.ui.Activator;
import org.eclipse.cdt.internal.qt.ui.actions.OpenDeclarationsAction;
import org.eclipse.cdt.qt.core.QMLAnalyzer;
import org.eclipse.cdt.qt.core.IQMLAnalyzer;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.preference.IPreferenceStore;
@ -41,7 +41,7 @@ public class QMLEditor extends TextEditor {
private static final String BRACKET_MATCHING_PREFERENCE = "org.eclipse.cdt.qt.ui.qmlMatchingBrackets"; //$NON-NLS-1$
private static final char[] BRACKETS = { '{', '}', '(', ')', '[', ']' };
private final QMLAnalyzer analyzer = Activator.getService(QMLAnalyzer.class);
private final IQMLAnalyzer analyzer = Activator.getService(IQMLAnalyzer.class);
@Override
protected void initializeEditor() {

View file

@ -14,7 +14,7 @@ import javax.script.Bindings;
import javax.script.ScriptException;
import org.eclipse.cdt.internal.qt.core.Activator;
import org.eclipse.cdt.qt.core.QMLAnalyzer;
import org.eclipse.cdt.qt.core.IQMLAnalyzer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jface.text.BadLocationException;
@ -57,7 +57,7 @@ public class QMLHyperlink implements IHyperlink {
@Override
public void open() {
QMLAnalyzer analyzer = Activator.getService(QMLAnalyzer.class);
IQMLAnalyzer analyzer = Activator.getService(IQMLAnalyzer.class);
try {
IDocument document = viewer.getDocument();
String selected = document.get(region.getOffset(), region.getLength());

View file

@ -17,7 +17,7 @@ import java.util.List;
import javax.script.ScriptException;
import org.eclipse.cdt.internal.qt.ui.Activator;
import org.eclipse.cdt.qt.core.QMLAnalyzer;
import org.eclipse.cdt.qt.core.IQMLAnalyzer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
@ -30,7 +30,7 @@ import org.eclipse.core.runtime.jobs.Job;
public class QMLTernFileUpdateJob extends Job {
private List<IResourceDelta> deltaList;
private final QMLAnalyzer analyzer = Activator.getService(QMLAnalyzer.class);
private final IQMLAnalyzer analyzer = Activator.getService(IQMLAnalyzer.class);
public QMLTernFileUpdateJob(List<IResourceDelta> deltas) {
super("Add/Remove Files in Tern"); //$NON-NLS-1$

View file

@ -12,7 +12,7 @@ import java.io.IOException;
import javax.script.ScriptException;
import org.eclipse.cdt.internal.qt.core.Activator;
import org.eclipse.cdt.qt.core.QMLAnalyzer;
import org.eclipse.cdt.qt.core.IQMLAnalyzer;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
@ -35,7 +35,7 @@ public class ReloadAnalyzerHandler extends AbstractHandler {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
Activator.getService(QMLAnalyzer.class).load();
Activator.getService(IQMLAnalyzer.class).load();
} catch (NoSuchMethodException | ScriptException | IOException e) {
return Activator.error("Reloading QML Analyzer", e);
}