@formspec/build 0.1.0-alpha.1 → 0.1.0-alpha.10

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 (145) hide show
  1. package/README.md +138 -0
  2. package/dist/__tests__/analyzer-edge-cases.test.d.ts +13 -0
  3. package/dist/__tests__/analyzer-edge-cases.test.d.ts.map +1 -0
  4. package/dist/__tests__/analyzer-edge-cases.test.js +376 -0
  5. package/dist/__tests__/analyzer-edge-cases.test.js.map +1 -0
  6. package/dist/__tests__/analyzer.test.d.ts +5 -0
  7. package/dist/__tests__/analyzer.test.d.ts.map +1 -0
  8. package/dist/__tests__/analyzer.test.js +190 -0
  9. package/dist/__tests__/analyzer.test.js.map +1 -0
  10. package/dist/__tests__/cli.test.js.map +1 -1
  11. package/dist/__tests__/codegen.test.d.ts +5 -0
  12. package/dist/__tests__/codegen.test.d.ts.map +1 -0
  13. package/dist/__tests__/codegen.test.js +506 -0
  14. package/dist/__tests__/codegen.test.js.map +1 -0
  15. package/dist/__tests__/decorator-pipeline.test.d.ts +11 -0
  16. package/dist/__tests__/decorator-pipeline.test.d.ts.map +1 -0
  17. package/dist/__tests__/decorator-pipeline.test.js +460 -0
  18. package/dist/__tests__/decorator-pipeline.test.js.map +1 -0
  19. package/dist/__tests__/edge-cases.test.js +10 -4
  20. package/dist/__tests__/edge-cases.test.js.map +1 -1
  21. package/dist/__tests__/fixtures/edge-cases.d.ts +110 -0
  22. package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -0
  23. package/dist/__tests__/fixtures/edge-cases.js +137 -0
  24. package/dist/__tests__/fixtures/edge-cases.js.map +1 -0
  25. package/dist/__tests__/fixtures/example-a-builtins.d.ts +12 -0
  26. package/dist/__tests__/fixtures/example-a-builtins.d.ts.map +1 -0
  27. package/dist/__tests__/fixtures/example-a-builtins.js +100 -0
  28. package/dist/__tests__/fixtures/example-a-builtins.js.map +1 -0
  29. package/dist/__tests__/fixtures/example-b-decorators.d.ts +5 -0
  30. package/dist/__tests__/fixtures/example-b-decorators.d.ts.map +1 -0
  31. package/dist/__tests__/fixtures/example-b-decorators.js +5 -0
  32. package/dist/__tests__/fixtures/example-b-decorators.js.map +1 -0
  33. package/dist/__tests__/fixtures/example-b-extended.d.ts +5 -0
  34. package/dist/__tests__/fixtures/example-b-extended.d.ts.map +1 -0
  35. package/dist/__tests__/fixtures/example-b-extended.js +60 -0
  36. package/dist/__tests__/fixtures/example-b-extended.js.map +1 -0
  37. package/dist/__tests__/fixtures/example-c-custom.d.ts +5 -0
  38. package/dist/__tests__/fixtures/example-c-custom.d.ts.map +1 -0
  39. package/dist/__tests__/fixtures/example-c-custom.js +61 -0
  40. package/dist/__tests__/fixtures/example-c-custom.js.map +1 -0
  41. package/dist/__tests__/fixtures/example-c-decorators.d.ts +5 -0
  42. package/dist/__tests__/fixtures/example-c-decorators.d.ts.map +1 -0
  43. package/dist/__tests__/fixtures/example-c-decorators.js +4 -0
  44. package/dist/__tests__/fixtures/example-c-decorators.js.map +1 -0
  45. package/dist/__tests__/fixtures/example-d-mixed-decorators.d.ts +6 -0
  46. package/dist/__tests__/fixtures/example-d-mixed-decorators.d.ts.map +1 -0
  47. package/dist/__tests__/fixtures/example-d-mixed-decorators.js +75 -0
  48. package/dist/__tests__/fixtures/example-d-mixed-decorators.js.map +1 -0
  49. package/dist/__tests__/fixtures/example-e-decorators.d.ts +11 -0
  50. package/dist/__tests__/fixtures/example-e-decorators.d.ts.map +1 -0
  51. package/dist/__tests__/fixtures/example-e-decorators.js +10 -0
  52. package/dist/__tests__/fixtures/example-e-decorators.js.map +1 -0
  53. package/dist/__tests__/fixtures/example-e-no-namespace.d.ts +5 -0
  54. package/dist/__tests__/fixtures/example-e-no-namespace.d.ts.map +1 -0
  55. package/dist/__tests__/fixtures/example-e-no-namespace.js +61 -0
  56. package/dist/__tests__/fixtures/example-e-no-namespace.js.map +1 -0
  57. package/dist/__tests__/fixtures/example-interface-types.d.ts +102 -0
  58. package/dist/__tests__/fixtures/example-interface-types.d.ts.map +1 -0
  59. package/dist/__tests__/fixtures/example-interface-types.js +8 -0
  60. package/dist/__tests__/fixtures/example-interface-types.js.map +1 -0
  61. package/dist/__tests__/fixtures/example-jsdoc-constraints.d.ts +16 -0
  62. package/dist/__tests__/fixtures/example-jsdoc-constraints.d.ts.map +1 -0
  63. package/dist/__tests__/fixtures/example-jsdoc-constraints.js +98 -0
  64. package/dist/__tests__/fixtures/example-jsdoc-constraints.js.map +1 -0
  65. package/dist/__tests__/fixtures/example-nested-class.d.ts +45 -0
  66. package/dist/__tests__/fixtures/example-nested-class.d.ts.map +1 -0
  67. package/dist/__tests__/fixtures/example-nested-class.js +248 -0
  68. package/dist/__tests__/fixtures/example-nested-class.js.map +1 -0
  69. package/dist/__tests__/fixtures/sample-forms.d.ts +55 -0
  70. package/dist/__tests__/fixtures/sample-forms.d.ts.map +1 -0
  71. package/dist/__tests__/fixtures/sample-forms.js +78 -0
  72. package/dist/__tests__/fixtures/sample-forms.js.map +1 -0
  73. package/dist/__tests__/generator.test.js +29 -3
  74. package/dist/__tests__/generator.test.js.map +1 -1
  75. package/dist/__tests__/integration.test.js +1 -3
  76. package/dist/__tests__/integration.test.js.map +1 -1
  77. package/dist/__tests__/interface-types.test.d.ts +11 -0
  78. package/dist/__tests__/interface-types.test.d.ts.map +1 -0
  79. package/dist/__tests__/interface-types.test.js +404 -0
  80. package/dist/__tests__/interface-types.test.js.map +1 -0
  81. package/dist/__tests__/jsdoc-constraints.test.d.ts +10 -0
  82. package/dist/__tests__/jsdoc-constraints.test.d.ts.map +1 -0
  83. package/dist/__tests__/jsdoc-constraints.test.js +465 -0
  84. package/dist/__tests__/jsdoc-constraints.test.js.map +1 -0
  85. package/dist/__tests__/write-schemas.test.js +10 -8
  86. package/dist/__tests__/write-schemas.test.js.map +1 -1
  87. package/dist/analyzer/class-analyzer.d.ts +139 -0
  88. package/dist/analyzer/class-analyzer.d.ts.map +1 -0
  89. package/dist/analyzer/class-analyzer.js +377 -0
  90. package/dist/analyzer/class-analyzer.js.map +1 -0
  91. package/dist/analyzer/decorator-extractor.d.ts +78 -0
  92. package/dist/analyzer/decorator-extractor.d.ts.map +1 -0
  93. package/dist/analyzer/decorator-extractor.js +336 -0
  94. package/dist/analyzer/decorator-extractor.js.map +1 -0
  95. package/dist/analyzer/jsdoc-constraints.d.ts +47 -0
  96. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -0
  97. package/dist/analyzer/jsdoc-constraints.js +153 -0
  98. package/dist/analyzer/jsdoc-constraints.js.map +1 -0
  99. package/dist/analyzer/program.d.ts +53 -0
  100. package/dist/analyzer/program.d.ts.map +1 -0
  101. package/dist/analyzer/program.js +114 -0
  102. package/dist/analyzer/program.js.map +1 -0
  103. package/dist/analyzer/type-converter.d.ts +75 -0
  104. package/dist/analyzer/type-converter.d.ts.map +1 -0
  105. package/dist/analyzer/type-converter.js +474 -0
  106. package/dist/analyzer/type-converter.js.map +1 -0
  107. package/dist/browser.d.ts +54 -0
  108. package/dist/browser.d.ts.map +1 -0
  109. package/dist/browser.js +48 -0
  110. package/dist/browser.js.map +1 -0
  111. package/dist/build.d.ts +580 -0
  112. package/dist/cli.js +1 -3
  113. package/dist/cli.js.map +1 -1
  114. package/dist/codegen/index.d.ts +75 -0
  115. package/dist/codegen/index.d.ts.map +1 -0
  116. package/dist/codegen/index.js +597 -0
  117. package/dist/codegen/index.js.map +1 -0
  118. package/dist/generators/class-schema.d.ts +118 -0
  119. package/dist/generators/class-schema.d.ts.map +1 -0
  120. package/dist/generators/class-schema.js +140 -0
  121. package/dist/generators/class-schema.js.map +1 -0
  122. package/dist/generators/method-schema.d.ts +67 -0
  123. package/dist/generators/method-schema.d.ts.map +1 -0
  124. package/dist/generators/method-schema.js +108 -0
  125. package/dist/generators/method-schema.js.map +1 -0
  126. package/dist/index.d.ts +8 -2
  127. package/dist/index.d.ts.map +1 -1
  128. package/dist/index.js +11 -1
  129. package/dist/index.js.map +1 -1
  130. package/dist/internals.d.ts +20 -0
  131. package/dist/internals.d.ts.map +1 -0
  132. package/dist/internals.js +22 -0
  133. package/dist/internals.js.map +1 -0
  134. package/dist/json-schema/generator.d.ts.map +1 -1
  135. package/dist/json-schema/generator.js +26 -6
  136. package/dist/json-schema/generator.js.map +1 -1
  137. package/dist/json-schema/types.d.ts +28 -0
  138. package/dist/json-schema/types.d.ts.map +1 -1
  139. package/dist/json-schema/types.js +27 -1
  140. package/dist/json-schema/types.js.map +1 -1
  141. package/dist/ui-schema/generator.d.ts.map +1 -1
  142. package/dist/ui-schema/generator.js +1 -3
  143. package/dist/ui-schema/generator.js.map +1 -1
  144. package/dist/ui-schema/types.d.ts.map +1 -1
  145. package/package.json +18 -5
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Decorator extractor for parsing decorator AST nodes.
3
+ *
4
+ * Extracts decorator names and arguments from class field decorators,
5
+ * supporting the FormSpec decorator DSL (@Field, @Minimum, @Maximum, etc.).
6
+ *
7
+ * Also supports branded type resolution via the TypeScript type checker
8
+ * to detect custom decorators created with `extendDecorator` and
9
+ * `customDecorator` from `@formspec/decorators`.
10
+ */
11
+ import * as ts from "typescript";
12
+ import {} from "@formspec/core";
13
+ /**
14
+ * Extracts decorators from a class member (property or method).
15
+ *
16
+ * @param member - The class member to extract decorators from
17
+ * @returns Array of extracted decorator info
18
+ */
19
+ export function extractDecorators(member) {
20
+ const decorators = [];
21
+ // TC39 decorators are in the modifiers array
22
+ const modifiers = ts.canHaveDecorators(member) ? ts.getDecorators(member) : undefined;
23
+ if (!modifiers)
24
+ return decorators;
25
+ for (const decorator of modifiers) {
26
+ const info = parseDecorator(decorator);
27
+ if (info) {
28
+ decorators.push(info);
29
+ }
30
+ }
31
+ return decorators;
32
+ }
33
+ /**
34
+ * Parses a single decorator node.
35
+ */
36
+ function parseDecorator(decorator) {
37
+ const expr = decorator.expression;
38
+ // Simple decorator: @Decorator
39
+ if (ts.isIdentifier(expr)) {
40
+ return {
41
+ name: expr.text,
42
+ args: [],
43
+ node: decorator,
44
+ };
45
+ }
46
+ // Call expression: @Decorator(args)
47
+ if (ts.isCallExpression(expr)) {
48
+ const callee = expr.expression;
49
+ // Get decorator name
50
+ let name = null;
51
+ if (ts.isIdentifier(callee)) {
52
+ name = callee.text;
53
+ }
54
+ else if (ts.isPropertyAccessExpression(callee)) {
55
+ // For namespaced decorators like @formspec.Field()
56
+ name = callee.name.text;
57
+ }
58
+ if (!name)
59
+ return null;
60
+ // Extract arguments
61
+ const args = expr.arguments.map(extractArgValue);
62
+ return {
63
+ name,
64
+ args,
65
+ node: decorator,
66
+ };
67
+ }
68
+ return null;
69
+ }
70
+ /**
71
+ * Extracts the value from an expression node.
72
+ * Supports literals, arrays, object literals, and RegExp.
73
+ */
74
+ function extractArgValue(node) {
75
+ // String literal
76
+ if (ts.isStringLiteral(node)) {
77
+ return node.text;
78
+ }
79
+ // Numeric literal
80
+ if (ts.isNumericLiteral(node)) {
81
+ return Number(node.text);
82
+ }
83
+ // Boolean literals (true/false are identifiers in TS AST)
84
+ if (node.kind === ts.SyntaxKind.TrueKeyword) {
85
+ return true;
86
+ }
87
+ if (node.kind === ts.SyntaxKind.FalseKeyword) {
88
+ return false;
89
+ }
90
+ // Null literal
91
+ if (node.kind === ts.SyntaxKind.NullKeyword) {
92
+ return null;
93
+ }
94
+ // Prefix unary expression (for negative numbers)
95
+ if (ts.isPrefixUnaryExpression(node)) {
96
+ if (node.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(node.operand)) {
97
+ return -Number(node.operand.text);
98
+ }
99
+ if (node.operator === ts.SyntaxKind.PlusToken && ts.isNumericLiteral(node.operand)) {
100
+ return Number(node.operand.text);
101
+ }
102
+ }
103
+ // Array literal
104
+ if (ts.isArrayLiteralExpression(node)) {
105
+ return node.elements.map((el) => {
106
+ if (ts.isSpreadElement(el)) {
107
+ // Can't evaluate spread at compile time
108
+ return null;
109
+ }
110
+ return extractArgValue(el);
111
+ });
112
+ }
113
+ // Object literal
114
+ if (ts.isObjectLiteralExpression(node)) {
115
+ const obj = {};
116
+ for (const prop of node.properties) {
117
+ if (ts.isPropertyAssignment(prop)) {
118
+ const key = getPropertyName(prop.name);
119
+ if (key) {
120
+ obj[key] = extractArgValue(prop.initializer);
121
+ }
122
+ }
123
+ else if (ts.isShorthandPropertyAssignment(prop)) {
124
+ // { foo } shorthand - we can't resolve the value
125
+ const key = prop.name.text;
126
+ obj[key] = null;
127
+ }
128
+ }
129
+ return obj;
130
+ }
131
+ // Template literal (simple case)
132
+ if (ts.isNoSubstitutionTemplateLiteral(node)) {
133
+ return node.text;
134
+ }
135
+ // RegExp literal: extract the source pattern string
136
+ if (ts.isRegularExpressionLiteral(node)) {
137
+ const regexText = node.text;
138
+ // RegExp literal format is /pattern/flags
139
+ const lastSlash = regexText.lastIndexOf("/");
140
+ if (lastSlash > 0) {
141
+ return regexText.substring(1, lastSlash);
142
+ }
143
+ return regexText;
144
+ }
145
+ // new RegExp("pattern") — extract pattern from constructor call
146
+ if (ts.isNewExpression(node)) {
147
+ if (ts.isIdentifier(node.expression) &&
148
+ node.expression.text === "RegExp" &&
149
+ node.arguments &&
150
+ node.arguments.length > 0) {
151
+ const firstArg = node.arguments[0];
152
+ if (firstArg && ts.isStringLiteral(firstArg)) {
153
+ return firstArg.text;
154
+ }
155
+ }
156
+ }
157
+ // Identifier - could be an enum member or constant
158
+ // We can't resolve it statically, return null
159
+ if (ts.isIdentifier(node)) {
160
+ return null;
161
+ }
162
+ // For other expressions, return null
163
+ return null;
164
+ }
165
+ /**
166
+ * Gets the property name from a property name node.
167
+ */
168
+ function getPropertyName(name) {
169
+ if (ts.isIdentifier(name)) {
170
+ return name.text;
171
+ }
172
+ if (ts.isStringLiteral(name)) {
173
+ return name.text;
174
+ }
175
+ if (ts.isNumericLiteral(name)) {
176
+ return name.text;
177
+ }
178
+ // Computed property names can't be resolved statically
179
+ return null;
180
+ }
181
+ /**
182
+ * Known FormSpec decorators and their expected argument types.
183
+ *
184
+ * This metadata object provides additional information about each decorator's
185
+ * expected argument types. The keys are constrained to match FormSpecDecoratorName.
186
+ */
187
+ export const FORMSPEC_DECORATORS = {
188
+ // Display metadata
189
+ Field: { argTypes: ["object"] },
190
+ // Grouping
191
+ Group: { argTypes: ["string"] },
192
+ // Conditional display
193
+ ShowWhen: { argTypes: ["object"] },
194
+ // Enum options
195
+ EnumOptions: { argTypes: ["array"] },
196
+ // Numeric constraints
197
+ Minimum: { argTypes: ["number"] },
198
+ Maximum: { argTypes: ["number"] },
199
+ ExclusiveMinimum: { argTypes: ["number"] },
200
+ ExclusiveMaximum: { argTypes: ["number"] },
201
+ // String constraints
202
+ MinLength: { argTypes: ["number"] },
203
+ MaxLength: { argTypes: ["number"] },
204
+ Pattern: { argTypes: ["string"] },
205
+ };
206
+ /**
207
+ * Checks if a file path belongs to the @formspec/decorators package.
208
+ *
209
+ * Matches both installed node_modules paths and local monorepo paths.
210
+ */
211
+ function isFormSpecDecoratorsPath(fileName) {
212
+ // Normalize separators for cross-platform matching
213
+ const normalized = fileName.replace(/\\/g, "/");
214
+ return (normalized.includes("node_modules/@formspec/decorators") ||
215
+ normalized.includes("/packages/decorators/"));
216
+ }
217
+ /**
218
+ * Resolves a decorator via the TypeScript type checker to determine
219
+ * if it is a FormSpec decorator (built-in, extended, or custom).
220
+ *
221
+ * This enables detection of:
222
+ * 1. Direct imports of built-in decorators from `@formspec/decorators`
223
+ * 2. Extended decorators created via `extendDecorator(...).as(...)`
224
+ * 3. Custom decorators created via `customDecorator(...).as(...)` or `.marker(...)`
225
+ *
226
+ * @param decorator - The decorator AST node
227
+ * @param checker - TypeScript type checker
228
+ * @returns Resolved decorator information, or null if not resolvable
229
+ */
230
+ export function resolveDecorator(decorator, checker) {
231
+ const expr = decorator.expression;
232
+ // Get the identifier to resolve
233
+ let targetNode;
234
+ let name;
235
+ if (ts.isIdentifier(expr)) {
236
+ // Simple marker decorator: @Decorator
237
+ targetNode = expr;
238
+ name = expr.text;
239
+ }
240
+ else if (ts.isCallExpression(expr)) {
241
+ // Parameterized decorator: @Decorator(args)
242
+ if (ts.isIdentifier(expr.expression)) {
243
+ targetNode = expr.expression;
244
+ name = expr.expression.text;
245
+ }
246
+ else {
247
+ return null;
248
+ }
249
+ }
250
+ else {
251
+ return null;
252
+ }
253
+ // Check if it's a known built-in by name
254
+ if (name in FORMSPEC_DECORATORS) {
255
+ // Verify it actually comes from @formspec/decorators by checking the symbol
256
+ const symbol = checker.getSymbolAtLocation(targetNode);
257
+ if (symbol) {
258
+ const declarations = symbol.declarations;
259
+ if (declarations && declarations.length > 0) {
260
+ const decl = declarations[0];
261
+ if (decl) {
262
+ const sourceFile = decl.getSourceFile();
263
+ const fileName = sourceFile.fileName;
264
+ if (isFormSpecDecoratorsPath(fileName)) {
265
+ return {
266
+ name,
267
+ isFormSpec: true,
268
+ isMarker: !ts.isCallExpression(expr),
269
+ };
270
+ }
271
+ }
272
+ }
273
+ }
274
+ }
275
+ // Try to resolve branded types for custom/extended decorators
276
+ const resolvedSymbol = checker.getSymbolAtLocation(targetNode);
277
+ if (!resolvedSymbol)
278
+ return null;
279
+ const type = checker.getTypeOfSymbol(resolvedSymbol);
280
+ const props = type.getProperties();
281
+ let extendsBuiltin;
282
+ let extensionName;
283
+ let isMarker = false;
284
+ for (const prop of props) {
285
+ // __String is a branded string type; cast is safe for read-only string operations
286
+ const escapedName = prop.getEscapedName();
287
+ // TypeScript represents unique symbol properties as __@<name>@<uniqueId>
288
+ // in escaped names. The <name> portion may be either the Symbol description
289
+ // (e.g., "formspec.extends") or the const variable name (e.g., "FORMSPEC_EXTENDS"),
290
+ // depending on how the symbol is declared and resolved by the type checker.
291
+ // We check for both patterns to handle all cases.
292
+ if (escapedName.startsWith("__@") &&
293
+ (escapedName.includes("formspec.extends") || escapedName.includes("FORMSPEC_EXTENDS"))) {
294
+ const propType = checker.getTypeOfSymbol(prop);
295
+ if (propType.isStringLiteral()) {
296
+ extendsBuiltin = propType.value;
297
+ }
298
+ }
299
+ if (escapedName.startsWith("__@") &&
300
+ (escapedName.includes("formspec.extension") || escapedName.includes("FORMSPEC_EXTENSION"))) {
301
+ const propType = checker.getTypeOfSymbol(prop);
302
+ if (propType.isStringLiteral()) {
303
+ extensionName = propType.value;
304
+ }
305
+ }
306
+ if (escapedName.startsWith("__@") &&
307
+ (escapedName.includes("formspec.marker") || escapedName.includes("FORMSPEC_MARKER"))) {
308
+ isMarker = true;
309
+ }
310
+ }
311
+ if (extendsBuiltin) {
312
+ return {
313
+ name,
314
+ extendsBuiltin,
315
+ isFormSpec: true,
316
+ isMarker: false,
317
+ };
318
+ }
319
+ if (extensionName) {
320
+ return {
321
+ name,
322
+ extensionName,
323
+ isFormSpec: true,
324
+ isMarker,
325
+ };
326
+ }
327
+ if (isMarker) {
328
+ return {
329
+ name,
330
+ isFormSpec: true,
331
+ isMarker: true,
332
+ };
333
+ }
334
+ return null;
335
+ }
336
+ //# sourceMappingURL=decorator-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorator-extractor.js","sourceRoot":"","sources":["../../src/analyzer/decorator-extractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * JSDoc constraint tag extractor.
3
+ *
4
+ * Extracts constraint tags from JSDoc comments on class fields and returns
5
+ * synthetic {@link DecoratorInfo} objects that integrate seamlessly with
6
+ * the existing decorator-based constraint pipeline.
7
+ *
8
+ * Supported tags correspond to keys in {@link CONSTRAINT_TAG_DEFINITIONS}
9
+ * from `@formspec/core` (e.g., `@Minimum`, `@Maximum`, `@Pattern`).
10
+ */
11
+ import * as ts from "typescript";
12
+ import type { DecoratorInfo } from "./decorator-extractor.js";
13
+ /**
14
+ * Extracts JSDoc constraint tags from a TypeScript AST node and returns
15
+ * synthetic {@link DecoratorInfo} objects.
16
+ *
17
+ * For each recognised tag (case-sensitive PascalCase match against
18
+ * {@link CONSTRAINT_TAG_DEFINITIONS}), the comment text is parsed
19
+ * according to the tag's declared value type:
20
+ * - `"number"` tags: parsed via `Number()` — skipped when NaN
21
+ * - `"string"` tags (`Pattern`): used as-is (trimmed)
22
+ *
23
+ * @param node - The AST node to inspect for JSDoc tags
24
+ * @returns Synthetic decorator info objects for each valid constraint tag
25
+ */
26
+ export declare function extractJSDocConstraints(node: ts.Node): DecoratorInfo[];
27
+ /**
28
+ * Extracts `@Field_displayName` and `@Field_description` TSDoc tags from
29
+ * a node and returns a synthetic `Field` {@link DecoratorInfo} if either
30
+ * is present.
31
+ *
32
+ * This enables interface properties to carry display metadata via TSDoc
33
+ * tags instead of the `@Field` decorator (which requires a class):
34
+ *
35
+ * ```typescript
36
+ * interface Config {
37
+ * // @Field_displayName Program Name
38
+ * // @Field_description Internal identifier
39
+ * programName: string;
40
+ * }
41
+ * ```
42
+ *
43
+ * @param node - The AST node to inspect for display metadata tags
44
+ * @returns A synthetic `Field` decorator info, or null if no tags found
45
+ */
46
+ export declare function extractJSDocFieldMetadata(node: ts.Node): DecoratorInfo | null;
47
+ //# sourceMappingURL=jsdoc-constraints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsdoc-constraints.d.ts","sourceRoot":"","sources":["../../src/analyzer/jsdoc-constraints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE5E;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,aAAa,EAAE,CAoDtE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,aAAa,GAAG,IAAI,CAiC7E"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * JSDoc constraint tag extractor.
3
+ *
4
+ * Extracts constraint tags from JSDoc comments on class fields and returns
5
+ * synthetic {@link DecoratorInfo} objects that integrate seamlessly with
6
+ * the existing decorator-based constraint pipeline.
7
+ *
8
+ * Supported tags correspond to keys in {@link CONSTRAINT_TAG_DEFINITIONS}
9
+ * from `@formspec/core` (e.g., `@Minimum`, `@Maximum`, `@Pattern`).
10
+ */
11
+ import * as ts from "typescript";
12
+ import { CONSTRAINT_TAG_DEFINITIONS } from "@formspec/core";
13
+ /**
14
+ * Extracts JSDoc constraint tags from a TypeScript AST node and returns
15
+ * synthetic {@link DecoratorInfo} objects.
16
+ *
17
+ * For each recognised tag (case-sensitive PascalCase match against
18
+ * {@link CONSTRAINT_TAG_DEFINITIONS}), the comment text is parsed
19
+ * according to the tag's declared value type:
20
+ * - `"number"` tags: parsed via `Number()` — skipped when NaN
21
+ * - `"string"` tags (`Pattern`): used as-is (trimmed)
22
+ *
23
+ * @param node - The AST node to inspect for JSDoc tags
24
+ * @returns Synthetic decorator info objects for each valid constraint tag
25
+ */
26
+ export function extractJSDocConstraints(node) {
27
+ const results = [];
28
+ const jsDocTags = ts.getJSDocTags(node);
29
+ for (const tag of jsDocTags) {
30
+ const tagName = tag.tagName.text;
31
+ // Case-sensitive check against known constraint tags
32
+ if (!(tagName in CONSTRAINT_TAG_DEFINITIONS)) {
33
+ continue;
34
+ }
35
+ const constraintName = tagName;
36
+ const expectedType = CONSTRAINT_TAG_DEFINITIONS[constraintName];
37
+ // Extract comment text — can be string, NodeArray<JSDocComment>, or undefined
38
+ const commentText = getTagCommentText(tag);
39
+ if (commentText === undefined || commentText === "") {
40
+ continue;
41
+ }
42
+ const trimmed = commentText.trim();
43
+ if (trimmed === "") {
44
+ continue;
45
+ }
46
+ if (expectedType === "number") {
47
+ const value = Number(trimmed);
48
+ if (Number.isNaN(value)) {
49
+ continue;
50
+ }
51
+ results.push(createSyntheticDecorator(constraintName, value));
52
+ }
53
+ else if (expectedType === "json") {
54
+ // JSON type (EnumOptions) — parse inline JSON array only.
55
+ // Downstream UI schema generation expects an array of options.
56
+ try {
57
+ const parsed = JSON.parse(trimmed);
58
+ if (!Array.isArray(parsed)) {
59
+ continue;
60
+ }
61
+ results.push(createSyntheticDecorator(constraintName, parsed));
62
+ }
63
+ catch {
64
+ // Skip malformed JSON
65
+ continue;
66
+ }
67
+ }
68
+ else {
69
+ // "string" type (Pattern)
70
+ results.push(createSyntheticDecorator(constraintName, trimmed));
71
+ }
72
+ }
73
+ return results;
74
+ }
75
+ /**
76
+ * Extracts `@Field_displayName` and `@Field_description` TSDoc tags from
77
+ * a node and returns a synthetic `Field` {@link DecoratorInfo} if either
78
+ * is present.
79
+ *
80
+ * This enables interface properties to carry display metadata via TSDoc
81
+ * tags instead of the `@Field` decorator (which requires a class):
82
+ *
83
+ * ```typescript
84
+ * interface Config {
85
+ * // @Field_displayName Program Name
86
+ * // @Field_description Internal identifier
87
+ * programName: string;
88
+ * }
89
+ * ```
90
+ *
91
+ * @param node - The AST node to inspect for display metadata tags
92
+ * @returns A synthetic `Field` decorator info, or null if no tags found
93
+ */
94
+ export function extractJSDocFieldMetadata(node) {
95
+ const jsDocTags = ts.getJSDocTags(node);
96
+ let displayName;
97
+ let description;
98
+ for (const tag of jsDocTags) {
99
+ const tagName = tag.tagName.text;
100
+ const commentText = getTagCommentText(tag);
101
+ if (commentText === undefined || commentText.trim() === "") {
102
+ continue;
103
+ }
104
+ const trimmed = commentText.trim();
105
+ if (tagName === "Field_displayName") {
106
+ displayName = trimmed;
107
+ }
108
+ else if (tagName === "Field_description") {
109
+ description = trimmed;
110
+ }
111
+ }
112
+ if (displayName === undefined && description === undefined) {
113
+ return null;
114
+ }
115
+ // Build the FieldOptions-shaped arg object
116
+ const fieldOpts = {
117
+ ...(displayName !== undefined ? { displayName } : {}),
118
+ ...(description !== undefined ? { description } : {}),
119
+ };
120
+ return createSyntheticDecorator("Field", fieldOpts);
121
+ }
122
+ /**
123
+ * Extracts the text content from a JSDoc tag's comment.
124
+ *
125
+ * The `tag.comment` property can be a plain string, an array of
126
+ * `JSDocComment` nodes, or undefined. This helper normalises all
127
+ * three cases to a single `string | undefined`.
128
+ */
129
+ function getTagCommentText(tag) {
130
+ if (tag.comment === undefined) {
131
+ return undefined;
132
+ }
133
+ if (typeof tag.comment === "string") {
134
+ return tag.comment;
135
+ }
136
+ // NodeArray<JSDocComment> — concatenate text spans
137
+ return ts.getTextOfJSDocComment(tag.comment);
138
+ }
139
+ /**
140
+ * Creates a synthetic {@link DecoratorInfo} for a JSDoc constraint tag.
141
+ *
142
+ * The `node` field is `undefined` because JSDoc constraints have no
143
+ * decorator AST node. Downstream constraint processing only uses
144
+ * the `name` and `args` fields.
145
+ */
146
+ function createSyntheticDecorator(name, value) {
147
+ return {
148
+ name,
149
+ args: [value],
150
+ node: undefined,
151
+ };
152
+ }
153
+ //# sourceMappingURL=jsdoc-constraints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsdoc-constraints.js","sourceRoot":"","sources":["../../src/analyzer/jsdoc-constraints.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * TypeScript program setup for static analysis.
3
+ *
4
+ * Creates a TypeScript program with type checker from a source file,
5
+ * using the project's tsconfig.json for compiler options.
6
+ */
7
+ import * as ts from "typescript";
8
+ /**
9
+ * Result of creating a TypeScript program for analysis.
10
+ */
11
+ export interface ProgramContext {
12
+ /** The TypeScript program */
13
+ program: ts.Program;
14
+ /** Type checker for resolving types */
15
+ checker: ts.TypeChecker;
16
+ /** The source file being analyzed */
17
+ sourceFile: ts.SourceFile;
18
+ }
19
+ /**
20
+ * Creates a TypeScript program for analyzing a source file.
21
+ *
22
+ * Looks for tsconfig.json in the file's directory or parent directories.
23
+ * Falls back to default compiler options if no config is found.
24
+ *
25
+ * @param filePath - Absolute path to the TypeScript source file
26
+ * @returns Program context with checker and source file
27
+ */
28
+ export declare function createProgramContext(filePath: string): ProgramContext;
29
+ /**
30
+ * Finds a class declaration by name in a source file.
31
+ *
32
+ * @param sourceFile - The source file to search
33
+ * @param className - Name of the class to find
34
+ * @returns The class declaration node, or null if not found
35
+ */
36
+ export declare function findClassByName(sourceFile: ts.SourceFile, className: string): ts.ClassDeclaration | null;
37
+ /**
38
+ * Finds an interface declaration by name in a source file.
39
+ *
40
+ * @param sourceFile - The source file to search
41
+ * @param interfaceName - Name of the interface to find
42
+ * @returns The interface declaration node, or null if not found
43
+ */
44
+ export declare function findInterfaceByName(sourceFile: ts.SourceFile, interfaceName: string): ts.InterfaceDeclaration | null;
45
+ /**
46
+ * Finds a type alias declaration by name in a source file.
47
+ *
48
+ * @param sourceFile - The source file to search
49
+ * @param aliasName - Name of the type alias to find
50
+ * @returns The type alias declaration node, or null if not found
51
+ */
52
+ export declare function findTypeAliasByName(sourceFile: ts.SourceFile, aliasName: string): ts.TypeAliasDeclaration | null;
53
+ //# sourceMappingURL=program.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../../src/analyzer/program.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAGjC;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,6BAA6B;IAC7B,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC;IACpB,uCAAuC;IACvC,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC;IACxB,qCAAqC;IACrC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CA6DrE;AA6BD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,SAAS,EAAE,MAAM,GAChB,EAAE,CAAC,gBAAgB,GAAG,IAAI,CAE5B;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,aAAa,EAAE,MAAM,GACpB,EAAE,CAAC,oBAAoB,GAAG,IAAI,CAEhC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,SAAS,EAAE,MAAM,GAChB,EAAE,CAAC,oBAAoB,GAAG,IAAI,CAEhC"}