mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-22 22:22:11 +02:00

Tern can now perform static analysis on the following QML constructs: - QML Property Declarations - QML Property Bindings - QML Signals - QML Signal Handlers Added basic support for the 'id' property that can be assigned to any QML Object. Fixed acorn-qml's member function parsing and added tests. The 'demo' folder contains an html demo of the qml tern implementation using codemirror as the editor. Change-Id: I9e4d3837b194ff92a36a8d62bef288f61743e4ce Signed-off-by: Matthew Bastien <mbastien@blackberry.com>
273 lines
No EOL
8.9 KiB
JavaScript
273 lines
No EOL
8.9 KiB
JavaScript
/*******************************************************************************
|
|
* 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
|
|
*******************************************************************************/
|
|
(function (mod) {
|
|
if (typeof exports === "object" && typeof module === "object") // CommonJS
|
|
return mod(require("tern/lib/infer"), require("tern/lib/tern"));
|
|
if (typeof define === "function" && define.amd) // AMD
|
|
return define(["tern/lib/infer", "tern/lib/tern"], mod);
|
|
mod(tern, tern); // Plain browser env
|
|
})(function (infer, tern) {
|
|
'use strict';
|
|
|
|
// Define a few shorthand variables/functions
|
|
var ANull = infer.ANull;
|
|
var AVal = infer.AVal;
|
|
var Scope = infer.Scope;
|
|
var Obj = infer.Obj;
|
|
var Fn = infer.Fn;
|
|
var Prim = infer.Prim;
|
|
|
|
function addVar(scope, node) {
|
|
return scope.defProp(node.name, node);
|
|
}
|
|
|
|
function skipThrough(node, st, c) {
|
|
c(node, st)
|
|
}
|
|
|
|
function ignore(node, st, c) {}
|
|
|
|
// Register the QML plugin in Tern
|
|
tern.registerPlugin("qml", function (server) {
|
|
extendTernScopeGatherer(infer.scopeGatherer);
|
|
extendTernInferExprVisitor(infer.inferExprVisitor);
|
|
extendTernInferWrapper(infer.inferWrapper);
|
|
extendTernSearchVisitor(infer.searchVisitor);
|
|
server.on("preParse", preParse);
|
|
});
|
|
|
|
function preParse(text, options) {
|
|
var plugins = options.plugins;
|
|
if (!plugins) plugins = options.plugins = {};
|
|
plugins["qml"] = true;
|
|
}
|
|
|
|
function extendTernScopeGatherer(scopeGatherer) {
|
|
scopeGatherer["QMLImportStatement"] = function (node, scope, c) {
|
|
if (node.qualifier) {
|
|
addVar(scope, node.qualifier.id);
|
|
}
|
|
};
|
|
scopeGatherer["QMLObjectLiteral"] = function (node, scope, c) {
|
|
var inner = node.scope = new Scope(scope, node);
|
|
inner.forward = undefined;
|
|
c(node.block, inner);
|
|
};
|
|
scopeGatherer["QMLPropertyDeclaration"] = function (node, scope, c) {
|
|
var prop = addVar(scope, node.id);
|
|
if (node.init) {
|
|
c(node.init, scope);
|
|
}
|
|
};
|
|
scopeGatherer["QMLPropertyBinding"] = function (node, scope, c) {
|
|
// Check for the 'id' property being set
|
|
var idParts = node.id.parts;
|
|
if (idParts.length == 1 && idParts[0].name === "id") {
|
|
if (node.expr.type === "Identifier") {
|
|
node.prop = scope.prev.defProp(node.expr.name, node.expr);
|
|
}
|
|
}
|
|
|
|
// If this appears to be a signal handler, pre-emptively create a new scope that
|
|
// will store references to the signal's arguments
|
|
var last = getLastIndex(idParts).name;
|
|
var inner = scope;
|
|
if (last.startsWith("on")) {
|
|
inner = node.scope = new Scope(scope, node);
|
|
}
|
|
|
|
// Delegate down to the expression
|
|
c(node.expr, inner);
|
|
};
|
|
scopeGatherer["QMLStatementBlock"] = function (node, scope, c) {
|
|
var inner = node.scope = new Scope(scope, node);
|
|
for (var i = 0; i < node.statements.length; i++) {
|
|
c(node.statements[i], inner, "Statement");
|
|
}
|
|
};
|
|
scopeGatherer["QMLSignalDefinition"] = function (node, scope, c) {
|
|
// Define the signal arguments in their own separate scope
|
|
var argNames = [],
|
|
argVals = [];
|
|
var fnScope = new Scope(scope, node);
|
|
for (var i = 0; i < node.params.length; i++) {
|
|
var param = node.params[i];
|
|
argNames.push(param.id.name);
|
|
argVals.push(addVar(fnScope, param.id));
|
|
}
|
|
|
|
// Define the signal function which can be referenced from JavaScript
|
|
var sig = scope.defProp(node.id.name, new AVal);
|
|
sig.fnType = new Fn(node.id.name, new AVal, argVals, argNames, ANull);
|
|
sig.fnType.fnScope = fnScope;
|
|
|
|
// Define the signal handler property
|
|
var handler = scope.defProp(getSignalHandlerName(node.id.name), node.id);
|
|
handler.sig = sig.fnType;
|
|
}
|
|
}
|
|
|
|
function extendTernInferExprVisitor(inferExprVisitor) {
|
|
// ret' taken from infer.js
|
|
function ret(f) {
|
|
return function (node, scope, out, name) {
|
|
var r = f(node, scope, name);
|
|
if (out) r.propagate(out);
|
|
return r;
|
|
};
|
|
}
|
|
|
|
// Extend the inferExprVisitor methods
|
|
inferExprVisitor["QMLStatementBlock"] = ret(function (node, scope, name) {
|
|
return ANull; // Statement blocks have no type
|
|
});
|
|
inferExprVisitor["QMLObjectLiteral"] = ret(function (node, scope, name) {
|
|
return node.scope.objType;
|
|
});
|
|
}
|
|
|
|
function findProp(node, scope) {
|
|
if (node.type === "QMLQualifiedID") {
|
|
// For now we can only find a property for a qualified id if it has a single part
|
|
var prop = node.parts.length > 1 ? null : scope.hasProp(node.parts[0].name)
|
|
if (!prop) {
|
|
// Try to find the full qualified id by name if the previous search was unsuccessful
|
|
prop = scope.hasProp(node.name);
|
|
}
|
|
return prop;
|
|
} else if (node.type === "Identifier") {
|
|
return scope.hasProp(node.name);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function getLastIndex(arr) {
|
|
return arr[arr.length - 1];
|
|
}
|
|
|
|
function getSignalHandlerName(str) {
|
|
return "on" + str.charAt(0).toUpperCase() + str.slice(1);
|
|
}
|
|
|
|
function extendTernInferWrapper(inferWrapper) {
|
|
// 'infer' taken from infer.js
|
|
function inf(node, scope, out, name) {
|
|
var handler = infer.inferExprVisitor[node.type];
|
|
return handler ? handler(node, scope, out, name) : ANull;
|
|
}
|
|
|
|
// Extend the inferWrapper methods
|
|
inferWrapper["QMLObjectLiteral"] = function (node, scope, c) {
|
|
// Define a new Obj which represents this Object Literal
|
|
var obj = node.scope.objType = new Obj(true, node.id.name);
|
|
// node.scope will contain all object properties so we don't have to walk the AST to find them
|
|
node.scope.forAllProps(function (name, prop, curr) {
|
|
if (curr) {
|
|
// Copy the property into the new type so that references to both of them
|
|
// will update the same object.
|
|
obj.props[name] = prop;
|
|
}
|
|
});
|
|
c(node.block, node.scope);
|
|
};
|
|
inferWrapper["QMLPropertyDeclaration"] = function (node, scope, c) {
|
|
var prop = findProp(node.id, scope);
|
|
// Infer the property's type from its assigned type
|
|
switch (node.kind) {
|
|
case "int":
|
|
case "double":
|
|
case "real":
|
|
infer.cx().num.propagate(prop);
|
|
break;
|
|
case "string":
|
|
case "color":
|
|
infer.cx().str.propagate(prop);
|
|
break;
|
|
case "boolean":
|
|
infer.cx().bool.propagate(prop);
|
|
break;
|
|
}
|
|
// Also infer the type from its init expression
|
|
if (node.init) {
|
|
inf(node.init, scope, prop, node.id.name);
|
|
c(node.init, scope);
|
|
}
|
|
};
|
|
inferWrapper["QMLPropertyBinding"] = function (node, scope, c) {
|
|
var prop = findProp(node.id, scope);
|
|
if (prop) {
|
|
if (prop.sig) {
|
|
// This is a signal handler and we should populate its scope with
|
|
// the arguments from its parent function.
|
|
prop.sig.fnScope.forAllProps(function (name, prop, curr) {
|
|
if (curr) {
|
|
node.scope.props[name] = prop;
|
|
}
|
|
});
|
|
} else {
|
|
inf(node.expr, scope, prop, getLastIndex(node.id.parts));
|
|
}
|
|
} else {
|
|
// Check for the 'id' property being set
|
|
var idParts = node.id.parts;
|
|
if (idParts.length == 1 && idParts[0].name === "id") {
|
|
if (node.expr.type === "Identifier") {
|
|
scope.objType.propagate(node.prop);
|
|
}
|
|
}
|
|
}
|
|
c(node.expr, node.scope || scope);
|
|
};
|
|
inferWrapper["QMLStatementBlock"] = function (node, scope, c) {
|
|
for (var i = 0; i < node.statements.length; i++) {
|
|
c(node.statements[i], node.scope, "Statement");
|
|
}
|
|
};
|
|
inferWrapper["QMLSignalDefinition"] = function (node, scope, c) {
|
|
var sig = scope.getProp(node.id.name);
|
|
var retval = new Obj(true, "Signal");
|
|
sig.fnType.retval = retval;
|
|
sig.fnType.propagate(sig);
|
|
|
|
var handler = scope.getProp(getSignalHandlerName(node.id.name));
|
|
var obj = new Obj(true, "Signal Handler");
|
|
obj.propagate(handler);
|
|
}
|
|
}
|
|
|
|
function extendTernSearchVisitor(searchVisitor) {
|
|
// Extend the search visitor to traverse the scope properly
|
|
searchVisitor["QMLObjectLiteral"] = function (node, scope, c) {
|
|
c(node.block, node.scope);
|
|
};
|
|
searchVisitor["QMLPropertyDeclaration"] = function (node, scope, c) {
|
|
c(node.id, scope);
|
|
if (node.init) {
|
|
c(node.init, scope);
|
|
}
|
|
}
|
|
searchVisitor["QMLPropertyBinding"] = function (node, scope, c) {
|
|
// A binding that is referencing a signal holds a scope. Other property bindings do not.
|
|
c(node.id, node.scope || scope);
|
|
c(node.expr, node.scope || scope);
|
|
};
|
|
searchVisitor["QMLQualifiedID"] = function (node, scope, c) {
|
|
for (var i = 0; i < node.parts.length; i++) {
|
|
c(node.parts[i], scope);
|
|
}
|
|
};
|
|
searchVisitor["QMLStatementBlock"] = function (node, scope, c) {
|
|
for (var i = 0; i < node.statements.length; i++) {
|
|
c(node.statements[i], node.scope, "Statement");
|
|
}
|
|
};
|
|
}
|
|
}) |