@formspec/build 0.1.0-alpha.13 → 0.1.0-alpha.14

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 (39) hide show
  1. package/README.md +20 -20
  2. package/dist/__tests__/alias-chain-propagation.test.d.ts +9 -0
  3. package/dist/__tests__/alias-chain-propagation.test.d.ts.map +1 -0
  4. package/dist/__tests__/fixtures/alias-chains.d.ts +37 -0
  5. package/dist/__tests__/fixtures/alias-chains.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures/example-a-builtins.d.ts +7 -7
  7. package/dist/__tests__/fixtures/example-interface-types.d.ts +17 -17
  8. package/dist/__tests__/json-utils.test.d.ts +5 -0
  9. package/dist/__tests__/json-utils.test.d.ts.map +1 -0
  10. package/dist/__tests__/path-target-parser.test.d.ts +9 -0
  11. package/dist/__tests__/path-target-parser.test.d.ts.map +1 -0
  12. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  13. package/dist/analyzer/jsdoc-constraints.d.ts +2 -2
  14. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  15. package/dist/analyzer/json-utils.d.ts +22 -0
  16. package/dist/analyzer/json-utils.d.ts.map +1 -0
  17. package/dist/analyzer/tsdoc-parser.d.ts +18 -4
  18. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  19. package/dist/browser.cjs +76 -7
  20. package/dist/browser.cjs.map +1 -1
  21. package/dist/browser.js +76 -7
  22. package/dist/browser.js.map +1 -1
  23. package/dist/build.d.ts +1 -0
  24. package/dist/cli.cjs +140 -41
  25. package/dist/cli.cjs.map +1 -1
  26. package/dist/cli.js +145 -41
  27. package/dist/cli.js.map +1 -1
  28. package/dist/index.cjs +134 -40
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.js +139 -41
  31. package/dist/index.js.map +1 -1
  32. package/dist/internals.cjs +147 -46
  33. package/dist/internals.cjs.map +1 -1
  34. package/dist/internals.js +152 -47
  35. package/dist/internals.js.map +1 -1
  36. package/dist/json-schema/ir-generator.d.ts +1 -0
  37. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  38. package/dist/validate/constraint-validator.d.ts.map +1 -1
  39. package/package.json +3 -3
