@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.
- package/README.md +138 -0
- package/dist/__tests__/analyzer-edge-cases.test.d.ts +13 -0
- package/dist/__tests__/analyzer-edge-cases.test.d.ts.map +1 -0
- package/dist/__tests__/analyzer-edge-cases.test.js +376 -0
- package/dist/__tests__/analyzer-edge-cases.test.js.map +1 -0
- package/dist/__tests__/analyzer.test.d.ts +5 -0
- package/dist/__tests__/analyzer.test.d.ts.map +1 -0
- package/dist/__tests__/analyzer.test.js +190 -0
- package/dist/__tests__/analyzer.test.js.map +1 -0
- package/dist/__tests__/cli.test.js.map +1 -1
- package/dist/__tests__/codegen.test.d.ts +5 -0
- package/dist/__tests__/codegen.test.d.ts.map +1 -0
- package/dist/__tests__/codegen.test.js +506 -0
- package/dist/__tests__/codegen.test.js.map +1 -0
- package/dist/__tests__/decorator-pipeline.test.d.ts +11 -0
- package/dist/__tests__/decorator-pipeline.test.d.ts.map +1 -0
- package/dist/__tests__/decorator-pipeline.test.js +460 -0
- package/dist/__tests__/decorator-pipeline.test.js.map +1 -0
- package/dist/__tests__/edge-cases.test.js +10 -4
- package/dist/__tests__/edge-cases.test.js.map +1 -1
- package/dist/__tests__/fixtures/edge-cases.d.ts +110 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -0
- package/dist/__tests__/fixtures/edge-cases.js +137 -0
- package/dist/__tests__/fixtures/edge-cases.js.map +1 -0
- package/dist/__tests__/fixtures/example-a-builtins.d.ts +12 -0
- package/dist/__tests__/fixtures/example-a-builtins.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-a-builtins.js +100 -0
- package/dist/__tests__/fixtures/example-a-builtins.js.map +1 -0
- package/dist/__tests__/fixtures/example-b-decorators.d.ts +5 -0
- package/dist/__tests__/fixtures/example-b-decorators.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-b-decorators.js +5 -0
- package/dist/__tests__/fixtures/example-b-decorators.js.map +1 -0
- package/dist/__tests__/fixtures/example-b-extended.d.ts +5 -0
- package/dist/__tests__/fixtures/example-b-extended.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-b-extended.js +60 -0
- package/dist/__tests__/fixtures/example-b-extended.js.map +1 -0
- package/dist/__tests__/fixtures/example-c-custom.d.ts +5 -0
- package/dist/__tests__/fixtures/example-c-custom.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-c-custom.js +61 -0
- package/dist/__tests__/fixtures/example-c-custom.js.map +1 -0
- package/dist/__tests__/fixtures/example-c-decorators.d.ts +5 -0
- package/dist/__tests__/fixtures/example-c-decorators.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-c-decorators.js +4 -0
- package/dist/__tests__/fixtures/example-c-decorators.js.map +1 -0
- package/dist/__tests__/fixtures/example-d-mixed-decorators.d.ts +6 -0
- package/dist/__tests__/fixtures/example-d-mixed-decorators.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-d-mixed-decorators.js +75 -0
- package/dist/__tests__/fixtures/example-d-mixed-decorators.js.map +1 -0
- package/dist/__tests__/fixtures/example-e-decorators.d.ts +11 -0
- package/dist/__tests__/fixtures/example-e-decorators.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-e-decorators.js +10 -0
- package/dist/__tests__/fixtures/example-e-decorators.js.map +1 -0
- package/dist/__tests__/fixtures/example-e-no-namespace.d.ts +5 -0
- package/dist/__tests__/fixtures/example-e-no-namespace.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-e-no-namespace.js +61 -0
- package/dist/__tests__/fixtures/example-e-no-namespace.js.map +1 -0
- package/dist/__tests__/fixtures/example-interface-types.d.ts +102 -0
- package/dist/__tests__/fixtures/example-interface-types.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-interface-types.js +8 -0
- package/dist/__tests__/fixtures/example-interface-types.js.map +1 -0
- package/dist/__tests__/fixtures/example-jsdoc-constraints.d.ts +16 -0
- package/dist/__tests__/fixtures/example-jsdoc-constraints.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-jsdoc-constraints.js +98 -0
- package/dist/__tests__/fixtures/example-jsdoc-constraints.js.map +1 -0
- package/dist/__tests__/fixtures/example-nested-class.d.ts +45 -0
- package/dist/__tests__/fixtures/example-nested-class.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-nested-class.js +248 -0
- package/dist/__tests__/fixtures/example-nested-class.js.map +1 -0
- package/dist/__tests__/fixtures/sample-forms.d.ts +55 -0
- package/dist/__tests__/fixtures/sample-forms.d.ts.map +1 -0
- package/dist/__tests__/fixtures/sample-forms.js +78 -0
- package/dist/__tests__/fixtures/sample-forms.js.map +1 -0
- package/dist/__tests__/generator.test.js +29 -3
- package/dist/__tests__/generator.test.js.map +1 -1
- package/dist/__tests__/integration.test.js +1 -3
- package/dist/__tests__/integration.test.js.map +1 -1
- package/dist/__tests__/interface-types.test.d.ts +11 -0
- package/dist/__tests__/interface-types.test.d.ts.map +1 -0
- package/dist/__tests__/interface-types.test.js +404 -0
- package/dist/__tests__/interface-types.test.js.map +1 -0
- package/dist/__tests__/jsdoc-constraints.test.d.ts +10 -0
- package/dist/__tests__/jsdoc-constraints.test.d.ts.map +1 -0
- package/dist/__tests__/jsdoc-constraints.test.js +465 -0
- package/dist/__tests__/jsdoc-constraints.test.js.map +1 -0
- package/dist/__tests__/write-schemas.test.js +10 -8
- package/dist/__tests__/write-schemas.test.js.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +139 -0
- package/dist/analyzer/class-analyzer.d.ts.map +1 -0
- package/dist/analyzer/class-analyzer.js +377 -0
- package/dist/analyzer/class-analyzer.js.map +1 -0
- package/dist/analyzer/decorator-extractor.d.ts +78 -0
- package/dist/analyzer/decorator-extractor.d.ts.map +1 -0
- package/dist/analyzer/decorator-extractor.js +336 -0
- package/dist/analyzer/decorator-extractor.js.map +1 -0
- package/dist/analyzer/jsdoc-constraints.d.ts +47 -0
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -0
- package/dist/analyzer/jsdoc-constraints.js +153 -0
- package/dist/analyzer/jsdoc-constraints.js.map +1 -0
- package/dist/analyzer/program.d.ts +53 -0
- package/dist/analyzer/program.d.ts.map +1 -0
- package/dist/analyzer/program.js +114 -0
- package/dist/analyzer/program.js.map +1 -0
- package/dist/analyzer/type-converter.d.ts +75 -0
- package/dist/analyzer/type-converter.d.ts.map +1 -0
- package/dist/analyzer/type-converter.js +474 -0
- package/dist/analyzer/type-converter.js.map +1 -0
- package/dist/browser.d.ts +54 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +48 -0
- package/dist/browser.js.map +1 -0
- package/dist/build.d.ts +580 -0
- package/dist/cli.js +1 -3
- package/dist/cli.js.map +1 -1
- package/dist/codegen/index.d.ts +75 -0
- package/dist/codegen/index.d.ts.map +1 -0
- package/dist/codegen/index.js +597 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/generators/class-schema.d.ts +118 -0
- package/dist/generators/class-schema.d.ts.map +1 -0
- package/dist/generators/class-schema.js +140 -0
- package/dist/generators/class-schema.js.map +1 -0
- package/dist/generators/method-schema.d.ts +67 -0
- package/dist/generators/method-schema.d.ts.map +1 -0
- package/dist/generators/method-schema.js +108 -0
- package/dist/generators/method-schema.js.map +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/internals.d.ts +20 -0
- package/dist/internals.d.ts.map +1 -0
- package/dist/internals.js +22 -0
- package/dist/internals.js.map +1 -0
- package/dist/json-schema/generator.d.ts.map +1 -1
- package/dist/json-schema/generator.js +26 -6
- package/dist/json-schema/generator.js.map +1 -1
- package/dist/json-schema/types.d.ts +28 -0
- package/dist/json-schema/types.d.ts.map +1 -1
- package/dist/json-schema/types.js +27 -1
- package/dist/json-schema/types.js.map +1 -1
- package/dist/ui-schema/generator.d.ts.map +1 -1
- package/dist/ui-schema/generator.js +1 -3
- package/dist/ui-schema/generator.js.map +1 -1
- package/dist/ui-schema/types.d.ts.map +1 -1
- 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":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAA8B,MAAM,gBAAgB,CAAC;AA4C5D;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAqD;IAErD,MAAM,UAAU,GAAoB,EAAE,CAAC;IAEvC,6CAA6C;IAC7C,MAAM,SAAS,GAAG,EAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEtF,IAAI,CAAC,SAAS;QAAE,OAAO,UAAU,CAAC;IAElC,KAAK,MAAM,SAAS,IAAI,SAAS,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,IAAI,EAAE,CAAC;YACT,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,SAAuB;IAC7C,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC;IAElC,+BAA+B;IAC/B,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,EAAE;YACR,IAAI,EAAE,SAAS;SAChB,CAAC;IACJ,CAAC;IAED,oCAAoC;IACpC,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;QAE/B,qBAAqB;QACrB,IAAI,IAAI,GAAkB,IAAI,CAAC;QAC/B,IAAI,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QACrB,CAAC;aAAM,IAAI,EAAE,CAAC,0BAA0B,CAAC,MAAM,CAAC,EAAE,CAAC;YACjD,mDAAmD;YACnD,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;QAC1B,CAAC;QAED,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QAEvB,oBAAoB;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAEjD,OAAO;YACL,IAAI;YACJ,IAAI;YACJ,IAAI,EAAE,SAAS;SAChB,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CAAC,IAAmB;IAC1C,iBAAiB;IACjB,IAAI,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED,kBAAkB;IAClB,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,0DAA0D;IAC1D,IAAI,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;QAC5C,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC;QAC7C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,eAAe;IACf,IAAI,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;QAC5C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,iDAAiD;IACjD,IAAI,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,QAAQ,KAAK,EAAE,CAAC,UAAU,CAAC,UAAU,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACpF,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,KAAK,EAAE,CAAC,UAAU,CAAC,SAAS,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACnF,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,gBAAgB;IAChB,IAAI,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;YAC9B,IAAI,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC,EAAE,CAAC;gBAC3B,wCAAwC;gBACxC,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,eAAe,CAAC,EAAE,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,iBAAiB;IACjB,IAAI,EAAE,CAAC,yBAAyB,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,GAAG,GAAiC,EAAE,CAAC;QAC7C,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACnC,IAAI,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClC,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACvC,IAAI,GAAG,EAAE,CAAC;oBACR,GAAG,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBAC/C,CAAC;YACH,CAAC;iBAAM,IAAI,EAAE,CAAC,6BAA6B,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClD,iDAAiD;gBACjD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;gBAC3B,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;YAClB,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,iCAAiC;IACjC,IAAI,EAAE,CAAC,+BAA+B,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED,oDAAoD;IACpD,IAAI,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC;QAC5B,0CAA0C;QAC1C,MAAM,SAAS,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAClB,OAAO,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,gEAAgE;IAChE,IAAI,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,IACE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC;YAChC,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,QAAQ;YACjC,IAAI,CAAC,SAAS;YACd,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EACzB,CAAC;YACD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YACnC,IAAI,QAAQ,IAAI,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC7C,OAAO,QAAQ,CAAC,IAAI,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAED,mDAAmD;IACnD,8CAA8C;IAC9C,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,qCAAqC;IACrC,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,IAAqB;IAC5C,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IACD,IAAI,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IACD,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IACD,uDAAuD;IACvD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAmE;IACjG,mBAAmB;IACnB,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE;IAE/B,WAAW;IACX,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE;IAE/B,sBAAsB;IACtB,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE;IAElC,eAAe;IACf,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE;IAEpC,sBAAsB;IACtB,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE;IACjC,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE;IACjC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE;IAC1C,gBAAgB,EAAE,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE;IAE1C,qBAAqB;IACrB,SAAS,EAAE,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE;IACnC,SAAS,EAAE,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE;IACnC,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE;CACzB,CAAC;AAEX;;;;GAIG;AACH,SAAS,wBAAwB,CAAC,QAAgB;IAChD,mDAAmD;IACnD,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAChD,OAAO,CACL,UAAU,CAAC,QAAQ,CAAC,mCAAmC,CAAC;QACxD,UAAU,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAC7C,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAC9B,SAAuB,EACvB,OAAuB;IAEvB,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC;IAElC,gCAAgC;IAChC,IAAI,UAAmB,CAAC;IACxB,IAAI,IAAY,CAAC;IAEjB,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,sCAAsC;QACtC,UAAU,GAAG,IAAI,CAAC;QAClB,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;SAAM,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,4CAA4C;QAC5C,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACrC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;YAC7B,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,IAAI,CAAC;IACd,CAAC;IAED,yCAAyC;IACzC,IAAI,IAAI,IAAI,mBAAmB,EAAE,CAAC;QAChC,4EAA4E;QAC5E,MAAM,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC;QACvD,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;YACzC,IAAI,YAAY,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5C,MAAM,IAAI,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;gBAC7B,IAAI,IAAI,EAAE,CAAC;oBACT,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;oBACxC,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC;oBACrC,IAAI,wBAAwB,CAAC,QAAQ,CAAC,EAAE,CAAC;wBACvC,OAAO;4BACL,IAAI;4BACJ,UAAU,EAAE,IAAI;4BAChB,QAAQ,EAAE,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC;yBACrC,CAAC;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,8DAA8D;IAC9D,MAAM,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAC/D,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IAEjC,MAAM,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;IAEnC,IAAI,cAAkC,CAAC;IACvC,IAAI,aAAiC,CAAC;IACtC,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,kFAAkF;QAClF,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,EAAY,CAAC;QAEpD,yEAAyE;QACzE,4EAA4E;QAC5E,oFAAoF;QACpF,4EAA4E;QAC5E,kDAAkD;QAClD,IACE,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC;YAC7B,CAAC,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,EACtF,CAAC;YACD,MAAM,QAAQ,GAAG,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;YAC/C,IAAI,QAAQ,CAAC,eAAe,EAAE,EAAE,CAAC;gBAC/B,cAAc,GAAG,QAAQ,CAAC,KAAK,CAAC;YAClC,CAAC;QACH,CAAC;QAED,IACE,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC;YAC7B,CAAC,WAAW,CAAC,QAAQ,CAAC,oBAAoB,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC,EAC1F,CAAC;YACD,MAAM,QAAQ,GAAG,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;YAC/C,IAAI,QAAQ,CAAC,eAAe,EAAE,EAAE,CAAC;gBAC/B,aAAa,GAAG,QAAQ,CAAC,KAAK,CAAC;YACjC,CAAC;QACH,CAAC;QAED,IACE,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC;YAC7B,CAAC,WAAW,CAAC,QAAQ,CAAC,iBAAiB,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,EACpF,CAAC;YACD,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;IACH,CAAC;IAED,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO;YACL,IAAI;YACJ,cAAc;YACd,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,KAAK;SAChB,CAAC;IACJ,CAAC;IAED,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO;YACL,IAAI;YACJ,aAAa;YACb,UAAU,EAAE,IAAI;YAChB,QAAQ;SACT,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO;YACL,IAAI;YACJ,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,IAAI;SACf,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -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"}
|