@formspec/eslint-plugin 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 (74) hide show
  1. package/README.md +237 -0
  2. package/dist/index.d.ts +71 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +117 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/rules/consistent-constraints.d.ts +22 -0
  7. package/dist/rules/consistent-constraints.d.ts.map +1 -0
  8. package/dist/rules/consistent-constraints.js +178 -0
  9. package/dist/rules/consistent-constraints.js.map +1 -0
  10. package/dist/rules/constraints/allowed-field-types.d.ts +16 -0
  11. package/dist/rules/constraints/allowed-field-types.d.ts.map +1 -0
  12. package/dist/rules/constraints/allowed-field-types.js +133 -0
  13. package/dist/rules/constraints/allowed-field-types.js.map +1 -0
  14. package/dist/rules/constraints/allowed-layouts.d.ts +17 -0
  15. package/dist/rules/constraints/allowed-layouts.d.ts.map +1 -0
  16. package/dist/rules/constraints/allowed-layouts.js +83 -0
  17. package/dist/rules/constraints/allowed-layouts.js.map +1 -0
  18. package/dist/rules/constraints/index.d.ts +9 -0
  19. package/dist/rules/constraints/index.d.ts.map +1 -0
  20. package/dist/rules/constraints/index.js +9 -0
  21. package/dist/rules/constraints/index.js.map +1 -0
  22. package/dist/rules/decorator-allowed-field-types.d.ts +17 -0
  23. package/dist/rules/decorator-allowed-field-types.d.ts.map +1 -0
  24. package/dist/rules/decorator-allowed-field-types.js +71 -0
  25. package/dist/rules/decorator-allowed-field-types.js.map +1 -0
  26. package/dist/rules/decorator-field-type-mismatch.d.ts +14 -0
  27. package/dist/rules/decorator-field-type-mismatch.d.ts.map +1 -0
  28. package/dist/rules/decorator-field-type-mismatch.js +116 -0
  29. package/dist/rules/decorator-field-type-mismatch.js.map +1 -0
  30. package/dist/rules/enum-options-match-type.d.ts +26 -0
  31. package/dist/rules/enum-options-match-type.d.ts.map +1 -0
  32. package/dist/rules/enum-options-match-type.js +115 -0
  33. package/dist/rules/enum-options-match-type.js.map +1 -0
  34. package/dist/rules/index.d.ts +13 -0
  35. package/dist/rules/index.d.ts.map +1 -0
  36. package/dist/rules/index.js +13 -0
  37. package/dist/rules/index.js.map +1 -0
  38. package/dist/rules/no-conflicting-decorators.d.ts +15 -0
  39. package/dist/rules/no-conflicting-decorators.d.ts.map +1 -0
  40. package/dist/rules/no-conflicting-decorators.js +72 -0
  41. package/dist/rules/no-conflicting-decorators.js.map +1 -0
  42. package/dist/rules/no-duplicate-decorators.d.ts +19 -0
  43. package/dist/rules/no-duplicate-decorators.d.ts.map +1 -0
  44. package/dist/rules/no-duplicate-decorators.js +59 -0
  45. package/dist/rules/no-duplicate-decorators.js.map +1 -0
  46. package/dist/rules/prefer-custom-decorator.d.ts +22 -0
  47. package/dist/rules/prefer-custom-decorator.d.ts.map +1 -0
  48. package/dist/rules/prefer-custom-decorator.js +72 -0
  49. package/dist/rules/prefer-custom-decorator.js.map +1 -0
  50. package/dist/rules/showwhen-field-exists.d.ts +21 -0
  51. package/dist/rules/showwhen-field-exists.d.ts.map +1 -0
  52. package/dist/rules/showwhen-field-exists.js +68 -0
  53. package/dist/rules/showwhen-field-exists.js.map +1 -0
  54. package/dist/rules/showwhen-suggests-optional.d.ts +19 -0
  55. package/dist/rules/showwhen-suggests-optional.d.ts.map +1 -0
  56. package/dist/rules/showwhen-suggests-optional.js +53 -0
  57. package/dist/rules/showwhen-suggests-optional.js.map +1 -0
  58. package/dist/utils/decorator-utils.d.ts +105 -0
  59. package/dist/utils/decorator-utils.d.ts.map +1 -0
  60. package/dist/utils/decorator-utils.js +216 -0
  61. package/dist/utils/decorator-utils.js.map +1 -0
  62. package/dist/utils/index.d.ts +6 -0
  63. package/dist/utils/index.d.ts.map +1 -0
  64. package/dist/utils/index.js +6 -0
  65. package/dist/utils/index.js.map +1 -0
  66. package/dist/utils/jsdoc-utils.d.ts +31 -0
  67. package/dist/utils/jsdoc-utils.d.ts.map +1 -0
  68. package/dist/utils/jsdoc-utils.js +81 -0
  69. package/dist/utils/jsdoc-utils.js.map +1 -0
  70. package/dist/utils/type-utils.d.ts +82 -0
  71. package/dist/utils/type-utils.d.ts.map +1 -0
  72. package/dist/utils/type-utils.js +216 -0
  73. package/dist/utils/type-utils.js.map +1 -0
  74. package/package.json +50 -0
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Rule: enum-options-match-type
3
+ *
4
+ * Ensures @EnumOptions values match the field's TypeScript type.
5
+ *
6
+ * Valid:
7
+ * @EnumOptions(["a", "b", "c"])
8
+ * field!: "a" | "b" | "c";
9
+ *
10
+ * @EnumOptions([{ id: "x", label: "X" }])
11
+ * field!: "x";
12
+ *
13
+ * @EnumOptions(["a", "b"])
14
+ * field!: string; // Permissive - string accepts any enum
15
+ *
16
+ * Invalid:
17
+ * @EnumOptions(["a", "b", "c"])
18
+ * field!: "x" | "y"; // Mismatch
19
+ */
20
+ import { ESLintUtils } from "@typescript-eslint/utils";
21
+ import { findDecorator, getDecoratorArrayArg } from "../utils/decorator-utils.js";
22
+ import { getPropertyType, getStringLiteralUnionValues, isStringType } from "../utils/type-utils.js";
23
+ const createRule = ESLintUtils.RuleCreator((name) => `https://formspec.dev/eslint-plugin/rules/${name}`);
24
+ export const enumOptionsMatchType = createRule({
25
+ name: "enum-options-match-type",
26
+ meta: {
27
+ type: "problem",
28
+ docs: {
29
+ description: "Ensures @EnumOptions values match the field's TypeScript union type",
30
+ },
31
+ messages: {
32
+ enumOptionsMismatch: "@EnumOptions values don't match field type. Options: [{{options}}], Type: {{fieldType}}",
33
+ enumOptionsMissing: "@EnumOptions is missing values that are in the field type: [{{missing}}]",
34
+ enumOptionsExtra: "@EnumOptions has values not in the field type: [{{extra}}]",
35
+ },
36
+ schema: [],
37
+ },
38
+ defaultOptions: [],
39
+ create(context) {
40
+ const services = ESLintUtils.getParserServices(context);
41
+ const checker = services.program.getTypeChecker();
42
+ return {
43
+ PropertyDefinition(node) {
44
+ const enumOptionsDecorator = findDecorator(node, "EnumOptions");
45
+ if (!enumOptionsDecorator)
46
+ return;
47
+ const type = getPropertyType(node, services);
48
+ if (!type)
49
+ return;
50
+ // If field type is plain `string`, any enum options are valid
51
+ if (isStringType(type, checker) && !type.isUnion()) {
52
+ return;
53
+ }
54
+ // Get the enum option values from the decorator
55
+ const decoratorValues = getDecoratorArrayArg(enumOptionsDecorator);
56
+ if (!decoratorValues)
57
+ return;
58
+ // Extract IDs from decorator options
59
+ const optionIds = new Set();
60
+ for (const value of decoratorValues) {
61
+ if (typeof value === "string") {
62
+ optionIds.add(value);
63
+ }
64
+ else if (typeof value === "object" && value !== null && "id" in value) {
65
+ const id = value.id;
66
+ if (typeof id === "string") {
67
+ optionIds.add(id);
68
+ }
69
+ }
70
+ }
71
+ // Get the union type values from the field type
72
+ const typeValues = getStringLiteralUnionValues(type, checker);
73
+ if (!typeValues) {
74
+ // Field type is not a string literal union - can't compare
75
+ return;
76
+ }
77
+ const typeSet = new Set(typeValues);
78
+ // Find missing values (in type but not in options)
79
+ const missing = [];
80
+ for (const v of typeValues) {
81
+ if (!optionIds.has(v)) {
82
+ missing.push(v);
83
+ }
84
+ }
85
+ // Find extra values (in options but not in type)
86
+ const extra = [];
87
+ for (const v of optionIds) {
88
+ if (!typeSet.has(v)) {
89
+ extra.push(v);
90
+ }
91
+ }
92
+ // Report specific issues
93
+ if (missing.length > 0) {
94
+ context.report({
95
+ node: enumOptionsDecorator.node,
96
+ messageId: "enumOptionsMissing",
97
+ data: {
98
+ missing: missing.map((v) => `"${v}"`).join(", "),
99
+ },
100
+ });
101
+ }
102
+ if (extra.length > 0) {
103
+ context.report({
104
+ node: enumOptionsDecorator.node,
105
+ messageId: "enumOptionsExtra",
106
+ data: {
107
+ extra: extra.map((v) => `"${v}"`).join(", "),
108
+ },
109
+ });
110
+ }
111
+ },
112
+ };
113
+ },
114
+ });
115
+ //# sourceMappingURL=enum-options-match-type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enum-options-match-type.js","sourceRoot":"","sources":["../../src/rules/enum-options-match-type.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * All FormSpec ESLint rules.
3
+ */
4
+ export { decoratorFieldTypeMismatch } from "./decorator-field-type-mismatch.js";
5
+ export { enumOptionsMatchType } from "./enum-options-match-type.js";
6
+ export { showwhenFieldExists } from "./showwhen-field-exists.js";
7
+ export { showwhenSuggestsOptional } from "./showwhen-suggests-optional.js";
8
+ export { consistentConstraints } from "./consistent-constraints.js";
9
+ export { noConflictingDecorators } from "./no-conflicting-decorators.js";
10
+ export { noDuplicateDecorators } from "./no-duplicate-decorators.js";
11
+ export { decoratorAllowedFieldTypes } from "./decorator-allowed-field-types.js";
12
+ export { preferCustomDecorator } from "./prefer-custom-decorator.js";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * All FormSpec ESLint rules.
3
+ */
4
+ export { decoratorFieldTypeMismatch } from "./decorator-field-type-mismatch.js";
5
+ export { enumOptionsMatchType } from "./enum-options-match-type.js";
6
+ export { showwhenFieldExists } from "./showwhen-field-exists.js";
7
+ export { showwhenSuggestsOptional } from "./showwhen-suggests-optional.js";
8
+ export { consistentConstraints } from "./consistent-constraints.js";
9
+ export { noConflictingDecorators } from "./no-conflicting-decorators.js";
10
+ export { noDuplicateDecorators } from "./no-duplicate-decorators.js";
11
+ export { decoratorAllowedFieldTypes } from "./decorator-allowed-field-types.js";
12
+ export { preferCustomDecorator } from "./prefer-custom-decorator.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Rule: no-conflicting-decorators
3
+ *
4
+ * Ensures a field doesn't have decorators that imply conflicting types.
5
+ *
6
+ * Invalid:
7
+ * @Minimum(0) // Implies number
8
+ * @MinLength(1) // Implies string
9
+ * field!: string;
10
+ */
11
+ import { ESLintUtils } from "@typescript-eslint/utils";
12
+ export declare const noConflictingDecorators: ESLintUtils.RuleModule<"conflictingDecorators", [], unknown, ESLintUtils.RuleListener> & {
13
+ name: string;
14
+ };
15
+ //# sourceMappingURL=no-conflicting-decorators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-conflicting-decorators.d.ts","sourceRoot":"","sources":["../../src/rules/no-conflicting-decorators.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAavD,eAAO,MAAM,uBAAuB;;CA4DlC,CAAC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Rule: no-conflicting-decorators
3
+ *
4
+ * Ensures a field doesn't have decorators that imply conflicting types.
5
+ *
6
+ * Invalid:
7
+ * @Minimum(0) // Implies number
8
+ * @MinLength(1) // Implies string
9
+ * field!: string;
10
+ */
11
+ import { ESLintUtils } from "@typescript-eslint/utils";
12
+ import { getFormSpecDecorators, DECORATOR_TYPE_HINTS, } from "../utils/decorator-utils.js";
13
+ const createRule = ESLintUtils.RuleCreator((name) => `https://formspec.dev/eslint-plugin/rules/${name}`);
14
+ export const noConflictingDecorators = createRule({
15
+ name: "no-conflicting-decorators",
16
+ meta: {
17
+ type: "problem",
18
+ docs: {
19
+ description: "Ensures a field doesn't have decorators that imply conflicting types",
20
+ },
21
+ messages: {
22
+ conflictingDecorators: "Field has conflicting decorators: @{{decorator1}} implies {{type1}}, but @{{decorator2}} implies {{type2}}",
23
+ },
24
+ schema: [],
25
+ },
26
+ defaultOptions: [],
27
+ create(context) {
28
+ return {
29
+ PropertyDefinition(node) {
30
+ const decorators = getFormSpecDecorators(node);
31
+ if (decorators.length < 2)
32
+ return;
33
+ // Collect type hints from decorators
34
+ const typeHints = [];
35
+ for (const decorator of decorators) {
36
+ // Only process decorators that have type hints
37
+ if (decorator.name in DECORATOR_TYPE_HINTS) {
38
+ const decoratorName = decorator.name;
39
+ const typeHint = DECORATOR_TYPE_HINTS[decoratorName];
40
+ typeHints.push({ decorator: decoratorName, type: typeHint });
41
+ }
42
+ }
43
+ // Check for conflicts
44
+ if (typeHints.length < 2)
45
+ return;
46
+ const firstHint = typeHints[0];
47
+ if (!firstHint)
48
+ return;
49
+ for (let i = 1; i < typeHints.length; i++) {
50
+ const hint = typeHints[i];
51
+ if (!hint)
52
+ continue;
53
+ if (hint.type !== firstHint.type) {
54
+ context.report({
55
+ node: node.key,
56
+ messageId: "conflictingDecorators",
57
+ data: {
58
+ decorator1: firstHint.decorator,
59
+ type1: firstHint.type,
60
+ decorator2: hint.decorator,
61
+ type2: hint.type,
62
+ },
63
+ });
64
+ // Only report once per field
65
+ break;
66
+ }
67
+ }
68
+ },
69
+ };
70
+ },
71
+ });
72
+ //# sourceMappingURL=no-conflicting-decorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-conflicting-decorators.js","sourceRoot":"","sources":["../../src/rules/no-conflicting-decorators.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EACL,qBAAqB,EACrB,oBAAoB,GAErB,MAAM,6BAA6B,CAAC;AAErC,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CAAC,4CAA4C,IAAI,EAAE,CAC7D,CAAC;AAIF,MAAM,CAAC,MAAM,uBAAuB,GAAG,UAAU,CAAiB;IAChE,IAAI,EAAE,2BAA2B;IACjC,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,sEAAsE;SACpF;QACD,QAAQ,EAAE;YACR,qBAAqB,EACnB,4GAA4G;SAC/G;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,kBAAkB,CAAC,IAAI;gBACrB,MAAM,UAAU,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;gBAC/C,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;oBAAE,OAAO;gBAElC,qCAAqC;gBACrC,MAAM,SAAS,GAA0C,EAAE,CAAC;gBAE5D,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;oBACnC,+CAA+C;oBAC/C,IAAI,SAAS,CAAC,IAAI,IAAI,oBAAoB,EAAE,CAAC;wBAC3C,MAAM,aAAa,GAAG,SAAS,CAAC,IAAyB,CAAC;wBAC1D,MAAM,QAAQ,GAAG,oBAAoB,CAAC,aAAa,CAAC,CAAC;wBACrD,SAAS,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;oBAC/D,CAAC;gBACH,CAAC;gBAED,sBAAsB;gBACtB,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;oBAAE,OAAO;gBAEjC,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;gBAC/B,IAAI,CAAC,SAAS;oBAAE,OAAO;gBAEvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC1C,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;oBAC1B,IAAI,CAAC,IAAI;wBAAE,SAAS;oBAEpB,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;wBACjC,OAAO,CAAC,MAAM,CAAC;4BACb,IAAI,EAAE,IAAI,CAAC,GAAG;4BACd,SAAS,EAAE,uBAAuB;4BAClC,IAAI,EAAE;gCACJ,UAAU,EAAE,SAAS,CAAC,SAAS;gCAC/B,KAAK,EAAE,SAAS,CAAC,IAAI;gCACrB,UAAU,EAAE,IAAI,CAAC,SAAS;gCAC1B,KAAK,EAAE,IAAI,CAAC,IAAI;6BACjB;yBACF,CAAC,CAAC;wBACH,6BAA6B;wBAC7B,MAAM;oBACR,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Rule: no-duplicate-decorators
3
+ *
4
+ * Ensures a field doesn't have the same decorator applied multiple times.
5
+ *
6
+ * Invalid:
7
+ * @Field({ displayName: "First" })
8
+ * @Field({ displayName: "Second" }) // Duplicate
9
+ * field!: string;
10
+ *
11
+ * @EnumOptions(["a", "b"])
12
+ * @EnumOptions(["x", "y"]) // Duplicate
13
+ * field!: string;
14
+ */
15
+ import { ESLintUtils } from "@typescript-eslint/utils";
16
+ export declare const noDuplicateDecorators: ESLintUtils.RuleModule<"duplicateDecorator", [], unknown, ESLintUtils.RuleListener> & {
17
+ name: string;
18
+ };
19
+ //# sourceMappingURL=no-duplicate-decorators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-duplicate-decorators.d.ts","sourceRoot":"","sources":["../../src/rules/no-duplicate-decorators.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AASvD,eAAO,MAAM,qBAAqB;;CAyChC,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Rule: no-duplicate-decorators
3
+ *
4
+ * Ensures a field doesn't have the same decorator applied multiple times.
5
+ *
6
+ * Invalid:
7
+ * @Field({ displayName: "First" })
8
+ * @Field({ displayName: "Second" }) // Duplicate
9
+ * field!: string;
10
+ *
11
+ * @EnumOptions(["a", "b"])
12
+ * @EnumOptions(["x", "y"]) // Duplicate
13
+ * field!: string;
14
+ */
15
+ import { ESLintUtils } from "@typescript-eslint/utils";
16
+ import { getFormSpecDecorators, FORMSPEC_DECORATORS } from "../utils/decorator-utils.js";
17
+ const createRule = ESLintUtils.RuleCreator((name) => `https://formspec.dev/eslint-plugin/rules/${name}`);
18
+ export const noDuplicateDecorators = createRule({
19
+ name: "no-duplicate-decorators",
20
+ meta: {
21
+ type: "problem",
22
+ docs: {
23
+ description: "Ensures a field doesn't have the same decorator applied multiple times",
24
+ },
25
+ messages: {
26
+ duplicateDecorator: "Duplicate @{{decorator}} decorator. Each decorator should only appear once per field.",
27
+ },
28
+ schema: [],
29
+ },
30
+ defaultOptions: [],
31
+ create(context) {
32
+ return {
33
+ PropertyDefinition(node) {
34
+ const decorators = getFormSpecDecorators(node);
35
+ if (decorators.length < 2)
36
+ return;
37
+ // Track seen decorators
38
+ const seen = new Map();
39
+ for (const decorator of decorators) {
40
+ if (!FORMSPEC_DECORATORS.has(decorator.name))
41
+ continue;
42
+ if (seen.has(decorator.name)) {
43
+ context.report({
44
+ node: decorator.node,
45
+ messageId: "duplicateDecorator",
46
+ data: {
47
+ decorator: decorator.name,
48
+ },
49
+ });
50
+ }
51
+ else {
52
+ seen.set(decorator.name, true);
53
+ }
54
+ }
55
+ },
56
+ };
57
+ },
58
+ });
59
+ //# sourceMappingURL=no-duplicate-decorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-duplicate-decorators.js","sourceRoot":"","sources":["../../src/rules/no-duplicate-decorators.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAEzF,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CAAC,4CAA4C,IAAI,EAAE,CAC7D,CAAC;AAIF,MAAM,CAAC,MAAM,qBAAqB,GAAG,UAAU,CAAiB;IAC9D,IAAI,EAAE,yBAAyB;IAC/B,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,wEAAwE;SACtF;QACD,QAAQ,EAAE;YACR,kBAAkB,EAChB,uFAAuF;SAC1F;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,kBAAkB,CAAC,IAAI;gBACrB,MAAM,UAAU,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;gBAC/C,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;oBAAE,OAAO;gBAElC,wBAAwB;gBACxB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAmB,CAAC;gBAExC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;oBACnC,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC;wBAAE,SAAS;oBAEvD,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;wBAC7B,OAAO,CAAC,MAAM,CAAC;4BACb,IAAI,EAAE,SAAS,CAAC,IAAI;4BACpB,SAAS,EAAE,oBAAoB;4BAC/B,IAAI,EAAE;gCACJ,SAAS,EAAE,SAAS,CAAC,IAAI;6BAC1B;yBACF,CAAC,CAAC;oBACL,CAAC;yBAAM,CAAC;wBACN,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;oBACjC,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Rule: prefer-custom-decorator
3
+ *
4
+ * When a project has symbols with FormSpecExtendsBrand<'Field'> type,
5
+ * flags direct usage of @Field from @formspec/decorators.
6
+ *
7
+ * TODO: Full implementation requires type-checker integration to detect
8
+ * FormSpecExtendsBrand symbols in scope. Currently implemented as a
9
+ * configurable rule that accepts a list of decorator names that should
10
+ * be preferred over @Field.
11
+ *
12
+ * Config example:
13
+ * "@formspec/prefer-custom-decorator": ["warn", { prefer: { "Field": "MyCustomField" } }]
14
+ */
15
+ import { ESLintUtils } from "@typescript-eslint/utils";
16
+ export interface PreferCustomDecoratorOptions {
17
+ prefer: Record<string, string>;
18
+ }
19
+ export declare const preferCustomDecorator: ESLintUtils.RuleModule<"preferCustomDecorator", [PreferCustomDecoratorOptions], unknown, ESLintUtils.RuleListener> & {
20
+ name: string;
21
+ };
22
+ //# sourceMappingURL=prefer-custom-decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-custom-decorator.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-custom-decorator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AASvD,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,eAAO,MAAM,qBAAqB;;CAwDhC,CAAC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Rule: prefer-custom-decorator
3
+ *
4
+ * When a project has symbols with FormSpecExtendsBrand<'Field'> type,
5
+ * flags direct usage of @Field from @formspec/decorators.
6
+ *
7
+ * TODO: Full implementation requires type-checker integration to detect
8
+ * FormSpecExtendsBrand symbols in scope. Currently implemented as a
9
+ * configurable rule that accepts a list of decorator names that should
10
+ * be preferred over @Field.
11
+ *
12
+ * Config example:
13
+ * "@formspec/prefer-custom-decorator": ["warn", { prefer: { "Field": "MyCustomField" } }]
14
+ */
15
+ import { ESLintUtils } from "@typescript-eslint/utils";
16
+ import { getFormSpecDecorators } from "../utils/decorator-utils.js";
17
+ const createRule = ESLintUtils.RuleCreator((name) => `https://formspec.dev/eslint-plugin/rules/${name}`);
18
+ export const preferCustomDecorator = createRule({
19
+ name: "prefer-custom-decorator",
20
+ meta: {
21
+ type: "suggestion",
22
+ docs: {
23
+ description: "Suggests using a custom decorator instead of a built-in FormSpec decorator when a project-specific alternative exists",
24
+ },
25
+ messages: {
26
+ preferCustomDecorator: "Prefer @{{preferred}} over @{{builtin}}. This project provides a custom decorator that extends the built-in.",
27
+ },
28
+ schema: [
29
+ {
30
+ type: "object",
31
+ properties: {
32
+ prefer: {
33
+ type: "object",
34
+ additionalProperties: { type: "string" },
35
+ },
36
+ },
37
+ required: ["prefer"],
38
+ additionalProperties: false,
39
+ },
40
+ ],
41
+ },
42
+ defaultOptions: [{ prefer: {} }],
43
+ create(context, [options]) {
44
+ const preferMap = options.prefer;
45
+ const builtinNames = new Set(Object.keys(preferMap));
46
+ if (builtinNames.size === 0)
47
+ return {};
48
+ return {
49
+ PropertyDefinition(node) {
50
+ const decorators = getFormSpecDecorators(node);
51
+ if (decorators.length === 0)
52
+ return;
53
+ for (const decorator of decorators) {
54
+ if (builtinNames.has(decorator.name)) {
55
+ const preferred = preferMap[decorator.name];
56
+ if (!preferred)
57
+ continue;
58
+ context.report({
59
+ node: decorator.node,
60
+ messageId: "preferCustomDecorator",
61
+ data: {
62
+ builtin: decorator.name,
63
+ preferred,
64
+ },
65
+ });
66
+ }
67
+ }
68
+ },
69
+ };
70
+ },
71
+ });
72
+ //# sourceMappingURL=prefer-custom-decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-custom-decorator.js","sourceRoot":"","sources":["../../src/rules/prefer-custom-decorator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAEpE,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CAAC,4CAA4C,IAAI,EAAE,CAC7D,CAAC;AAQF,MAAM,CAAC,MAAM,qBAAqB,GAAG,UAAU,CAA6C;IAC1F,IAAI,EAAE,yBAAyB;IAC/B,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EACT,uHAAuH;SAC1H;QACD,QAAQ,EAAE;YACR,qBAAqB,EACnB,8GAA8G;SACjH;QACD,MAAM,EAAE;YACN;gBACE,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,MAAM,EAAE;wBACN,IAAI,EAAE,QAAQ;wBACd,oBAAoB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;qBACzC;iBACF;gBACD,QAAQ,EAAE,CAAC,QAAQ,CAAC;gBACpB,oBAAoB,EAAE,KAAK;aAC5B;SACF;KACF;IACD,cAAc,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAChC,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC;QACvB,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;QACjC,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QAErD,IAAI,YAAY,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAEvC,OAAO;YACL,kBAAkB,CAAC,IAAI;gBACrB,MAAM,UAAU,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;gBAC/C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;oBAAE,OAAO;gBAEpC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;oBACnC,IAAI,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;wBACrC,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;wBAC5C,IAAI,CAAC,SAAS;4BAAE,SAAS;wBAEzB,OAAO,CAAC,MAAM,CAAC;4BACb,IAAI,EAAE,SAAS,CAAC,IAAI;4BACpB,SAAS,EAAE,uBAAuB;4BAClC,IAAI,EAAE;gCACJ,OAAO,EAAE,SAAS,CAAC,IAAI;gCACvB,SAAS;6BACV;yBACF,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Rule: showwhen-field-exists
3
+ *
4
+ * Ensures @ShowWhen references a field that exists in the same class.
5
+ *
6
+ * Valid:
7
+ * @EnumOptions(["a", "b"])
8
+ * type!: "a" | "b";
9
+ *
10
+ * @ShowWhen({ _predicate: "equals", field: "type", value: "a" })
11
+ * conditionalField?: string;
12
+ *
13
+ * Invalid:
14
+ * @ShowWhen({ _predicate: "equals", field: "nonexistent", value: "x" })
15
+ * field?: string; // "nonexistent" doesn't exist
16
+ */
17
+ import { ESLintUtils } from "@typescript-eslint/utils";
18
+ export declare const showwhenFieldExists: ESLintUtils.RuleModule<"fieldDoesNotExist", [], unknown, ESLintUtils.RuleListener> & {
19
+ name: string;
20
+ };
21
+ //# sourceMappingURL=showwhen-field-exists.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"showwhen-field-exists.d.ts","sourceRoot":"","sources":["../../src/rules/showwhen-field-exists.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,WAAW,EAAkB,MAAM,0BAA0B,CAAC;AAavE,eAAO,MAAM,mBAAmB;;CAoD9B,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Rule: showwhen-field-exists
3
+ *
4
+ * Ensures @ShowWhen references a field that exists in the same class.
5
+ *
6
+ * Valid:
7
+ * @EnumOptions(["a", "b"])
8
+ * type!: "a" | "b";
9
+ *
10
+ * @ShowWhen({ _predicate: "equals", field: "type", value: "a" })
11
+ * conditionalField?: string;
12
+ *
13
+ * Invalid:
14
+ * @ShowWhen({ _predicate: "equals", field: "nonexistent", value: "x" })
15
+ * field?: string; // "nonexistent" doesn't exist
16
+ */
17
+ import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
18
+ import { findDecorator, getShowWhenField, getClassPropertyNames, } from "../utils/decorator-utils.js";
19
+ const createRule = ESLintUtils.RuleCreator((name) => `https://formspec.dev/eslint-plugin/rules/${name}`);
20
+ export const showwhenFieldExists = createRule({
21
+ name: "showwhen-field-exists",
22
+ meta: {
23
+ type: "problem",
24
+ docs: {
25
+ description: "Ensures @ShowWhen references a field that exists in the same class",
26
+ },
27
+ messages: {
28
+ fieldDoesNotExist: "@ShowWhen references field '{{referencedField}}' which does not exist in this class",
29
+ },
30
+ schema: [],
31
+ },
32
+ defaultOptions: [],
33
+ create(context) {
34
+ return {
35
+ PropertyDefinition(node) {
36
+ const showWhenDecorator = findDecorator(node, "ShowWhen");
37
+ if (!showWhenDecorator)
38
+ return;
39
+ const referencedField = getShowWhenField(showWhenDecorator);
40
+ if (!referencedField)
41
+ return;
42
+ // Find the parent class
43
+ // PropertyDefinition -> ClassBody -> ClassDeclaration/ClassExpression
44
+ // TypeScript guarantees node.parent is ClassBody for PropertyDefinition
45
+ const classBody = node.parent;
46
+ const classNode = classBody.parent;
47
+ if (classNode.type !== AST_NODE_TYPES.ClassDeclaration &&
48
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive check
49
+ classNode.type !== AST_NODE_TYPES.ClassExpression) {
50
+ return;
51
+ }
52
+ // Get all property names in the class
53
+ const propertyNames = getClassPropertyNames(classNode);
54
+ // Check if the referenced field exists
55
+ if (!propertyNames.has(referencedField)) {
56
+ context.report({
57
+ node: showWhenDecorator.node,
58
+ messageId: "fieldDoesNotExist",
59
+ data: {
60
+ referencedField,
61
+ },
62
+ });
63
+ }
64
+ },
65
+ };
66
+ },
67
+ });
68
+ //# sourceMappingURL=showwhen-field-exists.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"showwhen-field-exists.js","sourceRoot":"","sources":["../../src/rules/showwhen-field-exists.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,6BAA6B,CAAC;AAErC,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CAAC,4CAA4C,IAAI,EAAE,CAC7D,CAAC;AAIF,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAiB;IAC5D,IAAI,EAAE,uBAAuB;IAC7B,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,oEAAoE;SAClF;QACD,QAAQ,EAAE;YACR,iBAAiB,EACf,qFAAqF;SACxF;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,kBAAkB,CAAC,IAAI;gBACrB,MAAM,iBAAiB,GAAG,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;gBAC1D,IAAI,CAAC,iBAAiB;oBAAE,OAAO;gBAE/B,MAAM,eAAe,GAAG,gBAAgB,CAAC,iBAAiB,CAAC,CAAC;gBAC5D,IAAI,CAAC,eAAe;oBAAE,OAAO;gBAE7B,wBAAwB;gBACxB,sEAAsE;gBACtE,wEAAwE;gBACxE,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC;gBAC9B,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC;gBACnC,IACE,SAAS,CAAC,IAAI,KAAK,cAAc,CAAC,gBAAgB;oBAClD,0FAA0F;oBAC1F,SAAS,CAAC,IAAI,KAAK,cAAc,CAAC,eAAe,EACjD,CAAC;oBACD,OAAO;gBACT,CAAC;gBAED,sCAAsC;gBACtC,MAAM,aAAa,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;gBAEvD,uCAAuC;gBACvC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC;oBACxC,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI,EAAE,iBAAiB,CAAC,IAAI;wBAC5B,SAAS,EAAE,mBAAmB;wBAC9B,IAAI,EAAE;4BACJ,eAAe;yBAChB;qBACF,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Rule: showwhen-suggests-optional
3
+ *
4
+ * Suggests that fields with @ShowWhen should be marked as optional (`?`)
5
+ * since they may not be present in the output when the condition is false.
6
+ *
7
+ * Valid:
8
+ * @ShowWhen({ _predicate: "equals", field: "type", value: "a" })
9
+ * conditionalField?: string; // Optional - good
10
+ *
11
+ * Warning:
12
+ * @ShowWhen({ _predicate: "equals", field: "type", value: "a" })
13
+ * conditionalField!: string; // Not optional - may cause issues
14
+ */
15
+ import { ESLintUtils } from "@typescript-eslint/utils";
16
+ export declare const showwhenSuggestsOptional: ESLintUtils.RuleModule<"shouldBeOptional", [], unknown, ESLintUtils.RuleListener> & {
17
+ name: string;
18
+ };
19
+ //# sourceMappingURL=showwhen-suggests-optional.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"showwhen-suggests-optional.d.ts","sourceRoot":"","sources":["../../src/rules/showwhen-suggests-optional.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAkB,MAAM,0BAA0B,CAAC;AAUvE,eAAO,MAAM,wBAAwB;;CAoCnC,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Rule: showwhen-suggests-optional
3
+ *
4
+ * Suggests that fields with @ShowWhen should be marked as optional (`?`)
5
+ * since they may not be present in the output when the condition is false.
6
+ *
7
+ * Valid:
8
+ * @ShowWhen({ _predicate: "equals", field: "type", value: "a" })
9
+ * conditionalField?: string; // Optional - good
10
+ *
11
+ * Warning:
12
+ * @ShowWhen({ _predicate: "equals", field: "type", value: "a" })
13
+ * conditionalField!: string; // Not optional - may cause issues
14
+ */
15
+ import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
16
+ import { findDecorator } from "../utils/decorator-utils.js";
17
+ import { isOptionalProperty } from "../utils/type-utils.js";
18
+ const createRule = ESLintUtils.RuleCreator((name) => `https://formspec.dev/eslint-plugin/rules/${name}`);
19
+ export const showwhenSuggestsOptional = createRule({
20
+ name: "showwhen-suggests-optional",
21
+ meta: {
22
+ type: "suggestion",
23
+ docs: {
24
+ description: "Suggests that fields with @ShowWhen should be marked as optional",
25
+ },
26
+ messages: {
27
+ shouldBeOptional: "Field '{{field}}' uses @ShowWhen but is not optional. Consider adding '?' since the field may not be in the output when the condition is false.",
28
+ },
29
+ schema: [],
30
+ },
31
+ defaultOptions: [],
32
+ create(context) {
33
+ return {
34
+ PropertyDefinition(node) {
35
+ const showWhenDecorator = findDecorator(node, "ShowWhen");
36
+ if (!showWhenDecorator)
37
+ return;
38
+ // Check if the field is already optional
39
+ if (isOptionalProperty(node))
40
+ return;
41
+ const fieldName = node.key.type === AST_NODE_TYPES.Identifier ? node.key.name : "<computed>";
42
+ context.report({
43
+ node: node.key,
44
+ messageId: "shouldBeOptional",
45
+ data: {
46
+ field: fieldName,
47
+ },
48
+ });
49
+ },
50
+ };
51
+ },
52
+ });
53
+ //# sourceMappingURL=showwhen-suggests-optional.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"showwhen-suggests-optional.js","sourceRoot":"","sources":["../../src/rules/showwhen-suggests-optional.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CAAC,4CAA4C,IAAI,EAAE,CAC7D,CAAC;AAIF,MAAM,CAAC,MAAM,wBAAwB,GAAG,UAAU,CAAiB;IACjE,IAAI,EAAE,4BAA4B;IAClC,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EAAE,kEAAkE;SAChF;QACD,QAAQ,EAAE;YACR,gBAAgB,EACd,iJAAiJ;SACpJ;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,kBAAkB,CAAC,IAAI;gBACrB,MAAM,iBAAiB,GAAG,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;gBAC1D,IAAI,CAAC,iBAAiB;oBAAE,OAAO;gBAE/B,yCAAyC;gBACzC,IAAI,kBAAkB,CAAC,IAAI,CAAC;oBAAE,OAAO;gBAErC,MAAM,SAAS,GACb,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC;gBAE7E,OAAO,CAAC,MAAM,CAAC;oBACb,IAAI,EAAE,IAAI,CAAC,GAAG;oBACd,SAAS,EAAE,kBAAkB;oBAC7B,IAAI,EAAE;wBACJ,KAAK,EAAE,SAAS;qBACjB;iBACF,CAAC,CAAC;YACL,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}