@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.
- package/README.md +237 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +117 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/consistent-constraints.d.ts +22 -0
- package/dist/rules/consistent-constraints.d.ts.map +1 -0
- package/dist/rules/consistent-constraints.js +178 -0
- package/dist/rules/consistent-constraints.js.map +1 -0
- package/dist/rules/constraints/allowed-field-types.d.ts +16 -0
- package/dist/rules/constraints/allowed-field-types.d.ts.map +1 -0
- package/dist/rules/constraints/allowed-field-types.js +133 -0
- package/dist/rules/constraints/allowed-field-types.js.map +1 -0
- package/dist/rules/constraints/allowed-layouts.d.ts +17 -0
- package/dist/rules/constraints/allowed-layouts.d.ts.map +1 -0
- package/dist/rules/constraints/allowed-layouts.js +83 -0
- package/dist/rules/constraints/allowed-layouts.js.map +1 -0
- package/dist/rules/constraints/index.d.ts +9 -0
- package/dist/rules/constraints/index.d.ts.map +1 -0
- package/dist/rules/constraints/index.js +9 -0
- package/dist/rules/constraints/index.js.map +1 -0
- package/dist/rules/decorator-allowed-field-types.d.ts +17 -0
- package/dist/rules/decorator-allowed-field-types.d.ts.map +1 -0
- package/dist/rules/decorator-allowed-field-types.js +71 -0
- package/dist/rules/decorator-allowed-field-types.js.map +1 -0
- package/dist/rules/decorator-field-type-mismatch.d.ts +14 -0
- package/dist/rules/decorator-field-type-mismatch.d.ts.map +1 -0
- package/dist/rules/decorator-field-type-mismatch.js +116 -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 +13 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +13 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/no-conflicting-decorators.d.ts +15 -0
- package/dist/rules/no-conflicting-decorators.d.ts.map +1 -0
- package/dist/rules/no-conflicting-decorators.js +72 -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 +59 -0
- package/dist/rules/no-duplicate-decorators.js.map +1 -0
- package/dist/rules/prefer-custom-decorator.d.ts +22 -0
- package/dist/rules/prefer-custom-decorator.d.ts.map +1 -0
- package/dist/rules/prefer-custom-decorator.js +72 -0
- package/dist/rules/prefer-custom-decorator.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 +105 -0
- package/dist/utils/decorator-utils.d.ts.map +1 -0
- package/dist/utils/decorator-utils.js +216 -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/jsdoc-utils.d.ts +31 -0
- package/dist/utils/jsdoc-utils.d.ts.map +1 -0
- package/dist/utils/jsdoc-utils.js +81 -0
- package/dist/utils/jsdoc-utils.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 +216 -0
- package/dist/utils/type-utils.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allowed-field-types.d.ts","sourceRoot":"","sources":["../../../src/rules/constraints/allowed-field-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAkB,MAAM,0BAA0B,CAAC;AAEvE,OAAO,EAEL,KAAK,oBAAoB,EAE1B,MAAM,+BAA+B,CAAC;AAwCvC,MAAM,MAAM,OAAO,GAAG,CAAC,oBAAoB,CAAC,CAAC;AAE7C,eAAO,MAAM,iBAAiB;;CAsF5B,CAAC"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: constraints/allowed-field-types
|
|
3
|
+
*
|
|
4
|
+
* Validates that field types used in the Chain DSL are allowed by the
|
|
5
|
+
* project's constraint configuration.
|
|
6
|
+
*
|
|
7
|
+
* Works with: field.text(), field.number(), field.boolean(), field.enum(),
|
|
8
|
+
* field.dynamicEnum(), field.dynamicSchema(), field.array(), field.object()
|
|
9
|
+
*/
|
|
10
|
+
import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
11
|
+
import { getFieldTypeSeverity, } from "@formspec/constraints/browser";
|
|
12
|
+
const createRule = ESLintUtils.RuleCreator((name) => `https://formspec.dev/eslint-plugin/rules/${name}`);
|
|
13
|
+
/**
|
|
14
|
+
* Maps DSL method names to constraint config keys.
|
|
15
|
+
*/
|
|
16
|
+
const METHOD_TO_CONSTRAINT = {
|
|
17
|
+
text: "text",
|
|
18
|
+
number: "number",
|
|
19
|
+
boolean: "boolean",
|
|
20
|
+
enum: "staticEnum",
|
|
21
|
+
dynamicEnum: "dynamicEnum",
|
|
22
|
+
dynamicSchema: "dynamicSchema",
|
|
23
|
+
array: "array",
|
|
24
|
+
arrayWithConfig: "array",
|
|
25
|
+
object: "object",
|
|
26
|
+
objectWithConfig: "object",
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Human-readable names for field types.
|
|
30
|
+
*/
|
|
31
|
+
const FIELD_TYPE_NAMES = {
|
|
32
|
+
text: "text field",
|
|
33
|
+
number: "number field",
|
|
34
|
+
boolean: "boolean field",
|
|
35
|
+
enum: "static enum field",
|
|
36
|
+
dynamicEnum: "dynamic enum field",
|
|
37
|
+
dynamicSchema: "dynamic schema field",
|
|
38
|
+
array: "array field",
|
|
39
|
+
arrayWithConfig: "array field",
|
|
40
|
+
object: "object field",
|
|
41
|
+
objectWithConfig: "object field",
|
|
42
|
+
};
|
|
43
|
+
export const allowedFieldTypes = createRule({
|
|
44
|
+
name: "constraints-allowed-field-types",
|
|
45
|
+
meta: {
|
|
46
|
+
type: "problem",
|
|
47
|
+
docs: {
|
|
48
|
+
description: "Validates that field types are allowed by the project's constraints",
|
|
49
|
+
},
|
|
50
|
+
messages: {
|
|
51
|
+
disallowedFieldType: "{{fieldTypeName}} is not allowed in this project. Field: '{{fieldName}}'",
|
|
52
|
+
},
|
|
53
|
+
schema: [
|
|
54
|
+
{
|
|
55
|
+
type: "object",
|
|
56
|
+
properties: {
|
|
57
|
+
text: { type: "string", enum: ["error", "warn", "off"] },
|
|
58
|
+
number: { type: "string", enum: ["error", "warn", "off"] },
|
|
59
|
+
boolean: { type: "string", enum: ["error", "warn", "off"] },
|
|
60
|
+
staticEnum: { type: "string", enum: ["error", "warn", "off"] },
|
|
61
|
+
dynamicEnum: { type: "string", enum: ["error", "warn", "off"] },
|
|
62
|
+
dynamicSchema: { type: "string", enum: ["error", "warn", "off"] },
|
|
63
|
+
array: { type: "string", enum: ["error", "warn", "off"] },
|
|
64
|
+
object: { type: "string", enum: ["error", "warn", "off"] },
|
|
65
|
+
},
|
|
66
|
+
additionalProperties: false,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
defaultOptions: [{}],
|
|
71
|
+
create(context) {
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- RuleTester may not apply defaultOptions
|
|
73
|
+
const constraints = context.options[0] ?? {};
|
|
74
|
+
return {
|
|
75
|
+
CallExpression(node) {
|
|
76
|
+
// Check for field.xxx() pattern
|
|
77
|
+
if (node.callee.type !== AST_NODE_TYPES.MemberExpression ||
|
|
78
|
+
node.callee.object.type !== AST_NODE_TYPES.Identifier ||
|
|
79
|
+
node.callee.object.name !== "field" ||
|
|
80
|
+
node.callee.property.type !== AST_NODE_TYPES.Identifier) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const methodName = node.callee.property.name;
|
|
84
|
+
const constraintKey = METHOD_TO_CONSTRAINT[methodName];
|
|
85
|
+
if (!constraintKey) {
|
|
86
|
+
return; // Not a recognized field method
|
|
87
|
+
}
|
|
88
|
+
// Map constraint key to internal field type for severity check
|
|
89
|
+
const fieldTypeMap = {
|
|
90
|
+
text: "text",
|
|
91
|
+
number: "number",
|
|
92
|
+
boolean: "boolean",
|
|
93
|
+
staticEnum: "enum",
|
|
94
|
+
dynamicEnum: "dynamic_enum",
|
|
95
|
+
dynamicSchema: "dynamic_schema",
|
|
96
|
+
array: "array",
|
|
97
|
+
object: "object",
|
|
98
|
+
};
|
|
99
|
+
const internalFieldType = fieldTypeMap[constraintKey];
|
|
100
|
+
const severity = getFieldTypeSeverity(internalFieldType, constraints);
|
|
101
|
+
if (severity === "off") {
|
|
102
|
+
return; // Allowed
|
|
103
|
+
}
|
|
104
|
+
// Extract field name from first argument
|
|
105
|
+
const fieldName = extractFieldName(node.arguments[0]);
|
|
106
|
+
const fieldTypeName = FIELD_TYPE_NAMES[methodName] ?? `${methodName} field`;
|
|
107
|
+
context.report({
|
|
108
|
+
node: node.callee.property,
|
|
109
|
+
messageId: "disallowedFieldType",
|
|
110
|
+
data: {
|
|
111
|
+
fieldTypeName,
|
|
112
|
+
fieldName,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
/**
|
|
120
|
+
* Extracts the field name from the first argument of a field.xxx() call.
|
|
121
|
+
*/
|
|
122
|
+
function extractFieldName(arg) {
|
|
123
|
+
if (!arg)
|
|
124
|
+
return "<unknown>";
|
|
125
|
+
if (arg.type === AST_NODE_TYPES.Literal && typeof arg.value === "string") {
|
|
126
|
+
return arg.value;
|
|
127
|
+
}
|
|
128
|
+
if (arg.type === AST_NODE_TYPES.TemplateLiteral && arg.quasis.length === 1) {
|
|
129
|
+
return arg.quasis[0]?.value.cooked ?? "<unknown>";
|
|
130
|
+
}
|
|
131
|
+
return "<dynamic>";
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=allowed-field-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allowed-field-types.js","sourceRoot":"","sources":["../../../src/rules/constraints/allowed-field-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: constraints/allowed-layouts
|
|
3
|
+
*
|
|
4
|
+
* Validates that layout constructs (group, when/conditionals) used in the
|
|
5
|
+
* Chain DSL are allowed by the project's constraint configuration.
|
|
6
|
+
*
|
|
7
|
+
* Works with: group(), when()
|
|
8
|
+
*/
|
|
9
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
10
|
+
import { type LayoutConstraints } from "@formspec/constraints/browser";
|
|
11
|
+
type MessageIds = "disallowedGroup" | "disallowedConditional";
|
|
12
|
+
export type Options = [LayoutConstraints];
|
|
13
|
+
export declare const allowedLayouts: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener> & {
|
|
14
|
+
name: string;
|
|
15
|
+
};
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=allowed-layouts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allowed-layouts.d.ts","sourceRoot":"","sources":["../../../src/rules/constraints/allowed-layouts.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAkB,MAAM,0BAA0B,CAAC;AAEvE,OAAO,EAAuB,KAAK,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAM5F,KAAK,UAAU,GAAG,iBAAiB,GAAG,uBAAuB,CAAC;AAE9D,MAAM,MAAM,OAAO,GAAG,CAAC,iBAAiB,CAAC,CAAC;AAE1C,eAAO,MAAM,cAAc;;CA6DzB,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: constraints/allowed-layouts
|
|
3
|
+
*
|
|
4
|
+
* Validates that layout constructs (group, when/conditionals) used in the
|
|
5
|
+
* Chain DSL are allowed by the project's constraint configuration.
|
|
6
|
+
*
|
|
7
|
+
* Works with: group(), when()
|
|
8
|
+
*/
|
|
9
|
+
import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
10
|
+
import { isLayoutTypeAllowed } from "@formspec/constraints/browser";
|
|
11
|
+
const createRule = ESLintUtils.RuleCreator((name) => `https://formspec.dev/eslint-plugin/rules/${name}`);
|
|
12
|
+
export const allowedLayouts = createRule({
|
|
13
|
+
name: "constraints-allowed-layouts",
|
|
14
|
+
meta: {
|
|
15
|
+
type: "problem",
|
|
16
|
+
docs: {
|
|
17
|
+
description: "Validates that layout constructs (group, conditionals) are allowed by the project's constraints",
|
|
18
|
+
},
|
|
19
|
+
messages: {
|
|
20
|
+
disallowedGroup: "group() is not allowed - visual grouping is not supported in this project{{labelInfo}}",
|
|
21
|
+
disallowedConditional: "when() conditional visibility is not allowed in this project",
|
|
22
|
+
},
|
|
23
|
+
schema: [
|
|
24
|
+
{
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
group: { type: "string", enum: ["error", "warn", "off"] },
|
|
28
|
+
conditionals: { type: "string", enum: ["error", "warn", "off"] },
|
|
29
|
+
maxNestingDepth: { type: "number", minimum: 0 },
|
|
30
|
+
},
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
defaultOptions: [{}],
|
|
36
|
+
create(context) {
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- RuleTester may not apply defaultOptions
|
|
38
|
+
const constraints = context.options[0] ?? {};
|
|
39
|
+
return {
|
|
40
|
+
CallExpression(node) {
|
|
41
|
+
// Check for group() or when() calls
|
|
42
|
+
if (node.callee.type !== AST_NODE_TYPES.Identifier) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const functionName = node.callee.name;
|
|
46
|
+
if (functionName === "group") {
|
|
47
|
+
if (!isLayoutTypeAllowed("group", constraints)) {
|
|
48
|
+
const label = extractGroupLabel(node.arguments[0]);
|
|
49
|
+
const labelInfo = label ? ` (label: "${label}")` : "";
|
|
50
|
+
context.report({
|
|
51
|
+
node: node.callee,
|
|
52
|
+
messageId: "disallowedGroup",
|
|
53
|
+
data: { labelInfo },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (functionName === "when") {
|
|
58
|
+
if (!isLayoutTypeAllowed("conditional", constraints)) {
|
|
59
|
+
context.report({
|
|
60
|
+
node: node.callee,
|
|
61
|
+
messageId: "disallowedConditional",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* Extracts the group label from the first argument of a group() call.
|
|
71
|
+
*/
|
|
72
|
+
function extractGroupLabel(arg) {
|
|
73
|
+
if (!arg)
|
|
74
|
+
return null;
|
|
75
|
+
if (arg.type === AST_NODE_TYPES.Literal && typeof arg.value === "string") {
|
|
76
|
+
return arg.value;
|
|
77
|
+
}
|
|
78
|
+
if (arg.type === AST_NODE_TYPES.TemplateLiteral && arg.quasis.length === 1) {
|
|
79
|
+
return arg.quasis[0]?.value.cooked ?? null;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=allowed-layouts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allowed-layouts.js","sourceRoot":"","sources":["../../../src/rules/constraints/allowed-layouts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constraint validation rules for FormSpec Chain DSL.
|
|
3
|
+
*
|
|
4
|
+
* These rules validate that FormSpec constructs are allowed by the
|
|
5
|
+
* project's constraint configuration (.formspec.yml).
|
|
6
|
+
*/
|
|
7
|
+
export { allowedFieldTypes } from "./allowed-field-types.js";
|
|
8
|
+
export { allowedLayouts } from "./allowed-layouts.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/rules/constraints/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constraint validation rules for FormSpec Chain DSL.
|
|
3
|
+
*
|
|
4
|
+
* These rules validate that FormSpec constructs are allowed by the
|
|
5
|
+
* project's constraint configuration (.formspec.yml).
|
|
6
|
+
*/
|
|
7
|
+
export { allowedFieldTypes } from "./allowed-field-types.js";
|
|
8
|
+
export { allowedLayouts } from "./allowed-layouts.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/constraints/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: decorator-allowed-field-types
|
|
3
|
+
*
|
|
4
|
+
* Configurable rule restricting TypeScript field types on decorated properties.
|
|
5
|
+
* Only applies to properties that have at least one FormSpec decorator.
|
|
6
|
+
*
|
|
7
|
+
* Config example:
|
|
8
|
+
* "@formspec/decorator-allowed-field-types": ["error", { allow: ["string", "number", "boolean", "enum"] }]
|
|
9
|
+
*/
|
|
10
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
11
|
+
export interface DecoratorAllowedFieldTypesOptions {
|
|
12
|
+
allow: string[];
|
|
13
|
+
}
|
|
14
|
+
export declare const decoratorAllowedFieldTypes: ESLintUtils.RuleModule<"disallowedFieldType", [DecoratorAllowedFieldTypesOptions], unknown, ESLintUtils.RuleListener> & {
|
|
15
|
+
name: string;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=decorator-allowed-field-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorator-allowed-field-types.d.ts","sourceRoot":"","sources":["../../src/rules/decorator-allowed-field-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAkB,MAAM,0BAA0B,CAAC;AAUvE,MAAM,WAAW,iCAAiC;IAChD,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,eAAO,MAAM,0BAA0B;;CA+DrC,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: decorator-allowed-field-types
|
|
3
|
+
*
|
|
4
|
+
* Configurable rule restricting TypeScript field types on decorated properties.
|
|
5
|
+
* Only applies to properties that have at least one FormSpec decorator.
|
|
6
|
+
*
|
|
7
|
+
* Config example:
|
|
8
|
+
* "@formspec/decorator-allowed-field-types": ["error", { allow: ["string", "number", "boolean", "enum"] }]
|
|
9
|
+
*/
|
|
10
|
+
import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
11
|
+
import { getFormSpecDecorators } from "../utils/decorator-utils.js";
|
|
12
|
+
import { getPropertyType, getFieldTypeCategory } from "../utils/type-utils.js";
|
|
13
|
+
const createRule = ESLintUtils.RuleCreator((name) => `https://formspec.dev/eslint-plugin/rules/${name}`);
|
|
14
|
+
export const decoratorAllowedFieldTypes = createRule({
|
|
15
|
+
name: "decorator-allowed-field-types",
|
|
16
|
+
meta: {
|
|
17
|
+
type: "problem",
|
|
18
|
+
docs: {
|
|
19
|
+
description: "Restricts TypeScript field types on decorated properties to an allowed list",
|
|
20
|
+
},
|
|
21
|
+
messages: {
|
|
22
|
+
disallowedFieldType: "Field '{{field}}' has type '{{actualType}}' (category: {{category}}), which is not in the allowed list: [{{allowed}}]",
|
|
23
|
+
},
|
|
24
|
+
schema: [
|
|
25
|
+
{
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {
|
|
28
|
+
allow: {
|
|
29
|
+
type: "array",
|
|
30
|
+
items: { type: "string" },
|
|
31
|
+
minItems: 1,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
required: ["allow"],
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
defaultOptions: [{ allow: ["string", "number", "boolean", "union"] }],
|
|
40
|
+
create(context, [options]) {
|
|
41
|
+
const services = ESLintUtils.getParserServices(context);
|
|
42
|
+
const checker = services.program.getTypeChecker();
|
|
43
|
+
const allowedSet = new Set(options.allow);
|
|
44
|
+
return {
|
|
45
|
+
PropertyDefinition(node) {
|
|
46
|
+
const decorators = getFormSpecDecorators(node);
|
|
47
|
+
if (decorators.length === 0)
|
|
48
|
+
return;
|
|
49
|
+
const type = getPropertyType(node, services);
|
|
50
|
+
if (!type)
|
|
51
|
+
return;
|
|
52
|
+
const category = getFieldTypeCategory(type, checker);
|
|
53
|
+
if (allowedSet.has(category))
|
|
54
|
+
return;
|
|
55
|
+
const fieldName = node.key.type === AST_NODE_TYPES.Identifier ? node.key.name : "<computed>";
|
|
56
|
+
const actualType = checker.typeToString(type);
|
|
57
|
+
context.report({
|
|
58
|
+
node: node.key,
|
|
59
|
+
messageId: "disallowedFieldType",
|
|
60
|
+
data: {
|
|
61
|
+
field: fieldName,
|
|
62
|
+
actualType,
|
|
63
|
+
category,
|
|
64
|
+
allowed: options.allow.join(", "),
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=decorator-allowed-field-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorator-allowed-field-types.js","sourceRoot":"","sources":["../../src/rules/decorator-allowed-field-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE/E,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CAAC,4CAA4C,IAAI,EAAE,CAC7D,CAAC;AAQF,MAAM,CAAC,MAAM,0BAA0B,GAAG,UAAU,CAGlD;IACA,IAAI,EAAE,+BAA+B;IACrC,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,6EAA6E;SAC3F;QACD,QAAQ,EAAE;YACR,mBAAmB,EACjB,uHAAuH;SAC1H;QACD,MAAM,EAAE;YACN;gBACE,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,KAAK,EAAE;wBACL,IAAI,EAAE,OAAO;wBACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;wBACzB,QAAQ,EAAE,CAAC;qBACZ;iBACF;gBACD,QAAQ,EAAE,CAAC,OAAO,CAAC;gBACnB,oBAAoB,EAAE,KAAK;aAC5B;SACF;KACF;IACD,cAAc,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;IACrE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC;QACvB,MAAM,QAAQ,GAAG,WAAW,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;QAClD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAE1C,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,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;gBAC7C,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAElB,MAAM,QAAQ,GAAG,oBAAoB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBACrD,IAAI,UAAU,CAAC,GAAG,CAAC,QAAQ,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;gBAC7E,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;gBAE9C,OAAO,CAAC,MAAM,CAAC;oBACb,IAAI,EAAE,IAAI,CAAC,GAAG;oBACd,SAAS,EAAE,qBAAqB;oBAChC,IAAI,EAAE;wBACJ,KAAK,EAAE,SAAS;wBAChB,UAAU;wBACV,QAAQ;wBACR,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;qBAClC;iBACF,CAAC,CAAC;YACL,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: decorator-field-type-mismatch
|
|
3
|
+
*
|
|
4
|
+
* Ensures FormSpec decorators are applied to fields with compatible types:
|
|
5
|
+
* - @Minimum/@Maximum/@ExclusiveMinimum/@ExclusiveMaximum only on number fields
|
|
6
|
+
* - @MinLength/@MaxLength/@Pattern only on string fields
|
|
7
|
+
*/
|
|
8
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
9
|
+
type MessageIds = "numericOnNonNumber" | "stringOnNonString";
|
|
10
|
+
export declare const decoratorFieldTypeMismatch: ESLintUtils.RuleModule<MessageIds, [], unknown, ESLintUtils.RuleListener> & {
|
|
11
|
+
name: string;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=decorator-field-type-mismatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorator-field-type-mismatch.d.ts","sourceRoot":"","sources":["../../src/rules/decorator-field-type-mismatch.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAkB,MAAM,0BAA0B,CAAC;AAiBvE,KAAK,UAAU,GAAG,oBAAoB,GAAG,mBAAmB,CAAC;AAuB7D,eAAO,MAAM,0BAA0B;;CA6FrC,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: decorator-field-type-mismatch
|
|
3
|
+
*
|
|
4
|
+
* Ensures FormSpec decorators are applied to fields with compatible types:
|
|
5
|
+
* - @Minimum/@Maximum/@ExclusiveMinimum/@ExclusiveMaximum only on number fields
|
|
6
|
+
* - @MinLength/@MaxLength/@Pattern only on string fields
|
|
7
|
+
*/
|
|
8
|
+
import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
9
|
+
import { getFormSpecDecorators, DECORATOR_TYPE_HINTS, } from "../utils/decorator-utils.js";
|
|
10
|
+
import { getJSDocConstraints } from "../utils/jsdoc-utils.js";
|
|
11
|
+
import { getPropertyType, getFieldTypeCategory, } from "../utils/type-utils.js";
|
|
12
|
+
const createRule = ESLintUtils.RuleCreator((name) => `https://formspec.dev/eslint-plugin/rules/${name}`);
|
|
13
|
+
const EXPECTED_TYPES = {
|
|
14
|
+
Minimum: ["number"],
|
|
15
|
+
Maximum: ["number"],
|
|
16
|
+
ExclusiveMinimum: ["number"],
|
|
17
|
+
ExclusiveMaximum: ["number"],
|
|
18
|
+
MinLength: ["string"],
|
|
19
|
+
MaxLength: ["string"],
|
|
20
|
+
Pattern: ["string"],
|
|
21
|
+
EnumOptions: ["string", "union"], // Handled by separate rule
|
|
22
|
+
};
|
|
23
|
+
const DECORATOR_MESSAGE_IDS = {
|
|
24
|
+
Minimum: "numericOnNonNumber",
|
|
25
|
+
Maximum: "numericOnNonNumber",
|
|
26
|
+
ExclusiveMinimum: "numericOnNonNumber",
|
|
27
|
+
ExclusiveMaximum: "numericOnNonNumber",
|
|
28
|
+
MinLength: "stringOnNonString",
|
|
29
|
+
MaxLength: "stringOnNonString",
|
|
30
|
+
Pattern: "stringOnNonString",
|
|
31
|
+
};
|
|
32
|
+
export const decoratorFieldTypeMismatch = createRule({
|
|
33
|
+
name: "decorator-field-type-mismatch",
|
|
34
|
+
meta: {
|
|
35
|
+
type: "problem",
|
|
36
|
+
docs: {
|
|
37
|
+
description: "Ensures FormSpec decorators are applied to fields with compatible types",
|
|
38
|
+
},
|
|
39
|
+
messages: {
|
|
40
|
+
numericOnNonNumber: "@{{decorator}} can only be used on number fields, but field '{{field}}' has type '{{actualType}}'",
|
|
41
|
+
stringOnNonString: "@{{decorator}} can only be used on string fields, but field '{{field}}' has type '{{actualType}}'",
|
|
42
|
+
},
|
|
43
|
+
schema: [],
|
|
44
|
+
},
|
|
45
|
+
defaultOptions: [],
|
|
46
|
+
create(context) {
|
|
47
|
+
const services = ESLintUtils.getParserServices(context);
|
|
48
|
+
const checker = services.program.getTypeChecker();
|
|
49
|
+
return {
|
|
50
|
+
PropertyDefinition(node) {
|
|
51
|
+
const decorators = getFormSpecDecorators(node);
|
|
52
|
+
const jsdocConstraints = getJSDocConstraints(node, context.sourceCode);
|
|
53
|
+
// Nothing to check if there are no decorators or JSDoc constraints
|
|
54
|
+
if (decorators.length === 0 && jsdocConstraints.length === 0)
|
|
55
|
+
return;
|
|
56
|
+
const type = getPropertyType(node, services);
|
|
57
|
+
if (!type)
|
|
58
|
+
return;
|
|
59
|
+
const fieldTypeCategory = getFieldTypeCategory(type, checker);
|
|
60
|
+
const fieldName = node.key.type === AST_NODE_TYPES.Identifier ? node.key.name : "<computed>";
|
|
61
|
+
const actualType = checker.typeToString(type);
|
|
62
|
+
for (const decorator of decorators) {
|
|
63
|
+
const decoratorName = decorator.name;
|
|
64
|
+
// Skip decorators that don't have type hints
|
|
65
|
+
if (!(decoratorName in DECORATOR_TYPE_HINTS))
|
|
66
|
+
continue;
|
|
67
|
+
// Skip EnumOptions - handled by separate rule
|
|
68
|
+
if (decoratorName === "EnumOptions")
|
|
69
|
+
continue;
|
|
70
|
+
const expectedTypes = EXPECTED_TYPES[decoratorName];
|
|
71
|
+
// Check if field type matches any expected type
|
|
72
|
+
const isCompatible = expectedTypes.includes(fieldTypeCategory);
|
|
73
|
+
if (!isCompatible) {
|
|
74
|
+
const messageId = DECORATOR_MESSAGE_IDS[decoratorName];
|
|
75
|
+
if (!messageId)
|
|
76
|
+
continue;
|
|
77
|
+
context.report({
|
|
78
|
+
node: decorator.node,
|
|
79
|
+
messageId,
|
|
80
|
+
data: {
|
|
81
|
+
decorator: decoratorName,
|
|
82
|
+
field: fieldName,
|
|
83
|
+
actualType,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Also check JSDoc constraint tags for type compatibility
|
|
89
|
+
for (const constraint of jsdocConstraints) {
|
|
90
|
+
const constraintName = constraint.name;
|
|
91
|
+
if (!(constraintName in DECORATOR_TYPE_HINTS))
|
|
92
|
+
continue;
|
|
93
|
+
if (constraintName === "EnumOptions")
|
|
94
|
+
continue;
|
|
95
|
+
const expectedTypes = EXPECTED_TYPES[constraintName];
|
|
96
|
+
const isCompatible = expectedTypes.includes(fieldTypeCategory);
|
|
97
|
+
if (!isCompatible) {
|
|
98
|
+
const messageId = DECORATOR_MESSAGE_IDS[constraintName];
|
|
99
|
+
if (!messageId)
|
|
100
|
+
continue;
|
|
101
|
+
context.report({
|
|
102
|
+
loc: constraint.comment.loc,
|
|
103
|
+
messageId,
|
|
104
|
+
data: {
|
|
105
|
+
decorator: constraintName,
|
|
106
|
+
field: fieldName,
|
|
107
|
+
actualType,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
//# sourceMappingURL=decorator-field-type-mismatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorator-field-type-mismatch.js","sourceRoot":"","sources":["../../src/rules/decorator-field-type-mismatch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
type MessageIds = "enumOptionsMismatch" | "enumOptionsMissing" | "enumOptionsExtra";
|
|
22
|
+
export declare const enumOptionsMatchType: ESLintUtils.RuleModule<MessageIds, [], unknown, ESLintUtils.RuleListener> & {
|
|
23
|
+
name: string;
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=enum-options-match-type.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enum-options-match-type.d.ts","sourceRoot":"","sources":["../../src/rules/enum-options-match-type.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAQvD,KAAK,UAAU,GAAG,qBAAqB,GAAG,oBAAoB,GAAG,kBAAkB,CAAC;AAEpF,eAAO,MAAM,oBAAoB;;CAmG/B,CAAC"}
|