@rhost/testkit 1.5.1 → 1.5.3
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/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +4 -1
- package/dist/cli/init.js.map +1 -1
- package/node_modules/@ursamu/mushcode/.github/workflows/publish.yml +36 -0
- package/node_modules/@ursamu/mushcode/LICENSE +21 -0
- package/node_modules/@ursamu/mushcode/README.md +110 -0
- package/node_modules/@ursamu/mushcode/_dist/mod.d.ts +36 -0
- package/node_modules/@ursamu/mushcode/_dist/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/parser/mod.d.ts +41 -0
- package/node_modules/@ursamu/mushcode/_dist/parser/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/commands.d.ts +15 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/commands.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/deps.d.ts +18 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/deps.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/mod.d.ts +20 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/tags.d.ts +6 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/tags.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/context.d.ts +85 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/context.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/engine.d.ts +48 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/engine.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/mod.d.ts +26 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/stdlib/mod.d.ts +3 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/stdlib/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/lint/mod.d.ts +38 -0
- package/node_modules/@ursamu/mushcode/_dist/src/lint/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/print/mod.d.ts +18 -0
- package/node_modules/@ursamu/mushcode/_dist/src/print/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/print/printer.d.ts +15 -0
- package/node_modules/@ursamu/mushcode/_dist/src/print/printer.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/traverse/mod.d.ts +19 -0
- package/node_modules/@ursamu/mushcode/_dist/src/traverse/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/traverse/transform.d.ts +27 -0
- package/node_modules/@ursamu/mushcode/_dist/src/traverse/transform.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/traverse/walk.d.ts +27 -0
- package/node_modules/@ursamu/mushcode/_dist/src/traverse/walk.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/deno.json +26 -0
- package/node_modules/@ursamu/mushcode/deno.lock +42 -0
- package/node_modules/@ursamu/mushcode/docs/analyze.md +145 -0
- package/node_modules/@ursamu/mushcode/docs/eval.md +312 -0
- package/node_modules/@ursamu/mushcode/docs/lint.md +152 -0
- package/node_modules/@ursamu/mushcode/docs/parser.md +196 -0
- package/node_modules/@ursamu/mushcode/docs/print.md +84 -0
- package/node_modules/@ursamu/mushcode/docs/stdlib.md +418 -0
- package/node_modules/@ursamu/mushcode/docs/traverse.md +167 -0
- package/node_modules/@ursamu/mushcode/grammar/mux-softcode.pegjs +781 -0
- package/node_modules/@ursamu/mushcode/mod.js +44 -0
- package/node_modules/@ursamu/mushcode/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/mod.ts +63 -0
- package/node_modules/@ursamu/mushcode/package.json +38 -0
- package/node_modules/@ursamu/mushcode/parser/mod.js +47 -0
- package/node_modules/@ursamu/mushcode/parser/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/parser/mod.ts +99 -0
- package/node_modules/@ursamu/mushcode/parser/mux-softcode.js +3833 -0
- package/node_modules/@ursamu/mushcode/parser/mux-softcode.mjs +3837 -0
- package/node_modules/@ursamu/mushcode/src/analyze/commands.js +29 -0
- package/node_modules/@ursamu/mushcode/src/analyze/commands.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/analyze/commands.ts +46 -0
- package/node_modules/@ursamu/mushcode/src/analyze/deps.js +45 -0
- package/node_modules/@ursamu/mushcode/src/analyze/deps.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/analyze/deps.ts +51 -0
- package/node_modules/@ursamu/mushcode/src/analyze/mod.js +18 -0
- package/node_modules/@ursamu/mushcode/src/analyze/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/analyze/mod.ts +20 -0
- package/node_modules/@ursamu/mushcode/src/analyze/tags.js +11 -0
- package/node_modules/@ursamu/mushcode/src/analyze/tags.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/analyze/tags.ts +11 -0
- package/node_modules/@ursamu/mushcode/src/eval/context.js +22 -0
- package/node_modules/@ursamu/mushcode/src/eval/context.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/context.ts +177 -0
- package/node_modules/@ursamu/mushcode/src/eval/engine.js +238 -0
- package/node_modules/@ursamu/mushcode/src/eval/engine.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/engine.ts +276 -0
- package/node_modules/@ursamu/mushcode/src/eval/mod.js +25 -0
- package/node_modules/@ursamu/mushcode/src/eval/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/mod.ts +31 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/compare.js +56 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/compare.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/compare.ts +16 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/db.js +91 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/db.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/db.ts +104 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/iter.js +91 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/iter.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/iter.ts +98 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/logic.js +79 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/logic.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/logic.ts +84 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/math.js +120 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/math.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/math.ts +115 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/mod.js +17 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/mod.ts +19 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/register.js +28 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/register.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/register.ts +31 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/string.js +153 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/string.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/string.ts +154 -0
- package/node_modules/@ursamu/mushcode/src/lint/builtin_arities.js +212 -0
- package/node_modules/@ursamu/mushcode/src/lint/builtin_arities.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/lint/builtin_arities.ts +68 -0
- package/node_modules/@ursamu/mushcode/src/lint/mod.js +60 -0
- package/node_modules/@ursamu/mushcode/src/lint/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/lint/mod.ts +96 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/arg_count.js +37 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/arg_count.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/arg_count.ts +44 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/iter_var_outside_iter.js +55 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/iter_var_outside_iter.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/iter_var_outside_iter.ts +60 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/missing_wildcard.js +31 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/missing_wildcard.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/missing_wildcard.ts +40 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/register_before_set.js +59 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/register_before_set.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/register_before_set.ts +64 -0
- package/node_modules/@ursamu/mushcode/src/print/lock_printer.js +43 -0
- package/node_modules/@ursamu/mushcode/src/print/lock_printer.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/print/lock_printer.ts +41 -0
- package/node_modules/@ursamu/mushcode/src/print/mod.js +17 -0
- package/node_modules/@ursamu/mushcode/src/print/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/print/mod.ts +18 -0
- package/node_modules/@ursamu/mushcode/src/print/printer.js +91 -0
- package/node_modules/@ursamu/mushcode/src/print/printer.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/print/printer.ts +132 -0
- package/node_modules/@ursamu/mushcode/src/traverse/child_slots.js +129 -0
- package/node_modules/@ursamu/mushcode/src/traverse/child_slots.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/traverse/child_slots.ts +51 -0
- package/node_modules/@ursamu/mushcode/src/traverse/mod.js +17 -0
- package/node_modules/@ursamu/mushcode/src/traverse/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/traverse/mod.ts +19 -0
- package/node_modules/@ursamu/mushcode/src/traverse/transform.js +70 -0
- package/node_modules/@ursamu/mushcode/src/traverse/transform.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/traverse/transform.ts +84 -0
- package/node_modules/@ursamu/mushcode/src/traverse/walk.js +55 -0
- package/node_modules/@ursamu/mushcode/src/traverse/walk.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/traverse/walk.ts +82 -0
- package/node_modules/@ursamu/mushcode/tests/01-literals.test.ts +105 -0
- package/node_modules/@ursamu/mushcode/tests/02-substitutions.test.ts +145 -0
- package/node_modules/@ursamu/mushcode/tests/03-function-calls.test.ts +184 -0
- package/node_modules/@ursamu/mushcode/tests/04-eval-blocks.test.ts +110 -0
- package/node_modules/@ursamu/mushcode/tests/05-braced-strings.test.ts +119 -0
- package/node_modules/@ursamu/mushcode/tests/06-commands.test.ts +222 -0
- package/node_modules/@ursamu/mushcode/tests/07-dollar-patterns.test.ts +156 -0
- package/node_modules/@ursamu/mushcode/tests/08-lock-expressions.test.ts +159 -0
- package/node_modules/@ursamu/mushcode/tests/09-edge-cases.test.ts +162 -0
- package/node_modules/@ursamu/mushcode/tests/10-regression.test.ts +211 -0
- package/node_modules/@ursamu/mushcode/tests/11-tags.test.ts +357 -0
- package/node_modules/@ursamu/mushcode/tests/12-locations.test.ts +162 -0
- package/node_modules/@ursamu/mushcode/tests/13-eval.test.ts +389 -0
- package/node_modules/@ursamu/mushcode/tests/analyze.test.ts +194 -0
- package/node_modules/@ursamu/mushcode/tests/helpers.ts +69 -0
- package/node_modules/@ursamu/mushcode/tests/lint.test.ts +232 -0
- package/node_modules/@ursamu/mushcode/tests/print.test.ts +204 -0
- package/node_modules/@ursamu/mushcode/tests/traverse.test.ts +211 -0
- package/package.json +10 -1
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Lint — all four rules: positive and negative cases
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { assertEquals } from "jsr:@std/assert@^1";
|
|
6
|
+
import { describe, it } from "jsr:/@std/testing@^1/bdd";
|
|
7
|
+
import { parse } from "../parser/mod.ts";
|
|
8
|
+
import type { ASTNode } from "../parser/mod.ts";
|
|
9
|
+
import { lint, RULES } from "../src/lint/mod.ts";
|
|
10
|
+
import type { Diagnostic } from "../src/lint/mod.ts";
|
|
11
|
+
|
|
12
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
function p(text: string): ASTNode {
|
|
15
|
+
return parse(text, "Start");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function rulesOf(diags: Diagnostic[]): string[] {
|
|
19
|
+
return diags.map(d => d.rule);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ── missing-wildcard ────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
describe("missing-wildcard", () => {
|
|
25
|
+
it("no diagnostic when wildcards match usage", () => {
|
|
26
|
+
const ast = p("$+finger *:@pemit %#=%0");
|
|
27
|
+
const diags = lint(ast, { rules: ["missing-wildcard"] });
|
|
28
|
+
assertEquals(diags.length, 0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("no diagnostic when %1 is used and pattern has one wildcard", () => {
|
|
32
|
+
const ast = p("$say *:@pemit %#=%1");
|
|
33
|
+
const diags = lint(ast, { rules: ["missing-wildcard"] });
|
|
34
|
+
assertEquals(diags.length, 0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("warns when %1 is used but pattern has no wildcards", () => {
|
|
38
|
+
const ast = p("$look:@pemit %#=%1");
|
|
39
|
+
const diags = lint(ast, { rules: ["missing-wildcard"] });
|
|
40
|
+
assertEquals(diags.length, 1);
|
|
41
|
+
assertEquals(diags[0].rule, "missing-wildcard");
|
|
42
|
+
assertEquals(diags[0].severity, "warning");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("warns when %2 is used but pattern has only one wildcard", () => {
|
|
46
|
+
const ast = p("$cmd *:@pemit %#=%2");
|
|
47
|
+
const diags = lint(ast, { rules: ["missing-wildcard"] });
|
|
48
|
+
assertEquals(diags.length, 1);
|
|
49
|
+
assertEquals(diags[0].rule, "missing-wildcard");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("no diagnostic for %0 even with no wildcards (%0 is always available)", () => {
|
|
53
|
+
const ast = p("$look:@pemit %#=%0");
|
|
54
|
+
const diags = lint(ast, { rules: ["missing-wildcard"] });
|
|
55
|
+
assertEquals(diags.length, 0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("warns for ListenPattern too", () => {
|
|
59
|
+
const ast = p("^hello:@pemit %#=%1");
|
|
60
|
+
const diags = lint(ast, { rules: ["missing-wildcard"] });
|
|
61
|
+
assertEquals(diags.length, 1);
|
|
62
|
+
assertEquals(diags[0].rule, "missing-wildcard");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("no false positive when action uses no positional subs", () => {
|
|
66
|
+
const ast = p("$look:@pemit %#=hello");
|
|
67
|
+
const diags = lint(ast, { rules: ["missing-wildcard"] });
|
|
68
|
+
assertEquals(diags.length, 0);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ── iter-var-outside-iter ─────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
describe("iter-var-outside-iter", () => {
|
|
75
|
+
it("## inside iter() — no diagnostic", () => {
|
|
76
|
+
const ast = p("[iter(lwho(),## )]");
|
|
77
|
+
const diags = lint(ast, { rules: ["iter-var-outside-iter"] });
|
|
78
|
+
assertEquals(diags.length, 0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("## inside map() — no diagnostic", () => {
|
|
82
|
+
const ast = p("[map(myattr,##)]");
|
|
83
|
+
const diags = lint(ast, { rules: ["iter-var-outside-iter"] });
|
|
84
|
+
assertEquals(diags.length, 0);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("## inside @dolist — no diagnostic", () => {
|
|
88
|
+
const ast = p("@dolist lwho()={@pemit ##=hi}");
|
|
89
|
+
const diags = lint(ast, { rules: ["iter-var-outside-iter"] });
|
|
90
|
+
assertEquals(diags.length, 0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("## outside iter — warns", () => {
|
|
94
|
+
const ast = p("[pemit(%#,##)]");
|
|
95
|
+
const diags = lint(ast, { rules: ["iter-var-outside-iter"] });
|
|
96
|
+
assertEquals(diags.length, 1);
|
|
97
|
+
assertEquals(diags[0].rule, "iter-var-outside-iter");
|
|
98
|
+
assertEquals(diags[0].severity, "warning");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("#@ outside iter — warns", () => {
|
|
102
|
+
const ast = p("[pemit(%#,#@)]");
|
|
103
|
+
const diags = lint(ast, { rules: ["iter-var-outside-iter"] });
|
|
104
|
+
assertEquals(diags.length, 1);
|
|
105
|
+
assertEquals(diags[0].rule, "iter-var-outside-iter");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("bare ## in attribute value — warns", () => {
|
|
109
|
+
const ast = p("&ATTR me=##");
|
|
110
|
+
const diags = lint(ast, { rules: ["iter-var-outside-iter"] });
|
|
111
|
+
assertEquals(diags.length, 1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("nested iter() — ## valid at both levels", () => {
|
|
115
|
+
const ast = p("[iter(iter(inner,##),##)]");
|
|
116
|
+
const diags = lint(ast, { rules: ["iter-var-outside-iter"] });
|
|
117
|
+
assertEquals(diags.length, 0);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ── arg-count ──────────────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
describe("arg-count", () => {
|
|
124
|
+
it("no diagnostic for correct arg counts", () => {
|
|
125
|
+
const ast = p("[add(1,2)]");
|
|
126
|
+
const diags = lint(ast, { rules: ["arg-count"] });
|
|
127
|
+
assertEquals(diags.length, 0);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("warns when too few args", () => {
|
|
131
|
+
// div() requires exactly 2 args
|
|
132
|
+
const ast = p("[div(1)]");
|
|
133
|
+
const diags = lint(ast, { rules: ["arg-count"] });
|
|
134
|
+
assertEquals(diags.length, 1);
|
|
135
|
+
assertEquals(diags[0].rule, "arg-count");
|
|
136
|
+
assertEquals(diags[0].severity, "warning");
|
|
137
|
+
assertEquals(diags[0].message.includes("at least"), true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("warns when too many args", () => {
|
|
141
|
+
// strlen() accepts only 1 arg
|
|
142
|
+
const ast = p("[strlen(foo,bar)]");
|
|
143
|
+
const diags = lint(ast, { rules: ["arg-count"] });
|
|
144
|
+
assertEquals(diags.length, 1);
|
|
145
|
+
assertEquals(diags[0].rule, "arg-count");
|
|
146
|
+
assertEquals(diags[0].message.includes("at most"), true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("unknown function — no diagnostic", () => {
|
|
150
|
+
const ast = p("[myfunc(a,b,c)]");
|
|
151
|
+
const diags = lint(ast, { rules: ["arg-count"] });
|
|
152
|
+
assertEquals(diags.length, 0);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("mid() with 3 args — no diagnostic", () => {
|
|
156
|
+
const ast = p("[mid(foo,1,2)]");
|
|
157
|
+
const diags = lint(ast, { rules: ["arg-count"] });
|
|
158
|
+
assertEquals(diags.length, 0);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// ── register-before-set ────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
describe("register-before-set", () => {
|
|
165
|
+
it("no diagnostic when register is set before use", () => {
|
|
166
|
+
const ast = p("[setq(0,hello)][pemit(%#,%q0)]");
|
|
167
|
+
const diags = lint(ast, { rules: ["register-before-set"] });
|
|
168
|
+
assertEquals(diags.length, 0);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("info diagnostic when %q0 used with no setq", () => {
|
|
172
|
+
const ast = p("[pemit(%#,%q0)]");
|
|
173
|
+
const diags = lint(ast, { rules: ["register-before-set"] });
|
|
174
|
+
assertEquals(diags.length, 1);
|
|
175
|
+
assertEquals(diags[0].rule, "register-before-set");
|
|
176
|
+
assertEquals(diags[0].severity, "info");
|
|
177
|
+
assertEquals(diags[0].message.includes("%q0"), true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("r() read of unset register — info diagnostic", () => {
|
|
181
|
+
const ast = p("[r(0)]");
|
|
182
|
+
const diags = lint(ast, { rules: ["register-before-set"] });
|
|
183
|
+
assertEquals(diags.length, 1);
|
|
184
|
+
assertEquals(diags[0].rule, "register-before-set");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("no diagnostic when r() matches setq()", () => {
|
|
188
|
+
const ast = p("[setq(foo,bar)][r(foo)]");
|
|
189
|
+
const diags = lint(ast, { rules: ["register-before-set"] });
|
|
190
|
+
assertEquals(diags.length, 0);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("named register — no diagnostic when set", () => {
|
|
194
|
+
const ast = p("[setq(name,Alice)][pemit(%#,%qname)]");
|
|
195
|
+
const diags = lint(ast, { rules: ["register-before-set"] });
|
|
196
|
+
assertEquals(diags.length, 0);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// ── LintOptions: rules whitelist ───────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
describe("lint — options", () => {
|
|
203
|
+
it("runs all rules by default", () => {
|
|
204
|
+
// Pattern with no wildcards using %1 → missing-wildcard
|
|
205
|
+
// ## outside iter → iter-var-outside-iter
|
|
206
|
+
const ast = p("$look:@pemit %#=%1 ##");
|
|
207
|
+
const diags = lint(ast);
|
|
208
|
+
const rules = rulesOf(diags);
|
|
209
|
+
assertEquals(rules.includes("missing-wildcard"), true);
|
|
210
|
+
assertEquals(rules.includes("iter-var-outside-iter"), true);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("whitelist: only runs specified rules", () => {
|
|
214
|
+
const ast = p("$look:@pemit %#=%1 ##");
|
|
215
|
+
const diags = lint(ast, { rules: ["missing-wildcard"] });
|
|
216
|
+
assertEquals(diags.every(d => d.rule === "missing-wildcard"), true);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("RULES constant lists all rule ids", () => {
|
|
220
|
+
assertEquals(RULES.length, 4);
|
|
221
|
+
assertEquals(Array.from(RULES).includes("missing-wildcard"), true);
|
|
222
|
+
assertEquals(Array.from(RULES).includes("iter-var-outside-iter"), true);
|
|
223
|
+
assertEquals(Array.from(RULES).includes("arg-count"), true);
|
|
224
|
+
assertEquals(Array.from(RULES).includes("register-before-set"), true);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("empty rules list produces no diagnostics", () => {
|
|
228
|
+
const ast = p("$look:@pemit %#=%1");
|
|
229
|
+
const diags = lint(ast, { rules: [] });
|
|
230
|
+
assertEquals(diags.length, 0);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Print — canonical softcode printer
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { assertEquals } from "jsr:@std/assert@^1";
|
|
6
|
+
import { describe, it } from "jsr:/@std/testing@^1/bdd";
|
|
7
|
+
import { parse } from "../parser/mod.ts";
|
|
8
|
+
import type { ASTNode } from "../parser/mod.ts";
|
|
9
|
+
import { print } from "../src/print/mod.ts";
|
|
10
|
+
|
|
11
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
function roundtrip(text: string): string {
|
|
14
|
+
return print(parse(text, "Start"));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function roundtripLock(text: string): string {
|
|
18
|
+
return print(parse(text, "LockExpr"));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ── Literals and leaves ────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
describe("Print — leaves", () => {
|
|
24
|
+
it("Literal", () => {
|
|
25
|
+
assertEquals(roundtrip("hello world"), "hello world");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("Escape → backslash + char", () => {
|
|
29
|
+
assertEquals(roundtrip("\\;"), "\\;");
|
|
30
|
+
assertEquals(roundtrip("\\["), "\\[");
|
|
31
|
+
assertEquals(roundtrip("\\\\"), "\\\\");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("Substitution → % + code", () => {
|
|
35
|
+
assertEquals(roundtrip("%0"), "%0");
|
|
36
|
+
assertEquals(roundtrip("%#"), "%#");
|
|
37
|
+
assertEquals(roundtrip("%q0"), "%q0");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("SpecialVar — ## #@ #$", () => {
|
|
41
|
+
assertEquals(roundtrip("##"), "##");
|
|
42
|
+
assertEquals(roundtrip("#@"), "#@");
|
|
43
|
+
assertEquals(roundtrip("#$"), "#$");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("TagRef → #name", () => {
|
|
47
|
+
assertEquals(roundtrip("[tag(me,#mytag)]"), "[tag(me,#mytag)]");
|
|
48
|
+
// Standalone TagRef in text
|
|
49
|
+
const node: ASTNode = { type: "TagRef", name: "alpha" };
|
|
50
|
+
assertEquals(print(node), "#alpha");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ── Expression containers ──────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
describe("Print — EvalBlock and BracedString", () => {
|
|
57
|
+
it("EvalBlock wraps in []", () => {
|
|
58
|
+
assertEquals(roundtrip("[add(1,2)]"), "[add(1,2)]");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("BracedString wraps in {}", () => {
|
|
62
|
+
assertEquals(roundtrip("{hello}"), "{hello}");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("nested EvalBlock inside BracedString", () => {
|
|
66
|
+
assertEquals(roundtrip("{[add(1,2)]}"), "{[add(1,2)]}");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ── FunctionCall ───────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
describe("Print — FunctionCall", () => {
|
|
73
|
+
it("no args", () => {
|
|
74
|
+
assertEquals(roundtrip("[rand()]"), "[rand()]");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("single arg", () => {
|
|
78
|
+
assertEquals(roundtrip("[strlen(hello)]"), "[strlen(hello)]");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("multiple args, comma-separated", () => {
|
|
82
|
+
assertEquals(roundtrip("[add(1,2)]"), "[add(1,2)]");
|
|
83
|
+
assertEquals(roundtrip("[mid(foo,1,2)]"), "[mid(foo,1,2)]");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("nested function calls", () => {
|
|
87
|
+
assertEquals(roundtrip("[add(mul(2,3),1)]"), "[add(mul(2,3),1)]");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ── Commands ───────────────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
describe("Print — AtCommand", () => {
|
|
94
|
+
it("basic @pemit with object and value", () => {
|
|
95
|
+
assertEquals(roundtrip("@pemit %#=hello"), "@pemit %#=hello");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("AtCommand with switch", () => {
|
|
99
|
+
assertEquals(roundtrip("@pemit/noeval %#=hello"), "@pemit/noeval %#=hello");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("AtCommand without value", () => {
|
|
103
|
+
assertEquals(roundtrip("@trigger me/GO"), "@trigger me/GO");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("@set with value", () => {
|
|
107
|
+
assertEquals(roundtrip("@set %#=WIZARD"), "@set %#=WIZARD");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("Print — AttributeSet", () => {
|
|
112
|
+
it("basic &ATTR obj=val", () => {
|
|
113
|
+
assertEquals(roundtrip("&MYATTR me=hello world"), "&MYATTR me=hello world");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("attribute with eval block value", () => {
|
|
117
|
+
assertEquals(roundtrip("&FINGER me=[u(me/FN_FINGER)]"), "&FINGER me=[u(me/FN_FINGER)]");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ── CommandList ────────────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
describe("Print — CommandList", () => {
|
|
124
|
+
it("compact mode (default) joins with ;", () => {
|
|
125
|
+
const ast = parse("@pemit %#=a;@pemit %#=b", "Start");
|
|
126
|
+
assertEquals(print(ast), "@pemit %#=a;@pemit %#=b");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("pretty mode joins with ;\\n", () => {
|
|
130
|
+
const ast = parse("@pemit %#=a;@pemit %#=b", "Start");
|
|
131
|
+
assertEquals(print(ast, { mode: "pretty" }), "@pemit %#=a;\n@pemit %#=b");
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ── Dollar and Listen patterns ─────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
describe("Print — patterns", () => {
|
|
138
|
+
it("DollarPattern round-trips", () => {
|
|
139
|
+
assertEquals(roundtrip("$+finger *:@pemit %#=[u(me/FN_FINGER,%0)]"),
|
|
140
|
+
"$+finger *:@pemit %#=[u(me/FN_FINGER,%0)]");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("ListenPattern round-trips", () => {
|
|
144
|
+
assertEquals(roundtrip("^hello *:@pemit %#=Hi!"),
|
|
145
|
+
"^hello *:@pemit %#=Hi!");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("PatternAlts with semicolon separator", () => {
|
|
149
|
+
// Pattern alternatives joined by ;
|
|
150
|
+
const text = "$look;+look:look";
|
|
151
|
+
assertEquals(roundtrip(text), text);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// ── Lock expressions ───────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
describe("Print — lock expressions", () => {
|
|
158
|
+
it("me", () => {
|
|
159
|
+
assertEquals(roundtripLock("me"), "me");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("dbref", () => {
|
|
163
|
+
assertEquals(roundtripLock("#123"), "#123");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("flag check", () => {
|
|
167
|
+
assertEquals(roundtripLock("flag^WIZARD"), "flag^WIZARD");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("player name check", () => {
|
|
171
|
+
assertEquals(roundtripLock("=Admin"), "=Admin");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("attr check", () => {
|
|
175
|
+
assertEquals(roundtripLock("attr^LOCKED"), "attr^LOCKED");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("type check", () => {
|
|
179
|
+
assertEquals(roundtripLock("type^PLAYER"), "type^PLAYER");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("LockAnd — & separates operands", () => {
|
|
183
|
+
assertEquals(roundtripLock("flag^WIZARD&flag^ADMIN"), "flag^WIZARD&flag^ADMIN");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("LockOr — | separates operands", () => {
|
|
187
|
+
assertEquals(roundtripLock("flag^WIZARD|flag^ADMIN"), "flag^WIZARD|flag^ADMIN");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("LockNot — prefix !", () => {
|
|
191
|
+
assertEquals(roundtripLock("!flag^WIZARD"), "!flag^WIZARD");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("LockOr inside LockAnd gets parens", () => {
|
|
195
|
+
// (a|b)&c — the OR branch needs parens to preserve precedence
|
|
196
|
+
const result = roundtripLock("(flag^WIZARD|me)&flag^ADMIN");
|
|
197
|
+
assertEquals(result, "(flag^WIZARD|me)&flag^ADMIN");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("LockNot wrapping compound expression gets parens", () => {
|
|
201
|
+
const result = roundtripLock("!(flag^WIZARD|me)");
|
|
202
|
+
assertEquals(result, "!(flag^WIZARD|me)");
|
|
203
|
+
});
|
|
204
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Traverse — walk, transform, findAll, findFirst, findFirstOrNull
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { assertEquals, assertThrows } from "jsr:@std/assert@^1";
|
|
6
|
+
import { describe, it } from "jsr:/@std/testing@^1/bdd";
|
|
7
|
+
import { parse } from "../parser/mod.ts";
|
|
8
|
+
import type { ASTNode } from "../parser/mod.ts";
|
|
9
|
+
import {
|
|
10
|
+
walk,
|
|
11
|
+
transform,
|
|
12
|
+
findAll,
|
|
13
|
+
findFirst,
|
|
14
|
+
findFirstOrNull,
|
|
15
|
+
} from "../src/traverse/mod.ts";
|
|
16
|
+
|
|
17
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
function p(text: string): ASTNode {
|
|
20
|
+
return parse(text, "Start");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── walk ───────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
describe("walk — enter/leave hooks", () => {
|
|
26
|
+
it("visits every node depth-first", () => {
|
|
27
|
+
const ast = p("[add(1,2)]");
|
|
28
|
+
const visited: string[] = [];
|
|
29
|
+
walk(ast, { enter(n) { visited.push(n.type); } });
|
|
30
|
+
// Must visit UserCommand, EvalBlock, FunctionCall, and two Arg+Literal nodes
|
|
31
|
+
const unique = [...new Set(visited)];
|
|
32
|
+
assertEquals(unique.includes("UserCommand"), true);
|
|
33
|
+
assertEquals(unique.includes("EvalBlock"), true);
|
|
34
|
+
assertEquals(unique.includes("FunctionCall"), true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("leave is called after children", () => {
|
|
38
|
+
const ast = p("[f(x)]");
|
|
39
|
+
const log: string[] = [];
|
|
40
|
+
walk(ast, {
|
|
41
|
+
enter(n) { log.push(`enter:${n.type}`); },
|
|
42
|
+
leave(n) { log.push(`leave:${n.type}`); },
|
|
43
|
+
});
|
|
44
|
+
const enterIdx = log.indexOf("enter:FunctionCall");
|
|
45
|
+
const leaveIdx = log.indexOf("leave:FunctionCall");
|
|
46
|
+
const argIdx = log.indexOf("enter:Arg");
|
|
47
|
+
assertEquals(enterIdx < argIdx, true, "enter:FunctionCall before enter:Arg");
|
|
48
|
+
assertEquals(argIdx < leaveIdx, true, "enter:Arg before leave:FunctionCall");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("returning false from enter skips children (leave not called)", () => {
|
|
52
|
+
const ast = p("[f(x)]");
|
|
53
|
+
const visited: string[] = [];
|
|
54
|
+
walk(ast, {
|
|
55
|
+
enter(n) {
|
|
56
|
+
visited.push(n.type);
|
|
57
|
+
if (n.type === "FunctionCall") return false;
|
|
58
|
+
},
|
|
59
|
+
leave(n) { visited.push(`leave:${n.type}`); },
|
|
60
|
+
});
|
|
61
|
+
assertEquals(visited.includes("Arg"), false, "Arg (child of FunctionCall) skipped");
|
|
62
|
+
assertEquals(visited.includes("leave:FunctionCall"), false, "leave not called when false returned");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("walk on a leaf node (Literal) fires enter+leave once with no children", () => {
|
|
66
|
+
const leaf: ASTNode = { type: "Literal", value: "hello" };
|
|
67
|
+
const log: string[] = [];
|
|
68
|
+
walk(leaf, { enter(n) { log.push(`e:${n.type}`); }, leave(n) { log.push(`l:${n.type}`); } });
|
|
69
|
+
assertEquals(log, ["e:Literal", "l:Literal"]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("walk over CommandList visits all commands", () => {
|
|
73
|
+
const ast = p("cmd1;cmd2;cmd3");
|
|
74
|
+
const cmds: string[] = [];
|
|
75
|
+
walk(ast, {
|
|
76
|
+
enter(n) {
|
|
77
|
+
if (n.type === "UserCommand") cmds.push(n.type);
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
assertEquals(cmds.length, 3);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ── findAll ────────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
describe("findAll", () => {
|
|
87
|
+
it("collects all FunctionCall nodes", () => {
|
|
88
|
+
const ast = p("[add(mul(2,3),4)]");
|
|
89
|
+
const fns = findAll(ast, "FunctionCall");
|
|
90
|
+
assertEquals(fns.length, 2);
|
|
91
|
+
const names = fns.map(n => n.name as string);
|
|
92
|
+
assertEquals(names.includes("add"), true);
|
|
93
|
+
assertEquals(names.includes("mul"), true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("returns empty array when type not found", () => {
|
|
97
|
+
const ast = p("plain text");
|
|
98
|
+
assertEquals(findAll(ast, "FunctionCall"), []);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("finds nested EvalBlocks", () => {
|
|
102
|
+
const ast = p("[[f()]]");
|
|
103
|
+
const evals = findAll(ast, "EvalBlock");
|
|
104
|
+
assertEquals(evals.length >= 1, true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("finds TagRef nodes", () => {
|
|
108
|
+
const ast = p("[tag(me,#mytag)]");
|
|
109
|
+
const refs = findAll(ast, "TagRef");
|
|
110
|
+
assertEquals(refs.length, 1);
|
|
111
|
+
assertEquals(refs[0].name, "mytag");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("finds multiple Substitution nodes", () => {
|
|
115
|
+
const ast = p("%0 %1 %2");
|
|
116
|
+
const subs = findAll(ast, "Substitution");
|
|
117
|
+
assertEquals(subs.length, 3);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ── findFirst / findFirstOrNull ────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
describe("findFirst / findFirstOrNull", () => {
|
|
124
|
+
it("findFirst returns the first matching node", () => {
|
|
125
|
+
const ast = p("[add(1,2)]");
|
|
126
|
+
const fn = findFirst(ast, "FunctionCall");
|
|
127
|
+
assertEquals(fn.type, "FunctionCall");
|
|
128
|
+
assertEquals(fn.name, "add");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("findFirst throws when not found", () => {
|
|
132
|
+
const ast = p("plain text");
|
|
133
|
+
assertThrows(
|
|
134
|
+
() => findFirst(ast, "FunctionCall"),
|
|
135
|
+
Error,
|
|
136
|
+
'No node of type "FunctionCall" found',
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("findFirstOrNull returns null when not found", () => {
|
|
141
|
+
const ast = p("plain text");
|
|
142
|
+
assertEquals(findFirstOrNull(ast, "FunctionCall"), null);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("findFirstOrNull returns the node when found", () => {
|
|
146
|
+
const ast = p("[f()]");
|
|
147
|
+
const fn = findFirstOrNull(ast, "FunctionCall");
|
|
148
|
+
assertEquals(fn?.type, "FunctionCall");
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ── transform ─────────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
describe("transform", () => {
|
|
155
|
+
it("returns the same reference when no changes made", () => {
|
|
156
|
+
const ast = p("hello");
|
|
157
|
+
const out = transform(ast, () => undefined);
|
|
158
|
+
assertEquals(out, ast);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("replaces a node", () => {
|
|
162
|
+
const ast = p("[#mytag]");
|
|
163
|
+
const out = transform(ast, (n) => {
|
|
164
|
+
if (n.type === "TagRef") {
|
|
165
|
+
return { type: "Literal", value: `<tag:${n.name as string}>` };
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
const literals = findAll(out, "Literal");
|
|
169
|
+
assertEquals(literals.some(l => (l.value as string) === "<tag:mytag>"), true);
|
|
170
|
+
// Original tree is unmodified
|
|
171
|
+
assertEquals(findAll(ast, "TagRef").length, 1);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("removes array items when fn returns null", () => {
|
|
175
|
+
const ast = p("[add(1,2)]");
|
|
176
|
+
// Remove all Arg nodes (the children of FunctionCall) — result has empty args array
|
|
177
|
+
const out = transform(ast, (n) => {
|
|
178
|
+
if (n.type === "Arg") return null;
|
|
179
|
+
});
|
|
180
|
+
const fn = findFirst(out, "FunctionCall");
|
|
181
|
+
assertEquals((fn.args as ASTNode[]).length, 0);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("top-down: replacement's children are recursed (not original's)", () => {
|
|
185
|
+
// Replace FunctionCall with a new FunctionCall that has different args;
|
|
186
|
+
// make sure the new child is visited, not the old one
|
|
187
|
+
const ast = p("[old(x)]");
|
|
188
|
+
const visited: string[] = [];
|
|
189
|
+
transform(ast, (n) => {
|
|
190
|
+
visited.push(n.type);
|
|
191
|
+
if (n.type === "FunctionCall" && n.name === "old") {
|
|
192
|
+
return {
|
|
193
|
+
type: "FunctionCall",
|
|
194
|
+
name: "new",
|
|
195
|
+
args: [{ type: "Arg", parts: [{ type: "Literal", value: "y" }] }],
|
|
196
|
+
} as ASTNode;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
// "new" FunctionCall replacement is entered; "old" args never visited
|
|
200
|
+
assertEquals(visited.filter(t => t === "FunctionCall").length, 1);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("does not mutate the original tree", () => {
|
|
204
|
+
const ast = p("[add(1,2)]");
|
|
205
|
+
const originalStr = JSON.stringify(ast);
|
|
206
|
+
transform(ast, (n) => {
|
|
207
|
+
if (n.type === "Literal") return { type: "Literal", value: "X" };
|
|
208
|
+
});
|
|
209
|
+
assertEquals(JSON.stringify(ast), originalStr);
|
|
210
|
+
});
|
|
211
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhost/testkit",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
4
4
|
"description": "SDK for programmatically testing MUSHcode against a RhostMUSH server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"SECURITY.md",
|
|
28
28
|
"ROADMAP.md"
|
|
29
29
|
],
|
|
30
|
+
"bundledDependencies": [
|
|
31
|
+
"@ursamu/mushcode"
|
|
32
|
+
],
|
|
30
33
|
"repository": {
|
|
31
34
|
"type": "git",
|
|
32
35
|
"url": "https://github.com/lcanady/rhost-testkit.git"
|
|
@@ -95,6 +98,12 @@
|
|
|
95
98
|
"testPathIgnorePatterns": [],
|
|
96
99
|
"moduleNameMapper": {
|
|
97
100
|
"^@ursamu/mushcode$": "<rootDir>/scripts/mushcode-compat.js"
|
|
101
|
+
},
|
|
102
|
+
"coverageThreshold": {
|
|
103
|
+
"global": {
|
|
104
|
+
"lines": 80,
|
|
105
|
+
"statements": 80
|
|
106
|
+
}
|
|
98
107
|
}
|
|
99
108
|
}
|
|
100
109
|
}
|