@checkdigit/eslint-plugin 7.3.0-PR.75-f04b → 7.3.0-PR.89-2e8d

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 (81) hide show
  1. package/README.md +2 -1
  2. package/dist-mjs/agent/no-full-response.mjs +35 -0
  3. package/dist-mjs/{no-serve-runtime.mjs → agent/no-serve-runtime.mjs} +3 -3
  4. package/dist-mjs/index.mjs +51 -172
  5. package/dist-mjs/library/tree.mjs +2 -2
  6. package/dist-mjs/no-random-v4-uuid.mjs +67 -0
  7. package/dist-mjs/require-resolve-full-response.mjs +15 -30
  8. package/dist-types/agent/no-full-response.d.ts +4 -0
  9. package/dist-types/index.d.ts +2 -4
  10. package/dist-types/no-random-v4-uuid.d.ts +5 -0
  11. package/package.json +1 -1
  12. package/src/agent/no-full-response.ts +41 -0
  13. package/src/{no-serve-runtime.ts → agent/no-serve-runtime.ts} +2 -2
  14. package/src/index.ts +50 -173
  15. package/src/library/tree.ts +0 -1
  16. package/src/no-random-v4-uuid.ts +92 -0
  17. package/src/require-resolve-full-response.ts +21 -38
  18. package/dist-mjs/agent/add-assert-import.mjs +0 -58
  19. package/dist-mjs/agent/add-base-path-const.mjs +0 -65
  20. package/dist-mjs/agent/add-base-path-import.mjs +0 -60
  21. package/dist-mjs/agent/add-url-domain.mjs +0 -61
  22. package/dist-mjs/agent/agent-test-wiring.mjs +0 -221
  23. package/dist-mjs/agent/fetch-response-body-json.mjs +0 -146
  24. package/dist-mjs/agent/fetch-response-header-getter.mjs +0 -117
  25. package/dist-mjs/agent/fetch-then.mjs +0 -269
  26. package/dist-mjs/agent/fetch.mjs +0 -38
  27. package/dist-mjs/agent/file.mjs +0 -43
  28. package/dist-mjs/agent/fix-function-call-arguments.mjs +0 -153
  29. package/dist-mjs/agent/no-fixture.mjs +0 -356
  30. package/dist-mjs/agent/no-mapped-response.mjs +0 -75
  31. package/dist-mjs/agent/no-service-wrapper.mjs +0 -185
  32. package/dist-mjs/agent/no-status-code.mjs +0 -59
  33. package/dist-mjs/agent/no-unused-function-argument.mjs +0 -79
  34. package/dist-mjs/agent/no-unused-imports.mjs +0 -81
  35. package/dist-mjs/agent/no-unused-service-variable.mjs +0 -74
  36. package/dist-mjs/agent/response-reference.mjs +0 -70
  37. package/dist-mjs/agent/url.mjs +0 -32
  38. package/dist-mjs/no-legacy-service-typing.mjs +0 -39
  39. package/dist-types/agent/add-assert-import.d.ts +0 -4
  40. package/dist-types/agent/add-base-path-const.d.ts +0 -4
  41. package/dist-types/agent/add-base-path-import.d.ts +0 -4
  42. package/dist-types/agent/add-url-domain.d.ts +0 -4
  43. package/dist-types/agent/agent-test-wiring.d.ts +0 -4
  44. package/dist-types/agent/fetch-response-body-json.d.ts +0 -4
  45. package/dist-types/agent/fetch-response-header-getter.d.ts +0 -4
  46. package/dist-types/agent/fetch-then.d.ts +0 -4
  47. package/dist-types/agent/fetch.d.ts +0 -5
  48. package/dist-types/agent/file.d.ts +0 -7
  49. package/dist-types/agent/fix-function-call-arguments.d.ts +0 -9
  50. package/dist-types/agent/no-fixture.d.ts +0 -4
  51. package/dist-types/agent/no-mapped-response.d.ts +0 -4
  52. package/dist-types/agent/no-service-wrapper.d.ts +0 -4
  53. package/dist-types/agent/no-status-code.d.ts +0 -4
  54. package/dist-types/agent/no-unused-function-argument.d.ts +0 -4
  55. package/dist-types/agent/no-unused-imports.d.ts +0 -4
  56. package/dist-types/agent/no-unused-service-variable.d.ts +0 -4
  57. package/dist-types/agent/response-reference.d.ts +0 -16
  58. package/dist-types/agent/url.d.ts +0 -4
  59. package/dist-types/no-legacy-service-typing.d.ts +0 -5
  60. package/src/agent/add-assert-import.ts +0 -74
  61. package/src/agent/add-base-path-const.ts +0 -81
  62. package/src/agent/add-base-path-import.ts +0 -69
  63. package/src/agent/add-url-domain.ts +0 -76
  64. package/src/agent/agent-test-wiring.ts +0 -273
  65. package/src/agent/fetch-response-body-json.ts +0 -197
  66. package/src/agent/fetch-response-header-getter.ts +0 -148
  67. package/src/agent/fetch-then.ts +0 -357
  68. package/src/agent/fetch.ts +0 -57
  69. package/src/agent/file.ts +0 -42
  70. package/src/agent/fix-function-call-arguments.ts +0 -200
  71. package/src/agent/no-fixture.ts +0 -512
  72. package/src/agent/no-mapped-response.ts +0 -84
  73. package/src/agent/no-service-wrapper.ts +0 -241
  74. package/src/agent/no-status-code.ts +0 -72
  75. package/src/agent/no-unused-function-argument.ts +0 -98
  76. package/src/agent/no-unused-imports.ts +0 -103
  77. package/src/agent/no-unused-service-variable.ts +0 -93
  78. package/src/agent/response-reference.ts +0 -129
  79. package/src/agent/url.ts +0 -32
  80. package/src/no-legacy-service-typing.ts +0 -49
  81. /package/dist-types/{no-serve-runtime.d.ts → agent/no-serve-runtime.d.ts} +0 -0
