@formspec/eslint-plugin 0.1.0-alpha.3
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 +217 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/decorator-field-type-mismatch.d.ts +15 -0
- package/dist/rules/decorator-field-type-mismatch.d.ts.map +1 -0
- package/dist/rules/decorator-field-type-mismatch.js +87 -0
- package/dist/rules/decorator-field-type-mismatch.js.map +1 -0
- package/dist/rules/enum-options-match-type.d.ts +26 -0
- package/dist/rules/enum-options-match-type.d.ts.map +1 -0
- package/dist/rules/enum-options-match-type.js +115 -0
- package/dist/rules/enum-options-match-type.js.map +1 -0
- package/dist/rules/index.d.ts +11 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +11 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/min-max-valid-range.d.ts +26 -0
- package/dist/rules/min-max-valid-range.d.ts.map +1 -0
- package/dist/rules/min-max-valid-range.js +82 -0
- package/dist/rules/min-max-valid-range.js.map +1 -0
- package/dist/rules/no-conflicting-decorators.d.ts +19 -0
- package/dist/rules/no-conflicting-decorators.d.ts.map +1 -0
- package/dist/rules/no-conflicting-decorators.js +76 -0
- package/dist/rules/no-conflicting-decorators.js.map +1 -0
- package/dist/rules/no-duplicate-decorators.d.ts +19 -0
- package/dist/rules/no-duplicate-decorators.d.ts.map +1 -0
- package/dist/rules/no-duplicate-decorators.js +74 -0
- package/dist/rules/no-duplicate-decorators.js.map +1 -0
- package/dist/rules/showwhen-field-exists.d.ts +21 -0
- package/dist/rules/showwhen-field-exists.d.ts.map +1 -0
- package/dist/rules/showwhen-field-exists.js +68 -0
- package/dist/rules/showwhen-field-exists.js.map +1 -0
- package/dist/rules/showwhen-suggests-optional.d.ts +19 -0
- package/dist/rules/showwhen-suggests-optional.d.ts.map +1 -0
- package/dist/rules/showwhen-suggests-optional.js +53 -0
- package/dist/rules/showwhen-suggests-optional.js.map +1 -0
- package/dist/utils/decorator-utils.d.ts +103 -0
- package/dist/utils/decorator-utils.d.ts.map +1 -0
- package/dist/utils/decorator-utils.js +222 -0
- package/dist/utils/decorator-utils.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/type-utils.d.ts +82 -0
- package/dist/utils/type-utils.d.ts.map +1 -0
- package/dist/utils/type-utils.js +192 -0
- package/dist/utils/type-utils.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,11 @@
|
|
|
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 { minMaxValidRange } from "./min-max-valid-range.js";
|
|
9
|
+
export { noConflictingDecorators } from "./no-conflicting-decorators.js";
|
|
10
|
+
export { noDuplicateDecorators } from "./no-duplicate-decorators.js";
|
|
11
|
+
//# 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,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: min-max-valid-range
|
|
3
|
+
*
|
|
4
|
+
* Ensures @Min value is less than or equal to @Max value when both are present.
|
|
5
|
+
*
|
|
6
|
+
* Valid:
|
|
7
|
+
* @Min(0)
|
|
8
|
+
* @Max(100)
|
|
9
|
+
* value!: number;
|
|
10
|
+
*
|
|
11
|
+
* @Min(5)
|
|
12
|
+
* @Max(5) // Equal is OK
|
|
13
|
+
* value!: number;
|
|
14
|
+
*
|
|
15
|
+
* Invalid:
|
|
16
|
+
* @Min(100)
|
|
17
|
+
* @Max(50) // Min > Max
|
|
18
|
+
* value!: number;
|
|
19
|
+
*/
|
|
20
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
21
|
+
type MessageIds = "minGreaterThanMax" | "minItemsGreaterThanMaxItems";
|
|
22
|
+
export declare const minMaxValidRange: ESLintUtils.RuleModule<MessageIds, [], unknown, ESLintUtils.RuleListener> & {
|
|
23
|
+
name: string;
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=min-max-valid-range.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"min-max-valid-range.d.ts","sourceRoot":"","sources":["../../src/rules/min-max-valid-range.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAOvD,KAAK,UAAU,GAAG,mBAAmB,GAAG,6BAA6B,CAAC;AAEtE,eAAO,MAAM,gBAAgB;;CAsE3B,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: min-max-valid-range
|
|
3
|
+
*
|
|
4
|
+
* Ensures @Min value is less than or equal to @Max value when both are present.
|
|
5
|
+
*
|
|
6
|
+
* Valid:
|
|
7
|
+
* @Min(0)
|
|
8
|
+
* @Max(100)
|
|
9
|
+
* value!: number;
|
|
10
|
+
*
|
|
11
|
+
* @Min(5)
|
|
12
|
+
* @Max(5) // Equal is OK
|
|
13
|
+
* value!: number;
|
|
14
|
+
*
|
|
15
|
+
* Invalid:
|
|
16
|
+
* @Min(100)
|
|
17
|
+
* @Max(50) // Min > Max
|
|
18
|
+
* value!: number;
|
|
19
|
+
*/
|
|
20
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
21
|
+
import { findDecorator, getDecoratorLiteralArg } from "../utils/decorator-utils.js";
|
|
22
|
+
const createRule = ESLintUtils.RuleCreator((name) => `https://formspec.dev/eslint-plugin/rules/${name}`);
|
|
23
|
+
export const minMaxValidRange = createRule({
|
|
24
|
+
name: "min-max-valid-range",
|
|
25
|
+
meta: {
|
|
26
|
+
type: "problem",
|
|
27
|
+
docs: {
|
|
28
|
+
description: "Ensures @Min/@Max and @MinItems/@MaxItems have valid ranges",
|
|
29
|
+
},
|
|
30
|
+
messages: {
|
|
31
|
+
minGreaterThanMax: "@Min({{min}}) is greater than @Max({{max}}). Min must be less than or equal to Max.",
|
|
32
|
+
minItemsGreaterThanMaxItems: "@MinItems({{min}}) is greater than @MaxItems({{max}}). MinItems must be less than or equal to MaxItems.",
|
|
33
|
+
},
|
|
34
|
+
schema: [],
|
|
35
|
+
},
|
|
36
|
+
defaultOptions: [],
|
|
37
|
+
create(context) {
|
|
38
|
+
return {
|
|
39
|
+
PropertyDefinition(node) {
|
|
40
|
+
// Check @Min/@Max
|
|
41
|
+
const minDecorator = findDecorator(node, "Min");
|
|
42
|
+
const maxDecorator = findDecorator(node, "Max");
|
|
43
|
+
if (minDecorator && maxDecorator) {
|
|
44
|
+
const minValue = getDecoratorLiteralArg(minDecorator);
|
|
45
|
+
const maxValue = getDecoratorLiteralArg(maxDecorator);
|
|
46
|
+
if (typeof minValue === "number" &&
|
|
47
|
+
typeof maxValue === "number" &&
|
|
48
|
+
minValue > maxValue) {
|
|
49
|
+
context.report({
|
|
50
|
+
node: minDecorator.node,
|
|
51
|
+
messageId: "minGreaterThanMax",
|
|
52
|
+
data: {
|
|
53
|
+
min: String(minValue),
|
|
54
|
+
max: String(maxValue),
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Check @MinItems/@MaxItems
|
|
60
|
+
const minItemsDecorator = findDecorator(node, "MinItems");
|
|
61
|
+
const maxItemsDecorator = findDecorator(node, "MaxItems");
|
|
62
|
+
if (minItemsDecorator && maxItemsDecorator) {
|
|
63
|
+
const minValue = getDecoratorLiteralArg(minItemsDecorator);
|
|
64
|
+
const maxValue = getDecoratorLiteralArg(maxItemsDecorator);
|
|
65
|
+
if (typeof minValue === "number" &&
|
|
66
|
+
typeof maxValue === "number" &&
|
|
67
|
+
minValue > maxValue) {
|
|
68
|
+
context.report({
|
|
69
|
+
node: minItemsDecorator.node,
|
|
70
|
+
messageId: "minItemsGreaterThanMaxItems",
|
|
71
|
+
data: {
|
|
72
|
+
min: String(minValue),
|
|
73
|
+
max: String(maxValue),
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
//# sourceMappingURL=min-max-valid-range.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"min-max-valid-range.js","sourceRoot":"","sources":["../../src/rules/min-max-valid-range.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAEpF,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CAAC,4CAA4C,IAAI,EAAE,CAC7D,CAAC;AAIF,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAiB;IACzD,IAAI,EAAE,qBAAqB;IAC3B,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EACT,6DAA6D;SAChE;QACD,QAAQ,EAAE;YACR,iBAAiB,EACf,qFAAqF;YACvF,2BAA2B,EACzB,yGAAyG;SAC5G;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,kBAAkB,CAAC,IAAI;gBACrB,kBAAkB;gBAClB,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;gBAChD,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;gBAEhD,IAAI,YAAY,IAAI,YAAY,EAAE,CAAC;oBACjC,MAAM,QAAQ,GAAG,sBAAsB,CAAC,YAAY,CAAC,CAAC;oBACtD,MAAM,QAAQ,GAAG,sBAAsB,CAAC,YAAY,CAAC,CAAC;oBAEtD,IACE,OAAO,QAAQ,KAAK,QAAQ;wBAC5B,OAAO,QAAQ,KAAK,QAAQ;wBAC5B,QAAQ,GAAG,QAAQ,EACnB,CAAC;wBACD,OAAO,CAAC,MAAM,CAAC;4BACb,IAAI,EAAE,YAAY,CAAC,IAAI;4BACvB,SAAS,EAAE,mBAAmB;4BAC9B,IAAI,EAAE;gCACJ,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC;gCACrB,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC;6BACtB;yBACF,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;gBAED,4BAA4B;gBAC5B,MAAM,iBAAiB,GAAG,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;gBAC1D,MAAM,iBAAiB,GAAG,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;gBAE1D,IAAI,iBAAiB,IAAI,iBAAiB,EAAE,CAAC;oBAC3C,MAAM,QAAQ,GAAG,sBAAsB,CAAC,iBAAiB,CAAC,CAAC;oBAC3D,MAAM,QAAQ,GAAG,sBAAsB,CAAC,iBAAiB,CAAC,CAAC;oBAE3D,IACE,OAAO,QAAQ,KAAK,QAAQ;wBAC5B,OAAO,QAAQ,KAAK,QAAQ;wBAC5B,QAAQ,GAAG,QAAQ,EACnB,CAAC;wBACD,OAAO,CAAC,MAAM,CAAC;4BACb,IAAI,EAAE,iBAAiB,CAAC,IAAI;4BAC5B,SAAS,EAAE,6BAA6B;4BACxC,IAAI,EAAE;gCACJ,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC;gCACrB,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC;6BACtB;yBACF,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: no-conflicting-decorators
|
|
3
|
+
*
|
|
4
|
+
* Ensures a field doesn't have decorators that imply conflicting types.
|
|
5
|
+
*
|
|
6
|
+
* Invalid:
|
|
7
|
+
* @Min(0) // Implies number
|
|
8
|
+
* @Placeholder("x") // Implies string
|
|
9
|
+
* field!: string;
|
|
10
|
+
*
|
|
11
|
+
* @MinItems(1) // Implies array
|
|
12
|
+
* @Min(0) // Implies number
|
|
13
|
+
* field!: number[];
|
|
14
|
+
*/
|
|
15
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
16
|
+
export declare const noConflictingDecorators: ESLintUtils.RuleModule<"conflictingDecorators", [], unknown, ESLintUtils.RuleListener> & {
|
|
17
|
+
name: string;
|
|
18
|
+
};
|
|
19
|
+
//# 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;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAavD,eAAO,MAAM,uBAAuB;;CA6DlC,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: no-conflicting-decorators
|
|
3
|
+
*
|
|
4
|
+
* Ensures a field doesn't have decorators that imply conflicting types.
|
|
5
|
+
*
|
|
6
|
+
* Invalid:
|
|
7
|
+
* @Min(0) // Implies number
|
|
8
|
+
* @Placeholder("x") // Implies string
|
|
9
|
+
* field!: string;
|
|
10
|
+
*
|
|
11
|
+
* @MinItems(1) // Implies array
|
|
12
|
+
* @Min(0) // Implies number
|
|
13
|
+
* field!: number[];
|
|
14
|
+
*/
|
|
15
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
16
|
+
import { getFormSpecDecorators, DECORATOR_TYPE_HINTS, } from "../utils/decorator-utils.js";
|
|
17
|
+
const createRule = ESLintUtils.RuleCreator((name) => `https://formspec.dev/eslint-plugin/rules/${name}`);
|
|
18
|
+
export const noConflictingDecorators = createRule({
|
|
19
|
+
name: "no-conflicting-decorators",
|
|
20
|
+
meta: {
|
|
21
|
+
type: "problem",
|
|
22
|
+
docs: {
|
|
23
|
+
description: "Ensures a field doesn't have decorators that imply conflicting types",
|
|
24
|
+
},
|
|
25
|
+
messages: {
|
|
26
|
+
conflictingDecorators: "Field has conflicting decorators: @{{decorator1}} implies {{type1}}, but @{{decorator2}} implies {{type2}}",
|
|
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
|
+
// Collect type hints from decorators
|
|
38
|
+
const typeHints = [];
|
|
39
|
+
for (const decorator of decorators) {
|
|
40
|
+
// Only process decorators that have type hints
|
|
41
|
+
if (decorator.name in DECORATOR_TYPE_HINTS) {
|
|
42
|
+
const decoratorName = decorator.name;
|
|
43
|
+
const typeHint = DECORATOR_TYPE_HINTS[decoratorName];
|
|
44
|
+
typeHints.push({ decorator: decoratorName, type: typeHint });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Check for conflicts
|
|
48
|
+
if (typeHints.length < 2)
|
|
49
|
+
return;
|
|
50
|
+
const firstHint = typeHints[0];
|
|
51
|
+
if (!firstHint)
|
|
52
|
+
return;
|
|
53
|
+
for (let i = 1; i < typeHints.length; i++) {
|
|
54
|
+
const hint = typeHints[i];
|
|
55
|
+
if (!hint)
|
|
56
|
+
continue;
|
|
57
|
+
if (hint.type !== firstHint.type) {
|
|
58
|
+
context.report({
|
|
59
|
+
node: node.key,
|
|
60
|
+
messageId: "conflictingDecorators",
|
|
61
|
+
data: {
|
|
62
|
+
decorator1: firstHint.decorator,
|
|
63
|
+
type1: firstHint.type,
|
|
64
|
+
decorator2: hint.decorator,
|
|
65
|
+
type2: hint.type,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
// Only report once per field
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
//# 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;;;;;;;;;;;;;GAaG;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,EACT,sEAAsE;SACzE;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
|
+
* @Label("First")
|
|
8
|
+
* @Label("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;AAyBvD,eAAO,MAAM,qBAAqB;;CA0ChC,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: no-duplicate-decorators
|
|
3
|
+
*
|
|
4
|
+
* Ensures a field doesn't have the same decorator applied multiple times.
|
|
5
|
+
*
|
|
6
|
+
* Invalid:
|
|
7
|
+
* @Label("First")
|
|
8
|
+
* @Label("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 } from "../utils/decorator-utils.js";
|
|
17
|
+
const createRule = ESLintUtils.RuleCreator((name) => `https://formspec.dev/eslint-plugin/rules/${name}`);
|
|
18
|
+
/**
|
|
19
|
+
* Decorators that should only appear once per field.
|
|
20
|
+
*/
|
|
21
|
+
const SINGLE_USE_DECORATORS = new Set([
|
|
22
|
+
"Label",
|
|
23
|
+
"Placeholder",
|
|
24
|
+
"Optional",
|
|
25
|
+
"Min",
|
|
26
|
+
"Max",
|
|
27
|
+
"MinItems",
|
|
28
|
+
"MaxItems",
|
|
29
|
+
"EnumOptions",
|
|
30
|
+
"Group",
|
|
31
|
+
"ShowWhen",
|
|
32
|
+
]);
|
|
33
|
+
export const noDuplicateDecorators = createRule({
|
|
34
|
+
name: "no-duplicate-decorators",
|
|
35
|
+
meta: {
|
|
36
|
+
type: "problem",
|
|
37
|
+
docs: {
|
|
38
|
+
description: "Ensures a field doesn't have the same decorator applied multiple times",
|
|
39
|
+
},
|
|
40
|
+
messages: {
|
|
41
|
+
duplicateDecorator: "Duplicate @{{decorator}} decorator. Each decorator should only appear once per field.",
|
|
42
|
+
},
|
|
43
|
+
schema: [],
|
|
44
|
+
},
|
|
45
|
+
defaultOptions: [],
|
|
46
|
+
create(context) {
|
|
47
|
+
return {
|
|
48
|
+
PropertyDefinition(node) {
|
|
49
|
+
const decorators = getFormSpecDecorators(node);
|
|
50
|
+
if (decorators.length < 2)
|
|
51
|
+
return;
|
|
52
|
+
// Track seen decorators
|
|
53
|
+
const seen = new Map();
|
|
54
|
+
for (const decorator of decorators) {
|
|
55
|
+
if (!SINGLE_USE_DECORATORS.has(decorator.name))
|
|
56
|
+
continue;
|
|
57
|
+
if (seen.has(decorator.name)) {
|
|
58
|
+
context.report({
|
|
59
|
+
node: decorator.node,
|
|
60
|
+
messageId: "duplicateDecorator",
|
|
61
|
+
data: {
|
|
62
|
+
decorator: decorator.name,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
seen.set(decorator.name, true);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
//# 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,MAAM,6BAA6B,CAAC;AAEpE,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CAAC,4CAA4C,IAAI,EAAE,CAC7D,CAAC;AAIF;;GAEG;AACH,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC;IACpC,OAAO;IACP,aAAa;IACb,UAAU;IACV,KAAK;IACL,KAAK;IACL,UAAU;IACV,UAAU;IACV,aAAa;IACb,OAAO;IACP,UAAU;CACX,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG,UAAU,CAAiB;IAC9D,IAAI,EAAE,yBAAyB;IAC/B,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EACT,wEAAwE;SAC3E;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,qBAAqB,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC;wBAAE,SAAS;oBAEzD,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,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;;CAqD9B,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,EACT,oEAAoE;SACvE;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;;CAqCnC,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,EACT,kEAAkE;SACrE;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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for working with decorator AST nodes.
|
|
3
|
+
*/
|
|
4
|
+
import { TSESTree } from "@typescript-eslint/utils";
|
|
5
|
+
/**
|
|
6
|
+
* Information extracted from a decorator.
|
|
7
|
+
*/
|
|
8
|
+
export interface DecoratorInfo {
|
|
9
|
+
/** The decorator name (e.g., "Min", "Label", "EnumOptions") */
|
|
10
|
+
name: string;
|
|
11
|
+
/** The arguments passed to the decorator */
|
|
12
|
+
args: TSESTree.Expression[];
|
|
13
|
+
/** The original decorator node */
|
|
14
|
+
node: TSESTree.Decorator;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* FormSpec decorator names that imply specific field types.
|
|
18
|
+
*/
|
|
19
|
+
export declare const DECORATOR_TYPE_HINTS: {
|
|
20
|
+
readonly Min: "number";
|
|
21
|
+
readonly Max: "number";
|
|
22
|
+
readonly Placeholder: "string";
|
|
23
|
+
readonly MinItems: "array";
|
|
24
|
+
readonly MaxItems: "array";
|
|
25
|
+
readonly EnumOptions: "enum";
|
|
26
|
+
};
|
|
27
|
+
export type TypeHintDecorator = keyof typeof DECORATOR_TYPE_HINTS;
|
|
28
|
+
/**
|
|
29
|
+
* All known FormSpec decorator names.
|
|
30
|
+
*/
|
|
31
|
+
export declare const FORMSPEC_DECORATORS: Set<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Extracts decorator information from a Decorator AST node.
|
|
34
|
+
*
|
|
35
|
+
* Handles both:
|
|
36
|
+
* - `@DecoratorName` (identifier)
|
|
37
|
+
* - `@DecoratorName(args)` (call expression)
|
|
38
|
+
*
|
|
39
|
+
* @param decorator - The decorator AST node
|
|
40
|
+
* @returns Decorator info or null if not a recognized pattern
|
|
41
|
+
*/
|
|
42
|
+
export declare function getDecoratorInfo(decorator: TSESTree.Decorator): DecoratorInfo | null;
|
|
43
|
+
/**
|
|
44
|
+
* Finds all decorators on a property definition.
|
|
45
|
+
*
|
|
46
|
+
* @param node - The property definition node
|
|
47
|
+
* @returns Array of decorator info objects for FormSpec decorators
|
|
48
|
+
*/
|
|
49
|
+
export declare function getFormSpecDecorators(node: TSESTree.PropertyDefinition): DecoratorInfo[];
|
|
50
|
+
/**
|
|
51
|
+
* Finds a specific decorator by name on a property.
|
|
52
|
+
*
|
|
53
|
+
* @param node - The property definition node
|
|
54
|
+
* @param name - The decorator name to find
|
|
55
|
+
* @returns The decorator info or null if not found
|
|
56
|
+
*/
|
|
57
|
+
export declare function findDecorator(node: TSESTree.PropertyDefinition, name: string): DecoratorInfo | null;
|
|
58
|
+
/**
|
|
59
|
+
* Checks if a property has a specific decorator.
|
|
60
|
+
*
|
|
61
|
+
* @param node - The property definition node
|
|
62
|
+
* @param name - The decorator name to check
|
|
63
|
+
* @returns True if the decorator is present
|
|
64
|
+
*/
|
|
65
|
+
export declare function hasDecorator(node: TSESTree.PropertyDefinition, name: string): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Gets the first argument of a decorator as a literal value.
|
|
68
|
+
*
|
|
69
|
+
* @param decorator - The decorator info
|
|
70
|
+
* @returns The literal value or null if not a literal
|
|
71
|
+
*/
|
|
72
|
+
export declare function getDecoratorLiteralArg(decorator: DecoratorInfo): unknown;
|
|
73
|
+
/**
|
|
74
|
+
* Gets the first argument of a decorator as an array of values.
|
|
75
|
+
* Used for @EnumOptions(["a", "b", "c"]).
|
|
76
|
+
*
|
|
77
|
+
* @param decorator - The decorator info
|
|
78
|
+
* @returns Array of values or null if not an array expression
|
|
79
|
+
*/
|
|
80
|
+
export declare function getDecoratorArrayArg(decorator: DecoratorInfo): unknown[] | null;
|
|
81
|
+
/**
|
|
82
|
+
* Gets the field reference from a @ShowWhen predicate.
|
|
83
|
+
* @ShowWhen({ _predicate: "equals", field: "foo", value: "bar" })
|
|
84
|
+
*
|
|
85
|
+
* @param decorator - The ShowWhen decorator info
|
|
86
|
+
* @returns The field name or null if not found
|
|
87
|
+
*/
|
|
88
|
+
export declare function getShowWhenField(decorator: DecoratorInfo): string | null;
|
|
89
|
+
/**
|
|
90
|
+
* Gets the property name from a PropertyDefinition.
|
|
91
|
+
*
|
|
92
|
+
* @param node - The property definition node
|
|
93
|
+
* @returns The property name or null if computed/symbol
|
|
94
|
+
*/
|
|
95
|
+
export declare function getPropertyName(node: TSESTree.PropertyDefinition): string | null;
|
|
96
|
+
/**
|
|
97
|
+
* Gets all property names from a class definition.
|
|
98
|
+
*
|
|
99
|
+
* @param classNode - The class declaration/expression node
|
|
100
|
+
* @returns Set of property names
|
|
101
|
+
*/
|
|
102
|
+
export declare function getClassPropertyNames(classNode: TSESTree.ClassDeclaration | TSESTree.ClassExpression): Set<string>;
|
|
103
|
+
//# sourceMappingURL=decorator-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorator-utils.d.ts","sourceRoot":"","sources":["../../src/utils/decorator-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAkB,MAAM,0BAA0B,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,IAAI,EAAE,QAAQ,CAAC,UAAU,EAAE,CAAC;IAC5B,kCAAkC;IAClC,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC;CAC1B;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;CAWvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,oBAAoB,CAAC;AAElE;;GAEG;AACH,eAAO,MAAM,mBAAmB,aAY9B,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,GAAG,aAAa,GAAG,IAAI,CAyBpF;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,GAChC,aAAa,EAAE,CAejB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,QAAQ,CAAC,kBAAkB,EACjC,IAAI,EAAE,MAAM,GACX,aAAa,GAAG,IAAI,CAGtB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAErF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,aAAa,GAAG,OAAO,CAWxE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,aAAa,GAAG,OAAO,EAAE,GAAG,IAAI,CAgC/E;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAqBxE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,GAAG,MAAM,GAAG,IAAI,CAQhF;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,QAAQ,CAAC,gBAAgB,GAAG,QAAQ,CAAC,eAAe,GAC9D,GAAG,CAAC,MAAM,CAAC,CAWb"}
|