diff --git a/qt/org.eclipse.cdt.qt.core/acorn-qml/index.js b/qt/org.eclipse.cdt.qt.core/acorn-qml/index.js index f724fad6cb1..7f4ad9d5586 100644 --- a/qt/org.eclipse.cdt.qt.core/acorn-qml/index.js +++ b/qt/org.eclipse.cdt.qt.core/acorn-qml/index.js @@ -8,18 +8,14 @@ * Contributors: * QNX Software Systems - Initial API and implementation *******************************************************************************/ - -// This will only be visible globally if we are in a browser environment -var acornQML; - (function (mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS - return module.exports = mod(require("./inject.js"), require("acorn")); + return mod(require("./inject.js"), require("acorn")); if (typeof define == "function" && define.amd) // AMD return define(["./inject.js", "acorn/dist/acorn"], mod); - acornQML = mod(injectQML, acorn); // Plain browser env -})(function (injectQML, acorn) { + mod(acornQMLInjector, acorn); // Plain browser env +})(function (acornQMLInjector, acorn) { 'use strict'; - return injectQML(acorn); -}) \ No newline at end of file + acornQMLInjector.inject(acorn); +}); \ No newline at end of file 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 25ed656b34c..16ff8d63165 100644 --- a/qt/org.eclipse.cdt.qt.core/acorn-qml/inject.js +++ b/qt/org.eclipse.cdt.qt.core/acorn-qml/inject.js @@ -8,20 +8,16 @@ * Contributors: * QNX Software Systems - Initial API and implementation *******************************************************************************/ - -// This will only be visible globally if we are in a browser environment -var injectQML; - -(function (mod) { +(function (root, mod) { if (typeof exports === "object" && typeof module === "object") // CommonJS - return module.exports = mod(); + return mod(exports); if (typeof define === "function" && define.amd) // AMD - return define([], mod); - injectQML = mod(); // Plain browser env -})(function () { + return define(["exports"], mod); + mod(root.acornQMLInjector || (root.acornQMLInjector = {})); // Plain browser env +})(this, function (exports) { 'use strict'; - return function (acorn) { + exports.inject = function (acorn) { // Acorn token types var tt = acorn.tokTypes; @@ -88,7 +84,7 @@ var injectQML; * the type import or pragma */ pp.qml_parseHeaderStatements = function () { - var node = this.startNode() + var node = this.startNode(); node.statements = []; var loop = true; @@ -103,7 +99,7 @@ var injectQML; } return this.finishNode(node, "QMLHeaderStatements"); - } + }; /* * Parses a QML Pragma statement of the form: @@ -115,7 +111,7 @@ var injectQML; node.id = this.parseIdent(false); this.semicolon(); return this.finishNode(node, "QMLPragmaStatement"); - } + }; /* * Parses a QML Import statement of the form: @@ -180,7 +176,7 @@ var injectQML; node.raw = this.input.slice(this.start, this.end); node.value = this.value; var matches; - if (matches = /(\d+)\.(\d+)/.exec(node.raw)) { + if ((matches = /(\d+)\.(\d+)/.exec(node.raw))) { node.major = parseInt(matches[1]); node.minor = parseInt(matches[2]); this.next(); @@ -189,7 +185,7 @@ var injectQML; } return this.finishNode(node, "QMLVersionLiteral"); - } + }; /* * Parses a QML Qualifier of the form: @@ -200,7 +196,7 @@ var injectQML; this.expectContextual(qtt._as); node.id = this.qml_parseIdent(false); return this.finishNode(node, "QMLQualifier"); - } + }; /* * Parses a QML Object Literal of the form: @@ -217,7 +213,7 @@ var injectQML; } node.body = this.qml_parseMemberBlock(); return this.finishNode(node, "QMLObjectLiteral"); - } + }; /* * Parses a QML Member Block of the form: @@ -231,7 +227,7 @@ var injectQML; node.members.push(this.qml_parseMember()); } return this.finishNode(node, "QMLMemberBlock"); - } + }; /* * Parses a QML Member which can be one of the following: @@ -250,7 +246,7 @@ var injectQML; return this.qml_parseFunctionMember(); } return this.qml_parseObjectLiteralOrPropertyBinding(); - } + }; /* * Parses a JavaScript function as a member of a QML Object Literal @@ -259,7 +255,7 @@ var injectQML; var node = this.startNode(); this.expect(tt._function); return this.parseFunction(node, true); - } + }; /* * Parses a QML Object Literal or Property Binding depending on the tokens found. @@ -278,7 +274,7 @@ var injectQML; return this.qml_parsePropertyBinding(node); } this.unexpected(); - } + }; /* * Parses a QML Property of the form: @@ -294,7 +290,7 @@ var injectQML; this.expect(tt.colon); node.binding = this.qml_parseBinding(); return this.finishNode(node, "QMLPropertyBinding"); - } + }; /* * Parses a QML Signal Definition of the form: @@ -325,7 +321,7 @@ var injectQML; this.qml_parseSignalParams(node); this.semicolon(); return this.finishNode(node, "QMLSignalDefinition"); - } + }; /* * Parses QML Signal Parameters of the form: @@ -344,7 +340,7 @@ var injectQML; this.expect(tt.parenR); } } - } + }; /* * Parses a QML Property Declaration (or Alias) of the form: @@ -354,10 +350,10 @@ var injectQML; var node = this.startNode(); // Parse 'default' or 'readonly' - node["default"] = false; - node["readonly"] = false; + node.default = false; + node.readonly = false; if (this.eat(tt._default)) { - node["default"] = true; + node.default = true; } else if (this.isContextual(qtt._readonly)) { // Parse as a qualified id in case this is not a property declaration var readonly = this.qml_parseQualifiedId(true); @@ -367,7 +363,7 @@ var injectQML; node.id = readonly; return this.qml_parseObjectLiteralOrPropertyBinding(node); } - node["readonly"] = true; + node.readonly = true; } else { // Readonly keyword is a qualified ID. This is not a property declaration. node.id = readonly; @@ -377,22 +373,22 @@ var injectQML; // Parse as a qualified id in case this is not a property declaration var property = this.qml_parseQualifiedId(true); - if (property.parts.length === 1 || node["default"] || node["readonly"]) { + if (property.parts.length === 1 || node.default || node.readonly) { if (property.name !== qtt._property) { this.unexpected(); } if (this.type === tt.colon || this.type === tt.braceL) { // This is a property binding or object literal. - node["default"] = undefined; - node["readonly"] = undefined; + node.default = undefined; + node.readonly = undefined; node.id = property; return this.qml_parseObjectLiteralOrPropertyBinding(node); } } else { // Property keyword is a qualified ID. This is not a property declaration. - node["default"] = undefined; - node["readonly"] = undefined; + node.default = undefined; + node.readonly = undefined; node.id = property; return this.qml_parseObjectLiteralOrPropertyBinding(node); } @@ -407,7 +403,7 @@ var injectQML; } return this.finishNode(node, "QMLPropertyDeclaration"); - } + }; /* * Parses one of the following possibilities for a QML Property assignment: @@ -422,7 +418,7 @@ var injectQML; // test: QMLObject.QualifiedId { } return this.qml_parseScriptBinding(); - } + }; /* * Parses one of the following Script Bindings: @@ -440,7 +436,7 @@ var injectQML; this.semicolon(); } return this.finishNode(node, "QMLScriptBinding"); - } + }; /* * Parses a QML Statement Block of the form: @@ -454,7 +450,7 @@ var injectQML; node.statements.push(this.parseStatement(true, false)); } return this.finishNode(node, "QMLStatementBlock"); - } + }; /* * Parses a QML Type which can be either a Qualified ID or a primitive type keyword. @@ -468,7 +464,7 @@ var injectQML; return this.qml_parseQualifiedId(false); } this.unexpected(); - } + }; /* * Parses a Qualified ID of the form: @@ -494,7 +490,7 @@ var injectQML; } return this.finishNode(node, "QMLQualifiedID"); - } + }; /* * Parses an Identifier in a QML Context. That is, this method uses 'isQMLContextual' @@ -511,7 +507,7 @@ var injectQML; } } return this.parseIdent(liberal); - } + }; /* * Returns whether or not a given token type and name can be a QML Identifier. @@ -520,13 +516,13 @@ var injectQML; pp.qml_isIdent = function (type, name) { if (type === tt.name) { var key; - if (key = keywords[name]) { - return key.isQMLContextual + if ((key = keywords[name])) { + return key.isQMLContextual; } return true; } return false; - } + }; /* * Returns whether or not the current token is a QML primitive type and consumes @@ -538,7 +534,7 @@ var injectQML; return true; } return false; - } + }; /* * Returns whether or not the current token is a QML primitive type. @@ -550,12 +546,12 @@ var injectQML; if (type === tt.name) { var key; - if (key = keywords[name]) { + if ((key = keywords[name])) { return key.isPrimitive; } } return false; - } + }; acorn.plugins.qml = function (instance) { @@ -589,8 +585,8 @@ var injectQML; return this.finishNode(node, "QMLProgram"); }; }); - } + }; return acorn; }; -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/qt/org.eclipse.cdt.qt.core/acorn-qml/loose/index.js b/qt/org.eclipse.cdt.qt.core/acorn-qml/loose/index.js index db2d6f5d249..524c15db178 100644 --- a/qt/org.eclipse.cdt.qt.core/acorn-qml/loose/index.js +++ b/qt/org.eclipse.cdt.qt.core/acorn-qml/loose/index.js @@ -8,18 +8,14 @@ * Contributors: * QNX Software Systems - Initial API and implementation *******************************************************************************/ - -// This will only be visible globally if we are in a browser environment -var acornQMLLoose; - (function (mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS - return module.exports = mod(require("./inject.js"), require("acorn"), require("acorn/dist/acorn_loose")); + return mod(require("./inject.js"), require("acorn"), require("acorn/dist/acorn_loose")); if (typeof define == "function" && define.amd) // AMD return define(["./inject.js", "acorn", "acorn/dist/acorn_loose"], mod); - acornQMLLoose = mod(injectQMLLoose, acorn, acorn); // Plain browser env -})(function (injectQMLLoose, acorn, acorn_loose) { + mod(acornQMLLooseInjector, acorn, acorn); // Plain browser env +})(function (acornQMLLooseInjector, acorn, acorn_loose) { "use strict"; - return injectQMLLoose(acorn); -}) \ No newline at end of file + acornQMLLooseInjector.inject(acorn); +}); \ No newline at end of file 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 35b6aaf8203..f54e0f943a4 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 @@ -12,16 +12,16 @@ // This will only be visible globally if we are in a browser environment var injectQMLLoose; -(function (mod) { +(function (root, mod) { if (typeof exports === "object" && typeof module === "object") // CommonJS - return module.exports = mod(); + return mod(module.exports); if (typeof define === "function" && define.amd) // AMD - return define([], mod); - injectQMLLoose = mod(); // Plain browser env -})(function () { + return define(["exports"], mod); + mod(root.acornQMLLooseInjector || (root.acornQMLLooseInjector = {})); // Plain browser env +})(this, function (exports) { "use strict"; - return function (acorn) { + exports.inject = function (acorn) { // Acorn token types var tt = acorn.tokTypes; @@ -38,7 +38,7 @@ var injectQMLLoose; * the type import or pragma */ lp.qml_parseHeaderStatements = function () { - var node = this.startNode() + var node = this.startNode(); node.statements = []; var loop = true; @@ -53,7 +53,7 @@ var injectQMLLoose; } return this.finishNode(node, "QMLHeaderStatements"); - } + }; /* * Parses a QML Pragma statement of the form: @@ -65,7 +65,7 @@ var injectQMLLoose; node.id = this.parseIdent(false); this.semicolon(); return this.finishNode(node, "QMLPragmaStatement"); - } + }; /* * Parses a QML Import statement of the form: @@ -122,7 +122,7 @@ var injectQMLLoose; node.value = this.tok.value; var matches; if (this.tok.type === tt.num) { - if (matches = /(\d+)\.(\d+)/.exec(node.raw)) { + if ((matches = /(\d+)\.(\d+)/.exec(node.raw))) { node.major = parseInt(matches[1]); node.minor = parseInt(matches[2]); this.next(); @@ -139,7 +139,7 @@ var injectQMLLoose; } return this.finishNode(node, "QMLVersionLiteral"); - } + }; /* * Parses a QML Qualifier of the form: @@ -150,7 +150,7 @@ var injectQMLLoose; this.expectContextual(qtt._as); node.id = this.qml_parseIdent(false); return this.finishNode(node, "QMLQualifier"); - } + }; /* * Parses a QML Object Literal of the form: @@ -163,7 +163,7 @@ var injectQMLLoose; node.id = this.qml_parseQualifiedId(false); node.body = this.qml_parseMemberBlock(); return this.finishNode(node, "QMLObjectLiteral"); - } + }; /* * Parses a QML Member Block of the form: @@ -184,7 +184,7 @@ var injectQMLLoose; this.popCx(); this.eat(tt.braceR); return this.finishNode(node, "QMLMemberBlock"); - } + }; /* * Parses a QML Member which can be one of the following: @@ -222,7 +222,7 @@ var injectQMLLoose; } // ignore the current token if it didn't pass the previous tests this.next(); - } + }; /* * Parses a JavaScript function as a member of a QML Object Literal @@ -231,7 +231,7 @@ var injectQMLLoose; var node = this.startNode(); this.expect(tt._function); return this.qml_parseFunction(node, true); - } + }; /* * QML version of 'parseFunction' needed to have proper error tolerant parsing @@ -250,7 +250,7 @@ var injectQMLLoose; node.body = this.parseBlock(); } else { if (this.options.locations) { - node.body = this.startNodeAt([ this.last.end, this.last.loc.end ]) + node.body = this.startNodeAt([ this.last.end, this.last.loc.end ]); } else { node.body = this.startNodeAt(this.last.end); } @@ -258,7 +258,7 @@ var injectQMLLoose; this.finishNode(node.body, "BlockStatement"); } return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression"); - } + }; /* * Parses a QML Object Literal or Property Binding depending on the tokens found. @@ -282,7 +282,7 @@ var injectQMLLoose; return this.qml_parsePropertyBinding(); } return null; - } + }; /* * Parses a QML Property of the form: @@ -295,7 +295,7 @@ var injectQMLLoose; this.expect(tt.colon); node.binding = this.qml_parseBinding(start); return this.finishNode(node, "QMLPropertyBinding"); - } + }; /* * Parses a QML Signal Definition of the form: @@ -315,13 +315,13 @@ var injectQMLLoose; this.qml_parseSignalParams(node); this.semicolon(); return this.finishNode(node, "QMLSignalDefinition"); - } + }; /* * Checks if the given node is a dummy identifier */ function isDummy(node) { - return node.name === "✖" + return node.name === "✖"; } /* @@ -329,8 +329,8 @@ var injectQMLLoose; * [( [',' ]* )]? */ lp.qml_parseSignalParams = function (node) { - this.pushCx() - var indent = this.curIndent, line = this.curLineStart + this.pushCx(); + var indent = this.curIndent, line = this.curLineStart; node.params = []; if (this.eat(tt.parenL)) { while (!this.closes(tt.parenR, indent + 1, line)) { @@ -352,15 +352,15 @@ var injectQMLLoose; } this.eat(tt.comma); } - this.popCx() + this.popCx(); if (!this.eat(tt.parenR)) { // If there is no closing brace, make the node span to the start // of the next token (this is useful for Tern) - this.last.end = this.tok.start - if (this.options.locations) this.last.loc.end = this.tok.loc.start + this.last.end = this.tok.start; + if (this.options.locations) this.last.loc.end = this.tok.loc.start; } } - } + }; /* * Parses a QML Property Declaration (or Alias) of the form: @@ -368,25 +368,26 @@ var injectQMLLoose; */ lp.qml_parsePropertyDeclaration = function () { var node = this.startNode(); + var objOrBind = null; // Parse 'default' or 'readonly' - node["default"] = false; - node["readonly"] = false; + node.default = false; + node.readonly = false; if (this.eat(tt._default)) { - node["default"] = true; + node.default = true; } else if (this.isContextual(qtt._readonly)) { - var objOrBind = this.qml_parseObjectLiteralOrPropertyBinding(); + objOrBind = this.qml_parseObjectLiteralOrPropertyBinding(); if (objOrBind) { - objOrBind["default"] = undefined; - objOrBind["readonly"] = undefined; + objOrBind.default = undefined; + objOrBind.readonly = undefined; return objOrBind; } this.expectContextual(qtt._readonly); - node["readonly"] = true; + node.readonly = true; } - if (!node["default"] && !node["readonly"]) { - var objOrBind = this.qml_parseObjectLiteralOrPropertyBinding(); + if (!node.default && !node.readonly) { + objOrBind = this.qml_parseObjectLiteralOrPropertyBinding(); if (objOrBind) { return objOrBind; } @@ -408,7 +409,7 @@ var injectQMLLoose; } return this.finishNode(node, "QMLPropertyDeclaration"); - } + }; /* * Parses one of the following possibilities for a QML Property assignment: @@ -437,7 +438,7 @@ var injectQMLLoose; } else { return this.qml_parseScriptBinding(start); } - } + }; /* * Parses one of the following Script Bindings: @@ -458,7 +459,7 @@ var injectQMLLoose; } // If this node consumed valid syntax, reset its start position - if (!node.script.type === "Identifier" || node.script.name !== "✖") { + if (node.script.type !== "Identifier" || node.script.name !== "✖") { if (node.loc) { node.loc.start = node.script.loc.start; } @@ -470,7 +471,7 @@ var injectQMLLoose; } return this.finishNode(node, "QMLScriptBinding"); - } + }; /* * Parses a QML Statement Block of the form: @@ -489,7 +490,7 @@ var injectQMLLoose; this.popCx(); this.eat(tt.braceR); return this.finishNode(node, "QMLStatementBlock"); - } + }; /* * Parses a QML Type which can be either a Qualified ID or a primitive type keyword. @@ -501,7 +502,7 @@ var injectQMLLoose; } else { return this.qml_parseQualifiedId(false); } - } + }; /* * Parses a Qualified ID of the form: @@ -527,7 +528,7 @@ var injectQMLLoose; } return this.finishNode(node, "QMLQualifiedID"); - } + }; /* * Parses an Identifier in a QML Context. That is, this method uses 'isQMLContextual' @@ -544,7 +545,7 @@ var injectQMLLoose; } } return this.parseIdent(); - } + }; /* * Checks the next token to see if it matches the given contextual keyword. If the @@ -552,14 +553,14 @@ var injectQMLLoose; * and jumps ahead if it was found there. Returns whether or not the keyword was found. */ lp.expectContextual = function(name) { - if (this.eatContextual(name)) return true + if (this.eatContextual(name)) return true; for (var i = 1; i <= 2; i++) { if (this.lookAhead(i).type == tt.name && this.lookAhead(i).value === name) { - for (var j = 0; j < i; j++) this.next() - return true + for (var j = 0; j < i; j++) this.next(); + return true; } } - } + }; // Functions left un-changed from the main parser lp.qml_isIdent = pp.qml_isIdent; @@ -592,8 +593,8 @@ var injectQMLLoose; return this.finishNode(node, "QMLProgram"); }; }); - } + }; return acorn; }; -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/qt/org.eclipse.cdt.qt.core/acorn-qml/test/driver.js b/qt/org.eclipse.cdt.qt.core/acorn-qml/test/driver.js index cfa2c1a3164..b379ea5263d 100644 --- a/qt/org.eclipse.cdt.qt.core/acorn-qml/test/driver.js +++ b/qt/org.eclipse.cdt.qt.core/acorn-qml/test/driver.js @@ -8,7 +8,7 @@ * Contributors: * QNX Software Systems - Initial API and implementation *******************************************************************************/ -"use-strict"; +"use strict"; var tests = []; @@ -31,10 +31,10 @@ exports.runTests = function(config, callback) { try { var testOpts = test.options || {locations: true}; var expected = {}; - if (expected.onComment = testOpts.onComment) { - testOpts.onComment = [] + if ((expected.onComment = testOpts.onComment)) { + testOpts.onComment = []; } - if (expected.onToken = testOpts.onToken) { + if ((expected.onToken = testOpts.onToken)) { testOpts.onToken = []; } testOpts.plugins = { qml: true }; @@ -87,6 +87,7 @@ function addPath(str, pt) { } var misMatch = exports.misMatch = function(exp, act) { + var mis = null; if (!exp || !act || (typeof exp != "object") || (typeof act != "object")) { if (exp !== act) return ppJSON(exp) + " !== " + ppJSON(act); } else if (exp instanceof RegExp || act instanceof RegExp) { @@ -96,12 +97,12 @@ var misMatch = exports.misMatch = function(exp, act) { if (!act.slice) return ppJSON(exp) + " != " + ppJSON(act); if (act.length != exp.length) return "array length mismatch " + exp.length + " != " + act.length; for (var i = 0; i < act.length; ++i) { - var mis = misMatch(exp[i], act[i]); + mis = misMatch(exp[i], act[i]); if (mis) return addPath(mis, i); } } else { for (var prop in exp) { - var mis = misMatch(exp[prop], act[prop]); + mis = misMatch(exp[prop], act[prop]); if (mis) return addPath(mis, prop); } } diff --git a/qt/org.eclipse.cdt.qt.core/acorn-qml/test/run.js b/qt/org.eclipse.cdt.qt.core/acorn-qml/test/run.js index fb56196c428..88b1184d665 100644 --- a/qt/org.eclipse.cdt.qt.core/acorn-qml/test/run.js +++ b/qt/org.eclipse.cdt.qt.core/acorn-qml/test/run.js @@ -8,11 +8,18 @@ * Contributors: * QNX Software Systems - Initial API and implementation *******************************************************************************/ -"use-strict"; +"use strict"; +// Get the driver and test code var driver = require("./driver.js"); require("./tests-qml.js"); +// Get and inject the QML plugin into Acorn +var acorn = require("acorn"); +require("acorn/dist/acorn_loose"); +require(".."); +require("../loose"); + function group(name) { if (typeof console === "object" && console.group) { console.group(name); @@ -32,7 +39,7 @@ function log(title, message) { var stats, modes = { Normal: { config: { - parse: require("..").parse, + parse: acorn.parse, normal: true, filter: function (test) { var opts = test.options || {}; @@ -42,7 +49,7 @@ var stats, modes = { }, Loose: { config: { - parse: require("../loose").parse_dammit, + parse: acorn.parse_dammit, loose: true, filter: function (test) { var opts = test.options || {}; @@ -63,9 +70,9 @@ for (var name in modes) { group(name); var mode = modes[name]; stats = mode.stats = {testsRun: 0, failed: 0}; - var t0 = +new Date; + var t0 = +new Date(); driver.runTests(mode.config, report); - mode.stats.duration = +new Date - t0; + mode.stats.duration = +new Date() - t0; groupEnd(); } diff --git a/qt/org.eclipse.cdt.qt.core/acorn-qml/test/tests-qml.js b/qt/org.eclipse.cdt.qt.core/acorn-qml/test/tests-qml.js index d50df7a4208..ea43827d7a4 100644 --- a/qt/org.eclipse.cdt.qt.core/acorn-qml/test/tests-qml.js +++ b/qt/org.eclipse.cdt.qt.core/acorn-qml/test/tests-qml.js @@ -8,11 +8,12 @@ * Contributors: * QNX Software Systems - Initial API and implementation *******************************************************************************/ -"use-strict"; +"use strict"; -var test = require("./driver.js").test; -var testFail = require("./driver.js").testFail; -var tokTypes = require("../").tokTypes; +var driver = require("./driver.js"); +var test = driver.test; +var testFail = driver.testFail; +var tokTypes = driver.tokTypes; testFail('', "QML only supports ECMA Script Language Specification 5 or older", { locations: true, ecmaVersion: 6, allowReserved: false }); @@ -3241,7 +3242,7 @@ function program(headerStatements, rootObject) { statements: headerStatements || [] }, rootObject: rootObject || null - } + }; } /* diff --git a/qt/org.eclipse.cdt.qt.core/acorn-qml/walk/index.js b/qt/org.eclipse.cdt.qt.core/acorn-qml/walk/index.js index 4ff5a0769b5..7678b13d403 100644 --- a/qt/org.eclipse.cdt.qt.core/acorn-qml/walk/index.js +++ b/qt/org.eclipse.cdt.qt.core/acorn-qml/walk/index.js @@ -10,7 +10,7 @@ *******************************************************************************/ (function (mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS - return module.exports = mod(require("acorn/dist/walk")); + return mod(require("acorn/dist/walk")); if (typeof define == "function" && define.amd) // AMD return define(["acorn/dist/walk"], mod); mod(acorn.walk); // Plain browser env @@ -18,53 +18,59 @@ "use strict"; function skipThrough(node, st, c) { - c(node, st) + c(node, st); } function ignore(node, st, c) {} - var base = walk.base; - base["QMLProgram"] = function (node, st, c) { - c(node.headerStatements, st); - if (node.rootObject) { - c(node.rootObject, st, "QMLRootObject"); + function extendWalk(walker, funcs) { + for (var prop in funcs) { + walker[prop] = funcs[prop]; } - }; - base["QMLHeaderStatements"] = function (node, st, c) { - for (var i = 0; i < node.statements.length; i++) { - c(node.statements[i], st, "QMLHeaderStatement"); - } - }; - base["QMLHeaderStatement"] = skipThrough; - base["QMLImportStatement"] = ignore; - base["QMLPragmaStatement"] = ignore; - base["QMLRootObject"] = skipThrough; - base["QMLObjectLiteral"] = function (node, st, c) { - c(node.body, st); - }; - base["QMLMemberBlock"] = function (node, st, c) { - for (var i = 0; i < node.members.length; i++) { - c(node.members[i], st, "QMLMember"); - } - }; - base["QMLMember"] = skipThrough; - base["QMLPropertyDeclaration"] = function (node, st, c) { - if (node.binding) { - c(node.binding, st); - } - }; - base["QMLSignalDefinition"] = ignore; - base["QMLPropertyBinding"] = function (node, st, c) { - c(node.binding, st); - }; - base["QMLScriptBinding"] = function (node, st, c) { - c(node.script, st); } - base["QMLQualifiedID"] = ignore; - base["QMLStatementBlock"] = function (node, st, c) { - for (var i = 0; i < node.statements.length; i++) { - c(node.statements[i], st, "Statement"); + + extendWalk(walk.base, { + QMLProgram: function (node, st, c) { + c(node.headerStatements, st); + if (node.rootObject) { + c(node.rootObject, st, "QMLRootObject"); + } + }, + QMLHeaderStatements: function (node, st, c) { + for (var i = 0; i < node.statements.length; i++) { + c(node.statements[i], st, "QMLHeaderStatement"); + } + }, + QMLHeaderStatement: skipThrough, + QMLImportStatement: ignore, + QMLPragmaStatement: ignore, + QMLRootObject: skipThrough, + QMLObjectLiteral: function (node, st, c) { + c(node.body, st); + }, + QMLMemberBlock: function (node, st, c) { + for (var i = 0; i < node.members.length; i++) { + c(node.members[i], st, "QMLMember"); + } + }, + QMLMember: skipThrough, + QMLPropertyDeclaration: function (node, st, c) { + if (node.binding) { + c(node.binding, st); + } + }, + QMLSignalDefinition: ignore, + QMLPropertyBinding: function (node, st, c) { + c(node.binding, st); + }, + QMLScriptBinding: function (node, st, c) { + c(node.script, st); + }, + QMLQualifiedID: ignore, + QMLStatementBlock: function (node, st, c) { + for (var i = 0; i < node.statements.length; i++) { + c(node.statements[i], st, "Statement"); + } } - }; - return walk; -}) \ No newline at end of file + }); +}); \ No newline at end of file diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/demo/defs/ecma5.json.js b/qt/org.eclipse.cdt.qt.core/tern-qml/demo/defs/ecma5.json.js index f5377300acd..ed9f62e03de 100644 --- a/qt/org.eclipse.cdt.qt.core/tern-qml/demo/defs/ecma5.json.js +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/demo/defs/ecma5.json.js @@ -1,5 +1,4 @@ -(function() { -var def = { +var ecma5 = { "!name": "ecma5", "!define": {"Error.prototype": "Error.prototype"}, "Infinity": { @@ -964,6 +963,4 @@ var def = { "!url": "https://developer.mozilla.org/en-US/docs/JSON", "!doc": "JSON (JavaScript Object Notation) is a data-interchange format. It closely resembles a subset of JavaScript syntax, although it is not a strict subset. (See JSON in the JavaScript Reference for full details.) It is useful when writing any kind of JavaScript-based application, including websites and browser extensions. For example, you might store user information in JSON format in a cookie, or you might store extension preferences in JSON in a string-valued browser preference." } -} -CodeMirror.tern.addDef(def); -})(); \ No newline at end of file +} \ No newline at end of file diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/demo/demo.css b/qt/org.eclipse.cdt.qt.core/tern-qml/demo/demo.css new file mode 100644 index 00000000000..4ee93e66992 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/demo/demo.css @@ -0,0 +1,91 @@ +.CodeMirror { + border: 1px solid silver; + height: 400px; + line-height: 1.3; + font-size: 13pt; + font-family: "Ubuntu Mono", monospace; + z-index: 2; +} +.CodeMirror pre { + padding: 0 9px; +} +.CodeMirror-linenumber { + font-size: 80%; + padding-top: 2px; +} + +ul.tabs { + list-style: none; + margin: 0 0 -1px 0; + padding: 0 0 0 37px; + font-family: "Ubuntu Mono", monospace; + overflow: hidden; +} +ul.tabs li { + font-size: 11pt; + color: #777; + cursor: pointer; + margin: 0 2px 0 0; + padding: 2px 10px 5px; + border: 1px solid silver; + border-bottom-width: 0px; + background: #f3f3f3; + display: inline-block; + z-index: 1; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + position: relative; +} +ul.tabs li:hover { + color: #444; +} +ul.tabs li.selected { + background: white; + color: #222; + z-index: 3; + cursor: default; +} +ul.tabs li:before, +ul.tabs li:after { + position: absolute; + bottom: 0; + width: 6px; + height: 6px; + content: " "; + border: 1px solid silver; +} +ul.tabs li:before { + left: -7px; + border-bottom-right-radius: 6px; + border-width: 0 1px 1px 0; + box-shadow: 2px 2px 0 #f3f3f3; +} +ul.tabs li:after { + right: -7px; + border-bottom-left-radius: 6px; + border-width: 0 0 1px 1px; + box-shadow: -2px 2px 0 #f3f3f3; +} +ul.tabs li.selected:before { + box-shadow: 2px 2px 0 white; +} +ul.tabs li.selected:after { + box-shadow: -2px 2px 0 white; +} + +#menus { + position: absolute; + right: 0; + top: 0; +} +#menus select { + font-family: "Ubuntu Mono", monospace; + font-size: 11pt; + width: 8em; +} + +.CodeMirror div.CodeMirror-cursor { + border-left: 2px solid #444; +} + +.CodeMirror-hint { font-size: 12pt; } diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/demo/demo.js b/qt/org.eclipse.cdt.qt.core/tern-qml/demo/demo.js new file mode 100644 index 00000000000..9b04c093709 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/demo/demo.js @@ -0,0 +1,232 @@ +var project; + +function Project(name, place, config, docs) { + this.name = name; + this.docs = Object.create(null); + this.tabs = place.appendChild(document.createElement("ul")); + this.tabs.className = "tabs"; + var self = this; + CodeMirror.on(this.tabs, "click", function (e) { + var target = e.target || e.srcElement; + if (target.nodeName.toLowerCase() != "li") return; + var doc = self.findDoc(target.textContent); + if (doc) self.selectDoc(doc); + }); + + var myConf = { + switchToDoc: function (name) { + self.selectDoc(self.findDoc(name)); + }, + useWorker: false + }; + for (var prop in config) myConf[prop] = config[prop]; + var server = this.server = new CodeMirror.TernServer(myConf); + + var firstDoc; + for (name in docs) { + var data = this.registerDoc(name, new CodeMirror.Doc(docs[name], "javascript")); + if (!firstDoc) firstDoc = data; + } + this.curDoc = firstDoc; + this.setSelectedTab(firstDoc); + + var keyMap = { + "Ctrl-I": function (cm) { + server.showType(cm); + }, + "Ctrl-O": function (cm) { + server.showDocs(cm); + }, + "Ctrl-Space": function (cm) { + server.complete(cm); + }, + "Ctrl-J": function (cm) { + server.jumpToDef(cm); + }, + "Ctrl-,": function (cm) { + server.jumpBack(cm); + }, + "Ctrl-Q": function (cm) { + server.rename(cm); + } + }; + this.editor = new CodeMirror(place, { + mode: 'text/javascript', + theme: "eclipse", + styleActiveLine: true, + lineNumbers: true, + lineWrapping: true, + autoCloseBrackets: true, + matchBrackets: true, + indentWithTabs: true, + indentUnit: 4, + tabSize: 4, + extraKeys: keyMap, + value: firstDoc.doc + }); + this.editor.on("cursorActivity", function (cm) { + server.updateArgHints(cm); + }); +} + +Project.prototype = { + findDoc: function (name) { + return this.docs[name]; + }, + + registerDoc: function (name, doc) { + this.server.addDoc(name, doc); + var data = this.docs[name] = { + name: name, + doc: doc, + tab: this.tabs.appendChild(document.createElement("li")) + }; + data.tab.textContent = name; + return data; + }, + + unregisterDoc: function (doc) { + this.server.delDoc(doc.name); + delete this.docs[doc.name]; + this.tabs.removeChild(doc.tab); + if (this.curDoc == doc) + for (var n in this.docs) return this.selectDoc(this.docs[n]); + }, + + setSelectedTab: function (doc) { + for (var n = this.tabs.firstChild; n; n = n.nextSibling) + n.className = n.textContent == doc.name ? "selected" : ""; + }, + + selectDoc: function (doc) { + if (doc == this.curDoc) return; + this.server.hideDoc(this.curDoc); + this.setSelectedTab(doc); + this.curDoc = doc; + this.editor.swapDoc(doc.doc); + } +}; + +// Initialization + +CodeMirror.on(window, "load", function () { + var cmds = document.getElementById("commands"); + CodeMirror.on(cmds, "change", function () { + if (!project || cmds.selectedIndex === 0) return; + var found = commands[cmds.value]; + cmds.selectedIndex = 0; + project.editor.focus(); + if (found) found(); + }); + + var projects = document.getElementById("projects"); + var projectNames = []; + var projectTags = document.querySelectorAll("project"); + for (var i = 0; i < projectTags.length; i++) { + var opt = projects.appendChild(document.createElement("option")); + projectNames.push(opt.textContent = projectTags[i].id); + } + CodeMirror.on(projects, "change", function () { + initProject(projects.value, function () { + projects.selectedIndex = 0; + project.editor.focus(); + }); + }); + + function updateFromHash() { + var name = location.hash.slice(1); + if (projectNames.indexOf(name) > -1 && + (!project || project.name != name)) { + initProject(name); + return true; + } + } + CodeMirror.on(window, "hashchange", updateFromHash); + + updateFromHash() || initProject("simple"); +}); + +function loadFiles(project, c) { + var found = {}; + + function inner(node) { + while (node && node.nodeName != "PRE") node = node.nextSibling; + if (!node) return c(found); + if (node.hasAttribute("file")) { + load(node.getAttribute("file"), function (data) { + found[node.id] = data; + inner(node.nextSibling); + }); + } else { + found[node.id] = node.textContent; + inner(node.nextSibling); + } + } + inner(project.firstChild); +} + +function words(str) { + return str ? str.split(" ") : []; +} + +function initProject(name, c) { + var node = document.getElementById(name); + var plugins = {}; + words(node.getAttribute("data-plugins")).forEach(function (name) { + plugins[name] = true; + }); + + if (plugins.requirejs) plugins.requirejs = { + override: { + "jquery": "=$" + } + }; + + loadFiles(node, function (files) { + var place = document.getElementById("place"); + place.textContent = ""; + + if (project) project.server.destroy(); + + project = new Project(name, place, { + defs: [ecma5], + plugins: plugins + }, files); + location.hash = "#" + name; + c && c(); + }); +} + +var commands = { + complete: function () { + project.server.complete(project.editor); + }, + jumptodef: function () { + project.server.jumpToDef(project.editor); + }, + finddocs: function () { + project.server.showDocs(project.editor); + }, + findtype: function () { + project.server.showType(project.editor); + }, + rename: function () { + project.server.rename(project.editor); + }, + addfile: function () { + var name = prompt("Name of the new buffer", ""); + if (name == null) return; + if (!name) name = "test"; + var i = 0; + while (project.findDoc(name + (i || ""))) ++i; + project.selectDoc(project.registerDoc(name + (i || ""), new CodeMirror.Doc("", "javascript"))); + }, + delfile: function () { + var hasDoc = false; + for (var _ in project.docs) { + hasDoc = true; + break; + } + if (hasDoc) project.unregisterDoc(project.curDoc); + } +}; \ No newline at end of file diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/demo/docs.css b/qt/org.eclipse.cdt.qt.core/tern-qml/demo/docs.css new file mode 100644 index 00000000000..82b8650fa39 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/demo/docs.css @@ -0,0 +1,142 @@ +@font-face { + font-family: 'Signika'; + font-style: normal; + font-weight: 700; + src: local('Signika-Bold'), url(http://themes.googleusercontent.com/static/fonts/signika/v3/7M5kxD4eGxuhgFaIk95pBRsxEYwM7FgeyaSgU71cLG0.woff) format('woff'); +} + +@font-face { + font-family: 'Ubuntu Mono'; + font-style: normal; + font-weight: 500; + src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url('http://themes.googleusercontent.com/static/fonts/ubuntumono/v3/ViZhet7Ak-LRXZMXzuAfkYbN6UDyHWBl620a-IRfuBk.woff') format('woff'); +} + +body { + font-family: "Helvetica Neue", "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, sans-serif; + color: #222; + font-size: 11pt; + max-width: 700px; + margin: 0 0 0 60px; + padding: 60px 0 4em; +} + +#top { + position: fixed; + top: 0; + background: white; + padding-top: 15px; + left: 20px; right: 20px; + z-index: 5; +} + +#head { + border-radius: 5px; + background: #1c5b64; + box-shadow: 0px 3px 3px rgba(0, 0, 0, .5); + position: relative; + font-family: "Signika", sans-serif; + font-weight: 700; +} + +#head > a { + border-right: 1px solid #276c81; + display: inline-block; + height: 25px; + padding: 5px 20px; + color: white; + text-decoration: none; + line-height: 25px; + font-size: 17px; +} + +#head > a.title { + font-size: 20px; + line-height: 24px; + padding-left: 80px; + background: url(logo.png); + background-position: 40px center; + background-size: auto 80%; + background-repeat: no-repeat; + border-bottom-left-radius: 5px; + border-top-left-radius: 5px; +} + +#head > a:hover { + background-color: #276c81 !important; +} + +.title span { + font-size: 13px; +} + +code, pre { font-family: "Ubuntu Mono", monospace; } +code { color: #124f55; font-size: 110%; } +pre { + border-left: 3px solid #def; + padding: 2px 0 2px 10px; +} + +h1, h2, h3, h4 { + font-family: "Signika", sans-serif; + font-weight: 700; + color: #124f55; +} +h1 { font-size: 20pt; } +h2 { font-size: 17pt; } +h3 { font-size: 13pt; } +h4 { font-size: 11pt; } + +a, a:visited, a code { color: #df4c11; text-decoration: none; } +a:hover { text-decoration: underline; } + +ul, ol { + margin: 0; + padding: 0; +} +li { + margin-left: 1.5em; + padding: 0; +} + +li p { margin: 0; } +dd p:first-child { margin-top: 1px; } +dd { margin-left: 20px; } +dt { text-indent: -1em; padding-left: 1em; } + +a[id] { + position: relative; + display: inline-block; + vertical-align: top; + top: -60px; +} + +.release-note { + margin-left: 1em; +} + +/* CodeMirror default theme for highlighting */ + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable {color: black;} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3 {color: #085;} +.cm-s-default .cm-property {color: black;} +.cm-s-default .cm-operator {color: black;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-error {color: #f00;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/demo/qml-demo.html b/qt/org.eclipse.cdt.qt.core/tern-qml/demo/qml-demo.html index ddb97e23118..75be78a1bef 100644 --- a/qt/org.eclipse.cdt.qt.core/tern-qml/demo/qml-demo.html +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/demo/qml-demo.html @@ -1,102 +1,254 @@ - - - QML Tern Demo - - - - - - - - - - + + + Tern QML Demo - - - - - - - - - + + + + - - - - - - - + + - - + + + + + - - - - + + + + - - - - + +

