@rhost/testkit 0.1.1 → 1.3.2

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.
Files changed (110) hide show
  1. package/README.md +393 -5
  2. package/ROADMAP.md +241 -0
  3. package/dist/benchmark.d.ts +44 -0
  4. package/dist/benchmark.d.ts.map +1 -0
  5. package/dist/benchmark.js +118 -0
  6. package/dist/benchmark.js.map +1 -0
  7. package/dist/cli/deploy.d.ts +2 -0
  8. package/dist/cli/deploy.d.ts.map +1 -0
  9. package/dist/cli/deploy.js +120 -0
  10. package/dist/cli/deploy.js.map +1 -0
  11. package/dist/cli/fmt.d.ts +2 -0
  12. package/dist/cli/fmt.d.ts.map +1 -0
  13. package/dist/cli/fmt.js +119 -0
  14. package/dist/cli/fmt.js.map +1 -0
  15. package/dist/cli/index.d.ts +3 -0
  16. package/dist/cli/index.d.ts.map +1 -0
  17. package/dist/cli/index.js +81 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/cli/init.d.ts +2 -0
  20. package/dist/cli/init.d.ts.map +1 -0
  21. package/dist/cli/init.js +210 -0
  22. package/dist/cli/init.js.map +1 -0
  23. package/dist/cli/validate.d.ts +2 -0
  24. package/dist/cli/validate.d.ts.map +1 -0
  25. package/dist/cli/validate.js +126 -0
  26. package/dist/cli/validate.js.map +1 -0
  27. package/dist/cli/watch.d.ts +2 -0
  28. package/dist/cli/watch.d.ts.map +1 -0
  29. package/dist/cli/watch.js +136 -0
  30. package/dist/cli/watch.js.map +1 -0
  31. package/dist/client.d.ts +56 -0
  32. package/dist/client.d.ts.map +1 -1
  33. package/dist/client.js +117 -30
  34. package/dist/client.js.map +1 -1
  35. package/dist/container.d.ts.map +1 -1
  36. package/dist/container.js +1 -1
  37. package/dist/container.js.map +1 -1
  38. package/dist/deployer.d.ts +86 -0
  39. package/dist/deployer.d.ts.map +1 -0
  40. package/dist/deployer.js +154 -0
  41. package/dist/deployer.js.map +1 -0
  42. package/dist/expect.d.ts +27 -1
  43. package/dist/expect.d.ts.map +1 -1
  44. package/dist/expect.js +47 -2
  45. package/dist/expect.js.map +1 -1
  46. package/dist/index.d.ts +10 -3
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +39 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/preflight.d.ts +70 -0
  51. package/dist/preflight.d.ts.map +1 -0
  52. package/dist/preflight.js +121 -0
  53. package/dist/preflight.js.map +1 -0
  54. package/dist/reporter.d.ts +2 -0
  55. package/dist/reporter.d.ts.map +1 -1
  56. package/dist/reporter.js +24 -1
  57. package/dist/reporter.js.map +1 -1
  58. package/dist/runner.d.ts +83 -2
  59. package/dist/runner.d.ts.map +1 -1
  60. package/dist/runner.js +137 -8
  61. package/dist/runner.js.map +1 -1
  62. package/dist/snapshots.d.ts +84 -0
  63. package/dist/snapshots.d.ts.map +1 -0
  64. package/dist/snapshots.js +230 -0
  65. package/dist/snapshots.js.map +1 -0
  66. package/dist/validator/builtins.d.ts +18 -0
  67. package/dist/validator/builtins.d.ts.map +1 -0
  68. package/dist/validator/builtins.js +265 -0
  69. package/dist/validator/builtins.js.map +1 -0
  70. package/dist/validator/checker.d.ts +13 -0
  71. package/dist/validator/checker.d.ts.map +1 -0
  72. package/dist/validator/checker.js +111 -0
  73. package/dist/validator/checker.js.map +1 -0
  74. package/dist/validator/clobber.d.ts +7 -0
  75. package/dist/validator/clobber.d.ts.map +1 -0
  76. package/dist/validator/clobber.js +102 -0
  77. package/dist/validator/clobber.js.map +1 -0
  78. package/dist/validator/compat.d.ts +19 -0
  79. package/dist/validator/compat.d.ts.map +1 -0
  80. package/dist/validator/compat.js +58 -0
  81. package/dist/validator/compat.js.map +1 -0
  82. package/dist/validator/formatter.d.ts +21 -0
  83. package/dist/validator/formatter.d.ts.map +1 -0
  84. package/dist/validator/formatter.js +120 -0
  85. package/dist/validator/formatter.js.map +1 -0
  86. package/dist/validator/index.d.ts +57 -0
  87. package/dist/validator/index.d.ts.map +1 -0
  88. package/dist/validator/index.js +133 -0
  89. package/dist/validator/index.js.map +1 -0
  90. package/dist/validator/parser.d.ts +16 -0
  91. package/dist/validator/parser.d.ts.map +1 -0
  92. package/dist/validator/parser.js +260 -0
  93. package/dist/validator/parser.js.map +1 -0
  94. package/dist/validator/tokenizer.d.ts +28 -0
  95. package/dist/validator/tokenizer.d.ts.map +1 -0
  96. package/dist/validator/tokenizer.js +174 -0
  97. package/dist/validator/tokenizer.js.map +1 -0
  98. package/dist/validator/types.d.ts +55 -0
  99. package/dist/validator/types.d.ts.map +1 -0
  100. package/dist/validator/types.js +6 -0
  101. package/dist/validator/types.js.map +1 -0
  102. package/dist/watcher.d.ts +44 -0
  103. package/dist/watcher.d.ts.map +1 -0
  104. package/dist/watcher.js +297 -0
  105. package/dist/watcher.js.map +1 -0
  106. package/dist/world.d.ts +79 -0
  107. package/dist/world.d.ts.map +1 -1
  108. package/dist/world.js +167 -1
  109. package/dist/world.js.map +1 -1
  110. package/package.json +19 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checker.js","sourceRoot":"","sources":["../../src/validator/checker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { ASTNode, Diagnostic } from './types';
