├── .gitignore ├── .travis.yml ├── AUTHORS ├── LICENSE ├── README.md ├── codecov.yml ├── gulpfile.js ├── package.json ├── src ├── api.js ├── header.js └── replacements.js └── tests ├── failing └── ReadonlyProperty.qml ├── js ├── Complex.js ├── Complex.js.exports.json ├── Complex.js.json ├── Pragma.js ├── Pragma.js.exports.json ├── Pragma.js.json ├── Simple.js ├── Simple.js.exports.json ├── Simple.js.json └── invalid │ ├── Invalid.js │ ├── Invalid.js.exports.json │ ├── Invalid.js.json │ ├── ParseError.js │ ├── ParseError.js.exports.json │ └── ParseError.js.json ├── qml ├── Alias.qml ├── Alias.qml.json ├── AnimationOn.qml ├── AnimationOn.qml.json ├── Children.qml ├── Children.qml.json ├── DefaultProperty.qml ├── DefaultProperty.qml.json ├── Drag.qml ├── Drag.qml.json ├── Functions.qml ├── Functions.qml.json ├── Imports.qml ├── Imports.qml.json ├── ImportsNamed.qml ├── ImportsNamed.qml.json ├── MultilineString.qml ├── MultilineString.qml.json ├── ParseFunctionVar.qml ├── ParseFunctionVar.qml.json ├── Pragma.qml ├── Pragma.qml.json ├── Properties.qml ├── Properties.qml.json ├── PropertiesGrouped.qml ├── PropertiesGrouped.qml.json ├── PropertyNamedSignal.qml ├── PropertyNamedSignal.qml.json ├── Signal.qml ├── Signal.qml.json ├── SignalSlot.qml ├── SignalSlot.qml.json └── invalid │ ├── AfterEof.qml │ ├── AfterEof.qml.json │ ├── InvalidAlias.qml │ ├── InvalidAlias.qml.json │ ├── InvalidAlias2.qml │ ├── InvalidAlias2.qml.json │ ├── InvalidSignal.qml │ ├── InvalidSignal.qml.json │ ├── InvalidSignal2.qml │ ├── InvalidSignal2.qml.json │ ├── ParseError.qml │ ├── ParseError.qml.json │ ├── SuddenEof.qml │ └── SuddenEof.qml.json └── tape.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | coverage/ 4 | tmp/ 5 | build/ 6 | .idea/ 7 | *~ 8 | *.bak 9 | *.log 10 | .*.swp 11 | .*.swo 12 | *.kdev4 13 | *kate-swp 14 | .arcconfig 15 | .project 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "stable" 5 | notifications: 6 | email: false 7 | cache: 8 | directories: 9 | - $HOME/.npm/ 10 | - node_modules 11 | before_install: 12 | - npm install -g codecov 13 | after_success: 14 | - npm run cover 15 | - codecov 16 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Authors ordered by first contribution. 2 | 3 | Lauri Paimen 4 | Anton Kreuzkamp 5 | Сковорода Никита Андреевич 6 | Michael Martin Moro 7 | Pavel Vasev 8 | Henrik Rudstrøm 9 | Stephen D'Angelo 10 | Alexander Rössler 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | QmlWeb parser is based on UglifyJS parser, and is distributed under 2 | the BSD-2-Clause license, as follows: 3 | 4 | """ 5 | Copyright (c) 2010 Mihai Bazon 6 | Copyright (c) 2011 Lauri Paimen 7 | Copyright (c) 2013 Anton Kreuzkamp 8 | Copyright (c) 2016 qmlweb-parser contributors 9 | Based on parse-js (http://marijn.haverbeke.nl/parse-js/). 10 | 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions 13 | are met: 14 | 15 | * Redistributions of source code must retain the above 16 | copyright notice, this list of conditions and the following 17 | disclaimer. 18 | 19 | * Redistributions in binary form must reproduce the above 20 | copyright notice, this list of conditions and the following 21 | disclaimer in the documentation and/or other materials 22 | provided with the distribution. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 25 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 27 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 28 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 29 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 30 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 31 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 32 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 33 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 34 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 | SUCH DAMAGE. 36 | """ 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QML parser in JavaScript 2 | 3 | [![Join the chat at https://gitter.im/qmlweb/qmlweb](https://badges.gitter.im/qmlweb/qmlweb.svg)](https://gitter.im/qmlweb/qmlweb) 4 | [![Build Status](https://travis-ci.org/qmlweb/qmlweb-parser.svg?branch=master)](https://travis-ci.org/qmlweb/qmlweb-parser) 5 | [![codecov](https://codecov.io/gh/qmlweb/qmlweb-parser/branch/master/graph/badge.svg)](https://codecov.io/gh/qmlweb/qmlweb-parser) 6 | 7 | [![npm](https://img.shields.io/npm/v/qmlweb-parser.svg)](https://www.npmjs.com/package/qmlweb-parser) 8 | [![GitHub tag](https://img.shields.io/github/tag/qmlweb/qmlweb-parser.svg)](https://github.com/qmlweb/qmlweb-parser/releases) 9 | 10 | This is a QML parser in pure JavaScript, based on UglifyJS parser. 11 | 12 | It serves both as an optional dependency to 13 | [QmlWeb](https://github.com/qmlweb/qmlweb) to allow it parse QML and 14 | JavaScript files in runtime and as a parser that powers 15 | [gulp-qmlweb](https://github.com/qmlweb/gulp-qmlweb) to pre-parse 16 | files before serving them to the browser. 17 | 18 | ## License 19 | 20 | QmlWeb parser is licensed under the BSD-2-Clause license, see 21 | [LICENSE](https://github.com/qmlweb/qmlweb-parser/blob/master/LICENSE). 22 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | threshold: 1 6 | changes: off 7 | patch: off 8 | 9 | comment: off 10 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const gulp = require("gulp"); 4 | const concat = require("gulp-concat"); 5 | const replace = require("gulp-replace"); 6 | const changed = require("gulp-changed"); 7 | const order = require("gulp-order"); 8 | const uglify = require("gulp-uglify"); 9 | const sourcemaps = require("gulp-sourcemaps"); 10 | const iife = require("gulp-iife"); 11 | const istanbul = require("gulp-istanbul"); 12 | const tape = require("gulp-tape"); 13 | const tapSpec = require("tap-spec"); 14 | 15 | const replacements = require("./src/replacements"); 16 | 17 | const sources = [ 18 | "src/header.js", 19 | "node_modules/uglify-js/lib/parse-js.js", 20 | "src/api.js" 21 | ]; 22 | 23 | gulp.task("build-dev", function() { 24 | return gulp.src(sources) 25 | .pipe(order(sources, { base: __dirname })) 26 | .pipe(sourcemaps.init()) 27 | .pipe(replace(replacements[0].from, replacements[0].to)) 28 | .pipe(replace(replacements[1].from, replacements[1].to)) 29 | .pipe(replace(replacements[2].from, replacements[2].to)) 30 | .pipe(replace(replacements[3].from, replacements[3].to)) 31 | .pipe(concat("qmlweb.parser.js")) 32 | .pipe(iife({ 33 | useStrict: false, 34 | params: ["exports"], 35 | args: ["{}"] 36 | })) 37 | .pipe(changed("./lib")) 38 | .pipe(sourcemaps.write("./")) 39 | .pipe(gulp.dest("./lib")); 40 | }); 41 | 42 | gulp.task("build", gulp.series("build-dev", function() { 43 | return gulp.src("./lib/qmlweb.parser.js") 44 | .pipe(concat("qmlweb.parser.min.js")) 45 | .pipe(changed("./lib")) 46 | .pipe(sourcemaps.init({ loadMaps: true })) 47 | .pipe(uglify()) 48 | .pipe(sourcemaps.write("./")) 49 | .pipe(gulp.dest("./lib")); 50 | })); 51 | 52 | gulp.task("build-covered-api", function() { 53 | return gulp.src(["src/api.js"]) 54 | .pipe(istanbul()) 55 | .pipe(concat("api.covered.js")) 56 | .pipe(gulp.dest("./tmp")); 57 | }); 58 | 59 | gulp.task("build-covered", gulp.series("build-covered-api", function() { 60 | process.env.QMLWEB_PARSER_PATH = "tmp/qmlweb.parser.covered"; 61 | return gulp.src( 62 | [ 63 | "src/header.js", 64 | "node_modules/uglify-js/lib/parse-js.js", 65 | "tmp/api.covered.js" 66 | ]) 67 | .pipe(order(sources, { base: __dirname })) 68 | .pipe(replace(replacements[0].from, replacements[0].to)) 69 | .pipe(replace(replacements[1].from, replacements[1].to)) 70 | .pipe(replace(replacements[2].from, replacements[2].to)) 71 | .pipe(replace(replacements[3].from, replacements[3].to)) 72 | .pipe(concat("qmlweb.parser.covered.js")) 73 | .pipe(iife({ 74 | useStrict: false, 75 | params: ["exports"], 76 | args: ["{}"] 77 | })) 78 | .pipe(gulp.dest("./tmp")); 79 | })); 80 | 81 | gulp.task("test", gulp.series("build", function() { 82 | return gulp.src("tests/tape.js") 83 | .pipe(tape({ 84 | reporter: tapSpec() 85 | })); 86 | })); 87 | 88 | gulp.task("cover", gulp.series("build-covered", function() { 89 | return gulp.src("tests/tape.js") 90 | .pipe(tape({ 91 | reporter: tapSpec() 92 | })) 93 | .pipe(istanbul.writeReports()); 94 | })); 95 | 96 | gulp.task("watch", gulp.series("build", function() { 97 | gulp.watch(sources, ["build"]); 98 | })); 99 | 100 | gulp.task("watch-dev", gulp.series("build-dev", function() { 101 | gulp.watch(sources, ["build-dev"]); 102 | })); 103 | 104 | gulp.task("default", gulp.series("watch")); 105 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qmlweb-parser", 3 | "version": "0.3.5", 4 | "description": "QML parser in JavaScript", 5 | "license": "BSD-2-Clause", 6 | "repository": "qmlweb/qmlweb-parser", 7 | "devDependencies": { 8 | "gulp": "^4.0.0", 9 | "gulp-changed": "^1.3.0", 10 | "gulp-concat": "^2.6.1", 11 | "gulp-iife": "^0.3.0", 12 | "gulp-istanbul": "^0.10.4", 13 | "gulp-order": "^1.1.1", 14 | "gulp-replace": "^0.6.1", 15 | "gulp-sourcemaps": "^2.6.4", 16 | "gulp-tape": "0.0.9", 17 | "gulp-uglify": "^3.0.0", 18 | "istanbul": "^0.4.3", 19 | "tap-spec": "^4.1.1", 20 | "tape": "^4.5.1", 21 | "uglify-js": "^1.3.5" 22 | }, 23 | "scripts": { 24 | "test": "gulp test", 25 | "cover": "gulp cover", 26 | "build": "gulp build", 27 | "watch": "gulp watch" 28 | }, 29 | "main": "lib/qmlweb.parser.js", 30 | "files": [ 31 | "AUTHORS", 32 | "LICENSE", 33 | "README.md", 34 | "lib/**/*.js", 35 | "lib/**/*.js.map", 36 | "src/**/*.js", 37 | "tests/**/*.js", 38 | "tests/**/*.json", 39 | "tests/**/*.qml", 40 | "gulpfile.js" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /src/api.js: -------------------------------------------------------------------------------- 1 | /* @license 2 | 3 | Copyright (c) 2010 Mihai Bazon 4 | Copyright (c) 2011 Lauri Paimen 5 | Copyright (c) 2013 Anton Kreuzkamp 6 | Copyright (c) 2016 qmlweb-parser contributors 7 | Based on parse-js (http://marijn.haverbeke.nl/parse-js/). 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions 11 | are met: 12 | 13 | * Redistributions of source code must retain the above 14 | copyright notice, this list of conditions and the following 15 | disclaimer. 16 | 17 | * Redistributions in binary form must reproduce the above 18 | copyright notice, this list of conditions and the following 19 | disclaimer in the documentation and/or other materials 20 | provided with the distribution. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 23 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 25 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 26 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 27 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 28 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 31 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 32 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 | SUCH DAMAGE. 34 | */ 35 | 36 | /* 37 | * QML parser and parsetree'er. 38 | * 39 | * Exports: 40 | * 41 | * - qmlweb_parse(src, type) -- parses QML source and returns it as output 42 | * tree expected by the QML engine 43 | */ 44 | 45 | // Object cloning for debug prints. 46 | function clone(obj) { 47 | if (obj == null || typeof obj !== 'object') 48 | return obj; 49 | 50 | var temp = {}; // changed 51 | 52 | for (var key in obj) 53 | temp[key] = clone(obj[key]); 54 | return temp; 55 | } 56 | 57 | function QMLParseError(message, line, col, pos, source) { 58 | JS_Parse_Error.call(this, message, line, col, pos); 59 | var comment = extractLinesForErrorDiag(source, line); 60 | this.comment = comment ? comment : ""; 61 | this.message += " (line: " + this.line + ", col: " + col + ", pos: " + pos + ")" + "\n" + comment + "\n"; 62 | this.file = qmlweb_parse.nowParsingFile; 63 | } 64 | QMLParseError.prototype = new Error(); 65 | 66 | function extractLinesForErrorDiag(text, line) { 67 | var r = ""; 68 | var lines = text.split("\n"); 69 | 70 | for (var i = line - 3; i <= line + 3; i++) { 71 | if (i >= 0 && i < lines.length ) { 72 | var mark = i === line ? ">>" : " "; 73 | r += mark + (i + 1) + " " + lines[i] + "\n"; 74 | } 75 | } 76 | 77 | return r; 78 | } 79 | 80 | function qmlweb_tokenizer($TEXT, document_type) { 81 | // Override UglifyJS methods 82 | 83 | parse_error = function(err) { 84 | throw new QMLParseError(err, S.tokline, S.tokcol, S.tokpos, S.text); 85 | }; 86 | 87 | if (document_type === qmlweb_parse.QMLDocument) { 88 | // We need to support multiline strings in QML mode, allow newline chars 89 | // We don't need to support octal escape sequences, as those are not 90 | // supported in QML 91 | read_string = function() { 92 | return with_eof_error("Unterminated string constant", function(){ 93 | var quote = next(), ret = ""; 94 | for (;;) { 95 | var ch = next(true); 96 | if (ch == "\\") { 97 | ch = read_escaped_char(true); 98 | } else if (ch == quote) { 99 | break; 100 | } 101 | ret += ch; 102 | } 103 | return token("string", ret); 104 | }); 105 | } 106 | } 107 | 108 | // WARNING: Here the original tokenizer() code gets embedded 109 | return tokenizer($TEXT); 110 | } 111 | 112 | function qmlweb_parse($TEXT, document_type, exigent_mode) { 113 | var embed_tokens = false; // embed_tokens option is not supported 114 | document_type = document_type || qmlweb_parse.QMLDocument; 115 | 116 | var TEXT = $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''); 117 | $TEXT = qmlweb_tokenizer($TEXT, document_type); 118 | 119 | // WARNING: Here the original parse() code gets embedded 120 | parse($TEXT,exigent_mode,false); 121 | // NOTE: Don't insert spaces between arguments! 122 | 123 | // Override UglifyJS methods 124 | 125 | croak = function(msg, line, col, pos) { 126 | var ctx = S.input.context(); 127 | throw new QMLParseError(msg, 128 | line != null ? line : ctx.tokline, 129 | col != null ? col : ctx.tokcol, 130 | pos != null ? pos : ctx.tokpos, 131 | TEXT 132 | ); 133 | }; 134 | 135 | expect_token = function(type, val) { 136 | if (is(type, val)) { 137 | return next(); 138 | } 139 | token_error(S.token, "Unexpected token " + S.token.type + " " + S.token.value + ", expected " + type + " " + val); 140 | }; 141 | 142 | var statement_js = statement; 143 | statement = function() { 144 | var in_qmlprop = !!statement.in_qmlprop; 145 | statement.in_qmlprop = false; 146 | switch (S.token.type) { 147 | case "punc": 148 | switch (S.token.value) { 149 | case ".": 150 | return is_token(peek(), "name", "pragma") ? qml_pragma_statement() : unexpected(); 151 | } 152 | case "keyword": 153 | switch (S.token.value) { 154 | case "function": 155 | if (in_qmlprop) { 156 | next(); 157 | return function_(false); 158 | } 159 | } 160 | } 161 | return statement_js(); 162 | }; 163 | 164 | array_ = function() { 165 | var from = S.token.pos; 166 | var stat = expr_list("]", !exigent_mode, true); 167 | var to = S.token.pos; 168 | return as("array", stat, "[" + TEXT.substr(from, to - from)); 169 | }; 170 | 171 | expression = function(commas, no_in) { 172 | if (arguments.length == 0) 173 | commas = true; 174 | var expr = maybe_qmlelem(no_in); 175 | if (commas && is("punc", ",")) { 176 | next(); 177 | return as("seq", expr, expression(true, no_in)); 178 | } 179 | return expr; 180 | }; 181 | 182 | // QML-specific methods 183 | 184 | function as_statement() { 185 | var res = slice(arguments); 186 | S.in_function++; 187 | var start = S.token.pos; 188 | res.push(statement()); 189 | var end = S.token.pos; 190 | S.in_function--; 191 | res.push(TEXT.substr(start, end - start)); 192 | return res; 193 | } 194 | 195 | function maybe_qmlelem(no_in) { 196 | var expr = maybe_assign(no_in); 197 | if (is("punc", "{")) { 198 | var qmlelem_name; 199 | if (expr[0] === "dot") { 200 | qmlelem_name = ["dot", expr[1][1], expr[2]]; 201 | } else { 202 | qmlelem_name = expr[1]; 203 | } 204 | return as("qmlelem", qmlelem_name, undefined, qmlblock()); 205 | } 206 | return expr; 207 | } 208 | 209 | function qml_is_element(name) { 210 | if (typeof name === "string") { 211 | return name[0].toUpperCase() === name[0]; 212 | } 213 | return qml_is_element(name[1]) && name[2][0].toUpperCase() === name[2][0]; 214 | } 215 | 216 | function qmlblock() { 217 | expect("{"); 218 | var a = []; 219 | while (!is("punc", "}")) { 220 | if (is("eof")) 221 | unexpected(); 222 | a.push(qmlstatement()); 223 | } 224 | expect("}"); 225 | return a; 226 | } 227 | 228 | function qmlpropdef() { 229 | var type = S.token.value; 230 | next(); 231 | 232 | var subtype; 233 | if (is("operator", "<")) { 234 | next(); 235 | subtype = S.token.value; 236 | next(); 237 | expect_token("operator", ">"); 238 | } 239 | 240 | var name = S.token.value; 241 | next(); 242 | if (type == "alias") { 243 | expect(":"); 244 | if (!is("name")) 245 | unexpected(); 246 | var objName = S.token.value; 247 | next(); 248 | if (is("punc", ".")) { 249 | next(); 250 | if (!is("name")) 251 | unexpected(); 252 | var propName = S.token.value; 253 | next(); 254 | } 255 | return as("qmlaliasdef", name, false, objName, propName); 256 | } 257 | if (is("punc", ":")) { 258 | next(); 259 | statement.in_qmlprop = true; 260 | return as_statement("qmlpropdef", name, false, type); 261 | } else if (is("punc", ";")) 262 | next(); 263 | return as("qmlpropdef", name, false, type); 264 | } 265 | 266 | function qmldefaultprop() { 267 | next(); 268 | expect_token("name", "property"); 269 | const tree = qmlpropdef(); 270 | tree[2] = true; 271 | return tree; 272 | } 273 | 274 | function qmlsignaldef() { 275 | var name = S.token.value; 276 | next(); 277 | var args = []; 278 | if (is("punc", "(")) { 279 | next(); 280 | var first = true; 281 | while (!is("punc", ")")) { 282 | if (first) 283 | first = false; 284 | else 285 | expect(","); 286 | if (!is("name") && !is('keyword', 'var')) 287 | unexpected(); 288 | var type = S.token.value; 289 | next(); 290 | if (!is("name")) 291 | unexpected(); 292 | args.push({ type: type, name: S.token.value }); 293 | next(); 294 | } 295 | next(); 296 | } 297 | if (is("punc", ";")) 298 | next(); 299 | return as("qmlsignaldef", name, args); 300 | } 301 | 302 | function qmlstatement() { 303 | if (is("keyword", "function")) { 304 | var from = S.token.pos; 305 | next(); 306 | var stat = function_(true); 307 | var to = S.token.pos; 308 | var name = stat[1]; 309 | return as("qmlmethod", name, stat, TEXT.substr(from, to - from)); 310 | } else if (is("name", "signal")) { 311 | next(); 312 | if (is("punc", ":")) { 313 | next(); 314 | return as_statement("qmlprop", "signal"); 315 | } else { 316 | return qmlsignaldef(); 317 | } 318 | } else if (S.token.type == "name") { 319 | if (S.token.value == "property" && !is_token(peek(), "punc", ":")) { 320 | next(); 321 | return qmlpropdef(); 322 | } 323 | 324 | var propname = subscripts(as_name(), false); 325 | if (qml_is_element(propname)) { 326 | // Element 327 | var onProp; 328 | if (is("name", "on")) { 329 | next(); 330 | onProp = S.token.value; 331 | next(); 332 | } 333 | return as("qmlelem", propname, onProp, qmlblock()); 334 | } else if (is("punc", "{")) { 335 | return as("qmlobj", propname, qmlblock()); 336 | } else { 337 | // Evaluatable item 338 | expect(":"); 339 | statement.in_qmlprop = true; 340 | return as_statement("qmlprop", propname); 341 | } 342 | } else if (is("keyword", "default")) { 343 | return qmldefaultprop(); 344 | } else { 345 | todo(); 346 | } 347 | } 348 | 349 | function qml_pragma_statement() { 350 | next(); 351 | next(); 352 | var pragma = S.token.value; 353 | next(); 354 | return as("qmlpragma", pragma); 355 | } 356 | 357 | function qmlpragma() { 358 | next(); 359 | var pragma = S.token.value; 360 | next(); 361 | return as("qmlpragma", pragma); 362 | } 363 | 364 | function qmlimport() { 365 | // todo 366 | next(); 367 | var moduleName = S.token.value; 368 | var isDottedNotation = S.token.type == "name"; 369 | next(); 370 | 371 | while (is("punc", ".")) { 372 | next(); 373 | moduleName += "." + S.token.value; 374 | next(); 375 | } 376 | if (is("num")) { 377 | var version = S.token.value; 378 | next(); 379 | } 380 | var namespace = ""; 381 | if (is("name", "as")) { 382 | next(); 383 | namespace = S.token.value; 384 | next(); 385 | } 386 | return as("qmlimport", moduleName, version, namespace, isDottedNotation); 387 | } 388 | 389 | function qmldocument() { 390 | var imports = []; 391 | var pragma = []; 392 | while (true) { 393 | if (is("name", "import")) { 394 | imports.push(qmlimport()); 395 | } else if (is("name", "pragma")) { 396 | pragma.push(qmlpragma()); 397 | } else { 398 | break; 399 | } 400 | } 401 | var root = qmlstatement(); 402 | if (!is("eof")) 403 | unexpected(); 404 | return pragma.length > 0 ? 405 | as("toplevel", imports, root, pragma) : 406 | as("toplevel", imports, root); 407 | } 408 | 409 | function jsdocument() { 410 | var statements = []; 411 | while (!is("eof")) { 412 | statements.push(statement()); 413 | } 414 | return as("jsresource", statements); 415 | } 416 | 417 | function amIn(s) { 418 | console && console.log(s, clone(S), S.token.type, S.token.value); 419 | } 420 | 421 | function todo() { 422 | amIn("todo parse:"); 423 | next(); 424 | } 425 | 426 | if (document_type === qmlweb_parse.JSResource) { 427 | return jsdocument(); 428 | } else { 429 | return qmldocument(); 430 | } 431 | } 432 | 433 | qmlweb_parse.nowParsingFile = ''; // TODO: make a parameter of qmlweb_parse 434 | qmlweb_parse.QMLDocument = 1; 435 | qmlweb_parse.JSResource = 2; 436 | 437 | function qmlweb_jsparse(source) { 438 | var obj = { pragma: [], exports: [], source: source }; 439 | var AST_Tree = qmlweb_parse(source, qmlweb_parse.JSResource); 440 | var main_scope = AST_Tree[1]; 441 | 442 | for (var i = 0 ; i < main_scope.length ; ++i) { 443 | var item = main_scope[i]; 444 | 445 | switch (item[0]) { 446 | case "var": 447 | obj.exports.push(item[1][0][0]); 448 | break ; 449 | case "defun": 450 | obj.exports.push(item[1]); 451 | break ; 452 | case "qmlpragma": 453 | obj.pragma.push(item[1]); 454 | break ; 455 | } 456 | } 457 | return obj; 458 | } 459 | 460 | if (typeof module !== 'undefined' && module.exports) { 461 | // Node.js 462 | module.exports.parse = qmlweb_parse; 463 | module.exports.jsparse = qmlweb_jsparse; 464 | // Legacy 465 | module.exports.qmlweb_parse = qmlweb_parse; 466 | module.exports.qmlweb_jsparse = qmlweb_jsparse; 467 | } 468 | if (typeof window !== 'undefined') { 469 | // Browser: export only QmlWeb.parse and QmlWeb.jsparse 470 | if (typeof QmlWeb === 'undefined') { 471 | window.QmlWeb = {}; 472 | } 473 | QmlWeb.parse = qmlweb_parse; 474 | QmlWeb.jsparse = qmlweb_jsparse; 475 | } 476 | -------------------------------------------------------------------------------- /src/header.js: -------------------------------------------------------------------------------- 1 | /* This bundle consists of two parts: UglifyJS and QmlWeb parser additions 2 | * on top of it. Those parts have different copyrights, but are both licensed 3 | * under BSD-2-Clause license (text follows). Each part has a separate header. 4 | */ 5 | 6 | var parse, tokenizer; 7 | -------------------------------------------------------------------------------- /src/replacements.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const uglify = require('uglify-js/lib/parse-js'); 4 | 5 | function source(fn) { 6 | return Function.prototype.toString.call(fn) 7 | .replace(/\n/g, '%NL%') 8 | .replace(/\r/g, '') 9 | .replace(/[^{]*{/, '') 10 | .replace(/}[^}]*$/, '') 11 | .replace(/%NL%/g, '\n'); 12 | } 13 | 14 | const tokenizer = source(uglify.tokenizer); 15 | const parse = source(uglify.parse).split('return as("toplevel"')[0]; 16 | 17 | module.exports = [ 18 | { from: Function.prototype.toString.call(uglify.parse), to: '' }, 19 | { from: Function.prototype.toString.call(uglify.tokenizer), to: '' }, 20 | { from: 'return tokenizer($TEXT);', to: tokenizer }, 21 | { from: 'parse($TEXT,exigent_mode,false);', to: parse } 22 | ]; 23 | -------------------------------------------------------------------------------- /tests/failing/ReadonlyProperty.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | readonly property var readonlyProperty: 10 5 | } 6 | -------------------------------------------------------------------------------- /tests/js/Complex.js: -------------------------------------------------------------------------------- 1 | function func() { 2 | variable++; 3 | } 4 | 5 | function func2() { 6 | return variable || 'Error'; 7 | } 8 | 9 | var info = { 10 | version: 42 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /tests/js/Complex.js.exports.json: -------------------------------------------------------------------------------- 1 | { 2 | "pragma" : [], 3 | "exports": [ 4 | "func", 5 | "func2", 6 | "info" 7 | ], 8 | "source": "function func() {\n variable++;\n}\n\nfunction func2() {\n return variable || 'Error';\n}\n\nvar info = {\n version: 42\n};\n\n" 9 | } -------------------------------------------------------------------------------- /tests/js/Complex.js.json: -------------------------------------------------------------------------------- 1 | [ 2 | "jsresource", 3 | [ 4 | [ 5 | "defun", 6 | "func", 7 | [], 8 | [ 9 | [ 10 | "stat", 11 | [ 12 | "unary-postfix", 13 | "++", 14 | [ 15 | "name", 16 | "variable" 17 | ] 18 | ] 19 | ] 20 | ] 21 | ], 22 | [ 23 | "defun", 24 | "func2", 25 | [], 26 | [ 27 | [ 28 | "return", 29 | [ 30 | "binary", 31 | "||", 32 | [ 33 | "name", 34 | "variable" 35 | ], 36 | [ 37 | "string", 38 | "Error" 39 | ] 40 | ] 41 | ] 42 | ] 43 | ], 44 | [ 45 | "var", 46 | [ 47 | [ 48 | "info", 49 | [ 50 | "object", 51 | [ 52 | [ 53 | "version", 54 | [ 55 | "num", 56 | 42 57 | ] 58 | ] 59 | ] 60 | ] 61 | ] 62 | ] 63 | ] 64 | ] 65 | ] -------------------------------------------------------------------------------- /tests/js/Pragma.js: -------------------------------------------------------------------------------- 1 | .pragma library 2 | 3 | var variable = 0; 4 | -------------------------------------------------------------------------------- /tests/js/Pragma.js.exports.json: -------------------------------------------------------------------------------- 1 | { 2 | "pragma" : [ 3 | "library" 4 | ], 5 | "exports": [ 6 | "variable" 7 | ], 8 | "source": ".pragma library\n\nvar variable = 0;\n" 9 | } -------------------------------------------------------------------------------- /tests/js/Pragma.js.json: -------------------------------------------------------------------------------- 1 | [ 2 | "jsresource", 3 | [ 4 | [ 5 | "qmlpragma", 6 | "library" 7 | ], 8 | [ 9 | "var", 10 | [ 11 | [ 12 | "variable", 13 | [ 14 | "num", 15 | 0 16 | ] 17 | ] 18 | ] 19 | ] 20 | ] 21 | ] -------------------------------------------------------------------------------- /tests/js/Simple.js: -------------------------------------------------------------------------------- 1 | function importedTest(x) { 2 | return x + x; 3 | } 4 | 5 | var importedColor = 'magenta'; 6 | -------------------------------------------------------------------------------- /tests/js/Simple.js.exports.json: -------------------------------------------------------------------------------- 1 | { 2 | "pragma" : [], 3 | "exports": [ 4 | "importedTest", 5 | "importedColor" 6 | ], 7 | "source": "function importedTest(x) {\n return x + x;\n}\n\nvar importedColor = 'magenta';\n" 8 | } -------------------------------------------------------------------------------- /tests/js/Simple.js.json: -------------------------------------------------------------------------------- 1 | [ 2 | "jsresource", 3 | [ 4 | [ 5 | "defun", 6 | "importedTest", 7 | [ 8 | "x" 9 | ], 10 | [ 11 | [ 12 | "return", 13 | [ 14 | "binary", 15 | "+", 16 | [ 17 | "name", 18 | "x" 19 | ], 20 | [ 21 | "name", 22 | "x" 23 | ] 24 | ] 25 | ] 26 | ] 27 | ], 28 | [ 29 | "var", 30 | [ 31 | [ 32 | "importedColor", 33 | [ 34 | "string", 35 | "magenta" 36 | ] 37 | ] 38 | ] 39 | ] 40 | ] 41 | ] -------------------------------------------------------------------------------- /tests/js/invalid/Invalid.js: -------------------------------------------------------------------------------- 1 | var x = 10; 2 | 3 | function test() { 4 | if (x) { 5 | retudrn 20; 6 | } else { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/js/invalid/Invalid.js.exports.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "name": "Error", 4 | "message": "Unexpected token: num (20) (line: 5, col: 12, pos: 54)\n 2 \n 3 function test() {\n 4 if (x) {\n>>5 retudrn 20;\n 6 } else {\n 7 }\n 8 }\n\n" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/js/invalid/Invalid.js.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "name": "Error", 4 | "message": "Unexpected token: num (20) (line: 5, col: 12, pos: 54)\n 2 \n 3 function test() {\n 4 if (x) {\n>>5 retudrn 20;\n 6 } else {\n 7 }\n 8 }\n\n" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/js/invalid/ParseError.js: -------------------------------------------------------------------------------- 1 | var x = 10; 2 | 3 | function test() { 4 | if (x) { 5 | ret'urn 20; 6 | } else { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/js/invalid/ParseError.js.exports.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "name": "Error", 4 | "message": "Unterminated string constant (line: 5, col: 7, pos: 49)\n 2 \n 3 function test() {\n 4 if (x) {\n>>5 ret'urn 20;\n 6 } else {\n 7 }\n 8 }\n\n" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/js/invalid/ParseError.js.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "name": "Error", 4 | "message": "Unterminated string constant (line: 5, col: 7, pos: 49)\n 2 \n 3 function test() {\n 4 if (x) {\n>>5 ret'urn 20;\n 6 } else {\n 7 }\n 8 }\n\n" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/qml/Alias.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | property alias childX: child.x 5 | 6 | Item { 7 | id: child 8 | x: 125 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/qml/Alias.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2, 8 | "", 9 | true 10 | ] 11 | ], 12 | [ 13 | "qmlelem", 14 | "Item", 15 | null, 16 | [ 17 | [ 18 | "qmlaliasdef", 19 | "childX", 20 | false, 21 | "child", 22 | "x" 23 | ], 24 | [ 25 | "qmlelem", 26 | "Item", 27 | null, 28 | [ 29 | [ 30 | "qmlprop", 31 | "id", 32 | [ 33 | "stat", 34 | [ 35 | "name", 36 | "child" 37 | ] 38 | ], 39 | "child\n " 40 | ], 41 | [ 42 | "qmlprop", 43 | "x", 44 | [ 45 | "stat", 46 | [ 47 | "num", 48 | 125 49 | ] 50 | ], 51 | "125\n " 52 | ] 53 | ] 54 | ] 55 | ] 56 | ] 57 | ] 58 | -------------------------------------------------------------------------------- /tests/qml/AnimationOn.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | width: 20 5 | height: 10 6 | 7 | NumberAnimation on x { 8 | from: 0; to: 10; duration: 50 9 | running: true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/qml/AnimationOn.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2, 8 | "", 9 | true 10 | ] 11 | ], 12 | [ 13 | "qmlelem", 14 | "Item", 15 | null, 16 | [ 17 | [ 18 | "qmlprop", 19 | "width", 20 | [ 21 | "stat", 22 | [ 23 | "num", 24 | 20 25 | ] 26 | ], 27 | "20\n " 28 | ], 29 | [ 30 | "qmlprop", 31 | "height", 32 | [ 33 | "stat", 34 | [ 35 | "num", 36 | 10 37 | ] 38 | ], 39 | "10\n\n " 40 | ], 41 | [ 42 | "qmlelem", 43 | "NumberAnimation", 44 | "x", 45 | [ 46 | [ 47 | "qmlprop", 48 | "from", 49 | [ 50 | "stat", 51 | [ 52 | "num", 53 | 0 54 | ] 55 | ], 56 | "0; " 57 | ], 58 | [ 59 | "qmlprop", 60 | "to", 61 | [ 62 | "stat", 63 | [ 64 | "num", 65 | 10 66 | ] 67 | ], 68 | "10; " 69 | ], 70 | [ 71 | "qmlprop", 72 | "duration", 73 | [ 74 | "stat", 75 | [ 76 | "num", 77 | 50 78 | ] 79 | ], 80 | "50\n " 81 | ], 82 | [ 83 | "qmlprop", 84 | "running", 85 | [ 86 | "stat", 87 | [ 88 | "name", 89 | "true" 90 | ] 91 | ], 92 | "true\n " 93 | ] 94 | ] 95 | ] 96 | ] 97 | ] 98 | ] -------------------------------------------------------------------------------- /tests/qml/Children.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | 3 | Rectangle { 4 | width: 75 5 | height: 15 6 | color: 'green' 7 | 8 | Rectangle { 9 | width: 10 10 | height: 10 11 | } 12 | Item { 13 | Rectangle { 14 | height: 20 15 | width: 20 16 | } 17 | } 18 | Item { 19 | width: 10 20 | height: 12 21 | x: 30 22 | border.width: 2 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/qml/Children.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2.5, 8 | "", 9 | true 10 | ] 11 | ], 12 | [ 13 | "qmlelem", 14 | "Rectangle", 15 | null, 16 | [ 17 | [ 18 | "qmlprop", 19 | "width", 20 | [ 21 | "stat", 22 | [ 23 | "num", 24 | 75 25 | ] 26 | ], 27 | "75\n " 28 | ], 29 | [ 30 | "qmlprop", 31 | "height", 32 | [ 33 | "stat", 34 | [ 35 | "num", 36 | 15 37 | ] 38 | ], 39 | "15\n " 40 | ], 41 | [ 42 | "qmlprop", 43 | "color", 44 | [ 45 | "stat", 46 | [ 47 | "string", 48 | "green" 49 | ] 50 | ], 51 | "'green'\n\n " 52 | ], 53 | [ 54 | "qmlelem", 55 | "Rectangle", 56 | null, 57 | [ 58 | [ 59 | "qmlprop", 60 | "width", 61 | [ 62 | "stat", 63 | [ 64 | "num", 65 | 10 66 | ] 67 | ], 68 | "10\n " 69 | ], 70 | [ 71 | "qmlprop", 72 | "height", 73 | [ 74 | "stat", 75 | [ 76 | "num", 77 | 10 78 | ] 79 | ], 80 | "10\n " 81 | ] 82 | ] 83 | ], 84 | [ 85 | "qmlelem", 86 | "Item", 87 | null, 88 | [ 89 | [ 90 | "qmlelem", 91 | "Rectangle", 92 | null, 93 | [ 94 | [ 95 | "qmlprop", 96 | "height", 97 | [ 98 | "stat", 99 | [ 100 | "num", 101 | 20 102 | ] 103 | ], 104 | "20\n " 105 | ], 106 | [ 107 | "qmlprop", 108 | "width", 109 | [ 110 | "stat", 111 | [ 112 | "num", 113 | 20 114 | ] 115 | ], 116 | "20\n " 117 | ] 118 | ] 119 | ] 120 | ] 121 | ], 122 | [ 123 | "qmlelem", 124 | "Item", 125 | null, 126 | [ 127 | [ 128 | "qmlprop", 129 | "width", 130 | [ 131 | "stat", 132 | [ 133 | "num", 134 | 10 135 | ] 136 | ], 137 | "10\n " 138 | ], 139 | [ 140 | "qmlprop", 141 | "height", 142 | [ 143 | "stat", 144 | [ 145 | "num", 146 | 12 147 | ] 148 | ], 149 | "12\n " 150 | ], 151 | [ 152 | "qmlprop", 153 | "x", 154 | [ 155 | "stat", 156 | [ 157 | "num", 158 | 30 159 | ] 160 | ], 161 | "30\n " 162 | ], 163 | [ 164 | "qmlprop", 165 | [ 166 | "dot", 167 | "border", 168 | "width" 169 | ], 170 | [ 171 | "stat", 172 | [ 173 | "num", 174 | 2 175 | ] 176 | ], 177 | "2\n " 178 | ] 179 | ] 180 | ] 181 | ] 182 | ] 183 | ] 184 | -------------------------------------------------------------------------------- /tests/qml/DefaultProperty.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | default property var defaultProperty 5 | 6 | width: defaultProperty.foo * 2 7 | } 8 | -------------------------------------------------------------------------------- /tests/qml/DefaultProperty.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2, 8 | "", 9 | true 10 | ] 11 | ], 12 | [ 13 | "qmlelem", 14 | "Item", 15 | null, 16 | [ 17 | [ 18 | "qmlpropdef", 19 | "defaultProperty", 20 | true, 21 | "var" 22 | ], 23 | [ 24 | "qmlprop", 25 | "width", 26 | [ 27 | "stat", 28 | [ 29 | "binary", 30 | "*", 31 | [ 32 | "dot", 33 | [ 34 | "name", 35 | "defaultProperty" 36 | ], 37 | "foo" 38 | ], 39 | [ 40 | "num", 41 | 2 42 | ] 43 | ] 44 | ], 45 | "defaultProperty.foo * 2\n" 46 | ] 47 | ] 48 | ] 49 | ] 50 | -------------------------------------------------------------------------------- /tests/qml/Drag.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Rectangle { 4 | width: 100 5 | height: 100 6 | 7 | Drag.hotSpot.x: width / 2 8 | Drag.hotSpot.y: height / 2 9 | } 10 | -------------------------------------------------------------------------------- /tests/qml/Drag.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2, 8 | "", 9 | true 10 | ] 11 | ], 12 | [ 13 | "qmlelem", 14 | "Rectangle", 15 | null, 16 | [ 17 | [ 18 | "qmlprop", 19 | "width", 20 | [ 21 | "stat", 22 | [ 23 | "num", 24 | 100 25 | ] 26 | ], 27 | "100\n " 28 | ], 29 | [ 30 | "qmlprop", 31 | "height", 32 | [ 33 | "stat", 34 | [ 35 | "num", 36 | 100 37 | ] 38 | ], 39 | "100\n\n " 40 | ], 41 | [ 42 | "qmlprop", 43 | [ 44 | "dot", 45 | [ 46 | "dot", 47 | "Drag", 48 | "hotSpot" 49 | ], 50 | "x" 51 | ], 52 | [ 53 | "stat", 54 | [ 55 | "binary", 56 | "/", 57 | [ 58 | "name", 59 | "width" 60 | ], 61 | [ 62 | "num", 63 | 2 64 | ] 65 | ] 66 | ], 67 | "width / 2\n " 68 | ], 69 | [ 70 | "qmlprop", 71 | [ 72 | "dot", 73 | [ 74 | "dot", 75 | "Drag", 76 | "hotSpot" 77 | ], 78 | "y" 79 | ], 80 | [ 81 | "stat", 82 | [ 83 | "binary", 84 | "/", 85 | [ 86 | "name", 87 | "height" 88 | ], 89 | [ 90 | "num", 91 | 2 92 | ] 93 | ] 94 | ], 95 | "height / 2\n" 96 | ] 97 | ] 98 | ] 99 | ] 100 | -------------------------------------------------------------------------------- /tests/qml/Functions.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | 3 | Item { 4 | width: 75 5 | height: 15 6 | 7 | function foo() { 8 | width = 80 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/qml/Functions.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2.5, 8 | "", 9 | true 10 | ] 11 | ], 12 | [ 13 | "qmlelem", 14 | "Item", 15 | null, 16 | [ 17 | [ 18 | "qmlprop", 19 | "width", 20 | [ 21 | "stat", 22 | [ 23 | "num", 24 | 75 25 | ] 26 | ], 27 | "75\n " 28 | ], 29 | [ 30 | "qmlprop", 31 | "height", 32 | [ 33 | "stat", 34 | [ 35 | "num", 36 | 15 37 | ] 38 | ], 39 | "15\n\n " 40 | ], 41 | [ 42 | "qmlmethod", 43 | "foo", 44 | [ 45 | "defun", 46 | "foo", 47 | [], 48 | [ 49 | [ 50 | "stat", 51 | [ 52 | "assign", 53 | true, 54 | [ 55 | "name", 56 | "width" 57 | ], 58 | [ 59 | "num", 60 | 80 61 | ] 62 | ] 63 | ] 64 | ] 65 | ], 66 | "function foo() {\n width = 80\n }\n" 67 | ] 68 | ] 69 | ] 70 | ] -------------------------------------------------------------------------------- /tests/qml/Imports.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QmlWeb.DOM 1.0 3 | import "." 4 | import "../directory" 5 | 6 | Item { 7 | } 8 | -------------------------------------------------------------------------------- /tests/qml/Imports.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2, 8 | "", 9 | true 10 | ], 11 | [ 12 | "qmlimport", 13 | "QmlWeb.DOM", 14 | 1, 15 | "", 16 | true 17 | ], 18 | [ 19 | "qmlimport", 20 | ".", 21 | null, 22 | "", 23 | false 24 | ], 25 | [ 26 | "qmlimport", 27 | "../directory", 28 | null, 29 | "", 30 | false 31 | ] 32 | ], 33 | [ 34 | "qmlelem", 35 | "Item", 36 | null, 37 | [] 38 | ] 39 | ] -------------------------------------------------------------------------------- /tests/qml/ImportsNamed.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QmlWeb.DOM 1.0 as DOM 3 | import "./library.js" as Library 4 | 5 | DOM.Div { 6 | } 7 | -------------------------------------------------------------------------------- /tests/qml/ImportsNamed.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2, 8 | "", 9 | true 10 | ], 11 | [ 12 | "qmlimport", 13 | "QmlWeb.DOM", 14 | 1, 15 | "DOM", 16 | true 17 | ], 18 | [ 19 | "qmlimport", 20 | "./library.js", 21 | null, 22 | "Library", 23 | false 24 | ] 25 | ], 26 | [ 27 | "qmlelem", 28 | [ 29 | "dot", 30 | "DOM", 31 | "Div" 32 | ], 33 | null, 34 | [] 35 | ] 36 | ] 37 | -------------------------------------------------------------------------------- /tests/qml/MultilineString.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | property string foo: "foo\"" 5 | property string bar: "foo 6 | hello 7 | world 8 | " 9 | property string test: ' 10 | \' 11 | ' 12 | } 13 | -------------------------------------------------------------------------------- /tests/qml/MultilineString.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2, 8 | "", 9 | true 10 | ] 11 | ], 12 | [ 13 | "qmlelem", 14 | "Item", 15 | null, 16 | [ 17 | [ 18 | "qmlpropdef", 19 | "foo", 20 | false, 21 | "string", 22 | [ 23 | "stat", 24 | [ 25 | "string", 26 | "foo\"" 27 | ] 28 | ], 29 | "\"foo\\\"\"\n " 30 | ], 31 | [ 32 | "qmlpropdef", 33 | "bar", 34 | false, 35 | "string", 36 | [ 37 | "stat", 38 | [ 39 | "string", 40 | "foo\n hello\n world\n " 41 | ] 42 | ], 43 | "\"foo\n hello\n world\n \"\n " 44 | ], 45 | [ 46 | "qmlpropdef", 47 | "test", 48 | false, 49 | "string", 50 | [ 51 | "stat", 52 | [ 53 | "string", 54 | "\n '\n " 55 | ] 56 | ], 57 | "'\n \\'\n '\n" 58 | ] 59 | ] 60 | ] 61 | ] 62 | -------------------------------------------------------------------------------- /tests/qml/ParseFunctionVar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | MyItem { 4 | property var aFunction: function(){} 5 | property var bFunction: function(h) { 6 | return 21; 7 | } 8 | cFunction: function (a,b, c) { 9 | a /= 2; 10 | return a - b + c; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/qml/ParseFunctionVar.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2, 8 | "", 9 | true 10 | ] 11 | ], 12 | [ 13 | "qmlelem", 14 | "MyItem", 15 | null, 16 | [ 17 | [ 18 | "qmlpropdef", 19 | "aFunction", 20 | false, 21 | "var", 22 | [ 23 | "function", 24 | null, 25 | [], 26 | [] 27 | ], 28 | "function(){}\n " 29 | ], 30 | [ 31 | "qmlpropdef", 32 | "bFunction", 33 | false, 34 | "var", 35 | [ 36 | "function", 37 | null, 38 | [ 39 | "h" 40 | ], 41 | [ 42 | [ 43 | "return", 44 | [ 45 | "num", 46 | 21 47 | ] 48 | ] 49 | ] 50 | ], 51 | "function(h) {\n return 21;\n }\n " 52 | ], 53 | [ 54 | "qmlprop", 55 | "cFunction", 56 | [ 57 | "function", 58 | null, 59 | [ 60 | "a", 61 | "b", 62 | "c" 63 | ], 64 | [ 65 | [ 66 | "stat", 67 | [ 68 | "assign", 69 | "/", 70 | [ 71 | "name", 72 | "a" 73 | ], 74 | [ 75 | "num", 76 | 2 77 | ] 78 | ] 79 | ], 80 | [ 81 | "return", 82 | [ 83 | "binary", 84 | "+", 85 | [ 86 | "binary", 87 | "-", 88 | [ 89 | "name", 90 | "a" 91 | ], 92 | [ 93 | "name", 94 | "b" 95 | ] 96 | ], 97 | [ 98 | "name", 99 | "c" 100 | ] 101 | ] 102 | ] 103 | ] 104 | ], 105 | "function (a,b, c) {\n a /= 2;\n return a - b + c;\n }\n" 106 | ] 107 | ] 108 | ] 109 | ] 110 | -------------------------------------------------------------------------------- /tests/qml/Pragma.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | 4 | Item { 5 | } 6 | -------------------------------------------------------------------------------- /tests/qml/Pragma.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2, 8 | "", 9 | true 10 | ] 11 | ], 12 | [ 13 | "qmlelem", 14 | "Item", 15 | null, 16 | [] 17 | ], 18 | [ 19 | [ 20 | "qmlpragma", 21 | "Singleton" 22 | ] 23 | ] 24 | ] -------------------------------------------------------------------------------- /tests/qml/Properties.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | import QmlWeb.DOM 1.0 as DOM 3 | 4 | Rectangle { 5 | width: 75 6 | height: 15 7 | color: 'green' 8 | border.width: 2 9 | property: 'test' 10 | 11 | property bool flag 12 | property int integer; 13 | property int number: 10 14 | property int number2: 10; 15 | property var foo: {} 16 | property var bar: [] 17 | property Item item: Item {} 18 | property list items 19 | property var domDiv: DOM.Div {} 20 | } 21 | -------------------------------------------------------------------------------- /tests/qml/Properties.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2.5, 8 | "", 9 | true 10 | ], 11 | [ 12 | "qmlimport", 13 | "QmlWeb.DOM", 14 | 1, 15 | "DOM", 16 | true 17 | ] 18 | ], 19 | [ 20 | "qmlelem", 21 | "Rectangle", 22 | null, 23 | [ 24 | [ 25 | "qmlprop", 26 | "width", 27 | [ 28 | "stat", 29 | [ 30 | "num", 31 | 75 32 | ] 33 | ], 34 | "75\n " 35 | ], 36 | [ 37 | "qmlprop", 38 | "height", 39 | [ 40 | "stat", 41 | [ 42 | "num", 43 | 15 44 | ] 45 | ], 46 | "15\n " 47 | ], 48 | [ 49 | "qmlprop", 50 | "color", 51 | [ 52 | "stat", 53 | [ 54 | "string", 55 | "green" 56 | ] 57 | ], 58 | "'green'\n " 59 | ], 60 | [ 61 | "qmlprop", 62 | [ 63 | "dot", 64 | "border", 65 | "width" 66 | ], 67 | [ 68 | "stat", 69 | [ 70 | "num", 71 | 2 72 | ] 73 | ], 74 | "2\n " 75 | ], 76 | [ 77 | "qmlprop", 78 | "property", 79 | [ 80 | "stat", 81 | [ 82 | "string", 83 | "test" 84 | ] 85 | ], 86 | "'test'\n\n " 87 | ], 88 | [ 89 | "qmlpropdef", 90 | "flag", 91 | false, 92 | "bool" 93 | ], 94 | [ 95 | "qmlpropdef", 96 | "integer", 97 | false, 98 | "int" 99 | ], 100 | [ 101 | "qmlpropdef", 102 | "number", 103 | false, 104 | "int", 105 | [ 106 | "stat", 107 | [ 108 | "num", 109 | 10 110 | ] 111 | ], 112 | "10\n " 113 | ], 114 | [ 115 | "qmlpropdef", 116 | "number2", 117 | false, 118 | "int", 119 | [ 120 | "stat", 121 | [ 122 | "num", 123 | 10 124 | ] 125 | ], 126 | "10;\n " 127 | ], 128 | [ 129 | "qmlpropdef", 130 | "foo", 131 | false, 132 | "var", 133 | [ 134 | "block", 135 | [] 136 | ], 137 | "{}\n " 138 | ], 139 | [ 140 | "qmlpropdef", 141 | "bar", 142 | false, 143 | "var", 144 | [ 145 | "stat", 146 | [ 147 | "array", 148 | [], 149 | "[]\n " 150 | ] 151 | ], 152 | "[]\n " 153 | ], 154 | [ 155 | "qmlpropdef", 156 | "item", 157 | false, 158 | "Item", 159 | [ 160 | "stat", 161 | [ 162 | "qmlelem", 163 | "Item", 164 | null, 165 | [] 166 | ] 167 | ], 168 | "Item {}\n " 169 | ], 170 | [ 171 | "qmlpropdef", 172 | "items", 173 | false, 174 | "list" 175 | ], 176 | [ 177 | "qmlpropdef", 178 | "domDiv", 179 | false, 180 | "var", 181 | [ 182 | "stat", 183 | [ 184 | "qmlelem", 185 | [ 186 | "dot", 187 | "DOM", 188 | "Div" 189 | ], 190 | null, 191 | [] 192 | ] 193 | ], 194 | "DOM.Div {}\n" 195 | ] 196 | ] 197 | ] 198 | ] 199 | -------------------------------------------------------------------------------- /tests/qml/PropertiesGrouped.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | Rectangle { 5 | color: 'red' 6 | anchors { 7 | left: parent.left 8 | bottom: parent.bottom 9 | right: parent.right; top: parent.top 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/qml/PropertiesGrouped.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2, 8 | "", 9 | true 10 | ] 11 | ], 12 | [ 13 | "qmlelem", 14 | "Item", 15 | null, 16 | [ 17 | [ 18 | "qmlelem", 19 | "Rectangle", 20 | null, 21 | [ 22 | [ 23 | "qmlprop", 24 | "color", 25 | [ 26 | "stat", 27 | [ 28 | "string", 29 | "red" 30 | ] 31 | ], 32 | "'red'\n " 33 | ], 34 | [ 35 | "qmlobj", 36 | "anchors", 37 | [ 38 | [ 39 | "qmlprop", 40 | "left", 41 | [ 42 | "stat", 43 | [ 44 | "dot", 45 | [ 46 | "name", 47 | "parent" 48 | ], 49 | "left" 50 | ] 51 | ], 52 | "parent.left\n " 53 | ], 54 | [ 55 | "qmlprop", 56 | "bottom", 57 | [ 58 | "stat", 59 | [ 60 | "dot", 61 | [ 62 | "name", 63 | "parent" 64 | ], 65 | "bottom" 66 | ] 67 | ], 68 | "parent.bottom\n " 69 | ], 70 | [ 71 | "qmlprop", 72 | "right", 73 | [ 74 | "stat", 75 | [ 76 | "dot", 77 | [ 78 | "name", 79 | "parent" 80 | ], 81 | "right" 82 | ] 83 | ], 84 | "parent.right; " 85 | ], 86 | [ 87 | "qmlprop", 88 | "top", 89 | [ 90 | "stat", 91 | [ 92 | "dot", 93 | [ 94 | "name", 95 | "parent" 96 | ], 97 | "top" 98 | ] 99 | ], 100 | "parent.top\n " 101 | ] 102 | ] 103 | ] 104 | ] 105 | ] 106 | ] 107 | ] 108 | ] -------------------------------------------------------------------------------- /tests/qml/PropertyNamedSignal.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | 3 | ItemWithPropertyNamedSignal { 4 | signal: 20 5 | } 6 | -------------------------------------------------------------------------------- /tests/qml/PropertyNamedSignal.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2.5, 8 | "", 9 | true 10 | ] 11 | ], 12 | [ 13 | "qmlelem", 14 | "ItemWithPropertyNamedSignal", 15 | null, 16 | [ 17 | [ 18 | "qmlprop", 19 | "signal", 20 | [ 21 | "stat", 22 | [ 23 | "num", 24 | 20 25 | ] 26 | ], 27 | "20\n" 28 | ] 29 | ] 30 | ] 31 | ] 32 | -------------------------------------------------------------------------------- /tests/qml/Signal.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | signal simpleSignal; 5 | signal signalWithParams(string someParam) 6 | signal anotherSignal(string someParam, int param2); 7 | signal signalWithParamVar(var param) 8 | } 9 | -------------------------------------------------------------------------------- /tests/qml/Signal.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2, 8 | "", 9 | true 10 | ] 11 | ], 12 | [ 13 | "qmlelem", 14 | "Item", 15 | null, 16 | [ 17 | [ 18 | "qmlsignaldef", 19 | "simpleSignal", 20 | [] 21 | ], 22 | [ 23 | "qmlsignaldef", 24 | "signalWithParams", 25 | [ 26 | { 27 | "type": "string", 28 | "name": "someParam" 29 | } 30 | ] 31 | ], 32 | [ 33 | "qmlsignaldef", 34 | "anotherSignal", 35 | [ 36 | { 37 | "type": "string", 38 | "name": "someParam" 39 | }, 40 | { 41 | "type": "int", 42 | "name": "param2" 43 | } 44 | ] 45 | ], 46 | [ 47 | "qmlsignaldef", 48 | "signalWithParamVar", 49 | [ 50 | { 51 | "type": "var", 52 | "name": "param" 53 | } 54 | ] 55 | ] 56 | ] 57 | ] 58 | ] -------------------------------------------------------------------------------- /tests/qml/SignalSlot.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | signal simpleSignal; 5 | signal signalWithParams(string someParam) 6 | 7 | onSimpleSignal: { 8 | console.log('foo'); 9 | } 10 | 11 | onSignalWithParams: { 12 | console.log('foo' + someParam); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/qml/SignalSlot.qml.json: -------------------------------------------------------------------------------- 1 | [ 2 | "toplevel", 3 | [ 4 | [ 5 | "qmlimport", 6 | "QtQuick", 7 | 2, 8 | "", 9 | true 10 | ] 11 | ], 12 | [ 13 | "qmlelem", 14 | "Item", 15 | null, 16 | [ 17 | [ 18 | "qmlsignaldef", 19 | "simpleSignal", 20 | [] 21 | ], 22 | [ 23 | "qmlsignaldef", 24 | "signalWithParams", 25 | [ 26 | { 27 | "type": "string", 28 | "name": "someParam" 29 | } 30 | ] 31 | ], 32 | [ 33 | "qmlprop", 34 | "onSimpleSignal", 35 | [ 36 | "block", 37 | [ 38 | [ 39 | "stat", 40 | [ 41 | "call", 42 | [ 43 | "dot", 44 | [ 45 | "name", 46 | "console" 47 | ], 48 | "log" 49 | ], 50 | [ 51 | [ 52 | "string", 53 | "foo" 54 | ] 55 | ] 56 | ] 57 | ] 58 | ] 59 | ], 60 | "{\n console.log('foo');\n }\n\n " 61 | ], 62 | [ 63 | "qmlprop", 64 | "onSignalWithParams", 65 | [ 66 | "block", 67 | [ 68 | [ 69 | "stat", 70 | [ 71 | "call", 72 | [ 73 | "dot", 74 | [ 75 | "name", 76 | "console" 77 | ], 78 | "log" 79 | ], 80 | [ 81 | [ 82 | "binary", 83 | "+", 84 | [ 85 | "string", 86 | "foo" 87 | ], 88 | [ 89 | "name", 90 | "someParam" 91 | ] 92 | ] 93 | ] 94 | ] 95 | ] 96 | ] 97 | ], 98 | "{\n console.log('foo' + someParam);\n }\n" 99 | ] 100 | ] 101 | ] 102 | ] -------------------------------------------------------------------------------- /tests/qml/invalid/AfterEof.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | height: 200 5 | } 6 | 7 | Item { 8 | } 9 | 10 | -------------------------------------------------------------------------------- /tests/qml/invalid/AfterEof.qml.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "name": "Error", 4 | "message": "Unexpected token: name (Item) (line: 7, col: 0, pos: 44)\n 4 height: 200\n 5 }\n 6 \n>>7 Item {\n 8 }\n 9 \n 10 \n\n" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/qml/invalid/InvalidAlias.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | property alias x: 200 5 | } 6 | -------------------------------------------------------------------------------- /tests/qml/invalid/InvalidAlias.qml.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "name": "Error", 4 | "message": "Unexpected token: num (200) (line: 4, col: 20, pos: 47)\n 1 import QtQuick 2.0\n 2 \n 3 Item {\n>>4 property alias x: 200\n 5 }\n 6 \n\n" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/qml/invalid/InvalidAlias2.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | property alias buttonText: asd. 5 | } 6 | -------------------------------------------------------------------------------- /tests/qml/invalid/InvalidAlias2.qml.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "name": "Error", 4 | "message": "Unexpected token: punc (}) (line: 5, col: 0, pos: 61)\n 2 \n 3 Item {\n 4 property alias buttonText: asd.\n>>5 }\n 6 \n\n" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/qml/invalid/InvalidSignal.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | signal simpleSignal(string abc, bool) 5 | } 6 | -------------------------------------------------------------------------------- /tests/qml/invalid/InvalidSignal.qml.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "name": "Error", 4 | "message": "Unexpected token: punc ()) (line: 4, col: 38, pos: 65)\n 1 import QtQuick 2.0\n 2 \n 3 Item {\n>>4 signal simpleSignal(string abc, bool)\n 5 }\n 6 \n\n" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/qml/invalid/InvalidSignal2.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | signal simpleSignal(20, 30) 5 | } 6 | -------------------------------------------------------------------------------- /tests/qml/invalid/InvalidSignal2.qml.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "name": "Error", 4 | "message": "Unexpected token: num (20) (line: 4, col: 22, pos: 49)\n 1 import QtQuick 2.0\n 2 \n 3 Item {\n>>4 signal simpleSignal(20, 30)\n 5 }\n 6 \n\n" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/qml/invalid/ParseError.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | 3 | Item { 4 | properly int error: 11 5 | } 6 | -------------------------------------------------------------------------------- /tests/qml/invalid/ParseError.qml.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "name": "Error", 4 | "message": "Unexpected token name int, expected punc : (line: 4, col: 11, pos: 38)\n 1 import QtQuick 2.5\n 2 \n 3 Item {\n>>4 properly int error: 11\n 5 }\n 6 \n\n" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/qml/invalid/SuddenEof.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | height: 200 5 | 6 | -------------------------------------------------------------------------------- /tests/qml/invalid/SuddenEof.qml.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "name": "Error", 4 | "message": "Unexpected token: eof (undefined) (line: 6, col: 0, pos: 42)\n 3 Item {\n 4 height: 200\n 5 \n>>6 \n\n" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/tape.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | 7 | const parser_path = process.env.QMLWEB_PARSER_PATH || 'lib/qmlweb.parser'; 8 | const parser = require('../' + parser_path); 9 | 10 | const saveMode = !!process.env.QMLWEB_TESTS_SAVE_MODE; 11 | 12 | function buildTree(dir, data) { 13 | data = data || {}; 14 | const subdirs = []; 15 | for (const file of fs.readdirSync(dir)) { 16 | if (file[0] === ".") { 17 | continue; 18 | } 19 | const filePath = path.join(dir, file); 20 | if (fs.lstatSync(filePath).isDirectory()) { 21 | subdirs.push(filePath); 22 | } else { 23 | if (!data[dir]) { 24 | data[dir] = []; 25 | } 26 | data[dir].push(file); 27 | } 28 | } 29 | for (const subdir of subdirs) { 30 | buildTree(subdir, data); 31 | } 32 | return data; 33 | } 34 | 35 | const tree = buildTree('tests'); 36 | for (const dir in tree) { 37 | if (dir === 'tests') continue; 38 | if (dir.indexOf('failing') !== -1) continue; 39 | 40 | const files = tree[dir]; 41 | 42 | test(dir.replace(/tests./, ''), function(t) { 43 | t.plan( 44 | files.filter(function(x) { 45 | return !/\.json$/.test(x); 46 | }).length + 47 | files.filter(function(x) { 48 | return /\.js$/.test(x); 49 | }).length 50 | ); 51 | 52 | for (const file of files) { 53 | const extension = file.replace(/.*\./, ''); 54 | const map = {}; 55 | 56 | switch (extension) { 57 | case 'json': 58 | continue; 59 | case 'qml': 60 | map['.json'] = function(source) { 61 | return parser.qmlweb_parse(source, parser.qmlweb_parse.QMLDocument); 62 | }; 63 | break; 64 | case 'js': 65 | map['.json'] = function(source) { 66 | return parser.qmlweb_parse(source, parser.qmlweb_parse.JSResource); 67 | }; 68 | map['.exports.json'] = parser.qmlweb_jsparse; 69 | break; 70 | default: 71 | throw new Error('Unexpected file extension: ' + extension); 72 | } 73 | 74 | const filePath = path.join(dir, file); 75 | const source = fs.readFileSync(filePath, 'utf-8'); 76 | 77 | Object.keys(map).forEach(function(suffix) { 78 | const func = map[suffix]; 79 | 80 | let actual, expected; 81 | try { 82 | actual = func(source); 83 | } catch (e) { 84 | actual = { error: { name: e.name, message: e.message } }; 85 | } 86 | 87 | // Normalize. NOTE: this translates undefined to null 88 | actual = JSON.parse(JSON.stringify(actual)); 89 | 90 | if (saveMode && files.indexOf(file + suffix) === -1) { 91 | const json = JSON.stringify(actual, undefined, 2); 92 | fs.writeFileSync(filePath + suffix, json); 93 | } 94 | 95 | const content = fs.readFileSync(filePath + suffix, 'utf-8'); 96 | expected = JSON.parse(content); 97 | 98 | t.deepEqual(actual, expected, file.replace(/.*[\\\/]/, '')); 99 | }); 100 | } 101 | }); 102 | } 103 | --------------------------------------------------------------------------------