@webpieces/eslint-rules 0.0.1 → 0.2.113
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/package.json +3 -2
- package/src/index.d.ts +29 -0
- package/src/index.js +39 -0
- package/src/index.js.map +1 -0
- package/src/rules/catch-error-pattern.d.ts +11 -0
- package/src/rules/{catch-error-pattern.ts → catch-error-pattern.js} +30 -142
- package/src/rules/catch-error-pattern.js.map +1 -0
- package/src/rules/enforce-architecture.d.ts +15 -0
- package/src/rules/{enforce-architecture.ts → enforce-architecture.js} +61 -128
- package/src/rules/enforce-architecture.js.map +1 -0
- package/src/rules/max-file-lines.d.ts +12 -0
- package/src/rules/{max-file-lines.ts → max-file-lines.js} +22 -37
- package/src/rules/max-file-lines.js.map +1 -0
- package/src/rules/max-method-lines.d.ts +12 -0
- package/src/rules/{max-method-lines.ts → max-method-lines.js} +31 -81
- package/src/rules/max-method-lines.js.map +1 -0
- package/src/rules/no-json-property-primitive-type.d.ts +17 -0
- package/src/rules/no-json-property-primitive-type.js +57 -0
- package/src/rules/no-json-property-primitive-type.js.map +1 -0
- package/src/rules/no-mat-cell-def.d.ts +15 -0
- package/src/rules/{no-mat-cell-def.ts → no-mat-cell-def.js} +8 -21
- package/src/rules/no-mat-cell-def.js.map +1 -0
- package/src/rules/no-unmanaged-exceptions.d.ts +22 -0
- package/src/rules/{no-unmanaged-exceptions.ts → no-unmanaged-exceptions.js} +27 -52
- package/src/rules/no-unmanaged-exceptions.js.map +1 -0
- package/src/rules/require-typed-template.d.ts +17 -0
- package/src/rules/{require-typed-template.ts → require-typed-template.js} +11 -31
- package/src/rules/require-typed-template.js.map +1 -0
- package/src/toError.d.ts +5 -0
- package/src/{toError.ts → toError.js} +7 -6
- package/src/toError.js.map +1 -0
- package/.webpieces/instruct-ai/webpieces.exceptions.md +0 -5
- package/.webpieces/instruct-ai/webpieces.filesize.md +0 -146
- package/.webpieces/instruct-ai/webpieces.methods.md +0 -97
- package/LICENSE +0 -373
- package/jest.config.ts +0 -16
- package/project.json +0 -22
- package/src/__tests__/catch-error-pattern.test.ts +0 -374
- package/src/__tests__/max-file-lines.test.ts +0 -207
- package/src/__tests__/max-method-lines.test.ts +0 -258
- package/src/__tests__/no-unmanaged-exceptions.test.ts +0 -359
- package/src/index.ts +0 -38
- package/src/rules/no-json-property-primitive-type.ts +0 -85
- package/tmp/webpieces/webpieces.exceptions.md +0 -5
- package/tsconfig.json +0 -22
- package/tsconfig.lib.json +0 -10
- package/tsconfig.spec.json +0 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/eslint-rules",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.113",
|
|
4
4
|
"description": "ESLint rules for WebPieces code patterns and architecture enforcement",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -16,5 +16,6 @@
|
|
|
16
16
|
},
|
|
17
17
|
"publishConfig": {
|
|
18
18
|
"access": "public"
|
|
19
|
-
}
|
|
19
|
+
},
|
|
20
|
+
"types": "./src/index.d.ts"
|
|
20
21
|
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint plugin for WebPieces
|
|
3
|
+
* Provides rules for enforcing WebPieces code patterns
|
|
4
|
+
*
|
|
5
|
+
* This plugin is automatically included in @webpieces/webpieces-rules
|
|
6
|
+
*
|
|
7
|
+
* Available rules:
|
|
8
|
+
* - catch-error-pattern: Enforce toError() usage in catch blocks (HOW to handle)
|
|
9
|
+
* - no-unmanaged-exceptions: Discourage try-catch outside tests (WHERE to handle)
|
|
10
|
+
* - max-method-lines: Enforce maximum method length (default: 70 lines)
|
|
11
|
+
* - max-file-lines: Enforce maximum file length (default: 700 lines)
|
|
12
|
+
* - enforce-architecture: Enforce architecture dependency boundaries
|
|
13
|
+
* - no-json-property-primitive-type: Ban @JsonProperty({ type: String/Number/Boolean })
|
|
14
|
+
* - require-typed-template: Require [templateClassType] on ng-template with let- variables (Angular)
|
|
15
|
+
* - no-mat-cell-def: Ban *matCellDef/*matHeaderCellDef — use div-grid tables (Angular)
|
|
16
|
+
*/
|
|
17
|
+
declare const _default: {
|
|
18
|
+
rules: {
|
|
19
|
+
'catch-error-pattern': import("eslint").Rule.RuleModule;
|
|
20
|
+
'no-unmanaged-exceptions': import("eslint").Rule.RuleModule;
|
|
21
|
+
'max-method-lines': import("eslint").Rule.RuleModule;
|
|
22
|
+
'max-file-lines': import("eslint").Rule.RuleModule;
|
|
23
|
+
'enforce-architecture': import("eslint").Rule.RuleModule;
|
|
24
|
+
'no-json-property-primitive-type': import("eslint").Rule.RuleModule;
|
|
25
|
+
'require-typed-template': import("eslint").Rule.RuleModule;
|
|
26
|
+
'no-mat-cell-def': import("eslint").Rule.RuleModule;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export = _default;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ESLint plugin for WebPieces
|
|
4
|
+
* Provides rules for enforcing WebPieces code patterns
|
|
5
|
+
*
|
|
6
|
+
* This plugin is automatically included in @webpieces/webpieces-rules
|
|
7
|
+
*
|
|
8
|
+
* Available rules:
|
|
9
|
+
* - catch-error-pattern: Enforce toError() usage in catch blocks (HOW to handle)
|
|
10
|
+
* - no-unmanaged-exceptions: Discourage try-catch outside tests (WHERE to handle)
|
|
11
|
+
* - max-method-lines: Enforce maximum method length (default: 70 lines)
|
|
12
|
+
* - max-file-lines: Enforce maximum file length (default: 700 lines)
|
|
13
|
+
* - enforce-architecture: Enforce architecture dependency boundaries
|
|
14
|
+
* - no-json-property-primitive-type: Ban @JsonProperty({ type: String/Number/Boolean })
|
|
15
|
+
* - require-typed-template: Require [templateClassType] on ng-template with let- variables (Angular)
|
|
16
|
+
* - no-mat-cell-def: Ban *matCellDef/*matHeaderCellDef — use div-grid tables (Angular)
|
|
17
|
+
*/
|
|
18
|
+
const tslib_1 = require("tslib");
|
|
19
|
+
const catch_error_pattern_1 = tslib_1.__importDefault(require("./rules/catch-error-pattern"));
|
|
20
|
+
const no_unmanaged_exceptions_1 = tslib_1.__importDefault(require("./rules/no-unmanaged-exceptions"));
|
|
21
|
+
const max_method_lines_1 = tslib_1.__importDefault(require("./rules/max-method-lines"));
|
|
22
|
+
const max_file_lines_1 = tslib_1.__importDefault(require("./rules/max-file-lines"));
|
|
23
|
+
const enforce_architecture_1 = tslib_1.__importDefault(require("./rules/enforce-architecture"));
|
|
24
|
+
const no_json_property_primitive_type_1 = tslib_1.__importDefault(require("./rules/no-json-property-primitive-type"));
|
|
25
|
+
const require_typed_template_1 = tslib_1.__importDefault(require("./rules/require-typed-template"));
|
|
26
|
+
const no_mat_cell_def_1 = tslib_1.__importDefault(require("./rules/no-mat-cell-def"));
|
|
27
|
+
module.exports = {
|
|
28
|
+
rules: {
|
|
29
|
+
'catch-error-pattern': catch_error_pattern_1.default,
|
|
30
|
+
'no-unmanaged-exceptions': no_unmanaged_exceptions_1.default,
|
|
31
|
+
'max-method-lines': max_method_lines_1.default,
|
|
32
|
+
'max-file-lines': max_file_lines_1.default,
|
|
33
|
+
'enforce-architecture': enforce_architecture_1.default,
|
|
34
|
+
'no-json-property-primitive-type': no_json_property_primitive_type_1.default,
|
|
35
|
+
'require-typed-template': require_typed_template_1.default,
|
|
36
|
+
'no-mat-cell-def': no_mat_cell_def_1.default,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/eslint-rules/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;AAEH,8FAA4D;AAC5D,sGAAoE;AACpE,wFAAsD;AACtD,oFAAkD;AAClD,gGAA+D;AAC/D,sHAAkF;AAClF,oGAAkE;AAClE,sFAAmD;AAEnD,iBAAS;IACL,KAAK,EAAE;QACH,qBAAqB,EAAE,6BAAiB;QACxC,yBAAyB,EAAE,iCAAqB;QAChD,kBAAkB,EAAE,0BAAc;QAClC,gBAAgB,EAAE,wBAAY;QAC9B,sBAAsB,EAAE,8BAAmB;QAC3C,iCAAiC,EAAE,yCAA2B;QAC9D,wBAAwB,EAAE,gCAAoB;QAC9C,iBAAiB,EAAE,yBAAY;KAClC;CACJ,CAAC","sourcesContent":["/**\n * ESLint plugin for WebPieces\n * Provides rules for enforcing WebPieces code patterns\n *\n * This plugin is automatically included in @webpieces/webpieces-rules\n *\n * Available rules:\n * - catch-error-pattern: Enforce toError() usage in catch blocks (HOW to handle)\n * - no-unmanaged-exceptions: Discourage try-catch outside tests (WHERE to handle)\n * - max-method-lines: Enforce maximum method length (default: 70 lines)\n * - max-file-lines: Enforce maximum file length (default: 700 lines)\n * - enforce-architecture: Enforce architecture dependency boundaries\n * - no-json-property-primitive-type: Ban @JsonProperty({ type: String/Number/Boolean })\n * - require-typed-template: Require [templateClassType] on ng-template with let- variables (Angular)\n * - no-mat-cell-def: Ban *matCellDef/*matHeaderCellDef — use div-grid tables (Angular)\n */\n\nimport catchErrorPattern from './rules/catch-error-pattern';\nimport noUnmanagedExceptions from './rules/no-unmanaged-exceptions';\nimport maxMethodLines from './rules/max-method-lines';\nimport maxFileLines from './rules/max-file-lines';\nimport enforceArchitecture from './rules/enforce-architecture';\nimport noJsonPropertyPrimitiveType from './rules/no-json-property-primitive-type';\nimport requireTypedTemplate from './rules/require-typed-template';\nimport noMatCellDef from './rules/no-mat-cell-def';\n\nexport = {\n rules: {\n 'catch-error-pattern': catchErrorPattern,\n 'no-unmanaged-exceptions': noUnmanagedExceptions,\n 'max-method-lines': maxMethodLines,\n 'max-file-lines': maxFileLines,\n 'enforce-architecture': enforceArchitecture,\n 'no-json-property-primitive-type': noJsonPropertyPrimitiveType,\n 'require-typed-template': requireTypedTemplate,\n 'no-mat-cell-def': noMatCellDef,\n },\n};\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule to enforce standardized catch block error handling patterns
|
|
3
|
+
*
|
|
4
|
+
* Enforces three approved patterns:
|
|
5
|
+
* 1. Standard: catch (err: unknown) { const error = toError(err); }
|
|
6
|
+
* 2. Ignored: catch (err: unknown) { //const error = toError(err); }
|
|
7
|
+
* 3. Nested: catch (err2: unknown) { const error2 = toError(err2); }
|
|
8
|
+
*/
|
|
9
|
+
import type { Rule } from 'eslint';
|
|
10
|
+
declare const rule: Rule.RuleModule;
|
|
11
|
+
export = rule;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* ESLint rule to enforce standardized catch block error handling patterns
|
|
3
4
|
*
|
|
@@ -6,72 +7,7 @@
|
|
|
6
7
|
* 2. Ignored: catch (err: unknown) { //const error = toError(err); }
|
|
7
8
|
* 3. Nested: catch (err2: unknown) { const error2 = toError(err2); }
|
|
8
9
|
*/
|
|
9
|
-
|
|
10
|
-
import type { Rule } from 'eslint';
|
|
11
|
-
|
|
12
|
-
// webpieces-disable no-any-unknown -- ESTree AST node interfaces require any for dynamic properties
|
|
13
|
-
// Using any for ESTree nodes to avoid complex type gymnastics
|
|
14
|
-
// ESLint rules work with dynamic AST nodes anyway
|
|
15
|
-
interface CatchClauseNode {
|
|
16
|
-
type: 'CatchClause';
|
|
17
|
-
param?: IdentifierNode | null;
|
|
18
|
-
body: BlockStatementNode;
|
|
19
|
-
// webpieces-disable no-any-unknown -- ESTree AST index signature
|
|
20
|
-
[key: string]: any;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface IdentifierNode {
|
|
24
|
-
type: 'Identifier';
|
|
25
|
-
name: string;
|
|
26
|
-
typeAnnotation?: TypeAnnotationNode;
|
|
27
|
-
// webpieces-disable no-any-unknown -- ESTree AST index signature
|
|
28
|
-
[key: string]: any;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface TypeAnnotationNode {
|
|
32
|
-
typeAnnotation?: {
|
|
33
|
-
type: string;
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface BlockStatementNode {
|
|
38
|
-
type: 'BlockStatement';
|
|
39
|
-
// webpieces-disable no-any-unknown -- ESTree AST dynamic body array
|
|
40
|
-
body: any[];
|
|
41
|
-
range: [number, number];
|
|
42
|
-
// webpieces-disable no-any-unknown -- ESTree AST index signature
|
|
43
|
-
[key: string]: any;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface VariableDeclarationNode {
|
|
47
|
-
type: 'VariableDeclaration';
|
|
48
|
-
declarations: VariableDeclaratorNode[];
|
|
49
|
-
// webpieces-disable no-any-unknown -- ESTree AST index signature
|
|
50
|
-
[key: string]: any;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface VariableDeclaratorNode {
|
|
54
|
-
type: 'VariableDeclarator';
|
|
55
|
-
id: IdentifierNode;
|
|
56
|
-
init?: CallExpressionNode | null;
|
|
57
|
-
// webpieces-disable no-any-unknown -- ESTree AST index signature
|
|
58
|
-
[key: string]: any;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
interface CallExpressionNode {
|
|
62
|
-
type: 'CallExpression';
|
|
63
|
-
callee: IdentifierNode;
|
|
64
|
-
// webpieces-disable no-any-unknown -- ESTree AST dynamic arguments array
|
|
65
|
-
arguments: any[];
|
|
66
|
-
// webpieces-disable no-any-unknown -- ESTree AST index signature
|
|
67
|
-
[key: string]: any;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function validateParamName(
|
|
71
|
-
context: Rule.RuleContext,
|
|
72
|
-
param: IdentifierNode,
|
|
73
|
-
expectedParamName: string,
|
|
74
|
-
): void {
|
|
10
|
+
function validateParamName(context, param, expectedParamName) {
|
|
75
11
|
if (param.type === 'Identifier' && param.name !== expectedParamName) {
|
|
76
12
|
context.report({
|
|
77
13
|
node: param,
|
|
@@ -80,17 +16,10 @@ function validateParamName(
|
|
|
80
16
|
});
|
|
81
17
|
}
|
|
82
18
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
context: Rule.RuleContext,
|
|
86
|
-
param: IdentifierNode,
|
|
87
|
-
expectedParamName: string,
|
|
88
|
-
): void {
|
|
89
|
-
if (
|
|
90
|
-
!param.typeAnnotation ||
|
|
19
|
+
function validateTypeAnnotation(context, param, expectedParamName) {
|
|
20
|
+
if (!param.typeAnnotation ||
|
|
91
21
|
!param.typeAnnotation.typeAnnotation ||
|
|
92
|
-
param.typeAnnotation.typeAnnotation.type !== 'TSUnknownKeyword'
|
|
93
|
-
) {
|
|
22
|
+
param.typeAnnotation.typeAnnotation.type !== 'TSUnknownKeyword') {
|
|
94
23
|
context.report({
|
|
95
24
|
node: param,
|
|
96
25
|
messageId: 'missingTypeAnnotation',
|
|
@@ -98,57 +27,35 @@ function validateTypeAnnotation(
|
|
|
98
27
|
});
|
|
99
28
|
}
|
|
100
29
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
catchNode
|
|
104
|
-
sourceCode: Rule.RuleContext['sourceCode'],
|
|
105
|
-
expectedVarName: string,
|
|
106
|
-
actualParamName: string,
|
|
107
|
-
): boolean {
|
|
108
|
-
const catchBlockStart = catchNode.body.range![0];
|
|
109
|
-
const catchBlockEnd = catchNode.body.range![1];
|
|
30
|
+
function hasIgnoreComment(catchNode, sourceCode, expectedVarName, actualParamName) {
|
|
31
|
+
const catchBlockStart = catchNode.body.range[0];
|
|
32
|
+
const catchBlockEnd = catchNode.body.range[1];
|
|
110
33
|
const catchBlockText = sourceCode.text.substring(catchBlockStart, catchBlockEnd);
|
|
111
|
-
|
|
112
|
-
const ignorePattern = new RegExp(
|
|
113
|
-
`//\\s*const\\s+${expectedVarName}\\s*=\\s*toError\\(${actualParamName}\\)`,
|
|
114
|
-
);
|
|
115
|
-
|
|
34
|
+
const ignorePattern = new RegExp(`//\\s*const\\s+${expectedVarName}\\s*=\\s*toError\\(${actualParamName}\\)`);
|
|
116
35
|
return ignorePattern.test(catchBlockText);
|
|
117
36
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// webpieces-disable no-any-unknown -- ESLint AST node param requires any type
|
|
122
|
-
node: any,
|
|
123
|
-
paramName: string,
|
|
124
|
-
): void {
|
|
37
|
+
function reportMissingToError(context,
|
|
38
|
+
// webpieces-disable no-any-unknown -- ESLint AST node param requires any type
|
|
39
|
+
node, paramName) {
|
|
125
40
|
context.report({
|
|
126
41
|
node,
|
|
127
42
|
messageId: 'missingToError',
|
|
128
43
|
data: { param: paramName },
|
|
129
44
|
});
|
|
130
45
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// webpieces-disable no-any-unknown -- ESLint AST node param requires any type
|
|
135
|
-
firstStatement: any,
|
|
136
|
-
expectedParamName: string,
|
|
137
|
-
expectedVarName: string,
|
|
138
|
-
actualParamName: string,
|
|
139
|
-
): void {
|
|
46
|
+
function validateToErrorCall(context,
|
|
47
|
+
// webpieces-disable no-any-unknown -- ESLint AST node param requires any type
|
|
48
|
+
firstStatement, expectedParamName, expectedVarName, actualParamName) {
|
|
140
49
|
if (firstStatement.type !== 'VariableDeclaration') {
|
|
141
50
|
reportMissingToError(context, firstStatement, expectedParamName);
|
|
142
51
|
return;
|
|
143
52
|
}
|
|
144
|
-
|
|
145
|
-
const varDecl = firstStatement as VariableDeclarationNode;
|
|
53
|
+
const varDecl = firstStatement;
|
|
146
54
|
const declaration = varDecl.declarations[0];
|
|
147
55
|
if (!declaration) {
|
|
148
56
|
reportMissingToError(context, firstStatement, expectedParamName);
|
|
149
57
|
return;
|
|
150
58
|
}
|
|
151
|
-
|
|
152
59
|
if (declaration.id.type !== 'Identifier' || declaration.id.name !== expectedVarName) {
|
|
153
60
|
context.report({
|
|
154
61
|
node: declaration.id,
|
|
@@ -157,30 +64,24 @@ function validateToErrorCall(
|
|
|
157
64
|
});
|
|
158
65
|
return;
|
|
159
66
|
}
|
|
160
|
-
|
|
161
67
|
if (!declaration.init || declaration.init.type !== 'CallExpression') {
|
|
162
68
|
reportMissingToError(context, declaration.init || declaration, expectedParamName);
|
|
163
69
|
return;
|
|
164
70
|
}
|
|
165
|
-
|
|
166
|
-
const callExpr = declaration.init as CallExpressionNode;
|
|
71
|
+
const callExpr = declaration.init;
|
|
167
72
|
const callee = callExpr.callee;
|
|
168
73
|
if (callee.type !== 'Identifier' || callee.name !== 'toError') {
|
|
169
74
|
reportMissingToError(context, callee, expectedParamName);
|
|
170
75
|
return;
|
|
171
76
|
}
|
|
172
|
-
|
|
173
77
|
const args = callExpr.arguments;
|
|
174
|
-
if (
|
|
175
|
-
args.length !== 1 ||
|
|
78
|
+
if (args.length !== 1 ||
|
|
176
79
|
args[0].type !== 'Identifier' ||
|
|
177
|
-
|
|
178
|
-
) {
|
|
80
|
+
args[0].name !== actualParamName) {
|
|
179
81
|
reportMissingToError(context, callExpr, actualParamName);
|
|
180
82
|
}
|
|
181
83
|
}
|
|
182
|
-
|
|
183
|
-
const rule: Rule.RuleModule = {
|
|
84
|
+
const rule = {
|
|
184
85
|
meta: {
|
|
185
86
|
type: 'problem',
|
|
186
87
|
docs: {
|
|
@@ -190,32 +91,26 @@ const rule: Rule.RuleModule = {
|
|
|
190
91
|
url: 'https://github.com/deanhiller/webpieces-ts/blob/main/claude.patterns.md#error-handling-pattern',
|
|
191
92
|
},
|
|
192
93
|
messages: {
|
|
193
|
-
missingToError:
|
|
194
|
-
'Catch block must call toError({{param}}) as first statement or comment it out to explicitly ignore errors',
|
|
94
|
+
missingToError: 'Catch block must call toError({{param}}) as first statement or comment it out to explicitly ignore errors',
|
|
195
95
|
wrongVariableName: 'Error variable must be named "{{expected}}", got "{{actual}}"',
|
|
196
96
|
missingTypeAnnotation: 'Catch parameter must be typed as "unknown": catch ({{param}}: unknown)',
|
|
197
|
-
wrongParameterName:
|
|
198
|
-
'Catch parameter must be named "err" (or "err2", "err3" for nested catches), got "{{actual}}"',
|
|
97
|
+
wrongParameterName: 'Catch parameter must be named "err" (or "err2", "err3" for nested catches), got "{{actual}}"',
|
|
199
98
|
toErrorNotFirst: 'toError({{param}}) must be the first statement in the catch block',
|
|
200
99
|
},
|
|
201
100
|
fixable: undefined,
|
|
202
101
|
schema: [],
|
|
203
102
|
},
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const catchStack: CatchClauseNode[] = [];
|
|
207
|
-
|
|
103
|
+
create(context) {
|
|
104
|
+
const catchStack = [];
|
|
208
105
|
return {
|
|
209
106
|
// webpieces-disable no-any-unknown -- ESLint visitor callback receives untyped AST node
|
|
210
|
-
CatchClause(node
|
|
211
|
-
const catchNode = node
|
|
107
|
+
CatchClause(node) {
|
|
108
|
+
const catchNode = node;
|
|
212
109
|
const depth = catchStack.length + 1;
|
|
213
110
|
catchStack.push(catchNode);
|
|
214
|
-
|
|
215
111
|
const suffix = depth === 1 ? '' : String(depth);
|
|
216
112
|
const expectedParamName = 'err' + suffix;
|
|
217
113
|
const expectedVarName = 'error' + suffix;
|
|
218
|
-
|
|
219
114
|
const param = catchNode.param;
|
|
220
115
|
if (!param) {
|
|
221
116
|
context.report({
|
|
@@ -225,32 +120,25 @@ const rule: Rule.RuleModule = {
|
|
|
225
120
|
});
|
|
226
121
|
return;
|
|
227
122
|
}
|
|
228
|
-
|
|
229
|
-
const actualParamName =
|
|
230
|
-
param.type === 'Identifier' ? param.name : expectedParamName;
|
|
231
|
-
|
|
123
|
+
const actualParamName = param.type === 'Identifier' ? param.name : expectedParamName;
|
|
232
124
|
validateParamName(context, param, expectedParamName);
|
|
233
125
|
validateTypeAnnotation(context, param, expectedParamName);
|
|
234
|
-
|
|
235
126
|
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
236
127
|
if (hasIgnoreComment(catchNode, sourceCode, expectedVarName, actualParamName)) {
|
|
237
128
|
return;
|
|
238
129
|
}
|
|
239
|
-
|
|
240
130
|
const body = catchNode.body.body;
|
|
241
131
|
if (body.length === 0) {
|
|
242
132
|
reportMissingToError(context, catchNode.body, expectedParamName);
|
|
243
133
|
return;
|
|
244
134
|
}
|
|
245
|
-
|
|
246
135
|
validateToErrorCall(context, body[0], expectedParamName, expectedVarName, actualParamName);
|
|
247
136
|
},
|
|
248
|
-
|
|
249
|
-
'CatchClause:exit'(): void {
|
|
137
|
+
'CatchClause:exit'() {
|
|
250
138
|
catchStack.pop();
|
|
251
139
|
},
|
|
252
140
|
};
|
|
253
141
|
},
|
|
254
142
|
};
|
|
255
|
-
|
|
256
|
-
|
|
143
|
+
module.exports = rule;
|
|
144
|
+
//# sourceMappingURL=catch-error-pattern.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catch-error-pattern.js","sourceRoot":"","sources":["../../../../../../packages/tooling/eslint-rules/src/rules/catch-error-pattern.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESLint rule to enforce standardized catch block error handling patterns\n *\n * Enforces three approved patterns:\n * 1. Standard: catch (err: unknown) { const error = toError(err); }\n * 2. Ignored: catch (err: unknown) { //const error = toError(err); }\n * 3. Nested: catch (err2: unknown) { const error2 = toError(err2); }\n */\n\nimport type { Rule } from 'eslint';\n\n// webpieces-disable no-any-unknown -- ESTree AST node interfaces require any for dynamic properties\n// Using any for ESTree nodes to avoid complex type gymnastics\n// ESLint rules work with dynamic AST nodes anyway\ninterface CatchClauseNode {\n type: 'CatchClause';\n param?: IdentifierNode | null;\n body: BlockStatementNode;\n // webpieces-disable no-any-unknown -- ESTree AST index signature\n [key: string]: any;\n}\n\ninterface IdentifierNode {\n type: 'Identifier';\n name: string;\n typeAnnotation?: TypeAnnotationNode;\n // webpieces-disable no-any-unknown -- ESTree AST index signature\n [key: string]: any;\n}\n\ninterface TypeAnnotationNode {\n typeAnnotation?: {\n type: string;\n };\n}\n\ninterface BlockStatementNode {\n type: 'BlockStatement';\n // webpieces-disable no-any-unknown -- ESTree AST dynamic body array\n body: any[];\n range: [number, number];\n // webpieces-disable no-any-unknown -- ESTree AST index signature\n [key: string]: any;\n}\n\ninterface VariableDeclarationNode {\n type: 'VariableDeclaration';\n declarations: VariableDeclaratorNode[];\n // webpieces-disable no-any-unknown -- ESTree AST index signature\n [key: string]: any;\n}\n\ninterface VariableDeclaratorNode {\n type: 'VariableDeclarator';\n id: IdentifierNode;\n init?: CallExpressionNode | null;\n // webpieces-disable no-any-unknown -- ESTree AST index signature\n [key: string]: any;\n}\n\ninterface CallExpressionNode {\n type: 'CallExpression';\n callee: IdentifierNode;\n // webpieces-disable no-any-unknown -- ESTree AST dynamic arguments array\n arguments: any[];\n // webpieces-disable no-any-unknown -- ESTree AST index signature\n [key: string]: any;\n}\n\nfunction validateParamName(\n context: Rule.RuleContext,\n param: IdentifierNode,\n expectedParamName: string,\n): void {\n if (param.type === 'Identifier' && param.name !== expectedParamName) {\n context.report({\n node: param,\n messageId: 'wrongParameterName',\n data: { actual: param.name },\n });\n }\n}\n\nfunction validateTypeAnnotation(\n context: Rule.RuleContext,\n param: IdentifierNode,\n expectedParamName: string,\n): void {\n if (\n !param.typeAnnotation ||\n !param.typeAnnotation.typeAnnotation ||\n param.typeAnnotation.typeAnnotation.type !== 'TSUnknownKeyword'\n ) {\n context.report({\n node: param,\n messageId: 'missingTypeAnnotation',\n data: { param: param.name || expectedParamName },\n });\n }\n}\n\nfunction hasIgnoreComment(\n catchNode: CatchClauseNode,\n sourceCode: Rule.RuleContext['sourceCode'],\n expectedVarName: string,\n actualParamName: string,\n): boolean {\n const catchBlockStart = catchNode.body.range![0];\n const catchBlockEnd = catchNode.body.range![1];\n const catchBlockText = sourceCode.text.substring(catchBlockStart, catchBlockEnd);\n\n const ignorePattern = new RegExp(\n `//\\\\s*const\\\\s+${expectedVarName}\\\\s*=\\\\s*toError\\\\(${actualParamName}\\\\)`,\n );\n\n return ignorePattern.test(catchBlockText);\n}\n\nfunction reportMissingToError(\n context: Rule.RuleContext,\n // webpieces-disable no-any-unknown -- ESLint AST node param requires any type\n node: any,\n paramName: string,\n): void {\n context.report({\n node,\n messageId: 'missingToError',\n data: { param: paramName },\n });\n}\n\nfunction validateToErrorCall(\n context: Rule.RuleContext,\n // webpieces-disable no-any-unknown -- ESLint AST node param requires any type\n firstStatement: any,\n expectedParamName: string,\n expectedVarName: string,\n actualParamName: string,\n): void {\n if (firstStatement.type !== 'VariableDeclaration') {\n reportMissingToError(context, firstStatement, expectedParamName);\n return;\n }\n\n const varDecl = firstStatement as VariableDeclarationNode;\n const declaration = varDecl.declarations[0];\n if (!declaration) {\n reportMissingToError(context, firstStatement, expectedParamName);\n return;\n }\n\n if (declaration.id.type !== 'Identifier' || declaration.id.name !== expectedVarName) {\n context.report({\n node: declaration.id,\n messageId: 'wrongVariableName',\n data: { expected: expectedVarName, actual: declaration.id.name || 'unknown' },\n });\n return;\n }\n\n if (!declaration.init || declaration.init.type !== 'CallExpression') {\n reportMissingToError(context, declaration.init || declaration, expectedParamName);\n return;\n }\n\n const callExpr = declaration.init as CallExpressionNode;\n const callee = callExpr.callee;\n if (callee.type !== 'Identifier' || callee.name !== 'toError') {\n reportMissingToError(context, callee, expectedParamName);\n return;\n }\n\n const args = callExpr.arguments;\n if (\n args.length !== 1 ||\n args[0].type !== 'Identifier' ||\n (args[0] as IdentifierNode).name !== actualParamName\n ) {\n reportMissingToError(context, callExpr, actualParamName);\n }\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Enforce standardized catch block error handling patterns',\n category: 'Best Practices',\n recommended: true,\n url: 'https://github.com/deanhiller/webpieces-ts/blob/main/claude.patterns.md#error-handling-pattern',\n },\n messages: {\n missingToError:\n 'Catch block must call toError({{param}}) as first statement or comment it out to explicitly ignore errors',\n wrongVariableName: 'Error variable must be named \"{{expected}}\", got \"{{actual}}\"',\n missingTypeAnnotation: 'Catch parameter must be typed as \"unknown\": catch ({{param}}: unknown)',\n wrongParameterName:\n 'Catch parameter must be named \"err\" (or \"err2\", \"err3\" for nested catches), got \"{{actual}}\"',\n toErrorNotFirst: 'toError({{param}}) must be the first statement in the catch block',\n },\n fixable: undefined,\n schema: [],\n },\n\n create(context: Rule.RuleContext): Rule.RuleListener {\n const catchStack: CatchClauseNode[] = [];\n\n return {\n // webpieces-disable no-any-unknown -- ESLint visitor callback receives untyped AST node\n CatchClause(node: any): void {\n const catchNode = node as CatchClauseNode;\n const depth = catchStack.length + 1;\n catchStack.push(catchNode);\n\n const suffix = depth === 1 ? '' : String(depth);\n const expectedParamName = 'err' + suffix;\n const expectedVarName = 'error' + suffix;\n\n const param = catchNode.param;\n if (!param) {\n context.report({\n node: catchNode,\n messageId: 'missingTypeAnnotation',\n data: { param: expectedParamName },\n });\n return;\n }\n\n const actualParamName =\n param.type === 'Identifier' ? param.name : expectedParamName;\n\n validateParamName(context, param, expectedParamName);\n validateTypeAnnotation(context, param, expectedParamName);\n\n const sourceCode = context.sourceCode || context.getSourceCode();\n if (hasIgnoreComment(catchNode, sourceCode, expectedVarName, actualParamName)) {\n return;\n }\n\n const body = catchNode.body.body;\n if (body.length === 0) {\n reportMissingToError(context, catchNode.body, expectedParamName);\n return;\n }\n\n validateToErrorCall(context, body[0], expectedParamName, expectedVarName, actualParamName);\n },\n\n 'CatchClause:exit'(): void {\n catchStack.pop();\n },\n };\n },\n};\n\nexport = rule;\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule to enforce architecture boundaries
|
|
3
|
+
*
|
|
4
|
+
* Validates that imports from @webpieces/* packages comply with the
|
|
5
|
+
* blessed dependency graph in .graphs/dependencies.json
|
|
6
|
+
*
|
|
7
|
+
* Supports transitive dependencies: if A depends on B and B depends on C,
|
|
8
|
+
* then A can import from C.
|
|
9
|
+
*
|
|
10
|
+
* Configuration:
|
|
11
|
+
* '@webpieces/enforce-architecture': 'error'
|
|
12
|
+
*/
|
|
13
|
+
import type { Rule } from 'eslint';
|
|
14
|
+
declare const rule: Rule.RuleModule;
|
|
15
|
+
export = rule;
|