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 b5606e115f Bug 481126 - Tern QML Tests
Added a testing framework similar to acorn-qml's for tern-qml and began
adding tests.  Updated acorn-qml and tern-qml to use acorn v2.6.4 which
includes the changes to acorn_loose that enable loose parser plugins.

Change-Id: Ib6af4b476321fde4b3f15aac8342764a45985dfa
Signed-off-by: Matthew Bastien <mbastien@blackberry.com>
2015-11-17 10:23:31 -05:00

339 lines
No EOL
11 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("acorn"), require("acorn/dist/acorn_loose"), require("tern/lib/infer"), require("tern/lib/tern"));
if (typeof define === "function" && define.amd) // AMD
return define([ "acorn", "acorn/dist/acorn_loose", "tern/lib/infer", "tern/lib/tern", "tern/lib/def"], mod);
mod(acorn, acorn, tern, tern, tern.def); // Plain browser env
})(function (acorn, acornLoose, infer, tern, def) {
'use strict';
// Outside of the browser environment we have to grab 'def' from 'infer'
if (!def) {
def = infer.def;
}
// Helper for adding a property to a scope.
function addVar(scope, node) {
if (node.type === "QMLQualifiedID") {
var currScope = scope;
for (var i = 1; i < node.parts.length; i++) {
var name = node.parts[i].name;
curr = curr.hasProp(name);
}
return prop;
} else if (node.type === "Identifier") {
return scope.defProp(node.name, node);
}
return null;
}
// Helper for finding a property in a scope.
function findProp(node, scope) {
if (node.type === "QMLQualifiedID") {
var curr = scope;
for (var i = 0; i < node.parts.length; i++) {
var name = node.parts[i].name;
curr = curr.hasProp(name);
if (!curr) {
return null;
}
}
return curr;
} else if (node.type === "Identifier") {
return scope.hasProp(node.name);
}
return null;
}
// Helper for getting the server from the current context
function getServer() {
var parent = infer.cx().parent;
return parent instanceof tern.Server ? parent : null;
}
function preParse(text, options) {
// Force ECMA Version to 5
options.ecmaVersion = 5;
// Register qml plugin with main parser
var plugins = options.plugins;
if (!plugins) plugins = options.plugins = {};
plugins["qml"] = true;
// Register qml plugin with loose parser
var pluginsLoose = options.pluginsLoose;
if (!pluginsLoose) pluginsLoose = options.pluginsLoose = {};
pluginsLoose["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 infer.Scope(scope, node);
c(node.body, inner);
};
scopeGatherer["QMLPropertyDeclaration"] = function (node, scope, c) {
var prop = addVar(scope, node.id);
var inner = scope;
if (node.init) {
// Create a JavaScript scope if init is a JavaScript environment
if (!node.init.type.startsWith("QML")) {
inner = node.scope = getServer().createJSScope(scope, node);
}
c(node.init, inner);
}
};
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);
}
}
// Create a JavaScript scope if init is a JavaScript environment
var inner = scope;
if (!node.expr.type.startsWith("QML")) {
inner = node.scope = getServer().createJSScope(scope, node);
} else {
// 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;
if (last.startsWith("on")) {
inner = node.scope = new infer.Scope(scope, node);
}
}
// Delegate down to the expression
c(node.expr, inner);
};
scopeGatherer["QMLStatementBlock"] = function (node, scope, c) {
var inner = node.scope = getServer().createJSScope(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 infer.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 type which can be referenced from JavaScript
var sig = addVar(scope, node.id);
sig.fnType = new infer.Fn(node.id.name, new infer.AVal, argVals, argNames, infer.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 infer.ANull; // Statement blocks have no type
});
inferExprVisitor["QMLObjectLiteral"] = ret(function (node, scope, name) {
return node.scope.objType;
});
}
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) : infer.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 infer.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.body, 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) {
c(node.init, scope);
inf(node.init, scope, prop, node.id.name);
}
};
inferWrapper["QMLPropertyBinding"] = function (node, scope, c) {
c(node.expr, node.scope || scope);
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);
}
}
}
};
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 infer.Obj(true, "Signal");
sig.fnType.retval = retval;
sig.fnType.propagate(sig);
var handler = scope.getProp(getSignalHandlerName(node.id.name));
var obj = new infer.Obj(true, "Signal Handler");
obj.propagate(handler);
}
}
function extendTernTypeFinder(typeFinder) {
// Extend the type finder to return valid types for QML AST elements
typeFinder["QMLObjectLiteral"] = function (node, scope) {
return node.scope.objType;
};
typeFinder["QMLStatementBlock"] = function (node, scope) {
return infer.ANull;
};
typeFinder["QMLQualifiedID"] = function (node, scope) {
return findProp(node, scope);
}
}
function extendTernSearchVisitor(searchVisitor) {
// Extend the search visitor to traverse the scope properly
searchVisitor["QMLObjectLiteral"] = function (node, scope, c) {
c(node.body, node.scope);
};
searchVisitor["QMLPropertyBinding"] = function (node, scope, c) {
c(node.id, scope);
// A binding that is referencing a signal holds a scope. Other property bindings do not.
c(node.expr, node.scope || scope);
};
searchVisitor["QMLStatementBlock"] = function (node, scope, c) {
for (var i = 0; i < node.statements.length; i++) {
c(node.statements[i], node.scope, "Statement");
}
};
}
// 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
// JavaScript specific defs to a new array 'jsDefs'. In order to make sure no
// other plugins mess with the new defs after us, we override addDefs and add
// a new method called 'addQMLDefs' to facilitate adding QML specific definitions.
server.jsDefs = server.defs;
server.defs = [];
server.addQMLDefs = function (defs, toFront) {
if (toFront) this.defs.unshift(defs)
else this.defs.push(defs)
if (this.cx) this.reset()
}
server.addDefs = function (defs, toFront) {
if (toFront) this.jsDefs.unshift(defs)
else this.jsDefs.push(defs)
if (this.cx) this.reset()
}
// Add a new method to server which creates a js scope based on jsDefs
server.createJSScope = function (prev, node, isBlock) {
var scope = new infer.Scope(prev, node, isBlock);
if (this.jsDefs) for (var i = 0; i < this.jsDefs.length; ++i)
def.load(this.jsDefs[i], scope);
return scope;
};
// Force Tern to use the QML plugin for Acorn
server.on("preParse", preParse);
// Add a new scope to a file which will hold its root Object and such
server.on("beforeLoad", function(file) {
file.scope = new infer.Scope(infer.cx().topScope);
file.scope.name = file.name;
});
// Extend Tern's inferencing system to include QML syntax
extendTernScopeGatherer(infer.scopeGatherer);
extendTernInferExprVisitor(infer.inferExprVisitor);
extendTernInferWrapper(infer.inferWrapper);
extendTernTypeFinder(infer.typeFinder);
extendTernSearchVisitor(infer.searchVisitor);
});
})