@checkdigit/eslint-plugin 7.17.1 → 8.0.0-PR.141-7ea9

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 (64) hide show
  1. package/dist-mjs/aws/is-aws-sdk-v3-used.mjs +7 -3
  2. package/dist-mjs/aws/require-aws-bare-bones.mjs +23 -5
  3. package/dist-mjs/aws/require-aws-config.mjs +5 -2
  4. package/dist-mjs/aws/require-consistent-read.mjs +12 -4
  5. package/dist-mjs/file-path-comment.mjs +15 -6
  6. package/dist-mjs/index.mjs +25 -9
  7. package/dist-mjs/invalid-json-stringify.mjs +8 -3
  8. package/dist-mjs/library/format.mjs +1 -1
  9. package/dist-mjs/library/tree.mjs +7 -2
  10. package/dist-mjs/library/ts-tree.mjs +7 -2
  11. package/dist-mjs/no-card-numbers.mjs +1 -1
  12. package/dist-mjs/no-duplicated-imports.mjs +25 -7
  13. package/dist-mjs/no-legacy-service-typing.mjs +18 -7
  14. package/dist-mjs/no-promise-instance-method.mjs +1 -1
  15. package/dist-mjs/no-random-v4-uuid.mjs +5 -2
  16. package/dist-mjs/no-side-effects.mjs +11 -7
  17. package/dist-mjs/no-status-code-assert.mjs +5 -2
  18. package/dist-mjs/no-test-import.mjs +5 -2
  19. package/dist-mjs/no-util.mjs +1 -1
  20. package/dist-mjs/no-uuid.mjs +1 -1
  21. package/dist-mjs/no-wallaby-comment.mjs +25 -7
  22. package/dist-mjs/object-literal-response.mjs +1 -1
  23. package/dist-mjs/regular-expression-comment.mjs +4 -2
  24. package/dist-mjs/require-assert-message.mjs +1 -1
  25. package/dist-mjs/require-assert-predicate-rejects-throws.mjs +1 -1
  26. package/dist-mjs/require-fixed-services-import.mjs +21 -6
  27. package/dist-mjs/require-resolve-full-response.mjs +32 -11
  28. package/dist-mjs/require-service-call-response-declaration.mjs +12 -4
  29. package/dist-mjs/require-strict-assert.mjs +5 -2
  30. package/dist-mjs/require-ts-extension-imports-exports.mjs +1 -1
  31. package/dist-mjs/require-type-out-of-type-only-imports.mjs +6 -2
  32. package/dist-types/no-legacy-service-typing.d.ts +3 -1
  33. package/package.json +1 -96
  34. package/src/aws/is-aws-sdk-v3-used.ts +6 -2
  35. package/src/aws/require-aws-bare-bones.ts +65 -42
  36. package/src/aws/require-aws-config.ts +85 -63
  37. package/src/aws/require-consistent-read.ts +97 -60
  38. package/src/file-path-comment.ts +12 -3
  39. package/src/index.ts +28 -10
  40. package/src/invalid-json-stringify.ts +9 -3
  41. package/src/library/format.ts +4 -1
  42. package/src/library/tree.ts +8 -2
  43. package/src/library/ts-tree.ts +24 -7
  44. package/src/no-card-numbers.ts +6 -1
  45. package/src/no-duplicated-imports.ts +48 -18
  46. package/src/no-legacy-service-typing.ts +25 -8
  47. package/src/no-promise-instance-method.ts +8 -3
  48. package/src/no-random-v4-uuid.ts +36 -11
  49. package/src/no-side-effects.ts +78 -29
  50. package/src/no-status-code-assert.ts +21 -7
  51. package/src/no-test-import.ts +8 -2
  52. package/src/no-util.ts +3 -1
  53. package/src/no-uuid.ts +8 -2
  54. package/src/no-wallaby-comment.ts +40 -9
  55. package/src/object-literal-response.ts +25 -9
  56. package/src/regular-expression-comment.ts +9 -3
  57. package/src/require-assert-message.ts +13 -5
  58. package/src/require-assert-predicate-rejects-throws.ts +6 -3
  59. package/src/require-fixed-services-import.ts +31 -10
  60. package/src/require-resolve-full-response.ts +221 -172
  61. package/src/require-service-call-response-declaration.ts +23 -8
  62. package/src/require-strict-assert.ts +21 -8
  63. package/src/require-ts-extension-imports-exports.ts +19 -6
  64. package/src/require-type-out-of-type-only-imports.ts +12 -4
