@webpieces/eslint-rules 0.0.1

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.
@@ -0,0 +1,275 @@
1
+ /**
2
+ * ESLint rule to enforce maximum file length
3
+ *
4
+ * Enforces a configurable maximum line count for files.
5
+ * Default: 700 lines
6
+ *
7
+ * Configuration:
8
+ * '@webpieces/max-file-lines': ['error', { max: 700 }]
9
+ */
10
+
11
+ import type { Rule } from 'eslint';
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import { toError } from '../toError';
15
+
16
+ interface FileLinesOptions {
17
+ max: number;
18
+ }
19
+
20
+ const FILE_DOC_CONTENT = `# AI Agent Instructions: File Too Long
21
+
22
+ **READ THIS FILE to fix files that are too long**
23
+
24
+ ## Core Principle
25
+ Files should contain a SINGLE COHESIVE UNIT.
26
+ - One class per file (Java convention)
27
+ - If class is too large, extract child responsibilities
28
+ - Use dependency injection to compose functionality
29
+
30
+ ## Command: Reduce File Size
31
+
32
+ ### Step 1: Check for Multiple Classes
33
+ If the file contains multiple classes, **SEPARATE each class into its own file**.
34
+
35
+ \`\`\`typescript
36
+ // BAD: UserController.ts (multiple classes)
37
+ export class UserController { /* ... */ }
38
+ export class UserValidator { /* ... */ }
39
+ export class UserNotifier { /* ... */ }
40
+
41
+ // GOOD: Three separate files
42
+ // UserController.ts
43
+ export class UserController { /* ... */ }
44
+
45
+ // UserValidator.ts
46
+ export class UserValidator { /* ... */ }
47
+
48
+ // UserNotifier.ts
49
+ export class UserNotifier { /* ... */ }
50
+ \`\`\`
51
+
52
+ ### Step 2: Extract Child Responsibilities (if single class is too large)
53
+
54
+ #### Pattern: Create New Service Class with Dependency Injection
55
+
56
+ \`\`\`typescript
57
+ // BAD: UserController.ts (800 lines, single class)
58
+ @provideSingleton()
59
+ @Controller()
60
+ export class UserController {
61
+ // 200 lines: CRUD operations
62
+ // 300 lines: validation logic
63
+ // 200 lines: notification logic
64
+ // 100 lines: analytics logic
65
+ }
66
+
67
+ // GOOD: Extract validation service
68
+ // 1. Create UserValidationService.ts
69
+ @provideSingleton()
70
+ export class UserValidationService {
71
+ validateUserData(data: UserData): ValidationResult {
72
+ // 300 lines of validation logic moved here
73
+ }
74
+
75
+ validateEmail(email: string): boolean { /* ... */ }
76
+ validatePassword(password: string): boolean { /* ... */ }
77
+ }
78
+
79
+ // 2. Inject into UserController.ts
80
+ @provideSingleton()
81
+ @Controller()
82
+ export class UserController {
83
+ constructor(
84
+ @inject(TYPES.UserValidationService)
85
+ private validator: UserValidationService
86
+ ) {}
87
+
88
+ async createUser(data: UserData): Promise<User> {
89
+ const validation = this.validator.validateUserData(data);
90
+ if (!validation.isValid) {
91
+ throw new ValidationError(validation.errors);
92
+ }
93
+ // ... rest of logic
94
+ }
95
+ }
96
+ \`\`\`
97
+
98
+ ## AI Agent Action Steps
99
+
100
+ 1. **ANALYZE** the file structure:
101
+ - Count classes (if >1, separate immediately)
102
+ - Identify logical responsibilities within single class
103
+
104
+ 2. **IDENTIFY** "child code" to extract:
105
+ - Validation logic -> ValidationService
106
+ - Notification logic -> NotificationService
107
+ - Data transformation -> TransformerService
108
+ - External API calls -> ApiService
109
+ - Business rules -> RulesEngine
110
+
111
+ 3. **CREATE** new service file(s):
112
+ - Start with temporary name: \`XXXX.ts\` or \`ChildService.ts\`
113
+ - Add \`@provideSingleton()\` decorator
114
+ - Move child methods to new class
115
+
116
+ 4. **UPDATE** dependency injection:
117
+ - Add to \`TYPES\` constants (if using symbol-based DI)
118
+ - Inject new service into original class constructor
119
+ - Replace direct method calls with \`this.serviceName.method()\`
120
+
121
+ 5. **RENAME** extracted file:
122
+ - Read the extracted code to understand its purpose
123
+ - Rename \`XXXX.ts\` to logical name (e.g., \`UserValidationService.ts\`)
124
+
125
+ 6. **VERIFY** file sizes:
126
+ - Original file should now be <700 lines
127
+ - Each extracted file should be <700 lines
128
+ - If still too large, extract more services
129
+
130
+ ## Examples of Child Responsibilities to Extract
131
+
132
+ | If File Contains | Extract To | Pattern |
133
+ |-----------------|------------|---------|
134
+ | Validation logic (200+ lines) | \`XValidator.ts\` or \`XValidationService.ts\` | Singleton service |
135
+ | Notification logic (150+ lines) | \`XNotifier.ts\` or \`XNotificationService.ts\` | Singleton service |
136
+ | Data transformation (200+ lines) | \`XTransformer.ts\` | Singleton service |
137
+ | External API calls (200+ lines) | \`XApiClient.ts\` | Singleton service |
138
+ | Complex business rules (300+ lines) | \`XRulesEngine.ts\` | Singleton service |
139
+ | Database queries (200+ lines) | \`XRepository.ts\` | Singleton service |
140
+
141
+ ## WebPieces Dependency Injection Pattern
142
+
143
+ \`\`\`typescript
144
+ // 1. Define service with @provideSingleton
145
+ import { provideSingleton } from '@webpieces/http-routing';
146
+
147
+ @provideSingleton()
148
+ export class MyService {
149
+ doSomething(): void { /* ... */ }
150
+ }
151
+
152
+ // 2. Inject into consumer
153
+ import { inject } from 'inversify';
154
+ import { TYPES } from './types';
155
+
156
+ @provideSingleton()
157
+ @Controller()
158
+ export class MyController {
159
+ constructor(
160
+ @inject(TYPES.MyService) private service: MyService
161
+ ) {}
162
+ }
163
+ \`\`\`
164
+
165
+ Remember: Find the "child code" and pull it down into a new class. Once moved, the code's purpose becomes clear, making it easy to rename to a logical name.
166
+ `;
167
+
168
+ // Module-level flag to prevent redundant file creation
169
+ let fileDocCreated = false;
170
+
171
+ function getWorkspaceRoot(context: Rule.RuleContext): string {
172
+ const filename = context.filename || context.getFilename();
173
+ let dir = path.dirname(filename);
174
+
175
+ // Walk up directory tree to find workspace root
176
+ while (dir !== path.dirname(dir)) {
177
+ const pkgPath = path.join(dir, 'package.json');
178
+ if (fs.existsSync(pkgPath)) {
179
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
180
+ try {
181
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
182
+ if (pkg.workspaces || pkg.name === 'webpieces-ts') {
183
+ return dir;
184
+ }
185
+ } catch (err: unknown) {
186
+ //const error = toError(err);
187
+ void err; // Continue searching if JSON parse fails
188
+ }
189
+ }
190
+ dir = path.dirname(dir);
191
+ }
192
+ return process.cwd(); // Fallback
193
+ }
194
+
195
+ function ensureDocFile(docPath: string, content: string): boolean {
196
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
197
+ try {
198
+ fs.mkdirSync(path.dirname(docPath), { recursive: true });
199
+ fs.writeFileSync(docPath, content, 'utf-8');
200
+ return true;
201
+ } catch (err: unknown) {
202
+ const error = toError(err);
203
+ console.warn(`[webpieces] Could not create doc file: ${docPath}`, error);
204
+ return false;
205
+ }
206
+ }
207
+
208
+ function ensureFileDoc(context: Rule.RuleContext): void {
209
+ if (fileDocCreated) return; // Performance: only create once per lint run
210
+
211
+ const workspaceRoot = getWorkspaceRoot(context);
212
+ const docPath = path.join(workspaceRoot, '.webpieces', 'instruct-ai', 'webpieces.filesize.md');
213
+
214
+ if (ensureDocFile(docPath, FILE_DOC_CONTENT)) {
215
+ fileDocCreated = true;
216
+ }
217
+ }
218
+
219
+ const rule: Rule.RuleModule = {
220
+ meta: {
221
+ type: 'suggestion',
222
+ docs: {
223
+ description: 'Enforce maximum file length',
224
+ category: 'Best Practices',
225
+ recommended: false,
226
+ url: 'https://github.com/deanhiller/webpieces-ts',
227
+ },
228
+ messages: {
229
+ tooLong:
230
+ 'AI Agent: READ .webpieces/instruct-ai/webpieces.filesize.md for fix instructions. File has {{actual}} lines (max: {{max}})',
231
+ },
232
+ fixable: undefined,
233
+ schema: [
234
+ {
235
+ type: 'object',
236
+ properties: {
237
+ max: {
238
+ type: 'integer',
239
+ minimum: 1,
240
+ },
241
+ },
242
+ additionalProperties: false,
243
+ },
244
+ ],
245
+ },
246
+
247
+ create(context: Rule.RuleContext): Rule.RuleListener {
248
+ const options = context.options[0] as FileLinesOptions | undefined;
249
+ const maxLines = options?.max ?? 700;
250
+
251
+ return {
252
+ // webpieces-disable no-any-unknown -- ESTree AST nodes require any for dynamic properties
253
+ Program(node: any): void {
254
+ ensureFileDoc(context);
255
+
256
+ const sourceCode = context.sourceCode || context.getSourceCode();
257
+ const lines = sourceCode.lines;
258
+ const lineCount = lines.length;
259
+
260
+ if (lineCount > maxLines) {
261
+ context.report({
262
+ node,
263
+ messageId: 'tooLong',
264
+ data: {
265
+ actual: String(lineCount),
266
+ max: String(maxLines),
267
+ },
268
+ });
269
+ }
270
+ },
271
+ };
272
+ },
273
+ };
274
+
275
+ export = rule;
@@ -0,0 +1,296 @@
1
+ /**
2
+ * ESLint rule to enforce maximum method length
3
+ *
4
+ * Enforces a configurable maximum line count for methods, functions, and arrow functions.
5
+ * Default: 70 lines
6
+ *
7
+ * Configuration:
8
+ * '@webpieces/max-method-lines': ['error', { max: 70 }]
9
+ */
10
+
11
+ import type { Rule } from 'eslint';
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import { toError } from '../toError';
15
+
16
+ interface MethodLinesOptions {
17
+ max: number;
18
+ }
19
+
20
+ // webpieces-disable no-any-unknown -- ESTree AST nodes require any for dynamic properties
21
+ interface FunctionNode {
22
+ type:
23
+ | 'FunctionDeclaration'
24
+ | 'FunctionExpression'
25
+ | 'ArrowFunctionExpression'
26
+ | 'MethodDefinition';
27
+ // webpieces-disable no-any-unknown -- ESTree AST dynamic body
28
+ body?: any;
29
+ loc?: {
30
+ start: { line: number };
31
+ end: { line: number };
32
+ };
33
+ key?: {
34
+ name?: string;
35
+ };
36
+ id?: {
37
+ name?: string;
38
+ };
39
+ // webpieces-disable no-any-unknown -- ESTree AST nodes require any for dynamic properties
40
+ [key: string]: any;
41
+ }
42
+
43
+ interface CheckerContext {
44
+ context: Rule.RuleContext;
45
+ maxLines: number;
46
+ }
47
+
48
+ const METHOD_DOC_CONTENT = `# AI Agent Instructions: Method Too Long
49
+
50
+ **READ THIS FILE to fix methods that are too long**
51
+
52
+ ## Core Principle
53
+ Every method should read like a TABLE OF CONTENTS of a book.
54
+ - Each method call is a "chapter"
55
+ - When you dive into a method, you find another table of contents
56
+ - Keeping methods under 70 lines is achievable with proper extraction
57
+
58
+ ## Command: Extract Code into Named Methods
59
+
60
+ ### Pattern 1: Extract Loop Bodies
61
+ \`\`\`typescript
62
+ // BAD: 50 lines embedded in loop
63
+ for (const order of orders) {
64
+ // 20 lines of validation logic
65
+ // 15 lines of processing logic
66
+ // 10 lines of notification logic
67
+ }
68
+
69
+ // GOOD: Extracted to named methods
70
+ for (const order of orders) {
71
+ validateOrder(order);
72
+ processOrderItems(order);
73
+ sendNotifications(order);
74
+ }
75
+ \`\`\`
76
+
77
+ ### Pattern 2: Try-Catch Wrapper for Exception Handling
78
+ \`\`\`typescript
79
+ // GOOD: Separates success path from error handling
80
+ async function handleRequest(req: Request): Promise<Response> {
81
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
82
+ try {
83
+ return await executeRequest(req);
84
+ } catch (err: unknown) {
85
+ const error = toError(err);
86
+ return createErrorResponse(error);
87
+ }
88
+ }
89
+ \`\`\`
90
+
91
+ ### Pattern 3: Sequential Method Calls (Table of Contents)
92
+ \`\`\`typescript
93
+ // GOOD: Self-documenting steps
94
+ function processOrder(order: Order): void {
95
+ validateOrderData(order);
96
+ calculateTotals(order);
97
+ applyDiscounts(order);
98
+ processPayment(order);
99
+ updateInventory(order);
100
+ sendConfirmation(order);
101
+ }
102
+ \`\`\`
103
+
104
+ ### Pattern 4: Separate Data Object Creation
105
+ \`\`\`typescript
106
+ // BAD: 15 lines of inline object creation
107
+ doSomething({ field1: ..., field2: ..., field3: ..., /* 15 more fields */ });
108
+
109
+ // GOOD: Extract to factory method
110
+ const request = createRequestObject(data);
111
+ doSomething(request);
112
+ \`\`\`
113
+
114
+ ### Pattern 5: Extract Inline Logic to Named Functions
115
+ \`\`\`typescript
116
+ // BAD: Complex inline logic
117
+ if (user.role === 'admin' && user.permissions.includes('write') && !user.suspended) {
118
+ // 30 lines of admin logic
119
+ }
120
+
121
+ // GOOD: Extract to named methods
122
+ if (isAdminWithWriteAccess(user)) {
123
+ performAdminOperation(user);
124
+ }
125
+ \`\`\`
126
+
127
+ ## AI Agent Action Steps
128
+
129
+ 1. **IDENTIFY** the long method in the error message
130
+ 2. **READ** the method to understand its logical sections
131
+ 3. **EXTRACT** logical units into separate methods with descriptive names
132
+ 4. **REPLACE** inline code with method calls
133
+ 5. **VERIFY** each extracted method is <70 lines
134
+ 6. **TEST** that functionality remains unchanged
135
+
136
+ ## Examples of "Logical Units" to Extract
137
+ - Validation logic -> \`validateX()\`
138
+ - Data transformation -> \`transformXToY()\`
139
+ - API calls -> \`fetchXFromApi()\`
140
+ - Object creation -> \`createX()\`
141
+ - Loop bodies -> \`processItem()\`
142
+ - Error handling -> \`handleXError()\`
143
+
144
+ Remember: Methods should read like a table of contents. Each line should be a "chapter title" (method call) that describes what happens, not how it happens.
145
+ `;
146
+
147
+ // Module-level flag to prevent redundant file creation
148
+ let methodDocCreated = false;
149
+
150
+ function getWorkspaceRoot(context: Rule.RuleContext): string {
151
+ const filename = context.filename || context.getFilename();
152
+ let dir = path.dirname(filename);
153
+
154
+ while (dir !== path.dirname(dir)) {
155
+ const pkgPath = path.join(dir, 'package.json');
156
+ if (fs.existsSync(pkgPath)) {
157
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
158
+ try {
159
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
160
+ if (pkg.workspaces || pkg.name === 'webpieces-ts') {
161
+ return dir;
162
+ }
163
+ } catch (err: unknown) {
164
+ //const error = toError(err);
165
+ void err;
166
+ }
167
+ }
168
+ dir = path.dirname(dir);
169
+ }
170
+ return process.cwd();
171
+ }
172
+
173
+ function ensureDocFile(docPath: string, content: string): boolean {
174
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
175
+ try {
176
+ fs.mkdirSync(path.dirname(docPath), { recursive: true });
177
+ fs.writeFileSync(docPath, content, 'utf-8');
178
+ return true;
179
+ } catch (err: unknown) {
180
+ const error = toError(err);
181
+ console.warn(`[webpieces] Could not create doc file: ${docPath}`, error);
182
+ return false;
183
+ }
184
+ }
185
+
186
+ function ensureMethodDoc(context: Rule.RuleContext): void {
187
+ const workspaceRoot = getWorkspaceRoot(context);
188
+ const docPath = path.join(workspaceRoot, '.webpieces', 'instruct-ai', 'webpieces.methods.md');
189
+
190
+ // Check if file exists AND flag is true - if both, skip
191
+ if (methodDocCreated && fs.existsSync(docPath)) return;
192
+
193
+ if (ensureDocFile(docPath, METHOD_DOC_CONTENT)) {
194
+ methodDocCreated = true;
195
+ }
196
+ }
197
+
198
+ function getFunctionName(funcNode: FunctionNode): string {
199
+ if (funcNode.type === 'FunctionDeclaration' && funcNode.id?.name) {
200
+ return funcNode.id.name;
201
+ }
202
+ if (funcNode.type === 'FunctionExpression' && funcNode.id?.name) {
203
+ return funcNode.id.name;
204
+ }
205
+ return 'anonymous';
206
+ }
207
+
208
+ // webpieces-disable no-any-unknown -- ESTree AST nodes require any for dynamic properties
209
+ function reportTooLong(ctx: CheckerContext, node: any, name: string, lineCount: number): void {
210
+ ctx.context.report({
211
+ node,
212
+ messageId: 'tooLong',
213
+ data: {
214
+ name,
215
+ actual: String(lineCount),
216
+ max: String(ctx.maxLines),
217
+ },
218
+ });
219
+ }
220
+
221
+ // webpieces-disable no-any-unknown -- ESTree AST nodes require any for dynamic properties
222
+ function checkFunctionNode(ctx: CheckerContext, node: any): void {
223
+ ensureMethodDoc(ctx.context);
224
+ const funcNode = node as FunctionNode;
225
+
226
+ // Skip function expressions inside method definitions
227
+ if (funcNode.type === 'FunctionExpression' && funcNode['parent']?.type === 'MethodDefinition') {
228
+ return;
229
+ }
230
+
231
+ if (!funcNode.loc || !funcNode.body) return;
232
+
233
+ const name = getFunctionName(funcNode);
234
+ const lineCount = funcNode.loc.end.line - funcNode.loc.start.line + 1;
235
+
236
+ if (lineCount > ctx.maxLines) {
237
+ reportTooLong(ctx, funcNode, name, lineCount);
238
+ }
239
+ }
240
+
241
+ // webpieces-disable no-any-unknown -- ESTree AST nodes require any for dynamic properties
242
+ function checkMethodNode(ctx: CheckerContext, node: any): void {
243
+ ensureMethodDoc(ctx.context);
244
+
245
+ if (!node.loc || !node.value) return;
246
+
247
+ const name = node.key?.name || 'anonymous';
248
+ const lineCount = node.loc.end.line - node.loc.start.line + 1;
249
+
250
+ if (lineCount > ctx.maxLines) {
251
+ reportTooLong(ctx, node, name, lineCount);
252
+ }
253
+ }
254
+
255
+ const rule: Rule.RuleModule = {
256
+ meta: {
257
+ type: 'suggestion',
258
+ docs: {
259
+ description: 'Enforce maximum method length',
260
+ category: 'Best Practices',
261
+ recommended: false,
262
+ url: 'https://github.com/deanhiller/webpieces-ts',
263
+ },
264
+ messages: {
265
+ tooLong:
266
+ 'AI Agent: READ .webpieces/instruct-ai/webpieces.methods.md for fix instructions. Method "{{name}}" has {{actual}} lines (max: {{max}})',
267
+ },
268
+ fixable: undefined,
269
+ schema: [
270
+ {
271
+ type: 'object',
272
+ properties: {
273
+ max: {
274
+ type: 'integer',
275
+ minimum: 1,
276
+ },
277
+ },
278
+ additionalProperties: false,
279
+ },
280
+ ],
281
+ },
282
+
283
+ create(context: Rule.RuleContext): Rule.RuleListener {
284
+ const options = context.options[0] as MethodLinesOptions | undefined;
285
+ const ctx: CheckerContext = { context, maxLines: options?.max ?? 70 };
286
+
287
+ return {
288
+ FunctionDeclaration: (node) => checkFunctionNode(ctx, node),
289
+ FunctionExpression: (node) => checkFunctionNode(ctx, node),
290
+ ArrowFunctionExpression: (node) => checkFunctionNode(ctx, node),
291
+ MethodDefinition: (node) => checkMethodNode(ctx, node),
292
+ };
293
+ },
294
+ };
295
+
296
+ export = rule;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * ESLint rule: no-json-property-primitive-type
3
+ *
4
+ * Bans @JsonProperty({ type: String }), @JsonProperty({ type: Number }),
5
+ * and @JsonProperty({ type: Boolean }).
6
+ *
7
+ * These pass the TypeScript build but break production deserialization.
8
+ * The typescript-json-serializer `type` option expects class constructors,
9
+ * not JavaScript primitive constructors.
10
+ *
11
+ * Correct usage:
12
+ * @JsonProperty() - for primitive arrays (string[], number[], boolean[])
13
+ * @JsonProperty({ type: MyDtoClass }) - for class types only
14
+ */
15
+
16
+ import type { Rule } from 'eslint';
17
+
18
+ // webpieces-disable no-any-unknown -- ESTree AST node interfaces require any for dynamic properties
19
+ interface CallExpressionNode {
20
+ type: 'CallExpression';
21
+ // webpieces-disable no-any-unknown -- ESTree AST dynamic callee
22
+ callee: { name?: string; [key: string]: any };
23
+ // webpieces-disable no-any-unknown -- ESTree AST dynamic arguments array
24
+ arguments: any[];
25
+ // webpieces-disable no-any-unknown -- ESTree AST index signature
26
+ [key: string]: any;
27
+ }
28
+
29
+ interface ObjectExpressionNode {
30
+ type: 'ObjectExpression';
31
+ properties: PropertyNode[];
32
+ }
33
+
34
+ interface PropertyNode {
35
+ key?: { name?: string };
36
+ value?: { type?: string; name?: string };
37
+ // webpieces-disable no-any-unknown -- ESTree AST index signature
38
+ [key: string]: any;
39
+ }
40
+
41
+ const BANNED_PRIMITIVES = ['String', 'Number', 'Boolean'];
42
+
43
+ const rule: Rule.RuleModule = {
44
+ meta: {
45
+ type: 'problem',
46
+ docs: {
47
+ description:
48
+ 'Ban @JsonProperty({ type: String/Number/Boolean }) — breaks production deserialization',
49
+ },
50
+ messages: {
51
+ noPrimitiveType:
52
+ '@JsonProperty({ type: {{ primitive }} }) breaks production deserialization. ' +
53
+ 'For primitive arrays (string[], number[], boolean[]), use @JsonProperty() with ' +
54
+ 'no type parameter. The type option is only for class types: ' +
55
+ '@JsonProperty({ type: MyDtoClass }).',
56
+ },
57
+ schema: [],
58
+ },
59
+ create(context: Rule.RuleContext): Rule.RuleListener {
60
+ return {
61
+ CallExpression(node: CallExpressionNode): void {
62
+ if (node.callee.name !== 'JsonProperty') return;
63
+ const arg = node.arguments[0];
64
+ if (!arg || arg.type !== 'ObjectExpression') return;
65
+ const objArg = arg as ObjectExpressionNode;
66
+ for (const prop of objArg.properties) {
67
+ if (
68
+ prop.key?.name === 'type' &&
69
+ prop.value?.type === 'Identifier' &&
70
+ BANNED_PRIMITIVES.includes(prop.value.name!)
71
+ ) {
72
+ context.report({
73
+ // webpieces-disable no-any-unknown -- ESTree AST cast for ESLint report
74
+ node: prop as unknown as Rule.Node,
75
+ messageId: 'noPrimitiveType',
76
+ data: { primitive: prop.value.name! },
77
+ });
78
+ }
79
+ }
80
+ },
81
+ };
82
+ },
83
+ };
84
+
85
+ export = rule;