diff --git a/files.test.ts b/files.test.ts index 10f6f99..8152852 100644 --- a/files.test.ts +++ b/files.test.ts @@ -7,7 +7,6 @@ import { initial_step } from "./src/expander"; import { pprint } from "./src/pprint"; import { StxError, syntax_error } from "./src/stx-error"; import { preexpand_helpers } from "./src/preexpand-helpers"; -import { source_file } from "./src/ast"; import { get_globals, init_global_context } from "./src/global-module"; const test_dir = __dirname + "/tests"; @@ -39,11 +38,7 @@ async function compile_script(filename: string, test_name: string) { return k(); }, }; - const source_file: source_file = { - package: { name: "@rewrite-ts/test", version: "0.0.0" }, - path: filename, - }; - const [_loc0, expand] = initial_step(parse(code, source_file), test_name, ["es2024.full"]); + const [_loc0, expand] = initial_step(parse(code, test_name), test_name, ["es2024.full"]); const result = await (async () => { try { const { loc } = await expand(helpers); @@ -65,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); + 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/ast.ts b/src/ast.ts index 0a074a3..4ad8fef 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -3,13 +3,14 @@ import { atom_tag, list_tag } from "./tags"; export type no_source = false; -export type source_file = { package: { name: string; version: string }; path: string }; +export type pos = number | { line: number; column: number; offset: number }; export type source = { type: "origin"; - p: number; - e: number; - f: source_file; + s: pos; + e: pos; + name: string | undefined; + cuid: string; }; export type src = no_source | source; diff --git a/src/expander.ts b/src/expander.ts index 4ea2f14..d606824 100644 --- a/src/expander.ts +++ b/src/expander.ts @@ -280,6 +280,9 @@ const list_handlers_table: { [tag in list_tag]: "descend" | "stop" | "todo" } = type_operator: "todo", type_predicate: "todo", type_reference: "todo", + new_expression: "descend", + throw_statement: "descend", + element_access_expression: "descend", syntax_list: "descend", }; diff --git a/src/library-manager.ts b/src/library-manager.ts index e1daf43..68d3e56 100644 --- a/src/library-manager.ts +++ b/src/library-manager.ts @@ -14,14 +14,14 @@ import { get_exported_identifiers_from_rib, exported_identifiers, } from "./preexpand-helpers"; -import { AST, source_file } from "./ast"; +import { AST } from "./ast"; import { normalize } from "node:path"; import { Binding, CompilationUnit, Context, Loc, Rib } from "./syntax-structures"; import stringify from "json-stringify-pretty-compact"; import { init_global_context } from "./global-module"; import { parse_dts } from "./parse-dts"; -const cookie = "rewrite-ts-016"; +const cookie = "rewrite-ts-021"; type module_state = | { type: "initial" } @@ -374,11 +374,7 @@ class RtsModule extends Module { const code = await fs.readFile(this.path, { encoding: "utf-8" }); const my_pkg = this.pkg; const my_path = this.pkg_relative_path; - const source_file: source_file = { - package: { name: my_pkg.name, version: my_pkg.version }, - path: my_path, - }; - const [_loc0, expand] = initial_step(parse(code, source_file), state.cid, this.libs); + const [_loc0, expand] = initial_step(parse(code, state.cid), state.cid, this.libs); try { const helpers = this.get_preexpand_helpers(my_pkg, my_path); const { loc, unit, context, modular } = await expand(helpers); @@ -406,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)); + 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)); @@ -415,7 +424,12 @@ class RtsModule extends Module { } catch (error) { this.state = { type: "error", reason: String(error) }; if (error instanceof StxError) { - await print_stx_error(error, this.library_manager); + await print_stx_error(error, { + get_module_by_cuid: (cuid) => { + return this.find_module_by_cid(cuid); + }, + //this.library_manager + }); } else { console.error(error); } diff --git a/src/llhelpers.ts b/src/llhelpers.ts index 2fdf6a3..6b0e579 100644 --- a/src/llhelpers.ts +++ b/src/llhelpers.ts @@ -16,6 +16,13 @@ export function llmap(ls: LL, f: (x: X) => Y): LL { return ls === null ? null : [f(ls[0]), llmap(ls[1], f)]; } +export function llforeach(ls: LL, f: (x: X) => Y): void { + if (ls !== null) { + f(ls[0]); + llforeach(ls[1], f); + } +} + export function array_to_ll(a: X[]): LL { let ll: LL = null; for (let i = a.length - 1; i >= 0; i--) ll = [a[i], ll]; diff --git a/src/parse.ts b/src/parse.ts index 645fd6d..27f4407 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -1,6 +1,6 @@ -import { AST, source_file, src } from "./ast"; -import { list_tag } from "./tags"; -import { array_to_ll, LL, llappend } from "./llhelpers"; +import { AST, src } from "./ast"; +import { atom_tag, list_tag } from "./tags"; +import { array_to_ll, LL, llappend, llforeach } from "./llhelpers"; import TS, { SyntaxKind } from "typescript"; import { assert } from "./assert"; @@ -36,6 +36,9 @@ const pass_through: { [k in SyntaxKind]?: list_tag } = { [SyntaxKind.MethodSignature]: "method_signature", [SyntaxKind.ParenthesizedType]: "parenthesized_type", [SyntaxKind.TypePredicate]: "type_predicate", + [SyntaxKind.NewExpression]: "new_expression", + [SyntaxKind.ThrowStatement]: "throw_statement", + [SyntaxKind.ElementAccessExpression]: "element_access_expression", [SyntaxKind.SyntaxList]: "syntax_list", }; @@ -54,7 +57,7 @@ const remove_singleton_identifier: { [k in SyntaxKind]?: list_tag } = { [SyntaxKind.ImportSpecifier]: "import_specifier", }; -function left_associate(op: string, [head, tail]: [AST, LL], src: src): AST { +function left_associate(op: string, [head, tail]: [AST, LL]): AST { function f(head: AST, tail: LL): AST { if (tail === null) { return head; @@ -63,6 +66,11 @@ function left_associate(op: string, [head, tail]: [AST, LL], src: src): AST assert(t0.content === op); assert(t1 !== null); const [t2, t3] = t1; + const src0 = head.src; + assert(src0 !== false); + const src1 = t2.src; + assert(src1 !== false); + const src: src = { ...src0, e: src1.e }; return f( { type: "list", tag: "binary_expression", content: [head, [t0, [t2, null]]], src }, t3, @@ -77,11 +85,17 @@ function left_associate(op: string, [head, tail]: [AST, LL], src: src): AST } } -function absurdly(node: TS.Node, source: TS.SourceFile, f: source_file): AST { - const src: src = { type: "origin", p: node.pos, e: node.end, f }; +function absurdly(node: TS.Node, source: TS.SourceFile, cuid: string): AST { const children = node.getChildren(source); if (children.length === 0 && node.kind !== SyntaxKind.SyntaxList) { const content = node.getText(source); + const src: src = { + type: "origin", + s: node.end - content.length, + e: node.end, + name: node.kind === TS.SyntaxKind.Identifier ? content : undefined, + cuid, + }; switch (node.kind) { case SyntaxKind.NumericLiteral: return { type: "atom", tag: "number", content, src }; @@ -111,6 +125,7 @@ function absurdly(node: TS.Node, source: TS.SourceFile, f: source_file): AST { case SyntaxKind.GreaterThanToken: case SyntaxKind.EqualsToken: case SyntaxKind.BarToken: + case SyntaxKind.BarBarToken: case SyntaxKind.AmpersandToken: case SyntaxKind.ImportKeyword: case SyntaxKind.ExportKeyword: @@ -141,6 +156,8 @@ function absurdly(node: TS.Node, source: TS.SourceFile, f: source_file): AST { case SyntaxKind.IsKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.NewKeyword: + case SyntaxKind.ThrowKeyword: return { type: "atom", tag: "other", content, src }; case SyntaxKind.EndOfFileToken: return { type: "atom", tag: "other", content, src }; @@ -149,7 +166,8 @@ function absurdly(node: TS.Node, source: TS.SourceFile, f: source_file): AST { } } } else { - const ls = children.filter((x) => x.kind !== null).map((x) => absurdly(x, source, f)); + const ls = children.filter((x) => x.kind !== null).map((x) => absurdly(x, source, cuid)); + const src: src = { type: "origin", s: node.pos, e: node.end, name: undefined, cuid }; const content = array_to_ll(ls); { const tag = remove_singleton_identifier[node.kind]; @@ -198,6 +216,7 @@ function absurdly(node: TS.Node, source: TS.SourceFile, f: source_file): AST { ls[0].content[0], // export keyword llappend(ls[1].content, [ls[2], null]), ], + src, }; } else { return { type: "list", tag: "ERROR", content, src }; @@ -217,13 +236,27 @@ function absurdly(node: TS.Node, source: TS.SourceFile, f: source_file): AST { assert(ls.length === 5, ls); const [lt, fmls, rt, ar, body] = ls; assert(fmls.tag === "syntax_list", fmls); + assert( + lt.src && + rt.src && + lt.src.cuid === rt.src.cuid && + typeof lt.src.s === "number" && + typeof rt.src.e === "number" && + lt.src.s < rt.src.e, + ); + const args_src = { ...lt.src, p: lt.src.s, e: rt.src.e }; const args: AST = { type: "list", tag: "formal_parameters", content: [lt, llappend(fmls.content, [rt, null])], - src, + src: args_src, + }; + return { + type: "list", + tag: "arrow_function", + content: [args, [ar, [body, null]]], + src: src, }; - return { type: "list", tag: "arrow_function", content: [args, [ar, [body, null]]], src }; } case SyntaxKind.ShorthandPropertyAssignment: case SyntaxKind.LiteralType: @@ -249,14 +282,14 @@ function absurdly(node: TS.Node, source: TS.SourceFile, f: source_file): AST { const x = ls[0]; assert(x.tag === "syntax_list"); assert(x.content !== null); - return left_associate("|", x.content, src); + return left_associate("|", x.content); } case SyntaxKind.IntersectionType: { assert(ls.length === 1); const x = ls[0]; assert(x.tag === "syntax_list"); assert(x.content !== null); - return left_associate("&", x.content, src); + return left_associate("&", x.content); } case SyntaxKind.AsExpression: { assert(ls.length === 3); @@ -284,14 +317,56 @@ function absurdly(node: TS.Node, source: TS.SourceFile, f: source_file): AST { } } -export function parse(code: string, f: source_file): AST { +function remap_source(ast: AST, code: string) { + let offset = 0; + let line = 0; + let column = 0; + + function get_src( + p: number, + tag: list_tag | atom_tag, + content: any, + ): { line: number; column: number; offset: number } { + if (p > offset) { + const frag = code.slice(offset, p); + const lines = frag.split(/\(\r\n\)|\r|\n/g); + if (lines.length === 1) { + const l0 = lines[0]; + column += l0.split("").length; + } else { + assert(lines.length > 1); + line += lines.length - 1; + column = lines[lines.length - 1].split("").length; + } + offset = p; + } + assert(p === offset, `rewind from ${offset} to ${p} in ${tag} ${content}`); + return { line, column, offset }; + } + + function remap(ast: AST) { + assert(ast.src !== false); + assert(typeof ast.src.s === "number", JSON.stringify(ast)); + ast.src.s = get_src(ast.src.s, ast.tag, ast.content); + if (ast.type === "list") { + llforeach(ast.content, remap); + } + assert(typeof ast.src.e === "number"); + ast.src.e = get_src(ast.src.e, ast.tag, ast.content); + } + + remap(ast); +} + +export function parse(code: string, cuid: string): AST { try { const options: TS.CreateSourceFileOptions = { languageVersion: TS.ScriptTarget.ESNext, jsDocParsingMode: TS.JSDocParsingMode.ParseNone, }; const src = TS.createSourceFile("code.tsx", code, options); - const ast = absurdly(src, src, f); + const ast = absurdly(src, src, cuid); + remap_source(ast, code); return ast; } catch (err) { console.error(err); diff --git a/src/pprint.ts b/src/pprint.ts index b1b3515..adc4192 100644 --- a/src/pprint.ts +++ b/src/pprint.ts @@ -1,12 +1,16 @@ -import { AST } from "./serialize"; +import { AST, source } from "./ast"; import { llmap, llreverse, ll_to_array } from "./llhelpers"; -import { Loc } from "./syntax-structures"; +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"; -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, @@ -17,8 +21,30 @@ const children_need_semi: { [k in list_tag]?: boolean } = { function loc_to_ns(loc: Loc): ns { /* */ - function stx_to_ns(stx: AST, semi: boolean): ns { - if (semi && stx.tag !== "other") return [stx_to_ns(stx, false), ";"]; + function push_semi(ns: ns, semi: string): ns { + if (Array.isArray(ns)) { + if (ns.length === 0) { + 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 { val: content, src: false }; + if (src.type !== "origin") return wrap_src(src.src, 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) => (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": { const semi = children_need_semi[stx.tag] ?? false; @@ -33,9 +59,13 @@ function loc_to_ns(loc: Loc): ns { case "string": case "regex": case "other": - return stx.content; + return wrap_src(stx.src, stx.content); case "ERROR": - return "!!!ERROR!!! " + 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; @@ -53,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": @@ -79,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; @@ -96,24 +123,86 @@ function loc_to_ns(loc: Loc): ns { } } -function ns_to_string(main_ns: ns) { - const ac: string[] = []; +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: 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); - return 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) { @@ -129,7 +218,17 @@ export async function pretty_print(code: string) { } } -export async function pprint(loc: Loc) { - const src = ns_to_string(loc_to_ns(loc)).join(""); - return await pretty_print(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/src/preexpand-helpers.ts b/src/preexpand-helpers.ts index 0e600b6..9ee72eb 100644 --- a/src/preexpand-helpers.ts +++ b/src/preexpand-helpers.ts @@ -15,6 +15,7 @@ export type import_resolution = { // }; export type imported_module = { + path: string; imported_modules: imported_module[]; dependant_modules: imported_module[]; resolve_exported_identifier: (name: string, loc: Loc) => Promise; diff --git a/src/stx-error.ts b/src/stx-error.ts index a34cacb..7beaebd 100644 --- a/src/stx-error.ts +++ b/src/stx-error.ts @@ -1,4 +1,3 @@ -import { assert } from "./assert"; import { Loc, STX } from "./syntax-structures"; import { isolate, unisolate } from "./zipper"; import indexToPosition from "index-to-position"; @@ -6,6 +5,7 @@ import { codeFrameColumns } from "@babel/code-frame"; import fs from "node:fs/promises"; import { llmap, llreduce } from "./llhelpers"; import { AST, source } from "./ast"; +import { assert } from "./assert"; export class StxError { name: string; @@ -36,7 +36,7 @@ export const in_isolation: ( }; type LibraryManager = { - get_package: (name: string, version: string) => { dir: string } | undefined; + get_module_by_cuid: (cuid: string) => { path: string } | undefined; }; export async function print_stx_error(error: StxError, library_manager: LibraryManager) { @@ -44,15 +44,15 @@ export async function print_stx_error(error: StxError, library_manager: LibraryM if (error.info) console.error(error.info); const ls = loc_src_origins(error.loc.t); for (const x of ls) { - const pkg = library_manager.get_package(x.f.package.name, x.f.package.version); - assert(pkg !== undefined); - const full_path = pkg.dir + "/" + x.f.path; + const mod = library_manager.get_module_by_cuid(x.cuid); + assert(mod !== undefined, `cannot find module with cuid ${x.cuid}`); + const full_path = mod.path; const code = await fs.readFile(full_path, { encoding: "utf8" }); - const pos0 = indexToPosition(code, x.p + 1, { oneBased: true }); - const pos1 = indexToPosition(code, x.e, { oneBased: true }); + const pos0 = typeof x.s === "number" ? indexToPosition(code, x.s + 1, { oneBased: true }) : x.s; + const pos1 = typeof x.e === "number" ? indexToPosition(code, x.e, { oneBased: true }) : x.e; const cf = codeFrameColumns(code, { start: pos0, end: pos1 }, { highlightCode: true }); console.error(cf); - console.error(`In ${full_path}:${x.p}-${x.e}`); + console.error(`In ${full_path}:${x.s}-${x.e}`); } } diff --git a/src/syntax-core-patterns.ts b/src/syntax-core-patterns.ts index 107a2be..51838bd 100644 --- a/src/syntax-core-patterns.ts +++ b/src/syntax-core-patterns.ts @@ -1,5 +1,5 @@ import { assert } from "./assert"; -import { AST, source_file } from "./ast"; +import { AST } from "./ast"; import { LL, llappend, llmap, llreduce, llreverse, ll_to_array } from "./llhelpers"; import { syntax_error } from "./stx-error"; import { @@ -592,13 +592,9 @@ export const core_handlers: { [k: string]: walker } = { define_rewrite_rules, }; -export const core_patterns = (parse: (code: string, source_file: source_file) => AST) => { +export const core_patterns = (parse: (code: string, cuid: string) => AST) => { const pattern = (code: string) => { - const src: source_file = { - package: { name: "rewrite-ts", version: "0.0.0" }, - path: "internal-patterns", - }; - const ast = parse(code, src); + const ast = parse(code, "internal-patterns rewrite-ts 0.0.0"); assert(ast.type === "list" && ast.tag === "program"); const bodies = ast.content; assert(bodies !== null); diff --git a/src/tags.ts b/src/tags.ts index 41ff175..901f066 100644 --- a/src/tags.ts +++ b/src/tags.ts @@ -68,5 +68,8 @@ export type list_tag = | "method_signature" | "parenthesized_type" | "type_predicate" + | "new_expression" + | "throw_statement" | "syntax_list" + | "element_access_expression" | "ERROR"; diff --git a/test-project/.rts/main.rts.json b/test-project/.rts/main.rts.json index bf253cf..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-016", + "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 e7e4287..379024c 100644 --- a/test-project/.rts/main.rts.ts +++ b/test-project/.rts/main.rts.ts @@ -1,5 +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); +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 74f50cf..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-016", + "cookie": "rewrite-ts-021", "imports": [], "exported_identifiers": { "x": [ @@ -65,15 +65,10 @@ "content": "fref", "src": { "type": "origin", - "p": 88, - "e": 93, - "f": { - "package": { - "name": "rewrite-ts-visualized", - "version": "0.0.0" - }, - "path": "test-project/mod.rts" - } + "s": {"line": 5, "column": 28, "offset": 89}, + "e": {"line": 5, "column": 32, "offset": 93}, + "name": "fref", + "cuid": "test-project/mod.rts rewrite-ts-visualized 0.0.0" } } }, @@ -103,15 +98,10 @@ "content": "f", "src": { "type": "origin", - "p": 94, - "e": 96, - "f": { - "package": { - "name": "rewrite-ts-visualized", - "version": "0.0.0" - }, - "path": "test-project/mod.rts" - } + "s": {"line": 5, "column": 40, "offset": 101}, + "e": {"line": 5, "column": 41, "offset": 102}, + "name": "f", + "cuid": "test-project/mod.rts rewrite-ts-visualized 0.0.0" } } } diff --git a/test-project/.rts/mod.rts.ts b/test-project/.rts/mod.rts.ts index e9ab0f9..a73863c 100644 --- a/test-project/.rts/mod.rts.ts +++ b/test-project/.rts/mod.rts.ts @@ -1,3 +1,2 @@ -export const x_1 = 12; -export type t_2 = number; -export const f_3 = 13; +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/sourcemap1.test.rts.json similarity index 69% rename from test-project/.rts/test1.test.rts.json rename to test-project/.rts/sourcemap1.test.rts.json index 5ecdb0a..3130a54 100644 --- a/test-project/.rts/test1.test.rts.json +++ b/test-project/.rts/sourcemap1.test.rts.json @@ -1,6 +1,6 @@ { - "cid": "test-project/test1.test.rts rewrite-ts-visualized 0.0.0", - "cookie": "rewrite-ts-016", + "cid": "test-project/sourcemap1.test.rts rewrite-ts-visualized 0.0.0", + "cookie": "rewrite-ts-021", "imports": [ { "pkg": {"name": "vitest", "version": "2.1.8"}, @@ -8,9 +8,12 @@ } ], "exported_identifiers": {}, - "context": {}, + "context": { + "l3": {"type": "lexical", "name": "err_3"}, + "l4": {"type": "lexical", "name": "trace_4"} + }, "unit": { - "cu_id": "test-project/test1.test.rts rewrite-ts-visualized 0.0.0", + "cu_id": "test-project/sourcemap1.test.rts rewrite-ts-visualized 0.0.0", "store": { "r0": { "type": "rib", @@ -18,7 +21,7 @@ "suite": [ [ [ - "test-project/test1.test.rts rewrite-ts-visualized 0.0.0", + "test-project/sourcemap1.test.rts rewrite-ts-visualized 0.0.0", ["top", null] ], { @@ -30,7 +33,7 @@ "test": [ [ [ - "test-project/test1.test.rts rewrite-ts-visualized 0.0.0", + "test-project/sourcemap1.test.rts rewrite-ts-visualized 0.0.0", ["top", null] ], { @@ -42,7 +45,7 @@ "expect": [ [ [ - "test-project/test1.test.rts rewrite-ts-visualized 0.0.0", + "test-project/sourcemap1.test.rts rewrite-ts-visualized 0.0.0", ["top", null] ], {"cuid": "dist/index.d.ts vitest 2.1.8", "name": "l.globalExpect"} @@ -53,7 +56,7 @@ "suite": [ [ [ - "test-project/test1.test.rts rewrite-ts-visualized 0.0.0", + "test-project/sourcemap1.test.rts rewrite-ts-visualized 0.0.0", ["top", null] ], { @@ -65,7 +68,7 @@ "test": [ [ [ - "test-project/test1.test.rts rewrite-ts-visualized 0.0.0", + "test-project/sourcemap1.test.rts rewrite-ts-visualized 0.0.0", ["top", null] ], { diff --git a/test-project/.rts/sourcemap1.test.rts.ts b/test-project/.rts/sourcemap1.test.rts.ts new file mode 100644 index 0000000..87bbdc1 --- /dev/null +++ b/test-project/.rts/sourcemap1.test.rts.ts @@ -0,0 +1,2 @@ +import { suite as suite_1 , test as test_2 , expect as expect_5 } from "vitest" ; suite_1 ( "source mapping for errors" , ( ( ) => { test_2 ( "simple error" , ( ( ) => { const err_3 = new Error ( "HERE" ) ; const trace_4 = ( ( err_3 . stack || "" ) ) . split ( "\n" ) ; expect_5 ( trace_4 [ 1 ] ) . toMatch ( /\/sourcemap1\.test\.rts:5:17$/ ) ; } ) ) ; } ) ) ; +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NvdXJjZW1hcDEudGVzdC5ydHMiXSwibmFtZXMiOlsic3VpdGUiLCJ0ZXN0IiwiZXJyIiwiRXJyb3IiLCJ0cmFjZSIsInN0YWNrIiwic3BsaXQiLCJleHBlY3QiLCJ0b01hdGNoIl0sIm1hcHBpbmdzIjoia0ZBRUFBLFFBQUssRUFBQyw0QkFBMkIsSUFBRSxFQUFDLEVBQUUsR0FBRyxFQUN2Q0MsT0FBSSxFQUFDLGVBQWMsSUFBRSxFQUFDLEVBQUUsR0FBRyxFQUN6QixNQUFNQyxNQUFJLEVBQUUsSUFBSUMsTUFBSyxFQUFDLE9BQU0sSUFHNUIsTUFBTUMsUUFBTSxFQUFFLElBQUNGLE1BQUcsRUFBQ0csTUFBTSxHQUFHLEtBQUUsRUFBQyxFQUFDQyxNQUFLLEVBQUMsS0FBSSxJQUMxQ0MsU0FBTSxFQUFDSCxRQUFLLEVBQUMsRUFBQyxFQUFDLEVBQUMsRUFBQ0ksUUFBTyxFQUFDLGdDQUErQixJQUMxRCxJQUFDLElBQ0gsSUFBQyIsImZpbGUiOiJzb3VyY2VtYXAxLnRlc3QucnRzLnRzIn0= diff --git a/test-project/.rts/test1.test.rts.ts b/test-project/.rts/test1.test.rts.ts deleted file mode 100644 index 87748b9..0000000 --- a/test-project/.rts/test1.test.rts.ts +++ /dev/null @@ -1,6 +0,0 @@ -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); - }); -}); diff --git a/test-project/mod.rts b/test-project/mod.rts index c4fd489..bd0af2f 100644 --- a/test-project/mod.rts +++ b/test-project/mod.rts @@ -3,6 +3,6 @@ export type t = number; const f = 13; -define_rewrite_rules([fref, fref, f]); +define_rewrite_rules([fref, fref, f]); export {fref}; diff --git a/test-project/sourcemap1.test.rts b/test-project/sourcemap1.test.rts new file mode 100644 index 0000000..3609e02 --- /dev/null +++ b/test-project/sourcemap1.test.rts @@ -0,0 +1,11 @@ +import { expect, test, suite } from "vitest"; + +suite("source mapping for errors", () => { + test("simple error", () => { + const err = new Error("HERE"); + // ^--- error origin + // put cursor on the new keyword and check that position is 5:17 + const trace = (err.stack || "").split("\n"); + expect(trace[1]).toMatch(/\/sourcemap1\.test\.rts:5:17$/); + }); +}); diff --git a/test-project/test1.test.rts.ts b/test-project/sourcemap1.test.rts.ts similarity index 54% rename from test-project/test1.test.rts.ts rename to test-project/sourcemap1.test.rts.ts index 37cfbd3..fbb3b49 100644 --- a/test-project/test1.test.rts.ts +++ b/test-project/sourcemap1.test.rts.ts @@ -1,2 +1,2 @@ /* This is an automatically generated file. Do not edit. */ -import { } from "./.rts/test1.test.rts.ts"; +import { } from "./.rts/sourcemap1.test.rts.ts"; diff --git a/test-project/test1.test.rts b/test-project/test1.test.rts deleted file mode 100644 index 25c46f8..0000000 --- a/test-project/test1.test.rts +++ /dev/null @@ -1,7 +0,0 @@ -import { expect, test, suite } from "vitest"; - -suite("sample test", () => { - test("should work", () => { - expect(5).toBe(5); - }); -}); diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo index a4be90d..e3e80b9 100644 --- a/tsconfig.tsbuildinfo +++ b/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./files.test.ts","./generate-stdlibs.test.ts","./vite.config.ts","./vitest.config.ts","./rtsc/compile-all.ts","./rtsc/watch.ts","./src/assert.ts","./src/ast.ts","./src/data.ts","./src/expander.ts","./src/fs-helpers.ts","./src/generate-stdlibs.ts","./src/generated-stdlibs.ts","./src/global-module.ts","./src/library-manager.ts","./src/llhelpers.ts","./src/parse-dts.ts","./src/parse.ts","./src/pprint.ts","./src/preexpand-handlers.ts","./src/preexpand-helpers.ts","./src/proxy-code.ts","./src/serialize.ts","./src/stx-error.ts","./src/stx.ts","./src/syntax-core-patterns.ts","./src/syntax-structures.ts","./src/tags.ts","./src/zipper.ts","./test-project/index.ts","./test-project/main.rts.ts","./test-project/mod.rts.ts","./test-project/test1.test.rts.ts","./ui/astvis.tsx","./ui/app.tsx","./ui/editor.tsx","./ui/main.tsx","./ui/vite-env.d.ts"],"version":"5.7.2"} \ No newline at end of file +{"root":["./files.test.ts","./generate-stdlibs.test.ts","./vite.config.ts","./vitest.config.ts","./rtsc/compile-all.ts","./rtsc/watch.ts","./src/assert.ts","./src/ast.ts","./src/data.ts","./src/expander.ts","./src/fs-helpers.ts","./src/generate-stdlibs.ts","./src/generated-stdlibs.ts","./src/global-module.ts","./src/library-manager.ts","./src/llhelpers.ts","./src/parse-dts.ts","./src/parse.ts","./src/pprint.ts","./src/preexpand-handlers.ts","./src/preexpand-helpers.ts","./src/proxy-code.ts","./src/serialize.ts","./src/stx-error.ts","./src/stx.ts","./src/syntax-core-patterns.ts","./src/syntax-structures.ts","./src/tags.ts","./src/zipper.ts","./test-project/index.ts","./test-project/main.rts.ts","./test-project/mod.rts.ts","./test-project/sourcemap1.test.rts.ts","./ui/astvis.tsx","./ui/app.tsx","./ui/editor.tsx","./ui/main.tsx","./ui/vite-env.d.ts"],"version":"5.7.2"} \ No newline at end of file diff --git a/ui/App.tsx b/ui/App.tsx index 4286368..f817673 100644 --- a/ui/App.tsx +++ b/ui/App.tsx @@ -11,7 +11,6 @@ import { parse } from "../src/parse"; import { pprint } from "../src/pprint"; import { StxError, syntax_error } from "../src/stx-error"; import { preexpand_helpers } from "../src/preexpand-helpers"; -import { source_file } from "../src/ast"; import { get_globals, init_global_context } from "../src/global-module"; type ExampleProps = { @@ -92,11 +91,8 @@ function StepperView({ step, step_number }: { step: Step; step_number: number }) function Example({ code, onChange }: ExampleProps) { type expand = (helpers: preexpand_helpers) => Promise<{ loc: Loc }>; function init_state(code: string): [State, expand] { - const source_file: source_file = { - package: { name: "@rewrite-ts/example", version: "0.0.0" }, - path: "example", - }; - const [loc0, expand] = initial_step(parse(code, source_file), "example", ["es2024.full"]); + const cuid = "example"; + const [loc0, expand] = initial_step(parse(code, cuid), cuid, ["es2024.full"]); return [initial_state(loc0), expand]; } const globals = get_globals("es2024.full"); @@ -162,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), [display_step]); + const code_to_display = useMemo( + () => pprint(display_step.loc, { prettify: true }), + [display_step], + ); return (