From a43976b8826cffde7cf801cf7929dab3448c662d Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Wed, 16 Dec 2015 11:38:33 -0500 Subject: [PATCH] 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 --- .../cdt/qt/core/tests/NashornTests.java | 138 +++++++++- .../META-INF/MANIFEST.MF | 3 +- .../acorn-qml/inject.js | 1 + .../acorn-qml/loose/inject.js | 1 + .../cdt/internal/qt/core/Activator.java | 6 +- .../{ => internal}/qt/core/QMLAnalyzer.java | 142 ++++++++-- .../internal/qt/core/QmlASTNodeHandler.java | 250 ++++++++++++++++++ .../org/eclipse/cdt/qt/core/IQMLAnalyzer.java | 46 ++++ .../core/qmljs/IJSAssignmentExpression.java | 11 + .../qt/core/qmljs/IJSBinaryExpression.java | 13 +- .../qt/core/qmljs/IJSLogicalExpression.java | 11 + .../cdt/qt/core/qmljs/IJSUnaryExpression.java | 11 + .../qt/core/qmljs/IJSUpdateExpression.java | 11 + qt/org.eclipse.cdt.qt.core/tern-qml/qml.js | 16 +- .../tern-qml/test/test-parse.js | 76 +++++- .../ui/editor/QMLContentAssistProcessor.java | 4 +- .../cdt/internal/qt/ui/editor/QMLEditor.java | 4 +- .../internal/qt/ui/editor/QMLHyperlink.java | 4 +- .../qt/ui/resources/QMLTernFileUpdateJob.java | 4 +- .../qt/ui/handlers/ReloadAnalyzerHandler.java | 6 +- 20 files changed, 695 insertions(+), 63 deletions(-) rename qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/{ => internal}/qt/core/QMLAnalyzer.java (64%) create mode 100644 qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QmlASTNodeHandler.java create mode 100644 qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/IQMLAnalyzer.java diff --git a/qt/org.eclipse.cdt.qt.core.tests/src/org/eclipse/cdt/qt/core/tests/NashornTests.java b/qt/org.eclipse.cdt.qt.core.tests/src/org/eclipse/cdt/qt/core/tests/NashornTests.java index 53e61490b2a..f9a95f9b810 100644 --- a/qt/org.eclipse.cdt.qt.core.tests/src/org/eclipse/cdt/qt/core/tests/NashornTests.java +++ b/qt/org.eclipse.cdt.qt.core.tests/src/org/eclipse/cdt/qt/core/tests/NashornTests.java @@ -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 QMLTernCompletions = analyzer.getCompletions("test1.qml", code, pos); + Collection QMLTernCompletions = analyzer.getCompletions("test1.qml", code, pos, false); Map set = new HashMap<>(); Set 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 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 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 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 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 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 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; } diff --git a/qt/org.eclipse.cdt.qt.core/META-INF/MANIFEST.MF b/qt/org.eclipse.cdt.qt.core/META-INF/MANIFEST.MF index 9660440da67..ac5d70d56a1 100644 --- a/qt/org.eclipse.cdt.qt.core/META-INF/MANIFEST.MF +++ b/qt/org.eclipse.cdt.qt.core/META-INF/MANIFEST.MF @@ -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 diff --git a/qt/org.eclipse.cdt.qt.core/acorn-qml/inject.js b/qt/org.eclipse.cdt.qt.core/acorn-qml/inject.js index 1533e619851..b16dba70421 100644 --- a/qt/org.eclipse.cdt.qt.core/acorn-qml/inject.js +++ b/qt/org.eclipse.cdt.qt.core/acorn-qml/inject.js @@ -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) { diff --git a/qt/org.eclipse.cdt.qt.core/acorn-qml/loose/inject.js b/qt/org.eclipse.cdt.qt.core/acorn-qml/loose/inject.js index ee3eff58b7f..0c2e0425834 100644 --- a/qt/org.eclipse.cdt.qt.core/acorn-qml/loose/inject.js +++ b/qt/org.eclipse.cdt.qt.core/acorn-qml/loose/inject.js @@ -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) { diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/Activator.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/Activator.java index df0e1a592a0..2c3f989c5bc 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/Activator.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/Activator.java @@ -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 { diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QMLAnalyzer.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QMLAnalyzer.java similarity index 64% rename from qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QMLAnalyzer.java rename to qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QMLAnalyzer.java index 3aca52cfa9a..cb30a069c25 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QMLAnalyzer.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QMLAnalyzer.java @@ -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[] toJavaArray(Bindings binding, Class clazz) throws NoSuchMethodException, ScriptException { + return clazz.cast(invoke.invokeMethod(engine.get("Java"), "to", binding, + clazz.getCanonicalName() + (clazz.isArray() ? "" : "[]"))); + } + + @Override public Collection getCompletions(String fileName, String text, int pos) throws NoSuchMethodException, ScriptException { + return getCompletions(fileName, text, pos, true); + } + + @Override + public Collection 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 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 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 definitions = new ArrayList<>(); diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QmlASTNodeHandler.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QmlASTNodeHandler.java new file mode 100644 index 00000000000..c59163b8385 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QmlASTNodeHandler.java @@ -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. + *

+ * To begin translation simply call the static method createQmlASTProxy. 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 + * createQmlASTProxy(node, null) + * + * @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 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 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 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$ + } +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/IQMLAnalyzer.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/IQMLAnalyzer.java new file mode 100644 index 00000000000..6c8343f110f --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/IQMLAnalyzer.java @@ -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 getCompletions(String fileName, String text, int pos) + throws NoSuchMethodException, ScriptException; + + Collection getCompletions(String fileName, String text, int pos, boolean includeKeywords) + throws NoSuchMethodException, ScriptException; + + List getDefinition(String identifier, String fileName, String text, int pos) + throws NoSuchMethodException, ScriptException; + + void load() throws ScriptException, IOException, NoSuchMethodException; + +} \ No newline at end of file diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSAssignmentExpression.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSAssignmentExpression.java index d07c0ef80d2..0bd47ba92be 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSAssignmentExpression.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSAssignmentExpression.java @@ -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) { diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSBinaryExpression.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSBinaryExpression.java index 43790d77d59..37958a8ce76 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSBinaryExpression.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSBinaryExpression.java @@ -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(); diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSLogicalExpression.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSLogicalExpression.java index de224729607..b79c83caddf 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSLogicalExpression.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSLogicalExpression.java @@ -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) { diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSUnaryExpression.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSUnaryExpression.java index 6fe2c021b12..389a2a7f8bc 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSUnaryExpression.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSUnaryExpression.java @@ -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) { diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSUpdateExpression.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSUpdateExpression.java index cddb8461a3e..3a771f7fdc7 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSUpdateExpression.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/qmljs/IJSUpdateExpression.java @@ -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) { diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js b/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js index 63bfd5d1b9d..fec8f8538c6 100644 --- a/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js @@ -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 { diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-parse.js b/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-parse.js index c4585d8e365..ae6ecd9367d 100644 --- a/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-parse.js +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-parse.js @@ -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); }); diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLContentAssistProcessor.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLContentAssistProcessor.java index a7de25a2720..6e1e79794a2 100644 --- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLContentAssistProcessor.java +++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLContentAssistProcessor.java @@ -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) { diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLEditor.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLEditor.java index 7339ca15618..1be6776a615 100644 --- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLEditor.java +++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLEditor.java @@ -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() { diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLHyperlink.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLHyperlink.java index d81873aac0e..c74b170f9d3 100644 --- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLHyperlink.java +++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLHyperlink.java @@ -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()); diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QMLTernFileUpdateJob.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QMLTernFileUpdateJob.java index ad9644c9f2d..8c97f80dcc8 100644 --- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QMLTernFileUpdateJob.java +++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QMLTernFileUpdateJob.java @@ -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 deltaList; - private final QMLAnalyzer analyzer = Activator.getService(QMLAnalyzer.class); + private final IQMLAnalyzer analyzer = Activator.getService(IQMLAnalyzer.class); public QMLTernFileUpdateJob(List deltas) { super("Add/Remove Files in Tern"); //$NON-NLS-1$ diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/qt/ui/handlers/ReloadAnalyzerHandler.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/qt/ui/handlers/ReloadAnalyzerHandler.java index 78b4d8c036f..5e394a782bb 100644 --- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/qt/ui/handlers/ReloadAnalyzerHandler.java +++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/qt/ui/handlers/ReloadAnalyzerHandler.java @@ -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; @@ -23,7 +23,7 @@ import org.eclipse.core.runtime.jobs.Job; /** * Our sample handler extends AbstractHandler, an IHandler base class. - * + * * @see org.eclipse.core.commands.IHandler * @see org.eclipse.core.commands.AbstractHandler */ @@ -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); }