@webpieces/eslint-rules 0.0.1 → 0.2.114

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 (47) hide show
  1. package/package.json +3 -2
  2. package/src/index.d.ts +29 -0
  3. package/src/index.js +39 -0
  4. package/src/index.js.map +1 -0
  5. package/src/rules/catch-error-pattern.d.ts +11 -0
  6. package/src/rules/{catch-error-pattern.ts → catch-error-pattern.js} +30 -142
  7. package/src/rules/catch-error-pattern.js.map +1 -0
  8. package/src/rules/enforce-architecture.d.ts +15 -0
  9. package/src/rules/{enforce-architecture.ts → enforce-architecture.js} +61 -128
  10. package/src/rules/enforce-architecture.js.map +1 -0
  11. package/src/rules/max-file-lines.d.ts +12 -0
  12. package/src/rules/{max-file-lines.ts → max-file-lines.js} +22 -37
  13. package/src/rules/max-file-lines.js.map +1 -0
  14. package/src/rules/max-method-lines.d.ts +12 -0
  15. package/src/rules/{max-method-lines.ts → max-method-lines.js} +31 -81
  16. package/src/rules/max-method-lines.js.map +1 -0
  17. package/src/rules/no-json-property-primitive-type.d.ts +17 -0
  18. package/src/rules/no-json-property-primitive-type.js +57 -0
  19. package/src/rules/no-json-property-primitive-type.js.map +1 -0
  20. package/src/rules/no-mat-cell-def.d.ts +15 -0
  21. package/src/rules/{no-mat-cell-def.ts → no-mat-cell-def.js} +8 -21
  22. package/src/rules/no-mat-cell-def.js.map +1 -0
  23. package/src/rules/no-unmanaged-exceptions.d.ts +22 -0
  24. package/src/rules/{no-unmanaged-exceptions.ts → no-unmanaged-exceptions.js} +27 -52
  25. package/src/rules/no-unmanaged-exceptions.js.map +1 -0
  26. package/src/rules/require-typed-template.d.ts +17 -0
  27. package/src/rules/{require-typed-template.ts → require-typed-template.js} +11 -31
  28. package/src/rules/require-typed-template.js.map +1 -0
  29. package/src/toError.d.ts +5 -0
  30. package/src/{toError.ts → toError.js} +7 -6
  31. package/src/toError.js.map +1 -0
  32. package/.webpieces/instruct-ai/webpieces.exceptions.md +0 -5
  33. package/.webpieces/instruct-ai/webpieces.filesize.md +0 -146
  34. package/.webpieces/instruct-ai/webpieces.methods.md +0 -97
  35. package/LICENSE +0 -373
  36. package/jest.config.ts +0 -16
  37. package/project.json +0 -22
  38. package/src/__tests__/catch-error-pattern.test.ts +0 -374
  39. package/src/__tests__/max-file-lines.test.ts +0 -207
  40. package/src/__tests__/max-method-lines.test.ts +0 -258
  41. package/src/__tests__/no-unmanaged-exceptions.test.ts +0 -359
  42. package/src/index.ts +0 -38
  43. package/src/rules/no-json-property-primitive-type.ts +0 -85
  44. package/tmp/webpieces/webpieces.exceptions.md +0 -5
  45. package/tsconfig.json +0 -22
  46. package/tsconfig.lib.json +0 -10
  47. package/tsconfig.spec.json +0 -14
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /**
2
3
  * ESLint rule to discourage try-catch blocks outside test files
3
4
  *
@@ -17,45 +18,32 @@
17
18
  * - Batch processing where partial failure is expected
18
19
  * - Resource cleanup (with approval)
19
20
  */
20
-
21
- import type { Rule } from 'eslint';
22
- import * as fs from 'fs';
23
- import * as path from 'path';
24
- import { toError } from '../toError';
25
-
26
- // webpieces-disable no-any-unknown -- ESTree AST node interface
27
- interface TryStatementNode {
28
- handler?: unknown;
29
- }
30
-
21
+ const tslib_1 = require("tslib");
22
+ const fs = tslib_1.__importStar(require("fs"));
23
+ const path = tslib_1.__importStar(require("path"));
31
24
  /**
32
25
  * Determines if a file is a test file based on naming conventions
33
26
  * Test files are auto-allowed to use try-catch blocks
34
27
  */
