@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,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Known arity ranges for TinyMUX / RhostMUSH built-in functions.
|
|
3
|
+
* [minArgs, maxArgs] — use Infinity for unlimited upper bound.
|
|
4
|
+
*
|
|
5
|
+
* Only strictly-bounded functions are listed. Omitting a function means the
|
|
6
|
+
* arg-count rule skips it entirely (no false positives for unknown functions).
|
|
7
|
+
*/ export const ARITIES = {
|
|
8
|
+
// Arithmetic
|
|
9
|
+
add: [
|
|
10
|
+
1,
|
|
11
|
+
Infinity
|
|
12
|
+
],
|
|
13
|
+
sub: [
|
|
14
|
+
1,
|
|
15
|
+
Infinity
|
|
16
|
+
],
|
|
17
|
+
mul: [
|
|
18
|
+
1,
|
|
19
|
+
Infinity
|
|
20
|
+
],
|
|
21
|
+
div: [
|
|
22
|
+
2,
|
|
23
|
+
2
|
|
24
|
+
],
|
|
25
|
+
mod: [
|
|
26
|
+
2,
|
|
27
|
+
2
|
|
28
|
+
],
|
|
29
|
+
abs: [
|
|
30
|
+
1,
|
|
31
|
+
1
|
|
32
|
+
],
|
|
33
|
+
// Comparison
|
|
34
|
+
eq: [
|
|
35
|
+
2,
|
|
36
|
+
2
|
|
37
|
+
],
|
|
38
|
+
neq: [
|
|
39
|
+
2,
|
|
40
|
+
2
|
|
41
|
+
],
|
|
42
|
+
gt: [
|
|
43
|
+
2,
|
|
44
|
+
2
|
|
45
|
+
],
|
|
46
|
+
gte: [
|
|
47
|
+
2,
|
|
48
|
+
2
|
|
49
|
+
],
|
|
50
|
+
lt: [
|
|
51
|
+
2,
|
|
52
|
+
2
|
|
53
|
+
],
|
|
54
|
+
lte: [
|
|
55
|
+
2,
|
|
56
|
+
2
|
|
57
|
+
],
|
|
58
|
+
// Logic
|
|
59
|
+
and: [
|
|
60
|
+
1,
|
|
61
|
+
Infinity
|
|
62
|
+
],
|
|
63
|
+
or: [
|
|
64
|
+
1,
|
|
65
|
+
Infinity
|
|
66
|
+
],
|
|
67
|
+
not: [
|
|
68
|
+
1,
|
|
69
|
+
1
|
|
70
|
+
],
|
|
71
|
+
// Control flow
|
|
72
|
+
if: [
|
|
73
|
+
2,
|
|
74
|
+
3
|
|
75
|
+
],
|
|
76
|
+
ifelse: [
|
|
77
|
+
3,
|
|
78
|
+
3
|
|
79
|
+
],
|
|
80
|
+
switch: [
|
|
81
|
+
3,
|
|
82
|
+
Infinity
|
|
83
|
+
],
|
|
84
|
+
// Registers
|
|
85
|
+
setq: [
|
|
86
|
+
2,
|
|
87
|
+
2
|
|
88
|
+
],
|
|
89
|
+
r: [
|
|
90
|
+
1,
|
|
91
|
+
1
|
|
92
|
+
],
|
|
93
|
+
// Iteration
|
|
94
|
+
iter: [
|
|
95
|
+
2,
|
|
96
|
+
4
|
|
97
|
+
],
|
|
98
|
+
map: [
|
|
99
|
+
2,
|
|
100
|
+
4
|
|
101
|
+
],
|
|
102
|
+
filter: [
|
|
103
|
+
2,
|
|
104
|
+
3
|
|
105
|
+
],
|
|
106
|
+
// Object / attribute
|
|
107
|
+
name: [
|
|
108
|
+
1,
|
|
109
|
+
1
|
|
110
|
+
],
|
|
111
|
+
loc: [
|
|
112
|
+
1,
|
|
113
|
+
1
|
|
114
|
+
],
|
|
115
|
+
get: [
|
|
116
|
+
1,
|
|
117
|
+
1
|
|
118
|
+
],
|
|
119
|
+
set: [
|
|
120
|
+
2,
|
|
121
|
+
2
|
|
122
|
+
],
|
|
123
|
+
v: [
|
|
124
|
+
1,
|
|
125
|
+
1
|
|
126
|
+
],
|
|
127
|
+
u: [
|
|
128
|
+
1,
|
|
129
|
+
Infinity
|
|
130
|
+
],
|
|
131
|
+
hasflag: [
|
|
132
|
+
2,
|
|
133
|
+
2
|
|
134
|
+
],
|
|
135
|
+
hasattr: [
|
|
136
|
+
2,
|
|
137
|
+
2
|
|
138
|
+
],
|
|
139
|
+
// String
|
|
140
|
+
strlen: [
|
|
141
|
+
1,
|
|
142
|
+
1
|
|
143
|
+
],
|
|
144
|
+
mid: [
|
|
145
|
+
3,
|
|
146
|
+
3
|
|
147
|
+
],
|
|
148
|
+
left: [
|
|
149
|
+
2,
|
|
150
|
+
2
|
|
151
|
+
],
|
|
152
|
+
right: [
|
|
153
|
+
2,
|
|
154
|
+
2
|
|
155
|
+
],
|
|
156
|
+
trim: [
|
|
157
|
+
1,
|
|
158
|
+
3
|
|
159
|
+
],
|
|
160
|
+
ljust: [
|
|
161
|
+
2,
|
|
162
|
+
3
|
|
163
|
+
],
|
|
164
|
+
rjust: [
|
|
165
|
+
2,
|
|
166
|
+
3
|
|
167
|
+
],
|
|
168
|
+
center: [
|
|
169
|
+
2,
|
|
170
|
+
3
|
|
171
|
+
],
|
|
172
|
+
// List / matching
|
|
173
|
+
words: [
|
|
174
|
+
1,
|
|
175
|
+
2
|
|
176
|
+
],
|
|
177
|
+
match: [
|
|
178
|
+
2,
|
|
179
|
+
3
|
|
180
|
+
],
|
|
181
|
+
pmatch: [
|
|
182
|
+
1,
|
|
183
|
+
1
|
|
184
|
+
],
|
|
185
|
+
lcon: [
|
|
186
|
+
1,
|
|
187
|
+
2
|
|
188
|
+
],
|
|
189
|
+
lwho: [
|
|
190
|
+
0,
|
|
191
|
+
1
|
|
192
|
+
],
|
|
193
|
+
// Formatting
|
|
194
|
+
ansi: [
|
|
195
|
+
2,
|
|
196
|
+
Infinity
|
|
197
|
+
],
|
|
198
|
+
// Tags (RhostMUSH)
|
|
199
|
+
tag: [
|
|
200
|
+
1,
|
|
201
|
+
1
|
|
202
|
+
],
|
|
203
|
+
listtags: [
|
|
204
|
+
0,
|
|
205
|
+
1
|
|
206
|
+
],
|
|
207
|
+
tagmatch: [
|
|
208
|
+
1,
|
|
209
|
+
1
|
|
210
|
+
]
|
|
211
|
+
};
|
|
212
|
+
//# sourceMappingURL=builtin_arities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builtin_arities.js","sources":["./builtin_arities.ts"],"names":[],"mappings":"AAAA;;;;;;CAMC,GACD,OAAO,MAAM,UAAsD;EACjE,aAAa;EACb,KAAS;IAAC;IAAG;GAAS;EACtB,KAAS;IAAC;IAAG;GAAS;EACtB,KAAS;IAAC;IAAG;GAAS;EACtB,KAAS;IAAC;IAAG;GAAE;EACf,KAAS;IAAC;IAAG;GAAE;EACf,KAAS;IAAC;IAAG;GAAE;EACf,aAAa;EACb,IAAS;IAAC;IAAG;GAAE;EACf,KAAS;IAAC;IAAG;GAAE;EACf,IAAS;IAAC;IAAG;GAAE;EACf,KAAS;IAAC;IAAG;GAAE;EACf,IAAS;IAAC;IAAG;GAAE;EACf,KAAS;IAAC;IAAG;GAAE;EACf,QAAQ;EACR,KAAS;IAAC;IAAG;GAAS;EACtB,IAAS;IAAC;IAAG;GAAS;EACtB,KAAS;IAAC;IAAG;GAAE;EACf,eAAe;EACf,IAAS;IAAC;IAAG;GAAE;EACf,QAAS;IAAC;IAAG;GAAE;EACf,QAAS;IAAC;IAAG;GAAS;EACtB,YAAY;EACZ,MAAS;IAAC;IAAG;GAAE;EACf,GAAS;IAAC;IAAG;GAAE;EACf,YAAY;EACZ,MAAS;IAAC;IAAG;GAAE;EACf,KAAS;IAAC;IAAG;GAAE;EACf,QAAS;IAAC;IAAG;GAAE;EACf,qBAAqB;EACrB,MAAS;IAAC;IAAG;GAAE;EACf,KAAS;IAAC;IAAG;GAAE;EACf,KAAS;IAAC;IAAG;GAAE;EACf,KAAS;IAAC;IAAG;GAAE;EACf,GAAS;IAAC;IAAG;GAAE;EACf,GAAS;IAAC;IAAG;GAAS;EACtB,SAAS;IAAC;IAAG;GAAE;EACf,SAAS;IAAC;IAAG;GAAE;EACf,SAAS;EACT,QAAS;IAAC;IAAG;GAAE;EACf,KAAS;IAAC;IAAG;GAAE;EACf,MAAS;IAAC;IAAG;GAAE;EACf,OAAS;IAAC;IAAG;GAAE;EACf,MAAS;IAAC;IAAG;GAAE;EACf,OAAS;IAAC;IAAG;GAAE;EACf,OAAS;IAAC;IAAG;GAAE;EACf,QAAS;IAAC;IAAG;GAAE;EACf,kBAAkB;EAClB,OAAS;IAAC;IAAG;GAAE;EACf,OAAS;IAAC;IAAG;GAAE;EACf,QAAS;IAAC;IAAG;GAAE;EACf,MAAS;IAAC;IAAG;GAAE;EACf,MAAS;IAAC;IAAG;GAAE;EACf,aAAa;EACb,MAAS;IAAC;IAAG;GAAS;EACtB,mBAAmB;EACnB,KAAa;IAAC;IAAG;GAAE;EACnB,UAAa;IAAC;IAAG;GAAE;EACnB,UAAa;IAAC;IAAG;GAAE;AACrB,EAAE"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Known arity ranges for TinyMUX / RhostMUSH built-in functions.
|
|
3
|
+
* [minArgs, maxArgs] — use Infinity for unlimited upper bound.
|
|
4
|
+
*
|
|
5
|
+
* Only strictly-bounded functions are listed. Omitting a function means the
|
|
6
|
+
* arg-count rule skips it entirely (no false positives for unknown functions).
|
|
7
|
+
*/
|
|
8
|
+
export const ARITIES: Readonly<Record<string, [number, number]>> = {
|
|
9
|
+
// Arithmetic
|
|
10
|
+
add: [1, Infinity],
|
|
11
|
+
sub: [1, Infinity],
|
|
12
|
+
mul: [1, Infinity],
|
|
13
|
+
div: [2, 2],
|
|
14
|
+
mod: [2, 2],
|
|
15
|
+
abs: [1, 1],
|
|
16
|
+
// Comparison
|
|
17
|
+
eq: [2, 2],
|
|
18
|
+
neq: [2, 2],
|
|
19
|
+
gt: [2, 2],
|
|
20
|
+
gte: [2, 2],
|
|
21
|
+
lt: [2, 2],
|
|
22
|
+
lte: [2, 2],
|
|
23
|
+
// Logic
|
|
24
|
+
and: [1, Infinity],
|
|
25
|
+
or: [1, Infinity],
|
|
26
|
+
not: [1, 1],
|
|
27
|
+
// Control flow
|
|
28
|
+
if: [2, 3],
|
|
29
|
+
ifelse: [3, 3],
|
|
30
|
+
switch: [3, Infinity],
|
|
31
|
+
// Registers
|
|
32
|
+
setq: [2, 2],
|
|
33
|
+
r: [1, 1],
|
|
34
|
+
// Iteration
|
|
35
|
+
iter: [2, 4],
|
|
36
|
+
map: [2, 4],
|
|
37
|
+
filter: [2, 3],
|
|
38
|
+
// Object / attribute
|
|
39
|
+
name: [1, 1],
|
|
40
|
+
loc: [1, 1],
|
|
41
|
+
get: [1, 1],
|
|
42
|
+
set: [2, 2],
|
|
43
|
+
v: [1, 1],
|
|
44
|
+
u: [1, Infinity],
|
|
45
|
+
hasflag: [2, 2],
|
|
46
|
+
hasattr: [2, 2],
|
|
47
|
+
// String
|
|
48
|
+
strlen: [1, 1],
|
|
49
|
+
mid: [3, 3],
|
|
50
|
+
left: [2, 2],
|
|
51
|
+
right: [2, 2],
|
|
52
|
+
trim: [1, 3],
|
|
53
|
+
ljust: [2, 3],
|
|
54
|
+
rjust: [2, 3],
|
|
55
|
+
center: [2, 3],
|
|
56
|
+
// List / matching
|
|
57
|
+
words: [1, 2],
|
|
58
|
+
match: [2, 3],
|
|
59
|
+
pmatch: [1, 1],
|
|
60
|
+
lcon: [1, 2],
|
|
61
|
+
lwho: [0, 1],
|
|
62
|
+
// Formatting
|
|
63
|
+
ansi: [2, Infinity],
|
|
64
|
+
// Tags (RhostMUSH)
|
|
65
|
+
tag: [1, 1],
|
|
66
|
+
listtags: [0, 1],
|
|
67
|
+
tagmatch: [1, 1],
|
|
68
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Static analysis for softcode: `lint()` runs built-in rules and returns
|
|
4
|
+
* `Diagnostic` findings with severity, rule ID, and the offending AST node.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { parse } from "@ursamu/mushcode/parse";
|
|
9
|
+
* import { lint } from "@ursamu/mushcode/lint";
|
|
10
|
+
*
|
|
11
|
+
* const ast = parse("$finger:@pemit %#=[u(me/FN)]");
|
|
12
|
+
* const diags = lint(ast);
|
|
13
|
+
* diags.forEach(d => console.log(`[${d.severity}] ${d.rule}: ${d.message}`));
|
|
14
|
+
* ```
|
|
15
|
+
*/ import { walk } from "../traverse/walk.js";
|
|
16
|
+
import { checkMissingWildcard } from "./rules/missing_wildcard.js";
|
|
17
|
+
import { checkIterVarOutsideIter } from "./rules/iter_var_outside_iter.js";
|
|
18
|
+
import { checkArgCount } from "./rules/arg_count.js";
|
|
19
|
+
import { checkRegisterBeforeSet } from "./rules/register_before_set.js";
|
|
20
|
+
// ── Available rules ───────────────────────────────────────────────────────────
|
|
21
|
+
/** All built-in rule IDs. Pass a subset to `LintOptions.rules` to enable only those rules. */ export const RULES = [
|
|
22
|
+
"missing-wildcard",
|
|
23
|
+
"iter-var-outside-iter",
|
|
24
|
+
"arg-count",
|
|
25
|
+
"register-before-set"
|
|
26
|
+
];
|
|
27
|
+
// ── lint ──────────────────────────────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* Run static analysis on an AST and return an array of diagnostics.
|
|
30
|
+
*
|
|
31
|
+
* Rules are applied at every DollarPattern/ListenPattern/FunctionCall/… node
|
|
32
|
+
* anywhere in the tree, so this works correctly on CommandList roots too.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* const diags = lint(parse("$+finger *:@pemit %#=[u(me/FN_FINGER,%0)]"));
|
|
36
|
+
* diags.forEach(d => console.log(`[${d.severity}] ${d.rule}: ${d.message}`));
|
|
37
|
+
*/ export function lint(root, opts) {
|
|
38
|
+
const enabled = new Set(opts?.rules ?? RULES);
|
|
39
|
+
const diags = [];
|
|
40
|
+
// Rules that examine individual nodes as we walk
|
|
41
|
+
walk(root, {
|
|
42
|
+
enter (node) {
|
|
43
|
+
if (enabled.has("missing-wildcard")) {
|
|
44
|
+
diags.push(...checkMissingWildcard(node));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
// Rules that need a full-tree view (run once on the root)
|
|
49
|
+
if (enabled.has("iter-var-outside-iter")) {
|
|
50
|
+
diags.push(...checkIterVarOutsideIter(root));
|
|
51
|
+
}
|
|
52
|
+
if (enabled.has("arg-count")) {
|
|
53
|
+
diags.push(...checkArgCount(root));
|
|
54
|
+
}
|
|
55
|
+
if (enabled.has("register-before-set")) {
|
|
56
|
+
diags.push(...checkRegisterBeforeSet(root));
|
|
57
|
+
}
|
|
58
|
+
return diags;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=mod.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.js","sources":["./mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;CAcC,GAED,SAAS,IAAI,8BAAsD;AACnE,SAAS,oBAAoB,sCAA8C;AAC3E,SAAS,uBAAuB,2CAAgD;AAChF,SAAS,aAAa,+BAA8C;AACpE,SAAS,sBAAsB,yCAA+C;AAyB9E,iFAAiF;AAEjF,6FAA6F,GAC7F,OAAO,MAAM,QAAQ;EACnB;EACA;EACA;EACA;CACD,CAAU;AAKX,iFAAiF;AAEjF;;;;;;;;;CASC,GACD,OAAO,SAAS,KAAK,IAAa,EAAE,IAAkB;EACpD,MAAM,UAAU,IAAI,IAAI,MAAM,SAAS;EACvC,MAAM,QAAsB,EAAE;EAE9B,iDAAiD;EACjD,KAAK,MAAM;IACT,OAAM,IAAI;MACR,IAAI,QAAQ,GAAG,CAAC,qBAAqB;QACnC,MAAM,IAAI,IAAI,qBAAqB;MACrC;IACF;EACF;EAEA,0DAA0D;EAC1D,IAAI,QAAQ,GAAG,CAAC,0BAA0B;IACxC,MAAM,IAAI,IAAI,wBAAwB;EACxC;EACA,IAAI,QAAQ,GAAG,CAAC,cAAc;IAC5B,MAAM,IAAI,IAAI,cAAc;EAC9B;EACA,IAAI,QAAQ,GAAG,CAAC,wBAAwB;IACtC,MAAM,IAAI,IAAI,uBAAuB;EACvC;EAEA,OAAO;AACT"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Static analysis for softcode: `lint()` runs built-in rules and returns
|
|
4
|
+
* `Diagnostic` findings with severity, rule ID, and the offending AST node.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { parse } from "@ursamu/mushcode/parse";
|
|
9
|
+
* import { lint } from "@ursamu/mushcode/lint";
|
|
10
|
+
*
|
|
11
|
+
* const ast = parse("$finger:@pemit %#=[u(me/FN)]");
|
|
12
|
+
* const diags = lint(ast);
|
|
13
|
+
* diags.forEach(d => console.log(`[${d.severity}] ${d.rule}: ${d.message}`));
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import type { ASTNode } from "../../parser/mod.ts";
|
|
17
|
+
import { walk } from "../traverse/walk.js";
|
|
18
|
+
import { checkMissingWildcard } from "./rules/missing_wildcard.js";
|
|
19
|
+
import { checkIterVarOutsideIter } from "./rules/iter_var_outside_iter.js";
|
|
20
|
+
import { checkArgCount } from "./rules/arg_count.js";
|
|
21
|
+
import { checkRegisterBeforeSet } from "./rules/register_before_set.js";
|
|
22
|
+
|
|
23
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/** Severity level of a lint diagnostic. */
|
|
26
|
+
export type Severity = "error" | "warning" | "info";
|
|
27
|
+
|
|
28
|
+
/** A single lint finding produced by a rule. */
|
|
29
|
+
export interface Diagnostic {
|
|
30
|
+
/** Identifier for the rule that produced this diagnostic. */
|
|
31
|
+
rule: string;
|
|
32
|
+
/** How serious the finding is. */
|
|
33
|
+
severity: Severity;
|
|
34
|
+
/** Human-readable description of the issue. */
|
|
35
|
+
message: string;
|
|
36
|
+
/** The AST node most closely associated with the issue. */
|
|
37
|
+
node: ASTNode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Options passed to the {@link lint} function. */
|
|
41
|
+
export interface LintOptions {
|
|
42
|
+
/** Whitelist of rule IDs to run. Omit to run all rules. */
|
|
43
|
+
rules?: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Available rules ───────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
/** All built-in rule IDs. Pass a subset to `LintOptions.rules` to enable only those rules. */
|
|
49
|
+
export const RULES = [
|
|
50
|
+
"missing-wildcard",
|
|
51
|
+
"iter-var-outside-iter",
|
|
52
|
+
"arg-count",
|
|
53
|
+
"register-before-set",
|
|
54
|
+
] as const;
|
|
55
|
+
|
|
56
|
+
/** Union type of every known rule ID string. */
|
|
57
|
+
export type RuleId = (typeof RULES)[number];
|
|
58
|
+
|
|
59
|
+
// ── lint ──────────────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Run static analysis on an AST and return an array of diagnostics.
|
|
63
|
+
*
|
|
64
|
+
* Rules are applied at every DollarPattern/ListenPattern/FunctionCall/… node
|
|
65
|
+
* anywhere in the tree, so this works correctly on CommandList roots too.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* const diags = lint(parse("$+finger *:@pemit %#=[u(me/FN_FINGER,%0)]"));
|
|
69
|
+
* diags.forEach(d => console.log(`[${d.severity}] ${d.rule}: ${d.message}`));
|
|
70
|
+
*/
|
|
71
|
+
export function lint(root: ASTNode, opts?: LintOptions): Diagnostic[] {
|
|
72
|
+
const enabled = new Set(opts?.rules ?? RULES);
|
|
73
|
+
const diags: Diagnostic[] = [];
|
|
74
|
+
|
|
75
|
+
// Rules that examine individual nodes as we walk
|
|
76
|
+
walk(root, {
|
|
77
|
+
enter(node) {
|
|
78
|
+
if (enabled.has("missing-wildcard")) {
|
|
79
|
+
diags.push(...checkMissingWildcard(node));
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Rules that need a full-tree view (run once on the root)
|
|
85
|
+
if (enabled.has("iter-var-outside-iter")) {
|
|
86
|
+
diags.push(...checkIterVarOutsideIter(root));
|
|
87
|
+
}
|
|
88
|
+
if (enabled.has("arg-count")) {
|
|
89
|
+
diags.push(...checkArgCount(root));
|
|
90
|
+
}
|
|
91
|
+
if (enabled.has("register-before-set")) {
|
|
92
|
+
diags.push(...checkRegisterBeforeSet(root));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return diags;
|
|
96
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { findAll } from "../../traverse/walk.js";
|
|
2
|
+
import { ARITIES } from "../builtin_arities.js";
|
|
3
|
+
/**
|
|
4
|
+
* arg-count
|
|
5
|
+
*
|
|
6
|
+
* Reports FunctionCall nodes where the number of arguments falls outside the
|
|
7
|
+
* known [minArgs, maxArgs] range for that built-in function.
|
|
8
|
+
*
|
|
9
|
+
* Only functions listed in ARITIES are checked; user-defined functions and
|
|
10
|
+
* unrecognised names are silently skipped.
|
|
11
|
+
*/ export function checkArgCount(root) {
|
|
12
|
+
const diagnostics = [];
|
|
13
|
+
for (const node of findAll(root, "FunctionCall")){
|
|
14
|
+
const name = node.name.toLowerCase();
|
|
15
|
+
const arity = ARITIES[name];
|
|
16
|
+
if (!arity) continue; // not a known built-in — skip
|
|
17
|
+
const [min, max] = arity;
|
|
18
|
+
const argc = node.args.length;
|
|
19
|
+
if (argc < min) {
|
|
20
|
+
diagnostics.push({
|
|
21
|
+
rule: "arg-count",
|
|
22
|
+
severity: "warning",
|
|
23
|
+
message: `${node.name}() requires at least ${min} argument(s), got ${argc}`,
|
|
24
|
+
node
|
|
25
|
+
});
|
|
26
|
+
} else if (argc > max) {
|
|
27
|
+
diagnostics.push({
|
|
28
|
+
rule: "arg-count",
|
|
29
|
+
severity: "warning",
|
|
30
|
+
message: `${node.name}() accepts at most ${max} argument(s), got ${argc}`,
|
|
31
|
+
node
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return diagnostics;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=arg_count.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arg_count.js","sources":["./arg_count.ts"],"names":[],"mappings":"AAEA,SAAS,OAAO,iCAA0C;AAC1D,SAAS,OAAO,gCAAyC;AAEzD;;;;;;;;CAQC,GACD,OAAO,SAAS,cAAc,IAAa;EACzC,MAAM,cAA4B,EAAE;EAEpC,KAAK,MAAM,QAAQ,QAAQ,MAAM,gBAAiB;IAChD,MAAM,OAAQ,AAAC,KAAK,IAAI,CAAY,WAAW;IAC/C,MAAM,QAAQ,OAAO,CAAC,KAAK;IAC3B,IAAI,CAAC,OAAO,UAAU,8BAA8B;IAEpD,MAAM,CAAC,KAAK,IAAI,GAAG;IACnB,MAAM,OAAO,AAAC,KAAK,IAAI,CAAe,MAAM;IAE5C,IAAI,OAAO,KAAK;MACd,YAAY,IAAI,CAAC;QACf,MAAU;QACV,UAAU;QACV,SAAU,GAAG,KAAK,IAAI,CAAC,qBAAqB,EAAE,IAAI,kBAAkB,EAAE,MAAM;QAC5E;MACF;IACF,OAAO,IAAI,OAAO,KAAK;MACrB,YAAY,IAAI,CAAC;QACf,MAAU;QACV,UAAU;QACV,SAAU,GAAG,KAAK,IAAI,CAAC,mBAAmB,EAAE,IAAI,kBAAkB,EAAE,MAAM;QAC1E;MACF;IACF;EACF;EAEA,OAAO;AACT"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ASTNode } from "../../../parser/mod.ts";
|
|
2
|
+
import type { Diagnostic } from "../mod.ts";
|
|
3
|
+
import { findAll } from "../../traverse/walk.js";
|
|
4
|
+
import { ARITIES } from "../builtin_arities.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* arg-count
|
|
8
|
+
*
|
|
9
|
+
* Reports FunctionCall nodes where the number of arguments falls outside the
|
|
10
|
+
* known [minArgs, maxArgs] range for that built-in function.
|
|
11
|
+
*
|
|
12
|
+
* Only functions listed in ARITIES are checked; user-defined functions and
|
|
13
|
+
* unrecognised names are silently skipped.
|
|
14
|
+
*/
|
|
15
|
+
export function checkArgCount(root: ASTNode): Diagnostic[] {
|
|
16
|
+
const diagnostics: Diagnostic[] = [];
|
|
17
|
+
|
|
18
|
+
for (const node of findAll(root, "FunctionCall")) {
|
|
19
|
+
const name = (node.name as string).toLowerCase();
|
|
20
|
+
const arity = ARITIES[name];
|
|
21
|
+
if (!arity) continue; // not a known built-in — skip
|
|
22
|
+
|
|
23
|
+
const [min, max] = arity;
|
|
24
|
+
const argc = (node.args as ASTNode[]).length;
|
|
25
|
+
|
|
26
|
+
if (argc < min) {
|
|
27
|
+
diagnostics.push({
|
|
28
|
+
rule: "arg-count",
|
|
29
|
+
severity: "warning",
|
|
30
|
+
message: `${node.name}() requires at least ${min} argument(s), got ${argc}`,
|
|
31
|
+
node,
|
|
32
|
+
});
|
|
33
|
+
} else if (argc > max) {
|
|
34
|
+
diagnostics.push({
|
|
35
|
+
rule: "arg-count",
|
|
36
|
+
severity: "warning",
|
|
37
|
+
message: `${node.name}() accepts at most ${max} argument(s), got ${argc}`,
|
|
38
|
+
node,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return diagnostics;
|
|
44
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { walk } from "../../traverse/walk.js";
|
|
2
|
+
const ITER_VARS = new Set([
|
|
3
|
+
"##",
|
|
4
|
+
"#@",
|
|
5
|
+
"#$"
|
|
6
|
+
]);
|
|
7
|
+
/**
|
|
8
|
+
* iter-var-outside-iter
|
|
9
|
+
*
|
|
10
|
+
* Reports when ##, #@, or #$ (iteration SpecialVars) appear outside an
|
|
11
|
+
* iter() function call or @dolist command, where they have no defined value.
|
|
12
|
+
*
|
|
13
|
+
* Valid contexts:
|
|
14
|
+
* iter(list, body) — ## is the current element in `body`
|
|
15
|
+
* @dolist list={body} — ## is the current element in `body`
|
|
16
|
+
*
|
|
17
|
+
* The rule is conservative: any nesting under an iter/dolist suppresses the
|
|
18
|
+
* warning for the entire subtree, avoiding false positives on patterns like
|
|
19
|
+
* iter(iter(inner, ##), ##).
|
|
20
|
+
*/ export function checkIterVarOutsideIter(root) {
|
|
21
|
+
const diagnostics = [];
|
|
22
|
+
let iterDepth = 0;
|
|
23
|
+
walk(root, {
|
|
24
|
+
enter (node) {
|
|
25
|
+
if (isIterContext(node)) {
|
|
26
|
+
iterDepth++;
|
|
27
|
+
return; // continue into children
|
|
28
|
+
}
|
|
29
|
+
if (node.type === "SpecialVar" && ITER_VARS.has(node.code)) {
|
|
30
|
+
if (iterDepth === 0) {
|
|
31
|
+
diagnostics.push({
|
|
32
|
+
rule: "iter-var-outside-iter",
|
|
33
|
+
severity: "warning",
|
|
34
|
+
message: `"${node.code}" is only meaningful inside iter() or @dolist`,
|
|
35
|
+
node
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
leave (node) {
|
|
41
|
+
if (isIterContext(node)) iterDepth--;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return diagnostics;
|
|
45
|
+
}
|
|
46
|
+
function isIterContext(node) {
|
|
47
|
+
if (node.type === "FunctionCall") {
|
|
48
|
+
return node.name.toLowerCase() === "iter" || node.name.toLowerCase() === "ilist" || node.name.toLowerCase() === "map";
|
|
49
|
+
}
|
|
50
|
+
if (node.type === "AtCommand") {
|
|
51
|
+
return node.name.toLowerCase() === "dolist";
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=iter_var_outside_iter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iter_var_outside_iter.js","sources":["./iter_var_outside_iter.ts"],"names":[],"mappings":"AAEA,SAAS,IAAI,iCAA6C;AAE1D,MAAM,YAAY,IAAI,IAAI;EAAC;EAAM;EAAM;CAAK;AAE5C;;;;;;;;;;;;;CAaC,GACD,OAAO,SAAS,wBAAwB,IAAa;EACnD,MAAM,cAA4B,EAAE;EACpC,IAAI,YAAY;EAEhB,KAAK,MAAM;IACT,OAAM,IAAI;MACR,IAAI,cAAc,OAAO;QACvB;QACA,QAAQ,yBAAyB;MACnC;MACA,IAAI,KAAK,IAAI,KAAK,gBAAgB,UAAU,GAAG,CAAC,KAAK,IAAI,GAAa;QACpE,IAAI,cAAc,GAAG;UACnB,YAAY,IAAI,CAAC;YACf,MAAU;YACV,UAAU;YACV,SAAU,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,6CAA6C,CAAC;YACtE;UACF;QACF;MACF;IACF;IACA,OAAM,IAAI;MACR,IAAI,cAAc,OAAO;IAC3B;EACF;EAEA,OAAO;AACT;AAEA,SAAS,cAAc,IAAa;EAClC,IAAI,KAAK,IAAI,KAAK,gBAAgB;IAChC,OAAO,AAAC,KAAK,IAAI,CAAY,WAAW,OAAO,UACxC,AAAC,KAAK,IAAI,CAAY,WAAW,OAAO,WACxC,AAAC,KAAK,IAAI,CAAY,WAAW,OAAO;EACjD;EACA,IAAI,KAAK,IAAI,KAAK,aAAa;IAC7B,OAAO,AAAC,KAAK,IAAI,CAAY,WAAW,OAAO;EACjD;EACA,OAAO;AACT"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ASTNode } from "../../../parser/mod.ts";
|
|
2
|
+
import type { Diagnostic } from "../mod.ts";
|
|
3
|
+
import { walk } from "../../traverse/walk.js";
|
|
4
|
+
|
|
5
|
+
const ITER_VARS = new Set(["##", "#@", "#$"]);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* iter-var-outside-iter
|
|
9
|
+
*
|
|
10
|
+
* Reports when ##, #@, or #$ (iteration SpecialVars) appear outside an
|
|
11
|
+
* iter() function call or @dolist command, where they have no defined value.
|
|
12
|
+
*
|
|
13
|
+
* Valid contexts:
|
|
14
|
+
* iter(list, body) — ## is the current element in `body`
|
|
15
|
+
* @dolist list={body} — ## is the current element in `body`
|
|
16
|
+
*
|
|
17
|
+
* The rule is conservative: any nesting under an iter/dolist suppresses the
|
|
18
|
+
* warning for the entire subtree, avoiding false positives on patterns like
|
|
19
|
+
* iter(iter(inner, ##), ##).
|
|
20
|
+
*/
|
|
21
|
+
export function checkIterVarOutsideIter(root: ASTNode): Diagnostic[] {
|
|
22
|
+
const diagnostics: Diagnostic[] = [];
|
|
23
|
+
let iterDepth = 0;
|
|
24
|
+
|
|
25
|
+
walk(root, {
|
|
26
|
+
enter(node) {
|
|
27
|
+
if (isIterContext(node)) {
|
|
28
|
+
iterDepth++;
|
|
29
|
+
return; // continue into children
|
|
30
|
+
}
|
|
31
|
+
if (node.type === "SpecialVar" && ITER_VARS.has(node.code as string)) {
|
|
32
|
+
if (iterDepth === 0) {
|
|
33
|
+
diagnostics.push({
|
|
34
|
+
rule: "iter-var-outside-iter",
|
|
35
|
+
severity: "warning",
|
|
36
|
+
message: `"${node.code}" is only meaningful inside iter() or @dolist`,
|
|
37
|
+
node,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
leave(node) {
|
|
43
|
+
if (isIterContext(node)) iterDepth--;
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return diagnostics;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isIterContext(node: ASTNode): boolean {
|
|
51
|
+
if (node.type === "FunctionCall") {
|
|
52
|
+
return (node.name as string).toLowerCase() === "iter" ||
|
|
53
|
+
(node.name as string).toLowerCase() === "ilist" ||
|
|
54
|
+
(node.name as string).toLowerCase() === "map";
|
|
55
|
+
}
|
|
56
|
+
if (node.type === "AtCommand") {
|
|
57
|
+
return (node.name as string).toLowerCase() === "dolist";
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { findAll } from "../../traverse/walk.js";
|
|
2
|
+
/**
|
|
3
|
+
* missing-wildcard
|
|
4
|
+
*
|
|
5
|
+
* Reports when a DollarPattern or ListenPattern action uses a positional
|
|
6
|
+
* substitution %N (N ≥ 1) but the pattern contains fewer than N wildcards.
|
|
7
|
+
*
|
|
8
|
+
* In TinyMUX:
|
|
9
|
+
* %0 = the full matched input (always available)
|
|
10
|
+
* %1 = first wildcard match, %2 = second, … %9 = ninth
|
|
11
|
+
*
|
|
12
|
+
* So if the action uses %2, the pattern must have at least 2 wildcards.
|
|
13
|
+
*/ export function checkMissingWildcard(node) {
|
|
14
|
+
if (node.type !== "DollarPattern" && node.type !== "ListenPattern") return [];
|
|
15
|
+
const pattern = node.pattern;
|
|
16
|
+
const action = node.action;
|
|
17
|
+
const wildcardCount = findAll(pattern, "Wildcard").length;
|
|
18
|
+
const maxPositional = findAll(action, "Substitution").map((s)=>s.code).filter((code)=>/^[1-9]$/.test(code)).reduce((max, code)=>Math.max(max, parseInt(code)), 0);
|
|
19
|
+
if (maxPositional > wildcardCount) {
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
rule: "missing-wildcard",
|
|
23
|
+
severity: "warning",
|
|
24
|
+
message: `Pattern has ${wildcardCount} wildcard(s) but action uses %${maxPositional} ` + `— needs at least ${maxPositional} wildcard(s) in pattern`,
|
|
25
|
+
node: pattern
|
|
26
|
+
}
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=missing_wildcard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missing_wildcard.js","sources":["./missing_wildcard.ts"],"names":[],"mappings":"AAEA,SAAS,OAAO,iCAA0C;AAE1D;;;;;;;;;;;CAWC,GACD,OAAO,SAAS,qBAAqB,IAAa;EAChD,IAAI,KAAK,IAAI,KAAK,mBAAmB,KAAK,IAAI,KAAK,iBAAiB,OAAO,EAAE;EAE7E,MAAM,UAAU,KAAK,OAAO;EAC5B,MAAM,SAAU,KAAK,MAAM;EAE3B,MAAM,gBAAgB,QAAQ,SAAS,YAAY,MAAM;EAEzD,MAAM,gBAAgB,QAAQ,QAAQ,gBACnC,GAAG,CAAC,CAAA,IAAK,EAAE,IAAI,EACf,MAAM,CAAC,CAAA,OAAQ,UAAU,IAAI,CAAC,OAC9B,MAAM,CAAC,CAAC,KAAK,OAAS,KAAK,GAAG,CAAC,KAAK,SAAS,QAAQ;EAExD,IAAI,gBAAgB,eAAe;IACjC,OAAO;MAAC;QACN,MAAU;QACV,UAAU;QACV,SAAU,CAAC,YAAY,EAAE,cAAc,8BAA8B,EAAE,cAAc,CAAC,CAAC,GAC7E,CAAC,iBAAiB,EAAE,cAAc,uBAAuB,CAAC;QACpE,MAAM;MACR;KAAE;EACJ;EACA,OAAO,EAAE;AACX"}
|