@checkdigit/eslint-plugin 6.6.0-PR.79-1cec → 6.7.0-PR.79-33df
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/dist-cjs/index.cjs +102396 -9
- package/dist-cjs/metafile.json +15071 -86
- package/dist-mjs/index.mjs +12 -3
- package/dist-mjs/library/format.mjs +14 -0
- package/dist-mjs/library/tree.mjs +64 -0
- package/dist-mjs/library/ts-tree.mjs +72 -0
- package/dist-mjs/library/variable.mjs +8 -0
- package/dist-mjs/no-enum.mjs +11 -2
- package/dist-mjs/require-resolve-full-response.mjs +159 -0
- package/dist-types/index.d.ts +5 -0
- package/dist-types/library/format.d.ts +4 -0
- package/dist-types/library/tree.d.ts +8 -0
- package/dist-types/library/ts-tree.d.ts +9 -0
- package/dist-types/library/variable.d.ts +1 -0
- package/dist-types/require-resolve-full-response.d.ts +6 -0
- package/package.json +1 -1
- package/src/index.ts +9 -0
- package/src/library/format.ts +20 -0
- package/src/library/tree.ts +90 -0
- package/src/library/ts-tree.ts +101 -0
- package/src/library/variable.ts +5 -0
- package/src/no-enum.ts +18 -3
- package/src/require-resolve-full-response.ts +200 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// tree.ts
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Copyright (c) 2021-2024 Check Digit, LLC
|
|
5
|
+
*
|
|
6
|
+
* This code is licensed under the MIT license (see LICENSE.txt for details).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Expression, Node } from 'estree';
|
|
10
|
+
|
|
11
|
+
type NodeParent = Node | undefined | null;
|
|
12
|
+
|
|
13
|
+
interface NodeParentExtension {
|
|
14
|
+
parent: NodeParent;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getParent(node: Node): Node | undefined | null {
|
|
18
|
+
return (node as unknown as NodeParentExtension).parent;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getAncestor(
|
|
22
|
+
node: Node,
|
|
23
|
+
matcher: string | ((testNode: Node) => boolean),
|
|
24
|
+
exitMatcher?: string | ((testNode: Node) => boolean),
|
|
25
|
+
): Node | undefined {
|
|
26
|
+
const parent = getParent(node);
|
|
27
|
+
if (!parent) {
|
|
28
|
+
return undefined;
|
|
29
|
+
} else if (typeof matcher === 'string' && parent.type === matcher) {
|
|
30
|
+
return parent;
|
|
31
|
+
} else if (typeof matcher === 'function' && matcher(parent)) {
|
|
32
|
+
return parent;
|
|
33
|
+
} else if (typeof exitMatcher === 'string' && parent.type === exitMatcher) {
|
|
34
|
+
return undefined;
|
|
35
|
+
} else if (typeof exitMatcher === 'function' && exitMatcher(parent)) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return getAncestor(parent, matcher, exitMatcher);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isBlockStatement(node: Node) {
|
|
42
|
+
return node.type.endsWith('Statement') || node.type.endsWith('Declaration');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getEnclosingStatement(node: Node) {
|
|
46
|
+
return getAncestor(node, isBlockStatement);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getEnclosingScopeNode(node: Node) {
|
|
50
|
+
return getAncestor(node, (parentNode) =>
|
|
51
|
+
['FunctionExpression', 'FunctionDeclaration', 'ArrowFunctionExpression', 'Program'].includes(parentNode.type),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function isUsedInArrayOrAsArgument(node: Node) {
|
|
56
|
+
if (isBlockStatement(node)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const parent = getParent(node);
|
|
61
|
+
if (!parent) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
parent.type === 'ArrayExpression' ||
|
|
67
|
+
(parent.type === 'CallExpression' && parent.arguments.includes(node as Expression))
|
|
68
|
+
) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// recurse up the tree until hitting a block statement
|
|
73
|
+
return isUsedInArrayOrAsArgument(parent);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getEnclosingFunction(node: Node) {
|
|
77
|
+
if (
|
|
78
|
+
node.type === 'FunctionDeclaration' ||
|
|
79
|
+
node.type === 'FunctionExpression' ||
|
|
80
|
+
node.type === 'ArrowFunctionExpression'
|
|
81
|
+
) {
|
|
82
|
+
return node;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const parent = getParent(node);
|
|
86
|
+
if (!parent) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
return getEnclosingFunction(parent);
|
|
90
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// tree.ts
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Copyright (c) 2021-2024 Check Digit, LLC
|
|
5
|
+
*
|
|
6
|
+
* This code is licensed under the MIT license (see LICENSE.txt for details).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils';
|
|
10
|
+
|
|
11
|
+
type NodeParent = TSESTree.Node | undefined | null;
|
|
12
|
+
|
|
13
|
+
interface NodeParentExtension {
|
|
14
|
+
parent: NodeParent;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getParent(node: TSESTree.Node): TSESTree.Node | undefined | null {
|
|
18
|
+
return (node as unknown as NodeParentExtension).parent;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getAncestor(
|
|
22
|
+
node: TSESTree.Node,
|
|
23
|
+
matcher: AST_NODE_TYPES | ((testNode: TSESTree.Node) => boolean),
|
|
24
|
+
exitMatcher?: AST_NODE_TYPES | ((testNode: TSESTree.Node) => boolean),
|
|
25
|
+
): TSESTree.Node | undefined {
|
|
26
|
+
const parent = getParent(node);
|
|
27
|
+
if (!parent) {
|
|
28
|
+
return undefined;
|
|
29
|
+
} else if (typeof matcher === 'string' && parent.type === matcher) {
|
|
30
|
+
return parent;
|
|
31
|
+
} else if (typeof matcher === 'function' && matcher(parent)) {
|
|
32
|
+
return parent;
|
|
33
|
+
} else if (typeof exitMatcher === 'string' && parent.type === exitMatcher) {
|
|
34
|
+
return undefined;
|
|
35
|
+
} else if (typeof exitMatcher === 'function' && exitMatcher(parent)) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return getAncestor(parent, matcher, exitMatcher);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isBlockStatement(node: TSESTree.Node) {
|
|
42
|
+
return node.type.endsWith('Statement') || node.type.endsWith('Declaration');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getEnclosingStatement(node: TSESTree.Node) {
|
|
46
|
+
return getAncestor(node, isBlockStatement);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getEnclosingScopeNode(node: TSESTree.Node) {
|
|
50
|
+
return getAncestor(node, (parentNode) =>
|
|
51
|
+
['FunctionExpression', 'FunctionDeclaration', 'ArrowFunctionExpression', 'Program'].includes(parentNode.type),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function isUsedInArrayOrAsArgument(node: TSESTree.Node) {
|
|
56
|
+
if (isBlockStatement(node)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const parent = getParent(node);
|
|
61
|
+
if (!parent) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
parent.type === AST_NODE_TYPES.ArrayExpression ||
|
|
67
|
+
(parent.type === AST_NODE_TYPES.CallExpression && parent.arguments.includes(node as TSESTree.Expression))
|
|
68
|
+
) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// recurse up the tree until hitting a block statement
|
|
73
|
+
return isUsedInArrayOrAsArgument(parent);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getEnclosingFunction(node: TSESTree.Node) {
|
|
77
|
+
if (
|
|
78
|
+
node.type === AST_NODE_TYPES.FunctionDeclaration ||
|
|
79
|
+
node.type === AST_NODE_TYPES.FunctionExpression ||
|
|
80
|
+
node.type === AST_NODE_TYPES.ArrowFunctionExpression
|
|
81
|
+
) {
|
|
82
|
+
return node;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const parent = getParent(node);
|
|
86
|
+
if (!parent) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
return getEnclosingFunction(parent);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getTypeParentNode(
|
|
93
|
+
node: TSESTree.Node | undefined,
|
|
94
|
+
): TSESTree.TSTypeAnnotation | TSESTree.TSAsExpression | undefined {
|
|
95
|
+
if (!node) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
return node.type === AST_NODE_TYPES.TSTypeAnnotation || node.type === AST_NODE_TYPES.TSAsExpression
|
|
99
|
+
? node
|
|
100
|
+
: getTypeParentNode(node.parent);
|
|
101
|
+
}
|
package/src/no-enum.ts
CHANGED
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
// no-enum.ts
|
|
2
2
|
|
|
3
|
-
import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
|
|
4
|
-
|
|
5
3
|
/*
|
|
6
4
|
* Copyright (c) 2021-2024 Check Digit, LLC
|
|
7
5
|
*
|
|
8
6
|
* This code is licensed under the MIT license (see LICENSE.txt for details).
|
|
9
7
|
*/
|
|
10
8
|
|
|
9
|
+
import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
|
|
10
|
+
|
|
11
11
|
export const ruleId = 'no-enum';
|
|
12
12
|
const NO_ENUM = 'NO_ENUM';
|
|
13
13
|
|
|
14
14
|
const createRule = ESLintUtils.RuleCreator((name) => name);
|
|
15
15
|
|
|
16
|
+
function isJsonSchemaProperty(node?: TSESTree.Node): boolean {
|
|
17
|
+
if (!node) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (
|
|
21
|
+
node.type === TSESTree.AST_NODE_TYPES.Property &&
|
|
22
|
+
node.key.type === TSESTree.AST_NODE_TYPES.Identifier &&
|
|
23
|
+
node.key.name === 'properties'
|
|
24
|
+
) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return isJsonSchemaProperty(node.parent);
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
const rule = createRule({
|
|
17
31
|
name: ruleId,
|
|
18
32
|
meta: {
|
|
@@ -38,7 +52,8 @@ const rule = createRule({
|
|
|
38
52
|
if (
|
|
39
53
|
node.key.type === TSESTree.AST_NODE_TYPES.Identifier &&
|
|
40
54
|
node.key.name === 'enum' &&
|
|
41
|
-
node.value.type === TSESTree.AST_NODE_TYPES.ArrayExpression
|
|
55
|
+
node.value.type === TSESTree.AST_NODE_TYPES.ArrayExpression &&
|
|
56
|
+
!isJsonSchemaProperty(node.parent)
|
|
42
57
|
) {
|
|
43
58
|
context.report({
|
|
44
59
|
node,
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// require-resolve-full-response.ts
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Copyright (c) 2021-2024 Check Digit, LLC
|
|
5
|
+
*
|
|
6
|
+
* This code is licensed under the MIT license (see LICENSE.txt for details).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils';
|
|
10
|
+
import { DefinitionType, type Scope } from '@typescript-eslint/scope-manager';
|
|
11
|
+
import { strict as assert } from 'node:assert';
|
|
12
|
+
import getDocumentationUrl from './get-documentation-url';
|
|
13
|
+
import { getEnclosingScopeNode } from './library/ts-tree';
|
|
14
|
+
|
|
15
|
+
export const ruleId = 'require-resolve-full-response';
|
|
16
|
+
export const PLAIN_URL_REGEXP = /^[`']\/\w+(?<serviceNamePart>-\w+)*\/v\d+\/(?<any>.|\r|\n)+[`']$/u;
|
|
17
|
+
export const TOKENIZED_URL_REGEXP = /^`\$\{(?<serviceNamePart>[A-Z]+_)*BASE_PATH\}\/(?<any>.|\r|\n)+`$/u;
|
|
18
|
+
|
|
19
|
+
const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
|
|
20
|
+
|
|
21
|
+
const rule = createRule({
|
|
22
|
+
name: ruleId,
|
|
23
|
+
meta: {
|
|
24
|
+
type: 'suggestion',
|
|
25
|
+
docs: {
|
|
26
|
+
description: 'Prefer native fetch over customized service wrapper.',
|
|
27
|
+
},
|
|
28
|
+
messages: {
|
|
29
|
+
invalidOptions:
|
|
30
|
+
'"options" argument should be provided with "resolveWithFullResponse" property set as "true". Otherwise, it indicates that the response body will be obtained without status code assertion which could result in unexpected issue.',
|
|
31
|
+
unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.',
|
|
32
|
+
},
|
|
33
|
+
schema: [],
|
|
34
|
+
},
|
|
35
|
+
defaultOptions: [],
|
|
36
|
+
create(context) {
|
|
37
|
+
const sourceCode = context.sourceCode;
|
|
38
|
+
const scopeManager = sourceCode.scopeManager;
|
|
39
|
+
const parserService = ESLintUtils.getParserServices(context);
|
|
40
|
+
const typeChecker = parserService.program.getTypeChecker();
|
|
41
|
+
|
|
42
|
+
function isUrlArgumentValid(urlArgument: TSESTree.Node | undefined, scope: Scope) {
|
|
43
|
+
if (
|
|
44
|
+
(urlArgument?.type === AST_NODE_TYPES.Literal && typeof urlArgument.value === 'string') ||
|
|
45
|
+
urlArgument?.type === AST_NODE_TYPES.TemplateLiteral
|
|
46
|
+
) {
|
|
47
|
+
const urlText = sourceCode.getText(urlArgument);
|
|
48
|
+
return PLAIN_URL_REGEXP.test(urlText) || TOKENIZED_URL_REGEXP.test(urlText);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (urlArgument?.type === AST_NODE_TYPES.Identifier) {
|
|
52
|
+
const foundVariable = scope.variables.find((variable) => variable.name === urlArgument.name);
|
|
53
|
+
if (foundVariable) {
|
|
54
|
+
const variableDefinition = foundVariable.defs.find((def) => def.type === DefinitionType.Variable);
|
|
55
|
+
assert.ok(variableDefinition, `Variable "${urlArgument.name}" not defined in scope`);
|
|
56
|
+
const variableDefinitionNode = variableDefinition.node;
|
|
57
|
+
assert.ok(variableDefinitionNode.type === AST_NODE_TYPES.VariableDeclarator);
|
|
58
|
+
assert.ok(variableDefinitionNode.init, 'Variable definition node has no init property');
|
|
59
|
+
return isUrlArgumentValid(variableDefinitionNode.init, scope);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getType(identifier: TSESTree.Identifier) {
|
|
67
|
+
const variable = parserService.esTreeNodeToTSNodeMap.get(identifier);
|
|
68
|
+
const variableType = typeChecker.getTypeAtLocation(variable);
|
|
69
|
+
return typeChecker.typeToString(variableType);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isServiceLikeName(name: string) {
|
|
73
|
+
return /.*[Ss]ervice$/u.test(name);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isCalleeServiceWrapper(serviceCall: TSESTree.CallExpression) {
|
|
77
|
+
const callee = serviceCall.callee;
|
|
78
|
+
if (callee.type !== AST_NODE_TYPES.MemberExpression) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const endpoint = callee.object;
|
|
83
|
+
if (endpoint.type === AST_NODE_TYPES.Identifier) {
|
|
84
|
+
return getType(endpoint) === 'Endpoint' || isServiceLikeName(endpoint.name);
|
|
85
|
+
}
|
|
86
|
+
if (endpoint.type !== AST_NODE_TYPES.CallExpression) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const [contextArgument] = endpoint.arguments;
|
|
91
|
+
if (contextArgument?.type !== AST_NODE_TYPES.Identifier) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (contextArgument.name !== 'EMPTY_CONTEXT' && getType(contextArgument) !== 'InboundContext') {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const service = endpoint.callee;
|
|
98
|
+
if (service.type === AST_NODE_TYPES.Identifier) {
|
|
99
|
+
return getType(service) === 'ResolvedService';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (service.type !== AST_NODE_TYPES.MemberExpression) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
const services = service.object;
|
|
106
|
+
if (services.type === AST_NODE_TYPES.Identifier) {
|
|
107
|
+
return getType(services) === 'ResolvedServices';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (services.type !== AST_NODE_TYPES.MemberExpression) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
const configuration = services.object;
|
|
114
|
+
if (configuration.type === AST_NODE_TYPES.Identifier) {
|
|
115
|
+
return ['Configuration', 'Configuration<ResolvedServices>'].includes(getType(configuration));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// following applies only to test code (fixture)
|
|
119
|
+
if (configuration.type !== AST_NODE_TYPES.MemberExpression) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
const fixture = configuration.object;
|
|
123
|
+
if (fixture.type === AST_NODE_TYPES.Identifier) {
|
|
124
|
+
return fixture.name === 'fixture' || getType(fixture) === 'Fixture';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
'CallExpression[callee.property.name=/^(head|get|put|post|del|patch)$/]': (
|
|
132
|
+
serviceCall: TSESTree.CallExpression,
|
|
133
|
+
) => {
|
|
134
|
+
try {
|
|
135
|
+
if (!isCalleeServiceWrapper(serviceCall)) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const enclosingScopeNode = getEnclosingScopeNode(serviceCall);
|
|
140
|
+
assert.ok(enclosingScopeNode, 'enclosingScopeNode is undefined');
|
|
141
|
+
const scope = scopeManager?.acquire(enclosingScopeNode);
|
|
142
|
+
assert.ok(scope, 'scope is undefined');
|
|
143
|
+
const urlArgument = serviceCall.arguments[0];
|
|
144
|
+
if (!isUrlArgumentValid(urlArgument, scope)) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
assert.ok(serviceCall.callee.type === AST_NODE_TYPES.MemberExpression);
|
|
149
|
+
assert.ok(serviceCall.callee.property.type === AST_NODE_TYPES.Identifier);
|
|
150
|
+
|
|
151
|
+
// method
|
|
152
|
+
const method = serviceCall.callee.property.name;
|
|
153
|
+
|
|
154
|
+
// options
|
|
155
|
+
const optionsArgument = ['get', 'head', 'del'].includes(method)
|
|
156
|
+
? serviceCall.arguments[1]
|
|
157
|
+
: serviceCall.arguments[2];
|
|
158
|
+
if (optionsArgument === undefined || optionsArgument.type !== AST_NODE_TYPES.ObjectExpression) {
|
|
159
|
+
context.report({
|
|
160
|
+
node: serviceCall,
|
|
161
|
+
messageId: 'invalidOptions',
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const resolveWithFullResponseProperty = optionsArgument.properties.find(
|
|
167
|
+
(property) =>
|
|
168
|
+
property.type === AST_NODE_TYPES.Property &&
|
|
169
|
+
property.key.type === AST_NODE_TYPES.Identifier &&
|
|
170
|
+
property.key.name === 'resolveWithFullResponse',
|
|
171
|
+
);
|
|
172
|
+
if (
|
|
173
|
+
resolveWithFullResponseProperty?.type !== AST_NODE_TYPES.Property ||
|
|
174
|
+
resolveWithFullResponseProperty.value.type !== AST_NODE_TYPES.Literal ||
|
|
175
|
+
resolveWithFullResponseProperty.value.value !== true
|
|
176
|
+
) {
|
|
177
|
+
context.report({
|
|
178
|
+
node: optionsArgument,
|
|
179
|
+
messageId: 'invalidOptions',
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
} catch (error) {
|
|
184
|
+
// eslint-disable-next-line no-console
|
|
185
|
+
console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
|
|
186
|
+
context.report({
|
|
187
|
+
node: serviceCall,
|
|
188
|
+
messageId: 'unknownError',
|
|
189
|
+
data: {
|
|
190
|
+
fileName: context.filename,
|
|
191
|
+
error: error instanceof Error ? error.toString() : JSON.stringify(error),
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
export default rule;
|