1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-04-22 22:22:11 +02:00
cdt/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js
Matthew Bastien 894c74be7e Bug 481126 - QML Static Analysis based on Tern.js
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>
2015-11-06 14:39:28 -05:00

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");
}
};
}
})