@intentius/tsad-reference 1.4.0

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 (55) hide show
  1. package/CAVEATS.md +95 -0
  2. package/README.md +42 -0
  3. package/dist/adapter.d.ts +5 -0
  4. package/dist/adapter.d.ts.map +1 -0
  5. package/dist/adapter.js +50 -0
  6. package/dist/adapter.js.map +1 -0
  7. package/dist/fnbody.d.ts +11 -0
  8. package/dist/fnbody.d.ts.map +1 -0
  9. package/dist/fnbody.js +68 -0
  10. package/dist/fnbody.js.map +1 -0
  11. package/dist/fold.d.ts +86 -0
  12. package/dist/fold.d.ts.map +1 -0
  13. package/dist/fold.js +558 -0
  14. package/dist/fold.js.map +1 -0
  15. package/dist/foldable-helpers.d.ts +23 -0
  16. package/dist/foldable-helpers.d.ts.map +1 -0
  17. package/dist/foldable-helpers.js +25 -0
  18. package/dist/foldable-helpers.js.map +1 -0
  19. package/dist/host.d.ts +45 -0
  20. package/dist/host.d.ts.map +1 -0
  21. package/dist/host.js +5 -0
  22. package/dist/host.js.map +1 -0
  23. package/dist/index.d.ts +9 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +9 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/module.d.ts +20 -0
  28. package/dist/module.d.ts.map +1 -0
  29. package/dist/module.js +74 -0
  30. package/dist/module.js.map +1 -0
  31. package/dist/project.d.ts +22 -0
  32. package/dist/project.d.ts.map +1 -0
  33. package/dist/project.js +481 -0
  34. package/dist/project.js.map +1 -0
  35. package/dist/revive.d.ts +16 -0
  36. package/dist/revive.d.ts.map +1 -0
  37. package/dist/revive.js +109 -0
  38. package/dist/revive.js.map +1 -0
  39. package/dist/subset.d.ts +42 -0
  40. package/dist/subset.d.ts.map +1 -0
  41. package/dist/subset.js +214 -0
  42. package/dist/subset.js.map +1 -0
  43. package/package.json +37 -0
  44. package/src/adapter.ts +48 -0
  45. package/src/fnbody.ts +61 -0
  46. package/src/fold.ts +581 -0
  47. package/src/foldable-helpers.ts +38 -0
  48. package/src/host.ts +38 -0
  49. package/src/index.ts +8 -0
  50. package/src/module.ts +52 -0
  51. package/src/no-own-execution.test.ts +86 -0
  52. package/src/prebuild.test.ts +68 -0
  53. package/src/project.ts +495 -0
  54. package/src/revive.ts +120 -0
  55. package/src/subset.ts +224 -0
