rapydscript-ng 0.7.24 → 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.
Files changed (114) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +72 -26
  3. package/bin/package.json +1 -0
  4. package/bin/rapydscript +65 -62
  5. package/editor-plugins/nvim/rapydscript/README.md +184 -0
  6. package/editor-plugins/nvim/rapydscript/bin/rapydscript.so +0 -0
  7. package/editor-plugins/nvim/rapydscript/ftdetect/rapydscript.lua +10 -0
  8. package/editor-plugins/nvim/rapydscript/lua/rapydscript/health.lua +88 -0
  9. package/editor-plugins/nvim/rapydscript/lua/rapydscript/init.lua +279 -0
  10. package/local-agent.md +28 -0
  11. package/package.json +4 -5
  12. package/release/baselib-plain-pretty.js +448 -326
  13. package/release/baselib-plain-ugly.js +3 -3
  14. package/release/compiler.js +2528 -1672
  15. package/release/signatures.json +17 -17
  16. package/src/ast.pyj +73 -25
  17. package/src/baselib-builtins.pyj +2 -2
  18. package/src/baselib-containers.pyj +153 -151
  19. package/src/baselib-internal.pyj +3 -3
  20. package/src/baselib-str.pyj +5 -5
  21. package/src/lib/aes.pyj +1 -1
  22. package/src/lib/encodings.pyj +1 -1
  23. package/src/lib/pythonize.pyj +1 -1
  24. package/src/lib/re.pyj +2 -2
  25. package/src/lib/traceback.pyj +165 -28
  26. package/src/lib/uuid.pyj +1 -1
  27. package/src/output/codegen.pyj +10 -3
  28. package/src/output/functions.pyj +31 -40
  29. package/src/output/literals.pyj +11 -14
  30. package/src/output/loops.pyj +25 -87
  31. package/src/output/modules.pyj +5 -47
  32. package/src/output/statements.pyj +1 -1
  33. package/src/output/stream.pyj +58 -31
  34. package/src/parse.pyj +777 -427
  35. package/src/tokenizer.pyj +16 -4
  36. package/src/utils.pyj +1 -1
  37. package/test/_treeshake_mod.pyj +30 -0
  38. package/test/_treeshake_side_effect.pyj +9 -0
  39. package/test/ast_serialization.pyj +392 -0
  40. package/test/async.pyj +95 -0
  41. package/test/baselib.pyj +1 -2
  42. package/test/embedded_compiler.pyj +57 -0
  43. package/test/fmt.pyj +201 -0
  44. package/test/generic.pyj +7 -3
  45. package/test/imports.pyj +8 -6
  46. package/test/internationalization.pyj +38 -37
  47. package/test/lint.pyj +120 -117
  48. package/test/lsp.pyj +222 -0
  49. package/test/repl.pyj +70 -65
  50. package/test/sourcemaps.pyj +315 -0
  51. package/test/starargs.pyj +46 -39
  52. package/test/traceback.pyj +263 -0
  53. package/test/treeshaking.pyj +181 -0
  54. package/test/web_repl_export.pyj +88 -0
  55. package/tools/ast_serialize.mjs +557 -0
  56. package/tools/cli.mjs +510 -0
  57. package/tools/compile.mjs +201 -0
  58. package/tools/compiler.mjs +127 -0
  59. package/tools/{completer.js → completer.mjs} +6 -6
  60. package/tools/{embedded_compiler.js → embedded_compiler.mjs} +17 -13
  61. package/tools/export.js +109 -56
  62. package/tools/fmt.mjs +735 -0
  63. package/tools/{gettext.js → gettext.mjs} +46 -53
  64. package/tools/{ini.js → ini.mjs} +9 -10
  65. package/tools/{lint.js → lint.mjs} +78 -55
  66. package/tools/lsp.mjs +914 -0
  67. package/tools/lsp_protocol.mjs +259 -0
  68. package/tools/lsp_symbols.mjs +418 -0
  69. package/tools/{msgfmt.js → msgfmt.mjs} +18 -18
  70. package/tools/{repl.js → repl.mjs} +52 -41
  71. package/tools/{self.js → self.mjs} +56 -46
  72. package/tools/sourcemap.mjs +123 -0
  73. package/tools/test.mjs +152 -0
  74. package/tools/treeshake.mjs +400 -0
  75. package/tools/{utils.js → utils.mjs} +26 -23
  76. package/tools/{web_repl.js → web_repl.mjs} +15 -13
  77. package/tools/web_repl_export.mjs +93 -0
  78. package/tree-sitter/README.md +101 -0
  79. package/tree-sitter/grammar.js +992 -0
  80. package/tree-sitter/package.json +43 -0
  81. package/tree-sitter/queries/rapydscript/highlights.scm +232 -0
  82. package/tree-sitter/queries/rapydscript/indents.scm +64 -0
  83. package/tree-sitter/queries/rapydscript/injections.scm +14 -0
  84. package/tree-sitter/queries/rapydscript/locals.scm +108 -0
  85. package/tree-sitter/src/grammar.json +4976 -0
  86. package/tree-sitter/src/node-types.json +2901 -0
  87. package/tree-sitter/src/parser.c +196465 -0
  88. package/tree-sitter/src/scanner.c +294 -0
  89. package/tree-sitter/src/tree_sitter/alloc.h +54 -0
  90. package/tree-sitter/src/tree_sitter/array.h +330 -0
  91. package/tree-sitter/src/tree_sitter/parser.h +286 -0
  92. package/tree-sitter/test/corpus/chaining.txt +99 -0
  93. package/tree-sitter/test/corpus/classes.txt +147 -0
  94. package/tree-sitter/test/corpus/comprehensions.txt +155 -0
  95. package/tree-sitter/test/corpus/containers.txt +242 -0
  96. package/tree-sitter/test/corpus/control_flow.txt +361 -0
  97. package/tree-sitter/test/corpus/decorators.txt +64 -0
  98. package/tree-sitter/test/corpus/existential.txt +102 -0
  99. package/tree-sitter/test/corpus/functions.txt +293 -0
  100. package/tree-sitter/test/corpus/imports.txt +117 -0
  101. package/tree-sitter/test/corpus/literals.txt +135 -0
  102. package/tree-sitter/test/corpus/operators.txt +296 -0
  103. package/tree-sitter/test/corpus/statements.txt +189 -0
  104. package/tree-sitter/test/corpus/strings.txt +90 -0
  105. package/tree-sitter/test/corpus/subscripts.txt +227 -0
  106. package/tree-sitter/tree-sitter.json +36 -0
  107. package/web-repl/env.js +35 -23
  108. package/web-repl/main.js +8 -8
  109. package/bin/export +0 -75
  110. package/bin/web-repl-export +0 -102
  111. package/tools/cli.js +0 -523
  112. package/tools/compile.js +0 -184
  113. package/tools/compiler.js +0 -84
  114. 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"; /*jshint node:true */
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(path) {
18
- var fs = require('fs');
19
+ async function path_exists(p) {
19
20
  try {
20
- fs.statSync(path);
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 safe_colored(string) {
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 !== ' ')) line += '-';
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 merge(new_path) {
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(merge);
115
+ process.env.RAPYDSCRIPT_IMPORT_PATH.split(path.delimiter).forEach(add);
115
116
  }
116
- if (paths_string) paths_string.split(path.delimiter).forEach(merge);
117
+ if (paths_string) paths_string.split(path.delimiter).forEach(add);
117
118
  return paths;
118
119
  }
119
120
 
120
- exports.comment_contents = comment_contents;
121
- exports.repeat = repeat;
122
- exports.wrap = wrap;
123
- exports.merge = merge;
124
- exports.colored = colored;
125
- exports.safe_colored = (supports_color()) ? colored : safe_colored;
126
- exports.generators_available = generators_available;
127
- exports.get_import_dirs = get_import_dirs;
128
- exports.path_exists = path_exists;
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;