@@ -1,241 +0,0 @@
1
- // agent/no-service-wrapper.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 { strict as assert } from 'node:assert';
10
-
11
- import { DefinitionType, type Scope } from '@typescript-eslint/scope-manager';
12
- import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils';
13
-
14
- import getDocumentationUrl from '../get-documentation-url';
15
- import { getEnclosingScopeNode } from '../library/ts-tree';
16
- import { getIndentation } from '../library/format';
17
- import { isServiceApiCallUrl, replaceEndpointUrlPrefixWithDomain } from './url';
18
-
19
- export const ruleId = 'no-service-wrapper';
20
-
21
- const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
22
-
23
- const rule: ESLintUtils.RuleModule<'unknownError' | 'preferNativeFetch' | 'invalidOptions'> = createRule({
24
- name: ruleId,
25
- meta: {
26
- type: 'suggestion',
27
- docs: {
28
- description: 'Prefer native fetch over customized service wrapper.',
29
- },
30
- messages: {
31
- preferNativeFetch: 'Prefer native fetch over customized service wrapper.',
32
- invalidOptions:
33
- '"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. Please manually convert the usage of customized service wrapper call to native fetch.',
34
- unknownError:
35
- 'Unknown error occurred in file "{{fileName}}": {{ error }}. Please manually convert the usage of customized service wrapper call to native fetch.',
36
- },
37
- fixable: 'code',
38
- schema: [],
39
- },
40
- defaultOptions: [],
41
- create(context) {
42
- const sourceCode = context.sourceCode;
43
- const scopeManager = sourceCode.scopeManager;
44
- const parserService = ESLintUtils.getParserServices(context);
45
- const typeChecker = parserService.program.getTypeChecker();
46
-
47
- function isUrlArgumentValid(urlArgument: TSESTree.Node | undefined, scope: Scope) {
48
- if (
49
- (urlArgument?.type === AST_NODE_TYPES.Literal && typeof urlArgument.value === 'string') ||
50
- urlArgument?.type === AST_NODE_TYPES.TemplateLiteral
51
- ) {
52
- const urlText = sourceCode.getText(urlArgument);
53
- return isServiceApiCallUrl(urlText);
54
- }
55
-
56
- if (urlArgument?.type === AST_NODE_TYPES.Identifier) {
57
- const foundVariable = scope.variables.find((variable) => variable.name === urlArgument.name);
58
- if (foundVariable) {
59
- const variableDefinition = foundVariable.defs.find((def) => def.type === DefinitionType.Variable);
60
- if (variableDefinition !== undefined) {
61
- const variableDefinitionNode = variableDefinition.node;
62
- assert.ok(variableDefinitionNode.init, 'Variable definition node has no init property');
63
- return isUrlArgumentValid(variableDefinitionNode.init, scope);
64
- }
65
- return true;
66
- }
67
- }
68
-
69
- return false;
70
- }
71
-
72
- function getType(identifier: TSESTree.Identifier) {
73
- const variable = parserService.esTreeNodeToTSNodeMap.get(identifier);
74
- const variableType = typeChecker.getTypeAtLocation(variable);
75
- return typeChecker.typeToString(variableType);
76
- }
77
-
78
- function isServiceLikeName(name: string) {
79
- return /.*[Ss]ervice$/u.test(name);
80
- }
81
-
82
- function isCalleeServiceWrapper(serviceCall: TSESTree.CallExpression) {
83
- const callee = serviceCall.callee;
84
- if (callee.type !== AST_NODE_TYPES.MemberExpression) {
85
- return false;
86
- }
87
-
88
- const endpoint = callee.object;
89
- if (endpoint.type === AST_NODE_TYPES.Identifier) {
90
- return getType(endpoint) === 'Endpoint' || isServiceLikeName(endpoint.name);
91
- }
92
- if (endpoint.type !== AST_NODE_TYPES.CallExpression) {
93
- return false;
94
- }
95
-
96
- const [contextArgument] = endpoint.arguments;
97
- if (contextArgument?.type !== AST_NODE_TYPES.Identifier) {
98
- return false;
99
- }
100
- if (contextArgument.name !== 'EMPTY_CONTEXT' && getType(contextArgument) !== 'InboundContext') {
101
- return false;
102
- }
103
- const service = endpoint.callee;
104
- if (service.type === AST_NODE_TYPES.Identifier) {
105
- return getType(service) === 'ResolvedService';
106
- }
107
-
108
- if (service.type !== AST_NODE_TYPES.MemberExpression) {
109
- return false;
110
- }
111
- const services = service.object;
112
- if (services.type === AST_NODE_TYPES.Identifier) {
113
- return getType(services) === 'ResolvedServices';
114
- }
115
-
116
- if (services.type !== AST_NODE_TYPES.MemberExpression) {
117
- return false;
118
- }
119
- const configuration = services.object;
120
- if (configuration.type === AST_NODE_TYPES.Identifier) {
121
- return ['Configuration', 'Configuration<ResolvedServices>'].includes(getType(configuration));
122
- }
123
-
124
- // following applies only to test code (fixture)
125
- if (configuration.type !== AST_NODE_TYPES.MemberExpression) {
126
- return false;
127
- }
128
- const fixture = configuration.object;
129
- if (fixture.type === AST_NODE_TYPES.Identifier) {
130
- return fixture.name === 'fixture' || getType(fixture) === 'Fixture';
131
- }
132
-
133
- return false;
134
- }
135
-
136
- return {
137
- 'CallExpression[callee.property.name=/^(head|get|put|post|del|patch)$/]': (
138
- serviceCall: TSESTree.CallExpression,
139
- ) => {
140
- try {
141
- if (!isCalleeServiceWrapper(serviceCall)) {
142
- return;
143
- }
144
-
145
- const enclosingScopeNode = getEnclosingScopeNode(serviceCall);
146
- assert.ok(enclosingScopeNode, 'enclosingScopeNode is undefined');
147
- const scope = scopeManager?.acquire(enclosingScopeNode);
148
- assert.ok(scope, 'scope is undefined');
149
- const urlArgument = serviceCall.arguments[0];
150
- if (!isUrlArgumentValid(urlArgument, scope)) {
151
- return;
152
- }
153
-
154
- assert.ok(serviceCall.callee.type === AST_NODE_TYPES.MemberExpression);
155
- assert.ok(serviceCall.callee.property.type === AST_NODE_TYPES.Identifier);
156
-
157
- // method
158
- const method = serviceCall.callee.property.name;
159
-
160
- // body
161
- let requestBodyProperty = ['put', 'post', 'options'].includes(method) ? serviceCall.arguments[1] : undefined;
162
- if (
163
- requestBodyProperty !== undefined &&
164
- requestBodyProperty.type === AST_NODE_TYPES.Identifier &&
165
- requestBodyProperty.name === 'undefined'
166
- ) {
167
- requestBodyProperty = undefined;
168
- }
169
- // options
170
- const optionsArgument = ['get', 'head', 'del'].includes(method)
171
- ? serviceCall.arguments[1]
172
- : serviceCall.arguments[2];
173
- if (optionsArgument === undefined || optionsArgument.type !== AST_NODE_TYPES.ObjectExpression) {
174
- context.report({
175
- node: serviceCall,
176
- messageId: 'invalidOptions',
177
- });
178
- return;
179
- }
180
- const resolveWithFullResponseProperty = optionsArgument.properties.find(
181
- (property) =>
182
- property.type === AST_NODE_TYPES.Property &&
183
- property.key.type === AST_NODE_TYPES.Identifier &&
184
- property.key.name === 'resolveWithFullResponse',
185
- );
186
- if (
187
- resolveWithFullResponseProperty?.type !== AST_NODE_TYPES.Property ||
188
- resolveWithFullResponseProperty.value.type !== AST_NODE_TYPES.Literal ||
189
- resolveWithFullResponseProperty.value.value !== true
190
- ) {
191
- context.report({
192
- node: optionsArgument,
193
- messageId: 'invalidOptions',
194
- });
195
- return;
196
- }
197
-
198
- // headers
199
- const requestHeadersProperty = optionsArgument.properties.find(
200
- (property) =>
201
- property.type === AST_NODE_TYPES.Property &&
202
- property.key.type === AST_NODE_TYPES.Identifier &&
203
- property.key.name === 'headers',
204
- );
205
-
206
- context.report({
207
- messageId: 'preferNativeFetch',
208
- node: serviceCall,
209
- fix(fixer) {
210
- const url = sourceCode.getText(urlArgument);
211
- const replacedUrl = replaceEndpointUrlPrefixWithDomain(url);
212
- const indentation = getIndentation(serviceCall, sourceCode);
213
-
214
- const fetchText = [
215
- `fetch(${replacedUrl}, {`,
216
- ` method: '${method.toLowerCase() === 'del' ? 'DELETE' : method.toUpperCase()}',`,
217
- ...(requestHeadersProperty ? [` ${sourceCode.getText(requestHeadersProperty)},`] : []),
218
- ...(requestBodyProperty ? [` body: JSON.stringify(${sourceCode.getText(requestBodyProperty)}),`] : []),
219
- '})',
220
- ].join(`\n${indentation}`);
221
- return fixer.replaceText(serviceCall, fetchText);
222
- },
223
- });
224
- } catch (error) {
225
- // eslint-disable-next-line no-console
226
- console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
227
- context.report({
228
- node: serviceCall,
229
- messageId: 'unknownError',
230
- data: {
231
- fileName: context.filename,
232
- error: error instanceof Error ? error.toString() : JSON.stringify(error),
233
- },
234
- });
235
- }
236
- },
237
- };
238
- },
239
- });
240
-
241
- export default rule;
@@ -1,72 +0,0 @@
1
- // agent/no-status-code.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 { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
10
-
11
- import getDocumentationUrl from '../get-documentation-url';
12
-
13
- export const ruleId = 'no-status-code';
14
-
15
- const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
16
-
17
- const rule: ESLintUtils.RuleModule<'unknownError' | 'replaceStatusCode'> = createRule({
18
- name: ruleId,
19
- meta: {
20
- type: 'suggestion',
21
- docs: {
22
- description: 'Access the status code property of the fetch Response using "status" instead of "statusCode".',
23
- },
24
- messages: {
25
- replaceStatusCode: 'Replace "statusCode" with "status".',
26
- unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.',
27
- },
28
- fixable: 'code',
29
- schema: [],
30
- },
31
- defaultOptions: [],
32
- create(context) {
33
- const parserServices = ESLintUtils.getParserServices(context);
34
- const typeChecker = parserServices.program.getTypeChecker();
35
-
36
- return {
37
- 'MemberExpression[property.name="statusCode"]': (responseStatusCode: TSESTree.MemberExpression) => {
38
- try {
39
- const responseNode = parserServices.esTreeNodeToTSNodeMap.get(responseStatusCode.object);
40
- const responseType = typeChecker.getTypeAtLocation(responseNode);
41
-
42
- const shouldReplace =
43
- responseType.getProperties().some((symbol) => symbol.name === 'status') &&
44
- !responseType.getProperties().some((symbol) => symbol.name === 'statusCode');
45
-
46
- if (shouldReplace) {
47
- context.report({
48
- messageId: 'replaceStatusCode',
49
- node: responseStatusCode.property,
50
- fix(fixer) {
51
- return fixer.replaceText(responseStatusCode.property, 'status');
52
- },
53
- });
54
- }
55
- } catch (error) {
56
- // eslint-disable-next-line no-console
57
- console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
58
- context.report({
59
- node: responseStatusCode,
60
- messageId: 'unknownError',
61
- data: {
62
- fileName: context.filename,
63
- error: error instanceof Error ? error.toString() : JSON.stringify(error),
64
- },
65
- });
66
- }
67
- },
68
- };
69
- },
70
- });
71
-
72
- export default rule;
@@ -1,98 +0,0 @@
1
- // agent/no-unused-function-argument.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 { strict as assert } from 'node:assert';
10
-
11
- import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
12
- import type { Scope } from '@typescript-eslint/utils/ts-eslint';
13
-
14
- import getDocumentationUrl from '../get-documentation-url';
15
-
16
- export const ruleId = 'no-unused-function-argument';
17
-
18
- const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
19
-
20
- const rule: ESLintUtils.RuleModule<'unknownError' | 'removeUnusedFunctionArguments'> = createRule({
21
- name: ruleId,
22
- meta: {
23
- type: 'suggestion',
24
- docs: {
25
- description: 'Remove unused function arguments.',
26
- },
27
- messages: {
28
- removeUnusedFunctionArguments: 'Removing unused function arguments.',
29
- unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.',
30
- },
31
- fixable: 'code',
32
- schema: [],
33
- },
34
- defaultOptions: [],
35
- create(context) {
36
- const sourceCode = context.sourceCode;
37
-
38
- function isParameterUsed(parameter: TSESTree.Identifier, scope: Scope.Scope): boolean {
39
- return (
40
- scope.references.some((ref) => ref.identifier.name === parameter.name) ||
41
- scope.childScopes.some((childScope) => isParameterUsed(parameter, childScope))
42
- );
43
- }
44
-
45
- return {
46
- FunctionDeclaration(functionDeclaration: TSESTree.FunctionDeclaration) {
47
- try {
48
- const parameters = functionDeclaration.params;
49
- if (parameters.length === 0) {
50
- return;
51
- }
52
-
53
- const functionScope = sourceCode.getScope(functionDeclaration);
54
- const parametersToKeep = parameters.filter(
55
- (parameter) =>
56
- parameter.type !== TSESTree.AST_NODE_TYPES.Identifier || isParameterUsed(parameter, functionScope),
57
- );
58
- if (parametersToKeep.length === parameters.length) {
59
- return;
60
- }
61
-
62
- const updatedParameters = parametersToKeep.map((parameter) => sourceCode.getText(parameter)).join(', ');
63
- context.report({
64
- node: functionDeclaration,
65
- messageId: 'removeUnusedFunctionArguments',
66
- fix(fixer) {
67
- const firstParameter = parameters[0];
68
- const lastParameter = parameters.at(-1);
69
- assert.ok(firstParameter !== undefined && lastParameter !== undefined);
70
- const tokenAfterParameters = sourceCode.getTokenAfter(lastParameter);
71
-
72
- return fixer.replaceTextRange(
73
- [
74
- firstParameter.range[0],
75
- tokenAfterParameters?.value === ',' ? tokenAfterParameters.range[1] : lastParameter.range[1],
76
- ],
77
- updatedParameters,
78
- );
79
- },
80
- });
81
- } catch (error) {
82
- // eslint-disable-next-line no-console
83
- console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
84
- context.report({
85
- node: functionDeclaration,
86
- messageId: 'unknownError',
87
- data: {
88
- fileName: context.filename,
89
- error: error instanceof Error ? error.toString() : JSON.stringify(error),
90
- },
91
- });
92
- }
93
- },
94
- };
95
- },
96
- });
97
-
98
- export default rule;
@@ -1,103 +0,0 @@
1
- // agent/no-unused-imports.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 { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
10
- import type { Scope } from '@typescript-eslint/utils/ts-eslint';
11
-
12
- import getDocumentationUrl from '../get-documentation-url';
13
-
14
- export const ruleId = 'no-unused-imports';
15
-
16
- const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
17
-
18
- const rule: ESLintUtils.RuleModule<'unknownError' | 'removeUnusedImports'> = createRule({
19
- name: ruleId,
20
- meta: {
21
- type: 'suggestion',
22
- docs: {
23
- description: 'Remove unused imports.',
24
- },
25
- messages: {
26
- removeUnusedImports: 'Removing unused imports.',
27
- unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.',
28
- },
29
- fixable: 'code',
30
- schema: [],
31
- },
32
- defaultOptions: [],
33
- create(context) {
34
- const sourceCode = context.sourceCode;
35
-
36
- function isImportUsed(specifier: TSESTree.ImportClause, scope: Scope.Scope): boolean {
37
- return (
38
- specifier.type !== TSESTree.AST_NODE_TYPES.ImportSpecifier ||
39
- scope.references.some((ref) => ref.identifier.name === specifier.local.name) ||
40
- scope.childScopes.some((childScope) => isImportUsed(specifier, childScope))
41
- );
42
- }
43
-
44
- return {
45
- ImportDeclaration(importDeclaration) {
46
- try {
47
- const moduleName = importDeclaration.source.value;
48
- if (
49
- !importDeclaration.specifiers.every(
50
- (specifier) => specifier.type === TSESTree.AST_NODE_TYPES.ImportSpecifier,
51
- ) ||
52
- // [TODO:] move to meta schema
53
- !['@checkdigit/serve-runtime', '@checkdigit/fixture'].includes(moduleName)
54
- ) {
55
- return;
56
- }
57
-
58
- const originalSpecifiers = importDeclaration.specifiers;
59
- const scope = sourceCode.getScope(importDeclaration);
60
- const usedSpecifiers = originalSpecifiers.filter((specifier) => isImportUsed(specifier, scope));
61
- if (usedSpecifiers.length === originalSpecifiers.length) {
62
- return;
63
- }
64
-
65
- if (usedSpecifiers.length === 0) {
66
- context.report({
67
- messageId: 'removeUnusedImports',
68
- node: importDeclaration,
69
- *fix(fixer) {
70
- yield fixer.remove(importDeclaration);
71
- },
72
- });
73
- return;
74
- }
75
-
76
- const usedSpecifierTexts = usedSpecifiers.map((specifier) => sourceCode.getText(specifier));
77
- const updatedImportDeclaration = `import ${importDeclaration.importKind === 'type' ? 'type ' : ''}{ ${usedSpecifierTexts.join(', ')} } from '${moduleName}';`;
78
-
79
- context.report({
80
- messageId: 'removeUnusedImports',
81
- node: importDeclaration,
82
- *fix(fixer) {
83
- yield fixer.replaceText(importDeclaration, updatedImportDeclaration);
84
- },
85
- });
86
- } catch (error) {
87
- // eslint-disable-next-line no-console
88
- console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
89
- context.report({
90
- node: importDeclaration,
91
- messageId: 'unknownError',
92
- data: {
93
- fileName: context.filename,
94
- error: error instanceof Error ? error.toString() : JSON.stringify(error),
95
- },
96
- });
97
- }
98
- },
99
- };
100
- },
101
- });
102
-
103
- export default rule;
@@ -1,93 +0,0 @@
1
- // agent/no-unused-service-variable.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 { strict as assert } from 'node:assert';
10
-
11
- import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
12
- import type { Scope } from '@typescript-eslint/utils/ts-eslint';
13
-
14
- import getDocumentationUrl from '../get-documentation-url';
15
- import { getEnclosingScopeNode } from '../library/ts-tree';
16
-
17
- export const ruleId = 'no-unused-service-variable';
18
-
19
- const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
20
-
21
- const rule: ESLintUtils.RuleModule<'unknownError' | 'removeUnusedServiceVariables'> = createRule({
22
- name: ruleId,
23
- meta: {
24
- type: 'suggestion',
25
- docs: {
26
- description: 'Remove unused service variables.',
27
- },
28
- messages: {
29
- removeUnusedServiceVariables: 'Removing unused service variables.',
30
- unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.',
31
- },
32
- fixable: 'code',
33
- schema: [],
34
- },
35
- defaultOptions: [],
36
- create(context) {
37
- const sourceCode = context.sourceCode;
38
- const scopeManager = sourceCode.scopeManager;
39
-
40
- function isVariableUsed(variableIdentifier: TSESTree.Identifier, scope: Scope.Scope): boolean {
41
- const variable = scope.variables.find((variableToCheck) => variableToCheck.name === variableIdentifier.name);
42
- return variable !== undefined && variable.references.length > 1;
43
- }
44
-
45
- return {
46
- VariableDeclaration(variableDeclaration: TSESTree.VariableDeclaration) {
47
- try {
48
- if (
49
- variableDeclaration.declarations.length !== 1 ||
50
- !sourceCode.getText(variableDeclaration).includes('.service.')
51
- ) {
52
- return;
53
- }
54
-
55
- const enclosingScopeNode = getEnclosingScopeNode(variableDeclaration);
56
- assert.ok(enclosingScopeNode, 'enclosingScopeNode is undefined');
57
-
58
- const declarator = variableDeclaration.declarations[0];
59
- if (declarator.id.type !== TSESTree.AST_NODE_TYPES.Identifier) {
60
- return;
61
- }
62
-
63
- const scope = scopeManager?.acquire(enclosingScopeNode);
64
- assert.ok(scope, 'variable declaration is undefined');
65
- if (isVariableUsed(declarator.id, scope)) {
66
- return;
67
- }
68
-
69
- context.report({
70
- node: variableDeclaration,
71
- messageId: 'removeUnusedServiceVariables',
72
- fix(fixer) {
73
- return fixer.remove(variableDeclaration);
74
- },
75
- });
76
- } catch (error) {
77
- // eslint-disable-next-line no-console
78
- console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
79
- context.report({
80
- node: variableDeclaration,
81
- messageId: 'unknownError',
82
- data: {
83
- fileName: context.filename,
84
- error: error instanceof Error ? error.toString() : JSON.stringify(error),
85
- },
86
- });
87
- }
88
- },
89
- };
90
- },
91
- });
92
-
93
- export default rule;