package/src/subset.ts ADDED
@@ -0,0 +1,224 @@
1
+ /**
2
+ * The shape classifier: grammar.md §2, one branch per S-* production.
3
+ *
4
+ * Written from the specification text, not derived from any implementation
5
+ * (#50). Decides from syntax alone and never resolves a name, which is the
6
+ * direction F-Direction permits: this may accept what the folder rejects and
7
+ * must never reject what the folder accepts, outside F-Exc-Lazy and
8
+ * F-Exc-Registry.
9
+ */
10
+ import * as ts from "typescript";
11
+ import { intrinsicCallFolds, intrinsicCallFoldsEagerly, type IntrinsicDef } from "./host.js";
12
+ import { isFoldableHelperName } from "./foldable-helpers.js";
13
+
14
+ export interface ShapeViolation {
15
+ /** The S-* production that refused it. */
16
+ readonly rule: string;
17
+ readonly node: ts.Node;
18
+ readonly message: string;
19
+ }
20
+ const no = (rule: string, node: ts.Node, message: string): ShapeViolation => ({ rule, node, message });
21
+
22
+ /** ⟨BinOp⟩, grammar.md §2. */
23
+ const BIN_OPS: ReadonlySet<ts.SyntaxKind> = new Set([
24
+ ts.SyntaxKind.AmpersandAmpersandToken,
25
+ ts.SyntaxKind.BarBarToken,
26
+ ts.SyntaxKind.QuestionQuestionToken,
27
+ ts.SyntaxKind.PlusToken,
28
+ ts.SyntaxKind.MinusToken,
29
+ ts.SyntaxKind.AsteriskToken,
30
+ ts.SyntaxKind.SlashToken,
31
+ ts.SyntaxKind.EqualsEqualsEqualsToken,
32
+ ts.SyntaxKind.ExclamationEqualsEqualsToken,
33
+ ts.SyntaxKind.GreaterThanToken,
34
+ ts.SyntaxKind.LessThanToken,
35
+ ts.SyntaxKind.GreaterThanEqualsToken,
36
+ ts.SyntaxKind.LessThanEqualsToken,
37
+ ]);
38
+ /** S-Unary's operator set. */
39
+ const UNARY_OPS: ReadonlySet<ts.SyntaxKind> = new Set([ts.SyntaxKind.ExclamationToken, ts.SyntaxKind.MinusToken]);
40
+
41
+ /** ⟨LiteralKey⟩ ::= ⟨Identifier⟩ | ⟨StringLiteral⟩ | ⟨NumericLiteral⟩ */
42
+ export function isLiteralKey(n: ts.PropertyName): n is ts.Identifier | ts.StringLiteral | ts.NumericLiteral {
43
+ return ts.isIdentifier(n) || ts.isStringLiteral(n) || ts.isNumericLiteral(n);
44
+ }
45
+ /** S-Index's key: a string or numeric literal only. */
46
+ export function isLiteralElementKey(n: ts.Expression): n is ts.StringLiteral | ts.NumericLiteral {
47
+ return ts.isStringLiteral(n) || ts.isNumericLiteral(n);
48
+ }
49
+
50
+ /**
51
+ * S-CompositeStep's callee condition.
52
+ *
53
+ * SPEC GAP (#51): the normative text calls this callee "unclaimed" in four
54
+ * places and defines it in none; all four cite inventory row L3.20, which is
55
+ * a ledger entry. Implemented here per that row's intent, which is that the
56
+ * callee is a bare identifier claimed by no other S-Call form and not
57
+ * shadowed by a local const. The registry and the local scope are resolution,
58
+ * so at shape level only the bare-identifier part is checkable.
59
+ */
60
+ export function isUnclaimedCallee(call: ts.CallExpression, intrinsics?: readonly IntrinsicDef[]): boolean {
61
+ if (!ts.isIdentifier(call.expression)) return false;
62
+ const name = call.expression.text;
63
+ if (isFoldableHelperName(name)) return false;
64
+ if (intrinsics?.some((i) => i.name === name && (intrinsicCallFolds(i) || intrinsicCallFoldsEagerly(i)))) return false;
65
+ return true;
66
+ }
67
+
68
+ /**
69
+ * Classify `node`'s shape. Returns the first violation in evaluation order,
70
+ * or undefined when the whole shape is admissible.
71
+ *
72
+ * `intrinsics` is the registry ρ. Absent, S-CallIntrinsic and S-CallEager are
73
+ * unrecognisable and their shapes fall to S-Reject, which is F-Exc-Registry:
74
+ * the one place this classifier is stricter than the folder.
75
+ */
76
+ export function findShapeViolation(node: ts.Node, intrinsics?: readonly IntrinsicDef[], localCallees?: ReadonlySet<string>): ShapeViolation | undefined {
77
+ const recur = (n: ts.Node) => findShapeViolation(n, intrinsics, localCallees);
78
+
79
+ // S-Unwrap
80
+ if (
81
+ ts.isParenthesizedExpression(node) ||
82
+ ts.isAsExpression(node) ||
83
+ ts.isSatisfiesExpression(node) ||
84
+ ts.isNonNullExpression(node)
85
+ ) {
86
+ return recur(node.expression);
87
+ }
88
+
89
+ // S-Literal
90
+ if (
91
+ ts.isStringLiteral(node) ||
92
+ ts.isNoSubstitutionTemplateLiteral(node) ||
93
+ ts.isNumericLiteral(node) ||
94
+ node.kind === ts.SyntaxKind.TrueKeyword ||
95
+ node.kind === ts.SyntaxKind.FalseKeyword ||
96
+ node.kind === ts.SyntaxKind.NullKeyword
97
+ ) {
98
+ return undefined;
99
+ }
100
+
101
+ // S-Ident, which S-Undefined is an instance of. Always shape-valid:
102
+ // whether it resolves is F-Eval-Ident's question (F-Div-Ident).
103
+ if (ts.isIdentifier(node)) return undefined;
104
+
105
+ // S-Tagged. The interior is opaque and is deliberately not recursed into:
106
+ // an intrinsic's interpolations legitimately hold shapes this classifier
107
+ // would refuse, and only the folder has the registry (F-Div-Tag).
108
+ if (ts.isTaggedTemplateExpression(node)) return undefined;
109
+
110
+ // S-Template
111
+ if (ts.isTemplateExpression(node)) {
112
+ for (const span of node.templateSpans) {
113
+ const v = recur(span.expression);
114
+ if (v) return v;
115
+ }
116
+ return undefined;
117
+ }
118
+
119
+ // S-Object
120
+ if (ts.isObjectLiteralExpression(node)) {
121
+ for (const member of node.properties) {
122
+ if (ts.isPropertyAssignment(member)) {
123
+ if (!isLiteralKey(member.name)) return no("S-Prop", member.name, "computed or dynamic property name");
124
+ const v = recur(member.initializer);
125
+ if (v) return v;
126
+ } else if (ts.isShorthandPropertyAssignment(member)) {
127
+ continue; // S-Shorthand: always shape-valid
128
+ } else if (ts.isSpreadAssignment(member)) {
129
+ const v = recur(member.expression);
130
+ if (v) return v;
131
+ } else {
132
+ return no("S-Object", member, "unsupported object member");
133
+ }
134
+ }
135
+ return undefined;
136
+ }
137
+
138
+ // S-Array
139
+ if (ts.isArrayLiteralExpression(node)) {
140
+ for (const el of node.elements) {
141
+ const v = recur(ts.isSpreadElement(el) ? el.expression : el);
142
+ if (v) return v;
143
+ }
144
+ return undefined;
145
+ }
146
+
147
+ // S-Member, with S-CompositeStep taking precedence when the member is
148
+ // `step` and the object is a call.
149
+ if (ts.isPropertyAccessExpression(node)) {
150
+ if (node.name.text === "step" && ts.isCallExpression(node.expression)) return undefined;
151
+ return recur(node.expression);
152
+ }
153
+
154
+ // S-Index
155
+ if (ts.isElementAccessExpression(node)) {
156
+ if (!isLiteralElementKey(node.argumentExpression)) {
157
+ return no("S-Index", node.argumentExpression, "element-access key must be a string or numeric literal");
158
+ }
159
+ return recur(node.expression);
160
+ }
161
+
162
+ // S-Unary
163
+ if (ts.isPrefixUnaryExpression(node)) {
164
+ if (!UNARY_OPS.has(node.operator)) return no("S-Unary", node, `unsupported unary operator: ${ts.SyntaxKind[node.operator]}`);
165
+ return recur(node.operand);
166
+ }
167
+
168
+ // S-Binary. Flow-insensitive by construction: both sides must be
169
+ // shape-valid even where the folder would evaluate only one (F-Exc-Lazy).
170
+ if (ts.isBinaryExpression(node)) {
171
+ const op = node.operatorToken.kind;
172
+ if (!BIN_OPS.has(op)) return no("S-Binary", node, `unsupported binary operator: ${ts.SyntaxKind[op]}`);
173
+ return recur(node.left) ?? recur(node.right);
174
+ }
175
+
176
+ // S-Conditional. Flow-insensitive for the same reason.
177
+ if (ts.isConditionalExpression(node)) {
178
+ return recur(node.condition) ?? recur(node.whenTrue) ?? recur(node.whenFalse);
179
+ }
180
+
181
+ // S-New. The callee's shape is unconstrained here; that it must be a plain
182
+ // identifier is resolution-adjacent and belongs to F-Eval-New (F-Div-NsNew).
183
+ if (ts.isNewExpression(node)) {
184
+ for (const arg of node.arguments ?? []) {
185
+ const v = recur(arg);
186
+ if (v) return v;
187
+ }
188
+ return undefined;
189
+ }
190
+
191
+ // S-Call
192
+ if (ts.isCallExpression(node)) {
193
+ const args = (): ShapeViolation | undefined => {
194
+ for (const a of node.arguments) {
195
+ const v = recur(a);
196
+ if (v) return v;
197
+ }
198
+ return undefined;
199
+ };
200
+
201
+ // S-CallMethod: receiver ∈ ⟨Expr⟩, method name unconstrained.
202
+ if (ts.isPropertyAccessExpression(node.expression)) return recur(node.expression.expression) ?? args();
203
+
204
+ if (ts.isIdentifier(node.expression)) {
205
+ const name = node.expression.text;
206
+ if (isFoldableHelperName(name)) return args(); // S-CallHelper
207
+ // S-CallLocal (spec 1.2): the callee is bound in this file by
208
+ // S-LocalFunction or by an import from a project specifier. The body's
209
+ // admissibility is the fold's question (S-FnBody at the call).
210
+ if (localCallees?.has(name)) return args();
211
+ const def = intrinsics?.find((i) => i.name === name);
212
+ // S-CallIntrinsic / S-CallEager, registry-gated. Without ρ neither is
213
+ // recognisable and the call falls to S-Reject (F-Exc-Registry).
214
+ if (def && (intrinsicCallFolds(def) || intrinsicCallFoldsEagerly(def))) return args();
215
+ }
216
+
217
+ return no("S-Reject", node, "function call as a value is not foldable");
218
+ }
219
+
220
+ // S-Reject. This is where the explicitly-outside list lands: a function or
221
+ // arrow expression used as a value, class expressions, await, yield,
222
+ // assignment, the comma operator, and every operator outside ⟨BinOp⟩.
223
+ return no("S-Reject", node, `unsupported expression: ${ts.SyntaxKind[node.kind]}`);
224
+ }