diff --git a/files.test.ts b/files.test.ts index e8021f5..8152852 100644 --- a/files.test.ts +++ b/files.test.ts @@ -60,7 +60,7 @@ async function compile_script(filename: string, test_name: string) { function q(str: string): string { return "`" + str + "`"; } - const prog = await pprint(result.loc, true); + const prog = await pprint(result.loc, { prettify: true }); const out = `## ${q(test_name)}\n\n` + `### Status: ${q(result.name)}\n\n` + diff --git a/package-lock.json b/package-lock.json index fa92060..0ac1fce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,12 +18,14 @@ "commander": "^12.1.0", "ignore": "^6.0.2", "index-to-position": "^1.0.0", + "js-base64": "^3.7.7", "json-stable-stringify": "^1.2.1", "json-stringify-pretty-compact": "^4.0.0", "prettier": "^3.4.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.1.1", + "source-map": "^0.7.4", "typescript": "^5.5.3", "zipper": "github:azizghuloum/zipper" }, @@ -3371,6 +3373,12 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", + "license": "BSD-3-Clause" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4169,6 +4177,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", diff --git a/package.json b/package.json index 4d12c75..a0ea3a0 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,11 @@ "lint": "eslint .", "preview": "vite preview" }, + "overrides": { + "vite-plugin-node-polyfills": { + "vite": "^6.0.0" + } + }, "dependencies": { "@babel/code-frame": "^7.26.2", "@codemirror/lang-javascript": "^6.2.2", @@ -26,12 +31,14 @@ "commander": "^12.1.0", "ignore": "^6.0.2", "index-to-position": "^1.0.0", + "js-base64": "^3.7.7", "json-stable-stringify": "^1.2.1", "json-stringify-pretty-compact": "^4.0.0", "prettier": "^3.4.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.1.1", + "source-map": "^0.7.4", "typescript": "^5.5.3", "zipper": "github:azizghuloum/zipper" }, diff --git a/src/library-manager.ts b/src/library-manager.ts index 1fe9fb5..68d3e56 100644 --- a/src/library-manager.ts +++ b/src/library-manager.ts @@ -21,7 +21,7 @@ import stringify from "json-stringify-pretty-compact"; import { init_global_context } from "./global-module"; import { parse_dts } from "./parse-dts"; -const cookie = "rewrite-ts-020"; +const cookie = "rewrite-ts-021"; type module_state = | { type: "initial" } @@ -402,7 +402,20 @@ class RtsModule extends Module { }; const code_path = this.get_generated_code_absolute_path(); await fs.mkdir(dirname(code_path), { recursive: true }); - await fs.writeFile(code_path, await pprint(loc, false)); + await fs.writeFile( + code_path, + await pprint(loc, { + prettify: false, + map: { + filename: basename(code_path), + resolve: async (cuid: string) => { + const mod = this.find_module_by_cid(cuid); + assert(mod !== undefined); + return relative(dirname(code_path), mod.path); + }, + }, + }), + ); await fs.writeFile(this.get_proxy_path(), proxy_code); const mtime = Date.now(); await fs.writeFile(this.get_json_path(), stringify(json_content)); diff --git a/src/pprint.ts b/src/pprint.ts index 6fe2252..adc4192 100644 --- a/src/pprint.ts +++ b/src/pprint.ts @@ -1,13 +1,16 @@ +import { AST, source } from "./ast"; import { llmap, llreverse, ll_to_array } from "./llhelpers"; import { Loc, STX } from "./syntax-structures"; import { list_tag } from "./tags"; import * as prettier from "prettier/standalone"; import * as prettier_ts from "prettier/plugins/typescript"; import * as prettier_estree from "prettier/plugins/estree"; +import { SourceMapGenerator } from "source-map"; +import { Base64 } from "js-base64"; import { assert } from "./assert"; -import { AST } from "./ast"; -type ns = string | ns[]; +type n = { val: string; src: source | false }; +type ns = n | ns[]; const children_need_semi: { [k in list_tag]?: boolean } = { program: true, @@ -19,29 +22,28 @@ function loc_to_ns(loc: Loc): ns { /* */ function push_semi(ns: ns, semi: string): ns { - if (typeof ns === "string") { - return ns.endsWith(";") ? ns : [ns, semi]; - } else { + if (Array.isArray(ns)) { if (ns.length === 0) { - return semi; + return { val: semi, src: false }; } else { return ns.map((x, i) => (i === ns.length - 1 ? push_semi(x, semi) : x)); } + } else { + return ns.val.endsWith(";") ? ns : [ns, { val: semi, src: false }]; } } type src = (AST | STX)["src"]; function wrap_src(src: src, content: string): ns { - if (!src) return content; + if (!src) return { val: content, src: false }; if (src.type !== "origin") return wrap_src(src.src, content); - // add src - return content; + return { val: content, src }; } function stx_to_ns(stx: AST | STX, semi: boolean): ns { if (stx.tag === "empty_statement") return []; if (stx.tag === "slice") return ll_to_array(stx.content) .map((x) => stx_to_ns(x, true)) - .filter((x) => x.length > 0); + .filter((x) => (Array.isArray(x) ? x.length > 0 : x.val.length > 0)); if (semi && stx.tag !== "other") return push_semi(stx_to_ns(stx, false), `;`); switch (stx.type) { case "list": { @@ -59,7 +61,11 @@ function loc_to_ns(loc: Loc): ns { case "other": return wrap_src(stx.src, stx.content); case "ERROR": - return ["!!!ERROR!!!", wrap_src(stx.src, stx.content), "!!!ERROR!!!"]; + return [ + wrap_src(stx.src, "!!!ERROR!!!"), + wrap_src(stx.src, stx.content), + wrap_src(stx.src, "!!!ERROR!!!"), + ]; default: const invalid: never = stx; throw invalid; @@ -77,15 +83,12 @@ function loc_to_ns(loc: Loc): ns { case "binary_expression": case "unary_expression": case "ternary_expression": - return ["(", ls, ")"]; + return [lparen, ls, rparen]; default: return ls; } } - const lp = "/*>>>*/"; - const rp = "/*<<<*/"; - function path_to_ns(path: Loc["p"], ns: ns): ns { switch (path.type) { case "top": @@ -103,11 +106,11 @@ function loc_to_ns(loc: Loc): ns { } function mark_top(ns: ns): ns { - return [lp, ns, rp]; + return [lpointer, ns, rpointer]; } function strip_top(ns: ns): ns { - if (Array.isArray(ns) && ns.length === 3 && ns[0] === lp && ns[2] === rp) { + if (Array.isArray(ns) && ns.length === 3 && ns[0] === lpointer && ns[2] === rpointer) { return ns[1]; } else { return ns; @@ -120,26 +123,86 @@ function loc_to_ns(loc: Loc): ns { } } +const lparen: n = { val: "(", src: false }; +const rparen: n = { val: ")", src: false }; +const space: n = { val: " ", src: false }; +const newline: n = { val: "\n", src: false }; +const lpointer: n = { val: "/*>>>*/", src: false }; +const rpointer: n = { val: "/*<<<*/", src: false }; + function ns_flatten(main_ns: ns) { - const ac: string[] = []; + const ac: n[] = []; - function push(x: string) { - ac.push(" "); + function push(x: n) { + ac.push(space); ac.push(x); } function conv(ns: ns) { - if (typeof ns === "string") { - push(ns); - } else { + if (Array.isArray(ns)) { ns.forEach(conv); + } else { + push(ns); } } conv(main_ns); - ac.push("\n"); - return ac[0] === " " ? ac.slice(1) : ac; + return ac[0] === space ? ac.slice(1) : ac; +} + +type map_options = { + filename: string; + resolve: (cuid: string) => Promise; +}; + +function uniq(ls: string[]): string[] { + return Object.keys(Object.fromEntries(ls.map((x) => [x, x]))); +} + +async function add_src_map(code: string, ls: n[], options: map_options): Promise { + const srcmap = new SourceMapGenerator({ + file: options.filename, + }); + let line = 0; + let column = 0; + + const paths: { [cuid: string]: string } = Object.fromEntries( + await Promise.all( + uniq(ls.map((x) => (x.src ? x.src.cuid : "")).filter((x) => x.length > 0)).map( + async (cuid) => [cuid, await options.resolve(cuid)], + ), + ), + ); + + function emit_src(src: source) { + assert(typeof src.s !== "number"); + const source = paths[src.cuid]; + assert(source !== undefined); + srcmap.addMapping({ + generated: { line: line + 1, column }, + original: { line: src.s.line + 1, column: src.s.column }, + source, + name: src.name, + }); + return; + } + function advance_loc(val: string) { + const lines = val.split(/\(\r\n\)|\r|\n/g); + if (lines.length === 1) { + column += lines[0].split("").length; + } else { + line += lines.length - 1; + column = lines[lines.length - 1].split("").length; + } + } + ls.forEach(({ src, val }) => { + if (src) emit_src(src); + advance_loc(val); + }); + const map_string = srcmap.toString(); + const base64 = Base64.encode(map_string); + return `${code}\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64}\n`; } export async function pretty_print(code: string) { @@ -155,7 +218,17 @@ export async function pretty_print(code: string) { } } -export async function pprint(loc: Loc, prettify: boolean) { - const src = ns_flatten(loc_to_ns(loc)).join(""); - return prettify ? await pretty_print(src) : src; +type options = { + prettify: boolean; + map?: map_options; +}; + +export async function pprint(loc: Loc, options: options): Promise { + const ls = ns_flatten(loc_to_ns(loc)); + const code = ls.map((x) => x.val).join(""); + return options.prettify + ? await pretty_print(code) + : options.map + ? add_src_map(code, ls, options.map) + : code; } diff --git a/test-project/.rts/main.rts.json b/test-project/.rts/main.rts.json index e79487d..baa79b5 100644 --- a/test-project/.rts/main.rts.json +++ b/test-project/.rts/main.rts.json @@ -1,6 +1,6 @@ { "cid": "test-project/main.rts rewrite-ts-visualized 0.0.0", - "cookie": "rewrite-ts-020", + "cookie": "rewrite-ts-021", "imports": [ { "pkg": {"name": "rewrite-ts-visualized", "version": "0.0.0"}, diff --git a/test-project/.rts/main.rts.ts b/test-project/.rts/main.rts.ts index cf7b119..379024c 100644 --- a/test-project/.rts/main.rts.ts +++ b/test-project/.rts/main.rts.ts @@ -1 +1,2 @@ import { type t_2 as t_2 , x_1 as x_3 , f_3 as f_4 } from "./mod.rts.ts" ; import { expect as expect_5 } from "vitest" ; export const y_1 : t_2 = ( x_3 + f_4 ) ; console . log ( expect_5 ) ; console . log ( y_1 ) ; +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL21haW4ucnRzIiwiLi4vbW9kLnJ0cyJdLCJuYW1lcyI6WyJ5IiwidCIsIngiLCJmIiwiY29uc29sZSIsImxvZyIsImV4cGVjdCJdLCJtYXBwaW5ncyI6ImdJQUVBLE1BQU1BLElBQUMsRUFBRUMsSUFBRSxJQUFFQyxJQUFFLEVDR3lCQyxRREZ4Q0MsUUFBTyxFQUFDQyxJQUFHLEVBQUNDLFNBQU0sSUFDbEJGLFFBQU8sRUFBQ0MsSUFBRyxFQUFDTCxJQUFDIiwiZmlsZSI6Im1haW4ucnRzLnRzIn0= diff --git a/test-project/.rts/mod.rts.json b/test-project/.rts/mod.rts.json index 384abae..997a8c7 100644 --- a/test-project/.rts/mod.rts.json +++ b/test-project/.rts/mod.rts.json @@ -1,6 +1,6 @@ { "cid": "test-project/mod.rts rewrite-ts-visualized 0.0.0", - "cookie": "rewrite-ts-020", + "cookie": "rewrite-ts-021", "imports": [], "exported_identifiers": { "x": [ diff --git a/test-project/.rts/mod.rts.ts b/test-project/.rts/mod.rts.ts index a5ede7d..a73863c 100644 --- a/test-project/.rts/mod.rts.ts +++ b/test-project/.rts/mod.rts.ts @@ -1 +1,2 @@ export const x_1 = 12 ; export type t_2 = number ; export const f_3 = 13 ; +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL21vZC5ydHMiXSwibmFtZXMiOlsieCIsInQiLCJmIl0sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLE1BQU1BLElBQUUsRUFBRSxHQUFFLEVBQ25CLE9BQU8sS0FBS0MsSUFBRSxFQUFFLE9BQU0sU0FFdEIsTUFBTUMsSUFBRSxFQUFFIiwiZmlsZSI6Im1vZC5ydHMudHMifQ== diff --git a/test-project/.rts/test1.test.rts.json b/test-project/.rts/test1.test.rts.json index 3c3ed18..e340831 100644 --- a/test-project/.rts/test1.test.rts.json +++ b/test-project/.rts/test1.test.rts.json @@ -1,6 +1,6 @@ { "cid": "test-project/test1.test.rts rewrite-ts-visualized 0.0.0", - "cookie": "rewrite-ts-020", + "cookie": "rewrite-ts-021", "imports": [ { "pkg": {"name": "vitest", "version": "2.1.8"}, diff --git a/test-project/.rts/test1.test.rts.ts b/test-project/.rts/test1.test.rts.ts index e8b0766..1bea5bb 100644 --- a/test-project/.rts/test1.test.rts.ts +++ b/test-project/.rts/test1.test.rts.ts @@ -1 +1,2 @@ import { suite as suite_1 , test as test_2 , expect as expect_3 } from "vitest" ; suite_1 ( "sample test" , ( ( ) => { test_2 ( "should work" , ( ( ) => { expect_3 ( 5 ) . toBe ( 5 ) ; } ) ) ; } ) ) ; +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3Rlc3QxLnRlc3QucnRzIl0sIm5hbWVzIjpbInN1aXRlIiwidGVzdCIsImV4cGVjdCIsInRvQmUiXSwibWFwcGluZ3MiOiJrRkFFQUEsUUFBSyxFQUFDLGNBQWEsSUFBRSxFQUFDLEVBQUUsR0FBRyxFQUN6QkMsT0FBSSxFQUFDLGNBQWEsSUFBRSxFQUFDLEVBQUUsR0FBRyxFQUN4QkMsU0FBTSxFQUFDLEVBQUMsRUFBQyxFQUFDQyxLQUFJLEVBQUMsRUFBQyxJQUNsQixJQUFDLElBQ0gsSUFBQyIsImZpbGUiOiJ0ZXN0MS50ZXN0LnJ0cy50cyJ9 diff --git a/ui/App.tsx b/ui/App.tsx index 20a0cb2..f817673 100644 --- a/ui/App.tsx +++ b/ui/App.tsx @@ -158,7 +158,10 @@ function Example({ code, onChange }: ExampleProps) { state.pointer === null || state.pointer >= state.prev_steps.length ? [state.last_step, state.step_number] : [state.prev_steps[state.pointer], state.pointer]; - const code_to_display = useMemo(() => pprint(display_step.loc, true), [display_step]); + const code_to_display = useMemo( + () => pprint(display_step.loc, { prettify: true }), + [display_step], + ); return (