@@ -22,11 +22,13 @@ const rule: TSESLint.RuleModule<string, unknown[]> = createRule({
22
22
  meta: {
23
23
  type: 'problem',
24
24
  docs: {
25
- description: 'Validate that message argument is always supplied to node:assert methods',
25
+ description:
26
+ 'Validate that message argument is always supplied to node:assert methods',
26
27
  },
27
28
  schema: [],
28
29
  messages: {
29
- [MISSING_ASSERT_MESSAGE]: 'Missing message argument in {{methodName}}() method.',
30
+ [MISSING_ASSERT_MESSAGE]:
31
+ 'Missing message argument in {{methodName}}() method.',
30
32
  },
31
33
  },
32
34
  defaultOptions: [],
@@ -39,7 +41,8 @@ const rule: TSESLint.RuleModule<string, unknown[]> = createRule({
39
41
  if (node.source.value === 'node:assert') {
40
42
  const specifier = node.specifiers.find(
41
43
  (importSpecifier) =>
42
- importSpecifier.type === TSESTree.AST_NODE_TYPES.ImportDefaultSpecifier ||
44
+ importSpecifier.type ===
45
+ TSESTree.AST_NODE_TYPES.ImportDefaultSpecifier ||
43
46
  importSpecifier.type === TSESTree.AST_NODE_TYPES.ImportSpecifier,
44
47
  );
45
48
  if (specifier) {
@@ -61,11 +64,16 @@ const rule: TSESLint.RuleModule<string, unknown[]> = createRule({
61
64
  if (!(methodName in messageIndexCache)) {
62
65
  const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
63
66
  const signature = checker.getResolvedSignature(tsNode);
64
- messageIndexCache[methodName] = signature?.getParameters().findIndex((param) => param.name === 'message');
67
+ messageIndexCache[methodName] = signature
68
+ ?.getParameters()
69
+ .findIndex((param) => param.name === 'message');
65
70
  }
66
71
 
67
72
  const messageIndex = messageIndexCache[methodName];
68
- if (messageIndex !== undefined && node.arguments.length <= messageIndex) {
73
+ if (
74
+ messageIndex !== undefined &&
75
+ node.arguments.length <= messageIndex
76
+ ) {
69
77
  context.report({
70
78
  node,
71
79
  messageId: MISSING_ASSERT_MESSAGE,
@@ -12,7 +12,8 @@ export default {
12
12
  meta: {
13
13
  type: 'problem',
14
14
  docs: {
15
- description: 'Validate that assert Predicate is always supplied to node:assert rejects,throws methods',
15
+ description:
16
+ 'Validate that assert Predicate is always supplied to node:assert rejects,throws methods',
16
17
  url: 'https://github.com/checkdigit/eslint-plugin',
17
18
  },
18
19
  },
@@ -39,7 +40,8 @@ export default {
39
40
  callee.object.type === 'Identifier' &&
40
41
  callee.object.name === assertIdentifier &&
41
42
  callee.property.type === 'Identifier' &&
42
- (callee.property.name === 'rejects' || callee.property.name === 'throws') &&
43
+ (callee.property.name === 'rejects' ||
44
+ callee.property.name === 'throws') &&
43
45
  node.arguments.length >= 1
44
46
  ) {
45
47
  const assertPredicate = node.arguments[1];
@@ -54,7 +56,8 @@ export default {
54
56
  ) {
55
57
  context.report({
56
58
  node,
57
- message: 'Second argument in {{method}} method should be of type AssertPredicate.',
59
+ message:
60
+ 'Second argument in {{method}} method should be of type AssertPredicate.',
58
61
  data: {
59
62
  method: callee.property.name,
60
63
  },
@@ -9,31 +9,43 @@
9
9
  import { strict as assert } from 'node:assert';
10
10
  import path from 'node:path';
11
11
 
12
- import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils';
12
+ import {
13
+ AST_NODE_TYPES,
14
+ ESLintUtils,
15
+ TSESTree,
16
+ } from '@typescript-eslint/utils';
13
17
 
14
18
  import getDocumentationUrl from './get-documentation-url.ts';
15
19
 
16
20
  export const ruleId = 'require-fixed-services-import';
17
21
 
18
22
  const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
19
- const SERVICE_TYPINGS_IMPORT_PATH = /(?<path>\.\.\/)+services(?!\/index(?:\.ts)?)\/.*/u;
23
+ const SERVICE_TYPINGS_IMPORT_PATH =
24
+ /(?<path>\.\.\/)+services(?!\/index(?:\.ts)?)\/.*/u;
20
25
  const SERVICE_TYPINGS_IMPORT_PATH_WITH_VERSION =
26
+ // Groups are destructed in the code
27
+ // eslint-disable-next-line sonarjs/unused-named-groups
21
28
  /(?<path>\.\.\/)+services\/(?<service>\w+)\/(?<version>v\d+)(?<index>\/index(?:\.ts)?)?/u;
22
29
 
23
30
  const rule: ESLintUtils.RuleModule<
24
- 'updateServicesImportSpecifier' | 'updateServicesImportSource' | 'renameServiceTypeReference'
31
+ | 'updateServicesImportSpecifier'
32
+ | 'updateServicesImportSource'
33
+ | 'renameServiceTypeReference'
25
34
  > = createRule({
26
35
  name: ruleId,
27
36
  meta: {
28
37
  type: 'suggestion',
29
38
  docs: {
30
- description: 'Require fixed "from" with service typing imports from "src/services".',
39
+ description:
40
+ 'Require fixed "from" with service typing imports from "src/services".',
31
41
  },
32
42
  messages: {
33
43
  updateServicesImportSpecifier:
34
44
  'Update service typing import specifiers to be from the corresponding service version namespace.',
35
- updateServicesImportSource: 'Update service typing imports to be from the fixed "src/services" path.',
36
- renameServiceTypeReference: 'Rename service type reference using the corresponding service version namespace.',
45
+ updateServicesImportSource:
46
+ 'Update service typing imports to be from the fixed "src/services" path.',
47
+ renameServiceTypeReference:
48
+ 'Rename service type reference using the corresponding service version namespace.',
37
49
  },
38
50
  fixable: 'code',
39
51
  schema: [],
@@ -53,7 +65,8 @@ const rule: ESLintUtils.RuleModule<
53
65
 
54
66
  if (SERVICE_TYPINGS_IMPORT_PATH.test(moduleName)) {
55
67
  // make sure that it matches only the src/services path
56
- const match = SERVICE_TYPINGS_IMPORT_PATH_WITH_VERSION.exec(moduleName);
68
+ const match =
69
+ SERVICE_TYPINGS_IMPORT_PATH_WITH_VERSION.exec(moduleName);
57
70
  if (match?.groups) {
58
71
  // need to import the service typings from the fixed path, and also apply the namespace to the referenced types
59
72
  const { service, version } = match.groups;
@@ -65,7 +78,10 @@ const rule: ESLintUtils.RuleModule<
65
78
  specifier.type === AST_NODE_TYPES.ImportSpecifier &&
66
79
  specifier.imported.type === AST_NODE_TYPES.Identifier
67
80
  ) {
68
- importedServiceTypeMapping.set(specifier.local.name, `${service}.${specifier.imported.name}`);
81
+ importedServiceTypeMapping.set(
82
+ specifier.local.name,
83
+ `${service}.${specifier.imported.name}`,
84
+ );
69
85
  }
70
86
  }
71
87
 
@@ -78,7 +94,10 @@ const rule: ESLintUtils.RuleModule<
78
94
  messageId: 'updateServicesImportSpecifier',
79
95
  node: importDeclaration.source,
80
96
  *fix(fixer) {
81
- yield fixer.replaceTextRange([rangeStart, rangeEnd], `${service}V${version.slice(1)} as ${service}`);
97
+ yield fixer.replaceTextRange(
98
+ [rangeStart, rangeEnd],
99
+ `${service}V${version.slice(1)} as ${service}`,
100
+ );
82
101
  },
83
102
  });
84
103
  }
@@ -102,7 +121,9 @@ const rule: ESLintUtils.RuleModule<
102
121
  typeReference.typeName.type === AST_NODE_TYPES.Identifier &&
103
122
  importedServiceTypeMapping.has(typeReference.typeName.name)
104
123
  ) {
105
- const renamedTypeName = importedServiceTypeMapping.get(typeReference.typeName.name);
124
+ const renamedTypeName = importedServiceTypeMapping.get(
125
+ typeReference.typeName.name,
126
+ );
106
127
  assert.ok(renamedTypeName !== undefined);
107
128
  context.report({
108
129
  messageId: 'renameServiceTypeReference',
@@ -7,210 +7,259 @@
7
7
  */
8
8
 
9
9
  import { strict as assert } from 'node:assert';
10
- import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils';
10
+ import {
11
+ AST_NODE_TYPES,
12
+ ESLintUtils,
13
+ TSESTree,
14
+ } from '@typescript-eslint/utils';
11
15
  import { DefinitionType, type Scope } from '@typescript-eslint/scope-manager';
12
16
  import getDocumentationUrl from './get-documentation-url.ts';
13
17
  import { getEnclosingScopeNode } from './library/ts-tree.ts';
14
18
 
15
19
  export const ruleId = 'require-resolve-full-response';
16
20
  // eslint-disable-next-line @typescript-eslint/no-inferrable-types
17
- export const PLAIN_URL_REGEXP: RegExp = /^[`']\/\w+(?<serviceNamePart>-\w+)*\/v\d+\/(?<any>.|\r|\n)+[`']$/u;
21
+ export const PLAIN_URL_REGEXP: RegExp =
22
+ /^[`']\/\w+(?<serviceNamePart>-\w+)*\/v\d+\/(?<any>.|\r|\n)+[`']$/u;
18
23
  // eslint-disable-next-line @typescript-eslint/no-inferrable-types
19
- export const TOKENIZED_URL_REGEXP: RegExp = /^`\$\{(?<serviceNamePart>[A-Z]+_)*BASE_PATH\}\/(?<any>.|\r|\n)+`$/u;
24
+ export const TOKENIZED_URL_REGEXP: RegExp =
25
+ /^`\$\{(?<serviceNamePart>[A-Z]+_)*BASE_PATH\}\/(?<any>.|\r|\n)+`$/u;
20
26
 
21
27
  const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
22
28
 
23
- const rule: ESLintUtils.RuleModule<'invalidOptions' | 'unknownError'> = createRule({
24
- name: ruleId,
25
- meta: {
26
- type: 'suggestion',
27
- docs: {
28
- description: 'Prefer native fetch over customized service wrapper.',
29
- },
30
- messages: {
31
- invalidOptions:
32
- '"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.',
33
- unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.',
29
+ const rule: ESLintUtils.RuleModule<'invalidOptions' | 'unknownError'> =
30
+ createRule({
31
+ name: ruleId,
32
+ meta: {
33
+ type: 'suggestion',
34
+ docs: {
35
+ description: 'Prefer native fetch over customized service wrapper.',
36
+ },
37
+ messages: {
38
+ invalidOptions:
39
+ '"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.',
40
+ unknownError:
41
+ 'Unknown error occurred in file "{{fileName}}": {{ error }}.',
42
+ },
43
+ schema: [],
34
44
  },
35
- schema: [],
36
- },
37
- defaultOptions: [],
38
- create(context) {
39
- const sourceCode = context.sourceCode;
40
- const scopeManager = sourceCode.scopeManager;
41
- const parserService = ESLintUtils.getParserServices(context);
42
- const typeChecker = parserService.program.getTypeChecker();
43
-
44
- function isUrlArgumentValid(urlArgument: TSESTree.Node | undefined, scope: Scope) {
45
- if (
46
- (urlArgument?.type === AST_NODE_TYPES.Literal && typeof urlArgument.value === 'string') ||
47
- urlArgument?.type === AST_NODE_TYPES.TemplateLiteral
45
+ // eslint-disable-next-line max-lines-per-function
46
+ create(context) {
47
+ const sourceCode = context.sourceCode;
48
+ const scopeManager = sourceCode.scopeManager;
49
+ const parserService = ESLintUtils.getParserServices(context);
50
+ const typeChecker = parserService.program.getTypeChecker();
51
+
52
+ function isUrlArgumentValid(
53
+ urlArgument: TSESTree.Node | undefined,
54
+ scope: Scope,
48
55
  ) {
49
- const urlText = sourceCode.getText(urlArgument);
50
- return PLAIN_URL_REGEXP.test(urlText) || TOKENIZED_URL_REGEXP.test(urlText);
51
- }
56
+ if (
57
+ (urlArgument?.type === AST_NODE_TYPES.Literal &&
58
+ typeof urlArgument.value === 'string') ||
59
+ urlArgument?.type === AST_NODE_TYPES.TemplateLiteral
60
+ ) {
61
+ const urlText = sourceCode.getText(urlArgument);
62
+ return (
63
+ PLAIN_URL_REGEXP.test(urlText) || TOKENIZED_URL_REGEXP.test(urlText)
64
+ );
65
+ }
52
66
 
53
- if (urlArgument?.type === AST_NODE_TYPES.Identifier) {
54
- const foundVariable = scope.variables.find((variable) => variable.name === urlArgument.name);
55
- if (foundVariable) {
56
- const variableDefinition = foundVariable.defs.find((def) => def.type === DefinitionType.Variable);
57
- if (variableDefinition) {
58
- const variableDefinitionNode = variableDefinition.node;
59
- assert.ok(variableDefinitionNode.init, 'Variable definition node has no init property');
60
- return isUrlArgumentValid(variableDefinitionNode.init, scope);
67
+ if (urlArgument?.type === AST_NODE_TYPES.Identifier) {
68
+ const foundVariable = scope.variables.find(
69
+ (variable) => variable.name === urlArgument.name,
70
+ );
71
+ if (foundVariable) {
72
+ const variableDefinition = foundVariable.defs.find(
73
+ (def) => def.type === DefinitionType.Variable,
74
+ );
75
+ if (variableDefinition) {
76
+ const variableDefinitionNode = variableDefinition.node;
77
+ assert.ok(
78
+ variableDefinitionNode.init,
79
+ 'Variable definition node has no init property',
80
+ );
81
+ return isUrlArgumentValid(variableDefinitionNode.init, scope);
82
+ }
83
+ return true;
61
84
  }
62
- return true;
63
85
  }
64
- }
65
-
66
- return false;
67
- }
68
86
 
69
- function getType(identifier: TSESTree.Identifier) {
70
- const variable = parserService.esTreeNodeToTSNodeMap.get(identifier);
71
- const variableType = typeChecker.getTypeAtLocation(variable);
72
- return typeChecker.typeToString(variableType);
73
- }
74
-
75
- function isServiceLikeName(name: string) {
76
- return /.*[Ss]ervice$/u.test(name);
77
- }
78
-
79
- function isCalleeServiceWrapper(serviceCall: TSESTree.CallExpression) {
80
- const callee = serviceCall.callee;
81
- if (callee.type !== AST_NODE_TYPES.MemberExpression) {
82
87
  return false;
83
88
  }
84
89
 
85
- const endpoint = callee.object;
86
- if (endpoint.type === AST_NODE_TYPES.Identifier) {
87
- return getType(endpoint) === 'Endpoint' || isServiceLikeName(endpoint.name);
88
- }
89
- if (endpoint.type !== AST_NODE_TYPES.CallExpression) {
90
- return false;
90
+ function getType(identifier: TSESTree.Identifier) {
91
+ const variable = parserService.esTreeNodeToTSNodeMap.get(identifier);
92
+ const variableType = typeChecker.getTypeAtLocation(variable);
93
+ return typeChecker.typeToString(variableType);
91
94
  }
92
95
 
93
- const [contextArgument] = endpoint.arguments;
94
- if (contextArgument?.type !== AST_NODE_TYPES.Identifier) {
95
- return false;
96
- }
97
- if (contextArgument.name !== 'EMPTY_CONTEXT' && getType(contextArgument) !== 'InboundContext') {
98
- return false;
99
- }
100
- const service = endpoint.callee;
101
- if (service.type === AST_NODE_TYPES.Identifier) {
102
- return getType(service) === 'ResolvedService';
96
+ function isServiceLikeName(name: string) {
97
+ // eslint-disable-next-line sonarjs/slow-regex
98
+ return /.*[Ss]ervice$/u.test(name);
103
99
  }
104
100
 
105
- if (service.type !== AST_NODE_TYPES.MemberExpression) {
106
- return false;
107
- }
108
- const services = service.object;
109
- if (services.type === AST_NODE_TYPES.Identifier) {
110
- return getType(services) === 'ResolvedServices';
111
- }
101
+ function isCalleeServiceWrapper(serviceCall: TSESTree.CallExpression) {
102
+ const callee = serviceCall.callee;
103
+ if (callee.type !== AST_NODE_TYPES.MemberExpression) {
104
+ return false;
105
+ }
112
106
 
113
- if (services.type !== AST_NODE_TYPES.MemberExpression) {
114
- return false;
115
- }
116
- const configuration = services.object;
117
- if (configuration.type === AST_NODE_TYPES.Identifier) {
118
- return ['Configuration', 'Configuration<ResolvedServices>'].includes(getType(configuration));
119
- }
107
+ const endpoint = callee.object;
108
+ if (endpoint.type === AST_NODE_TYPES.Identifier) {
109
+ return (
110
+ getType(endpoint) === 'Endpoint' || isServiceLikeName(endpoint.name)
111
+ );
112
+ }
113
+ if (endpoint.type !== AST_NODE_TYPES.CallExpression) {
114
+ return false;
115
+ }
116
+
117
+ const [contextArgument] = endpoint.arguments;
118
+ if (contextArgument?.type !== AST_NODE_TYPES.Identifier) {
119
+ return false;
120
+ }
121
+ if (
122
+ contextArgument.name !== 'EMPTY_CONTEXT' &&
123
+ getType(contextArgument) !== 'InboundContext'
124
+ ) {
125
+ return false;
126
+ }
127
+ const service = endpoint.callee;
128
+ if (service.type === AST_NODE_TYPES.Identifier) {
129
+ return getType(service) === 'ResolvedService';
130
+ }
131
+
132
+ if (service.type !== AST_NODE_TYPES.MemberExpression) {
133
+ return false;
134
+ }
135
+ const services = service.object;
136
+ if (services.type === AST_NODE_TYPES.Identifier) {
137
+ return getType(services) === 'ResolvedServices';
138
+ }
139
+
140
+ if (services.type !== AST_NODE_TYPES.MemberExpression) {
141
+ return false;
142
+ }
143
+ const configuration = services.object;
144
+ if (configuration.type === AST_NODE_TYPES.Identifier) {
145
+ return ['Configuration', 'Configuration<ResolvedServices>'].includes(
146
+ getType(configuration),
147
+ );
148
+ }
149
+
150
+ // following applies only to test code (fixture)
151
+ if (configuration.type !== AST_NODE_TYPES.MemberExpression) {
152
+ return false;
153
+ }
154
+ const fixture = configuration.object;
155
+ if (fixture.type === AST_NODE_TYPES.Identifier) {
156
+ return fixture.name === 'fixture' || getType(fixture) === 'Fixture';
157
+ }
120
158
 
121
- // following applies only to test code (fixture)
122
- if (configuration.type !== AST_NODE_TYPES.MemberExpression) {
123
159
  return false;
124
160
  }
125
- const fixture = configuration.object;
126
- if (fixture.type === AST_NODE_TYPES.Identifier) {
127
- return fixture.name === 'fixture' || getType(fixture) === 'Fixture';
128
- }
129
161
 
130
- return false;
131
- }
162
+ return {
163
+ 'CallExpression[callee.property.name=/^(head|get|put|post|del|patch)$/]':
164
+ (serviceCall: TSESTree.CallExpression) => {
165
+ try {
166
+ if (!isCalleeServiceWrapper(serviceCall)) {
167
+ return;
168
+ }
132
169
 
133
- return {
134
- 'CallExpression[callee.property.name=/^(head|get|put|post|del|patch)$/]': (
135
- serviceCall: TSESTree.CallExpression,
136
- ) => {
137
- try {
138
- if (!isCalleeServiceWrapper(serviceCall)) {
139
- return;
140
- }
170
+ const enclosingScopeNode = getEnclosingScopeNode(serviceCall);
171
+ assert.ok(enclosingScopeNode, 'enclosingScopeNode is undefined');
172
+ const scope = scopeManager?.acquire(enclosingScopeNode);
173
+ assert.ok(scope, 'scope is undefined');
174
+ const urlArgument = serviceCall.arguments[0];
175
+ if (!isUrlArgumentValid(urlArgument, scope)) {
176
+ return;
177
+ }
141
178
 
142
- const enclosingScopeNode = getEnclosingScopeNode(serviceCall);
143
- assert.ok(enclosingScopeNode, 'enclosingScopeNode is undefined');
144
- const scope = scopeManager?.acquire(enclosingScopeNode);
145
- assert.ok(scope, 'scope is undefined');
146
- const urlArgument = serviceCall.arguments[0];
147
- if (!isUrlArgumentValid(urlArgument, scope)) {
148
- return;
149
- }
179
+ assert.ok(
180
+ serviceCall.callee.type === AST_NODE_TYPES.MemberExpression,
181
+ );
182
+ assert.ok(
183
+ serviceCall.callee.property.type === AST_NODE_TYPES.Identifier,
184
+ );
150
185
 
151
- assert.ok(serviceCall.callee.type === AST_NODE_TYPES.MemberExpression);
152
- assert.ok(serviceCall.callee.property.type === AST_NODE_TYPES.Identifier);
153
-
154
- // method
155
- const method = serviceCall.callee.property.name;
156
-
157
- // options
158
- const optionsArgument = ['get', 'head', 'del'].includes(method)
159
- ? serviceCall.arguments[1]
160
- : serviceCall.arguments[2];
161
- if (optionsArgument === undefined) {
162
- context.report({
163
- node: serviceCall,
164
- messageId: 'invalidOptions',
165
- });
166
- return;
167
- }
186
+ // method
187
+ const method = serviceCall.callee.property.name;
168
188
 
169
- if (optionsArgument.type === AST_NODE_TYPES.Identifier) {
170
- const optionsTypeString = getType(optionsArgument);
171
- if (optionsTypeString === 'FullResponseOptions') {
172
- return;
173
- }
174
- const variable = parserService.esTreeNodeToTSNodeMap.get(optionsArgument);
175
- const optionType = typeChecker.getTypeAtLocation(variable);
176
- const resolveWithFullResponseProperty = optionType.getProperty('resolveWithFullResponse');
177
- if (resolveWithFullResponseProperty?.declarations?.[0]?.getText() === 'resolveWithFullResponse: true') {
178
- return;
179
- }
180
- } else if (optionsArgument.type === AST_NODE_TYPES.ObjectExpression) {
181
- const resolveWithFullResponseProperty = optionsArgument.properties.find(
182
- (property) =>
183
- property.type === AST_NODE_TYPES.Property &&
184
- property.key.type === AST_NODE_TYPES.Identifier &&
185
- property.key.name === 'resolveWithFullResponse',
186
- );
187
- if (
188
- resolveWithFullResponseProperty?.type === AST_NODE_TYPES.Property &&
189
- resolveWithFullResponseProperty.value.type === AST_NODE_TYPES.Literal &&
190
- resolveWithFullResponseProperty.value.value === true
191
- ) {
192
- return;
189
+ // options
190
+ const optionsArgument = ['get', 'head', 'del'].includes(method)
191
+ ? serviceCall.arguments[1]
192
+ : serviceCall.arguments[2];
193
+ if (optionsArgument === undefined) {
194
+ context.report({
195
+ node: serviceCall,
196
+ messageId: 'invalidOptions',
197
+ });
198
+ return;
199
+ }
200
+
201
+ if (optionsArgument.type === AST_NODE_TYPES.Identifier) {
202
+ const optionsTypeString = getType(optionsArgument);
203
+ if (optionsTypeString === 'FullResponseOptions') {
204
+ return;
205
+ }
206
+ const variable =
207
+ parserService.esTreeNodeToTSNodeMap.get(optionsArgument);
208
+ const optionType = typeChecker.getTypeAtLocation(variable);
209
+ const resolveWithFullResponseProperty = optionType.getProperty(
210
+ 'resolveWithFullResponse',
211
+ );
212
+ if (
213
+ resolveWithFullResponseProperty?.declarations?.[0]?.getText() ===
214
+ 'resolveWithFullResponse: true'
215
+ ) {
216
+ return;
217
+ }
218
+ } else if (
219
+ optionsArgument.type === AST_NODE_TYPES.ObjectExpression
220
+ ) {
221
+ const resolveWithFullResponseProperty =
222
+ optionsArgument.properties.find(
223
+ (property) =>
224
+ property.type === AST_NODE_TYPES.Property &&
225
+ property.key.type === AST_NODE_TYPES.Identifier &&
226
+ property.key.name === 'resolveWithFullResponse',
227
+ );
228
+ if (
229
+ resolveWithFullResponseProperty?.type ===
230
+ AST_NODE_TYPES.Property &&
231
+ resolveWithFullResponseProperty.value.type ===
232
+ AST_NODE_TYPES.Literal &&
233
+ resolveWithFullResponseProperty.value.value === true
234
+ ) {
235
+ return;
236
+ }
237
+ }
238
+ context.report({
239
+ node: optionsArgument,
240
+ messageId: 'invalidOptions',
241
+ });
242
+ } catch (error) {
243
+ // eslint-disable-next-line no-console
244
+ console.error(
245
+ `Failed to apply ${ruleId} rule for file "${context.filename}":`,
246
+ error,
247
+ );
248
+ context.report({
249
+ node: serviceCall,
250
+ messageId: 'unknownError',
251
+ data: {
252
+ fileName: context.filename,
253
+ error:
254
+ error instanceof Error
255
+ ? error.toString()
256
+ : JSON.stringify(error),
257
+ },
258
+ });
193
259
  }
194
- }
195
- context.report({
196
- node: optionsArgument,
197
- messageId: 'invalidOptions',
198
- });
199
- } catch (error) {
200
- // eslint-disable-next-line no-console
201
- console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
202
- context.report({
203
- node: serviceCall,
204
- messageId: 'unknownError',
205
- data: {
206
- fileName: context.filename,
207
- error: error instanceof Error ? error.toString() : JSON.stringify(error),
208
- },
209
- });
210
- }
211
- },
212
- };
213
- },
214
- });
260
+ },
261
+ };
262
+ },
263
+ });
215
264
 
216
265
  export default rule;