Demo with QML Tern plugin

+ QML Tern Demo - - + + + + + + + + - - + + + + + + + + + - - - - - - -

Demo with QML Tern plugin

-
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ - - - + \ No newline at end of file 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 6f2ca438aca..9e49cac257a 100644 --- a/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js @@ -8,130 +8,507 @@ * Contributors: * QNX Software Systems - Initial API and implementation *******************************************************************************/ -(function (mod) { +(function (root, mod) { if (typeof exports === "object" && typeof module === "object") // CommonJS - return mod(require("acorn"), require("acorn/dist/acorn_loose"), require("acorn/dist/walk"), require("acorn-qml"), - require("acorn-qml/loose"), require("acorn-qml/walk"), require("tern/lib/infer"), require("tern")); + return mod(exports, require("acorn"), require("acorn/dist/acorn_loose"), require("acorn/dist/walk"), + require("acorn-qml"), require("acorn-qml/loose"), require("acorn-qml/walk"), require("tern"), + require("tern/lib/infer"), require("tern/lib/signal")); if (typeof define === "function" && define.amd) // AMD - return define([ "acorn", "acorn/dist/acorn_loose", "acorn/dist/walk", "acorn-qml", "acorn-qml/loose", "acorn-qml/walk", - "tern/lib/infer", "tern"], mod); - mod(acorn, acorn, acorn.walk, acorn, acorn, acorn.walk, tern, tern, tern.def); // Plain browser env -})(function (acorn, acornLoose, walk, acornQML, acornQMLLoose, acornQMLWalk, infer, tern, def) { + return define(["exports", "acorn/dist/acorn", "acorn/dist/acorn_loose", "acorn/dist/walk", "acorn-qml", + "acorn-qml/loose", "acorn-qml/walk", "tern", "tern/lib/infer", "tern/lib/signal"], mod); + mod(root.ternQML || (root.ternQML = {}), acorn, acorn, acorn.walk, acorn, acorn, acorn.walk, tern, tern, tern.signal); // Plain browser env +})(this, function (exports, acorn, acornLoose, walk, acornQML, acornQMLLoose, acornQMLWalk, tern, infer, signal) { 'use strict'; - // Outside of the browser environment we have to grab 'def' from 'infer' - if (!def) { - def = infer.def; + // Grab 'def' from 'infer' (used for jsDefs) + var def = infer.def; + + // 'extend' taken from infer.js + function extend(proto, props) { + var obj = Object.create(proto); + if (props) { + for (var prop in props) obj[prop] = props[prop]; + } + return obj; } - // QML Scope Builder - var ScopeBuilder = function (file, jsDefs) { - this.file = file; - this.file.qmlIDScope = new infer.Scope(); - this.file.qmlIDScope.name = ""; - this.jsDefs = jsDefs; - this.scopes = []; - this.jsScopes = []; - this.propScopes = []; - this.sigScopes = []; + // QML Import Handler + var qmlImportHandler = exports.importHandler = null; + var ImportHandler = function (server) { + this.server = server; + this.imports = null; }; - - ScopeBuilder.prototype.getRootScope = function () { - return this.scopes.length > 0 ? this.scopes[0] : null; - }; - - ScopeBuilder.prototype.isRootScope = function (scope) { - return scope === this.rootScope(); - }; - - ScopeBuilder.prototype.newObjScope = function (node) { - var rootScope = this.getRootScope(), scope; - if (!rootScope) { - scope = new infer.Scope(this.file.scope, node); - } else { - scope = new infer.Scope(rootScope, node); - } - scope.name = ""; - this.scopes.push(scope); - return scope; - }; - - ScopeBuilder.prototype.forEachObjScope = function (callback) { - for (var i = 0; i < this.scopes.length; i++) { - var scope = this.scopes[i]; - if (scope.name === "") { - callback(scope); + ImportHandler.prototype = { + reset: function () { + this.imports = null; + }, + resolveDirectory: function (file, path) { + var impl = this.server.options.resolveDirectory; + if (impl) { + return impl(file, path); } - } - }; - - ScopeBuilder.prototype.getObjScope = function (scope) { - while (scope && scope.name != "") { - scope = scope.prev; - } - return scope; - }; - - ScopeBuilder.prototype.getIDScope = function () { - return this.file.qmlIDScope; - }; - - ScopeBuilder.prototype.newPropertyScope = function (objScope, node) { - var propScope = new infer.Scope(null, node); - propScope.name = ""; - propScope.objScope = objScope; - this.propScopes.push(propScope); - return propScope; - }; - - ScopeBuilder.prototype.forEachPropertyScope = function (callback) { - for (var i = 0; i < this.propScopes.length; i++) { - callback(this.propScopes[i], this.propScopes[i].objScope); - } - }; - - ScopeBuilder.prototype.newJSScope = function (scope, node) { - var jsScope = new infer.Scope(scope, node); - jsScope.name = ""; - var curOrigin = infer.cx().curOrigin; - for (var i = 0; i < this.jsDefs.length; ++i) - def.load(this.jsDefs[i], jsScope); - infer.cx().curOrigin = curOrigin; - this.jsScopes.push(jsScope); - return jsScope; - }; - - ScopeBuilder.prototype.forEachJSScope = function (callback) { - for (var i = 0; i < this.jsScopes.length; i++) { - callback(this.jsScopes[i]); - } - }; - - ScopeBuilder.prototype.newSignalScope = function (scope, node) { - var sigScope = new infer.Scope(scope, node); - sigScope.name = ""; - this.sigScopes.push(sigScope); - return sigScope - }; - - ScopeBuilder.prototype.hasFunctionScope = function (objScope) { - var found = null; - this.forEachFunctionScope(function (scope) { - if (scope.objScope == objScope) { - found = scope; + // Getting to this point means that we were unable to find an implementation of + // the 'resolveDirectory' method. The only time this should happen is during + // a test case which we expect to have an import of the style "./ ..." and nothing + // else. This method will simply remove the './', add the file's base, and return. + if (!path) { + // If no path was specified, return the base directory of the file + var dir = file.name; + dir = dir.substring(0, dir.lastIndexOf("/") + 1); + return dir; } - }); + if (path.startsWith("./")) { + path = file.directory + path.substring(2); + } + if (path.substr(path.length - 1, 1) !== "/") { + path = path + "/"; + } + return path; + }, + updateDirectoryImportList: function () { + if (!this.imports) { + this.imports = {}; + } + var dir, f; + var seenDirs = {}; + for (var i = 0; i < this.server.files.length; i++) { + var file = this.server.files[i]; + dir = file.directory; + f = file.nameExt; + if (!dir) { + // Resolve the directory name and file name/extension + dir = file.directory = this.resolveDirectory(file, null); + f = file.nameExt = this.getFileNameAndExtension(file); + } + seenDirs[dir] = true; + // No file scope means the file was recently added/changed and we should + // update its import reference + if (!file.scope) { + // Check for a valid QML Object Identifier + if (f.extension === "qml") { + var ch = f.name.charAt(0); + if (ch.toUpperCase() === ch && f.name.indexOf(".") === -1) { + // Create the array for this directory if necessary + if (!this.imports[dir]) { + this.imports[dir] = {}; + } + + // Create an Obj to represent this import + var obj = new infer.Obj(null, f.name); + obj.origin = file.name; + this.imports[dir][f.name] = obj; + } + } + } + } + for (dir in this.imports) { + if (!(dir in seenDirs)) { + this.imports[dir] = undefined; + } + } + }, + getFileNameAndExtension: function (file) { + var fileName = file.name.substring(file.name.lastIndexOf("/") + 1); + var dot = fileName.lastIndexOf("."); + return { + name: dot >= 0 ? fileName.substring(0, dot) : fileName, + extension: fileName.substring(dot + 1) + }; + }, + resolveObject: function (loc, name) { + return loc[name]; + }, + defineImport: function (scope, loc, name, obj) { + var prop = scope.defProp(name); + var objRef = new ObjRef(loc, name, this); + prop.objType = objRef; + objRef.propagate(prop); + }, + defineImports: function (file, scope) { + scope = scope || file.scope; + + // Add any imports from the current directory + var imports = this.imports[file.directory]; + var f = file.nameExt; + if (imports) { + for (var name in imports) { + if (f.name !== name) { + this.defineImport(scope, imports, name, imports[name]); + } + } + } + + // Walk the AST for any imports + var ih = this; + walk.simple(file.ast, { + QMLImportStatement: function (node) { + var prop = null; + var scope = file.scope; + if (node.qualifier) { + prop = file.scope.defProp(node.qualifier.id.name, node.qualifier.id); + prop.origin = file.name; + var obj = new infer.Obj(null, node.qualifier.id.name); + obj.propagate(prop); + prop.objType = obj; + scope = obj; + } + if (node.directory) { + var dir = ih.resolveDirectory(file, node.directory.value); + var imports = ih.imports[dir]; + if (imports) { + for (var name in imports) { + ih.defineImport(scope, imports, name, imports[name]); + } + } + } + } + }); + }, + createQMLObjectType: function (file, node, isRoot) { + // Find the imported object + var obj = this.getQMLObjectType(file, node.id); + // If this is the root, connect the imported object to the root object + if (isRoot) { + var tmp = this.getRootQMLObjectType(file, node.id); + if (tmp) { + // Hook up the Obj Reference + tmp.proto = obj; + obj = tmp; + obj.originNode = node.id; + + // Break any cyclic dependencies + while ((tmp = tmp.proto)) { + if (tmp.resolve() == obj.resolve()) { + tmp.proto = null; + } + } + } + } + return obj; + }, + getQMLObjectType: function (file, qid) { + var prop = findProp(qid, file.scope); + if (prop) { + return prop.objType; + } + return new infer.Obj(null, qid.name); + }, + getRootQMLObjectType: function (file, qid) { + var f = file.nameExt; + var imports = this.imports[file.directory]; + if (imports && imports[f.name]) { + return imports[f.name]; + } + return new infer.Obj(null, qid.name); + } + }; + + // 'isInteger' taken from infer.js + function isInteger(str) { + var c0 = str.charCodeAt(0); + if (c0 >= 48 && c0 <= 57) return !/\D/.test(str); + else return false; + } + + /* + * We have to redefine 'hasProp' to make it work with our scoping. The original 'hasProp' + * function checked proto.props instead of using proto.hasProp. + */ + infer.Obj.prototype.hasProp = function (prop, searchProto) { + if (isInteger(prop)) prop = this.normalizeIntegerProp(prop); + var found = this.props[prop]; + if (searchProto !== false && this.proto && !found) + found = this.proto.hasProp(prop, true); return found; }; - // Helper for adding a property to a scope. + // Creating a resolve function on 'infer.Obj' so we can simplify some of our 'ObjRef' logic + infer.Obj.prototype.resolve = function () { + return this; + }; + + /* + * QML Object Reference + * + * An ObjRef behaves exactly the same as an ordinary 'infer.Obj' object, except that it + * mirrors its internal state to a referenced object. This object is resolved by the QML + * Import Handler each time the ObjRef is accessed (including getting and setting internal + * variables). In theory this means we don't have to know at runtime whether or not an + * object is an ObjRef or an infer.Obj. + */ + var ObjRef = function (loc, lookup, ih) { + // Using underscores for property names so we don't accidentally collide with any + // 'infer.Obj' property names (which would cause a stack overflow if we were to + // try to access them here). + this._loc = loc; + this._objLookup = lookup; + this._ih = ih; + var obj = this.resolve(); + // Use Object.defineProperty to setup getter and setter methods that delegate + // to the resolved object's properties. We only need to do this once since all + // 'infer.Obj' objects should have the same set of property names. + for (var propertyName in obj) { + if (!(obj[propertyName] instanceof Function)) { + (function () { + var prop = propertyName; + Object.defineProperty(this, prop, { + enumerable: true, + get: function () { + return this.resolve()[prop]; + }, + set: function (value) { + this.resolve()[prop] = value; + } + }); + }).call(this); + } + } + }; + ObjRef.prototype = extend(infer.Type.prototype, { + resolve: function () { + return this._ih.resolveObject(this._loc, this._objLookup); + } + }); + (function () { + // Wire up all base functions to use the resolved object's implementation + for (var _func in infer.Obj.prototype) { + if (_func !== "resolve") { + (function () { + var fn = _func; + ObjRef.prototype[fn] = function () { + return this.resolve()[fn](arguments[0], arguments[1], arguments[2], arguments[3]); + }; + })(); + } + } + })(); + + /* + * QML Object Scope (inherits methods from infer.Scope) + * + * A QML Object Scope does not contain its own properties. Instead, its properties + * are defined in its given Object Type and resolved from there. Any properties + * defined within the Object Type are visible without qualifier to any downstream + * scopes. + */ + var QMLObjScope = exports.QMLObjScope = function (prev, originNode, objType) { + infer.Scope.call(this, prev, originNode, false); + this.objType = objType; + }; + QMLObjScope.prototype = extend(infer.Scope.prototype, { + hasProp: function (prop, searchProto) { + // Search for a property in the Object type. + // Always search the Object Type's prototype as well + var found = this.objType.hasProp(prop, true); + if (found) { + return found; + } + + // Search for a property in the prototype (previous scope) + if (this.proto && searchProto !== false) { + return this.proto.hasProp(prop, searchProto); + } + }, + defProp: function (prop, originNode) { + return this.objType.defProp(prop, originNode); + }, + removeProp: function (prop) { + return this.objType.removeProp(prop); + }, + gatherProperties: function (f, depth) { + // Gather properties from the Object Type and its prototype(s) + var obj = this.objType; + var callback = function (prop, obj, d) { + f(prop, obj, depth); + }; + while (obj) { + obj.gatherProperties(callback, depth); + obj = obj.proto; + } + // gather properties from the prototype (previous scope) + if (this.proto) { + this.proto.gatherProperties(f, depth + 1); + } + } + }); + + /* + * QML Member Scope (inherits methods from infer.Scope) + * + * A QML Member Scope is a bit of a special case when it comes to QML scoping. Like + * the QML Object Scope, it does not contain any properties of its own. The reason + * that it is special is it only gathers properties from its immediate predecessor + * that aren't functions (i.e. They don't have the 'isFunction' flag set. The + * 'isFunction' flag is created by QML signal properties and JavaScript functions + * that are QML Members.) + */ + var QMLMemScope = exports.QMLMemScope = function (prev, originNode, fileScope) { + infer.Scope.call(this, prev, originNode, false); + this.fileScope = fileScope; + }; + QMLMemScope.prototype = extend(infer.Scope.prototype, { + hasProp: function (prop, searchProto) { + // Search for a property in the prototype + var found = null; + if (this.proto) { + // Don't continue searching after the previous scope + found = this.proto.hasProp(prop, false); + if (found && !found.isFunction) { + return found; + } + } + + // Search for a property in the file Scope + if (this.fileScope) { + return this.fileScope.hasProp(prop, searchProto); + } + }, + defProp: function (prop, originNode) { + return this.prev.defProp(prop, originNode); + }, + removeProp: function (prop) { + return this.prev.removeProp(prop); + }, + gatherProperties: function (f, depth) { + // Gather properties from the prototype (previous scope) + var found = null; + if (this.proto) { + this.proto.gatherProperties(function (prop, obj, d) { + // Don't continue passed the predecessor by checking depth + if (d === depth) { + var propObj = obj.hasProp(prop); + if (propObj && !propObj.isFunction) { + f(prop, obj, d); + } + } + }, depth); + } + // Gather properties from the file Scope + this.fileScope.gatherProperties(f, depth); + } + }); + + /* + * QML JavaScript Scope (inherits methods from infer.Scope) + * + * A QML JavaScript Scope also contains references to the file's ID Scope, the global + * JavaScript Scope, and a possible function parameter scope. Most likely, this + * scope will not contain its own properties. The resolution order for 'getProp' and + * 'hasProp' are: + * 1. The ID Scope + * 2. This Scope's properties + * 3. The Function Scope (if it exists) + * 4. The JavaScript Scope + * 5. The Previous Scope in the chain + */ + var QMLJSScope = exports.QMLJSScope = function (prev, originNode, idScope, jsScope, fnScope) { + infer.Scope.call(this, prev, originNode, false); + this.idScope = idScope; + this.jsScope = jsScope; + this.fnScope = fnScope; + }; + QMLJSScope.prototype = extend(infer.Scope.prototype, { + hasProp: function (prop, searchProto) { + if (isInteger(prop)) { + prop = this.normalizeIntegerProp(prop); + } + // Search the ID scope + var found = null; + if (this.idScope) { + found = this.idScope.hasProp(prop, searchProto); + } + // Search the current scope + if (!found) { + found = this.props[prop]; + } + // Search the Function Scope + if (!found && this.fnScope) { + found = this.fnScope.hasProp(prop, searchProto); + } + // Search the JavaScript Scope + if (!found && this.jsScope) { + found = this.jsScope.hasProp(prop, searchProto); + } + // Search the prototype (previous scope) + if (!found && this.proto && searchProto !== false) { + found = this.proto.hasProp(prop, searchProto); + } + return found; + }, + gatherProperties: function (f, depth) { + // Gather from the ID Scope + if (this.idScope) { + this.idScope.gatherProperties(f, depth); + } + // Gather from the current scope + for (var prop in this.props) { + f(prop, this, depth); + } + // Gather from the Function Scope + if (this.fnScope) { + this.fnScope.gatherProperties(f, depth); + } + // Gather from the JS Scope + if (this.jsScope) { + this.jsScope.gatherProperties(f, depth); + } + // Gather from the prototype (previous scope) + if (this.proto) { + this.proto.gatherProperties(f, depth + 1); + } + } + }); + + // QML Scope Builder + var ScopeBuilder = function (file, jsDefs) { + // File Scope + this.scope = file.scope; + this.file = file; + // ID Scope + this.idScope = new infer.Scope(); + this.idScope.name = ""; + // JavaScript Scope + this.jsScope = new infer.Scope(); + this.jsScope.name = ""; + var curOrigin = infer.cx().curOrigin; + for (var i = 0; i < jsDefs.length; ++i) { + def.load(jsDefs[i], this.jsScope); + } + infer.cx().curOrigin = curOrigin; + }; + ScopeBuilder.prototype = { + newObjScope: function (node) { + var obj = qmlImportHandler.createQMLObjectType(this.file, node, !this.rootScope); + var scope = new QMLObjScope(this.rootScope || this.scope, node, obj); + scope.name = ""; + if (!this.rootScope) { + this.rootScope = scope; + } + return scope; + }, + getIDScope: function () { + return this.idScope; + }, + newMemberScope: function (objScope, node) { + var memScope = new QMLMemScope(objScope, node, this.scope); + memScope.name = ""; + return memScope; + }, + newJSScope: function (scope, node, fnScope) { + var jsScope = new QMLJSScope(scope, node, this.idScope, this.jsScope, fnScope); + jsScope.name = ""; + return jsScope; + }, + }; + + // Helper for adding a variable to a scope. function addVar(scope, node) { return scope.defProp(node.name, node); } // Helper for finding a property in a scope. function findProp(node, scope, pos) { - if (pos == null || pos < 0) { + if (pos === null || pos === undefined || pos < 0) { pos = Number.MAX_VALUE; } if (node.type === "QMLQualifiedID") { @@ -161,124 +538,11 @@ 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; - } - - // Helper for getting the current file's scope builder + // Helper for getting the current context's scope builder function getScopeBuilder() { return infer.cx().qmlScopeBuilder; } - // Object which holds two scopes - function Scopes(object, property) { - this.object = object; - this.property = property || object; - } - - function extendTernScopeGatherer(scopeGatherer) { - // Build up the scopes in the QML document using the scopeGatherer. A second - // pass will be done after the scopeGatherer executes in order to finalize a - // few of the extra scopes such as the JavaScript, ID, Property, and Signal - // Handler scopes. - scopeGatherer["QMLObjectLiteral"] = function (node, scope, c) { - var inner = node.scope = getScopeBuilder().newObjScope(node); - c(node.body, inner); - }; - scopeGatherer["QMLMemberBlock"] = function (node, scope, c) { - var s = infer.cx().curOrigin; - var propertyScope = node.scope = getScopeBuilder().newPropertyScope(scope, node); - var scopes = new Scopes(scope, propertyScope); - for (var i = 0; i < node.members.length; i++) { - var member = node.members[i]; - if (member.type === "FunctionDeclaration") { - c(member, scope); - - // Insert the JavaScript scope after the Function has had a chance to build it's own scope - var jsScope = getScopeBuilder().newJSScope(scope, member); - member.scope.prev = member.scope.proto = jsScope; - - // Indicate that the property is a function - var prop = scope.hasProp(member.id.name); - if (prop) { - prop.isFunction = true; - } - } else if (member.type === "QMLPropertyDeclaration" || member.type === "QMLPropertyBinding") { - c(member, scopes); - } else { - c(member, scope); - } - } - }; - scopeGatherer["QMLPropertyDeclaration"] = function (node, scopes, c) { - var inner = scopes.object; - var prop = addVar(inner, node.id); - if (node.binding) { - node.binding.scope = inner; - c(node.binding, inner); - } - }; - scopeGatherer["QMLPropertyBinding"] = function (node, scopes, c) { - // Create a JavaScript scope if init is a JavaScript environment - var inner = node.binding.scope = scopes.object; - // Check for the 'id' property being set - if (node.id.name == "id") { - if (node.binding.type === "QMLScriptBinding") { - var binding = node.binding; - if (!binding.block && binding.script.type === "Identifier") { - node.prop = addVar(getScopeBuilder().getIDScope(), binding.script); - } - } - } else { - // If this appears to be a signal handler, pre-emptively create a new scope that - // will store references to the signal's arguments - if (node.id.name.startsWith("on")) { - inner = node.binding.scope = new infer.Scope(inner, node); - } - } - - // Delegate down to the expression - c(node.binding, inner); - }; - scopeGatherer["QMLScriptBinding"] = function (node, scope, c) { - var inner = node.scope = getScopeBuilder().newJSScope(node.scope || scope, node); - c(node.script, inner); - }; - scopeGatherer["QMLStatementBlock"] = function (node, scope, c) { - var inner = getScopeBuilder().newJSScope(node.scope || scope, node); - node.scope = inner; - for (var i = 0; i < node.statements.length; i++) { - c(node.statements[i], inner, "Statement"); - } - }; - scopeGatherer["QMLSignalDefinition"] = function (node, scope, c) { - // Scope Builder - var sb = getScopeBuilder(); - - // Define the signal arguments in their own separate scope - var argNames = [], - argVals = []; - var sigScope = sb.newSignalScope(scope, node); - for (var i = 0; i < node.params.length; i++) { - var param = node.params[i]; - argNames.push(param.id.name); - argVals.push(addVar(sigScope, param.id)); - } - - // Define the signal function type which can be referenced from JavaScript - var sig = addVar(scope, node.id); - sig.isFunction = true; - sig.sigType = new infer.Fn(node.id.name, new infer.AVal, argVals, argNames, infer.ANull); - sig.sigType.sigScope = sigScope; - - // Define the signal handler property - var handler = scope.defProp(getSignalHandlerName(node.id.name), node.id); - handler.sig = sig.sigType; - } - } - // 'infer' taken from infer.js function inf(node, scope, out, name) { var handler = infer.inferExprVisitor[node.type]; @@ -288,19 +552,19 @@ // Infers the property's type from its given primitive value function infKind(kind, out) { switch (kind) { - case "int": - case "double": - case "real": - infer.cx().num.propagate(out); - break; - case "string": - case "color": - infer.cx().str.propagate(out); - break; - case "boolean": - infer.cx().bool.propagate(out); - break; - } + case "int": + case "double": + case "real": + infer.cx().num.propagate(out); + break; + case "string": + case "color": + infer.cx().str.propagate(out); + break; + case "boolean": + infer.cx().bool.propagate(out); + break; + } } // 'ret' taken from infer.js @@ -314,199 +578,306 @@ // 'fill' taken from infer.js function fill(f) { - return function(node, scope, out, name) { - if (!out) out = new AVal; + return function (node, scope, out, name) { + if (!out) out = new infer.AVal(); f(node, scope, out, name); return out; }; } - function extendTernInferExprVisitor(inferExprVisitor) { - // Extend the inferExprVisitor methods - inferExprVisitor["QMLStatementBlock"] = ret(function (node, scope, name) { - return infer.ANull; // Statement blocks have no type - }); - inferExprVisitor["QMLScriptBinding"] = fill(function (node, scope, out, name) { - return inf(node.script, node.scope || scope, out, name); - }); - inferExprVisitor["QMLObjectLiteral"] = ret(function (node, scope, name) { - return node.scope.objType; - }); - } - + // Helper method to get the last index of an array function getLastIndex(arr) { return arr[arr.length - 1]; } + // Helper method to get the signal handler name of a signal function function getSignalHandlerName(str) { return "on" + str.charAt(0).toUpperCase() + str.slice(1); } - function extendTernInferWrapper(inferWrapper) { - // 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; + // Object which holds two scopes. Used to store both the Member Scope and Object + // Scope for QML Property Bindings and Declarations. + function Scopes(obj, mem) { + this.object = obj; + this.member = mem; + } + + // Helper to add functionality to a set of walk methods + function extendWalk(walker, funcs) { + for (var prop in funcs) { + walker[prop] = funcs[prop]; + } + } + + function extendTernScopeGatherer(scopeGatherer) { + // Extend the Tern scopeGatherer to build up our custom QML scoping + extendWalk(scopeGatherer, { + QMLObjectLiteral: function (node, scope, c) { + var inner = node.scope = getScopeBuilder().newObjScope(node); + c(node.body, inner); + }, + QMLMemberBlock: function (node, scope, c) { + var memScope = node.scope = getScopeBuilder().newMemberScope(scope, node); + for (var i = 0; i < node.members.length; i++) { + var member = node.members[i]; + if (member.type === "FunctionDeclaration") { + c(member, scope); + + // Insert the JavaScript scope after the Function has had a chance to build it's own scope + var jsScope = getScopeBuilder().newJSScope(scope, member, member.scope); + jsScope.fnType = member.scope.fnType; + member.scope.prev = member.scope.proto = null; + member.scope = jsScope; + + // Indicate that the property is a function + var prop = scope.hasProp(member.id.name); + if (prop) { + prop.isFunction = true; + } + } else if (member.type === "QMLPropertyDeclaration" || member.type === "QMLPropertyBinding") { + c(member, new Scopes(scope, memScope)); + } else { + c(member, scope); + } } - }); - c(node.body, node.scope); - }; - inferWrapper["QMLMemberBlock"] = function (node, scope, c) { - var scopes = new Scopes(scope, node.scope); - for (var i = 0; i < node.members.length; i++) { - var member = node.members[i]; - if (member.type === "QMLPropertyDeclaration" || member.type === "QMLPropertyBinding") { - c(member, scopes); - } else { - c(member, scope); - } - } - }; - inferWrapper["QMLPropertyDeclaration"] = function (node, scopes, c) { - var prop = findProp(node.id, scopes.property); - if (prop) { - infKind(node.kind, prop); + }, + QMLPropertyDeclaration: function (node, scopes, c) { + var prop = addVar(scopes.member, node.id); if (node.binding) { c(node.binding, scopes.object); - inf(node.binding, scopes.object, prop, node.id.name); } - } - }; - inferWrapper["QMLPropertyBinding"] = function (node, scopes, c) { - c(node.binding, scopes.object); - // Check for the 'id' property being set - if (node.id.name === "id") { - if (node.binding.type === "QMLScriptBinding") { - var binding = node.binding; - if (binding.script.type === "Identifier") { - scopes.object.objType.propagate(node.prop); + }, + QMLPropertyBinding: function (node, scopes, c) { + // Check for the 'id' property being set + if (node.id.name == "id") { + if (node.binding.type === "QMLScriptBinding") { + var binding = node.binding; + if (!binding.block && binding.script.type === "Identifier") { + node.prop = addVar(getScopeBuilder().getIDScope(), binding.script); + } } } - } else { - var prop = findProp(node.id, scopes.property); - 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.sigScope.forAllProps(function (name, prop, curr) { - if (curr) { - node.binding.scope.props[name] = prop; - } - }); - } else { - inf(node.binding, node.binding.scope, prop, getLastIndex(node.id.parts)); - } + // Delegate down to the expression + c(node.binding, scopes.object); + }, + QMLScriptBinding: function (node, scope, c) { + var inner = node.scope = getScopeBuilder().newJSScope(scope, node); + c(node.script, inner); + }, + QMLStatementBlock: function (node, scope, c) { + var inner = getScopeBuilder().newJSScope(scope, node); + node.scope = inner; + for (var i = 0; i < node.statements.length; i++) { + c(node.statements[i], inner, "Statement"); } - } - }; - 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); - for (var i = 0; i < node.params.length; i++) { - var param = node.params[i]; - infKind(param.kind.name, sig.sigType.args[i]); - } - sig.sigType.retval = infer.ANull; - sig.sigType.propagate(sig); + }, + QMLSignalDefinition: function (node, scope, c) { + // Scope Builder + var sb = getScopeBuilder(); - var handler = scope.getProp(getSignalHandlerName(node.id.name)); - var obj = new infer.Obj(true, "Signal Handler"); - obj.propagate(handler); - } + // Define the signal arguments in their own separate scope + var argNames = []; + var argVals = []; + var sigScope = new infer.Scope(null, node); + for (var i = 0; i < node.params.length; i++) { + var param = node.params[i]; + argNames.push(param.id.name); + argVals.push(addVar(sigScope, param.id)); + } + + // Define the signal function type which can be referenced from JavaScript + var sig = addVar(scope, node.id); + sig.isFunction = true; + sig.sigType = new infer.Fn(node.id.name, new infer.AVal(), argVals, argNames, infer.ANull); + sig.sigType.sigScope = sigScope; + + // Define the signal handler property + var handler = scope.defProp(getSignalHandlerName(node.id.name), node.id); + handler.sig = sig.sigType; + } + }); + } + + function extendTernInferExprVisitor(inferExprVisitor) { + // Extend the inferExprVisitor methods + extendWalk(inferExprVisitor, { + QMLStatementBlock: ret(function (node, scope, name) { + return infer.ANull; // TODO: check return statements + }), + QMLScriptBinding: fill(function (node, scope, out, name) { + return inf(node.script, node.scope, out, name); + }), + QMLObjectLiteral: ret(function (node, scope, name) { + return node.scope.objType; + }) + }); + } + + function extendTernInferWrapper(inferWrapper) { + // Extend the inferWrapper methods + extendWalk(inferWrapper, { + QMLObjectLiteral: function (node, scope, c) { + c(node.body, node.scope); + }, + QMLMemberBlock: function (node, scope, c) { + for (var i = 0; i < node.members.length; i++) { + var member = node.members[i]; + if (member.type === "QMLPropertyDeclaration" || member.type === "QMLPropertyBinding") { + c(member, new Scopes(scope, node.scope)); + } else { + c(member, scope); + } + } + }, + QMLPropertyDeclaration: function (node, scopes, c) { + var prop = findProp(node.id, scopes.member); + if (prop) { + infKind(node.kind, prop); + if (node.binding) { + c(node.binding, scopes.object); + inf(node.binding, scopes.object, prop, node.id.name); + } + } + }, + QMLPropertyBinding: function (node, scopes, c) { + c(node.binding, scopes.object); + // Check for the 'id' property being set + if (node.id.name === "id") { + if (node.binding.type === "QMLScriptBinding") { + var binding = node.binding; + if (binding.script.type === "Identifier") { + scopes.object.objType.propagate(node.prop); + } + } + } else { + var prop = findProp(node.id, scopes.member); + if (prop) { + if (prop.sig) { + // This is a signal handler + node.binding.scope.fnScope = prop.sig.sigScope; + } else { + inf(node.binding, scopes.object, prop, getLastIndex(node.id.parts)); + } + } + } + }, + QMLScriptBinding: function (node, scope, c) { + c(node.script, node.scope); + }, + QMLStatementBlock: function (node, scope, c) { + for (var i = 0; i < node.statements.length; i++) { + c(node.statements[i], node.scope, "Statement"); + } + }, + QMLSignalDefinition: function (node, scope, c) { + var sig = scope.getProp(node.id.name); + for (var i = 0; i < node.params.length; i++) { + var param = node.params[i]; + infKind(param.kind.name, sig.sigType.args[i]); + } + sig.sigType.retval = infer.ANull; + sig.sigType.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["QMLMemberBlock"] = function (node, scope) { - return infer.ANull; - }; - typeFinder["FunctionDeclaration"] = function (node, scope) { - return scope.name === "" ? infer.ANull : undefined; - }; - typeFinder["QMLScriptBinding"] = function (node, scope) { - // Trick Tern into thinking this node is a type so that it will use - // this node's scope when handling improperly written script bindings - return infer.ANull; - }; - typeFinder["QMLQualifiedID"] = function (node, scope) { - return findProp(node, scope) || infer.ANull; - }; - typeFinder["QML_ID"] = function (node, scope) { - // Reverse the hack from search visitor before finding the property in - // the id scope - node.type = "Identifier"; - return findProp(node, getScopeBuilder().getIDScope()); - }; + extendWalk(typeFinder, { + QMLObjectLiteral: function (node, scope) { + return node.scope.objType; + }, + QMLMemberBlock: function (node, scope) { + return infer.ANull; + }, + FunctionDeclaration: function (node, scope) { + // Quick little hack to get 'findExprAt' to find a Function Declaration which + // is a QML Object Member. All other Function Declarations are ignored. + return scope.name === "" ? infer.ANull : undefined; + }, + QMLScriptBinding: function (node, scope) { + // Trick Tern into thinking this node is a type so that it will use + // this node's scope when handling improperly written script bindings + return infer.ANull; + }, + QMLQualifiedID: function (node, scope) { + return findProp(node, scope) || infer.ANull; + }, + QML_ID: function (node, scope) { + // Reverse the hack from search visitor before finding the property in + // the id scope + node.type = "Identifier"; + return findProp(node, getScopeBuilder().getIDScope()); + } + }); } function extendTernSearchVisitor(searchVisitor) { // Extend the search visitor to traverse the scope properly - searchVisitor["QMLObjectLiteral"] = function (node, scope, c) { - c(node.body, node.scope); - }; - searchVisitor["QMLMemberBlock"] = function (node, scope, c) { - var scopes = new Scopes(scope, node.scope); - for (var i = 0; i < node.members.length; i++) { - var member = node.members[i]; - if (member.type === "QMLPropertyDeclaration" || member.type === "QMLPropertyBinding") { - c(member, scopes); - } else { - c(member, scope); - } - } - }; - searchVisitor["QMLSignalDefinition"] = function (node, scope, c) { - c(node.id, scope); - }; - searchVisitor["QMLPropertyDeclaration"] = function (node, scopes, c) { - if (node.binding) { - c(node.binding, node.binding.scope); - } - }; - searchVisitor["QMLPropertyBinding"] = function (node, scopes, c) { - if (node.id.name === "id") { - if (node.binding.type === "QMLScriptBinding") { - var binding = node.binding; - if (binding.script.type === "Identifier") { - // Hack to bypass Tern's type finding algorithm which uses node.type instead - // of the overriden type. - binding.script.type = "QML_ID"; - c(binding.script, binding.scope, "QML_ID"); - binding.script.type = "Identifier"; + extendWalk(searchVisitor, { + QMLObjectLiteral: function (node, scope, c) { + c(node.body, node.scope); + }, + QMLMemberBlock: function (node, scope, c) { + for (var i = 0; i < node.members.length; i++) { + var member = node.members[i]; + if (member.type === "QMLPropertyDeclaration" || member.type === "QMLPropertyBinding") { + c(member, new Scopes(scope, node.scope)); + } else { + c(member, scope); } } - var prop = findProp(node.id, scopes.property); - if (!prop) { - return; + }, + QMLSignalDefinition: function (node, scope, c) { + c(node.id, scope); + }, + QMLPropertyDeclaration: function (node, scopes, c) { + c(node.id, scopes.member); + if (node.binding) { + c(node.binding, scopes.object); + } + }, + QMLPropertyBinding: function (node, scopes, c) { + if (node.id.name === "id") { + if (node.binding.type === "QMLScriptBinding") { + var binding = node.binding; + if (binding.script.type === "Identifier") { + // Hack to bypass Tern's type finding algorithm which uses node.type instead + // of the overriden type. + binding.script.type = "QML_ID"; + c(binding.script, binding.scope, "QML_ID"); + binding.script.type = "Identifier"; + } + } + var prop = findProp(node.id, scopes.member); + if (!prop) { + return; + } + } + c(node.id, scopes.member); + c(node.binding, scopes.object); + }, + QMLScriptBinding: function (node, scope, c) { + c(node.script, node.scope); + }, + QML_ID: function (node, st, c) { + // Ignore + }, + QMLStatementBlock: function (node, scope, c) { + for (var i = 0; i < node.statements.length; i++) { + c(node.statements[i], node.scope, "Statement"); } } - c(node.id, scopes.property); - c(node.binding, node.binding.scope); - }; - searchVisitor["QML_ID"] = function(node, st, c) {}; - searchVisitor["QMLStatementBlock"] = function (node, scope, c) { - for (var i = 0; i < node.statements.length; i++) { - c(node.statements[i], node.scope, "Statement"); - } - }; + }); } /* - * Prepares acorn to consume QML syntax rather than standard JavaScript - */ + * Prepares acorn to consume QML syntax rather than standard JavaScript + */ function preParse(text, options) { // Force ECMA Version to 5 options.ecmaVersion = 5; @@ -514,45 +885,58 @@ // Register qml plugin with main parser var plugins = options.plugins; if (!plugins) plugins = options.plugins = {}; - plugins["qml"] = true; + plugins.qml = true; // Register qml plugin with loose parser var pluginsLoose = options.pluginsLoose; if (!pluginsLoose) pluginsLoose = options.pluginsLoose = {}; - pluginsLoose["qml"] = true; + pluginsLoose.qml = true; } /* - * Performs a second pass over the generated scopes directly after the scopeGatherer - * executes and before type inferral begins. This pass is used to finalize any - * scopes that require information from other scopes created during the scope - * gatherer's pass. This includes the JavaScript scopes which require information - * from the QML ID scope, as well as the Property scopes which require information - * from their respective Object Literal scopes. - */ - function scopeGatheringSecondPass(ast, scope) { - var scopeBuilder = getScopeBuilder(); + * Initializes the file's top level scope and creates a ScopeBuilder to facilitate + * the creation of QML scopes. + */ + function beforeLoad(file) { + // We dont care for the Context's top scope + file.scope = null; - // Aggregate IDs and Signal Handlers for the JavaScript scopes - scopeBuilder.forEachJSScope(function (scope) { - // Merge any QML IDs into all JavaScript scopes - scopeBuilder.getIDScope().forAllProps(function (name, prop, curr) { - if (curr) { - // Since QML checks the idScope before all others, we can safely over-write - // conflicting property names as they will be hidden anyway. - scope.props[name] = prop; - } - }); - }); + // Update the ImportHandler + qmlImportHandler.updateDirectoryImportList(); - // Aggregate properties for the property scopes - scopeBuilder.forEachPropertyScope(function (scope, objScope) { - objScope.forAllProps(function (name, prop, curr) { - if (curr && !prop.isFunction) { - scope.props[name] = prop; - } - }); - }); + // Create the file's top scope + file.scope = new infer.Scope(infer.cx().topScope); + var name = file.name; + var end = file.name.lastIndexOf(".qml"); + file.scope.name = end > 0 ? name.substring(0, end) : name; + + // Get the ImportHandler to define imports for us + qmlImportHandler.defineImports(file, file.scope); + + // Create the ScopeBuilder + var sb = new ScopeBuilder(file, infer.cx().parent.jsDefs); + infer.cx().qmlScopeBuilder = sb; + } + + /* + * Helper to reset some of the internal state of the QML plugin when the server + * resets + */ + function reset() { + qmlImportHandler.reset(); + } + + function completions(file, query) { + // We can get relatively simple completions on QML Object Types for free if we + // update the Context.paths variable. Tern uses this variable to complete + // non Member Expressions that contain a '.' character. Will be much more + // accurate if we just roll our own completions for this in the future, but it + // works relatively well for now. + var cx = infer.cx(); + cx.paths = {}; + for (var prop in file.scope.props) { + cx.paths[prop] = file.scope[prop]; + } } // Register the QML plugin in Tern @@ -566,22 +950,16 @@ if (toFront) this.jsDefs.unshift(defs); else this.jsDefs.push(defs); if (this.cx) this.reset(); - } + }; + + // Create the QML Import Handler + qmlImportHandler = exports.importHandler = new ImportHandler(server); // Hook into server signals server.on("preParse", preParse); - server.on("preInfer", scopeGatheringSecondPass); - server.on("beforeLoad", function(file) { - // Create the file's top scope - file.scope = new infer.Scope(infer.cx().topScope); - var name = file.name; - var end = file.name.lastIndexOf(".qml"); - file.scope.name = end > 0 ? name.substring(0, end) : name; - - // Create the ScopeBuilder - var sb = new ScopeBuilder(file, server.jsDefs); - infer.cx().qmlScopeBuilder = sb; - }); + server.on("beforeLoad", beforeLoad); + server.on("postReset", reset); + server.on("completion", completions); // Extend Tern's inferencing system to include QML syntax extendTernScopeGatherer(infer.scopeGatherer); @@ -590,4 +968,4 @@ extendTernTypeFinder(infer.typeFinder); extendTernSearchVisitor(infer.searchVisitor); }); -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/test/driver.js b/qt/org.eclipse.cdt.qt.core/tern-qml/test/driver.js index 5681747c39d..41be94a0262 100644 --- a/qt/org.eclipse.cdt.qt.core/tern-qml/test/driver.js +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/test/driver.js @@ -24,8 +24,8 @@ var groupName; function TestCase(group, code, run) { this.code = code; this.group = group; - this.runTest = run || function (server, callback) { - callback("fail", code, "runTest function was not provided."); + this.runTest = run || function (server, callback, code) { + callback("fail", this.code, "runTest function was not provided."); }; } @@ -37,19 +37,19 @@ exports.isolate = function (code) { i--; } } -} +}; exports.groupStart = function (group) { groupName = group; -} +}; exports.groupEnd = function () { groupName = undefined; -} +}; exports.test = function (code, runTest) { testCases.push(new TestCase(groupName || "Default", code, runTest)); -} +}; exports.testCompletion = function (code, expected, beforeTest) { testCases.push(new TestCase(groupName || "Default", code, function (server, callback) { @@ -70,7 +70,7 @@ exports.testCompletion = function (code, expected, beforeTest) { } }); }, beforeTest)); -} +}; exports.testDefinition = function (code, expected, beforeTest) { testCases.push(new TestCase(groupName || "Default", code, function (server, callback) { @@ -91,21 +91,21 @@ exports.testDefinition = function (code, expected, beforeTest) { } }); }, beforeTest)); -} +}; exports.runTests = function (config, callback) { for (var i = 0; i < testCases.length; ++i) { var test = testCases[i]; if (test.group === config.group) { var server = createServer(); - test.runTest(server, callback); + test.runTest(server, callback, test.code); } } }; function createServer(defs) { var plugins = {}; - plugins["qml"] = {}; + plugins.qml = true; var server = new tern.Server({ ecmaVersion: 5, plugins : plugins, @@ -114,12 +114,12 @@ function createServer(defs) { return server; } -function assertCompletion(server, code, expected, pos, callback) { - server.addFile("test1.qml", code); +var assertCompletion = exports.assertCompletion = function (server, code, expected, pos, callback) { + server.addFile("main.qml", code); server.request({ query : { type: "completions", - file: "test1.qml", + file: "main.qml", end: pos, types: true, docs: false, @@ -139,12 +139,12 @@ function assertCompletion(server, code, expected, pos, callback) { }); }; -function assertDefinition(server, code, expected, pos, callback) { - server.addFile("test1.qml", code); +var assertDefinition = exports.assertDefinition = function (server, code, expected, pos, callback) { + server.addFile("main.qml", code); server.request({ query : { type: "definition", - file: "test1.qml", + file: "main.qml", end: pos, types: true, docs: false, @@ -175,6 +175,7 @@ function addPath(str, pt) { } var misMatch = exports.misMatch = function (exp, act) { + var mis = null; if (!exp || !act || (typeof exp != "object") || (typeof act != "object")) { if (exp !== act) return ppJSON(exp) + " !== " + ppJSON(act); } else if (exp instanceof RegExp || act instanceof RegExp) { @@ -184,12 +185,12 @@ var misMatch = exports.misMatch = function (exp, act) { if (!act.slice) return ppJSON(exp) + " != " + ppJSON(act); if (act.length != exp.length) return "array length mismatch " + exp.length + " != " + act.length; for (var i = 0; i < act.length; ++i) { - var mis = misMatch(exp[i], act[i]); + mis = misMatch(exp[i], act[i]); if (mis) return addPath(mis, i); } } else { for (var prop in exp) { - var mis = misMatch(exp[prop], act[prop]); + mis = misMatch(exp[prop], act[prop]); if (mis) return addPath(mis, prop); } } diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/test/ecma5-defs.js b/qt/org.eclipse.cdt.qt.core/tern-qml/test/ecma5-defs.js index 952c6fbc27b..58fb3b127f4 100644 --- a/qt/org.eclipse.cdt.qt.core/tern-qml/test/ecma5-defs.js +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/test/ecma5-defs.js @@ -8,6 +8,8 @@ * Contributors: * QNX Software Systems - Initial API and implementation *******************************************************************************/ +"use strict"; + module.exports = { "!name": "ecma5", "!define": { @@ -975,4 +977,4 @@ module.exports = { "!url": "https://developer.mozilla.org/en-US/docs/JSON", "!doc": "JSON (JavaScript Object Notation) is a data-interchange format. It closely resembles a subset of JavaScript syntax, although it is not a strict subset. (See JSON in the JavaScript Reference for full details.) It is useful when writing any kind of JavaScript-based application, including websites and browser extensions. For example, you might store user information in JSON format in a cookie, or you might store extension preferences in JSON in a string-valued browser preference." } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/test/run.js b/qt/org.eclipse.cdt.qt.core/tern-qml/test/run.js index 0af2df252c1..02f99944e14 100644 --- a/qt/org.eclipse.cdt.qt.core/tern-qml/test/run.js +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/test/run.js @@ -8,7 +8,7 @@ * Contributors: * QNX Software Systems - Initial API and implementation *******************************************************************************/ -"use-strict"; +"use strict"; var driver = require("./driver.js"); @@ -20,7 +20,7 @@ function group(name) { function groupEnd() { if (typeof console === "object" && console.groupEnd) { - console.groupEnd(name); + console.groupEnd(); } } @@ -29,8 +29,9 @@ function log(title, message) { } var stats, tests = []; -tests.push(require("./test-completions.js")); +tests.push(require("./test-scoping.js")); tests.push(require("./test-finddef.js")); +tests.push(require("./test-completions.js")); function report(state, code, message) { if (state != "ok") {++stats.failed; log(code, message);} @@ -45,9 +46,9 @@ for (var i = 0; i < tests.length; i++) { stats = test.stats = {testsRun: 0, failed: 0}; var config = test.config || {}; config.group = test.group; - var t0 = +new Date; + var t0 = +new Date(); driver.runTests(config, report); - test.stats.duration = +new Date - t0; + test.stats.duration = +new Date() - t0; groupEnd(); } diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-completions.js b/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-completions.js index 50b1502703c..d9eb5f91666 100644 --- a/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-completions.js +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-completions.js @@ -8,58 +8,259 @@ * Contributors: * QNX Software Systems - Initial API and implementation *******************************************************************************/ -"use-strict"; +"use strict"; var driver = require("./driver.js"); var test = driver.test; var testCompletion = driver.testCompletion; +var assertCompletion = driver.assertCompletion; var groupStart = driver.groupStart; var groupEnd = driver.groupEnd; -var group = exports.group = "Code Completion" +var group = exports.group = "Code Completion"; groupStart(group); -// Import Completions -//testCompletion("import QtQu|", { -// start: { line: 0, ch: 7 }, -// end: { line: 0, ch: 11 }, -// isProperty: false, -// isObjectKey: false, -// completions: [{ name: "QtQuick", type: "", origin: "QML" }] -//}); -// -//testCompletion("import QtQuick 2.|", { -// start: { line: 0, ch: 17 }, -// end: { line: 0, ch: 17 }, -// isProperty: false, -// isObjectKey: false, -// completions: [ -// { name: "0", type: "number", origin: "QML" }, -// { name: "1", type: "number", origin: "QML" }, -// { name: "2", type: "number", origin: "QML" }, -// { name: "3", type: "number", origin: "QML" }, -// { name: "4", type: "number", origin: "QML" }, -// { name: "5", type: "number", origin: "QML" } -// ] -//}); -// -//testCompletion('import "other.qml" as Other\n|', { -// start: { line: 1, ch: 0 }, -// end: { line: 1, ch: 0 }, -// isProperty: false, -// isObjectKey: false, -// completions: [{ name: "Other", type: "?", origin: "test1.qml" }] -//}) -// -//testCompletion('My|', { -// start: { line: 0, ch: 2 }, -// end: { line: 0, ch: 2 }, -// isProperty: false, -// isObjectKey: false, -// completions: [{ name: "MyObject", type: "Button", origin: "MyObject.qml" }] -//}, function (server) { -// server.addFile("MyObject.qml", "Button {\n\tproperty int width\n}"); -//}); +// Local Directory Completions +testCompletion('My|', { + start: { line: 0, ch: 0 }, + end: { line: 0, ch: 2 }, + isProperty: false, + isObjectKey: false, + completions: [{ name: "MyObject", type: "MyObject", origin: "MyObject.qml" }] +}, function (server) { + server.addFile("MyObject.qml", "Button {}"); +}); + +testCompletion('import "./subdir/"\nSameDirTest {\n\t|\n}', { + start: { line: 2, ch: 1 }, + end: { line: 2, ch: 1 }, + isProperty: false, + isObjectKey: false, + completions: [ + { name: "obj", type: "?", origin: "subdir/Button.qml" }, + { name: "Button", type: "Button", origin: "subdir/Button.qml" }, + { name: "SameDirTest", type: "SameDirTest", origin: "subdir/SameDirTest.qml" } + ] +}, function (server) { + server.addFile("subdir/SameDirTest.qml", "Button {}"); + server.addFile("subdir/Button.qml", "QtObject {property var obj}"); +}); + +testCompletion('MyObject {\n\t|\n}', { + start: { line: 1, ch: 1 }, + end: { line: 1, ch: 1 }, + isProperty: false, + isObjectKey: false, + completions: [ + { name: "width", type: "number", origin: "MyObject.qml" }, + { name: "MyObject", type: "MyObject", origin: "MyObject.qml" } + ] +}, function (server) { + server.addFile("MyObject.qml", "Button {\n\tproperty int width\n}"); +}); + +testCompletion('MyObject {\n\tid: obj\n\ts: obj.|\n}', { + start: { line: 2, ch: 8 }, + end: { line: 2, ch: 8 }, + isProperty: true, + isObjectKey: false, + completions: [{ name: "width", type: "number", origin: "MyObject.qml" }] +}, function (server) { + server.addFile("MyObject.qml", "Button {\n\tproperty int width\n}"); +}); + +testCompletion('Button {\n\t|\n}', { + start: { line: 1, ch: 1 }, + end: { line: 1, ch: 1 }, + isProperty: false, + isObjectKey: false, + completions: [ + { name: "onClicked", type: "Signal Handler", origin: "Button.qml" }, + { name: "Button", type: "Button", origin: "Button.qml" } + ] +}, function (server) { + server.addFile("Button.qml", "QtObject {\n\signal clicked(int mouseX, int mouseY)\n}"); +}); + +testCompletion('CButton {\n\t|\n}', { + start: { line: 1, ch: 1 }, + end: { line: 1, ch: 1 }, + isProperty: false, + isObjectKey: false, + completions: [ + { name: "height", type: "number", origin: "Button.qml" }, + { name: "numClicks", type: "number", origin: "CButton.qml" }, + { name: "text", type: "string", origin: "Button.qml" }, + { name: "width", type: "number", origin: "Button.qml" }, + { name: "Button", type: "Button", origin: "Button.qml" }, + { name: "CButton", type: "CButton", origin: "CButton.qml" } + ] +}, function (server) { + server.addFile("CButton.qml", "Button {\n\tproperty int numClicks\n}"); + server.addFile("Button.qml", "QtObject {\n\tproperty string text\n\tproperty int width\n\tproperty int height\n}"); +}); + +testCompletion('CButton {\n\tid:btn\n\ts: btn.|\n}', { + start: { line: 2, ch: 8 }, + end: { line: 2, ch: 8 }, + isProperty: true, + isObjectKey: false, + completions: [ + { name: "height", type: "number", origin: "Button.qml" }, + { name: "numClicks", type: "number", origin: "CButton.qml" }, + { name: "text", type: "string", origin: "Button.qml" }, + { name: "width", type: "number", origin: "Button.qml" } + ] +}, function (server) { + server.addFile("CButton.qml", "Button {\n\tproperty int numClicks\n}"); + server.addFile("Button.qml", "QtObject {\n\tproperty string text\n\tproperty int width\n\tproperty int height\n}"); +}); + +// Directory Import Completions +testCompletion('NotVisible {\n\t|\n}', { + start: { line: 1, ch: 1 }, + end: { line: 1, ch: 1 }, + isProperty: false, + isObjectKey: false, + completions: [] +}, function (server) { + server.addFile("subdir/NotVisible.qml", "QtObject {\n\signal clicked(int mouseX, int mouseY)\n}"); +}); + +testCompletion('import "./subdir/"\nButton {\n\t|\n}', { + start: { line: 2, ch: 1 }, + end: { line: 2, ch: 1 }, + isProperty: false, + isObjectKey: false, + completions: [ + { name: "onClicked", type: "Signal Handler", origin: "subdir/Button.qml" }, + { name: "Button", type: "Button", origin: "subdir/Button.qml" } + ] +}, function (server) { + server.addFile("subdir/Button.qml", "QtObject {\n\signal clicked(int mouseX, int mouseY)\n}"); +}); + +testCompletion('import "./subdir/" as Controls\nControls.Button {\n\t|\n}', { + start: { line: 2, ch: 1 }, + end: { line: 2, ch: 1 }, + isProperty: false, + isObjectKey: false, + completions: [ + { name: "onClicked", type: "Signal Handler", origin: "subdir/Button.qml" }, + { name: "Controls", type: "Controls", origin: "main.qml" } + ] +}, function (server) { + server.addFile("subdir/Button.qml", "QtObject {\n\signal clicked(int mouseX, int mouseY)\n}"); +}); + +testCompletion('import "./subdir/" as Controls\nControls.|', { + start: { line: 1, ch: 9 }, + end: { line: 1, ch: 9 }, + isProperty: false, + isObjectKey: false, + completions: [ + { name: "Button", type: "Button", origin: "subdir/Button.qml" } + ] +}, function (server) { + server.addFile("subdir/Button.qml", "QtObject {\n\signal clicked(int mouseX, int mouseY)\n}"); +}); + +testCompletion('import "./subdir/" as Controls\nControls.|.', { + start: { line: 1, ch: 9 }, + end: { line: 1, ch: 9 }, + isProperty: false, + isObjectKey: false, + completions: [ + { name: "Button", type: "Button", origin: "subdir/Button.qml" } + ] +}, function (server) { + server.addFile("subdir/Button.qml", "QtObject {\n\signal clicked(int mouseX, int mouseY)\n}"); +}); + +testCompletion('import "./subdir/" as Controls\nControls..|', { + start: { line: 1, ch: 10 }, + end: { line: 1, ch: 10 }, + isProperty: false, + isObjectKey: false, + completions: [] +}, function (server) { + server.addFile("subdir/Button.qml", "QtObject {\n\signal clicked(int mouseX, int mouseY)\n}"); +}); + +test("{Add File After Import}", function (server, callback, name) { + var failed; + assertCompletion(server, "", { + start: { line: 0, ch: 0 }, + end: { line: 0, ch: 0 }, + isProperty: false, + isObjectKey: false, + completions: [] + }, 0, function (mis) { + failed = mis; + }); + if (failed) { + return callback("fail", name, "- failed on initial file " + failed); + } + server.addFile("MyObject.qml", "QtObject {\n\tproperty var test\n}"); + assertCompletion(server, "M", { + start: { line: 0, ch: 0 }, + end: { line: 0, ch: 1 }, + isProperty: false, + isObjectKey: false, + completions: [{ name: "MyObject", type: "MyObject", origin: "MyObject.qml" }] + }, 1, function (mis) { + failed = mis; + }); + if (failed) { + return callback("fail", name, "- failed after adding file " + failed); + } + return callback("ok", name); +}); + +// Cyclic Dependency Completions +testCompletion('Cyclic {\n\t|\n}', { + start: { line: 1, ch: 1 }, + end: { line: 1, ch: 1 }, + isProperty: false, + isObjectKey: false, + completions: [ + { name: "test", type: "?", origin: "Cyclic.qml" }, + { name: "Cyclic", type: "Cyclic", origin: "Cyclic.qml" } + ] +}, function (server) { + server.addFile("Cyclic.qml", "Cyclic {\n\property var test\n}"); +}); + +testCompletion('Cyclic2 {\n\t|\n}', { + start: { line: 1, ch: 1 }, + end: { line: 1, ch: 1 }, + isProperty: false, + isObjectKey: false, + completions: [ + { name: "test1", type: "?", origin: "Cyclic2.qml" }, + { name: "test2", type: "?", origin: "OtherCyclic.qml" }, + { name: "Cyclic2", type: "Cyclic2", origin: "Cyclic2.qml" }, + { name: "OtherCyclic", type: "OtherCyclic", origin: "OtherCyclic.qml" } + ] +}, function (server) { + server.addFile("Cyclic2.qml", "OtherCyclic {\n\property var test1\n}"); + server.addFile("OtherCyclic.qml", "Cyclic2 {\n\property var test2\n}"); +}); + +testCompletion('OtherCyclic {\n\t|\n}', { + start: { line: 1, ch: 1 }, + end: { line: 1, ch: 1 }, + isProperty: false, + isObjectKey: false, + completions: [ + { name: "test2", type: "?", origin: "OtherCyclic.qml" }, + { name: "Cyclic", type: "Cyclic", origin: "Cyclic.qml" }, + { name: "OtherCyclic", type: "OtherCyclic", origin: "OtherCyclic.qml" } + ] +}, function (server) { + server.addFile("Cyclic.qml", "OtherCyclic {\n\property var test1\n}"); + server.addFile("OtherCyclic.qml", "Cyclic {\n\property var test2\n}"); +}); // QML Object Property Completions testCompletion("Window {\n\tproperty int height\n\the|\n}", { @@ -67,7 +268,7 @@ testCompletion("Window {\n\tproperty int height\n\the|\n}", { end: { line: 2, ch: 3 }, isProperty: false, isObjectKey: false, - completions: [{ name: "height", type: "number", origin: "test1.qml" }] + completions: [{ name: "height", type: "number", origin: "main.qml" }] }); testCompletion("Window {\n\tproperty int height\n\tproperty int width\n\tproperty string text\n\t|\n}", { @@ -76,9 +277,9 @@ testCompletion("Window {\n\tproperty int height\n\tproperty int width\n\tpropert isProperty: false, isObjectKey: false, completions: [ - { name: "height", type: "number", origin: "test1.qml" }, - { name: "text", type: "string", origin: "test1.qml" }, - { name: "width", type: "number", origin: "test1.qml" } + { name: "height", type: "number", origin: "main.qml" }, + { name: "text", type: "string", origin: "main.qml" }, + { name: "width", type: "number", origin: "main.qml" } ] }); @@ -96,8 +297,8 @@ testCompletion("Window {\n\tproperty var prop\n\tfunction test() {\n\t\t|\n\t}\n isProperty: false, isObjectKey: false, completions: [ - { name: "prop", type: "?", origin: "test1.qml" }, - { name: "test", type: "fn()", origin: "test1.qml" } + { name: "prop", type: "?", origin: "main.qml" }, + { name: "test", type: "fn()", origin: "main.qml" } ] }, function (server) { server.jsDefs = []; }); @@ -115,7 +316,7 @@ testCompletion("Window {\n\tButton {\n\t\tid: btn\n\t\tproperty int height\n\t}\ end: { line: 5, ch: 11 }, isProperty: true, isObjectKey: false, - completions: [{ name: "height", type: "number", origin: "test1.qml" }] + completions: [{ name: "height", type: "number", origin: "main.qml" }] }, function (server) { server.jsDefs = []; }); testCompletion("Window {\n\tproperty var btn\n\tButton {\n\t\tid: btn\n\t\tproperty int height\n\t}\n\ttest: btn.|\n}", { @@ -123,7 +324,7 @@ testCompletion("Window {\n\tproperty var btn\n\tButton {\n\t\tid: btn\n\t\tprope end: { line: 6, ch: 11 }, isProperty: true, isObjectKey: false, - completions: [{ name: "height", type: "number", origin: "test1.qml" }] + completions: [{ name: "height", type: "number", origin: "main.qml" }] }, function (server) { server.jsDefs = []; }); testCompletion("Window {\n\tButton {\n\t\tproperty var id\n\t\tid: btn\n\t}\n\ts: bt|\n}", { @@ -131,7 +332,7 @@ testCompletion("Window {\n\tButton {\n\t\tproperty var id\n\t\tid: btn\n\t}\n\ts end: { line: 5, ch: 6 }, isProperty: false, isObjectKey: false, - completions: [{ name: "btn", type: "Button", origin: "test1.qml" }] + completions: [{ name: "btn", type: "Button", origin: "main.qml" }] }, function (server) { server.jsDefs = []; }); testCompletion("Window {\n\tButton {\n\t\tproperty var id\n\t\tid: btn\n\t\ts: i|\n\t}\n}", { @@ -139,7 +340,7 @@ testCompletion("Window {\n\tButton {\n\t\tproperty var id\n\t\tid: btn\n\t\ts: i end: { line: 4, ch: 6 }, isProperty: false, isObjectKey: false, - completions: [{ name: "id", type: "?", origin: "test1.qml" }] + completions: [{ name: "id", type: "?", origin: "main.qml" }] }, function (server) { server.jsDefs = []; }); testCompletion("Window {\n\tButton {\n\t\tproperty var id: 34\n\t\tid: btn\n\t\ts: i|\n\t}\n}", { @@ -147,7 +348,7 @@ testCompletion("Window {\n\tButton {\n\t\tproperty var id: 34\n\t\tid: btn\n\t\t end: { line: 4, ch: 6 }, isProperty: false, isObjectKey: false, - completions: [{ name: "id", type: "number", origin: "test1.qml" }] + completions: [{ name: "id", type: "number", origin: "main.qml" }] }, function (server) { server.jsDefs = []; }); testCompletion("Window {\n\tButton {\n\t\tproperty string id\n\t\tid: btn\n\t\ts: i|\n\t}\n}", { @@ -155,7 +356,7 @@ testCompletion("Window {\n\tButton {\n\t\tproperty string id\n\t\tid: btn\n\t\ts end: { line: 4, ch: 6 }, isProperty: false, isObjectKey: false, - completions: [{ name: "id", type: "string", origin: "test1.qml" }] + completions: [{ name: "id", type: "string", origin: "main.qml" }] }, function (server) { server.jsDefs = []; }); testCompletion("Window {\n\tButton {\n\t\tproperty var id\n\t\ts: i|\n\t}\n}", { @@ -163,7 +364,7 @@ testCompletion("Window {\n\tButton {\n\t\tproperty var id\n\t\ts: i|\n\t}\n}", { end: { line: 3, ch: 6 }, isProperty: false, isObjectKey: false, - completions: [{ name: "id", type: "?", origin: "test1.qml" }] + completions: [{ name: "id", type: "?", origin: "main.qml" }] }, function (server) { server.jsDefs = []; }); testCompletion("Window {\n\tButton {\n\t\tproperty var id: 34\n\t\ts: i|\n\t}\n}", { @@ -171,7 +372,7 @@ testCompletion("Window {\n\tButton {\n\t\tproperty var id: 34\n\t\ts: i|\n\t}\n} end: { line: 3, ch: 6 }, isProperty: false, isObjectKey: false, - completions: [{ name: "id", type: "number", origin: "test1.qml" }] + completions: [{ name: "id", type: "number", origin: "main.qml" }] }, function (server) { server.jsDefs = []; }); testCompletion("Window {\n\tButton {\n\t\tproperty string id\n\t\ts: i|\n\t}\n}", { @@ -179,7 +380,7 @@ testCompletion("Window {\n\tButton {\n\t\tproperty string id\n\t\ts: i|\n\t}\n}" end: { line: 3, ch: 6 }, isProperty: false, isObjectKey: false, - completions: [{ name: "id", type: "string", origin: "test1.qml" }] + completions: [{ name: "id", type: "string", origin: "main.qml" }] }, function (server) { server.jsDefs = []; }); testCompletion("Window {\n\tid: wind\n\tfunction test() {\n\t\t|\n\t}\n}", { @@ -188,8 +389,8 @@ testCompletion("Window {\n\tid: wind\n\tfunction test() {\n\t\t|\n\t}\n}", { isProperty: false, isObjectKey: false, completions: [ - { name: "test", type: "fn()", origin: "test1.qml" }, - { name: "wind", type: "Window", origin: "test1.qml" } + { name: "test", type: "fn()", origin: "main.qml" }, + { name: "wind", type: "Window", origin: "main.qml" } ] }, function (server) { server.jsDefs = []; }); @@ -209,7 +410,7 @@ testCompletion("Window {\n\tproperty var test: |\n}", { { name: "isNaN", type: "fn(value: number) -> bool", origin: "ecma5" }, { name: "parseFloat", type: "fn(string: string) -> number", origin: "ecma5" }, { name: "parseInt", type: "fn(string: string, radix?: number) -> number", origin: "ecma5" }, - { name: "test", type: "?", origin: "test1.qml" }, + { name: "test", type: "?", origin: "main.qml" }, { name: "undefined", type: "?", origin: "ecma5" }, { name: "Array", type: "fn(size: number)", origin: "ecma5" }, { name: "Boolean", type: "fn(value: ?) -> bool", origin: "ecma5" }, @@ -324,7 +525,7 @@ testCompletion("Window {\n\tfunction test() {\n\t\t|\n\t}\n}", { { name: "isNaN", type: "fn(value: number) -> bool", origin: "ecma5" }, { name: "parseFloat", type: "fn(string: string) -> number", origin: "ecma5" }, { name: "parseInt", type: "fn(string: string, radix?: number) -> number", origin: "ecma5" }, - { name: "test", type: "fn()", origin: "test1.qml" }, + { name: "test", type: "fn()", origin: "main.qml" }, { name: "undefined", type: "?", origin: "ecma5" }, { name: "Array", type: "fn(size: number)", origin: "ecma5" }, { name: "Boolean", type: "fn(value: ?) -> bool", origin: "ecma5" }, @@ -355,7 +556,7 @@ testCompletion("Window {\n\tsignal clicked(int mouseX, int mouseY)\n\t|\n}", { isProperty: false, isObjectKey: false, completions: [ - { name: "onClicked", type: "Signal Handler", origin: "test1.qml" } + { name: "onClicked", type: "Signal Handler", origin: "main.qml" } ] }); @@ -373,8 +574,8 @@ testCompletion("Window {\n\tsignal clicked(int mouseX, int mouseY)\n\ts: |\n}", isProperty: false, isObjectKey: false, completions: [ - { name: "clicked", type: "fn(mouseX: number, mouseY: number)", origin: "test1.qml" }, - { name: "onClicked", type: "Signal Handler", origin: "test1.qml" } + { name: "clicked", type: "fn(mouseX: number, mouseY: number)", origin: "main.qml" }, + { name: "onClicked", type: "Signal Handler", origin: "main.qml" } ] }, function (server) { server.jsDefs = []; }); @@ -384,8 +585,8 @@ testCompletion("Window {\n\tsignal error(string msg, boolean flag)\n\ts: |\n}", isProperty: false, isObjectKey: false, completions: [ - { name: "error", type: "fn(msg: string, flag: bool)", origin: "test1.qml" }, - { name: "onError", type: "Signal Handler", origin: "test1.qml" } + { name: "error", type: "fn(msg: string, flag: bool)", origin: "main.qml" }, + { name: "onError", type: "Signal Handler", origin: "main.qml" } ] }, function (server) { server.jsDefs = []; }); @@ -395,8 +596,8 @@ testCompletion("Window {\n\tid: wind\n\tsignal error(string msg, boolean flag)\n isProperty: true, isObjectKey: false, completions: [ - { name: "error", type: "fn(msg: string, flag: bool)", origin: "test1.qml" }, - { name: "onError", type: "Signal Handler", origin: "test1.qml" } + { name: "error", type: "fn(msg: string, flag: bool)", origin: "main.qml" }, + { name: "onError", type: "Signal Handler", origin: "main.qml" } ] }, function (server) { server.jsDefs = []; }); @@ -406,10 +607,10 @@ testCompletion("Window {\n\tsignal error(string msg, boolean flag)\n\tonError: | isProperty: false, isObjectKey: false, completions: [ - { name: "error", type: "fn(msg: string, flag: bool)", origin: "test1.qml" }, - { name: "flag", type: "bool", origin: "test1.qml" }, - { name: "msg", type: "string", origin: "test1.qml" }, - { name: "onError", type: "Signal Handler", origin: "test1.qml" } + { name: "error", type: "fn(msg: string, flag: bool)", origin: "main.qml" }, + { name: "flag", type: "bool", origin: "main.qml" }, + { name: "msg", type: "string", origin: "main.qml" }, + { name: "onError", type: "Signal Handler", origin: "main.qml" } ] }, function (server) { server.jsDefs = []; }); @@ -419,10 +620,10 @@ testCompletion("Window {\n\tsignal error(string msg, boolean flag)\n\tonError: { isProperty: false, isObjectKey: false, completions: [ - { name: "error", type: "fn(msg: string, flag: bool)", origin: "test1.qml" }, - { name: "flag", type: "bool", origin: "test1.qml" }, - { name: "msg", type: "string", origin: "test1.qml" }, - { name: "onError", type: "Signal Handler", origin: "test1.qml" } + { name: "error", type: "fn(msg: string, flag: bool)", origin: "main.qml" }, + { name: "flag", type: "bool", origin: "main.qml" }, + { name: "msg", type: "string", origin: "main.qml" }, + { name: "onError", type: "Signal Handler", origin: "main.qml" } ] }, function (server) { server.jsDefs = []; }); @@ -432,10 +633,10 @@ testCompletion("Window {\n\tproperty int msg\n\tsignal error(string msg, boolean isProperty: false, isObjectKey: false, completions: [ - { name: "error", type: "fn(msg: string, flag: bool)", origin: "test1.qml" }, - { name: "flag", type: "bool", origin: "test1.qml" }, - { name: "msg", type: "string", origin: "test1.qml" }, - { name: "onError", type: "Signal Handler", origin: "test1.qml" } + { name: "error", type: "fn(msg: string, flag: bool)", origin: "main.qml" }, + { name: "flag", type: "bool", origin: "main.qml" }, + { name: "msg", type: "string", origin: "main.qml" }, + { name: "onError", type: "Signal Handler", origin: "main.qml" } ] }, function (server) { server.jsDefs = []; }); @@ -454,10 +655,10 @@ testCompletion("Window {\n\tfunction test(a, b, c) {\n\t\t|\n\t}\n}", { isProperty: false, isObjectKey: false, completions: [ - { name: "a", type: "?", origin: "test1.qml" }, - { name: "b", type: "?", origin: "test1.qml" }, - { name: "c", type: "?", origin: "test1.qml" }, - { name: "test", type: "fn(a: ?, b: ?, c: ?)", origin: "test1.qml" } + { name: "a", type: "?", origin: "main.qml" }, + { name: "b", type: "?", origin: "main.qml" }, + { name: "c", type: "?", origin: "main.qml" }, + { name: "test", type: "fn(a: ?, b: ?, c: ?)", origin: "main.qml" } ] }, function (server) { server.jsDefs = []; }); @@ -467,8 +668,8 @@ testCompletion("Window {\n\tfunction test(a) {\n\t\ta = 3\n\t\t|\n\t}\n}", { isProperty: false, isObjectKey: false, completions: [ - { name: "a", type: "number", origin: "test1.qml" }, - { name: "test", type: "fn(a: number)", origin: "test1.qml" } + { name: "a", type: "number", origin: "main.qml" }, + { name: "test", type: "fn(a: number)", origin: "main.qml" } ] }, function (server) { server.jsDefs = []; }); @@ -478,8 +679,8 @@ testCompletion('Window {\n\tfunction test(a) {\n\t\ttest("something")\n\t\t|\n\t isProperty: false, isObjectKey: false, completions: [ - { name: "a", type: "string", origin: "test1.qml" }, - { name: "test", type: "fn(a: string)", origin: "test1.qml" } + { name: "a", type: "string", origin: "main.qml" }, + { name: "test", type: "fn(a: string)", origin: "main.qml" } ] }, function (server) { server.jsDefs = []; }); @@ -489,8 +690,8 @@ testCompletion('Window {\n\tfunction test(a) {\n\t\t|\n\t\treturn 7\n\t}\n}', { isProperty: false, isObjectKey: false, completions: [ - { name: "a", type: "?", origin: "test1.qml" }, - { name: "test", type: "fn(a: ?) -> number", origin: "test1.qml" } + { name: "a", type: "?", origin: "main.qml" }, + { name: "test", type: "fn(a: ?) -> number", origin: "main.qml" } ] }, function (server) { server.jsDefs = []; }); @@ -502,8 +703,8 @@ testCompletion('Window {\n\tfunction test(a) {\n\t\t|\n\t\treturn 7\n\t}\n}', { // isProperty: false, // isObjectKey: false, // completions: [ -// { name: "a", type: "?", origin: "test1.qml" }, -// { name: "test", type: "fn()", origin: "test1.qml" } +// { name: "a", type: "?", origin: "main.qml" }, +// { name: "test", type: "fn()", origin: "main.qml" } // ] //}, function (server) { server.jsDefs = []; }); diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-finddef.js b/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-finddef.js index 626b7d08afe..c6090d4f1b4 100644 --- a/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-finddef.js +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-finddef.js @@ -8,79 +8,163 @@ * Contributors: * QNX Software Systems - Initial API and implementation *******************************************************************************/ -"use-strict"; +"use strict"; var driver = require("./driver.js"); +var test = driver.test; var testDefinition = driver.testDefinition; +var assertDefinition = driver.assertDefintion; var groupStart = driver.groupStart; var groupEnd = driver.groupEnd; var group = exports.group = "Find Definition"; groupStart(group); +// Directory Imports +// TODO: Getting this test to pass breaks some of the type inferencing which is more important +//testDefinition('My|Object {}', { +// origin: "MyObject.qml", +// start: { line: 0, ch: 0 }, +// end: { line: 0, ch: 6 }, +// file: "MyObject.qml", +// contextOffset: 0 +//}, function (server) { +// server.addFile("MyObject.qml", "Button {}"); +//}); + +testDefinition("MyObject {\n\tte|stProp: ident\n}", { + origin: "MyObject.qml", + start: { line: 1, ch: 14 }, + end: { line: 1, ch: 22 }, + file: "MyObject.qml", + contextOffset: 23 +}, function (server) { + server.addFile("MyObject.qml", "Button {\n\tproperty var testProp\n}"); +}); + +testDefinition("MyObject {\n\ts: te|stSig()\n}", { + origin: "MyObject.qml", + start: { line: 1, ch: 8 }, + end: { line: 1, ch: 15 }, + file: "MyObject.qml", + contextOffset: 17 +}, function (server) { + server.addFile("MyObject.qml", "Button {\n\tsignal testSig()\n}"); +}); + +testDefinition("MyObject {\n\tonTe|stSig: ident\n}", { + origin: "MyObject.qml", + start: { line: 1, ch: 8 }, + end: { line: 1, ch: 15 }, + file: "MyObject.qml", + contextOffset: 17 +}, function (server) { + server.addFile("MyObject.qml", "Button {\n\tsignal testSig()\n}"); +}); + +testDefinition("MyObject {\n\tonTestSig: ar|g0\n}", { + origin: "MyObject.qml", + start: { line: 1, ch: 20 }, + end: { line: 1, ch: 24 }, + file: "MyObject.qml", + contextOffset: 29 +}, function (server) { + server.addFile("MyObject.qml", "Button {\n\tsignal testSig(int arg0)\n}"); +}); + +testDefinition("MyObject {\n\ts: te|stFn()\n}", { + origin: "MyObject.qml", + start: { line: 1, ch: 10 }, + end: { line: 1, ch: 16 }, + file: "MyObject.qml", + contextOffset: 19 +}, function (server) { + server.addFile("MyObject.qml", "Button {\n\tfunction testFn() {}\n}"); +}); + +testDefinition("MyObject {\n\ts: btn|Id\n}", { + origin: undefined, + start: undefined, + end: undefined, + file: undefined, + contextOffset: undefined +}, function (server) { + server.addFile("MyObject.qml", "Button {\n\tid: btnId\n}"); +}); + +testDefinition("MyObject {\n\tot|her: ident\n}", { + origin: undefined, + start: undefined, + end: undefined, + file: undefined, + contextOffset: undefined +}, function (server) { + server.addFile("MyObject.qml", "Button {\n\tSecondButton {\n\t\tjproperty var other\n\t}\n}"); +}); + // ID Property testDefinition("QtObject {\n\tid: obj\n\tproperty var prop: {\n\t\tob|j\n\t}\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 5 }, end: { line: 1, ch: 8 }, - file: "test1.qml", + file: "main.qml", contextOffset: 16 }); testDefinition("Window {\n\tButton {\n\t\tid: btn\n\t\tproperty int height\n\t}\n\ttest: bt|n\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 2, ch: 6 }, end: { line: 2, ch: 9 }, - file: "test1.qml", + file: "main.qml", contextOffset: 25 }); testDefinition("Window {\n\tproperty var btn\n\tButton {\n\t\tid: btn\n\t\tproperty int height\n\t}\n\ttest: bt|n\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 3, ch: 6 }, end: { line: 3, ch: 9 }, - file: "test1.qml", + file: "main.qml", contextOffset: 43 }); testDefinition("Window {\n\tproperty var btn\n\tButton {\n\t\tid: bt|n\n\t\tproperty int height\n\t}\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 3, ch: 6 }, end: { line: 3, ch: 9 }, - file: "test1.qml", + file: "main.qml", contextOffset: 43 }); // Property Declarations testDefinition("QtObject {\n\tproperty var prop\n\tpr|op: 3\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 14 }, end: { line: 1, ch: 18 }, - file: "test1.qml", + file: "main.qml", contextOffset: 25 }); testDefinition("Window {\n\tproperty var btn\n\tButton {\n\t\tprop: b|tn\n\t}\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 14 }, end: { line: 1, ch: 17 }, - file: "test1.qml", + file: "main.qml", contextOffset: 23 }); testDefinition("Window {\n\tproperty var btn\n\tButton {\n\t\tButton {\n\t\t\tprop: b|tn\n\t\t}\n\t}\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 14 }, end: { line: 1, ch: 17 }, - file: "test1.qml", + file: "main.qml", contextOffset: 23 }); testDefinition("Window {\n\tButton {\n\t\tproperty var btn\n\t\tbt|n: 3\n\t}\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 2, ch: 15 }, end: { line: 2, ch: 18 }, - file: "test1.qml", + file: "main.qml", contextOffset: 34 }); @@ -94,115 +178,115 @@ testDefinition("Window {\n\tButton {\n\t\tproperty var btn\n\t\tButton {\n\t\t\t // Signals and Signal Handlers testDefinition("Window {\n\tsignal clic|ked(int mouseX, int mouseY)\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 8 }, end: { line: 1, ch: 15 }, - file: "test1.qml", + file: "main.qml", contextOffset: 17 }); testDefinition("Window {\n\tsignal clicked(int mouseX, int mouseY)\n\tonCli|cked: 3\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 8 }, end: { line: 1, ch: 15 }, - file: "test1.qml", + file: "main.qml", contextOffset: 17 }); testDefinition("Window {\n\tsignal clicked(int mouseX, int mouseY)\n\tonClicked: mou|seX\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 20 }, end: { line: 1, ch: 26 }, - file: "test1.qml", + file: "main.qml", contextOffset: 29 }); testDefinition("Window {\n\tsignal clicked(int mouseX, int mouseY)\n\tonClicked: cli|cked(3,4)\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 8 }, end: { line: 1, ch: 15 }, - file: "test1.qml", + file: "main.qml", contextOffset: 17 }); testDefinition("Window {\n\tsignal clicked(int mouseX, int mouseY)\n\tonClicked: onCli|cked\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 8 }, end: { line: 1, ch: 15 }, - file: "test1.qml", + file: "main.qml", contextOffset: 17 }); testDefinition("Window {\n\tsignal clicked(int mouseX, int mouseY)\n\tid: wind\n\tonClicked: wind.onCli|cked\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 8 }, end: { line: 1, ch: 15 }, - file: "test1.qml", + file: "main.qml", contextOffset: 17 }); testDefinition("Window {\n\tsignal clicked(int mouseX, int mouseY)\n\tid: wind\n\tonClicked: wind.cli|cked(3,4)\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 8 }, end: { line: 1, ch: 15 }, - file: "test1.qml", + file: "main.qml", contextOffset: 17 }); testDefinition("Window {\n\tproperty int msg\n\tsignal error(string msg, boolean flag)\n\tonError: {\n\t\tms|g\n\t}\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 2, ch: 21 }, end: { line: 2, ch: 24 }, - file: "test1.qml", + file: "main.qml", contextOffset: 48 }, function (server) { server.jsDefs = []; }); // Function Declarations testDefinition("Window {\n\tfunction te|st(a) {}\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 10 }, end: { line: 1, ch: 14 }, - file: "test1.qml", + file: "main.qml", contextOffset: 19 }); testDefinition("Window {\n\tfunction test(a) {\n\t\ta|\n\t}\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 15 }, end: { line: 1, ch: 16 }, - file: "test1.qml", + file: "main.qml", contextOffset: 24 }); testDefinition("Window {\n\tfunction test(a) {\n\t\tte|st(3)\n\t}\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 10 }, end: { line: 1, ch: 14 }, - file: "test1.qml", + file: "main.qml", contextOffset: 19 }); testDefinition("Window {\n\tfunction test(a) {}\n\ts: te|st(3)\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 10 }, end: { line: 1, ch: 14 }, - file: "test1.qml", + file: "main.qml", contextOffset: 19 }); testDefinition("Window {\n\tfunction test(a) {}\n\ts: {\n\t\tte|st(3)\n\t}\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 10 }, end: { line: 1, ch: 14 }, - file: "test1.qml", + file: "main.qml", contextOffset: 19 }); testDefinition("Window {\n\tfunction test(a) {}\n\tid: wind\n\ts: {\n\t\twind.te|st(3)\n\t}\n}", { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 10 }, end: { line: 1, ch: 14 }, - file: "test1.qml", + file: "main.qml", contextOffset: 19 }); @@ -215,10 +299,10 @@ testDefinition("Window {\n\tfunction test(a) {}\n\tte|st: 3;\n}", { }); testDefinition('Window {\n\tfunction test(a) {\n\t\ta|\n\t}\n}\n\tproperty int a', { - origin: "test1.qml", + origin: "main.qml", start: { line: 1, ch: 15 }, end: { line: 1, ch: 16 }, - file: "test1.qml", + file: "main.qml", contextOffset: 24 }); diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-scoping.js b/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-scoping.js new file mode 100644 index 00000000000..c44f8b4104c --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/test/test-scoping.js @@ -0,0 +1,409 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +"use strict"; + +var plugin = require("../qml.js"); +var infer = require("tern/lib/infer"); +var driver = require("./driver.js"); +var test = driver.test; +var groupStart = driver.groupStart; +var groupEnd = driver.groupEnd; + +var group = exports.group = "Custom Scoping"; +groupStart(group); + +test("QMLObjScope::defProp()", function (server, callback, name) { + infer.withContext(new infer.Context([], server), function () { + // Create the scope and objects to test + var fileScope = new infer.Scope(); + var obj = new infer.Obj(null, "MyObject"); + var scope = new plugin.QMLObjScope(fileScope, null, obj); + + // Define some properties + scope.defProp("first", null); + scope.defProp("second", null); + + // Make sure there are no properties in the scopes themselves + if (fileScope.props && fileScope.props.length > 0) { + return callback("fail", name, "- File scope contained properties (none expected)"); + } + if (scope.props && scope.props.length > 0) { + return callback("fail", name, "- QMLObjScope contained properties (none expected)"); + } + + // Check the Obj Type for the props we created + if (!obj.props.first) { + return callback("fail", name, "- Obj did not contain property 'first'"); + } + if (!obj.props.second) { + return callback("fail", name, "- Obj did not contain property 'second'"); + } + return callback("ok", name); + }); +}); + +test("QMLObjScope::hasProp()", function (server, callback, name) { + infer.withContext(new infer.Context([], server), function () { + // Create the scope and objects to test + var fileScope = new infer.Scope(); + var obj = new infer.Obj(null, "MyObject"); + var scope = new plugin.QMLObjScope(fileScope, null, obj); + + // Define a property on the Obj Type + obj.defProp("first", null); + + // Query the scope for the prop we created and make sure it returns the right one + var prop = scope.hasProp("first", true); + if (!prop) { + return callback("fail", name, "- hasProp('first') returned null"); + } else if (prop.propertyName !== "first") { + return callback("fail", name, "- hasProp('first') returned invalid property"); + } + return callback("ok", name); + }); +}); + +test("QMLObjScope::removeProp()", function (server, callback, name) { + infer.withContext(new infer.Context([], server), function () { + // Create the scope and objects to test + var fileScope = new infer.Scope(); + var obj = new infer.Obj(null, "MyObject"); + var scope = new plugin.QMLObjScope(fileScope, null, obj); + + // Define some properties + scope.defProp("first", null); + scope.defProp("second", null); + + // Remove the properties we defined + scope.removeProp("first"); + scope.removeProp("second"); + + // Check the Obj Type for the props we created + if (obj.props && obj.props.length > 0) { + return callback("fail", name, "- Obj contained properties (none expected)"); + } + return callback("ok", name); + }); +}); + +test("QMLObjScope::gatherProperties()", function (server, callback, name) { + infer.withContext(new infer.Context([], server), function () { + // Create the scope and objects to test + var fileScope = new infer.Scope(); + var obj = new infer.Obj(null, "MyObject"); + var scope = new plugin.QMLObjScope(fileScope, null, obj); + + // Define some properties + fileScope.defProp("third", null); + scope.defProp("first", null); + scope.defProp("second", null); + + // Gather the properties and store them in the order they were received + var props = []; + scope.gatherProperties(function (prop, obj, depth) { + props.push(prop); + }); + + // Check the gathered properties for correctness (order matters) + if (props.length !== 3) { + return callback("fail", name, "- Invalid number of properties gathered (" + props.length + ")"); + } + if (props[0] !== "first") { + return callback("fail", name, "- props[0] was not property 'first'"); + } + if (props[1] !== "second") { + return callback("fail", name, "- props[1] was not property 'second'"); + } + if (props[2] !== "third") { + return callback("fail", name, "- props[2] was not property 'third'"); + } + return callback("ok", name); + }); +}); + +test("QMLMemScope::defProp()", function (server, callback, name) { + infer.withContext(new infer.Context([], server), function () { + // Create the scope and objects to test + var fileScope = new infer.Scope(); + var obj = new infer.Obj(null, "MyObject"); + var objScope = new plugin.QMLObjScope(fileScope, null, obj); + var scope = new plugin.QMLMemScope(objScope, null, fileScope); + + // Define some properties + scope.defProp("first", null); + scope.defProp("second", null); + + // Make sure there are no properties in the scopes themselves + if (fileScope.props && fileScope.props.length > 0) { + return callback("fail", name, "- File scope contained properties (none expected)"); + } + if (scope.props && scope.props.length > 0) { + return callback("fail", name, "- QMLMemScope contained properties (none expected)"); + } + + // Check the Obj Type for the props we created + if (!obj.props.first) { + return callback("fail", name, "- Obj did not contain property 'first'"); + } + if (!obj.props.second) { + return callback("fail", name, "- Obj did not contain property 'second'"); + } + return callback("ok", name); + }); +}); + +test("QMLMemScope::hasProp()", function (server, callback, name) { + infer.withContext(new infer.Context([], server), function () { + // Create the scope and objects to test + var fileScope = new infer.Scope(); + var obj = new infer.Obj(null, "MyObject"); + var objScope = new plugin.QMLObjScope(fileScope, null, obj); + var scope = new plugin.QMLMemScope(objScope, null, fileScope); + + // Define a property on the Obj Type and File Scope + obj.defProp("first", null); + fileScope.defProp("second", null); + + // Query the scope for the prop we created and make sure it returns the right one + var prop = scope.hasProp("first", true); + if (!prop) { + return callback("fail", name, "- hasProp('first') returned null"); + } else if (prop.propertyName !== "first") { + return callback("fail", name, "- hasProp('first') returned invalid property"); + } + prop = scope.hasProp("second", true); + if (!prop) { + return callback("fail", name, "- hasProp('second') returned null"); + } else if (prop.propertyName !== "second") { + return callback("fail", name, "- hasProp('second') returned invalid property"); + } + return callback("ok", name); + }); +}); + +test("QMLMemScope::hasProp() [Multiple Parent Scopes]", function (server, callback, name) { + infer.withContext(new infer.Context([], server), function () { + // Create the scope and objects to test + var fileScope = new infer.Scope(); + var rootObj = new infer.Obj(null, "Root"); + var rootObjScope = new plugin.QMLObjScope(fileScope, null, rootObj); + var obj = new infer.Obj(null, "MyObject"); + var objScope = new plugin.QMLObjScope(rootObjScope, null, obj); + var scope = new plugin.QMLMemScope(objScope, null, fileScope); + + // Define a property on the Root Obj Type and Obj Type + rootObj.defProp("notVisible", null); + obj.defProp("visible", null); + + // Query the scope for the prop we created and make sure it returns the right one + var prop = scope.hasProp("notVisible", true); + if (prop) { + return callback("fail", name, "- found property 'notVisible'"); + } + prop = scope.hasProp("visible", true); + if (!prop) { + return callback("fail", name, "- hasProp('visible') returned null"); + } else if (prop.propertyName !== "visible") { + return callback("fail", name, "- hasProp('visible') returned invalid property"); + } + return callback("ok", name); + }); +}); + +test("QMLMemScope::removeProp()", function (server, callback, name) { + infer.withContext(new infer.Context([], server), function () { + // Create the scope and objects to test + var fileScope = new infer.Scope(); + var obj = new infer.Obj(null, "MyObject"); + var objScope = new plugin.QMLObjScope(fileScope, null, obj); + var scope = new plugin.QMLMemScope(objScope, null, fileScope); + + // Define some properties + scope.defProp("first", null); + scope.defProp("second", null); + + // Remove the properties we defined + scope.removeProp("first"); + scope.removeProp("second"); + + // Check the Obj Type for the props we created + if (obj.props && obj.props.length > 0) { + return callback("fail", name, "- Obj contained properties (none expected)"); + } + return callback("ok", name); + }); +}); + +test("QMLMemScope::gatherProperties()", function (server, callback, name) { + infer.withContext(new infer.Context([], server), function () { + // Create the scope and objects to test + var fileScope = new infer.Scope(); + var obj = new infer.Obj(null, "MyObject"); + var objScope = new plugin.QMLObjScope(fileScope, null, obj); + var scope = new plugin.QMLMemScope(objScope, null, fileScope); + + // Define some properties + fileScope.defProp("third", null); + scope.defProp("first", null); + scope.defProp("second", null); + + // Gather the properties and store them in the order they were received + var props = []; + scope.gatherProperties(function (prop, obj, depth) { + props.push(prop); + }); + + // Check the gathered properties for correctness (order matters) + if (props.length !== 3) { + return callback("fail", name, "- Invalid number of properties gathered (" + props.length + ")"); + } + if (props[0] !== "first") { + return callback("fail", name, "- props[0] was not property 'first'"); + } + if (props[1] !== "second") { + return callback("fail", name, "- props[1] was not property 'second'"); + } + if (props[2] !== "third") { + return callback("fail", name, "- props[2] was not property 'third'"); + } + return callback("ok", name); + }); +}); + +test("QMLMemScope::gatherProperties() [Multiple Parent Scopes]", function (server, callback, name) { + infer.withContext(new infer.Context([], server), function () { + // Create the scope and objects to test + var fileScope = new infer.Scope(); + var rootObj = new infer.Obj(null, "Root"); + var rootObjScope = new plugin.QMLObjScope(fileScope, null, rootObj); + var obj = new infer.Obj(null, "MyObject"); + var objScope = new plugin.QMLObjScope(rootObjScope, null, obj); + var scope = new plugin.QMLMemScope(objScope, null, fileScope); + + // Define a property on the Root Obj Type and Obj Type + rootObj.defProp("notVisible", null); + obj.defProp("visible", null); + + // Gather the properties and store them in the order they were received + var props = []; + scope.gatherProperties(function (prop, obj, depth) { + props.push(prop); + }); + + // Check the gathered properties for correctness (order matters) + if (props.length !== 1) { + return callback("fail", name, "- Invalid number of properties gathered (" + props.length + ")"); + } + if (props[0] !== "visible") { + return callback("fail", name, "- props[0] was not property 'visible'"); + } + return callback("ok", name); + }); +}); + +test("QMLJSScope::hasProp()", function (server, callback, name) { + infer.withContext(new infer.Context([], server), function () { + // Create the scope and objects to test + var fileScope = new infer.Scope(); + var idScope = new infer.Scope(null, ""); + var jsScope = new infer.Scope(null, ""); + var fnScope = new infer.Scope(null, ""); + var scope = new plugin.QMLJSScope(fileScope, null, idScope, jsScope, fnScope); + + // Define properties in each scope + fileScope.defProp("first", null); + idScope.defProp("second", null); + jsScope.defProp("third", null); + fnScope.defProp("fourth", null); + scope.defProp("fifth", null); + + // Query the scope for the prop we created and make sure it returns the right one + var prop = scope.hasProp("first", true); + if (!prop) { + return callback("fail", name, "- hasProp('first') returned null"); + } else if (prop.propertyName !== "first") { + return callback("fail", name, "- hasProp('first') returned invalid property"); + } + prop = scope.hasProp("second", true); + if (!prop) { + return callback("fail", name, "- hasProp('second') returned null"); + } else if (prop.propertyName !== "second") { + return callback("fail", name, "- hasProp('second') returned invalid property"); + } + prop = scope.hasProp("third", true); + if (!prop) { + return callback("fail", name, "- hasProp('third') returned null"); + } else if (prop.propertyName !== "third") { + return callback("fail", name, "- hasProp('third') returned invalid property"); + } + prop = scope.hasProp("fourth", true); + if (!prop) { + return callback("fail", name, "- hasProp('fourth') returned null"); + } else if (prop.propertyName !== "fourth") { + return callback("fail", name, "- hasProp('fourth') returned invalid property"); + } + prop = scope.hasProp("fifth", true); + if (!prop) { + return callback("fail", name, "- hasProp('fifth') returned null"); + } else if (prop.propertyName !== "fifth") { + return callback("fail", name, "- hasProp('fifth') returned invalid property"); + } + return callback("ok", name); + }); +}); + +test("QMLJSScope::gatherProperties()", function (server, callback, name) { + infer.withContext(new infer.Context([], server), function () { + // Create the scope and objects to test + // Create the scope and objects to test + var fileScope = new infer.Scope(); + var idScope = new infer.Scope(null, ""); + var jsScope = new infer.Scope(null, ""); + var fnScope = new infer.Scope(null, ""); + var scope = new plugin.QMLJSScope(fileScope, null, idScope, jsScope, fnScope); + + // Define properties in each scope + fileScope.defProp("fifth", null); + idScope.defProp("first", null); + jsScope.defProp("fourth", null); + fnScope.defProp("third", null); + scope.defProp("second", null); + + // Gather the properties and store them in the order they were received + var props = []; + scope.gatherProperties(function (prop, obj, depth) { + props.push(prop); + }); + + // Check the gathered properties for correctness (order matters) + if (props.length !== 5) { + return callback("fail", name, "- Invalid number of properties gathered (" + props.length + ")"); + } + if (props[0] !== "first") { + return callback("fail", name, "- props[0] was not property 'first'"); + } + if (props[1] !== "second") { + return callback("fail", name, "- props[1] was not property 'second'"); + } + if (props[2] !== "third") { + return callback("fail", name, "- props[2] was not property 'third'"); + } + if (props[3] !== "fourth") { + return callback("fail", name, "- props[3] was not property 'fourth'"); + } + if (props[4] !== "fifth") { + return callback("fail", name, "- props[4] was not property 'fifth'"); + } + return callback("ok", name); + }); +}); + +groupEnd(); \ No newline at end of file