1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-04-23 14:42:11 +02:00

Bug 481126 - Walk QML Types AST

Added several classes that can be used to aggregate all of the
information from within a .qmltypes file as defined by
http://doc.qt.io/qt-5/qtqml-modules-qmldir.html#writing-a-qmltypes-file

Change-Id: Ia36c3bb1a4a0254c4125733d6faabbb5a4606133
Signed-off-by: Matthew Bastien <mbastien@blackberry.com>
This commit is contained in:
Matthew Bastien 2015-12-18 13:55:56 -05:00 committed by Gerrit Code Review @ Eclipse.org
parent c68dc46f54
commit 0027b29aba
21 changed files with 1254 additions and 95 deletions

View file

@ -615,7 +615,8 @@
throw new Error("QML only supports ECMA Script Language Specification 5 or older");
}
if (this.options.mode === "qml" || this.options.mode === "qmltypes") {
// Disabled 'qmltypes' mode for now since the normal parser can't parse it anyway
if (this.options.mode === "qml") {
// Force strict mode
this.strict = true;

View file

@ -55,18 +55,6 @@ var stats, modes = {
}
}
},
"Normal QMLTypes": {
config: {
parse: acorn.parse,
options: {
mode: "qmltypes"
},
filter: function (test) {
var opts = test.options || {};
return opts.normal !== false && opts.qmltypes !== false;
}
}
},
"Loose QMLTypes": {
config: {
parse: acorn.parse_dammit,

View file

@ -34,11 +34,14 @@ import org.eclipse.cdt.qt.core.qmljs.IQmlASTNode;
@SuppressWarnings("nls")
public class QMLAnalyzer implements IQMLAnalyzer {
private QMLModuleResolver moduleResolver;
private ScriptEngine engine;
private Invocable invoke;
private Object tern;
@Override
public void load() throws ScriptException, IOException, NoSuchMethodException {
moduleResolver = new QMLModuleResolver(this);
engine = new ScriptEngineManager().getEngineByName("nashorn");
invoke = (Invocable) engine;
@ -88,6 +91,7 @@ public class QMLAnalyzer implements IQMLAnalyzer {
return fixPathString(path.normalize().toString());
};
options.put("resolveDirectory", invoke.invokeFunction("resolveDirectory", resolveDirectory));
options.put("resolveModule", invoke.invokeFunction("resolveModule", moduleResolver));
synchronized (this) {
tern = invoke.invokeFunction("newTernServer", options);
@ -203,19 +207,13 @@ public class QMLAnalyzer implements IQMLAnalyzer {
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));
invoke.invokeMethod(tern, "parseString", text, options, invoke.invokeFunction("requestCallback", callback));
return callback.getAST();
}

View file

@ -0,0 +1,99 @@
/*******************************************************************************
* 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.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import javax.script.ScriptException;
import org.eclipse.cdt.internal.qt.core.qmltypes.QMLModelBuilder;
import org.eclipse.cdt.internal.qt.core.qmltypes.QMLModuleInfo;
import org.eclipse.cdt.qt.core.IQtInstall;
import org.eclipse.cdt.qt.core.IQtInstallManager;
import org.eclipse.cdt.qt.core.qmldir.QMLDirectoryInfo;
import org.eclipse.cdt.qt.core.qmljs.IQmlASTNode;
public class QMLModuleResolver {
private final QMLAnalyzer analyzer;
private final IQtInstallManager manager;
private final QMLModelBuilder builder;
public QMLModuleResolver(QMLAnalyzer analyzer) {
this.analyzer = analyzer;
this.manager = Activator.getService(IQtInstallManager.class);
this.builder = new QMLModelBuilder();
}
// TODO: determine exactly how to give this to Tern. For now we'll just return the reference to the QMLModuleInfo
// that we found
public QMLModuleInfo resolveModule(String module) throws NoSuchMethodException, ScriptException {
QMLModuleInfo info = builder.getModule(module);
if (info == null) {
Path path = getModulePath(module);
if (path != null) {
File qmldir = path.resolve("qmldir").normalize().toFile(); //$NON-NLS-1$
try {
String types = getQmlTypesFile(qmldir);
File qmlTypes = path.resolve(types).toFile();
String typeContents = fileToString(qmlTypes);
IQmlASTNode ast = analyzer.parseString(typeContents, "qmltypes", false, false); //$NON-NLS-1$
info = builder.addModule(module, ast);
} catch (IOException e) {
Activator.log(e);
}
}
}
return info;
}
private String fileToString(File file) throws IOException {
try (InputStream stream = new FileInputStream(file)) {
StringBuilder sb = new StringBuilder();
int read = -1;
while ((read = stream.read()) != -1) {
sb.append((char) read);
}
return sb.toString();
}
}
private String getQmlTypesFile(File qmldir) throws IOException {
try (InputStream stream = new FileInputStream(qmldir)) {
QMLDirectoryInfo info = new QMLDirectoryInfo(stream);
return info.getTypesFileName();
}
}
private Path getModulePath(String module) {
if (module != null) {
for (IQtInstall install : manager.getInstalls()) {
Path qmlPath = install.getQmlPath();
Path modPath = null;
if (module.equals("QtQuick")) { //$NON-NLS-1$
modPath = qmlPath.resolve("QtQuick.2").normalize(); //$NON-NLS-1$
} else {
modPath = qmlPath;
for (String part : module.split("\\.")) { //$NON-NLS-1$
modPath = modPath.resolve(part).normalize();
}
}
if (modPath.toFile().exists()) {
return modPath;
}
}
}
return null;
}
}

View file

@ -150,11 +150,20 @@ public class QmlASTNodeHandler implements InvocationHandler {
methodResults.put(mName, handleObject(node.get(pName), method.getReturnType()));
}
}
return methodResults.get(method.getName());
return methodResults.get(mName);
}
private Object handleObject(Object value, Class<?> expectedType) throws Throwable {
if (expectedType.isAssignableFrom(ISourceLocation.class)) {
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.equals(Object.class)) {
return value;
} else 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) {
@ -170,13 +179,6 @@ public class QmlASTNodeHandler implements InvocationHandler {
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<>();

View file

@ -40,6 +40,11 @@ public class QtInstall implements IQtInstall {
return qmakePath.resolve("../lib"); //$NON-NLS-1$
}
@Override
public Path getQmlPath() {
return qmakePath.resolve("../../qml"); //$NON-NLS-1$
}
public static String getSpec(String qmakePath) throws IOException {
Process proc = new ProcessBuilder(qmakePath, "-query", "QMAKE_XSPEC").start(); //$NON-NLS-1$ //$NON-NLS-2$
try (BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {

View file

@ -0,0 +1,139 @@
/*******************************************************************************
* 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.qmltypes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectDefinition;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectMember;
import org.eclipse.cdt.qt.core.qmljs.IQmlPropertyBinding;
public class QMLComponentInfo {
static final String IDENTIFIER = "Component"; //$NON-NLS-1$
static final String PROPERTY_NAME = "name"; //$NON-NLS-1$
static final String PROPERTY_PROTOTYPE = "prototype"; //$NON-NLS-1$
static final String PROPERTY_DEF_PROPERTY = "defaultProperty"; //$NON-NLS-1$
static final String PROPERTY_ATTACHED_TYPE = "attachedType"; //$NON-NLS-1$
static final String PROPERTY_EXPORTS = "exports"; //$NON-NLS-1$
static final String PROPERTY_EXPORT_REVISIONS = "exportMetaObjectRevisions"; //$NON-NLS-1$
private String name;
private String prototype;
private String defaultProperty;
private String attachedType;
private Integer[] exportMetaObjectRevisions;
private List<QMLExportInfo> exportList = new ArrayList<>();
private List<QMLPropertyInfo> propertyList = new ArrayList<>();
private List<QMLMethodInfo> methodList = new ArrayList<>();
private List<QMLSignalInfo> signalList = new ArrayList<>();
private List<QMLEnumInfo> enumList = new ArrayList<>();
protected QMLComponentInfo(QMLModelBuilder builder, IQmlObjectDefinition obj) {
builder.ensureIdentifier(obj.getIdentifier(), IDENTIFIER);
for (IQmlObjectMember member : obj.getBody().getMembers()) {
if (member instanceof IQmlPropertyBinding) {
IQmlPropertyBinding prop = (IQmlPropertyBinding) member;
switch (prop.getIdentifier().getName()) {
case PROPERTY_NAME:
this.name = builder.getStringBinding(prop);
break;
case PROPERTY_PROTOTYPE:
this.prototype = builder.getStringBinding(prop);
break;
case PROPERTY_DEF_PROPERTY:
this.defaultProperty = builder.getStringBinding(prop);
break;
case PROPERTY_ATTACHED_TYPE:
this.attachedType = builder.getStringBinding(prop);
break;
case PROPERTY_EXPORTS:
String[] exports = builder.getStringArrayBinding(prop);
for (String exp : exports) {
this.exportList.add(new QMLExportInfo(builder, exp));
}
break;
case PROPERTY_EXPORT_REVISIONS:
this.exportMetaObjectRevisions = builder.getIntegerArrayBinding(prop);
break;
default:
}
} else if (member instanceof IQmlObjectDefinition) {
IQmlObjectDefinition object = (IQmlObjectDefinition) member;
switch (object.getIdentifier().getName()) {
case QMLPropertyInfo.IDENTIFIER:
this.propertyList.add(new QMLPropertyInfo(builder, object));
break;
case QMLMethodInfo.IDENTIFIER:
this.methodList.add(new QMLMethodInfo(builder, object));
break;
case QMLSignalInfo.IDENTIFIER:
this.signalList.add(new QMLSignalInfo(builder, object));
break;
case QMLEnumInfo.IDENTIFIER:
this.enumList.add(new QMLEnumInfo(builder, object));
break;
default:
}
} else {
builder.unexpectedNode(member);
}
}
exportList = Collections.unmodifiableList(exportList);
propertyList = Collections.unmodifiableList(propertyList);
methodList = Collections.unmodifiableList(methodList);
signalList = Collections.unmodifiableList(signalList);
enumList = Collections.unmodifiableList(enumList);
}
public String getName() {
return name;
}
public String getPrototype() {
return prototype;
}
public String getDefaultProperty() {
return defaultProperty;
}
public String getAttachedType() {
return attachedType;
}
public List<QMLExportInfo> getExports() {
return exportList;
}
public Integer[] getExportMetaObjectRevisions() {
return Arrays.copyOf(exportMetaObjectRevisions, exportMetaObjectRevisions.length);
}
public List<QMLPropertyInfo> getProperties() {
return propertyList;
}
public List<QMLMethodInfo> getMethods() {
return methodList;
}
public List<QMLSignalInfo> getSignals() {
return signalList;
}
public List<QMLEnumInfo> getEnums() {
return enumList;
}
}

View file

@ -0,0 +1,96 @@
/*******************************************************************************
* 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.qmltypes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.cdt.qt.core.qmljs.IJSObjectExpression;
import org.eclipse.cdt.qt.core.qmljs.IJSProperty;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectDefinition;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectMember;
import org.eclipse.cdt.qt.core.qmljs.IQmlPropertyBinding;
import org.eclipse.cdt.qt.core.qmljs.IQmlScriptBinding;
import org.eclipse.cdt.qt.core.qmljs.QMLExpressionEvaluator;
import org.eclipse.cdt.qt.core.qmljs.QMLExpressionEvaluator.InvalidExpressionException;
public class QMLEnumInfo {
public static class EnumConst {
private final String identifier;
private final int value;
private EnumConst(String ident, int val) {
this.identifier = ident;
this.value = val;
}
public String getIdentifier() {
return identifier;
}
public int getValue() {
return value;
}
}
static final String IDENTIFIER = "Enum"; //$NON-NLS-1$
static final String PROPERTY_NAME = "name"; //$NON-NLS-1$
static final String PROPERTY_VALUE = "values"; //$NON-NLS-1$
private String name;
private List<EnumConst> constantList = new ArrayList<>();
QMLEnumInfo(QMLModelBuilder builder, IQmlObjectDefinition obj) {
if (builder.ensureIdentifier(obj.getIdentifier(), IDENTIFIER)) {
for (IQmlObjectMember member : obj.getBody().getMembers()) {
if (builder.ensureNode(member, IQmlPropertyBinding.class)) {
IQmlPropertyBinding prop = (IQmlPropertyBinding) member;
switch (prop.getIdentifier().getName()) {
case PROPERTY_NAME:
this.name = builder.getStringBinding(prop);
break;
case PROPERTY_VALUE:
if (builder.ensureNode(prop.getBinding(), IQmlScriptBinding.class)) {
IQmlScriptBinding binding = ((IQmlScriptBinding) prop.getBinding());
if (builder.ensureNode(binding.getScript(), IJSObjectExpression.class)) {
IJSObjectExpression objExpr = (IJSObjectExpression) binding.getScript();
for (IJSProperty property : objExpr.getProperties()) {
Object value;
try {
value = QMLExpressionEvaluator.evaluateConstExpr(property.getValue());
if (value instanceof Number) {
constantList.add(new EnumConst(property.getType(), ((Number) value).intValue()));
}
} catch (InvalidExpressionException e) {
builder.handleException(e);
}
}
}
}
break;
default:
}
}
}
}
constantList = Collections.unmodifiableList(constantList);
}
public String getName() {
return name;
}
public List<EnumConst> getConstants() {
return constantList;
}
}

View file

@ -0,0 +1,37 @@
/*******************************************************************************
* 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.qmltypes;
public class QMLExportInfo {
private String type;
private String version;
QMLExportInfo(QMLModelBuilder builder, String export) {
String[] info = export.split("\\h+"); //$NON-NLS-1$
switch (info.length) {
case 2:
this.type = info[0];
this.version = info[1];
break;
case 1:
this.type = info[0];
break;
}
}
public String getType() {
return type;
}
public String getVersion() {
return version;
}
}

View file

@ -0,0 +1,69 @@
/*******************************************************************************
* 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.qmltypes;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectDefinition;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectMember;
import org.eclipse.cdt.qt.core.qmljs.IQmlPropertyBinding;
public class QMLMethodInfo {
static final String IDENTIFIER = "Method"; //$NON-NLS-1$
static final String PROPERTY_NAME = "name"; //$NON-NLS-1$ s
static final String PROPERTY_TYPE = "type"; //$NON-NLS-1$
static final String PROPERTY_REVISION = "revision"; //$NON-NLS-1$
private String name;
private String type;
private int revision;
private List<QMLParameterInfo> parameterList = new ArrayList<>();
QMLMethodInfo(QMLModelBuilder builder, IQmlObjectDefinition obj) {
if (builder.ensureIdentifier(obj.getIdentifier(), IDENTIFIER)) {
for (IQmlObjectMember member : obj.getBody().getMembers()) {
if (member instanceof IQmlPropertyBinding) {
IQmlPropertyBinding prop = (IQmlPropertyBinding) member;
switch (prop.getIdentifier().getName()) {
case PROPERTY_NAME:
this.name = builder.getStringBinding(prop);
break;
case PROPERTY_TYPE:
this.type = builder.getStringBinding(prop);
break;
case PROPERTY_REVISION:
this.revision = builder.getIntegerBinding(prop);
break;
default:
}
} else if (member instanceof IQmlObjectDefinition) {
this.parameterList.add(new QMLParameterInfo(builder, (IQmlObjectDefinition) member));
} else {
builder.unexpectedNode(member);
}
}
}
}
public String getName() {
return name;
}
public String getType() {
return type;
}
public int getRevision() {
return revision;
}
}

View file

@ -0,0 +1,191 @@
/*******************************************************************************
* 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.qmltypes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.cdt.internal.qt.core.Activator;
import org.eclipse.cdt.qt.core.qmljs.IJSArrayExpression;
import org.eclipse.cdt.qt.core.qmljs.IJSExpression;
import org.eclipse.cdt.qt.core.qmljs.IQmlASTNode;
import org.eclipse.cdt.qt.core.qmljs.IQmlBinding;
import org.eclipse.cdt.qt.core.qmljs.IQmlProgram;
import org.eclipse.cdt.qt.core.qmljs.IQmlPropertyBinding;
import org.eclipse.cdt.qt.core.qmljs.IQmlQualifiedID;
import org.eclipse.cdt.qt.core.qmljs.IQmlRootObject;
import org.eclipse.cdt.qt.core.qmljs.IQmlScriptBinding;
import org.eclipse.cdt.qt.core.qmljs.QMLExpressionEvaluator;
import org.eclipse.cdt.qt.core.qmljs.QMLExpressionEvaluator.InvalidExpressionException;
public class QMLModelBuilder {
private final Map<String, QMLModuleInfo> moduleMap = new HashMap<>();
public QMLModelBuilder() {
}
public QMLModuleInfo addModule(String module, IQmlASTNode ast) {
QMLModuleInfo info = moduleMap.get(module);
if (!moduleMap.containsKey(module)) {
if (ensureNode(ast, IQmlProgram.class)) {
IQmlRootObject obj = ((IQmlProgram) ast).getRootObject();
if (ensureNode(obj, IQmlRootObject.class)) {
info = new QMLModuleInfo(this, obj);
moduleMap.put(module, info);
}
}
}
return info;
}
public QMLModuleInfo getModule(String module) {
return moduleMap.get(module);
}
public boolean hasModule(String module) {
return moduleMap.get(module) != null;
}
boolean ensureIdentifier(IQmlQualifiedID actual, String expected) {
if (!actual.getName().equals(expected)) {
Activator.log("[QmlTypes] Unexpected node identifier: expected '" + expected + "', but was '" //$NON-NLS-1$ //$NON-NLS-2$
+ actual.getName() + "'"); //$NON-NLS-1$
return false;
}
return true;
}
boolean ensureNode(IQmlASTNode actual, Class<? extends IQmlASTNode> expected) {
if (!expected.isInstance(actual)) {
Activator.log("[QmlTypes] Expected node '" + expected + "', but was '" + actual.getClass().getInterfaces()[0] + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
return false;
}
return true;
}
boolean ensureValue(Object actual, Class<?> expected) {
if (!expected.isInstance(actual)) {
Activator.log("[QmlTypes] Unexpected value: expected '" + expected + "', but was '" //$NON-NLS-1$ //$NON-NLS-2$
+ actual.getClass().getInterfaces()[0] + "'"); //$NON-NLS-1$
return false;
}
return true;
}
void unexpectedNode(IQmlASTNode node) {
Activator.log("[QmlTypes] Unexpected node '" + node.getClass().getInterfaces()[0] + "'"); //$NON-NLS-1$ //$NON-NLS-2$
}
String getStringBinding(IQmlPropertyBinding prop) {
IQmlBinding b = prop.getBinding();
if (ensureNode(b, IQmlScriptBinding.class)) {
IQmlScriptBinding sb = (IQmlScriptBinding) b;
if (ensureNode(sb.getScript(), IJSExpression.class)) {
try {
Object value = QMLExpressionEvaluator.evaluateConstExpr((IJSExpression) sb.getScript());
if (value instanceof String) {
return (String) value;
}
} catch (InvalidExpressionException e) {
handleException(e);
}
}
}
return null;
}
String[] getStringArrayBinding(IQmlPropertyBinding prop) {
ArrayList<String> result = new ArrayList<>();
IQmlBinding b = prop.getBinding();
if (ensureNode(b, IQmlScriptBinding.class)) {
IQmlScriptBinding sb = (IQmlScriptBinding) b;
if (ensureNode(sb.getScript(), IJSArrayExpression.class)) {
IJSArrayExpression arrExpr = (IJSArrayExpression) sb.getScript();
for (IJSExpression expr : arrExpr.getElements()) {
try {
Object value = QMLExpressionEvaluator.evaluateConstExpr(expr);
if (value instanceof String) {
result.add((String) value);
}
} catch (InvalidExpressionException e) {
handleException(e);
}
}
}
}
return result.toArray(new String[result.size()]);
}
public Integer[] getIntegerArrayBinding(IQmlPropertyBinding prop) {
ArrayList<Integer> result = new ArrayList<>();
IQmlBinding b = prop.getBinding();
if (ensureNode(b, IQmlScriptBinding.class)) {
IQmlScriptBinding sb = (IQmlScriptBinding) b;
if (ensureNode(sb.getScript(), IJSArrayExpression.class)) {
IJSArrayExpression arrExpr = (IJSArrayExpression) sb.getScript();
for (IJSExpression expr : arrExpr.getElements()) {
try {
Object value = QMLExpressionEvaluator.evaluateConstExpr(expr);
if (value instanceof Number) {
result.add(((Number) value).intValue());
}
} catch (InvalidExpressionException e) {
handleException(e);
}
}
}
}
return result.toArray(new Integer[result.size()]);
}
boolean getBooleanBinding(IQmlPropertyBinding prop) {
IQmlBinding b = prop.getBinding();
if (ensureNode(b, IQmlScriptBinding.class)) {
IQmlScriptBinding sb = (IQmlScriptBinding) b;
if (ensureNode(sb.getScript(), IJSExpression.class)) {
try {
Object value = QMLExpressionEvaluator.evaluateConstExpr((IJSExpression) sb.getScript());
if (value instanceof Number) {
return (Boolean) value;
}
} catch (InvalidExpressionException e) {
handleException(e);
}
}
}
return false;
}
public Integer getIntegerBinding(IQmlPropertyBinding prop) {
IQmlBinding b = prop.getBinding();
if (ensureNode(b, IQmlScriptBinding.class)) {
IQmlScriptBinding sb = (IQmlScriptBinding) b;
if (ensureNode(sb.getScript(), IJSExpression.class)) {
try {
Object value = QMLExpressionEvaluator.evaluateConstExpr((IJSExpression) sb.getScript());
if (value instanceof Number) {
return ((Number) value).intValue();
}
} catch (InvalidExpressionException e) {
handleException(e);
}
}
}
return 0;
}
public void handleException(Throwable t) {
Activator.log("[QmlTypes] " + t.getMessage()); //$NON-NLS-1$
}
}

View file

@ -0,0 +1,40 @@
/*******************************************************************************
* 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.qmltypes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectDefinition;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectMember;
import org.eclipse.cdt.qt.core.qmljs.IQmlRootObject;
public class QMLModuleInfo {
static final String IDENTIFIER = "Module"; //$NON-NLS-1$
private List<QMLComponentInfo> componentsList = new ArrayList<>();
QMLModuleInfo(QMLModelBuilder builder, IQmlRootObject obj) {
if (builder.ensureIdentifier(obj.getIdentifier(), IDENTIFIER)) {
for (IQmlObjectMember member : obj.getBody().getMembers()) {
if (builder.ensureNode(member, IQmlObjectDefinition.class)) {
componentsList.add(new QMLComponentInfo(builder, (IQmlObjectDefinition) member));
}
}
}
componentsList = Collections.unmodifiableList(componentsList);
}
public List<QMLComponentInfo> getComponents() {
return componentsList;
}
}

View file

@ -0,0 +1,52 @@
/*******************************************************************************
* 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.qmltypes;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectDefinition;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectMember;
import org.eclipse.cdt.qt.core.qmljs.IQmlPropertyBinding;
public class QMLParameterInfo {
static final String IDENTIFIER = "Parameter"; //$NON-NLS-1$
static final String PROPERTY_NAME = "name"; //$NON-NLS-1$
static final String PROPERTY_TYPE = "type"; //$NON-NLS-1$
private String name;
private String type;
QMLParameterInfo(QMLModelBuilder builder, IQmlObjectDefinition obj) {
if (builder.ensureIdentifier(obj.getIdentifier(), IDENTIFIER)) {
for (IQmlObjectMember member : obj.getBody().getMembers()) {
if (builder.ensureNode(member, IQmlPropertyBinding.class)) {
IQmlPropertyBinding prop = (IQmlPropertyBinding) member;
switch (prop.getIdentifier().getName()) {
case PROPERTY_NAME:
this.name = builder.getStringBinding(prop);
break;
case PROPERTY_TYPE:
this.type = builder.getStringBinding(prop);
break;
default:
}
}
}
}
}
public String getName() {
return name;
}
public String getType() {
return type;
}
}

View file

@ -0,0 +1,88 @@
/*******************************************************************************
* 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.qmltypes;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectDefinition;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectMember;
import org.eclipse.cdt.qt.core.qmljs.IQmlPropertyBinding;
public class QMLPropertyInfo {
static final String IDENTIFIER = "Property"; //$NON-NLS-1$
static final String PROPERTY_NAME = "name"; //$NON-NLS-1$
static final String PROPERTY_TYPE = "type"; //$NON-NLS-1$
static final String PROPERTY_READONLY = "isReadonly"; //$NON-NLS-1$
static final String PROPERTY_POINTER = "isPointer"; //$NON-NLS-1$
static final String PROPERTY_LIST = "isList"; //$NON-NLS-1$
static final String PROPERTY_REVISION = "revision"; //$NON-NLS-1$
private String name;
private String type;
private boolean readonly = false;
private boolean pointer = false;
private boolean list = false;
private int revision;
QMLPropertyInfo(QMLModelBuilder builder, IQmlObjectDefinition obj) {
if (builder.ensureIdentifier(obj.getIdentifier(), IDENTIFIER)) {
for (IQmlObjectMember member : obj.getBody().getMembers()) {
if (builder.ensureNode(member, IQmlPropertyBinding.class)) {
IQmlPropertyBinding prop = (IQmlPropertyBinding) member;
switch (prop.getIdentifier().getName()) {
case PROPERTY_NAME:
this.name = builder.getStringBinding(prop);
break;
case PROPERTY_TYPE:
this.type = builder.getStringBinding(prop);
break;
case PROPERTY_READONLY:
this.readonly = builder.getBooleanBinding(prop);
break;
case PROPERTY_POINTER:
this.pointer = builder.getBooleanBinding(prop);
break;
case PROPERTY_LIST:
this.list = builder.getBooleanBinding(prop);
break;
case PROPERTY_REVISION:
this.revision = builder.getIntegerBinding(prop);
break;
default:
}
}
}
}
}
public String getName() {
return name;
}
public String getType() {
return type;
}
public int getRevision() {
return revision;
}
public boolean isReadonly() {
return readonly;
}
public boolean isPointer() {
return pointer;
}
public boolean isList() {
return list;
}
}

View file

@ -0,0 +1,69 @@
/*******************************************************************************
* 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.qmltypes;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectDefinition;
import org.eclipse.cdt.qt.core.qmljs.IQmlObjectMember;
import org.eclipse.cdt.qt.core.qmljs.IQmlPropertyBinding;
public class QMLSignalInfo {
static final String IDENTIFIER = "Signal"; //$NON-NLS-1$
static final String PROPERTY_NAME = "name"; //$NON-NLS-1$ s
static final String PROPERTY_TYPE = "type"; //$NON-NLS-1$
static final String PROPERTY_REVISION = "revision"; //$NON-NLS-1$
private String name;
private String type;
private Integer revision;
private List<QMLParameterInfo> parameterList = new ArrayList<>();
QMLSignalInfo(QMLModelBuilder builder, IQmlObjectDefinition obj) {
if (builder.ensureIdentifier(obj.getIdentifier(), IDENTIFIER)) {
for (IQmlObjectMember member : obj.getBody().getMembers()) {
if (member instanceof IQmlPropertyBinding) {
IQmlPropertyBinding prop = (IQmlPropertyBinding) member;
switch (prop.getIdentifier().getName()) {
case PROPERTY_NAME:
this.name = builder.getStringBinding(prop);
break;
case PROPERTY_TYPE:
this.type = builder.getStringBinding(prop);
break;
case PROPERTY_REVISION:
this.revision = builder.getIntegerBinding(prop);
break;
default:
}
} else if (member instanceof IQmlObjectDefinition) {
this.parameterList.add(new QMLParameterInfo(builder, (IQmlObjectDefinition) member));
} else {
builder.unexpectedNode(member);
}
}
}
}
public String getName() {
return name;
}
public String getType() {
return type;
}
public int getRevision() {
return revision;
}
}

View file

@ -25,4 +25,6 @@ public interface IQtInstall {
Path getLibPath();
Path getQmlPath();
}

View file

@ -0,0 +1,180 @@
/*******************************************************************************
* 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.qmldir;
import java.io.InputStream;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import org.eclipse.cdt.internal.qt.core.Activator;
public class QMLDirectoryInfo {
public static class Module {
private final String name;
private final String initialVersion;
public Module(String name, String ver) {
this.name = name;
this.initialVersion = ver;
}
public String getName() {
return name;
}
public String getInitialVersion() {
return initialVersion;
}
}
public static class Plugin {
private final String name;
private final Path path;
private Plugin(String name, String path) {
this.name = name;
Path p = null;
if (path != null) {
try {
p = Paths.get(path);
} catch (InvalidPathException e) {
Activator.log(e);
}
}
this.path = p;
}
public String getName() {
return name;
}
public Path getRelativePath() {
return path;
}
}
public static class ResourceFile {
private final String name;
private final boolean internal;
private final boolean singleton;
private final String initialVersion;
private ResourceFile(String name, String ver, boolean internal, boolean singleton) {
this.name = name;
this.initialVersion = ver;
this.internal = internal;
this.singleton = singleton;
}
public String getName() {
return name;
}
public String getInitialVersion() {
return initialVersion;
}
public boolean isSingleton() {
return singleton;
}
public boolean isInternal() {
return internal;
}
}
private String moduleIdentifier;
private Plugin plugin;
private String classname;
private String typeInfo;
private final Collection<Module> depends;
private final Collection<ResourceFile> resources;
private boolean designersupported;
public QMLDirectoryInfo(InputStream input) {
this.depends = new LinkedList<>();
this.resources = new LinkedList<>();
IQDirAST ast = new QMLDirectoryParser().parse(input);
for (IQDirCommand c : ast.getCommands()) {
if (c instanceof IQDirModuleCommand) {
if (moduleIdentifier == null) {
moduleIdentifier = ((IQDirModuleCommand) c).getModuleIdentifier().getText();
}
} else if (c instanceof IQDirPluginCommand) {
if (plugin == null) {
IQDirPluginCommand pc = (IQDirPluginCommand) c;
plugin = new Plugin(pc.getName().getText(), pc.getPath() != null ? pc.getPath().getText() : null);
}
} else if (c instanceof IQDirTypeInfoCommand) {
if (typeInfo == null) {
typeInfo = ((IQDirTypeInfoCommand) c).getFile().getText();
}
} else if (c instanceof IQDirResourceCommand) {
IQDirResourceCommand rc = (IQDirResourceCommand) c;
resources.add(new ResourceFile(rc.getFile().getText(),
rc.getInitialVersion().getVersionString(),
false, false));
} else if (c instanceof IQDirInternalCommand) {
IQDirInternalCommand rc = (IQDirInternalCommand) c;
resources.add(new ResourceFile(rc.getFile().getText(),
null, true, false));
} else if (c instanceof IQDirSingletonCommand) {
IQDirSingletonCommand rc = (IQDirSingletonCommand) c;
resources.add(new ResourceFile(rc.getFile().getText(),
rc.getInitialVersion().getVersionString(),
false, true));
} else if (c instanceof IQDirDependsCommand) {
IQDirDependsCommand dc = (IQDirDependsCommand) c;
depends.add(new Module(dc.getModuleIdentifier().getText(),
dc.getInitialVersion().getVersionString()));
} else if (c instanceof IQDirClassnameCommand) {
if (classname == null) {
classname = ((IQDirClassnameCommand) c).getIdentifier().getText();
}
} else if (c instanceof IQDirDesignerSupportedCommand) {
designersupported = true;
}
}
}
public String getModuleIdentifier() {
return moduleIdentifier;
}
public Plugin getPlugin() {
return plugin;
}
public String getClassname() {
return classname;
}
public String getTypesFileName() {
return typeInfo;
}
public Collection<Module> getDependentModules() {
return Collections.unmodifiableCollection(depends);
}
public Collection<ResourceFile> getResources() {
return Collections.unmodifiableCollection(resources);
}
public boolean isDesignersupported() {
return designersupported;
}
}

View file

@ -0,0 +1,120 @@
/*******************************************************************************
* 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.qmljs;
public final class QMLExpressionEvaluator {
private QMLExpressionEvaluator() {
}
public static class InvalidExpressionException extends Exception {
private static final long serialVersionUID = 4803923632666457229L;
private IJSExpression offendingExpression;
public InvalidExpressionException(String msg, IJSExpression expr) {
super(msg);
this.offendingExpression = expr;
}
public IJSExpression getOffendingExpression() {
return offendingExpression;
}
}
/**
* Evaluates the given {@link IJSExpression} as a constant expression and returns the result. At the moment this only supports
* very simple expressions involving unary operators and literals alone. Support for more complex expressions will be added as
* needed.
*
* @param expr
* the expression to be evaluated
*
* @throws InvalidExpressionException
* if the given expression can't be reduced to a single constant
*/
public static Object evaluateConstExpr(IJSExpression expr) throws InvalidExpressionException {
if (expr instanceof IJSLiteral) {
return ((IJSLiteral) expr).getValue();
} else if (expr instanceof IJSUnaryExpression) {
IJSUnaryExpression unary = (IJSUnaryExpression) expr;
Object arg = evaluateConstExpr(unary.getArgument());
switch (unary.getOperator()) {
case Plus:
return unaryPlus(arg, unary.getArgument());
case Negation:
return unaryNegate(arg, unary.getArgument());
case BitwiseNot:
return unaryBitwiseNot(arg, unary.getArgument());
case Not:
return unaryNot(arg, unary.getArgument());
default:
}
}
throw new InvalidExpressionException("Cannot reduce '" + expr + "' to a constant", expr); //$NON-NLS-1$ //$NON-NLS-2$
}
private static Object unaryPlus(Object object, IJSExpression expr) throws InvalidExpressionException {
if (object instanceof Byte) {
return +((Byte) object);
} else if (object instanceof Short) {
return +((Short) object);
} else if (object instanceof Integer) {
return +((Integer) object);
} else if (object instanceof Long) {
return +((Long) object);
} else if (object instanceof Float) {
return +((Float) object);
} else if (object instanceof Double) {
return +((Double) object);
}
throw new InvalidExpressionException("Cannot perform unary plus operation on a non-number", expr); //$NON-NLS-1$
}
private static Object unaryNegate(Object object, IJSExpression expr) throws InvalidExpressionException {
if (object instanceof Byte) {
return -((Byte) object);
} else if (object instanceof Short) {
return -((Short) object);
} else if (object instanceof Integer) {
return -((Integer) object);
} else if (object instanceof Long) {
return -((Long) object);
} else if (object instanceof Float) {
return -((Float) object);
} else if (object instanceof Double) {
return -((Double) object);
}
throw new InvalidExpressionException("Cannot perform unary negation operation on a non-number", expr); //$NON-NLS-1$
}
private static Object unaryBitwiseNot(Object object, IJSExpression expr) throws InvalidExpressionException {
if (object instanceof Byte) {
return ~((Byte) object);
} else if (object instanceof Short) {
return ~((Short) object);
} else if (object instanceof Integer) {
return ~((Integer) object);
} else if (object instanceof Long) {
return ~((Long) object);
} else if (object instanceof Float || object instanceof Double) {
return ~((Number) object).longValue();
}
throw new InvalidExpressionException("Cannot perform binary not operation on a non-number", expr); //$NON-NLS-1$
}
private static Object unaryNot(Object object, IJSExpression expr) throws InvalidExpressionException {
if (object instanceof Boolean) {
return !((Boolean) object);
}
throw new InvalidExpressionException("Cannot perform unary not operation on a non-boolean", expr); //$NON-NLS-1$
}
}

View file

@ -8,6 +8,12 @@ function resolveDirectory(obj) {
};
}
function resolveModule(obj) {
return function (module) {
return obj.resolveModule(module);
};
}
function requestCallback(obj) {
return function (err, data) {
obj.callback(err, data);

View file

@ -65,6 +65,12 @@
}
return path;
},
resolveModule: function (module) {
var impl = this.server.options.resolveModule;
if (impl) {
return impl(module);
}
},
updateDirectoryImportList: function () {
if (!this.imports) {
this.imports = {};
@ -961,35 +967,6 @@
}
}
/*
* Called when a parse query is made to the server.
*/
function parse(srv, query, file) {
var ast = null;
if (query.file) {
// Get the file's AST. It should have been parsed already by the server.
ast = file.ast;
} else {
// Parse the file manually and get the AST.
var options = {
directSourceFile: file,
allowReturnOutsideFunction: true,
allowImportExportEverywhere: true,
ecmaVersion: srv.options.ecmaVersion
};
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 {
ast: ast
};
}
// Register the QML plugin in Tern
tern.registerPlugin("qml", function (server) {
// First we want to replace the top-level defs array with our own and save the
@ -1003,19 +980,41 @@
if (this.cx) this.reset();
};
// Create a method on the server object that parses a string. We can't make this
// a query due to the fact that tern always does its infer processing on every
// query regardless of its contents.
server.parseString = function (text, options, callback) {
try {
var opts = {
allowReturnOutsideFunction: true,
allowImportExportEverywhere: true,
ecmaVersion: this.options.ecmaVersion
};
for (var opt in options) {
opts[opt] = options[opt];
}
text = this.signalReturnFirst("preParse", text, opts) || text;
var ast = infer.parse(text, opts);
callback(null, {
ast: ast
});
this.signal("postParse", ast, text);
} catch (err) {
callback(err, null);
}
};
// Create the QML Import Handler
qmlImportHandler = exports.importHandler = new ImportHandler(server);
// Define the 'parseFile' and 'parseString' query types. The reason we need
// two separate queries for these is that Tern will not allow us to resolve
// a file without 'takesFile' being true. However, if we set 'takesFile' to
// true, Tern will not allow a null file in the query.
// Define the 'parseFile' query type.
tern.defineQueryType("parseFile", {
takesFile: true,
run: parse
});
tern.defineQueryType("parseString", {
run: parse
run: function (srv, query, file) {
return {
ast: file.ast
};
}
});
// Hook into server signals

View file

@ -61,12 +61,7 @@ test("{Parse given file}", function (server, callback, name) {
});
test("{Parse empty text}", function (server, callback, name) {
server.request({
query: {
type: "parseString",
text: ""
}
}, function (err, resp) {
server.parseString("", null, function (err, resp) {
if (err) {
throw err;
}
@ -80,12 +75,7 @@ test("{Parse empty text}", function (server, callback, name) {
});
test("{Parse text no mode}", function (server, callback, name) {
server.request({
query: {
type: "parseString",
text: "import QtQuick 2.0\nModule {\n\tComponent {\n\t}\n}"
}
}, function (err, resp) {
server.parseString("import QtQuick 2.0\nModule {\n\tComponent {\n\t}\n}", null, function (err, resp) {
if (err) {
throw err;
}
@ -99,14 +89,8 @@ test("{Parse text no mode}", function (server, callback, 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"
}
}
server.parseString("QtObject {\n\tobj: {\n\t\tprop1: 1,\n\t\tprop2: 2\n\t}\n}", {
mode: "qmltypes"
}, function (err, resp) {
if (err) {
throw err;
@ -121,15 +105,9 @@ test("{Parse text (mode: qmltypes)}", function (server, callback, name) {
});
test("{Parse text with locations}", function (server, callback, name) {
server.request({
query: {
type: "parseString",
text: "var w = 3",
options: {
mode: "js",
locations: true
}
}
server.parseString("var w = 3", {
mode: "js",
locations: true
}, function (err, resp) {
if (err) {
throw err;