35
- function isTestFile(filename: string): boolean {
28
+ function isTestFile(filename) {
36
29
  const normalizedPath = filename.toLowerCase();
37
-
38
30
  // Check file extensions
39
31
  if (normalizedPath.endsWith('.test.ts') || normalizedPath.endsWith('.spec.ts')) {
40
32
  return true;
41
33
  }
42
-
43
34
  // Check directory names (cross-platform)
44
35
  if (normalizedPath.includes('/__tests__/') || normalizedPath.includes('\\__tests__\\')) {
45
36
  return true;
46
37
  }
47
-
48
38
  return false;
49
39
  }
50
-
51
40
  /**
52
41
  * Finds the workspace root by walking up the directory tree
53
42
  * Looks for package.json with workspaces or name === 'webpieces-ts'
54
43
  */
55
- function getWorkspaceRoot(context: Rule.RuleContext): string {
44
+ function getWorkspaceRoot(context) {
56
45
  const filename = context.filename || context.getFilename();
57
46
  let dir = path.dirname(filename);
58
-
59
47
  // Walk up directory tree
60
48
  for (let i = 0; i < 10; i++) {
61
49
  const pkgPath = path.join(dir, 'package.json');
@@ -67,46 +55,43 @@ function getWorkspaceRoot(context: Rule.RuleContext): string {
67
55
  if (pkg.workspaces || pkg.name === 'webpieces-ts') {
68
56
  return dir;
69
57
  }
70
- } catch (err: unknown) {
58
+ }
59
+ catch (err) {
71
60
  //const error = toError(err);
72
61
  void err; // Invalid JSON, keep searching
73
62
  }
74
63
  }
75
-
76
64
  const parentDir = path.dirname(dir);
77
- if (parentDir === dir) break; // Reached filesystem root
65
+ if (parentDir === dir)
66
+ break; // Reached filesystem root
78
67
  dir = parentDir;
79
68
  }
80
-
81
69
  // Fallback: return current directory
82
70
  return process.cwd();
83
71
  }
84
-
85
72
  /**
86
73
  * Ensures a documentation file exists at the given path
87
74
  * Creates parent directories if needed
88
75
  */
89
- function ensureDocFile(docPath: string, content: string): boolean {
76
+ function ensureDocFile(docPath, content) {
90
77
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
91
78
  try {
92
79
  const dir = path.dirname(docPath);
93
80
  if (!fs.existsSync(dir)) {
94
81
  fs.mkdirSync(dir, { recursive: true });
95
82
  }
96
-
97
83
  // Only write if file doesn't exist or is empty
98
84
  if (!fs.existsSync(docPath) || fs.readFileSync(docPath, 'utf-8').trim() === '') {
99
85
  fs.writeFileSync(docPath, content, 'utf-8');
100
86
  }
101
-
102
87
  return true;
103
- } catch (err: unknown) {
88
+ }
89
+ catch (err) {
104
90
  //const error = toError(err);
105
91
  void err; // Silently fail - don't break linting if file creation fails
106
92
  return false;
107
93
  }
108
94
  }
109
-
110
95
  /**
111
96
  * Ensures the exception documentation markdown file exists
112
97
  * Only creates file once per lint run using module-level flag
@@ -114,39 +99,34 @@ function ensureDocFile(docPath: string, content: string): boolean {
114
99
  * Reads from the template file packaged with @webpieces/webpieces-rules
115
100
  * and copies it to .webpieces/instruct-ai/ for AI agents to read.
116
101
  */
117
- function ensureExceptionDoc(context: Rule.RuleContext): void {
118
- if (exceptionDocCreated) return;
119
-
102
+ function ensureExceptionDoc(context) {
103
+ if (exceptionDocCreated)
104
+ return;
120
105
  const workspaceRoot = getWorkspaceRoot(context);
121
106
  const docPath = path.join(workspaceRoot, '.webpieces', 'instruct-ai', 'webpieces.exceptions.md');
122
-
123
107
  // Read from the template file packaged with the npm module
124
108
  // Path: from eslint-plugin/rules/ -> ../../templates/
125
109
  const templatePath = path.join(__dirname, '..', '..', 'templates', 'webpieces.exceptions.md');
126
-
127
- let content: string;
110
+ let content;
128
111
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
129
112
  try {
130
113
  content = fs.readFileSync(templatePath, 'utf-8');
131
- } catch (err: unknown) {
114
+ }
115
+ catch (err) {
132
116
  //const error = toError(err);
133
117
  void err;
134
118
  // Fallback message if template not found (shouldn't happen in published package)
135
119
  content = `# Exception Documentation Not Found\n\nTemplate file not found at: ${templatePath}\n\nPlease ensure @webpieces/webpieces-rules is properly installed.`;
136
120
  }
137
-
138
121
  if (ensureDocFile(docPath, content)) {
139
122
  exceptionDocCreated = true;
140
123
  }
141
124
  }
142
-
143
125
  // Module-level flag to prevent redundant markdown file creation
144
126
  let exceptionDocCreated = false;
145
-
146
127
  // NOTE: Documentation content moved to templates/webpieces.exceptions.md
147
128
  // The ensureExceptionDoc function reads from that file at runtime.
148
-
149
- const rule: Rule.RuleModule = {
129
+ const rule = {
150
130
  meta: {
151
131
  type: 'problem',
152
132
  docs: {
@@ -156,39 +136,34 @@ const rule: Rule.RuleModule = {
156
136
  url: 'https://github.com/deanhiller/webpieces-ts/blob/main/CLAUDE.md#exception-handling-philosophy',
157
137
  },
158
138
  messages: {
159
- noUnmanagedExceptions:
160
- 'AI Agent: READ .webpieces/instruct-ai/webpieces.exceptions.md for context. Try-catch blocks are discouraged - use global error handlers instead. Only allowed in test files or with eslint-disable comment.',
139
+ noUnmanagedExceptions: 'AI Agent: READ .webpieces/instruct-ai/webpieces.exceptions.md for context. Try-catch blocks are discouraged - use global error handlers instead. Only allowed in test files or with eslint-disable comment.',
161
140
  },
162
141
  fixable: undefined,
163
142
  schema: [],
164
143
  },
165
-
166
- create(context: Rule.RuleContext): Rule.RuleListener {
144
+ create(context) {
167
145
  return {
168
146
  // webpieces-disable no-any-unknown -- ESLint visitor callback parameter type
169
- TryStatement(node: unknown): void {
147
+ TryStatement(node) {
170
148
  // Skip try..finally blocks (no catch handler, no exception handling)
171
149
  // webpieces-disable no-any-unknown -- ESTree AST node type assertion
172
- if (!(node as TryStatementNode).handler) {
150
+ if (!node.handler) {
173
151
  return;
174
152
  }
175
-
176
153
  // Auto-allow in test files
177
154
  const filename = context.filename || context.getFilename();
178
155
  if (isTestFile(filename)) {
179
156
  return;
180
157
  }
181
-
182
158
  // Has catch block outside test file - report violation
183
159
  ensureExceptionDoc(context);
184
160
  context.report({
185
- node: node as Rule.Node,
161
+ node: node,
186
162
  messageId: 'noUnmanagedExceptions',
187
163
  });
188
164
  },
189
165
  };
190
166
  },
191
167
  };
192
-
193
- export = rule;
194
-
168
+ module.exports = rule;
169
+ //# sourceMappingURL=no-unmanaged-exceptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unmanaged-exceptions.js","sourceRoot":"","sources":["../../../../../../packages/tooling/eslint-rules/src/rules/no-unmanaged-exceptions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESLint rule to discourage try-catch blocks outside test files\n *\n * Works alongside catch-error-pattern rule:\n * - catch-error-pattern: Enforces HOW to handle exceptions (with toError())\n * - no-unmanaged-exceptions: Enforces WHERE try-catch is allowed (tests only by default)\n *\n * Philosophy: Exceptions should bubble to global error handlers where they are logged\n * with traceId and stored for debugging via /debugLocal and /debugCloud endpoints.\n * Local try-catch blocks break this architecture and create blind spots in production.\n *\n * Auto-allowed in:\n * - Test files (.test.ts, .spec.ts, __tests__/)\n *\n * Requires eslint-disable comment in:\n * - Retry loops with exponential backoff\n * - Batch processing where partial failure is expected\n * - Resource cleanup (with approval)\n */\n\nimport type { Rule } from 'eslint';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { toError } from '../toError';\n\n// webpieces-disable no-any-unknown -- ESTree AST node interface\ninterface TryStatementNode {\n handler?: unknown;\n}\n\n/**\n * Determines if a file is a test file based on naming conventions\n * Test files are auto-allowed to use try-catch blocks\n */\nfunction isTestFile(filename: string): boolean {\n const normalizedPath = filename.toLowerCase();\n\n // Check file extensions\n if (normalizedPath.endsWith('.test.ts') || normalizedPath.endsWith('.spec.ts')) {\n return true;\n }\n\n // Check directory names (cross-platform)\n if (normalizedPath.includes('/__tests__/') || normalizedPath.includes('\\\\__tests__\\\\')) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Finds the workspace root by walking up the directory tree\n * Looks for package.json with workspaces or name === 'webpieces-ts'\n */\nfunction getWorkspaceRoot(context: Rule.RuleContext): string {\n const filename = context.filename || context.getFilename();\n let dir = path.dirname(filename);\n\n // Walk up directory tree\n for (let i = 0; i < 10; i++) {\n const pkgPath = path.join(dir, 'package.json');\n if (fs.existsSync(pkgPath)) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\n // Check if this is the root workspace\n if (pkg.workspaces || pkg.name === 'webpieces-ts') {\n return dir;\n }\n } catch (err: unknown) {\n //const error = toError(err);\n void err; // Invalid JSON, keep searching\n }\n }\n\n const parentDir = path.dirname(dir);\n if (parentDir === dir) break; // Reached filesystem root\n dir = parentDir;\n }\n\n // Fallback: return current directory\n return process.cwd();\n}\n\n/**\n * Ensures a documentation file exists at the given path\n * Creates parent directories if needed\n */\nfunction ensureDocFile(docPath: string, content: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const dir = path.dirname(docPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n // Only write if file doesn't exist or is empty\n if (!fs.existsSync(docPath) || fs.readFileSync(docPath, 'utf-8').trim() === '') {\n fs.writeFileSync(docPath, content, 'utf-8');\n }\n\n return true;\n } catch (err: unknown) {\n //const error = toError(err);\n void err; // Silently fail - don't break linting if file creation fails\n return false;\n }\n}\n\n/**\n * Ensures the exception documentation markdown file exists\n * Only creates file once per lint run using module-level flag\n *\n * Reads from the template file packaged with @webpieces/webpieces-rules\n * and copies it to .webpieces/instruct-ai/ for AI agents to read.\n */\nfunction ensureExceptionDoc(context: Rule.RuleContext): void {\n if (exceptionDocCreated) return;\n\n const workspaceRoot = getWorkspaceRoot(context);\n const docPath = path.join(workspaceRoot, '.webpieces', 'instruct-ai', 'webpieces.exceptions.md');\n\n // Read from the template file packaged with the npm module\n // Path: from eslint-plugin/rules/ -> ../../templates/\n const templatePath = path.join(__dirname, '..', '..', 'templates', 'webpieces.exceptions.md');\n\n let content: string;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n content = fs.readFileSync(templatePath, 'utf-8');\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n // Fallback message if template not found (shouldn't happen in published package)\n content = `# Exception Documentation Not Found\\n\\nTemplate file not found at: ${templatePath}\\n\\nPlease ensure @webpieces/webpieces-rules is properly installed.`;\n }\n\n if (ensureDocFile(docPath, content)) {\n exceptionDocCreated = true;\n }\n}\n\n// Module-level flag to prevent redundant markdown file creation\nlet exceptionDocCreated = false;\n\n// NOTE: Documentation content moved to templates/webpieces.exceptions.md\n// The ensureExceptionDoc function reads from that file at runtime.\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Discourage try-catch blocks outside test files - use global error handlers',\n category: 'Best Practices',\n recommended: true,\n url: 'https://github.com/deanhiller/webpieces-ts/blob/main/CLAUDE.md#exception-handling-philosophy',\n },\n messages: {\n noUnmanagedExceptions:\n 'AI Agent: READ .webpieces/instruct-ai/webpieces.exceptions.md for context. Try-catch blocks are discouraged - use global error handlers instead. Only allowed in test files or with eslint-disable comment.',\n },\n fixable: undefined,\n schema: [],\n },\n\n create(context: Rule.RuleContext): Rule.RuleListener {\n return {\n // webpieces-disable no-any-unknown -- ESLint visitor callback parameter type\n TryStatement(node: unknown): void {\n // Skip try..finally blocks (no catch handler, no exception handling)\n // webpieces-disable no-any-unknown -- ESTree AST node type assertion\n if (!(node as TryStatementNode).handler) {\n return;\n }\n\n // Auto-allow in test files\n const filename = context.filename || context.getFilename();\n if (isTestFile(filename)) {\n return;\n }\n\n // Has catch block outside test file - report violation\n ensureExceptionDoc(context);\n context.report({\n node: node as Rule.Node,\n messageId: 'noUnmanagedExceptions',\n });\n },\n };\n },\n};\n\nexport = rule;\n\n"]}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * ESLint rule: require-typed-template
3
+ *
4
+ * Enforces that every <ng-template> with let- variables also has [templateClassType]
5
+ * to preserve type safety via TypedTemplateOutletDirective.
6
+ *
7
+ * Works with @angular-eslint/template-parser AST where:
8
+ * - ng-template variables (let-xxx) appear in node.variables[]
9
+ * - bound inputs ([templateClassType]) appear in node.inputs[]
10
+ * - static attributes appear in node.attributes[]
11
+ *
12
+ * NOTE: This rule only works when files are parsed with @angular-eslint/template-parser.
13
+ * It is intended for Angular HTML template files (**.html).
14
+ */
15
+ import type { Rule } from 'eslint';
16
+ declare const rule: Rule.RuleModule;
17
+ export = rule;
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /**
2
3
  * ESLint rule: require-typed-template
3
4
  *
@@ -12,31 +13,14 @@
12
13
  * NOTE: This rule only works when files are parsed with @angular-eslint/template-parser.
13
14
  * It is intended for Angular HTML template files (**.html).
14
15
  */
15
-
16
- import type { Rule } from 'eslint';
17
-
18
- // webpieces-disable no-any-unknown -- Angular template AST node interfaces
19
- // These interfaces represent the Template node shape from @angular-eslint/template-parser.
20
- // We define them inline since the parser is not a dependency of this plugin.
21
- interface AngularTemplateNode {
22
- tagName?: string;
23
- variables?: Array<{ name: string }>;
24
- inputs?: Array<{ name: string }>;
25
- attributes?: Array<{ name: string }>;
26
- // webpieces-disable no-any-unknown -- ESTree AST index signature
27
- [key: string]: any;
28
- }
29
-
30
- const rule: Rule.RuleModule = {
16
+ const rule = {
31
17
  meta: {
32
18
  type: 'problem',
33
19
  docs: {
34
- description:
35
- 'Require [templateClassType] on ng-template elements that use let- variables',
20
+ description: 'Require [templateClassType] on ng-template elements that use let- variables',
36
21
  },
37
22
  messages: {
38
- missingTypedTemplate:
39
- 'ng-template with let- variables must include ' +
23
+ missingTypedTemplate: 'ng-template with let- variables must include ' +
40
24
  '[templateClassType]="YourDtoClass" to preserve type safety. ' +
41
25
  'Fix: (1) Add [templateClassType]="YourDtoClass" to this ng-template, ' +
42
26
  '(2) Add TypedTemplateOutletDirective to component imports array, ' +
@@ -45,30 +29,26 @@ const rule: Rule.RuleModule = {
45
29
  },
46
30
  schema: [],
47
31
  },
48
- create(context: Rule.RuleContext): Rule.RuleListener {
32
+ create(context) {
49
33
  return {
50
- Template(node: AngularTemplateNode): void {
34
+ Template(node) {
51
35
  // Only match explicit <ng-template>, not desugared structural directives
52
36
  // (*ngFor, *ngIf, etc.) which also produce Template AST nodes
53
37
  if (node.tagName !== 'ng-template') {
54
38
  return;
55
39
  }
56
-
57
40
  const hasLetVariables = node.variables && node.variables.length > 0;
58
41
  if (!hasLetVariables) {
59
42
  return;
60
43
  }
61
-
62
- const hasTemplateClassType =
63
- (node.inputs &&
64
- node.inputs.some((input) => input.name === 'templateClassType')) ||
44
+ const hasTemplateClassType = (node.inputs &&
45
+ node.inputs.some((input) => input.name === 'templateClassType')) ||
65
46
  (node.attributes &&
66
47
  node.attributes.some((attr) => attr.name === 'templateClassType'));
67
-
68
48
  if (!hasTemplateClassType) {
69
49
  context.report({
70
50
  // webpieces-disable no-any-unknown -- ESTree AST cast for ESLint report
71
- node: node as unknown as Rule.Node,
51
+ node: node,
72
52
  messageId: 'missingTypedTemplate',
73
53
  });
74
54
  }
@@ -76,5 +56,5 @@ const rule: Rule.RuleModule = {
76
56
  };
77
57
  },
78
58
  };
79
-
80
- export = rule;
59
+ module.exports = rule;
60
+ //# sourceMappingURL=require-typed-template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-typed-template.js","sourceRoot":"","sources":["../../../../../../packages/tooling/eslint-rules/src/rules/require-typed-template.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;AAgBH,MAAM,IAAI,GAAoB;IAC1B,IAAI,EAAE;QACF,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACF,WAAW,EACP,6EAA6E;SACpF;QACD,QAAQ,EAAE;YACN,oBAAoB,EAChB,+CAA+C;gBAC/C,8DAA8D;gBAC9D,uEAAuE;gBACvE,mEAAmE;gBACnE,4EAA4E;gBAC5E,8CAA8C;SACrD;QACD,MAAM,EAAE,EAAE;KACb;IACD,MAAM,CAAC,OAAyB;QAC5B,OAAO;YACH,QAAQ,CAAC,IAAyB;gBAC9B,yEAAyE;gBACzE,8DAA8D;gBAC9D,IAAI,IAAI,CAAC,OAAO,KAAK,aAAa,EAAE,CAAC;oBACjC,OAAO;gBACX,CAAC;gBAED,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;gBACpE,IAAI,CAAC,eAAe,EAAE,CAAC;oBACnB,OAAO;gBACX,CAAC;gBAED,MAAM,oBAAoB,GACtB,CAAC,IAAI,CAAC,MAAM;oBACR,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,mBAAmB,CAAC,CAAC;oBACpE,CAAC,IAAI,CAAC,UAAU;wBACZ,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,mBAAmB,CAAC,CAAC,CAAC;gBAE3E,IAAI,CAAC,oBAAoB,EAAE,CAAC;oBACxB,OAAO,CAAC,MAAM,CAAC;wBACX,wEAAwE;wBACxE,IAAI,EAAE,IAA4B;wBAClC,SAAS,EAAE,sBAAsB;qBACpC,CAAC,CAAC;gBACP,CAAC;YACL,CAAC;SACJ,CAAC;IACN,CAAC;CACJ,CAAC;AAEF,iBAAS,IAAI,CAAC","sourcesContent":["/**\n * ESLint rule: require-typed-template\n *\n * Enforces that every <ng-template> with let- variables also has [templateClassType]\n * to preserve type safety via TypedTemplateOutletDirective.\n *\n * Works with @angular-eslint/template-parser AST where:\n * - ng-template variables (let-xxx) appear in node.variables[]\n * - bound inputs ([templateClassType]) appear in node.inputs[]\n * - static attributes appear in node.attributes[]\n *\n * NOTE: This rule only works when files are parsed with @angular-eslint/template-parser.\n * It is intended for Angular HTML template files (**.html).\n */\n\nimport type { Rule } from 'eslint';\n\n// webpieces-disable no-any-unknown -- Angular template AST node interfaces\n// These interfaces represent the Template node shape from @angular-eslint/template-parser.\n// We define them inline since the parser is not a dependency of this plugin.\ninterface AngularTemplateNode {\n tagName?: string;\n variables?: Array<{ name: string }>;\n inputs?: Array<{ name: string }>;\n attributes?: Array<{ name: string }>;\n // webpieces-disable no-any-unknown -- ESTree AST index signature\n [key: string]: any;\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require [templateClassType] on ng-template elements that use let- variables',\n },\n messages: {\n missingTypedTemplate:\n 'ng-template with let- variables must include ' +\n '[templateClassType]=\"YourDtoClass\" to preserve type safety. ' +\n 'Fix: (1) Add [templateClassType]=\"YourDtoClass\" to this ng-template, ' +\n '(2) Add TypedTemplateOutletDirective to component imports array, ' +\n '(3) Expose the DTO class: protected readonly YourDtoClass = YourDtoClass. ' +\n 'See @fuse/directives/typed-template-outlet/.',\n },\n schema: [],\n },\n create(context: Rule.RuleContext): Rule.RuleListener {\n return {\n Template(node: AngularTemplateNode): void {\n // Only match explicit <ng-template>, not desugared structural directives\n // (*ngFor, *ngIf, etc.) which also produce Template AST nodes\n if (node.tagName !== 'ng-template') {\n return;\n }\n\n const hasLetVariables = node.variables && node.variables.length > 0;\n if (!hasLetVariables) {\n return;\n }\n\n const hasTemplateClassType =\n (node.inputs &&\n node.inputs.some((input) => input.name === 'templateClassType')) ||\n (node.attributes &&\n node.attributes.some((attr) => attr.name === 'templateClassType'));\n\n if (!hasTemplateClassType) {\n context.report({\n // webpieces-disable no-any-unknown -- ESTree AST cast for ESLint report\n node: node as unknown as Rule.Node,\n messageId: 'missingTypedTemplate',\n });\n }\n },\n };\n },\n};\n\nexport = rule;\n"]}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Lightweight duplicate of @webpieces/core-util toError for use in eslint-plugin.
3
+ * eslint-plugin is Level 0 and cannot depend on core-util (also Level 0).
4
+ */
5
+ export declare function toError(err: unknown): Error;
@@ -1,17 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toError = toError;
1
4
  /**
2
5
  * Lightweight duplicate of @webpieces/core-util toError for use in eslint-plugin.
3
6
  * eslint-plugin is Level 0 and cannot depend on core-util (also Level 0).
4
7
  */
5
8
  // webpieces-disable no-any-unknown -- toError intentionally accepts unknown to safely convert any thrown value to Error
6
- export function toError(err: unknown): Error {
9
+ function toError(err) {
7
10
  if (err instanceof Error) {
8
11
  return err;
9
12
  }
10
-
11
13
  if (err && typeof err === 'object') {
12
14
  if ('message' in err) {
13
15
  const error = new Error(String(err.message));
14
-
15
16
  if ('stack' in err && typeof err.stack === 'string') {
16
17
  error.stack = err.stack;
17
18
  }
@@ -20,17 +21,17 @@ export function toError(err: unknown): Error {
20
21
  }
21
22
  return error;
22
23
  }
23
-
24
24
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- must handle circular references without recursion
25
25
  try {
26
26
  return new Error(`Non-Error object thrown: ${JSON.stringify(err)}`);
27
- } catch (err: unknown) {
27
+ }
28
+ catch (err) {
28
29
  //const error = toError(err);
29
30
  void err;
30
31
  return new Error('Non-Error object thrown (unable to stringify)');
31
32
  }
32
33
  }
33
-
34
34
  const message = err == null ? 'Null or undefined thrown' : String(err);
35
35
  return new Error(message);
36
36
  }
37
+ //# sourceMappingURL=toError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toError.js","sourceRoot":"","sources":["../../../../../packages/tooling/eslint-rules/src/toError.ts"],"names":[],"mappings":";;AAKA,0BA8BC;AAnCD;;;GAGG;AACH,wHAAwH;AACxH,SAAgB,OAAO,CAAC,GAAY;IAChC,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACvB,OAAO,GAAG,CAAC;IACf,CAAC;IAED,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACjC,IAAI,SAAS,IAAI,GAAG,EAAE,CAAC;YACnB,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;YAE7C,IAAI,OAAO,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAClD,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;YAC5B,CAAC;YACD,IAAI,MAAM,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAChD,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;YAC1B,CAAC;YACD,OAAO,KAAK,CAAC;QACjB,CAAC;QAED,mHAAmH;QACnH,IAAI,CAAC;YACD,OAAO,IAAI,KAAK,CAAC,4BAA4B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,6BAA6B;YAC7B,KAAK,GAAG,CAAC;YACT,OAAO,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACtE,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACvE,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;AAC9B,CAAC","sourcesContent":["/**\n * Lightweight duplicate of @webpieces/core-util toError for use in eslint-plugin.\n * eslint-plugin is Level 0 and cannot depend on core-util (also Level 0).\n */\n// webpieces-disable no-any-unknown -- toError intentionally accepts unknown to safely convert any thrown value to Error\nexport function toError(err: unknown): Error {\n if (err instanceof Error) {\n return err;\n }\n\n if (err && typeof err === 'object') {\n if ('message' in err) {\n const error = new Error(String(err.message));\n\n if ('stack' in err && typeof err.stack === 'string') {\n error.stack = err.stack;\n }\n if ('name' in err && typeof err.name === 'string') {\n error.name = err.name;\n }\n return error;\n }\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- must handle circular references without recursion\n try {\n return new Error(`Non-Error object thrown: ${JSON.stringify(err)}`);\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n return new Error('Non-Error object thrown (unable to stringify)');\n }\n }\n\n const message = err == null ? 'Null or undefined thrown' : String(err);\n return new Error(message);\n}\n"]}
@@ -1,5 +0,0 @@
1
- # Exception Documentation Not Found
2
-
3
- Template file not found at: /Users/deanhiller/openclaw/personal/webpieces-ts30/packages/tooling/eslint-plugin/templates/webpieces.exceptions.md
4
-
5
- Please ensure @webpieces/dev-config is properly installed.
@@ -1,146 +0,0 @@
1
- # AI Agent Instructions: File Too Long
2
-
3
- **READ THIS FILE to fix files that are too long**
4
-
5
- ## Core Principle
6
- Files should contain a SINGLE COHESIVE UNIT.
7
- - One class per file (Java convention)
8
- - If class is too large, extract child responsibilities
9
- - Use dependency injection to compose functionality
10
-
11
- ## Command: Reduce File Size
12
-
13
- ### Step 1: Check for Multiple Classes
14
- If the file contains multiple classes, **SEPARATE each class into its own file**.
15
-
16
- ```typescript
17
- // BAD: UserController.ts (multiple classes)
18
- export class UserController { /* ... */ }
19
- export class UserValidator { /* ... */ }
20
- export class UserNotifier { /* ... */ }
21
-
22
- // GOOD: Three separate files
23
- // UserController.ts
24
- export class UserController { /* ... */ }
25
-
26
- // UserValidator.ts
27
- export class UserValidator { /* ... */ }
28
-
29
- // UserNotifier.ts
30
- export class UserNotifier { /* ... */ }
31
- ```
32
-
33
- ### Step 2: Extract Child Responsibilities (if single class is too large)
34
-
35
- #### Pattern: Create New Service Class with Dependency Injection
36
-
37
- ```typescript
38
- // BAD: UserController.ts (800 lines, single class)
39
- @provideSingleton()
40
- @Controller()
41
- export class UserController {
42
- // 200 lines: CRUD operations
43
- // 300 lines: validation logic
44
- // 200 lines: notification logic
45
- // 100 lines: analytics logic
46
- }
47
-
48
- // GOOD: Extract validation service
49
- // 1. Create UserValidationService.ts
50
- @provideSingleton()
51
- export class UserValidationService {
52
- validateUserData(data: UserData): ValidationResult {
53
- // 300 lines of validation logic moved here
54
- }
55
-
56
- validateEmail(email: string): boolean { /* ... */ }
57
- validatePassword(password: string): boolean { /* ... */ }
58
- }
59
-
60
- // 2. Inject into UserController.ts
61
- @provideSingleton()
62
- @Controller()
63
- export class UserController {
64
- constructor(
65
- @inject(TYPES.UserValidationService)
66
- private validator: UserValidationService
67
- ) {}
68
-
69
- async createUser(data: UserData): Promise<User> {
70
- const validation = this.validator.validateUserData(data);
71
- if (!validation.isValid) {
72
- throw new ValidationError(validation.errors);
73
- }
74
- // ... rest of logic
75
- }
76
- }
77
- ```
78
-
79
- ## AI Agent Action Steps
80
-
81
- 1. **ANALYZE** the file structure:
82
- - Count classes (if >1, separate immediately)
83
- - Identify logical responsibilities within single class
84
-
85
- 2. **IDENTIFY** "child code" to extract:
86
- - Validation logic -> ValidationService
87
- - Notification logic -> NotificationService
88
- - Data transformation -> TransformerService
89
- - External API calls -> ApiService
90
- - Business rules -> RulesEngine
91
-
92
- 3. **CREATE** new service file(s):
93
- - Start with temporary name: `XXXX.ts` or `ChildService.ts`
94
- - Add `@provideSingleton()` decorator
95
- - Move child methods to new class
96
-
97
- 4. **UPDATE** dependency injection:
98
- - Add to `TYPES` constants (if using symbol-based DI)
99
- - Inject new service into original class constructor
100
- - Replace direct method calls with `this.serviceName.method()`
101
-
102
- 5. **RENAME** extracted file:
103
- - Read the extracted code to understand its purpose
104
- - Rename `XXXX.ts` to logical name (e.g., `UserValidationService.ts`)
105
-
106
- 6. **VERIFY** file sizes:
107
- - Original file should now be <700 lines
108
- - Each extracted file should be <700 lines
109
- - If still too large, extract more services
110
-
111
- ## Examples of Child Responsibilities to Extract
112
-
113
- | If File Contains | Extract To | Pattern |
114
- |-----------------|------------|---------|
115
- | Validation logic (200+ lines) | `XValidator.ts` or `XValidationService.ts` | Singleton service |
116
- | Notification logic (150+ lines) | `XNotifier.ts` or `XNotificationService.ts` | Singleton service |
117
- | Data transformation (200+ lines) | `XTransformer.ts` | Singleton service |
118
- | External API calls (200+ lines) | `XApiClient.ts` | Singleton service |
119
- | Complex business rules (300+ lines) | `XRulesEngine.ts` | Singleton service |
120
- | Database queries (200+ lines) | `XRepository.ts` | Singleton service |
121
-
122
- ## WebPieces Dependency Injection Pattern
123
-
124
- ```typescript
125
- // 1. Define service with @provideSingleton
126
- import { provideSingleton } from '@webpieces/http-routing';
127
-
128
- @provideSingleton()
129
- export class MyService {
130
- doSomething(): void { /* ... */ }
131
- }
132
-
133
- // 2. Inject into consumer
134
- import { inject } from 'inversify';
135
- import { TYPES } from './types';
136
-
137
- @provideSingleton()
138
- @Controller()
139
- export class MyController {
140
- constructor(
141
- @inject(TYPES.MyService) private service: MyService
142
- ) {}
143
- }
144
- ```
145
-
146
- 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.
@@ -1,97 +0,0 @@
1
- # AI Agent Instructions: Method Too Long
2
-
3
- **READ THIS FILE to fix methods that are too long**
4
-
5
- ## Core Principle
6
- Every method should read like a TABLE OF CONTENTS of a book.
7
- - Each method call is a "chapter"
8
- - When you dive into a method, you find another table of contents
9
- - Keeping methods under 70 lines is achievable with proper extraction
10
-
11
- ## Command: Extract Code into Named Methods
12
-
13
- ### Pattern 1: Extract Loop Bodies
14
- ```typescript
15
- // BAD: 50 lines embedded in loop
16
- for (const order of orders) {
17
- // 20 lines of validation logic
18
- // 15 lines of processing logic
19
- // 10 lines of notification logic
20
- }
21
-
22
- // GOOD: Extracted to named methods
23
- for (const order of orders) {
24
- validateOrder(order);
25
- processOrderItems(order);
26
- sendNotifications(order);
27
- }
28
- ```
29
-
30
- ### Pattern 2: Try-Catch Wrapper for Exception Handling
31
- ```typescript
32
- // GOOD: Separates success path from error handling
33
- async function handleRequest(req: Request): Promise<Response> {
34
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
35
- try {
36
- return await executeRequest(req);
37
- } catch (err: unknown) {
38
- const error = toError(err);
39
- return createErrorResponse(error);
40
- }
41
- }
42
- ```
43
-
44
- ### Pattern 3: Sequential Method Calls (Table of Contents)
45
- ```typescript
46
- // GOOD: Self-documenting steps
47
- function processOrder(order: Order): void {
48
- validateOrderData(order);
49
- calculateTotals(order);
50
- applyDiscounts(order);
51
- processPayment(order);
52
- updateInventory(order);
53
- sendConfirmation(order);
54
- }
55
- ```
56
-
57
- ### Pattern 4: Separate Data Object Creation
58
- ```typescript
59
- // BAD: 15 lines of inline object creation
60
- doSomething({ field1: ..., field2: ..., field3: ..., /* 15 more fields */ });
61
-
62
- // GOOD: Extract to factory method
63
- const request = createRequestObject(data);
64
- doSomething(request);
65
- ```
66
-
67
- ### Pattern 5: Extract Inline Logic to Named Functions
68
- ```typescript
69
- // BAD: Complex inline logic
70
- if (user.role === 'admin' && user.permissions.includes('write') && !user.suspended) {
71
- // 30 lines of admin logic
72
- }
73
-
74
- // GOOD: Extract to named methods
75
- if (isAdminWithWriteAccess(user)) {
76
- performAdminOperation(user);
77
- }
78
- ```
79
-
80
- ## AI Agent Action Steps
81
-
82
- 1. **IDENTIFY** the long method in the error message
83
- 2. **READ** the method to understand its logical sections
84
- 3. **EXTRACT** logical units into separate methods with descriptive names
85
- 4. **REPLACE** inline code with method calls
86
- 5. **VERIFY** each extracted method is <70 lines
87
- 6. **TEST** that functionality remains unchanged
88
-
89
- ## Examples of "Logical Units" to Extract
90
- - Validation logic -> `validateX()`
91
- - Data transformation -> `transformXToY()`
92
- - API calls -> `fetchXFromApi()`
93
- - Object creation -> `createX()`
94
- - Loop bodies -> `processItem()`
95
- - Error handling -> `handleXError()`
96
-
97
- 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.