package/README.md CHANGED
@@ -106,11 +106,11 @@ The analyzer extracts type information and JSDoc constraint tags (e.g., `/** @Mi
106
106
 
107
107
  ### Entry Points
108
108
 
109
- | Entry Point | Audience | Description |
110
- | ------------------------- | ------------------ | ------------------------------------------------------------------------------------------------- |
111
- | `@formspec/build` | Public API | `buildFormSchemas`, `writeSchemas`, `generateSchemasFromClass`, schema generators |
112
- | `@formspec/build/browser` | Browser (playground) | Schema generators without Node.js `fs`/`path` — safe for bundlers |
113
- | `@formspec/build/internals` | CLI (unstable) | Internal APIs: `createProgramContext`, `analyzeClass`, `generateClassSchemas` |
109
+ | Entry Point | Audience | Description |
110
+ | --------------------------- | -------------------- | --------------------------------------------------------------------------------- |
111
+ | `@formspec/build` | Public API | `buildFormSchemas`, `writeSchemas`, `generateSchemasFromClass`, schema generators |
112
+ | `@formspec/build/browser` | Browser (playground) | Schema generators without Node.js `fs`/`path` — safe for bundlers |
113
+ | `@formspec/build/internals` | CLI (unstable) | Internal APIs: `createProgramContext`, `analyzeClass`, `generateClassSchemas` |
114
114
 
115
115
  ## Generated Output
116
116
 
@@ -149,25 +149,25 @@ The analyzer extracts type information and JSDoc constraint tags (e.g., `/** @Mi
149
149
 
150
150
  ### Functions
151
151
 
152
- | Function | Description |
153
- | ------------------------------------ | ------------------------------------------------------------------ |
154
- | `buildFormSchemas(form)` | Generate both JSON Schema and UI Schema |
155
- | `generateJsonSchema(form)` | Generate only JSON Schema |
156
- | `generateUiSchema(form)` | Generate only UI Schema |
157
- | `writeSchemas(form, options)` | Build and write schemas to disk |
158
- | `generateSchemasFromClass(options)` | Generate schemas from a TypeScript class via static analysis |
159
- | `generateSchemas(options)` | Generate schemas from a TypeScript type via static analysis |
152
+ | Function | Description |
153
+ | ----------------------------------- | ------------------------------------------------------------ |
154
+ | `buildFormSchemas(form)` | Generate both JSON Schema and UI Schema |
155
+ | `generateJsonSchema(form)` | Generate only JSON Schema |
156
+ | `generateUiSchema(form)` | Generate only UI Schema |
157
+ | `writeSchemas(form, options)` | Build and write schemas to disk |
158
+ | `generateSchemasFromClass(options)` | Generate schemas from a TypeScript class via static analysis |
159
+ | `generateSchemas(options)` | Generate schemas from a TypeScript type via static analysis |
160
160
 
161
161
  ### Types
162
162
 
163
- | Type | Description |
164
- | ------------------------- | --------------------------------------- |
165
- | `BuildResult` | Return type of `buildFormSchemas` |
166
- | `WriteSchemasOptions` | Options for `writeSchemas` |
167
- | `WriteSchemasResult` | Return type of `writeSchemas` |
168
- | `JsonSchema2020` | JSON Schema 2020-12 type |
163
+ | Type | Description |
164
+ | -------------------------- | -------------------------------------- |
165
+ | `BuildResult` | Return type of `buildFormSchemas` |
166
+ | `WriteSchemasOptions` | Options for `writeSchemas` |
167
+ | `WriteSchemasResult` | Return type of `writeSchemas` |
168
+ | `JsonSchema2020` | JSON Schema 2020-12 type |
169
169
  | `GenerateFromClassOptions` | Options for `generateSchemasFromClass` |
170
- | `UISchema` | JSON Forms UI Schema type |
170
+ | `UISchema` | JSON Forms UI Schema type |
171
171
 
172
172
  ## License
173
173
 
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Tests for transitive type alias constraint propagation.
3
+ *
4
+ * Verifies that constraints declared on type aliases propagate transitively
5
+ * through alias chains (e.g., Integer → Percentage → field), and that
6
+ * excessively deep chains throw a clear error.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=alias-chain-propagation.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alias-chain-propagation.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/alias-chain-propagation.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,37 @@
1
+ /** @multipleOf 1 */
2
+ type Integer = number;
3
+ /** @minimum 0 @maximum 100 */
4
+ type Percentage = Integer;
5
+ export declare class TwoLevelChain {
6
+ /** @minimum 10 */
7
+ cpuUsage: Percentage;
8
+ memoryUsage: Percentage;
9
+ }
10
+ /** @minimum 0 */
11
+ type Base = number;
12
+ /** @maximum 1000 */
13
+ type Mid = Base;
14
+ /** @multipleOf 5 */
15
+ type Leaf = Mid;
16
+ export declare class ThreeLevelChain {
17
+ value: Leaf;
18
+ }
19
+ export declare class NoAlias {
20
+ /** @minimum 0 */
21
+ count: number;
22
+ }
23
+ type D0 = number;
24
+ type D1 = D0;
25
+ type D2 = D1;
26
+ type D3 = D2;
27
+ type D4 = D3;
28
+ type D5 = D4;
29
+ type D6 = D5;
30
+ type D7 = D6;
31
+ type D8 = D7;
32
+ type D9 = D8;
33
+ export declare class ExceedsMaxDepth {
34
+ value: D9;
35
+ }
36
+ export {};
37
+ //# sourceMappingURL=alias-chains.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alias-chains.d.ts","sourceRoot":"","sources":["../../../src/__tests__/fixtures/alias-chains.ts"],"names":[],"mappings":"AAIA,oBAAoB;AACpB,KAAK,OAAO,GAAG,MAAM,CAAC;AAEtB,8BAA8B;AAC9B,KAAK,UAAU,GAAG,OAAO,CAAC;AAE1B,qBAAa,aAAa;IACxB,kBAAkB;IAClB,QAAQ,EAAG,UAAU,CAAC;IACtB,WAAW,EAAG,UAAU,CAAC;CAC1B;AAID,iBAAiB;AACjB,KAAK,IAAI,GAAG,MAAM,CAAC;AAEnB,oBAAoB;AACpB,KAAK,GAAG,GAAG,IAAI,CAAC;AAEhB,oBAAoB;AACpB,KAAK,IAAI,GAAG,GAAG,CAAC;AAEhB,qBAAa,eAAe;IAC1B,KAAK,EAAG,IAAI,CAAC;CACd;AAID,qBAAa,OAAO;IAClB,iBAAiB;IACjB,KAAK,EAAG,MAAM,CAAC;CAChB;AAOD,KAAK,EAAE,GAAG,MAAM,CAAC;AACjB,KAAK,EAAE,GAAG,EAAE,CAAC;AACb,KAAK,EAAE,GAAG,EAAE,CAAC;AACb,KAAK,EAAE,GAAG,EAAE,CAAC;AACb,KAAK,EAAE,GAAG,EAAE,CAAC;AACb,KAAK,EAAE,GAAG,EAAE,CAAC;AACb,KAAK,EAAE,GAAG,EAAE,CAAC;AACb,KAAK,EAAE,GAAG,EAAE,CAAC;AACb,KAAK,EAAE,GAAG,EAAE,CAAC;AACb,KAAK,EAAE,GAAG,EAAE,CAAC;AAEb,qBAAa,eAAe;IAC1B,KAAK,EAAG,EAAE,CAAC;CACZ"}
@@ -1,25 +1,25 @@
1
1
  export declare class ExampleAForm {
2
2
  /** @Field_displayName Full Name
3
3
  * @Field_description Your legal name
4
- * @MinLength 2
5
- * @MaxLength 100
4
+ * @minLength 2
5
+ * @maxLength 100
6
6
  */
7
7
  name: string;
8
8
  /** @Field_displayName Age
9
- * @Minimum 0
10
- * @Maximum 150
9
+ * @minimum 0
10
+ * @maximum 150
11
11
  */
12
12
  age: number;
13
13
  /** @Field_displayName Score
14
- * @ExclusiveMinimum 0
14
+ * @exclusiveMinimum 0
15
15
  */
16
16
  score: number;
17
17
  /** @Field_displayName Email
18
- * @Pattern ^[^@]+@[^@]+$
18
+ * @pattern ^[^@]+@[^@]+$
19
19
  */
20
20
  email?: string;
21
21
  /** @Field_displayName Country
22
- * @EnumOptions [{"id":"us","label":"United States"},{"id":"ca","label":"Canada"}]
22
+ * @enumOptions [{"id":"us","label":"United States"},{"id":"ca","label":"Canada"}]
23
23
  */
24
24
  country: "us" | "ca";
25
25
  state?: string;
@@ -8,13 +8,13 @@ export interface SimpleConfig {
8
8
  /**
9
9
  * @Field_displayName Full Name
10
10
  * @Field_description The user's legal name
11
- * @MinLength 1
12
- * @MaxLength 200
11
+ * @minLength 1
12
+ * @maxLength 200
13
13
  */
14
14
  name: string;
15
- /** @Field_displayName Age @Minimum 0 @Maximum 150 */
15
+ /** @Field_displayName Age @minimum 0 @maximum 150 */
16
16
  age: number;
17
- /** @Field_displayName Email @Pattern ^[^@]+@[^@]+$ */
17
+ /** @Field_displayName Email @pattern ^[^@]+@[^@]+$ */
18
18
  email?: string;
19
19
  /** @Field_displayName Active */
20
20
  active: boolean;
@@ -22,12 +22,12 @@ export interface SimpleConfig {
22
22
  export interface WithEnumOptions {
23
23
  /**
24
24
  * @Field_displayName Status
25
- * @EnumOptions ["draft","active","archived"]
25
+ * @enumOptions ["draft","active","archived"]
26
26
  */
27
27
  status: "draft" | "active" | "archived";
28
28
  /**
29
29
  * @Field_displayName Priority
30
- * @EnumOptions [{"id":"low","label":"Low Priority"},{"id":"high","label":"High Priority"}]
30
+ * @enumOptions [{"id":"low","label":"Low Priority"},{"id":"high","label":"High Priority"}]
31
31
  */
32
32
  priority: "low" | "high";
33
33
  }
@@ -47,9 +47,9 @@ export interface DeprecatedFieldInterface {
47
47
  fullName: string;
48
48
  }
49
49
  export type SimpleTypeAlias = {
50
- /** @Field_displayName Label @MinLength 1 */
50
+ /** @Field_displayName Label @minLength 1 */
51
51
  label: string;
52
- /** @Field_displayName Count @Minimum 0 */
52
+ /** @Field_displayName Count @minimum 0 */
53
53
  count: number;
54
54
  /** @Field_displayName Description */
55
55
  description?: string;
@@ -57,17 +57,17 @@ export type SimpleTypeAlias = {
57
57
  export type TypeAliasWithEnumOptions = {
58
58
  /**
59
59
  * @Field_displayName Color
60
- * @EnumOptions ["red","green","blue"]
60
+ * @enumOptions ["red","green","blue"]
61
61
  */
62
62
  color: "red" | "green" | "blue";
63
63
  };
64
64
  export type StringAlias = string;
65
65
  export type UnionAlias = "a" | "b" | "c";
66
- /** @Minimum 0 @Maximum 100 */
66
+ /** @minimum 0 @maximum 100 */
67
67
  export type Percent = number;
68
- /** @MinLength 1 @MaxLength 255 @Pattern ^[^@]+@[^@]+$ */
68
+ /** @minLength 1 @maxLength 255 @pattern ^[^@]+@[^@]+$ */
69
69
  export type Email = string;
70
- /** @Field_displayName Discount Rate @Field_description Percentage discount applied @Minimum 0 @Maximum 100 */
70
+ /** @Field_displayName Discount Rate @Field_description Percentage discount applied @minimum 0 @maximum 100 */
71
71
  export type AnnotatedPercent = number;
72
72
  export interface ConfigWithAliasedTypes {
73
73
  /** @Field_displayName Discount */
@@ -78,17 +78,17 @@ export interface ConfigWithAliasedTypes {
78
78
  taxRate: AnnotatedPercent;
79
79
  }
80
80
  export interface Address {
81
- /** @Field_displayName Street @MinLength 1 @MaxLength 200 */
81
+ /** @Field_displayName Street @minLength 1 @maxLength 200 */
82
82
  street: string;
83
- /** @Field_displayName City @MinLength 1 */
83
+ /** @Field_displayName City @minLength 1 */
84
84
  city: string;
85
- /** @Field_displayName Zip @Pattern ^[0-9]{5}$ */
85
+ /** @Field_displayName Zip @pattern ^[0-9]{5}$ */
86
86
  zip?: string;
87
87
  }
88
88
  export type ContactInfo = {
89
- /** @Field_displayName Email @Pattern ^[^@]+@[^@]+$ */
89
+ /** @Field_displayName Email @pattern ^[^@]+@[^@]+$ */
90
90
  email: string;
91
- /** @Field_displayName Phone @MaxLength 20 */
91
+ /** @Field_displayName Phone @maxLength 20 */
92
92
  phone?: string;
93
93
  };
94
94
  export interface NestedConfig {
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for the shared JSON parsing utility used by the analyzer pipeline.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=json-utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-utils.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/json-utils.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Unit tests for path-target extraction in TSDoc constraint tags.
3
+ *
4
+ * Covers both the low-level `extractPathTarget` helper and integration
5
+ * via `parseTSDocTags` to confirm that ConstraintNode objects carry the
6
+ * parsed `path` field when a `:identifier` prefix is present.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=path-target-parser.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-target-parser.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/path-target-parser.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -1 +1 @@
1
- {"version":3,"file":"class-analyzer.d.ts","sourceRoot":"","sources":["../../src/analyzer/class-analyzer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,KAAK,EACV,SAAS,EACT,QAAQ,EAKR,cAAc,EACd,SAAS,EACV,MAAM,gBAAgB,CAAC;AAmCxB;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,qEAAqE;IACrE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,6FAA6F;IAC7F,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC;CAC3E;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,gBAAgB;IAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,iEAAiE;IACjE,QAAQ,CAAC,YAAY,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACtD,kDAAkD;IAClD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACtD,0EAA0E;IAC1E,QAAQ,CAAC,eAAe,EAAE,SAAS,UAAU,EAAE,CAAC;IAChD,qBAAqB;IACrB,QAAQ,CAAC,aAAa,EAAE,SAAS,UAAU,EAAE,CAAC;CAC/C;AAED;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAClC;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAA;CAAE,GACzD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAMnD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,EAAE,CAAC,gBAAgB,EAC9B,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,IAAI,SAAK,GACR,eAAe,CA8BjB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,EAAE,CAAC,oBAAoB,EACtC,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,IAAI,SAAK,GACR,eAAe,CAiBjB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,EAAE,CAAC,oBAAoB,EAClC,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,IAAI,SAAK,GACR,0BAA0B,CAqC5B;AAoHD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,EAC5C,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,GACrB,QAAQ,CAoDV;AA4VD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,uBAAuB;IACvB,cAAc,EAAE,EAAE,CAAC,QAAQ,GAAG,SAAS,CAAC;IACxC,2BAA2B;IAC3B,UAAU,EAAE,EAAE,CAAC,IAAI,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,QAAQ,EAAE,EAAE,CAAC,QAAQ,GAAG,SAAS,CAAC;IAClC,oBAAoB;IACpB,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC;IACd,0DAA0D;IAC1D,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAC;CACnB"}
1
+ {"version":3,"file":"class-analyzer.d.ts","sourceRoot":"","sources":["../../src/analyzer/class-analyzer.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Extracts constraints and annotation tags from JSDoc comments on
5
5
  * class/interface fields and returns canonical IR nodes directly:
6
- * - {@link ConstraintNode} for set-influencing tags (@Minimum, @Pattern, etc.)
6
+ * - {@link ConstraintNode} for set-influencing tags (@minimum, @pattern, etc.)
7
7
  * - {@link AnnotationNode} for value-influencing tags (@Field_displayName, etc.)
8
8
  *
9
9
  * The IR extraction path uses the official `@microsoft/tsdoc` parser for
@@ -12,7 +12,7 @@
12
12
  * `@Field_displayName`).
13
13
  *
14
14
  * Supported constraints correspond to keys in {@link BUILTIN_CONSTRAINT_DEFINITIONS}
15
- * from `@formspec/core` (e.g., `@Minimum`, `@Maximum`, `@Pattern`).
15
+ * from `@formspec/core` (e.g., `@minimum`, `@maximum`, `@pattern`).
16
16
  */
17
17
  import * as ts from "typescript";
18
18
  import { type ConstraintNode, type AnnotationNode } from "@formspec/core";
@@ -1 +1 @@
1
- {"version":3,"file":"jsdoc-constraints.d.ts","sourceRoot":"","sources":["../../src/analyzer/jsdoc-constraints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,cAAc,EAEpB,MAAM,gBAAgB,CAAC;AAOxB;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,aAAa,EAAE,GACf;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAA;CAAE,CAAC;AAErC;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,sEAAsE;IACtE,IAAI,EAAE,EAAE,CAAC,SAAS,GAAG,SAAS,CAAC;CAChC;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,SAAK,GAAG,cAAc,EAAE,CAGtF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,SAAK,GAAG,cAAc,EAAE,CAGtF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,OAAO,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,EAAE,CAAC,UAAU,GAAG,SAAS,EACtC,IAAI,SAAK,GACR,cAAc,GAAG,IAAI,CAwCvB;AAMD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,cAAc,EAAE,CA8CvE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,cAAc,GAAG,IAAI,CAgC9E"}
1
+ {"version":3,"file":"jsdoc-constraints.d.ts","sourceRoot":"","sources":["../../src/analyzer/jsdoc-constraints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,cAAc,EAEpB,MAAM,gBAAgB,CAAC;AAQxB;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,aAAa,EAAE,GACf;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAA;CAAE,CAAC;AAErC;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,sEAAsE;IACtE,IAAI,EAAE,EAAE,CAAC,SAAS,GAAG,SAAS,CAAC;CAChC;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,SAAK,GAAG,cAAc,EAAE,CAGtF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,SAAK,GAAG,cAAc,EAAE,CAGtF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,OAAO,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,EAAE,CAAC,UAAU,GAAG,SAAS,EACtC,IAAI,SAAK,GACR,cAAc,GAAG,IAAI,CAwCvB;AAMD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,cAAc,EAAE,CA0CvE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,cAAc,GAAG,IAAI,CAgC9E"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Shared JSON parsing utilities for the analyzer pipeline.
3
+ */
4
+ /**
5
+ * Attempts to parse `text` as JSON.
6
+ *
7
+ * Returns the parsed value on success, or `null` if the input is not valid
8
+ * JSON. This is the canonical "try-parse" wrapper used by the constraint tag
9
+ * parsers so that every `JSON.parse` call in this package is consistent and
10
+ * centrally tested.
11
+ *
12
+ * Note: when the input is the literal string `"null"`, the return value is
13
+ * also `null` (valid JSON). This helper therefore cannot distinguish parse
14
+ * failure from a successfully-parsed JSON `null`. Callers that need to
15
+ * distinguish these cases should use a different API (for example, a wrapper
16
+ * that returns a discriminated result such as `{ ok: boolean, value?: unknown }`).
17
+ *
18
+ * @param text - Raw string to parse
19
+ * @returns The parsed value, or `null` on parse failure
20
+ */
21
+ export declare function tryParseJson(text: string): unknown;
22
+ //# sourceMappingURL=json-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-utils.d.ts","sourceRoot":"","sources":["../../src/analyzer/json-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMlD"}
@@ -8,9 +8,11 @@
8
8
  * The parser recognises two categories of tags:
9
9
  *
10
10
  * 1. **Constraint tags** (all alphanumeric, TSDoc-compliant):
11
- * `@Minimum`, `@Maximum`, `@ExclusiveMinimum`, `@ExclusiveMaximum`,
12
- * `@MinLength`, `@MaxLength`, `@Pattern`, `@EnumOptions`
11
+ * `@minimum`, `@maximum`, `@exclusiveMinimum`, `@exclusiveMaximum`,
12
+ * `@multipleOf`, `@minLength`, `@maxLength`, `@minItems`, `@maxItems`,
13
+ * `@pattern`, `@enumOptions`
13
14
  * — Parsed via TSDocParser as custom block tags.
15
+ * Both camelCase and PascalCase forms are accepted (e.g., `@Minimum`).
14
16
  *
15
17
  * 2. **Annotation tags** (`@Field_displayName`, `@Field_description`):
16
18
  * These contain underscores which are not valid in TSDoc tag names.
@@ -27,7 +29,7 @@
27
29
  * verbatim.
28
30
  */
29
31
  import * as ts from "typescript";
30
- import { type ConstraintNode, type AnnotationNode } from "@formspec/core";
32
+ import { type ConstraintNode, type AnnotationNode, type PathTarget } from "@formspec/core";
31
33
  /**
32
34
  * Result of parsing a single JSDoc comment attached to a TS AST node.
33
35
  */
@@ -42,7 +44,7 @@ export interface TSDocParseResult {
42
44
  * official TSDoc parser and returns canonical IR constraint and annotation
43
45
  * nodes.
44
46
  *
45
- * For constraint tags (`@Minimum`, `@Pattern`, `@EnumOptions`, etc.),
47
+ * For constraint tags (`@minimum`, `@pattern`, `@enumOptions`, etc.),
46
48
  * the structured TSDoc parser is used. For annotation tags that contain
47
49
  * underscores (`@Field_displayName`, `@Field_description`), the TypeScript
48
50
  * compiler JSDoc API is used as a fallback.
@@ -58,4 +60,16 @@ export declare function parseTSDocTags(node: ts.Node, file?: string): TSDocParse
58
60
  * Falls back to the TS compiler API for nodes without doc comments.
59
61
  */
60
62
  export declare function hasDeprecatedTagTSDoc(node: ts.Node): boolean;
63
+ /**
64
+ * Extracts a path-target prefix (`:fieldName`) from constraint tag text.
65
+ * Returns the parsed PathTarget and remaining text, or null if no path target.
66
+ *
67
+ * @example
68
+ * extractPathTarget(":value 0") // → { path: { segments: ["value"] }, remainingText: "0" }
69
+ * extractPathTarget("42") // → null
70
+ */
71
+ export declare function extractPathTarget(text: string): {
72
+ path: PathTarget;
73
+ remainingText: string;
74
+ } | null;
61
75
  //# sourceMappingURL=tsdoc-parser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tsdoc-parser.d.ts","sourceRoot":"","sources":["../../src/analyzer/tsdoc-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAYjC,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,cAAc,EAIpB,MAAM,gBAAgB,CAAC;AA8ExB;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,EAAE,SAAS,cAAc,EAAE,CAAC;IAChD,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,EAAE,SAAS,cAAc,EAAE,CAAC;CACjD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,SAAK,GAAG,gBAAgB,CAuHzE;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,OAAO,CAsB5D"}
1
+ {"version":3,"file":"tsdoc-parser.d.ts","sourceRoot":"","sources":["../../src/analyzer/tsdoc-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAYjC,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,cAAc,EAInB,KAAK,UAAU,EAChB,MAAM,gBAAgB,CAAC;AA6ExB;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,EAAE,SAAS,cAAc,EAAE,CAAC;IAChD,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,EAAE,SAAS,cAAc,EAAE,CAAC;CACjD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,SAAK,GAAG,gBAAgB,CAuHzE;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,OAAO,CAsB5D;AAMD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,GACX;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQpD"}
package/dist/browser.cjs CHANGED
@@ -408,8 +408,70 @@ function collectFields(elements, properties, required, ctx) {
408
408
  }
409
409
  function generateFieldSchema(field, ctx) {
410
410
  const schema = generateTypeNode(field.type, ctx);
411
- applyConstraints(schema, field.constraints);
411
+ const directConstraints = [];
412
+ const pathConstraints = [];
413
+ for (const c of field.constraints) {
414
+ if (c.path) {
415
+ pathConstraints.push(c);
416
+ } else {
417
+ directConstraints.push(c);
418
+ }
419
+ }
420
+ applyConstraints(schema, directConstraints);
412
421
  applyAnnotations(schema, field.annotations);
422
+ if (pathConstraints.length === 0) {
423
+ return schema;
424
+ }
425
+ return applyPathTargetedConstraints(schema, pathConstraints);
426
+ }
427
+ function applyPathTargetedConstraints(schema, pathConstraints) {
428
+ if (schema.type === "array" && schema.items) {
429
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
430
+ return schema;
431
+ }
432
+ const byTarget = /* @__PURE__ */ new Map();
433
+ for (const c of pathConstraints) {
434
+ const target = c.path?.segments[0];
435
+ if (!target) continue;
436
+ const group = byTarget.get(target) ?? [];
437
+ group.push(c);
438
+ byTarget.set(target, group);
439
+ }
440
+ const propertyOverrides = {};
441
+ for (const [target, constraints] of byTarget) {
442
+ const subSchema = {};
443
+ applyConstraints(subSchema, constraints);
444
+ propertyOverrides[target] = subSchema;
445
+ }
446
+ if (schema.$ref) {
447
+ const { $ref, ...rest } = schema;
448
+ const refPart = { $ref };
449
+ const overridePart = {
450
+ properties: propertyOverrides,
451
+ ...rest
452
+ };
453
+ return { allOf: [refPart, overridePart] };
454
+ }
455
+ if (schema.type === "object" && schema.properties) {
456
+ const missingOverrides = {};
457
+ for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
458
+ if (schema.properties[target]) {
459
+ Object.assign(schema.properties[target], overrideSchema);
460
+ } else {
461
+ missingOverrides[target] = overrideSchema;
462
+ }
463
+ }
464
+ if (Object.keys(missingOverrides).length === 0) {
465
+ return schema;
466
+ }
467
+ return {
468
+ allOf: [schema, { properties: missingOverrides }]
469
+ };
470
+ }
471
+ if (schema.allOf) {
472
+ schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
473
+ return schema;
474
+ }
413
475
  return schema;
414
476
  }
415
477
  function generateTypeNode(type, ctx) {
@@ -929,14 +991,10 @@ function addUnknownExtension(ctx, message, primary) {
929
991
  });
930
992
  }
931
993
  function findNumeric(constraints, constraintKind) {
932
- return constraints.find(
933
- (c) => c.constraintKind === constraintKind
934
- );
994
+ return constraints.find((c) => c.constraintKind === constraintKind);
935
995
  }
936
996
  function findLength(constraints, constraintKind) {
937
- return constraints.find(
938
- (c) => c.constraintKind === constraintKind
939
- );
997
+ return constraints.find((c) => c.constraintKind === constraintKind);
940
998
  }
941
999
  function findAllowedMembers(constraints) {
942
1000
  return constraints.filter(
@@ -1060,6 +1118,17 @@ function checkTypeApplicability(ctx, fieldName, type, constraints) {
1060
1118
  const isEnum = type.kind === "enum";
1061
1119
  const label = typeLabel(type);
1062
1120
  for (const constraint of constraints) {
1121
+ if (constraint.path) {
1122
+ const isTraversable = type.kind === "object" || type.kind === "array" || type.kind === "reference";
1123
+ if (!isTraversable) {
1124
+ addTypeMismatch(
1125
+ ctx,
1126
+ `Field "${fieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${label}" cannot be traversed`,
1127
+ constraint.provenance
1128
+ );
1129
+ }
1130
+ continue;
1131
+ }
1063
1132
  const ck = constraint.constraintKind;
1064
1133
  switch (ck) {
1065
1134
  case "minimum":