@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.
Files changed (50) hide show
  1. package/README.md +217 -0
  2. package/dist/index.d.ts +57 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +104 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/rules/decorator-field-type-mismatch.d.ts +15 -0
  7. package/dist/rules/decorator-field-type-mismatch.d.ts.map +1 -0
  8. package/dist/rules/decorator-field-type-mismatch.js +87 -0
  9. package/dist/rules/decorator-field-type-mismatch.js.map +1 -0
  10. package/dist/rules/enum-options-match-type.d.ts +26 -0
  11. package/dist/rules/enum-options-match-type.d.ts.map +1 -0
  12. package/dist/rules/enum-options-match-type.js +115 -0
  13. package/dist/rules/enum-options-match-type.js.map +1 -0
  14. package/dist/rules/index.d.ts +11 -0
  15. package/dist/rules/index.d.ts.map +1 -0
  16. package/dist/rules/index.js +11 -0
  17. package/dist/rules/index.js.map +1 -0
  18. package/dist/rules/min-max-valid-range.d.ts +26 -0
  19. package/dist/rules/min-max-valid-range.d.ts.map +1 -0
  20. package/dist/rules/min-max-valid-range.js +82 -0
  21. package/dist/rules/min-max-valid-range.js.map +1 -0
  22. package/dist/rules/no-conflicting-decorators.d.ts +19 -0
  23. package/dist/rules/no-conflicting-decorators.d.ts.map +1 -0
  24. package/dist/rules/no-conflicting-decorators.js +76 -0
  25. package/dist/rules/no-conflicting-decorators.js.map +1 -0
  26. package/dist/rules/no-duplicate-decorators.d.ts +19 -0
  27. package/dist/rules/no-duplicate-decorators.d.ts.map +1 -0
  28. package/dist/rules/no-duplicate-decorators.js +74 -0
  29. package/dist/rules/no-duplicate-decorators.js.map +1 -0
  30. package/dist/rules/showwhen-field-exists.d.ts +21 -0
  31. package/dist/rules/showwhen-field-exists.d.ts.map +1 -0
  32. package/dist/rules/showwhen-field-exists.js +68 -0
  33. package/dist/rules/showwhen-field-exists.js.map +1 -0
  34. package/dist/rules/showwhen-suggests-optional.d.ts +19 -0
  35. package/dist/rules/showwhen-suggests-optional.d.ts.map +1 -0
  36. package/dist/rules/showwhen-suggests-optional.js +53 -0
  37. package/dist/rules/showwhen-suggests-optional.js.map +1 -0
  38. package/dist/utils/decorator-utils.d.ts +103 -0
  39. package/dist/utils/decorator-utils.d.ts.map +1 -0
  40. package/dist/utils/decorator-utils.js +222 -0
  41. package/dist/utils/decorator-utils.js.map +1 -0
  42. package/dist/utils/index.d.ts +6 -0
  43. package/dist/utils/index.d.ts.map +1 -0
  44. package/dist/utils/index.js +6 -0
  45. package/dist/utils/index.js.map +1 -0
  46. package/dist/utils/type-utils.d.ts +82 -0
  47. package/dist/utils/type-utils.d.ts.map +1 -0
  48. package/dist/utils/type-utils.js +192 -0
  49. package/dist/utils/type-utils.js.map +1 -0
  50. package/package.json +48 -0
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Utility functions for working with decorator AST nodes.
3
+ */
4
+ import { TSESTree, AST_NODE_TYPES } from "@typescript-eslint/utils";
5
+ /**
6
+ * FormSpec decorator names that imply specific field types.
7
+ */
8
+ export const DECORATOR_TYPE_HINTS = {
9
+ // Number field decorators
10
+ Min: "number",
11
+ Max: "number",
12
+ // String/text field decorators
13
+ Placeholder: "string",
14
+ // Array field decorators
15
+ MinItems: "array",
16
+ MaxItems: "array",
17
+ // Enum field decorators
18
+ EnumOptions: "enum",
19
+ };
20
+ /**
21
+ * All known FormSpec decorator names.
22
+ */
23
+ export const FORMSPEC_DECORATORS = new Set([
24
+ "FormClass",
25
+ "Label",
26
+ "Optional",
27
+ "Placeholder",
28
+ "Min",
29
+ "Max",
30
+ "EnumOptions",
31
+ "Group",
32
+ "ShowWhen",
33
+ "MinItems",
34
+ "MaxItems",
35
+ ]);
36
+ /**
37
+ * Extracts decorator information from a Decorator AST node.
38
+ *
39
+ * Handles both:
40
+ * - `@DecoratorName` (identifier)
41
+ * - `@DecoratorName(args)` (call expression)
42
+ *
43
+ * @param decorator - The decorator AST node
44
+ * @returns Decorator info or null if not a recognized pattern
45
+ */
46
+ export function getDecoratorInfo(decorator) {
47
+ const expr = decorator.expression;
48
+ // Case 1: @DecoratorName() - CallExpression
49
+ if (expr.type === AST_NODE_TYPES.CallExpression) {
50
+ const callee = expr.callee;
51
+ if (callee.type === AST_NODE_TYPES.Identifier) {
52
+ return {
53
+ name: callee.name,
54
+ args: expr.arguments,
55
+ node: decorator,
56
+ };
57
+ }
58
+ }
59
+ // Case 2: @DecoratorName - Identifier (no parentheses)
60
+ if (expr.type === AST_NODE_TYPES.Identifier) {
61
+ return {
62
+ name: expr.name,
63
+ args: [],
64
+ node: decorator,
65
+ };
66
+ }
67
+ return null;
68
+ }
69
+ /**
70
+ * Finds all decorators on a property definition.
71
+ *
72
+ * @param node - The property definition node
73
+ * @returns Array of decorator info objects for FormSpec decorators
74
+ */
75
+ export function getFormSpecDecorators(node) {
76
+ const decorators = node.decorators;
77
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- decorators can be undefined
78
+ if (!decorators || decorators.length === 0) {
79
+ return [];
80
+ }
81
+ const result = [];
82
+ for (const decorator of decorators) {
83
+ const info = getDecoratorInfo(decorator);
84
+ if (info && FORMSPEC_DECORATORS.has(info.name)) {
85
+ result.push(info);
86
+ }
87
+ }
88
+ return result;
89
+ }
90
+ /**
91
+ * Finds a specific decorator by name on a property.
92
+ *
93
+ * @param node - The property definition node
94
+ * @param name - The decorator name to find
95
+ * @returns The decorator info or null if not found
96
+ */
97
+ export function findDecorator(node, name) {
98
+ const decorators = getFormSpecDecorators(node);
99
+ return decorators.find((d) => d.name === name) ?? null;
100
+ }
101
+ /**
102
+ * Checks if a property has a specific decorator.
103
+ *
104
+ * @param node - The property definition node
105
+ * @param name - The decorator name to check
106
+ * @returns True if the decorator is present
107
+ */
108
+ export function hasDecorator(node, name) {
109
+ return findDecorator(node, name) !== null;
110
+ }
111
+ /**
112
+ * Gets the first argument of a decorator as a literal value.
113
+ *
114
+ * @param decorator - The decorator info
115
+ * @returns The literal value or null if not a literal
116
+ */
117
+ export function getDecoratorLiteralArg(decorator) {
118
+ const arg = decorator.args[0];
119
+ if (!arg) {
120
+ return null;
121
+ }
122
+ if (arg.type === AST_NODE_TYPES.Literal) {
123
+ return arg.value;
124
+ }
125
+ return null;
126
+ }
127
+ /**
128
+ * Gets the first argument of a decorator as an array of values.
129
+ * Used for @EnumOptions(["a", "b", "c"]).
130
+ *
131
+ * @param decorator - The decorator info
132
+ * @returns Array of values or null if not an array expression
133
+ */
134
+ export function getDecoratorArrayArg(decorator) {
135
+ const arg = decorator.args[0];
136
+ if (!arg) {
137
+ return null;
138
+ }
139
+ if (arg.type === AST_NODE_TYPES.ArrayExpression) {
140
+ const values = [];
141
+ for (const element of arg.elements) {
142
+ if (!element)
143
+ continue;
144
+ if (element.type === AST_NODE_TYPES.Literal) {
145
+ values.push(element.value);
146
+ }
147
+ else if (element.type === AST_NODE_TYPES.ObjectExpression) {
148
+ // Handle { id: "x", label: "X" } objects
149
+ const obj = {};
150
+ for (const prop of element.properties) {
151
+ if (prop.type === AST_NODE_TYPES.Property &&
152
+ prop.key.type === AST_NODE_TYPES.Identifier &&
153
+ prop.value.type === AST_NODE_TYPES.Literal) {
154
+ obj[prop.key.name] = prop.value.value;
155
+ }
156
+ }
157
+ values.push(obj);
158
+ }
159
+ }
160
+ return values;
161
+ }
162
+ return null;
163
+ }
164
+ /**
165
+ * Gets the field reference from a @ShowWhen predicate.
166
+ * @ShowWhen({ _predicate: "equals", field: "foo", value: "bar" })
167
+ *
168
+ * @param decorator - The ShowWhen decorator info
169
+ * @returns The field name or null if not found
170
+ */
171
+ export function getShowWhenField(decorator) {
172
+ const arg = decorator.args[0];
173
+ if (!arg) {
174
+ return null;
175
+ }
176
+ if (arg.type === AST_NODE_TYPES.ObjectExpression) {
177
+ for (const prop of arg.properties) {
178
+ if (prop.type === AST_NODE_TYPES.Property &&
179
+ prop.key.type === AST_NODE_TYPES.Identifier &&
180
+ prop.key.name === "field" &&
181
+ prop.value.type === AST_NODE_TYPES.Literal &&
182
+ typeof prop.value.value === "string") {
183
+ return prop.value.value;
184
+ }
185
+ }
186
+ }
187
+ return null;
188
+ }
189
+ /**
190
+ * Gets the property name from a PropertyDefinition.
191
+ *
192
+ * @param node - The property definition node
193
+ * @returns The property name or null if computed/symbol
194
+ */
195
+ export function getPropertyName(node) {
196
+ if (node.key.type === AST_NODE_TYPES.Identifier) {
197
+ return node.key.name;
198
+ }
199
+ if (node.key.type === AST_NODE_TYPES.Literal && typeof node.key.value === "string") {
200
+ return node.key.value;
201
+ }
202
+ return null;
203
+ }
204
+ /**
205
+ * Gets all property names from a class definition.
206
+ *
207
+ * @param classNode - The class declaration/expression node
208
+ * @returns Set of property names
209
+ */
210
+ export function getClassPropertyNames(classNode) {
211
+ const names = new Set();
212
+ for (const member of classNode.body.body) {
213
+ if (member.type === AST_NODE_TYPES.PropertyDefinition) {
214
+ const name = getPropertyName(member);
215
+ if (name) {
216
+ names.add(name);
217
+ }
218
+ }
219
+ }
220
+ return names;
221
+ }
222
+ //# sourceMappingURL=decorator-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorator-utils.js","sourceRoot":"","sources":["../../src/utils/decorator-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Utility functions for FormSpec ESLint rules.
3
+ */
4
+ export * from "./decorator-utils.js";
5
+ export * from "./type-utils.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Utility functions for FormSpec ESLint rules.
3
+ */
4
+ export * from "./decorator-utils.js";
5
+ export * from "./type-utils.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Utility functions for TypeScript type checking in ESLint rules.
3
+ */
4
+ import type { ParserServicesWithTypeInformation } from "@typescript-eslint/utils";
5
+ import type { TSESTree } from "@typescript-eslint/utils";
6
+ import type ts from "typescript";
7
+ /**
8
+ * Field type categories for FormSpec.
9
+ */
10
+ export type FieldTypeCategory = "string" | "number" | "boolean" | "array" | "object" | "union" | "unknown";
11
+ /**
12
+ * Checks if a TypeScript type is a string type.
13
+ *
14
+ * Handles string primitives, string literals, and unions of string types.
15
+ *
16
+ * @param type - The TypeScript type to check
17
+ * @param checker - The TypeScript type checker instance
18
+ * @returns True if the type is string, a string literal, or a union of string types
19
+ */
20
+ export declare function isStringType(type: ts.Type, checker: ts.TypeChecker): boolean;
21
+ /**
22
+ * Checks if a TypeScript type is a number type.
23
+ *
24
+ * Handles number primitives, number literals, and unions of number types.
25
+ *
26
+ * @param type - The TypeScript type to check
27
+ * @param checker - The TypeScript type checker instance
28
+ * @returns True if the type is number, a number literal, or a union of number types
29
+ */
30
+ export declare function isNumberType(type: ts.Type, checker: ts.TypeChecker): boolean;
31
+ /**
32
+ * Checks if a TypeScript type is a boolean type.
33
+ *
34
+ * Handles boolean primitives, boolean literals (true/false), and unions of boolean types.
35
+ *
36
+ * @param type - The TypeScript type to check
37
+ * @param checker - The TypeScript type checker instance
38
+ * @returns True if the type is boolean, true, false, or a union of boolean types
39
+ */
40
+ export declare function isBooleanType(type: ts.Type, checker: ts.TypeChecker): boolean;
41
+ /**
42
+ * Checks if a TypeScript type is an array type.
43
+ *
44
+ * Handles Array<T>, T[], and readonly arrays.
45
+ *
46
+ * @param type - The TypeScript type to check
47
+ * @param checker - The TypeScript type checker instance
48
+ * @returns True if the type is an array type
49
+ */
50
+ export declare function isArrayType(type: ts.Type, checker: ts.TypeChecker): boolean;
51
+ /**
52
+ * Determines the field type category from a TypeScript type.
53
+ *
54
+ * Categories help determine which decorators are valid for a field.
55
+ *
56
+ * @param type - The TypeScript type to categorize
57
+ * @param checker - The TypeScript type checker instance
58
+ * @returns The field type category
59
+ */
60
+ export declare function getFieldTypeCategory(type: ts.Type, checker: ts.TypeChecker): FieldTypeCategory;
61
+ /**
62
+ * Gets the TypeScript type of a class property.
63
+ */
64
+ export declare function getPropertyType(node: TSESTree.PropertyDefinition, services: ParserServicesWithTypeInformation): ts.Type | null;
65
+ /**
66
+ * Extracts string literal values from a union type.
67
+ * For type "a" | "b" | "c", returns ["a", "b", "c"].
68
+ */
69
+ export declare function getStringLiteralUnionValues(type: ts.Type, _checker: ts.TypeChecker): string[] | null;
70
+ /**
71
+ * Checks if a field is marked as optional (has `?` modifier).
72
+ */
73
+ export declare function isOptionalProperty(node: TSESTree.PropertyDefinition): boolean;
74
+ /**
75
+ * Gets the type checker from parser services.
76
+ */
77
+ export declare function getTypeChecker(services: ParserServicesWithTypeInformation): ts.TypeChecker;
78
+ /**
79
+ * Converts a TypeScript type to a readable string.
80
+ */
81
+ export declare function typeToString(type: ts.Type, checker: ts.TypeChecker): string;
82
+ //# sourceMappingURL=type-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type-utils.d.ts","sourceRoot":"","sources":["../../src/utils/type-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,0BAA0B,CAAC;AAClF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3G;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,WAAW,GAAG,OAAO,CAiB5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,WAAW,GAAG,OAAO,CAiB5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,WAAW,GAAG,OAAO,CAiB7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,WAAW,GAAG,OAAO,CAqB3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,OAAO,EAAE,EAAE,CAAC,WAAW,GACtB,iBAAiB,CAiBnB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,CAAC,kBAAkB,EACjC,QAAQ,EAAE,iCAAiC,GAC1C,EAAE,CAAC,IAAI,GAAG,IAAI,CAchB;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,QAAQ,EAAE,EAAE,CAAC,WAAW,GACvB,MAAM,EAAE,GAAG,IAAI,CAoBjB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,GAAG,OAAO,CAE7E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,iCAAiC,GAC1C,EAAE,CAAC,WAAW,CAEhB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,OAAO,EAAE,EAAE,CAAC,WAAW,GACtB,MAAM,CAER"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Utility functions for TypeScript type checking in ESLint rules.
3
+ */
4
+ /**
5
+ * Checks if a TypeScript type is a string type.
6
+ *
7
+ * Handles string primitives, string literals, and unions of string types.
8
+ *
9
+ * @param type - The TypeScript type to check
10
+ * @param checker - The TypeScript type checker instance
11
+ * @returns True if the type is string, a string literal, or a union of string types
12
+ */
13
+ export function isStringType(type, checker) {
14
+ // Check for string primitive
15
+ if (type.flags & 4 /* ts.TypeFlags.String */) {
16
+ return true;
17
+ }
18
+ // Check for string literal
19
+ if (type.flags & 128 /* ts.TypeFlags.StringLiteral */) {
20
+ return true;
21
+ }
22
+ // Check for union of string literals (e.g., "a" | "b")
23
+ if (type.isUnion()) {
24
+ return type.types.every((t) => isStringType(t, checker));
25
+ }
26
+ return false;
27
+ }
28
+ /**
29
+ * Checks if a TypeScript type is a number type.
30
+ *
31
+ * Handles number primitives, number literals, and unions of number types.
32
+ *
33
+ * @param type - The TypeScript type to check
34
+ * @param checker - The TypeScript type checker instance
35
+ * @returns True if the type is number, a number literal, or a union of number types
36
+ */
37
+ export function isNumberType(type, checker) {
38
+ // Check for number primitive
39
+ if (type.flags & 8 /* ts.TypeFlags.Number */) {
40
+ return true;
41
+ }
42
+ // Check for number literal
43
+ if (type.flags & 256 /* ts.TypeFlags.NumberLiteral */) {
44
+ return true;
45
+ }
46
+ // Check for union of number literals
47
+ if (type.isUnion()) {
48
+ return type.types.every((t) => isNumberType(t, checker));
49
+ }
50
+ return false;
51
+ }
52
+ /**
53
+ * Checks if a TypeScript type is a boolean type.
54
+ *
55
+ * Handles boolean primitives, boolean literals (true/false), and unions of boolean types.
56
+ *
57
+ * @param type - The TypeScript type to check
58
+ * @param checker - The TypeScript type checker instance
59
+ * @returns True if the type is boolean, true, false, or a union of boolean types
60
+ */
61
+ export function isBooleanType(type, checker) {
62
+ // Check for boolean primitive
63
+ if (type.flags & 16 /* ts.TypeFlags.Boolean */) {
64
+ return true;
65
+ }
66
+ // Check for boolean literal (true or false)
67
+ if (type.flags & 512 /* ts.TypeFlags.BooleanLiteral */) {
68
+ return true;
69
+ }
70
+ // Check for union of true | false
71
+ if (type.isUnion()) {
72
+ return type.types.every((t) => isBooleanType(t, checker));
73
+ }
74
+ return false;
75
+ }
76
+ /**
77
+ * Checks if a TypeScript type is an array type.
78
+ *
79
+ * Handles Array<T>, T[], and readonly arrays.
80
+ *
81
+ * @param type - The TypeScript type to check
82
+ * @param checker - The TypeScript type checker instance
83
+ * @returns True if the type is an array type
84
+ */
85
+ export function isArrayType(type, checker) {
86
+ // Check symbol name for Array
87
+ const symbol = type.getSymbol();
88
+ if (symbol?.name === "Array") {
89
+ return true;
90
+ }
91
+ // Check for array type node (T[])
92
+ const typeString = checker.typeToString(type);
93
+ if (typeString.endsWith("[]") || typeString.startsWith("Array<")) {
94
+ return true;
95
+ }
96
+ // Check if it's a reference to Array
97
+ const typeRef = type;
98
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- target may not exist on non-reference types
99
+ if (typeRef.target && typeRef.target.symbol?.name === "Array") {
100
+ return true;
101
+ }
102
+ return false;
103
+ }
104
+ /**
105
+ * Determines the field type category from a TypeScript type.
106
+ *
107
+ * Categories help determine which decorators are valid for a field.
108
+ *
109
+ * @param type - The TypeScript type to categorize
110
+ * @param checker - The TypeScript type checker instance
111
+ * @returns The field type category
112
+ */
113
+ export function getFieldTypeCategory(type, checker) {
114
+ if (isStringType(type, checker))
115
+ return "string";
116
+ if (isNumberType(type, checker))
117
+ return "number";
118
+ if (isBooleanType(type, checker))
119
+ return "boolean";
120
+ if (isArrayType(type, checker))
121
+ return "array";
122
+ // Check for object types (but not arrays)
123
+ if (type.flags & 524288 /* ts.TypeFlags.Object */) {
124
+ return "object";
125
+ }
126
+ // Check for unions that aren't all strings/numbers
127
+ if (type.isUnion()) {
128
+ return "union";
129
+ }
130
+ return "unknown";
131
+ }
132
+ /**
133
+ * Gets the TypeScript type of a class property.
134
+ */
135
+ export function getPropertyType(node, services) {
136
+ try {
137
+ const checker = services.program.getTypeChecker();
138
+ const tsNode = services.esTreeNodeToTSNodeMap.get(node);
139
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- map.get() can return undefined
140
+ if (!tsNode)
141
+ return null;
142
+ // Get the type from the type annotation or initializer
143
+ const type = checker.getTypeAtLocation(tsNode);
144
+ return type;
145
+ }
146
+ catch {
147
+ return null;
148
+ }
149
+ }
150
+ /**
151
+ * Extracts string literal values from a union type.
152
+ * For type "a" | "b" | "c", returns ["a", "b", "c"].
153
+ */
154
+ export function getStringLiteralUnionValues(type, _checker) {
155
+ if (!type.isUnion()) {
156
+ // Single string literal
157
+ if (type.isStringLiteral()) {
158
+ return [type.value];
159
+ }
160
+ return null;
161
+ }
162
+ const values = [];
163
+ for (const t of type.types) {
164
+ if (t.isStringLiteral()) {
165
+ values.push(t.value);
166
+ }
167
+ else {
168
+ // Not all members are string literals
169
+ return null;
170
+ }
171
+ }
172
+ return values.length > 0 ? values : null;
173
+ }
174
+ /**
175
+ * Checks if a field is marked as optional (has `?` modifier).
176
+ */
177
+ export function isOptionalProperty(node) {
178
+ return node.optional;
179
+ }
180
+ /**
181
+ * Gets the type checker from parser services.
182
+ */
183
+ export function getTypeChecker(services) {
184
+ return services.program.getTypeChecker();
185
+ }
186
+ /**
187
+ * Converts a TypeScript type to a readable string.
188
+ */
189
+ export function typeToString(type, checker) {
190
+ return checker.typeToString(type);
191
+ }
192
+ //# sourceMappingURL=type-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type-utils.js","sourceRoot":"","sources":["../../src/utils/type-utils.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@formspec/eslint-plugin",
3
+ "version": "0.1.0-alpha.3",
4
+ "description": "ESLint rules for FormSpec decorator DSL type safety",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/eslint-plugin.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/eslint-plugin.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "dependencies": {
18
+ "@typescript-eslint/utils": "^8.0.0"
19
+ },
20
+ "peerDependencies": {
21
+ "eslint": "^9.0.0",
22
+ "typescript": "^5.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@typescript-eslint/rule-tester": "^8.0.0",
26
+ "vitest": "^3.0.0"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "license": "UNLICENSED",
32
+ "keywords": [
33
+ "eslint",
34
+ "eslint-plugin",
35
+ "formspec",
36
+ "decorators",
37
+ "typescript"
38
+ ],
39
+ "scripts": {
40
+ "build": "tsc",
41
+ "clean": "rm -rf dist temp",
42
+ "typecheck": "tsc --noEmit",
43
+ "test": "vitest run",
44
+ "api-extractor": "api-extractor run",
45
+ "api-extractor:local": "api-extractor run --local",
46
+ "api-documenter": "api-documenter markdown -i temp -o docs"
47
+ }
48
+ }