2
+ /**
3
+ * Walk the AST and emit W006 warnings for every `setq()` call that appears
4
+ * inside a loop-body argument without an intervening `localize()`.
5
+ */
6
+ export declare function registerClobberCheck(nodes: ASTNode[]): Diagnostic[];
7
+ //# sourceMappingURL=clobber.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clobber.d.ts","sourceRoot":"","sources":["../../src/validator/clobber.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAoB,MAAM,SAAS,CAAC;AAsBhE;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,UAAU,EAAE,CAInE"}
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // Register clobber / re-entrancy analyzer
4
+ //
5
+ // Detects `setq()` calls inside loop-body arguments (iter, parse, filter,
6
+ // map, fold, step, munge, filterfun). Because MUSH registers (%q0–%q9) are
7
+ // scoped per queue entry, concurrent or nested invocations of the same
8
+ // attribute can silently overwrite each other's register values.
9
+ //
10
+ // Safe escape hatch: wrapping the body in `localize()` creates a fresh
11
+ // register scope, so setq() inside localize() is not flagged.
12
+ // ---------------------------------------------------------------------------
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.registerClobberCheck = registerClobberCheck;
15
+ // Functions whose body argument is evaluated once per list element.
16
+ // Value is the index of the first "body" argument (0-based).
17
+ // All arguments at index >= bodyArgIndex are treated as body.
18
+ const LOOP_FUNCTIONS = new Map([
19
+ ['iter', 1], // iter(list, BODY, ...)
20
+ ['parse', 1], // parse(list, BODY, ...)
21
+ ['filter', 0], // filter(BODY/attr, list, ...)
22
+ ['filterfun', 0], // filterfun(BODY/attr, list, ...)
23
+ ['map', 0], // map(BODY/attr, list, ...)
24
+ ['fold', 0], // fold(BODY/attr, base, list, ...)
25
+ ['step', 0], // step(BODY/attr, list, ...)
26
+ ['munge', 0], // munge(BODY/attr, list, ...)
27
+ ['matchall', 1], // matchall(list, pattern, ...) — pattern is text, not code
28
+ ['graball', 1], // graball(list, pattern, ...)
29
+ ['sortby', 0], // sortby(BODY/attr, list, ...)
30
+ ]);
31
+ // Functions that create a new register scope — safe for setq()
32
+ const SCOPE_WRAPPERS = new Set(['localize']);
33
+ /**
34
+ * Walk the AST and emit W006 warnings for every `setq()` call that appears
35
+ * inside a loop-body argument without an intervening `localize()`.
36
+ */
37
+ function registerClobberCheck(nodes) {
38
+ const diagnostics = [];
39
+ walkNodes(nodes, false, diagnostics);
40
+ return diagnostics;
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // Internal walkers
44
+ // ---------------------------------------------------------------------------
45
+ function walkNodes(nodes, inLoop, out) {
46
+ for (const node of nodes) {
47
+ walkNode(node, inLoop, out);
48
+ }
49
+ }
50
+ function walkNode(node, inLoop, out) {
51
+ switch (node.type) {
52
+ case 'BracketEval':
53
+ walkNodes(node.nodes, inLoop, out);
54
+ break;
55
+ case 'FunctionCall':
56
+ walkFunctionCall(node, inLoop, out);
57
+ break;
58
+ // RawText / Substitution — nothing to check
59
+ default:
60
+ break;
61
+ }
62
+ }
63
+ function walkFunctionCall(node, inLoop, out) {
64
+ const key = node.name.toLowerCase();
65
+ // localize() creates a new register scope — safe to setq() inside
66
+ if (SCOPE_WRAPPERS.has(key)) {
67
+ for (const arg of node.args) {
68
+ walkNodes(arg, false, out); // reset inLoop to false
69
+ }
70
+ return;
71
+ }
72
+ // setq() inside a loop body is the hazard
73
+ if (inLoop && key === 'setq') {
74
+ out.push({
75
+ severity: 'warning',
76
+ code: 'W006',
77
+ message: `'setq' inside a loop body risks register clobber on re-entrant calls — ` +
78
+ `wrap the body with localize() to create a safe register scope`,
79
+ offset: node.nameOffset,
80
+ length: node.name.length,
81
+ });
82
+ // Still recurse so nested loops / deeper setqs are also caught
83
+ for (const arg of node.args) {
84
+ walkNodes(arg, inLoop, out);
85
+ }
86
+ return;
87
+ }
88
+ const bodyArgIndex = LOOP_FUNCTIONS.get(key);
89
+ if (bodyArgIndex !== undefined) {
90
+ for (let i = 0; i < node.args.length; i++) {
91
+ // Arguments before the body start index are data (list, base value, etc.) — not loop body
92
+ const isBodyArg = i >= bodyArgIndex;
93
+ walkNodes(node.args[i], isBodyArg, out);
94
+ }
95
+ return;
96
+ }
97
+ // Regular function — propagate current inLoop state
98
+ for (const arg of node.args) {
99
+ walkNodes(arg, inLoop, out);
100
+ }
101
+ }
102
+ //# sourceMappingURL=clobber.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clobber.js","sourceRoot":"","sources":["../../src/validator/clobber.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { ASTNode } from './types';
2
+ import { Platform } from './builtins';
3
+ export interface CompatibilityEntry {
4
+ name: string;
5
+ /** Platforms that support this function. */
6
+ platforms: Platform[];
7
+ }
8
+ export interface CompatibilityReport {
9
+ /** Functions that are not available on all platforms. */
10
+ restricted: CompatibilityEntry[];
11
+ /** true if every known function in the expression is universally portable. */
12
+ portable: boolean;
13
+ }
14
+ /**
15
+ * Walk a parsed AST and collect any platform-restricted function calls.
16
+ * Called internally by `compatibilityReport` in the validator index.
17
+ */
18
+ export declare function compatibilityCheck(nodes: ASTNode[]): CompatibilityReport;
19
+ //# sourceMappingURL=compat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compat.d.ts","sourceRoot":"","sources":["../../src/validator/compat.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,EAAoB,MAAM,SAAS,CAAC;AACpD,OAAO,EAAqB,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,WAAW,kBAAkB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,SAAS,EAAE,QAAQ,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAChC,yDAAyD;IACzD,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,8EAA8E;IAC9E,QAAQ,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,mBAAmB,CAYxE"}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // Dialect compatibility report
4
+ //
5
+ // Walks the AST and reports which functions are restricted to specific
6
+ // platforms (Rhost-only, Penn+Rhost, etc.) vs. universally available.
7
+ // ---------------------------------------------------------------------------
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.compatibilityCheck = compatibilityCheck;
10
+ const builtins_1 = require("./builtins");
11
+ /**
12
+ * Walk a parsed AST and collect any platform-restricted function calls.
13
+ * Called internally by `compatibilityReport` in the validator index.
14
+ */
15
+ function compatibilityCheck(nodes) {
16
+ const seen = new Map();
17
+ walkNodes(nodes, seen);
18
+ const restricted = Array.from(seen.entries()).map(([name, platforms]) => ({ name, platforms }));
19
+ return {
20
+ restricted,
21
+ portable: restricted.length === 0,
22
+ };
23
+ }
24
+ // ---------------------------------------------------------------------------
25
+ // Internal walkers
26
+ // ---------------------------------------------------------------------------
27
+ function walkNodes(nodes, seen) {
28
+ for (const node of nodes) {
29
+ walkNode(node, seen);
30
+ }
31
+ }
32
+ function walkNode(node, seen) {
33
+ switch (node.type) {
34
+ case 'FunctionCall':
35
+ checkFunction(node, seen);
36
+ break;
37
+ case 'BracketEval':
38
+ walkNodes(node.nodes, seen);
39
+ break;
40
+ default:
41
+ break;
42
+ }
43
+ }
44
+ function checkFunction(node, seen) {
45
+ const key = node.name.toLowerCase();
46
+ const sig = builtins_1.BUILTIN_FUNCTIONS.get(key);
47
+ if (sig?.platforms && sig.platforms.length > 0) {
48
+ // Deduplicate — only record each restricted function once
49
+ if (!seen.has(key)) {
50
+ seen.set(key, sig.platforms);
51
+ }
52
+ }
53
+ // Recurse into arguments
54
+ for (const arg of node.args) {
55
+ walkNodes(arg, seen);
56
+ }
57
+ }
58
+ //# sourceMappingURL=compat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compat.js","sourceRoot":"","sources":["../../src/validator/compat.ts"],"names":[],"mappings":";AAAA,8EAA8E;AAC9E,+BAA+B;AAC/B,EAAE;AACF,uEAAuE;AACvE,sEAAsE;AACtE,8EAA8E;;AAsB9E,gDAYC;AA/BD,yCAAyD;AAezD;;;GAGG;AACH,SAAgB,kBAAkB,CAAC,KAAgB;IAC/C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC3C,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAEvB,MAAM,UAAU,GAAyB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CACnE,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAC/C,CAAC;IAEF,OAAO;QACH,UAAU;QACV,QAAQ,EAAE,UAAU,CAAC,MAAM,KAAK,CAAC;KACpC,CAAC;AACN,CAAC;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,SAAS,SAAS,CAAC,KAAgB,EAAE,IAA6B;IAC9D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,IAAa,EAAE,IAA6B;IAC1D,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,cAAc;YACf,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC1B,MAAM;QACV,KAAK,aAAa;YACd,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC5B,MAAM;QACV;YACI,MAAM;IACd,CAAC;AACL,CAAC;AAED,SAAS,aAAa,CAAC,IAAsB,EAAE,IAA6B;IACxE,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IACpC,MAAM,GAAG,GAAG,4BAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAEvC,IAAI,GAAG,EAAE,SAAS,IAAI,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7C,0DAA0D;QAC1D,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACjB,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;QACjC,CAAC;IACL,CAAC;IAED,yBAAyB;IACzB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC;AACL,CAAC"}
@@ -0,0 +1,21 @@
1
+ export interface FormatOptions {
2
+ /** Add newlines + indentation for human readability. Default: false. */
3
+ pretty?: boolean;
4
+ /** Lowercase all function names. Default: false. */
5
+ lowercase?: boolean;
6
+ }
7
+ export interface FormatResult {
8
+ formatted: string;
9
+ changed: boolean;
10
+ }
11
+ /**
12
+ * Format a MUSHcode expression.
13
+ *
14
+ * Compact mode (default): strips extra whitespace around `(`, `,`, `)` while
15
+ * preserving whitespace inside argument text.
16
+ *
17
+ * Pretty mode: additionally adds newlines + indentation at each nesting level
18
+ * for human readability. Not suitable for direct upload to a MUSH server.
19
+ */
20
+ export declare function format(expr: string, options?: FormatOptions): FormatResult;
21
+ //# sourceMappingURL=formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../src/validator/formatter.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,YAAY,CAO9E"}
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // Softcode Formatter — serialize an AST back to a normalized string
4
+ // ---------------------------------------------------------------------------
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.format = format;
7
+ const tokenizer_1 = require("./tokenizer");
8
+ const parser_1 = require("./parser");
9
+ /**
10
+ * Format a MUSHcode expression.
11
+ *
12
+ * Compact mode (default): strips extra whitespace around `(`, `,`, `)` while
13
+ * preserving whitespace inside argument text.
14
+ *
15
+ * Pretty mode: additionally adds newlines + indentation at each nesting level
16
+ * for human readability. Not suitable for direct upload to a MUSH server.
17
+ */
18
+ function format(expr, options = {}) {
19
+ const tokens = (0, tokenizer_1.tokenize)(expr);
20
+ const { nodes } = (0, parser_1.parse)(tokens);
21
+ const serializer = new Serializer(options);
22
+ const formatted = serializer.serializeNodes(nodes, 0);
23
+ return { formatted, changed: formatted !== expr };
24
+ }
25
+ // ---------------------------------------------------------------------------
26
+ // Serializer
27
+ // ---------------------------------------------------------------------------
28
+ class Serializer {
29
+ constructor(opts) {
30
+ this.opts = opts;
31
+ }
32
+ serializeNodes(nodes, depth) {
33
+ return nodes.map(n => this.serializeNode(n, depth)).join('');
34
+ }
35
+ serializeNode(node, depth) {
36
+ switch (node.type) {
37
+ case 'FunctionCall': return this.serializeFunctionCall(node, depth);
38
+ case 'BracketEval': return this.serializeBracketEval(node, depth);
39
+ case 'Substitution': return node.raw;
40
+ case 'RawText': return node.value;
41
+ }
42
+ }
43
+ serializeFunctionCall(node, depth) {
44
+ const name = this.opts.lowercase ? node.name.toLowerCase() : node.name;
45
+ if (node.args.length === 0) {
46
+ return `${name}()`;
47
+ }
48
+ // Trim leading/trailing RawText whitespace from each arg
49
+ const trimmedArgs = node.args.map(arg => trimArgWhitespace(arg));
50
+ const pretty = this.opts.pretty;
51
+ // Pretty mode: wrap if any arg contains a function call
52
+ if (pretty && hasNestedCall(trimmedArgs)) {
53
+ const indent = ' '.repeat(depth + 1);
54
+ const closingIndent = ' '.repeat(depth);
55
+ const serializedArgs = trimmedArgs.map(arg => indent + this.serializeNodes(arg, depth + 1));
56
+ return `${name}(\n${serializedArgs.join(',\n')}\n${closingIndent})`;
57
+ }
58
+ const serializedArgs = trimmedArgs.map(arg => this.serializeNodes(arg, depth + 1));
59
+ return `${name}(${serializedArgs.join(',')})`;
60
+ }
61
+ serializeBracketEval(node, depth) {
62
+ return `[${this.serializeNodes(node.nodes, depth)}]`;
63
+ }
64
+ }
65
+ // ---------------------------------------------------------------------------
66
+ // Helpers
67
+ // ---------------------------------------------------------------------------
68
+ /** Trim leading and trailing RawText-whitespace-only nodes from an arg list. */
69
+ function trimArgWhitespace(arg) {
70
+ if (arg.length === 0)
71
+ return arg;
72
+ let start = 0;
73
+ let end = arg.length - 1;
74
+ // Trim leading whitespace-only RawText nodes
75
+ while (start <= end) {
76
+ const node = arg[start];
77
+ if (node.type === 'RawText' && node.value.trim() === '') {
78
+ start++;
79
+ }
80
+ else {
81
+ break;
82
+ }
83
+ }
84
+ // Trim trailing whitespace-only RawText nodes
85
+ while (end >= start) {
86
+ const node = arg[end];
87
+ if (node.type === 'RawText' && node.value.trim() === '') {
88
+ end--;
89
+ }
90
+ else {
91
+ break;
92
+ }
93
+ }
94
+ if (start > end)
95
+ return [];
96
+ const trimmed = arg.slice(start, end + 1);
97
+ // Also trim leading whitespace from the value of the first RawText node
98
+ // (handles cases like arg starting with a non-whitespace-only RawText that begins with spaces)
99
+ if (trimmed.length > 0 && trimmed[0].type === 'RawText') {
100
+ const first = trimmed[0];
101
+ const trimmedValue = first.value.trimStart();
102
+ if (trimmedValue !== first.value) {
103
+ trimmed[0] = { ...first, value: trimmedValue };
104
+ }
105
+ }
106
+ // And trailing whitespace from the last RawText node
107
+ if (trimmed.length > 0 && trimmed[trimmed.length - 1].type === 'RawText') {
108
+ const last = trimmed[trimmed.length - 1];
109
+ const trimmedValue = last.value.trimEnd();
110
+ if (trimmedValue !== last.value) {
111
+ trimmed[trimmed.length - 1] = { ...last, value: trimmedValue };
112
+ }
113
+ }
114
+ return trimmed;
115
+ }
116
+ /** Returns true if any arg list contains a FunctionCall node (for pretty indentation decisions). */
117
+ function hasNestedCall(args) {
118
+ return args.some(arg => arg.some(node => node.type === 'FunctionCall' || node.type === 'BracketEval'));
119
+ }
120
+ //# sourceMappingURL=formatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.js","sourceRoot":"","sources":["../../src/validator/formatter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { CompatibilityReport } from './compat';
2
+ import { ValidationResult } from './types';
3
+ export { ValidationResult, Diagnostic, Severity } from './types';
4
+ export { FunctionSignature, BUILTIN_FUNCTIONS, Platform } from './builtins';
5
+ export { CompatibilityReport, CompatibilityEntry } from './compat';
6
+ /**
7
+ * Validate a RhostMUSH softcode expression without a server connection.
8
+ *
9
+ * The validator runs three stages:
10
+ * 1. **Tokenizer** — converts the expression to a flat token stream
11
+ * 2. **Parser** — builds an AST; detects structural errors (unbalanced parens/brackets)
12
+ * 3. **Semantic checker** — validates function names and argument counts
13
+ *
14
+ * `valid` is `true` unless at least one diagnostic has `severity: 'error'`.
15
+ * Warnings do not affect validity.
16
+ *
17
+ * @example
18
+ * import { validate } from '@rhost/testkit/validator';
19
+ *
20
+ * const result = validate('add(2,3)');
21
+ * // { valid: true, diagnostics: [] }
22
+ *
23
+ * const bad = validate('add(2,3');
24
+ * // { valid: false, diagnostics: [{ code: 'E001', severity: 'error', ... }] }
25
+ *
26
+ * @example Catch wrong arg count
27
+ * validate('abs(1,2)');
28
+ * // { valid: false, diagnostics: [{ code: 'E007', ... }] }
29
+ *
30
+ * @example Unknown function (warning, still valid)
31
+ * validate('myfunc(arg)');
32
+ * // { valid: true, diagnostics: [{ code: 'W005', severity: 'warning', ... }] }
33
+ */
34
+ export declare function validate(expr: string): ValidationResult;
35
+ /**
36
+ * Analyse a softcode expression for cross-platform dialect compatibility.
37
+ *
38
+ * Returns a `CompatibilityReport` listing any functions that are not
39
+ * available on all MUSH platforms (PennMUSH, TinyMUX, RhostMUSH).
40
+ *
41
+ * @example
42
+ * const report = compatibilityReport('encode64(hello)');
43
+ * // report.portable === false
44
+ * // report.restricted === [{ name: 'encode64', platforms: ['rhost'] }]
45
+ */
46
+ export declare function compatibilityReport(expr: string): CompatibilityReport;
47
+ /**
48
+ * Validate the softcode expression contained in a file.
49
+ *
50
+ * The entire file content is treated as a single expression.
51
+ * For files with multiple expressions (e.g. one per line) consider
52
+ * reading and validating each line individually.
53
+ *
54
+ * @throws If the file cannot be read.
55
+ */
56
+ export declare function validateFile(filePath: string): ValidationResult;
57
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validator/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAsB,mBAAmB,EAAsB,MAAM,UAAU,CAAC;AACvF,OAAO,EAAc,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEvD,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAwBvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAOrE;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAG/D"}
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // Public API for the RhostMUSH softcode offline validator
4
+ // ---------------------------------------------------------------------------
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.BUILTIN_FUNCTIONS = void 0;
40
+ exports.validate = validate;
41
+ exports.compatibilityReport = compatibilityReport;
42
+ exports.validateFile = validateFile;
43
+ const fs = __importStar(require("fs"));
44
+ const tokenizer_1 = require("./tokenizer");
45
+ const parser_1 = require("./parser");
46
+ const checker_1 = require("./checker");
47
+ const clobber_1 = require("./clobber");
48
+ const compat_1 = require("./compat");
49
+ var builtins_1 = require("./builtins");
50
+ Object.defineProperty(exports, "BUILTIN_FUNCTIONS", { enumerable: true, get: function () { return builtins_1.BUILTIN_FUNCTIONS; } });
51
+ /**
52
+ * Validate a RhostMUSH softcode expression without a server connection.
53
+ *
54
+ * The validator runs three stages:
55
+ * 1. **Tokenizer** — converts the expression to a flat token stream
56
+ * 2. **Parser** — builds an AST; detects structural errors (unbalanced parens/brackets)
57
+ * 3. **Semantic checker** — validates function names and argument counts
58
+ *
59
+ * `valid` is `true` unless at least one diagnostic has `severity: 'error'`.
60
+ * Warnings do not affect validity.
61
+ *
62
+ * @example
63
+ * import { validate } from '@rhost/testkit/validator';
64
+ *
65
+ * const result = validate('add(2,3)');
66
+ * // { valid: true, diagnostics: [] }
67
+ *
68
+ * const bad = validate('add(2,3');
69
+ * // { valid: false, diagnostics: [{ code: 'E001', severity: 'error', ... }] }
70
+ *
71
+ * @example Catch wrong arg count
72
+ * validate('abs(1,2)');
73
+ * // { valid: false, diagnostics: [{ code: 'E007', ... }] }
74
+ *
75
+ * @example Unknown function (warning, still valid)
76
+ * validate('myfunc(arg)');
77
+ * // { valid: true, diagnostics: [{ code: 'W005', severity: 'warning', ... }] }
78
+ */
79
+ function validate(expr) {
80
+ // Empty expression: valid but warn
81
+ if (expr.trim() === '') {
82
+ const diagnostics = [
83
+ {
84
+ severity: 'warning',
85
+ code: 'W001',
86
+ message: 'Expression is empty',
87
+ offset: 0,
88
+ length: 0,
89
+ },
90
+ ];
91
+ return { valid: true, diagnostics };
92
+ }
93
+ const tokens = (0, tokenizer_1.tokenize)(expr);
94
+ const { nodes, diagnostics: structural } = (0, parser_1.parse)(tokens);
95
+ const semantic = (0, checker_1.semanticCheck)(nodes);
96
+ const clobber = (0, clobber_1.registerClobberCheck)(nodes);
97
+ const all = [...structural, ...semantic, ...clobber];
98
+ const valid = !all.some((d) => d.severity === 'error');
99
+ return { valid, diagnostics: all };
100
+ }
101
+ /**
102
+ * Analyse a softcode expression for cross-platform dialect compatibility.
103
+ *
104
+ * Returns a `CompatibilityReport` listing any functions that are not
105
+ * available on all MUSH platforms (PennMUSH, TinyMUX, RhostMUSH).
106
+ *
107
+ * @example
108
+ * const report = compatibilityReport('encode64(hello)');
109
+ * // report.portable === false
110
+ * // report.restricted === [{ name: 'encode64', platforms: ['rhost'] }]
111
+ */
112
+ function compatibilityReport(expr) {
113
+ if (expr.trim() === '') {
114
+ return { restricted: [], portable: true };
115
+ }
116
+ const tokens = (0, tokenizer_1.tokenize)(expr);
117
+ const { nodes } = (0, parser_1.parse)(tokens);
118
+ return (0, compat_1.compatibilityCheck)(nodes);
119
+ }
120
+ /**
121
+ * Validate the softcode expression contained in a file.
122
+ *
123
+ * The entire file content is treated as a single expression.
124
+ * For files with multiple expressions (e.g. one per line) consider
125
+ * reading and validating each line individually.
126
+ *
127
+ * @throws If the file cannot be read.
128
+ */
129
+ function validateFile(filePath) {
130
+ const content = fs.readFileSync(filePath, 'utf-8');
131
+ return validate(content);
132
+ }
133
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/validator/index.ts"],"names":[],"mappings":";AAAA,8EAA8E;AAC9E,0DAA0D;AAC1D,8EAA8E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0C9E,4BAwBC;AAaD,kDAOC;AAWD,oCAGC;AAlGD,uCAAyB;AACzB,2CAAuC;AACvC,qCAAiC;AACjC,uCAA0C;AAC1C,uCAAiD;AACjD,qCAAuF;AAIvF,uCAA4E;AAAhD,6GAAA,iBAAiB,OAAA;AAG7C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,SAAgB,QAAQ,CAAC,IAAY;IACnC,mCAAmC;IACnC,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACvB,MAAM,WAAW,GAAiB;YAChC;gBACE,QAAQ,EAAE,SAAS;gBACnB,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,qBAAqB;gBAC9B,MAAM,EAAE,CAAC;gBACT,MAAM,EAAE,CAAC;aACV;SACF,CAAC;QACF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IACtC,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,oBAAQ,EAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,IAAA,cAAK,EAAC,MAAM,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,IAAA,uBAAa,EAAC,KAAK,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,IAAA,8BAAoB,EAAC,KAAK,CAAC,CAAC;IAE5C,MAAM,GAAG,GAAG,CAAC,GAAG,UAAU,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;IAEvD,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,mBAAmB,CAAC,IAAY;IAC9C,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACvB,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC5C,CAAC;IACD,MAAM,MAAM,GAAG,IAAA,oBAAQ,EAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,EAAE,KAAK,EAAE,GAAG,IAAA,cAAK,EAAC,MAAM,CAAC,CAAC;IAChC,OAAO,IAAA,2BAAkB,EAAC,KAAK,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,YAAY,CAAC,QAAgB;IAC3C,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACnD,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { Token } from './tokenizer';
2
+ import { ASTNode, Diagnostic } from './types';
3
+ export interface ParseResult {
4
+ nodes: ASTNode[];
5
+ /** Structural diagnostics: unclosed parens/brackets, stray ), ], etc. */
6
+ diagnostics: Diagnostic[];
7
+ }
8
+ /**
9
+ * Parse a flat token stream into an AST.
10
+ *
11
+ * Structural errors (unbalanced parens/brackets) are embedded in `diagnostics`.
12
+ * The returned `nodes` array always contains the best-effort parse even when
13
+ * there are errors, enabling downstream semantic checks to still run.
14
+ */
15
+ export declare function parse(tokens: Token[]): ParseResult;
16
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/validator/parser.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,OAAO,EAAmB,UAAU,EAAmD,MAAM,SAAS,CAAC;AAEhH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,yEAAyE;IACzE,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AA4RD;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,WAAW,CAIlD"}