rapydscript-ng 0.7.23 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +72 -26
- package/bin/package.json +1 -0
- package/bin/rapydscript +65 -62
- package/editor-plugins/nvim/rapydscript/README.md +184 -0
- package/editor-plugins/nvim/rapydscript/bin/rapydscript.so +0 -0
- package/editor-plugins/nvim/rapydscript/ftdetect/rapydscript.lua +10 -0
- package/editor-plugins/nvim/rapydscript/lua/rapydscript/health.lua +88 -0
- package/editor-plugins/nvim/rapydscript/lua/rapydscript/init.lua +279 -0
- package/local-agent.md +28 -0
- package/package.json +4 -5
- package/release/baselib-plain-pretty.js +448 -326
- package/release/baselib-plain-ugly.js +3 -3
- package/release/compiler.js +2528 -1672
- package/release/signatures.json +17 -17
- package/src/ast.pyj +73 -25
- package/src/baselib-builtins.pyj +2 -2
- package/src/baselib-containers.pyj +153 -151
- package/src/baselib-internal.pyj +3 -3
- package/src/baselib-str.pyj +5 -5
- package/src/lib/aes.pyj +1 -1
- package/src/lib/encodings.pyj +1 -1
- package/src/lib/pythonize.pyj +1 -1
- package/src/lib/re.pyj +2 -2
- package/src/lib/traceback.pyj +165 -28
- package/src/lib/uuid.pyj +1 -1
- package/src/output/codegen.pyj +10 -3
- package/src/output/functions.pyj +31 -40
- package/src/output/literals.pyj +11 -14
- package/src/output/loops.pyj +25 -87
- package/src/output/modules.pyj +5 -47
- package/src/output/statements.pyj +1 -1
- package/src/output/stream.pyj +58 -31
- package/src/parse.pyj +777 -427
- package/src/tokenizer.pyj +16 -4
- package/src/utils.pyj +1 -1
- package/test/_treeshake_mod.pyj +30 -0
- package/test/_treeshake_side_effect.pyj +9 -0
- package/test/ast_serialization.pyj +392 -0
- package/test/async.pyj +95 -0
- package/test/baselib.pyj +1 -2
- package/test/embedded_compiler.pyj +57 -0
- package/test/fmt.pyj +201 -0
- package/test/generic.pyj +7 -3
- package/test/imports.pyj +8 -6
- package/test/internationalization.pyj +38 -37
- package/test/lint.pyj +120 -115
- package/test/lsp.pyj +222 -0
- package/test/repl.pyj +70 -65
- package/test/sourcemaps.pyj +315 -0
- package/test/starargs.pyj +46 -39
- package/test/traceback.pyj +263 -0
- package/test/treeshaking.pyj +181 -0
- package/test/web_repl_export.pyj +88 -0
- package/tools/ast_serialize.mjs +557 -0
- package/tools/cli.mjs +510 -0
- package/tools/compile.mjs +201 -0
- package/tools/compiler.mjs +127 -0
- package/tools/{completer.js → completer.mjs} +6 -6
- package/tools/{embedded_compiler.js → embedded_compiler.mjs} +17 -13
- package/tools/export.js +109 -56
- package/tools/fmt.mjs +735 -0
- package/tools/{gettext.js → gettext.mjs} +46 -53
- package/tools/{ini.js → ini.mjs} +9 -10
- package/tools/{lint.js → lint.mjs} +107 -56
- package/tools/lsp.mjs +914 -0
- package/tools/lsp_protocol.mjs +259 -0
- package/tools/lsp_symbols.mjs +418 -0
- package/tools/{msgfmt.js → msgfmt.mjs} +18 -18
- package/tools/{repl.js → repl.mjs} +52 -41
- package/tools/{self.js → self.mjs} +56 -46
- package/tools/sourcemap.mjs +123 -0
- package/tools/test.mjs +152 -0
- package/tools/treeshake.mjs +400 -0
- package/tools/{utils.js → utils.mjs} +26 -23
- package/tools/{web_repl.js → web_repl.mjs} +15 -13
- package/tools/web_repl_export.mjs +93 -0
- package/tree-sitter/README.md +101 -0
- package/tree-sitter/grammar.js +992 -0
- package/tree-sitter/package.json +43 -0
- package/tree-sitter/queries/rapydscript/highlights.scm +232 -0
- package/tree-sitter/queries/rapydscript/indents.scm +64 -0
- package/tree-sitter/queries/rapydscript/injections.scm +14 -0
- package/tree-sitter/queries/rapydscript/locals.scm +108 -0
- package/tree-sitter/src/grammar.json +4976 -0
- package/tree-sitter/src/node-types.json +2901 -0
- package/tree-sitter/src/parser.c +196465 -0
- package/tree-sitter/src/scanner.c +294 -0
- package/tree-sitter/src/tree_sitter/alloc.h +54 -0
- package/tree-sitter/src/tree_sitter/array.h +330 -0
- package/tree-sitter/src/tree_sitter/parser.h +286 -0
- package/tree-sitter/test/corpus/chaining.txt +99 -0
- package/tree-sitter/test/corpus/classes.txt +147 -0
- package/tree-sitter/test/corpus/comprehensions.txt +155 -0
- package/tree-sitter/test/corpus/containers.txt +242 -0
- package/tree-sitter/test/corpus/control_flow.txt +361 -0
- package/tree-sitter/test/corpus/decorators.txt +64 -0
- package/tree-sitter/test/corpus/existential.txt +102 -0
- package/tree-sitter/test/corpus/functions.txt +293 -0
- package/tree-sitter/test/corpus/imports.txt +117 -0
- package/tree-sitter/test/corpus/literals.txt +135 -0
- package/tree-sitter/test/corpus/operators.txt +296 -0
- package/tree-sitter/test/corpus/statements.txt +189 -0
- package/tree-sitter/test/corpus/strings.txt +90 -0
- package/tree-sitter/test/corpus/subscripts.txt +227 -0
- package/tree-sitter/tree-sitter.json +36 -0
- package/web-repl/env.js +35 -23
- package/web-repl/main.js +8 -8
- package/bin/export +0 -75
- package/bin/web-repl-export +0 -102
- package/tools/cli.js +0 -523
- package/tools/compile.js +0 -184
- package/tools/compiler.js +0 -84
- package/tools/test.js +0 -110
package/tools/test.mjs
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test.js
|
|
3
|
+
* Copyright (C) 2015 Kovid Goyal <kovid at kovidgoyal.net>
|
|
4
|
+
*
|
|
5
|
+
* Distributed under terms of the BSD license.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import assert from 'assert';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
import vm from 'vm';
|
|
13
|
+
import { createRequire } from 'module';
|
|
14
|
+
import { create_compiler } from './compiler.mjs';
|
|
15
|
+
import embedded_compiler_factory from './embedded_compiler.mjs';
|
|
16
|
+
import * as utils from './utils.mjs';
|
|
17
|
+
import { gettext as rs_gettext, entry_to_string as rs_entry_to_string } from './gettext.mjs';
|
|
18
|
+
import { parse as rs_msgfmt_parse, build as rs_msgfmt_build } from './msgfmt.mjs';
|
|
19
|
+
import rs_repl_fn from './repl.mjs';
|
|
20
|
+
import { generate_source_map as rs_generate_source_map } from './sourcemap.mjs';
|
|
21
|
+
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
const RapydScript = await create_compiler();
|
|
24
|
+
var colored = utils.safe_colored;
|
|
25
|
+
|
|
26
|
+
export default async function(argv, base_path, src_path, lib_path) {
|
|
27
|
+
// run all tests and exit
|
|
28
|
+
var failures = [];
|
|
29
|
+
var compiler_dir = path.join(base_path, 'dev');
|
|
30
|
+
if (!await utils.path_exists(path.join(compiler_dir, 'compiler.js'))) compiler_dir = path.join(base_path, 'release');
|
|
31
|
+
var test_dir = path.join(base_path, 'test');
|
|
32
|
+
var baselib = await fs.promises.readFile(path.join(lib_path, 'baselib-plain-pretty.js'), 'utf-8');
|
|
33
|
+
var files;
|
|
34
|
+
var deep_eq = assert.deepEqual;
|
|
35
|
+
assert.deepEqual = function(a, b, message) {
|
|
36
|
+
// Compare array objects that have extra properties as simple arrays
|
|
37
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
38
|
+
if (a === b) return;
|
|
39
|
+
if (a.length !== b.length) throw new assert.AssertionError({actual:a, expected:b, operator:'deepEqual', stackStartFunction:assert.deepEqual});
|
|
40
|
+
for(var i=0; i < a.length; i++) assert.deepEqual(a[i], b[i], message);
|
|
41
|
+
} else if (a !== undefined && a !== null && typeof a.__eq__ === 'function') {
|
|
42
|
+
if (!a.__eq__(b)) throw new assert.AssertionError({actual:a, expected:b, operator:'deepEqual', stackStartFunction:assert.deepEqual});
|
|
43
|
+
} else return deep_eq(a, b, message);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (argv.files.length) {
|
|
47
|
+
files = argv.files.map(fname => fname + '.pyj');
|
|
48
|
+
} else {
|
|
49
|
+
files = (await fs.promises.readdir(test_dir)).filter(function(name){
|
|
50
|
+
return /^[^_].*\.pyj$/.test(name);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const file of files) {
|
|
55
|
+
let filepath = path.join(test_dir, file);
|
|
56
|
+
let failed = false;
|
|
57
|
+
let src;
|
|
58
|
+
let ast;
|
|
59
|
+
try {
|
|
60
|
+
src = await fs.promises.readFile(filepath, "utf-8");
|
|
61
|
+
ast = await RapydScript.parse(src, {
|
|
62
|
+
filename: file,
|
|
63
|
+
toplevel: ast,
|
|
64
|
+
basedir: test_dir,
|
|
65
|
+
libdir: path.join(src_path, 'lib'),
|
|
66
|
+
});
|
|
67
|
+
} catch(e) {
|
|
68
|
+
failures.push(file);
|
|
69
|
+
failed = true;
|
|
70
|
+
console.log(colored(file, 'red') + ': ' + e + "\n\n");
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// generate output
|
|
75
|
+
var output = new RapydScript.OutputStream({
|
|
76
|
+
baselib_plain: baselib,
|
|
77
|
+
beautify: true,
|
|
78
|
+
js_version: 6,
|
|
79
|
+
keep_docstrings: true,
|
|
80
|
+
});
|
|
81
|
+
ast.print(output);
|
|
82
|
+
|
|
83
|
+
// test that output performs correct JS operations
|
|
84
|
+
var jsfile = path.join(os.tmpdir(), file + '-es6.js');
|
|
85
|
+
var code = output.toString();
|
|
86
|
+
try {
|
|
87
|
+
const unhandled = [];
|
|
88
|
+
const on_rejection = (reason) => unhandled.push(reason);
|
|
89
|
+
process.on('unhandledRejection', on_rejection);
|
|
90
|
+
try {
|
|
91
|
+
// __test_async_done__ is a sandbox global that async test files can assign
|
|
92
|
+
// their top-level Promise to (e.g. `__test_async_done__ = run_tests()`).
|
|
93
|
+
// Assigning to an existing global inside a strict-mode IIFE is legal, so
|
|
94
|
+
// this lets us properly await tests whose async Promise is otherwise buried
|
|
95
|
+
// inside a synchronous wrapper IIFE and never returned from the script.
|
|
96
|
+
const sandbox = {
|
|
97
|
+
'assrt':assert,
|
|
98
|
+
'__name__': jsfile,
|
|
99
|
+
'require':require,
|
|
100
|
+
'fs':fs,
|
|
101
|
+
'RapydScript':RapydScript,
|
|
102
|
+
'console':console,
|
|
103
|
+
'compiler_dir': compiler_dir,
|
|
104
|
+
'test_path':test_dir,
|
|
105
|
+
'Buffer': Buffer,
|
|
106
|
+
'rs_gettext': rs_gettext,
|
|
107
|
+
'rs_entry_to_string': rs_entry_to_string,
|
|
108
|
+
'rs_msgfmt': { parse: rs_msgfmt_parse, build: rs_msgfmt_build },
|
|
109
|
+
'rs_repl': rs_repl_fn,
|
|
110
|
+
'rs_generate_source_map': rs_generate_source_map,
|
|
111
|
+
'rs_create_embedded_compiler': async function(opts) {
|
|
112
|
+
// Mirror what the browser bundle does: compiler + embedded baselib → factory.
|
|
113
|
+
// Tests may pass { virtual_file_system } to override readfile/writefile.
|
|
114
|
+
var compiler = await create_compiler(opts);
|
|
115
|
+
return await embedded_compiler_factory(compiler, baselib, undefined, undefined, undefined, undefined);
|
|
116
|
+
},
|
|
117
|
+
'__test_async_done__': null,
|
|
118
|
+
};
|
|
119
|
+
let result = vm.runInNewContext(code, sandbox, {'filename':jsfile});
|
|
120
|
+
if (result && typeof result.then === 'function') {
|
|
121
|
+
await result;
|
|
122
|
+
}
|
|
123
|
+
// Await any async Promise that the test file registered via __test_async_done__.
|
|
124
|
+
if (sandbox.__test_async_done__ && typeof sandbox.__test_async_done__.then === 'function') {
|
|
125
|
+
await sandbox.__test_async_done__;
|
|
126
|
+
}
|
|
127
|
+
// Drain one event-loop tick so Node.js can fire unhandledRejection
|
|
128
|
+
// for any async work that was not captured by either mechanism above.
|
|
129
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
130
|
+
if (unhandled.length) throw unhandled[0];
|
|
131
|
+
} finally {
|
|
132
|
+
process.removeListener('unhandledRejection', on_rejection);
|
|
133
|
+
}
|
|
134
|
+
} catch (e) {
|
|
135
|
+
failures.push(file);
|
|
136
|
+
failed = true;
|
|
137
|
+
await fs.promises.writeFile(jsfile, code);
|
|
138
|
+
console.error('Failed running: ' + colored(jsfile, 'red'));
|
|
139
|
+
if (e.stack)
|
|
140
|
+
console.error(colored(file, 'red') + ":\n" + e.stack + "\n\n");
|
|
141
|
+
else
|
|
142
|
+
console.error(colored(file, 'red') + ": " + e + "\n\n");
|
|
143
|
+
}
|
|
144
|
+
if (!failed) console.log(colored(file, 'green') + ": test completed successfully\n");
|
|
145
|
+
else { console.log(colored(file, 'red') + ":\ttest failed\n"); }
|
|
146
|
+
}
|
|
147
|
+
if (failures.length) {
|
|
148
|
+
console.log(colored('There were ' + failures.length + ' test failure(s):', 'red'));
|
|
149
|
+
console.log.apply(console, failures);
|
|
150
|
+
} else console.log(colored('All tests passed!', 'green'));
|
|
151
|
+
process.exit((failures.length) ? 1 : 0);
|
|
152
|
+
}
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* treeshake.js - Dead code elimination for RapydScript
|
|
3
|
+
* Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
|
|
4
|
+
*
|
|
5
|
+
* Distributed under terms of the BSD license.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
var has_prop = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty);
|
|
9
|
+
|
|
10
|
+
// Node-type check via constructor name. Works across Node.js VM context
|
|
11
|
+
// boundaries because it relies on string comparison rather than instanceof.
|
|
12
|
+
function node_type(node) {
|
|
13
|
+
return node && node.constructor && node.constructor.name;
|
|
14
|
+
}
|
|
15
|
+
function is_function(node) { return node_type(node) === 'AST_Function'; }
|
|
16
|
+
function is_class(node) { return node_type(node) === 'AST_Class'; }
|
|
17
|
+
function is_imports(node) { return node_type(node) === 'AST_Imports'; }
|
|
18
|
+
function is_symref(node) { return node_type(node) === 'AST_SymbolRef'; }
|
|
19
|
+
|
|
20
|
+
// Returns true when a decorator node is the special @no_prune annotation.
|
|
21
|
+
// @no_prune is a tree-shaking annotation, not a runtime function — it pins the
|
|
22
|
+
// decorated symbol so the shaker never removes it, and is stripped from output.
|
|
23
|
+
function is_no_prune_decorator(dec) {
|
|
24
|
+
return dec && dec.expression && is_symref(dec.expression) &&
|
|
25
|
+
dec.expression.name === 'no_prune';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Returns true when stmt (an AST_Function or AST_Class) carries @no_prune.
|
|
29
|
+
function node_is_pinned(stmt) {
|
|
30
|
+
if (!stmt.decorators || !stmt.decorators.length) return false;
|
|
31
|
+
for (var i = 0; i < stmt.decorators.length; i++) {
|
|
32
|
+
if (is_no_prune_decorator(stmt.decorators[i])) return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Returns true when a module's immediately-executing body contains at least one
|
|
38
|
+
// function call — a conservative signal that the module has externally
|
|
39
|
+
// observable side effects (e.g. patching String.prototype).
|
|
40
|
+
function has_side_effect_calls(mod) {
|
|
41
|
+
if (!mod) return false;
|
|
42
|
+
// Use pre-computed flag if available (avoids body access for lazy modules).
|
|
43
|
+
if (typeof mod._has_side_effects === 'boolean') return mod._has_side_effects;
|
|
44
|
+
if (!mod.body) return false;
|
|
45
|
+
var found = false;
|
|
46
|
+
mod.body.forEach(function(stmt) {
|
|
47
|
+
if (found) return;
|
|
48
|
+
if (is_function(stmt) || is_class(stmt) || is_imports(stmt)) return;
|
|
49
|
+
var walker = new TreeWalker(function(n) {
|
|
50
|
+
if (node_type(n) === 'AST_Call') { found = true; return true; }
|
|
51
|
+
});
|
|
52
|
+
stmt.walk(walker);
|
|
53
|
+
});
|
|
54
|
+
return found;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Minimal TreeWalker compatible with the AST's _walk() protocol.
|
|
58
|
+
// If the callback returns truthy, descend is suppressed.
|
|
59
|
+
function noop() {}
|
|
60
|
+
function TreeWalker(callback) { this.visit = callback; }
|
|
61
|
+
TreeWalker.prototype._visit = function(node, descend) {
|
|
62
|
+
var ret = this.visit(node, descend || noop);
|
|
63
|
+
if (!ret && descend) descend.call(node);
|
|
64
|
+
return ret;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
* tree_shake(toplevel)
|
|
69
|
+
*
|
|
70
|
+
* Performs dead code elimination on the parsed AST.
|
|
71
|
+
*
|
|
72
|
+
* Removes:
|
|
73
|
+
* - Unused top-level function and class definitions from all modules
|
|
74
|
+
* - Import statements for modules that are completely unused
|
|
75
|
+
* - Unused argnames from "from X import a, b" statements
|
|
76
|
+
*
|
|
77
|
+
* A definition is "live" if it is transitively reachable from any
|
|
78
|
+
* immediately-executing (non-def/class/import) top-level statement,
|
|
79
|
+
* starting from __main__ and following import references.
|
|
80
|
+
*
|
|
81
|
+
* Modifies the AST in-place and returns the modified toplevel.
|
|
82
|
+
*
|
|
83
|
+
* Cached modules (is_cached=true, empty body): their pre-built IIFE output
|
|
84
|
+
* cannot be modified internally, but the entire module can still be removed.
|
|
85
|
+
*/
|
|
86
|
+
function tree_shake(toplevel) {
|
|
87
|
+
|
|
88
|
+
// ── 1. Collect all modules ────────────────────────────────────────────
|
|
89
|
+
var all_modules = {};
|
|
90
|
+
all_modules['__main__'] = toplevel;
|
|
91
|
+
Object.keys(toplevel.imports).forEach(function(mid) {
|
|
92
|
+
all_modules[mid] = toplevel.imports[mid];
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// ── 2. Per-module index tables (built lazily when a module is first marked live) ──
|
|
96
|
+
//
|
|
97
|
+
// top_defs[mid] : {name -> ast_node} (top-level defs only)
|
|
98
|
+
// import_bindings[mid] : {local_name -> {source_module, imported_name, is_namespace}}
|
|
99
|
+
//
|
|
100
|
+
// Building lazily means dead modules' bodies are never accessed, enabling
|
|
101
|
+
// lazy AST deserialization: ast_from_json() is skipped for dead modules.
|
|
102
|
+
|
|
103
|
+
var top_defs = {};
|
|
104
|
+
var import_bindings = {};
|
|
105
|
+
// pinned_defs[mid][name] = true when the def carries @no_prune
|
|
106
|
+
var pinned_defs = {};
|
|
107
|
+
|
|
108
|
+
function ensure_module_indexed(mid) {
|
|
109
|
+
if (has_prop(top_defs, mid)) return; // already indexed
|
|
110
|
+
top_defs[mid] = {};
|
|
111
|
+
import_bindings[mid] = {};
|
|
112
|
+
pinned_defs[mid] = {};
|
|
113
|
+
var mod = all_modules[mid];
|
|
114
|
+
if (!mod) return;
|
|
115
|
+
|
|
116
|
+
// Use precomputed index when available (v7+ caches): avoids body access,
|
|
117
|
+
// so ast_from_json is not triggered during the fixpoint iteration.
|
|
118
|
+
var idx = mod._cache_index;
|
|
119
|
+
if (idx) {
|
|
120
|
+
Object.keys(idx.top_defs || {}).forEach(function(name) {
|
|
121
|
+
var di = idx.top_defs[name];
|
|
122
|
+
// Store a plain index entry ({_idx:true, refs:[...]}) so mark_def_live
|
|
123
|
+
// can push refs directly without needing the AST node.
|
|
124
|
+
top_defs[mid][name] = {_idx: true, refs: di.refs || []};
|
|
125
|
+
if (di.pinned) pinned_defs[mid][name] = true;
|
|
126
|
+
});
|
|
127
|
+
Object.keys(idx.import_bindings || {}).forEach(function(local) {
|
|
128
|
+
var b = idx.import_bindings[local];
|
|
129
|
+
import_bindings[mid][local] = {
|
|
130
|
+
source_module: b.source,
|
|
131
|
+
imported_name: b.imported_name,
|
|
132
|
+
is_namespace: b.is_namespace,
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!mod.body) return;
|
|
139
|
+
mod.body.forEach(function(stmt) {
|
|
140
|
+
if ((is_function(stmt) || is_class(stmt)) && stmt.name) {
|
|
141
|
+
top_defs[mid][stmt.name.name] = stmt;
|
|
142
|
+
if (node_is_pinned(stmt)) {
|
|
143
|
+
pinned_defs[mid][stmt.name.name] = true;
|
|
144
|
+
}
|
|
145
|
+
} else if (is_imports(stmt)) {
|
|
146
|
+
stmt.imports.forEach(function(imp) {
|
|
147
|
+
if (imp.argnames && imp.argnames.length) {
|
|
148
|
+
// from X import a [as b], c [as d], …
|
|
149
|
+
imp.argnames.forEach(function(arg) {
|
|
150
|
+
var local = arg.alias ? arg.alias.name : arg.name;
|
|
151
|
+
import_bindings[mid][local] = {
|
|
152
|
+
source_module: imp.key,
|
|
153
|
+
imported_name: arg.name,
|
|
154
|
+
is_namespace: false,
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
} else {
|
|
158
|
+
// import X [as Y] or import X.Y [as Z]
|
|
159
|
+
var local;
|
|
160
|
+
if (imp.alias) {
|
|
161
|
+
local = imp.alias.name;
|
|
162
|
+
} else {
|
|
163
|
+
local = imp.key.split('.')[0];
|
|
164
|
+
}
|
|
165
|
+
import_bindings[mid][local] = {
|
|
166
|
+
source_module: imp.key,
|
|
167
|
+
imported_name: null,
|
|
168
|
+
is_namespace: true,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── 3. Liveness tracking ─────────────────────────────────────────────
|
|
177
|
+
var live_modules = {}; // {mid -> true}
|
|
178
|
+
var live_defs = {}; // {mid -> {def_name -> true}} (initialised lazily)
|
|
179
|
+
var used_bindings = {}; // {mid -> {local_name -> true}} (initialised lazily)
|
|
180
|
+
|
|
181
|
+
var work_queue = [];
|
|
182
|
+
|
|
183
|
+
// Collect all AST_SymbolRef names reachable from `node`.
|
|
184
|
+
// Reuse a single TreeWalker (JS is single-threaded; no re-entrancy risk).
|
|
185
|
+
var _collect_refs_target = null;
|
|
186
|
+
var _collect_refs_walker = new TreeWalker(function(n) {
|
|
187
|
+
if (is_symref(n)) _collect_refs_target[n.name] = true;
|
|
188
|
+
});
|
|
189
|
+
function collect_refs(node, refs) {
|
|
190
|
+
_collect_refs_target = refs;
|
|
191
|
+
node.walk(_collect_refs_walker);
|
|
192
|
+
_collect_refs_target = null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Mark a module as live; seed its immediately-executing code into the queue
|
|
196
|
+
function mark_module_live(mod_id) {
|
|
197
|
+
if (live_modules[mod_id]) return;
|
|
198
|
+
live_modules[mod_id] = true;
|
|
199
|
+
if (!live_defs[mod_id]) live_defs[mod_id] = {};
|
|
200
|
+
if (!used_bindings[mod_id]) used_bindings[mod_id] = {};
|
|
201
|
+
ensure_module_indexed(mod_id);
|
|
202
|
+
var mod = all_modules[mod_id];
|
|
203
|
+
if (!mod) return;
|
|
204
|
+
// @no_prune defs are unconditionally live whenever their module is live
|
|
205
|
+
Object.keys(pinned_defs[mod_id]).forEach(function(name) {
|
|
206
|
+
mark_def_live(mod_id, name);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Fast path: use precomputed index — no body access, no AST deserialization.
|
|
210
|
+
var idx = mod._cache_index;
|
|
211
|
+
if (idx) {
|
|
212
|
+
if (idx.exec_refs && idx.exec_refs.length) {
|
|
213
|
+
work_queue.push({mod_id: mod_id, refs: idx.exec_refs});
|
|
214
|
+
}
|
|
215
|
+
// Check namespace imports for side-effect-bearing modules.
|
|
216
|
+
// has_side_effect_calls uses mod._has_side_effects for v6+ modules,
|
|
217
|
+
// so this never triggers body access on cached modules.
|
|
218
|
+
Object.keys(import_bindings[mod_id]).forEach(function(local) {
|
|
219
|
+
var b = import_bindings[mod_id][local];
|
|
220
|
+
if (!b.is_namespace) return;
|
|
221
|
+
var parts = b.source_module.split('.');
|
|
222
|
+
var partial = '';
|
|
223
|
+
for (var p = 0; p < parts.length; p++) {
|
|
224
|
+
partial = (p === 0) ? parts[0] : partial + '.' + parts[p];
|
|
225
|
+
if (has_side_effect_calls(all_modules[partial])) {
|
|
226
|
+
mark_module_live(partial);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!mod.body) return;
|
|
234
|
+
mod.body.forEach(function(stmt) {
|
|
235
|
+
if (!is_function(stmt) && !is_class(stmt) && !is_imports(stmt)) {
|
|
236
|
+
work_queue.push({mod_id: mod_id, node: stmt});
|
|
237
|
+
} else if (is_imports(stmt)) {
|
|
238
|
+
// "import X" always executes X's module code. When X has
|
|
239
|
+
// immediately-executing function calls that may have globally
|
|
240
|
+
// observable side effects (e.g. patching String.prototype),
|
|
241
|
+
// mark X live even if its local name is never referenced.
|
|
242
|
+
stmt.imports.forEach(function(imp) {
|
|
243
|
+
if (imp.argnames && imp.argnames.length) return;
|
|
244
|
+
var parts = imp.key.split('.');
|
|
245
|
+
var partial = '';
|
|
246
|
+
for (var p = 0; p < parts.length; p++) {
|
|
247
|
+
partial = (p === 0) ? parts[0] : partial + '.' + parts[p];
|
|
248
|
+
if (has_side_effect_calls(all_modules[partial])) {
|
|
249
|
+
mark_module_live(partial);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Mark a top-level def as live; enqueue its body for scanning
|
|
258
|
+
function mark_def_live(mod_id, def_name) {
|
|
259
|
+
if (!live_defs[mod_id]) live_defs[mod_id] = {};
|
|
260
|
+
if (live_defs[mod_id][def_name]) return;
|
|
261
|
+
live_defs[mod_id][def_name] = true;
|
|
262
|
+
if (top_defs[mod_id] && has_prop(top_defs[mod_id], def_name)) {
|
|
263
|
+
var entry = top_defs[mod_id][def_name];
|
|
264
|
+
if (entry._idx) {
|
|
265
|
+
// Precomputed index entry: push refs directly, no AST walk needed.
|
|
266
|
+
work_queue.push({mod_id: mod_id, refs: entry.refs});
|
|
267
|
+
} else {
|
|
268
|
+
work_queue.push({mod_id: mod_id, node: entry});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// For namespace imports (import X [as Y]), we cannot statically determine
|
|
274
|
+
// which properties of X are accessed. Mark every top-level def in mod_id
|
|
275
|
+
// as live so that X.any_func() calls work correctly at runtime.
|
|
276
|
+
function mark_all_defs_live(mod_id) {
|
|
277
|
+
if (!top_defs[mod_id]) return;
|
|
278
|
+
Object.keys(top_defs[mod_id]).forEach(function(def_name) {
|
|
279
|
+
mark_def_live(mod_id, def_name);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Process a single symbol reference found in live code of `mod_id`
|
|
284
|
+
function process_ref(mod_id, name) {
|
|
285
|
+
// Local top-level def?
|
|
286
|
+
if (top_defs[mod_id] && has_prop(top_defs[mod_id], name)) {
|
|
287
|
+
mark_def_live(mod_id, name);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
// Import binding?
|
|
291
|
+
if (import_bindings[mod_id] && has_prop(import_bindings[mod_id], name)) {
|
|
292
|
+
if (!used_bindings[mod_id]) used_bindings[mod_id] = {};
|
|
293
|
+
used_bindings[mod_id][name] = true;
|
|
294
|
+
var b = import_bindings[mod_id][name];
|
|
295
|
+
// Mark every partial module in the path as live (e.g. A, A.B, A.B.C).
|
|
296
|
+
// For namespace imports also mark ALL top-level defs in each partial
|
|
297
|
+
// module live, since we cannot know which properties are accessed via
|
|
298
|
+
// the namespace object (e.g. A.func(), A.B.other()).
|
|
299
|
+
var parts = b.source_module.split('.');
|
|
300
|
+
var partial = '';
|
|
301
|
+
for (var p = 0; p < parts.length; p++) {
|
|
302
|
+
partial = (p === 0) ? parts[0] : partial + '.' + parts[p];
|
|
303
|
+
mark_module_live(partial);
|
|
304
|
+
if (b.is_namespace) mark_all_defs_live(partial);
|
|
305
|
+
}
|
|
306
|
+
// For "from X import foo" mark only the specific def in X
|
|
307
|
+
if (!b.is_namespace && b.imported_name) {
|
|
308
|
+
mark_def_live(b.source_module, b.imported_name);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// Built-in, global, or unknown → ignore
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ── 4. Seed from __main__ ─────────────────────────────────────────────
|
|
315
|
+
mark_module_live('__main__');
|
|
316
|
+
|
|
317
|
+
// ── 5. Fixed-point iteration ──────────────────────────────────────────
|
|
318
|
+
// Use pop() (O(1)) instead of shift() (O(n)) — order doesn't matter for correctness.
|
|
319
|
+
while (work_queue.length > 0) {
|
|
320
|
+
var item = work_queue.pop();
|
|
321
|
+
if (item.refs) {
|
|
322
|
+
// Precomputed ref list from the cache index — no AST walk needed.
|
|
323
|
+
item.refs.forEach(function(name) {
|
|
324
|
+
process_ref(item.mod_id, name);
|
|
325
|
+
});
|
|
326
|
+
} else {
|
|
327
|
+
var refs = {};
|
|
328
|
+
collect_refs(item.node, refs);
|
|
329
|
+
Object.keys(refs).forEach(function(name) {
|
|
330
|
+
process_ref(item.mod_id, name);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ── 6. Prune dead code from live modules ──────────────────────────────
|
|
336
|
+
Object.keys(all_modules).forEach(function(mid) {
|
|
337
|
+
if (!live_modules[mid]) return; // dead modules handled below
|
|
338
|
+
|
|
339
|
+
var mod = all_modules[mid];
|
|
340
|
+
if (!mod || !mod.body) return;
|
|
341
|
+
|
|
342
|
+
var ld = live_defs[mid] || {};
|
|
343
|
+
var ub = used_bindings[mid] || {};
|
|
344
|
+
var td = top_defs[mid] || {};
|
|
345
|
+
var new_body = [];
|
|
346
|
+
mod.body.forEach(function(stmt) {
|
|
347
|
+
if ((is_function(stmt) || is_class(stmt)) && stmt.name) {
|
|
348
|
+
if (!ld[stmt.name.name]) return; // dead def
|
|
349
|
+
// Strip @no_prune — it is a tree-shaking annotation, not a
|
|
350
|
+
// runtime function, so it must not appear in compiled output.
|
|
351
|
+
if (stmt.decorators && stmt.decorators.length) {
|
|
352
|
+
stmt.decorators = stmt.decorators.filter(function(d) {
|
|
353
|
+
return !is_no_prune_decorator(d);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
} else if (is_imports(stmt)) {
|
|
357
|
+
var live_imps = [];
|
|
358
|
+
stmt.imports.forEach(function(imp) {
|
|
359
|
+
if (!live_modules[imp.key]) return; // entire source module dead
|
|
360
|
+
|
|
361
|
+
if (imp.argnames && imp.argnames.length) {
|
|
362
|
+
// "from X import a, b" – keep only used argnames
|
|
363
|
+
var live_args = imp.argnames.filter(function(arg) {
|
|
364
|
+
var local = arg.alias ? arg.alias.name : arg.name;
|
|
365
|
+
return !!ub[local];
|
|
366
|
+
});
|
|
367
|
+
if (live_args.length === 0) return; // no argnames used
|
|
368
|
+
imp.argnames = live_args;
|
|
369
|
+
}
|
|
370
|
+
// "import X [as Y]": keep when module is live
|
|
371
|
+
live_imps.push(imp);
|
|
372
|
+
});
|
|
373
|
+
if (live_imps.length === 0) return; // every import in this stmt dead
|
|
374
|
+
stmt.imports = live_imps;
|
|
375
|
+
}
|
|
376
|
+
new_body.push(stmt);
|
|
377
|
+
});
|
|
378
|
+
mod.body = new_body;
|
|
379
|
+
|
|
380
|
+
// Update exports: remove entries for dead defs
|
|
381
|
+
if (mod.exports) {
|
|
382
|
+
mod.exports = mod.exports.filter(function(sym) {
|
|
383
|
+
var name = sym.name;
|
|
384
|
+
if (!has_prop(td, name)) return true; // variable – always keep
|
|
385
|
+
return !!ld[name];
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// ── 7. Remove dead modules from the shared imports map ───────────────
|
|
391
|
+
Object.keys(toplevel.imports).forEach(function(mod_id) {
|
|
392
|
+
if (!live_modules[mod_id]) {
|
|
393
|
+
delete toplevel.imports[mod_id];
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
return toplevel;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export default tree_shake;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/* vim:fileencoding=utf-8
|
|
2
|
-
*
|
|
2
|
+
*
|
|
3
3
|
* Copyright (C) 2015 Kovid Goyal <kovid at kovidgoyal.net>
|
|
4
4
|
*
|
|
5
5
|
* Distributed under terms of the BSD license
|
|
6
6
|
*/
|
|
7
|
-
"use strict";
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
8
11
|
|
|
9
|
-
var comment_contents = /\/\*!?(?:\@preserve)?[ \t]*(?:\r\n|\n)([\s\S]*?)(?:\r\n|\n)[ \t]*\*\//;
|
|
10
12
|
var colors = ['red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'];
|
|
11
13
|
|
|
12
14
|
function ansi(code) {
|
|
@@ -14,13 +16,13 @@ function ansi(code) {
|
|
|
14
16
|
return String.fromCharCode(27) + '[' + code + 'm';
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
function path_exists(
|
|
18
|
-
var fs = require('fs');
|
|
19
|
+
async function path_exists(p) {
|
|
19
20
|
try {
|
|
20
|
-
fs.
|
|
21
|
+
await fs.promises.stat(p);
|
|
21
22
|
return true;
|
|
22
23
|
} catch(e) {
|
|
23
24
|
if (e.code != 'ENOENT') throw e;
|
|
25
|
+
return false;
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -57,7 +59,7 @@ function supports_color(stdout) {
|
|
|
57
59
|
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
function
|
|
62
|
+
function passthrough_colored(string) {
|
|
61
63
|
return string;
|
|
62
64
|
}
|
|
63
65
|
|
|
@@ -82,11 +84,11 @@ function wrap(lines, width) {
|
|
|
82
84
|
line = prev + line;
|
|
83
85
|
prev = '';
|
|
84
86
|
if (line.length > width) {
|
|
85
|
-
prev = line.substr(width);
|
|
87
|
+
prev = line.substr(width - 1);
|
|
86
88
|
if (prev) prev += ' ';
|
|
87
89
|
line = line.substr(0, width - 1);
|
|
88
|
-
if (line.substr(line.length - 1 !== ' ')
|
|
89
|
-
}
|
|
90
|
+
if (line.substr(line.length - 1) !== ' ') line += '-';
|
|
91
|
+
}
|
|
90
92
|
ans.push(line);
|
|
91
93
|
});
|
|
92
94
|
if (prev) ans = ans.concat(wrap([prev]));
|
|
@@ -105,24 +107,25 @@ function merge() {
|
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
function get_import_dirs(paths_string, ignore_env) {
|
|
108
|
-
var path = require('path');
|
|
109
110
|
var paths = [];
|
|
110
|
-
function
|
|
111
|
+
function add(new_path) {
|
|
111
112
|
if (paths.indexOf(new_path) == -1) paths.push(new_path);
|
|
112
113
|
}
|
|
113
114
|
if (!ignore_env && process && process.env && process.env.RAPYDSCRIPT_IMPORT_PATH) {
|
|
114
|
-
process.env.RAPYDSCRIPT_IMPORT_PATH.split(path.delimiter).forEach(
|
|
115
|
+
process.env.RAPYDSCRIPT_IMPORT_PATH.split(path.delimiter).forEach(add);
|
|
115
116
|
}
|
|
116
|
-
if (paths_string) paths_string.split(path.delimiter).forEach(
|
|
117
|
+
if (paths_string) paths_string.split(path.delimiter).forEach(add);
|
|
117
118
|
return paths;
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
export {
|
|
122
|
+
repeat,
|
|
123
|
+
wrap,
|
|
124
|
+
merge,
|
|
125
|
+
colored,
|
|
126
|
+
generators_available,
|
|
127
|
+
get_import_dirs,
|
|
128
|
+
path_exists,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export var safe_colored = (supports_color()) ? colored : passthrough_colored;
|