nomen-lang 0.0.5 → 0.0.7
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/dist/index.mjs +18650 -9439
- package/package.json +4 -7
- package/src/bench_loop.nm +113 -0
- package/src/docs.ts +267 -0
- package/src/format_errors.ts +23 -9
- package/src/index.ts +163 -25
- package/src/test.ts +518 -0
- package/test/fixtures/build/calc.test +0 -0
- package/test/fixtures/build/calc.test.c +969 -0
- package/test/fixtures/build/main.h +170 -0
- package/test/fixtures/calc.test.nm +32 -0
- package/vite.config.ts +7 -0
- package/dist/index.d.mts +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nomen-lang",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "The CLI for the Nomen programming language.",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"license": "ISC",
|
|
@@ -19,16 +19,13 @@
|
|
|
19
19
|
"go": "tsx src/index.ts",
|
|
20
20
|
"register": "pnpm build && pnpm add -g ."
|
|
21
21
|
},
|
|
22
|
-
"dependencies": {
|
|
23
|
-
"chokidar": "^5.0.0",
|
|
24
|
-
"yargs": "^18.0.0"
|
|
25
|
-
},
|
|
26
22
|
"devDependencies": {
|
|
27
|
-
"@types/node": "^26.1.
|
|
23
|
+
"@types/node": "^26.1.2",
|
|
28
24
|
"@types/yargs": "^17.0.35",
|
|
25
|
+
"chokidar": "^5.0.0",
|
|
29
26
|
"tsx": "^4.23.1",
|
|
30
27
|
"typescript": "^7.0.2",
|
|
31
28
|
"vite-plus": "catalog:",
|
|
32
|
-
"
|
|
29
|
+
"yargs": "^18.1.0"
|
|
33
30
|
}
|
|
34
31
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Template for one benchmark's timing loop. The CLI substitutes __NAME__,
|
|
2
|
+
// __TARGET__ and __N__ and concatenates this after the test file's source
|
|
3
|
+
// (so it is user code and can call the test file's functions). Kept as a
|
|
4
|
+
// separate .nm file (not a TS template literal) because Nomen's generic syntax
|
|
5
|
+
// (List<int>) is not valid TypeScript and would break the CLI's own build.
|
|
6
|
+
//
|
|
7
|
+
// Placeholders use the leading-AND-trailing `__NAME__` convention so they don't
|
|
8
|
+
// collide with the `__w` / `__i` / `__samples` locals (leading-only).
|
|
9
|
+
//
|
|
10
|
+
// min / max / sum / sum-of-squares are tracked as running aggregates during
|
|
11
|
+
// sampling, so mean and stddev need no `List.at` calls at all. Only the median
|
|
12
|
+
// needs sorted data, so the samples are insertion-sorted afterwards. Every
|
|
13
|
+
// `List.at` / `List.set` is guarded by `idx >= 0 && idx < __samples.length` —
|
|
14
|
+
// the exact bound `List.at`'s constraint requires — because the constraint
|
|
15
|
+
// checker treats an unverifiable index as an out-of-bounds risk (the backends
|
|
16
|
+
// emit unchecked strided loads).
|
|
17
|
+
func bench_loop___NAME__ = (ref Tester t) {
|
|
18
|
+
// Warm up so the first samples don't pay for cold caches / lazy binding.
|
|
19
|
+
var int __w = 0
|
|
20
|
+
while __w < 8 {
|
|
21
|
+
__TARGET__()
|
|
22
|
+
__w += 1
|
|
23
|
+
}
|
|
24
|
+
// Collect __N__ samples, tracking running aggregates for every statistic
|
|
25
|
+
// except the median (which needs sorted data).
|
|
26
|
+
var List<int> __samples = List<int>()
|
|
27
|
+
var int __sum = 0
|
|
28
|
+
var float __sum_sq = 0.0
|
|
29
|
+
var int __min = 0
|
|
30
|
+
var int __max = 0
|
|
31
|
+
var int __i = 0
|
|
32
|
+
while __i < __N__ {
|
|
33
|
+
const uint64 __t0 = Time.now_ns()
|
|
34
|
+
__TARGET__()
|
|
35
|
+
const uint64 __t1 = Time.now_ns()
|
|
36
|
+
const int __dt = (__t1 - __t0) as int
|
|
37
|
+
__samples.push(__dt)
|
|
38
|
+
__sum += __dt
|
|
39
|
+
__sum_sq = __sum_sq + (__dt as float) * (__dt as float)
|
|
40
|
+
if __i == 0 {
|
|
41
|
+
__min = __dt
|
|
42
|
+
__max = __dt
|
|
43
|
+
}
|
|
44
|
+
if __i > 0 {
|
|
45
|
+
if __dt < __min {
|
|
46
|
+
__min = __dt
|
|
47
|
+
}
|
|
48
|
+
if __dt > __max {
|
|
49
|
+
__max = __dt
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
__i += 1
|
|
53
|
+
}
|
|
54
|
+
const int __n = __samples.length
|
|
55
|
+
if __n > 0 {
|
|
56
|
+
const float __fn = __n as float
|
|
57
|
+
const float __mean = (__sum as float) / __fn
|
|
58
|
+
var float __variance = __sum_sq / __fn - __mean * __mean
|
|
59
|
+
if __variance < 0.0 {
|
|
60
|
+
__variance = 0.0
|
|
61
|
+
}
|
|
62
|
+
const float __stddev = Math.sqrt(__variance)
|
|
63
|
+
// Insertion sort the samples so we can pick a median. `length` is
|
|
64
|
+
// stable throughout (only `set` mutates), so every access is guarded
|
|
65
|
+
// against `__samples.length` directly.
|
|
66
|
+
var int __s = 1
|
|
67
|
+
while __s < __n {
|
|
68
|
+
if __s >= 0 && __s < __samples.length {
|
|
69
|
+
const int __v = __samples.at(__s)
|
|
70
|
+
var int __j = __s
|
|
71
|
+
var bool __more = true
|
|
72
|
+
while __more {
|
|
73
|
+
var int __pm1 = __j - 1
|
|
74
|
+
if __pm1 >= 0 && __pm1 < __samples.length {
|
|
75
|
+
const int __prev = __samples.at(__pm1)
|
|
76
|
+
if __prev > __v {
|
|
77
|
+
if __j >= 0 && __j < __samples.length {
|
|
78
|
+
__samples.set(__j, __prev)
|
|
79
|
+
}
|
|
80
|
+
__j -= 1
|
|
81
|
+
} else {
|
|
82
|
+
__more = false
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
__more = false
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if __j >= 0 && __j < __samples.length {
|
|
89
|
+
__samples.set(__j, __v)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
__s += 1
|
|
93
|
+
}
|
|
94
|
+
// Median of the sorted samples. Indices are precomputed into locals so
|
|
95
|
+
// the guards are simple variable bounds.
|
|
96
|
+
var int __median = __min
|
|
97
|
+
var int __hi = __n / 2
|
|
98
|
+
if __hi >= 0 && __hi < __samples.length {
|
|
99
|
+
__median = __samples.at(__hi)
|
|
100
|
+
}
|
|
101
|
+
if __n % 2 == 0 {
|
|
102
|
+
var int __lo = __n / 2 - 1
|
|
103
|
+
if __lo >= 0 && __lo < __samples.length {
|
|
104
|
+
if __hi >= 0 && __hi < __samples.length {
|
|
105
|
+
var int __a = __samples.at(__lo)
|
|
106
|
+
var int __b = __samples.at(__hi)
|
|
107
|
+
__median = (__a + __b) / 2
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
t.record_bench(t.bench_label, __n, __min, __median, __max, __mean, __stddev)
|
|
112
|
+
}
|
|
113
|
+
}
|
package/src/docs.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { get_library, resolve_export_files, type Library } from "../../src/lib.ts";
|
|
5
|
+
import type BaseNode from "../../src/nodes/BaseNode.ts";
|
|
6
|
+
import type BitsetNode from "../../src/nodes/BitsetNode.ts";
|
|
7
|
+
import type EnumNode from "../../src/nodes/EnumNode.ts";
|
|
8
|
+
import type FunctionNode from "../../src/nodes/FunctionNode.ts";
|
|
9
|
+
import type StructNode from "../../src/nodes/StructNode.ts";
|
|
10
|
+
import type TraitNode from "../../src/nodes/TraitNode.ts";
|
|
11
|
+
import type Type from "../../src/nodes/Type.ts";
|
|
12
|
+
import parse from "../../src/parse.ts";
|
|
13
|
+
|
|
14
|
+
interface DocItem {
|
|
15
|
+
kind: string;
|
|
16
|
+
name: string;
|
|
17
|
+
signature?: string;
|
|
18
|
+
doc?: string;
|
|
19
|
+
node: BaseNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `nomen docs`: parse every `.nm` file in scope, then write one markdown file per
|
|
24
|
+
* source file into `<root>/docs/`, mirroring the source tree. Emits a warning
|
|
25
|
+
* for each top-level `pub` item that has no documentation comment.
|
|
26
|
+
*/
|
|
27
|
+
export function run_docs(explicit_in?: string): void {
|
|
28
|
+
const root = path.resolve(explicit_in || process.cwd());
|
|
29
|
+
const { files, library, docs_root } = gather_doc_files(root);
|
|
30
|
+
if (!files.length) {
|
|
31
|
+
console.log(`No .nm files found under ${root}`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let warnings = 0;
|
|
36
|
+
let written = 0;
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
const text = fs.readFileSync(file, "utf8");
|
|
39
|
+
let parsed;
|
|
40
|
+
try {
|
|
41
|
+
parsed = parse(text, library, file);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
44
|
+
console.log(` warning: could not parse ${path.relative(root, file)} (${msg})`);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const items = collect_items(parsed.root, text.length);
|
|
48
|
+
if (!items.length) continue;
|
|
49
|
+
|
|
50
|
+
const rel = path.relative(docs_root, file).replace(/\.nm$/, ".md");
|
|
51
|
+
const out_path = path.join(docs_root, "docs", rel);
|
|
52
|
+
const { markdown, warns } = render_file(file, items, text);
|
|
53
|
+
warnings += warns;
|
|
54
|
+
fs.mkdirSync(path.dirname(out_path), { recursive: true });
|
|
55
|
+
fs.writeFileSync(out_path, markdown);
|
|
56
|
+
written++;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(
|
|
60
|
+
`Wrote ${written} doc file(s) to ${path.join(docs_root, "docs")}` +
|
|
61
|
+
(warnings ? ` with ${warnings} warning(s)` : ""),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Decide which files to document. A package.jsonc with `exports` is a library
|
|
66
|
+
// (document every exported file); otherwise document the resolved input file
|
|
67
|
+
// alongside its module siblings.
|
|
68
|
+
function gather_doc_files(root: string): {
|
|
69
|
+
files: string[];
|
|
70
|
+
library: Library | undefined;
|
|
71
|
+
docs_root: string;
|
|
72
|
+
} {
|
|
73
|
+
const config_path = path.join(root, "package.jsonc");
|
|
74
|
+
if (fs.existsSync(config_path)) {
|
|
75
|
+
try {
|
|
76
|
+
const raw = fs.readFileSync(config_path, "utf8");
|
|
77
|
+
const json = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
78
|
+
const parsed = JSON.parse(json);
|
|
79
|
+
if (parsed.exports) {
|
|
80
|
+
let files: string[] = [];
|
|
81
|
+
for (const pattern of Object.values(parsed.exports) as string[]) {
|
|
82
|
+
files = files.concat(resolve_export_files(root, pattern));
|
|
83
|
+
}
|
|
84
|
+
return { files, library: get_library(root), docs_root: root };
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
// fall through
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// App / single-file mode: one module folder of siblings.
|
|
92
|
+
const dir = fs.existsSync(root) && fs.lstatSync(root).isDirectory() ? root : path.dirname(root);
|
|
93
|
+
let files: string[] = [];
|
|
94
|
+
try {
|
|
95
|
+
files = fs
|
|
96
|
+
.readdirSync(dir)
|
|
97
|
+
.filter((f) => f.endsWith(".nm"))
|
|
98
|
+
.map((f) => path.join(dir, f));
|
|
99
|
+
} catch {
|
|
100
|
+
// unreadable
|
|
101
|
+
}
|
|
102
|
+
return { files, library: resolve_library_for(dir), docs_root: dir };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function resolve_library_for(dir: string): Library | undefined {
|
|
106
|
+
let d = dir;
|
|
107
|
+
for (let i = 0; i < 20; i++) {
|
|
108
|
+
const config_path = path.join(d, "package.jsonc");
|
|
109
|
+
if (fs.existsSync(config_path)) {
|
|
110
|
+
try {
|
|
111
|
+
const raw = fs.readFileSync(config_path, "utf8");
|
|
112
|
+
const json = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
113
|
+
const parsed = JSON.parse(json);
|
|
114
|
+
if (parsed.exports) return get_library(d);
|
|
115
|
+
if (parsed.imports?.System) return get_library(path.resolve(d, parsed.imports.System));
|
|
116
|
+
} catch {
|
|
117
|
+
// ignore
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const lib_config = path.join(d, "core", "package.jsonc");
|
|
121
|
+
if (fs.existsSync(lib_config)) return get_library(path.join(d, "core"));
|
|
122
|
+
const parent = path.dirname(d);
|
|
123
|
+
if (parent === d) break;
|
|
124
|
+
d = parent;
|
|
125
|
+
}
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Collect this file's own top-level pub declarations (those starting before the
|
|
130
|
+
// appended library source — i.e. defined in `text`, not pulled in for linking).
|
|
131
|
+
function collect_items(root_node: BaseNode, user_length: number): DocItem[] {
|
|
132
|
+
const items: DocItem[] = [];
|
|
133
|
+
const statements = (root_node as unknown as { statements: BaseNode[] }).statements;
|
|
134
|
+
for (const stmt of statements) {
|
|
135
|
+
if (stmt.start >= user_length) continue;
|
|
136
|
+
switch (stmt.node_type) {
|
|
137
|
+
case "struct": {
|
|
138
|
+
const s = stmt as unknown as StructNode;
|
|
139
|
+
if (s.visibility !== "pub") break;
|
|
140
|
+
items.push({
|
|
141
|
+
kind: s.is_class ? "class" : "struct",
|
|
142
|
+
name: s.name,
|
|
143
|
+
doc: s.doc,
|
|
144
|
+
node: s,
|
|
145
|
+
});
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "trait": {
|
|
149
|
+
const t = stmt as unknown as TraitNode;
|
|
150
|
+
if (t.visibility !== "pub") break;
|
|
151
|
+
items.push({ kind: "trait", name: t.name, doc: t.doc, node: t });
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case "enum": {
|
|
155
|
+
const e = stmt as unknown as EnumNode;
|
|
156
|
+
if (e.visibility !== "pub") break;
|
|
157
|
+
items.push({ kind: "enum", name: e.name, doc: e.doc, node: e });
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case "bitset": {
|
|
161
|
+
const b = stmt as unknown as BitsetNode;
|
|
162
|
+
if (b.visibility !== "pub") break;
|
|
163
|
+
items.push({ kind: "bitset", name: b.name, doc: b.doc, node: b });
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case "func": {
|
|
167
|
+
const f = stmt as unknown as FunctionNode;
|
|
168
|
+
if (f.visibility !== "pub") break;
|
|
169
|
+
items.push({
|
|
170
|
+
kind: "func",
|
|
171
|
+
name: f.name,
|
|
172
|
+
signature: signature_of(f),
|
|
173
|
+
doc: f.doc,
|
|
174
|
+
node: f,
|
|
175
|
+
});
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return items;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function render_file(
|
|
184
|
+
file: string,
|
|
185
|
+
items: DocItem[],
|
|
186
|
+
source: string,
|
|
187
|
+
): { markdown: string; warns: number } {
|
|
188
|
+
const lines: string[] = [];
|
|
189
|
+
const title = path.basename(file, ".nm");
|
|
190
|
+
lines.push(`# ${title}`);
|
|
191
|
+
lines.push("");
|
|
192
|
+
|
|
193
|
+
let warns = 0;
|
|
194
|
+
for (const item of items) {
|
|
195
|
+
const where = `${path.basename(file)}:${line_of(source, item.node.start)}`;
|
|
196
|
+
lines.push(`## \`${heading_of(item)}\``);
|
|
197
|
+
lines.push("");
|
|
198
|
+
if (item.doc) {
|
|
199
|
+
lines.push(item.doc);
|
|
200
|
+
} else {
|
|
201
|
+
lines.push(`_No documentation._`);
|
|
202
|
+
console.log(` warning: ${item.kind} \`${item.name}\` has no doc comment (${where})`);
|
|
203
|
+
warns++;
|
|
204
|
+
}
|
|
205
|
+
lines.push("");
|
|
206
|
+
|
|
207
|
+
// Struct/trait: list pub members.
|
|
208
|
+
const members = members_of(item.node);
|
|
209
|
+
if (members.length) {
|
|
210
|
+
lines.push("**Members:**");
|
|
211
|
+
lines.push("");
|
|
212
|
+
for (const m of members) {
|
|
213
|
+
const sig = signature_of(m);
|
|
214
|
+
const doc = m.doc ? ` — ${m.doc.split("\n")[0]}` : "";
|
|
215
|
+
lines.push(`- \`${sig}\`${doc}`);
|
|
216
|
+
}
|
|
217
|
+
lines.push("");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return { markdown: lines.join("\n"), warns };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function heading_of(item: DocItem): string {
|
|
224
|
+
if (item.kind === "func") return `func ${item.name}${item.signature ?? ""}`;
|
|
225
|
+
const generic = generic_params_of(item.node);
|
|
226
|
+
return `${item.kind} ${item.name}${generic}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Render a function signature like `name(type name, …) -> Ret`, omitting the
|
|
230
|
+
// implicit `self` parameter of methods.
|
|
231
|
+
function signature_of(fn: FunctionNode): string {
|
|
232
|
+
const params = fn.params
|
|
233
|
+
.filter((p) => !p.is_self_param)
|
|
234
|
+
.map((p) => `${render_type(p.type)}${p.name ? " " + p.name : ""}`);
|
|
235
|
+
let sig = `${fn.name}(${params.join(", ")})`;
|
|
236
|
+
if (fn.return_type?.name) sig += ` -> ${render_type(fn.return_type)}`;
|
|
237
|
+
return sig;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function members_of(node: BaseNode): FunctionNode[] {
|
|
241
|
+
if (node.node_type === "struct" || node.node_type === "trait") {
|
|
242
|
+
const n = node as unknown as { functions: FunctionNode[] };
|
|
243
|
+
return n.functions.filter((f) => f.visibility === "pub" && !f.name.startsWith("#"));
|
|
244
|
+
}
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function generic_params_of(node: BaseNode): string {
|
|
249
|
+
const n = node as unknown as { type_params?: string[] };
|
|
250
|
+
if (n.type_params && n.type_params.length) return `<${n.type_params.join(", ")}>`;
|
|
251
|
+
return "";
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function render_type(t: Type): string {
|
|
255
|
+
let s = t.name;
|
|
256
|
+
if (t.type_args?.length) s += `<${t.type_args.map(render_type).join(", ")}>`;
|
|
257
|
+
if (t.is_ref) s = `ref ${s}`;
|
|
258
|
+
if (t.is_view) s = `view ${s}`;
|
|
259
|
+
if (t.is_array) s += "[]";
|
|
260
|
+
return s;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function line_of(source: string, start: number): number {
|
|
264
|
+
let line = 1;
|
|
265
|
+
for (let i = 0; i < start && i < source.length; i++) if (source[i] === "\n") line++;
|
|
266
|
+
return line;
|
|
267
|
+
}
|
package/src/format_errors.ts
CHANGED
|
@@ -47,32 +47,46 @@ function visual_start(line_text: string, col: number): number {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export default function render_errors(source: string, errors: CompileError[]): string {
|
|
50
|
+
return render_messages(source, errors, "Error");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Render `messages` with a `Warning:`/`Error:` label and matching summary. */
|
|
54
|
+
export function render_warnings(source: string, warnings: CompileError[]): string {
|
|
55
|
+
return render_messages(source, warnings, "Warning");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function render_messages(
|
|
59
|
+
source: string,
|
|
60
|
+
messages: CompileError[],
|
|
61
|
+
severity: "Error" | "Warning",
|
|
62
|
+
): string {
|
|
63
|
+
if (!messages.length) return "";
|
|
50
64
|
const lines = source.split("\n");
|
|
51
65
|
const markers = find_file_markers(source);
|
|
52
66
|
|
|
53
67
|
const blocks: string[] = [];
|
|
54
|
-
for (const
|
|
55
|
-
const line_text = lines[
|
|
68
|
+
for (const message of messages) {
|
|
69
|
+
const line_text = lines[message.line - 1] ?? "";
|
|
56
70
|
|
|
57
71
|
// Map the joined-source line back to the originating file + in-file line.
|
|
58
72
|
let rel_path = "";
|
|
59
|
-
let file_line =
|
|
73
|
+
let file_line = message.line;
|
|
60
74
|
for (let m = markers.length - 1; m >= 0; m--) {
|
|
61
|
-
if (markers[m].joined_line <
|
|
62
|
-
file_line =
|
|
75
|
+
if (markers[m].joined_line < message.line) {
|
|
76
|
+
file_line = message.line - markers[m].joined_line;
|
|
63
77
|
rel_path = path.relative(process.cwd(), markers[m].abs_path) || markers[m].abs_path;
|
|
64
78
|
break;
|
|
65
79
|
}
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
const gutter = " ".repeat(String(file_line).length);
|
|
69
|
-
const col = Math.max(1,
|
|
83
|
+
const col = Math.max(1, message.column);
|
|
70
84
|
const display_line = line_text.replace(/\t/g, " ".repeat(TAB_WIDTH));
|
|
71
85
|
const squiggle = "~".repeat(token_width(line_text, col - 1));
|
|
72
86
|
|
|
73
87
|
blocks.push(
|
|
74
88
|
[
|
|
75
|
-
|
|
89
|
+
`${severity}: ${message.message}`,
|
|
76
90
|
` File: ${rel_path}:${file_line}:${col}`,
|
|
77
91
|
`${gutter} |`,
|
|
78
92
|
`${file_line} | ${display_line}`,
|
|
@@ -81,6 +95,6 @@ export default function render_errors(source: string, errors: CompileError[]): s
|
|
|
81
95
|
);
|
|
82
96
|
}
|
|
83
97
|
|
|
84
|
-
const label =
|
|
85
|
-
return `\n${blocks.join("\n\n")}\n\n${
|
|
98
|
+
const label = messages.length === 1 ? severity.toLowerCase() : `${severity.toLowerCase()}s`;
|
|
99
|
+
return `\n${blocks.join("\n\n")}\n\n${messages.length} ${label} found.\n`;
|
|
86
100
|
}
|