@checkdigit/eslint-plugin 7.5.0 → 7.6.0-PR.75-5da1

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 (66) hide show
  1. package/dist-mjs/agent/add-assert-import.mjs +58 -0
  2. package/dist-mjs/agent/add-base-path-const.mjs +65 -0
  3. package/dist-mjs/agent/add-base-path-import.mjs +60 -0
  4. package/dist-mjs/agent/add-url-domain.mjs +61 -0
  5. package/dist-mjs/agent/agent-test-wiring.mjs +221 -0
  6. package/dist-mjs/agent/fetch-response-body-json.mjs +146 -0
  7. package/dist-mjs/agent/fetch-response-header-getter.mjs +117 -0
  8. package/dist-mjs/agent/fetch-response-status.mjs +66 -0
  9. package/dist-mjs/agent/fetch-then.mjs +269 -0
  10. package/dist-mjs/agent/fetch.mjs +38 -0
  11. package/dist-mjs/agent/file.mjs +43 -0
  12. package/dist-mjs/agent/fix-function-call-arguments.mjs +153 -0
  13. package/dist-mjs/agent/no-fixture.mjs +361 -0
  14. package/dist-mjs/agent/no-mapped-response.mjs +75 -0
  15. package/dist-mjs/agent/no-service-wrapper.mjs +185 -0
  16. package/dist-mjs/agent/no-status-code.mjs +59 -0
  17. package/dist-mjs/agent/no-unused-function-argument.mjs +79 -0
  18. package/dist-mjs/agent/no-unused-imports.mjs +81 -0
  19. package/dist-mjs/agent/no-unused-service-variable.mjs +74 -0
  20. package/dist-mjs/agent/response-reference.mjs +70 -0
  21. package/dist-mjs/agent/url.mjs +32 -0
  22. package/dist-mjs/index.mjs +146 -4
  23. package/dist-types/agent/add-assert-import.d.ts +4 -0
  24. package/dist-types/agent/add-base-path-const.d.ts +4 -0
  25. package/dist-types/agent/add-base-path-import.d.ts +4 -0
  26. package/dist-types/agent/add-url-domain.d.ts +4 -0
  27. package/dist-types/agent/agent-test-wiring.d.ts +4 -0
  28. package/dist-types/agent/fetch-response-body-json.d.ts +4 -0
  29. package/dist-types/agent/fetch-response-header-getter.d.ts +4 -0
  30. package/dist-types/agent/fetch-response-status.d.ts +4 -0
  31. package/dist-types/agent/fetch-then.d.ts +4 -0
  32. package/dist-types/agent/fetch.d.ts +5 -0
  33. package/dist-types/agent/file.d.ts +7 -0
  34. package/dist-types/agent/fix-function-call-arguments.d.ts +9 -0
  35. package/dist-types/agent/no-fixture.d.ts +4 -0
  36. package/dist-types/agent/no-mapped-response.d.ts +4 -0
  37. package/dist-types/agent/no-service-wrapper.d.ts +4 -0
  38. package/dist-types/agent/no-status-code.d.ts +4 -0
  39. package/dist-types/agent/no-unused-function-argument.d.ts +4 -0
  40. package/dist-types/agent/no-unused-imports.d.ts +4 -0
  41. package/dist-types/agent/no-unused-service-variable.d.ts +4 -0
  42. package/dist-types/agent/response-reference.d.ts +16 -0
  43. package/dist-types/agent/url.d.ts +4 -0
  44. package/package.json +1 -96
  45. package/src/agent/add-assert-import.ts +74 -0
  46. package/src/agent/add-base-path-const.ts +81 -0
  47. package/src/agent/add-base-path-import.ts +69 -0
  48. package/src/agent/add-url-domain.ts +76 -0
  49. package/src/agent/agent-test-wiring.ts +273 -0
  50. package/src/agent/fetch-response-body-json.ts +197 -0
  51. package/src/agent/fetch-response-header-getter.ts +148 -0
  52. package/src/agent/fetch-response-status.ts +87 -0
  53. package/src/agent/fetch-then.ts +357 -0
  54. package/src/agent/fetch.ts +57 -0
  55. package/src/agent/file.ts +42 -0
  56. package/src/agent/fix-function-call-arguments.ts +200 -0
  57. package/src/agent/no-fixture.ts +521 -0
  58. package/src/agent/no-mapped-response.ts +84 -0
  59. package/src/agent/no-service-wrapper.ts +241 -0
  60. package/src/agent/no-status-code.ts +72 -0
  61. package/src/agent/no-unused-function-argument.ts +98 -0
  62. package/src/agent/no-unused-imports.ts +103 -0
  63. package/src/agent/no-unused-service-variable.ts +93 -0
  64. package/src/agent/response-reference.ts +129 -0
  65. package/src/agent/url.ts +32 -0
  66. package/src/index.ts +142 -0
@@ -0,0 +1,185 @@
1
+ // src/agent/no-service-wrapper.ts
2
+ import { strict as assert } from "node:assert";
3
+ import { DefinitionType } from "@typescript-eslint/scope-manager";
4
+ import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
5
+ import getDocumentationUrl from "../get-documentation-url.mjs";
6
+ import { getEnclosingScopeNode } from "../library/ts-tree.mjs";
7
+ import { getIndentation } from "../library/format.mjs";
8
+ import { isServiceApiCallUrl, replaceEndpointUrlPrefixWithDomain } from "./url.mjs";
9
+ var ruleId = "no-service-wrapper";
10
+ var createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
11
+ var rule = createRule({
12
+ name: ruleId,
13
+ meta: {
14
+ type: "suggestion",
15
+ docs: {
16
+ description: "Prefer native fetch over customized service wrapper."
17
+ },
18
+ messages: {
19
+ preferNativeFetch: "Prefer native fetch over customized service wrapper.",
20
+ invalidOptions: '"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.',
21
+ unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}. Please manually convert the usage of customized service wrapper call to native fetch.'
22
+ },
23
+ fixable: "code",
24
+ schema: []
25
+ },
26
+ defaultOptions: [],
27
+ create(context) {
28
+ const sourceCode = context.sourceCode;
29
+ const scopeManager = sourceCode.scopeManager;
30
+ const parserService = ESLintUtils.getParserServices(context);
31
+ const typeChecker = parserService.program.getTypeChecker();
32
+ function isUrlArgumentValid(urlArgument, scope) {
33
+ if (urlArgument?.type === AST_NODE_TYPES.Literal && typeof urlArgument.value === "string" || urlArgument?.type === AST_NODE_TYPES.TemplateLiteral) {
34
+ const urlText = sourceCode.getText(urlArgument);
35
+ return isServiceApiCallUrl(urlText);
36
+ }
37
+ if (urlArgument?.type === AST_NODE_TYPES.Identifier) {
38
+ const foundVariable = scope.variables.find((variable) => variable.name === urlArgument.name);
39
+ if (foundVariable) {
40
+ const variableDefinition = foundVariable.defs.find((def) => def.type === DefinitionType.Variable);
41
+ if (variableDefinition !== void 0) {
42
+ const variableDefinitionNode = variableDefinition.node;
43
+ assert.ok(variableDefinitionNode.init, "Variable definition node has no init property");
44
+ return isUrlArgumentValid(variableDefinitionNode.init, scope);
45
+ }
46
+ return true;
47
+ }
48
+ }
49
+ return false;
50
+ }
51
+ function getType(identifier) {
52
+ const variable = parserService.esTreeNodeToTSNodeMap.get(identifier);
53
+ const variableType = typeChecker.getTypeAtLocation(variable);
54
+ return typeChecker.typeToString(variableType);
55
+ }
56
+ function isServiceLikeName(name) {
57
+ return /.*[Ss]ervice$/u.test(name);
58
+ }
59
+ function isCalleeServiceWrapper(serviceCall) {
60
+ const callee = serviceCall.callee;
61
+ if (callee.type !== AST_NODE_TYPES.MemberExpression) {
62
+ return false;
63
+ }
64
+ const endpoint = callee.object;
65
+ if (endpoint.type === AST_NODE_TYPES.Identifier) {
66
+ return getType(endpoint) === "Endpoint" || isServiceLikeName(endpoint.name);
67
+ }
68
+ if (endpoint.type !== AST_NODE_TYPES.CallExpression) {
69
+ return false;
70
+ }
71
+ const [contextArgument] = endpoint.arguments;
72
+ if (contextArgument?.type !== AST_NODE_TYPES.Identifier) {
73
+ return false;
74
+ }
75
+ if (contextArgument.name !== "EMPTY_CONTEXT" && getType(contextArgument) !== "InboundContext") {
76
+ return false;
77
+ }
78
+ const service = endpoint.callee;
79
+ if (service.type === AST_NODE_TYPES.Identifier) {
80
+ return getType(service) === "ResolvedService";
81
+ }
82
+ if (service.type !== AST_NODE_TYPES.MemberExpression) {
83
+ return false;
84
+ }
85
+ const services = service.object;
86
+ if (services.type === AST_NODE_TYPES.Identifier) {
87
+ return getType(services) === "ResolvedServices";
88
+ }
89
+ if (services.type !== AST_NODE_TYPES.MemberExpression) {
90
+ return false;
91
+ }
92
+ const configuration = services.object;
93
+ if (configuration.type === AST_NODE_TYPES.Identifier) {
94
+ return ["Configuration", "Configuration<ResolvedServices>"].includes(getType(configuration));
95
+ }
96
+ if (configuration.type !== AST_NODE_TYPES.MemberExpression) {
97
+ return false;
98
+ }
99
+ const fixture = configuration.object;
100
+ if (fixture.type === AST_NODE_TYPES.Identifier) {
101
+ return fixture.name === "fixture" || getType(fixture) === "Fixture";
102
+ }
103
+ return false;
104
+ }
105
+ return {
106
+ "CallExpression[callee.property.name=/^(head|get|put|post|del|patch)$/]": (serviceCall) => {
107
+ try {
108
+ if (!isCalleeServiceWrapper(serviceCall)) {
109
+ return;
110
+ }
111
+ const enclosingScopeNode = getEnclosingScopeNode(serviceCall);
112
+ assert.ok(enclosingScopeNode, "enclosingScopeNode is undefined");
113
+ const scope = scopeManager?.acquire(enclosingScopeNode);
114
+ assert.ok(scope, "scope is undefined");
115
+ const urlArgument = serviceCall.arguments[0];
116
+ if (!isUrlArgumentValid(urlArgument, scope)) {
117
+ return;
118
+ }
119
+ assert.ok(serviceCall.callee.type === AST_NODE_TYPES.MemberExpression);
120
+ assert.ok(serviceCall.callee.property.type === AST_NODE_TYPES.Identifier);
121
+ const method = serviceCall.callee.property.name;
122
+ let requestBodyProperty = ["put", "post", "options"].includes(method) ? serviceCall.arguments[1] : void 0;
123
+ if (requestBodyProperty !== void 0 && requestBodyProperty.type === AST_NODE_TYPES.Identifier && requestBodyProperty.name === "undefined") {
124
+ requestBodyProperty = void 0;
125
+ }
126
+ const optionsArgument = ["get", "head", "del"].includes(method) ? serviceCall.arguments[1] : serviceCall.arguments[2];
127
+ if (optionsArgument === void 0 || optionsArgument.type !== AST_NODE_TYPES.ObjectExpression) {
128
+ context.report({
129
+ node: serviceCall,
130
+ messageId: "invalidOptions"
131
+ });
132
+ return;
133
+ }
134
+ const resolveWithFullResponseProperty = optionsArgument.properties.find(
135
+ (property) => property.type === AST_NODE_TYPES.Property && property.key.type === AST_NODE_TYPES.Identifier && property.key.name === "resolveWithFullResponse"
136
+ );
137
+ if (resolveWithFullResponseProperty?.type !== AST_NODE_TYPES.Property || resolveWithFullResponseProperty.value.type !== AST_NODE_TYPES.Literal || resolveWithFullResponseProperty.value.value !== true) {
138
+ context.report({
139
+ node: optionsArgument,
140
+ messageId: "invalidOptions"
141
+ });
142
+ return;
143
+ }
144
+ const requestHeadersProperty = optionsArgument.properties.find(
145
+ (property) => property.type === AST_NODE_TYPES.Property && property.key.type === AST_NODE_TYPES.Identifier && property.key.name === "headers"
146
+ );
147
+ context.report({
148
+ messageId: "preferNativeFetch",
149
+ node: serviceCall,
150
+ fix(fixer) {
151
+ const url = sourceCode.getText(urlArgument);
152
+ const replacedUrl = replaceEndpointUrlPrefixWithDomain(url);
153
+ const indentation = getIndentation(serviceCall, sourceCode);
154
+ const fetchText = [
155
+ `fetch(${replacedUrl}, {`,
156
+ ` method: '${method.toLowerCase() === "del" ? "DELETE" : method.toUpperCase()}',`,
157
+ ...requestHeadersProperty ? [` ${sourceCode.getText(requestHeadersProperty)},`] : [],
158
+ ...requestBodyProperty ? [` body: JSON.stringify(${sourceCode.getText(requestBodyProperty)}),`] : [],
159
+ "})"
160
+ ].join(`
161
+ ${indentation}`);
162
+ return fixer.replaceText(serviceCall, fetchText);
163
+ }
164
+ });
165
+ } catch (error) {
166
+ console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
167
+ context.report({
168
+ node: serviceCall,
169
+ messageId: "unknownError",
170
+ data: {
171
+ fileName: context.filename,
172
+ error: error instanceof Error ? error.toString() : JSON.stringify(error)
173
+ }
174
+ });
175
+ }
176
+ }
177
+ };
178
+ }
179
+ });
180
+ var no_service_wrapper_default = rule;
181
+ export {
182
+ no_service_wrapper_default as default,
183
+ ruleId
184
+ };
185
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2FnZW50L25vLXNlcnZpY2Utd3JhcHBlci50cyJdLAogICJtYXBwaW5ncyI6ICI7QUFRQSxTQUFTLFVBQVUsY0FBYztBQUVqQyxTQUFTLHNCQUFrQztBQUMzQyxTQUFTLGdCQUFnQixtQkFBNkI7QUFFdEQsT0FBTyx5QkFBeUI7QUFDaEMsU0FBUyw2QkFBNkI7QUFDdEMsU0FBUyxzQkFBc0I7QUFDL0IsU0FBUyxxQkFBcUIsMENBQTBDO0FBRWpFLElBQU0sU0FBUztBQUV0QixJQUFNLGFBQWEsWUFBWSxZQUFZLENBQUMsU0FBUyxvQkFBb0IsSUFBSSxDQUFDO0FBRTlFLElBQU0sT0FBd0YsV0FBVztBQUFBLEVBQ3ZHLE1BQU07QUFBQSxFQUNOLE1BQU07QUFBQSxJQUNKLE1BQU07QUFBQSxJQUNOLE1BQU07QUFBQSxNQUNKLGFBQWE7QUFBQSxJQUNmO0FBQUEsSUFDQSxVQUFVO0FBQUEsTUFDUixtQkFBbUI7QUFBQSxNQUNuQixnQkFDRTtBQUFBLE1BQ0YsY0FDRTtBQUFBLElBQ0o7QUFBQSxJQUNBLFNBQVM7QUFBQSxJQUNULFFBQVEsQ0FBQztBQUFBLEVBQ1g7QUFBQSxFQUNBLGdCQUFnQixDQUFDO0FBQUEsRUFDakIsT0FBTyxTQUFTO0FBQ2QsVUFBTSxhQUFhLFFBQVE7QUFDM0IsVUFBTSxlQUFlLFdBQVc7QUFDaEMsVUFBTSxnQkFBZ0IsWUFBWSxrQkFBa0IsT0FBTztBQUMzRCxVQUFNLGNBQWMsY0FBYyxRQUFRLGVBQWU7QUFFekQsYUFBUyxtQkFBbUIsYUFBd0MsT0FBYztBQUNoRixVQUNHLGFBQWEsU0FBUyxlQUFlLFdBQVcsT0FBTyxZQUFZLFVBQVUsWUFDOUUsYUFBYSxTQUFTLGVBQWUsaUJBQ3JDO0FBQ0EsY0FBTSxVQUFVLFdBQVcsUUFBUSxXQUFXO0FBQzlDLGVBQU8sb0JBQW9CLE9BQU87QUFBQSxNQUNwQztBQUVBLFVBQUksYUFBYSxTQUFTLGVBQWUsWUFBWTtBQUNuRCxjQUFNLGdCQUFnQixNQUFNLFVB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
@@ -0,0 +1,59 @@
1
+ // src/agent/no-status-code.ts
2
+ import { ESLintUtils } from "@typescript-eslint/utils";
3
+ import getDocumentationUrl from "../get-documentation-url.mjs";
4
+ var ruleId = "no-status-code";
5
+ var createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
6
+ var rule = createRule({
7
+ name: ruleId,
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: 'Access the status code property of the fetch Response using "status" instead of "statusCode".'
12
+ },
13
+ messages: {
14
+ replaceStatusCode: 'Replace "statusCode" with "status".',
15
+ unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.'
16
+ },
17
+ fixable: "code",
18
+ schema: []
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ const parserServices = ESLintUtils.getParserServices(context);
23
+ const typeChecker = parserServices.program.getTypeChecker();
24
+ return {
25
+ 'MemberExpression[property.name="statusCode"]': (responseStatusCode) => {
26
+ try {
27
+ const responseNode = parserServices.esTreeNodeToTSNodeMap.get(responseStatusCode.object);
28
+ const responseType = typeChecker.getTypeAtLocation(responseNode);
29
+ const shouldReplace = responseType.getProperties().some((symbol) => symbol.name === "status") && !responseType.getProperties().some((symbol) => symbol.name === "statusCode");
30
+ if (shouldReplace) {
31
+ context.report({
32
+ messageId: "replaceStatusCode",
33
+ node: responseStatusCode.property,
34
+ fix(fixer) {
35
+ return fixer.replaceText(responseStatusCode.property, "status");
36
+ }
37
+ });
38
+ }
39
+ } catch (error) {
40
+ console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
41
+ context.report({
42
+ node: responseStatusCode,
43
+ messageId: "unknownError",
44
+ data: {
45
+ fileName: context.filename,
46
+ error: error instanceof Error ? error.toString() : JSON.stringify(error)
47
+ }
48
+ });
49
+ }
50
+ }
51
+ };
52
+ }
53
+ });
54
+ var no_status_code_default = rule;
55
+ export {
56
+ no_status_code_default as default,
57
+ ruleId
58
+ };
59
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2FnZW50L25vLXN0YXR1cy1jb2RlLnRzIl0sCiAgIm1hcHBpbmdzIjogIjtBQVFBLFNBQVMsbUJBQTZCO0FBRXRDLE9BQU8seUJBQXlCO0FBRXpCLElBQU0sU0FBUztBQUV0QixJQUFNLGFBQWEsWUFBWSxZQUFZLENBQUMsU0FBUyxvQkFBb0IsSUFBSSxDQUFDO0FBRTlFLElBQU0sT0FBcUUsV0FBVztBQUFBLEVBQ3BGLE1BQU07QUFBQSxFQUNOLE1BQU07QUFBQSxJQUNKLE1BQU07QUFBQSxJQUNOLE1BQU07QUFBQSxNQUNKLGFBQWE7QUFBQSxJQUNmO0FBQUEsSUFDQSxVQUFVO0FBQUEsTUFDUixtQkFBbUI7QUFBQSxNQUNuQixjQUFjO0FBQUEsSUFDaEI7QUFBQSxJQUNBLFNBQVM7QUFBQSxJQUNULFFBQVEsQ0FBQztBQUFBLEVBQ1g7QUFBQSxFQUNBLGdCQUFnQixDQUFDO0FBQUEsRUFDakIsT0FBTyxTQUFTO0FBQ2QsVUFBTSxpQkFBaUIsWUFBWSxrQkFBa0IsT0FBTztBQUM1RCxVQUFNLGNBQWMsZUFBZSxRQUFRLGVBQWU7QUFFMUQsV0FBTztBQUFBLE1BQ0wsZ0RBQWdELENBQUMsdUJBQWtEO0FBQ2pHLFlBQUk7QUFDRixnQkFBTSxlQUFlLGVBQWUsc0JBQXNCLElBQUksbUJBQW1CLE1BQU07QUFDdkYsZ0JBQU0sZUFBZSxZQUFZLGtCQUFrQixZQUFZO0FBRS9ELGdCQUFNLGdCQUNKLGFBQWEsY0FBYyxFQUFFLEtBQUssQ0FBQyxXQUFXLE9BQU8sU0FBUyxRQUFRLEtBQ3RFLENBQUMsYUFBYSxjQUFjLEVBQUUsS0FBSyxDQUFDLFdBQVcsT0FBTyxTQUFTLFlBQVk7QUFFN0UsY0FBSSxlQUFlO0FBQ2pCLG9CQUFRLE9BQU87QUFBQSxjQUNiLFdBQVc7QUFBQSxjQUNYLE1BQU0sbUJBQW1CO0FBQUEsY0FDekIsSUFBSSxPQUFPO0FBQ1QsdUJBQU8sTUFBTSxZQUFZLG1CQUFtQixVQUFVLFFBQVE7QUFBQSxjQUNoRTtBQUFBLFlBQ0YsQ0FBQztBQUFBLFVBQ0g7QUFBQSxRQUNGLFNBQVMsT0FBTztBQUVkLGtCQUFRLE1BQU0sbUJBQW1CLE1BQU0sbUJBQW1CLFFBQVEsUUFBUSxNQUFNLEtBQUs7QUFDckYsa0JBQVEsT0FBTztBQUFBLFlBQ2IsTUFBTTtBQUFBLFlBQ04sV0FBVztBQUFBLFlBQ1gsTUFBTTtBQUFBLGNBQ0osVUFBVSxRQUFRO0FBQUEsY0FDbEIsT0FBTyxpQkFBaUIsUUFBUSxNQUFNLFNBQVMsSUFBSSxLQUFLLFVBQVUsS0FBSztBQUFBLFlBQ3pFO0FBQUEsVUFDRixDQUFDO0FBQUEsUUFDSDtBQUFBLE1BQ0Y7QUFBQSxJQUNGO0FBQUEsRUFDRjtBQUNGLENBQUM7QUFFRCxJQUFPLHlCQUFROyIsCiAgIm5hbWVzIjogW10KfQo=
@@ -0,0 +1,79 @@
1
+ // src/agent/no-unused-function-argument.ts
2
+ import { strict as assert } from "node:assert";
3
+ import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
4
+ import getDocumentationUrl from "../get-documentation-url.mjs";
5
+ var ruleId = "no-unused-function-argument";
6
+ var createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
7
+ var rule = createRule({
8
+ name: ruleId,
9
+ meta: {
10
+ type: "suggestion",
11
+ docs: {
12
+ description: "Remove unused function arguments."
13
+ },
14
+ messages: {
15
+ removeUnusedFunctionArguments: "Removing unused function arguments.",
16
+ unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.'
17
+ },
18
+ fixable: "code",
19
+ schema: []
20
+ },
21
+ defaultOptions: [],
22
+ create(context) {
23
+ const sourceCode = context.sourceCode;
24
+ function isParameterUsed(parameter, scope) {
25
+ return scope.references.some((ref) => ref.identifier.name === parameter.name) || scope.childScopes.some((childScope) => isParameterUsed(parameter, childScope));
26
+ }
27
+ return {
28
+ FunctionDeclaration(functionDeclaration) {
29
+ try {
30
+ const parameters = functionDeclaration.params;
31
+ if (parameters.length === 0) {
32
+ return;
33
+ }
34
+ const functionScope = sourceCode.getScope(functionDeclaration);
35
+ const parametersToKeep = parameters.filter(
36
+ (parameter) => parameter.type !== TSESTree.AST_NODE_TYPES.Identifier || isParameterUsed(parameter, functionScope)
37
+ );
38
+ if (parametersToKeep.length === parameters.length) {
39
+ return;
40
+ }
41
+ const updatedParameters = parametersToKeep.map((parameter) => sourceCode.getText(parameter)).join(", ");
42
+ context.report({
43
+ node: functionDeclaration,
44
+ messageId: "removeUnusedFunctionArguments",
45
+ fix(fixer) {
46
+ const firstParameter = parameters[0];
47
+ const lastParameter = parameters.at(-1);
48
+ assert.ok(firstParameter !== void 0 && lastParameter !== void 0);
49
+ const tokenAfterParameters = sourceCode.getTokenAfter(lastParameter);
50
+ return fixer.replaceTextRange(
51
+ [
52
+ firstParameter.range[0],
53
+ tokenAfterParameters?.value === "," ? tokenAfterParameters.range[1] : lastParameter.range[1]
54
+ ],
55
+ updatedParameters
56
+ );
57
+ }
58
+ });
59
+ } catch (error) {
60
+ console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
61
+ context.report({
62
+ node: functionDeclaration,
63
+ messageId: "unknownError",
64
+ data: {
65
+ fileName: context.filename,
66
+ error: error instanceof Error ? error.toString() : JSON.stringify(error)
67
+ }
68
+ });
69
+ }
70
+ }
71
+ };
72
+ }
73
+ });
74
+ var no_unused_function_argument_default = rule;
75
+ export {
76
+ no_unused_function_argument_default as default,
77
+ ruleId
78
+ };
79
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2FnZW50L25vLXVudXNlZC1mdW5jdGlvbi1hcmd1bWVudC50cyJdLAogICJtYXBwaW5ncyI6ICI7QUFRQSxTQUFTLFVBQVUsY0FBYztBQUVqQyxTQUFTLGFBQWEsZ0JBQWdCO0FBR3RDLE9BQU8seUJBQXlCO0FBRXpCLElBQU0sU0FBUztBQUV0QixJQUFNLGFBQWEsWUFBWSxZQUFZLENBQUMsU0FBUyxvQkFBb0IsSUFBSSxDQUFDO0FBRTlFLElBQU0sT0FBaUYsV0FBVztBQUFBLEVBQ2hHLE1BQU07QUFBQSxFQUNOLE1BQU07QUFBQSxJQUNKLE1BQU07QUFBQSxJQUNOLE1BQU07QUFBQSxNQUNKLGFBQWE7QUFBQSxJQUNmO0FBQUEsSUFDQSxVQUFVO0FBQUEsTUFDUiwrQkFBK0I7QUFBQSxNQUMvQixjQUFjO0FBQUEsSUFDaEI7QUFBQSxJQUNBLFNBQVM7QUFBQSxJQUNULFFBQVEsQ0FBQztBQUFBLEVBQ1g7QUFBQSxFQUNBLGdCQUFnQixDQUFDO0FBQUEsRUFDakIsT0FBTyxTQUFTO0FBQ2QsVUFBTSxhQUFhLFFBQVE7QUFFM0IsYUFBUyxnQkFBZ0IsV0FBZ0MsT0FBNkI7QUFDcEYsYUFDRSxNQUFNLFdBQVcsS0FBSyxDQUFDLFFBQVEsSUFBSSxXQUFXLFNBQVMsVUFBVSxJQUFJLEtBQ3JFLE1BQU0sWUFBWSxLQUFLLENBQUMsZUFBZSxnQkFBZ0IsV0FBVyxVQUFVLENBQUM7QUFBQSxJQUVqRjtBQUVBLFdBQU87QUFBQSxNQUNMLG9CQUFvQixxQkFBbUQ7QUFDckUsWUFBSTtBQUNGLGdCQUFNLGFBQWEsb0JBQW9CO0FBQ3ZDLGNBQUksV0FBVyxXQUFXLEdBQUc7QUFDM0I7QUFBQSxVQUNGO0FBRUEsZ0JBQU0sZ0JBQWdCLFdBQVcsU0FBUyxtQkFBbUI7QUFDN0QsZ0JBQU0sbUJBQW1CLFdBQVc7QUFBQSxZQUNsQyxDQUFDLGNBQ0MsVUFBVSxTQUFTLFNBQVMsZUFBZSxjQUFjLGdCQUFnQixXQUFXLGFBQWE7QUFBQSxVQUNyRztBQUNBLGNBQUksaUJBQWlCLFdBQVcsV0FBVyxRQUFRO0FBQ2pEO0FBQUEsVUFDRjtBQUVBLGdCQUFNLG9CQUFvQixpQkFBaUIsSUFBSSxDQUFDLGNBQWMsV0FBVyxRQUFRLFNBQVMsQ0FBQyxFQUFFLEtBQUssSUFBSTtBQUN0RyxrQkFBUSxPQUFPO0FBQUEsWUFDYixNQUFNO0FBQUEsWUFDTixXQUFXO0FBQUEsWUFDWCxJQUFJLE9BQU87QUFDVCxvQkFBTSxpQkFBaUIsV0FBVyxDQUFDO0FBQ25DLG9CQUFNLGdCQUFnQixXQUFXLEdBQUcsRUFBRTtBQUN0QyxxQkFBTyxHQUFHLG1CQUFtQixVQUFhLGtCQUFrQixNQUFTO0FBQ3JFLG9CQUFNLHVCQUF1QixXQUFXLGNBQWMsYUFBYTtBQUVuRSxxQkFBTyxNQUFNO0FBQUEsZ0JBQ1g7QUFBQSxrQkFDRSxlQUFlLE1BQU0sQ0FBQztBQUFBLGtCQUN0QixzQkFBc0IsVUFBVSxNQUFNLHFCQUFxQixNQUFNLENBQUMsSUFBSSxjQUFjLE1BQU0sQ0FBQztBQUFBLGdCQUM3RjtBQUFBLGdCQUNBO0FBQUEsY0FDRjtBQUFBLFlBQ0Y7QUFBQSxVQUNGLENBQUM7QUFBQSxRQUNILFNBQVMsT0FBTztBQUVkLGtCQUFRLE1BQU0sbUJBQW1CLE1BQU0sbUJBQW1CLFFBQVEsUUFBUSxNQUFNLEtBQUs7QUFDckYsa0JBQVEsT0FBTztBQUFBLFlBQ2IsTUFBTTtBQUFBLFlBQ04sV0FBVztBQUFBLFlBQ1gsTUFBTTtBQUFBLGNBQ0osVUFBVSxRQUFRO0FBQUEsY0FDbEIsT0FBTyxpQkFBaUIsUUFBUSxNQUFNLFNBQVMsSUFBSSxLQUFLLFVBQVUsS0FBSztBQUFBLFlBQ3pFO0FBQUEsVUFDRixDQUFDO0FBQUEsUUFDSDtBQUFBLE1BQ0Y7QUFBQSxJQUNGO0FBQUEsRUFDRjtBQUNGLENBQUM7QUFFRCxJQUFPLHNDQUFROyIsCiAgIm5hbWVzIjogW10KfQo=
@@ -0,0 +1,81 @@
1
+ // src/agent/no-unused-imports.ts
2
+ import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
3
+ import getDocumentationUrl from "../get-documentation-url.mjs";
4
+ var ruleId = "no-unused-imports";
5
+ var createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
6
+ var rule = createRule({
7
+ name: ruleId,
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Remove unused imports."
12
+ },
13
+ messages: {
14
+ removeUnusedImports: "Removing unused imports.",
15
+ unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.'
16
+ },
17
+ fixable: "code",
18
+ schema: []
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ const sourceCode = context.sourceCode;
23
+ function isImportUsed(specifier, scope) {
24
+ return specifier.type !== TSESTree.AST_NODE_TYPES.ImportSpecifier || scope.references.some((ref) => ref.identifier.name === specifier.local.name) || scope.childScopes.some((childScope) => isImportUsed(specifier, childScope));
25
+ }
26
+ return {
27
+ ImportDeclaration(importDeclaration) {
28
+ try {
29
+ const moduleName = importDeclaration.source.value;
30
+ if (!importDeclaration.specifiers.every(
31
+ (specifier) => specifier.type === TSESTree.AST_NODE_TYPES.ImportSpecifier
32
+ ) || // [TODO:] move to meta schema
33
+ !["@checkdigit/serve-runtime", "@checkdigit/fixture"].includes(moduleName)) {
34
+ return;
35
+ }
36
+ const originalSpecifiers = importDeclaration.specifiers;
37
+ const scope = sourceCode.getScope(importDeclaration);
38
+ const usedSpecifiers = originalSpecifiers.filter((specifier) => isImportUsed(specifier, scope));
39
+ if (usedSpecifiers.length === originalSpecifiers.length) {
40
+ return;
41
+ }
42
+ if (usedSpecifiers.length === 0) {
43
+ context.report({
44
+ messageId: "removeUnusedImports",
45
+ node: importDeclaration,
46
+ *fix(fixer) {
47
+ yield fixer.remove(importDeclaration);
48
+ }
49
+ });
50
+ return;
51
+ }
52
+ const usedSpecifierTexts = usedSpecifiers.map((specifier) => sourceCode.getText(specifier));
53
+ const updatedImportDeclaration = `import ${importDeclaration.importKind === "type" ? "type " : ""}{ ${usedSpecifierTexts.join(", ")} } from '${moduleName}';`;
54
+ context.report({
55
+ messageId: "removeUnusedImports",
56
+ node: importDeclaration,
57
+ *fix(fixer) {
58
+ yield fixer.replaceText(importDeclaration, updatedImportDeclaration);
59
+ }
60
+ });
61
+ } catch (error) {
62
+ console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
63
+ context.report({
64
+ node: importDeclaration,
65
+ messageId: "unknownError",
66
+ data: {
67
+ fileName: context.filename,
68
+ error: error instanceof Error ? error.toString() : JSON.stringify(error)
69
+ }
70
+ });
71
+ }
72
+ }
73
+ };
74
+ }
75
+ });
76
+ var no_unused_imports_default = rule;
77
+ export {
78
+ no_unused_imports_default as default,
79
+ ruleId
80
+ };
81
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2FnZW50L25vLXVudXNlZC1pbXBvcnRzLnRzIl0sCiAgIm1hcHBpbmdzIjogIjtBQVFBLFNBQVMsYUFBYSxnQkFBZ0I7QUFHdEMsT0FBTyx5QkFBeUI7QUFFekIsSUFBTSxTQUFTO0FBRXRCLElBQU0sYUFBYSxZQUFZLFlBQVksQ0FBQyxTQUFTLG9CQUFvQixJQUFJLENBQUM7QUFFOUUsSUFBTSxPQUF1RSxXQUFXO0FBQUEsRUFDdEYsTUFBTTtBQUFBLEVBQ04sTUFBTTtBQUFBLElBQ0osTUFBTTtBQUFBLElBQ04sTUFBTTtBQUFBLE1BQ0osYUFBYTtBQUFBLElBQ2Y7QUFBQSxJQUNBLFVBQVU7QUFBQSxNQUNSLHFCQUFxQjtBQUFBLE1BQ3JCLGNBQWM7QUFBQSxJQUNoQjtBQUFBLElBQ0EsU0FBUztBQUFBLElBQ1QsUUFBUSxDQUFDO0FBQUEsRUFDWDtBQUFBLEVBQ0EsZ0JBQWdCLENBQUM7QUFBQSxFQUNqQixPQUFPLFNBQVM7QUFDZCxVQUFNLGFBQWEsUUFBUTtBQUUzQixhQUFTLGFBQWEsV0FBa0MsT0FBNkI7QUFDbkYsYUFDRSxVQUFVLFNBQVMsU0FBUyxlQUFlLG1CQUMzQyxNQUFNLFdBQVcsS0FBSyxDQUFDLFFBQVEsSUFBSSxXQUFXLFNBQVMsVUFBVSxNQUFNLElBQUksS0FDM0UsTUFBTSxZQUFZLEtBQUssQ0FBQyxlQUFlLGFBQWEsV0FBVyxVQUFVLENBQUM7QUFBQSxJQUU5RTtBQUVBLFdBQU87QUFBQSxNQUNMLGtCQUFrQixtQkFBbUI7QUFDbkMsWUFBSTtBQUNGLGdCQUFNLGFBQWEsa0JBQWtCLE9BQU87QUFDNUMsY0FDRSxDQUFDLGtCQUFrQixXQUFXO0FBQUEsWUFDNUIsQ0FBQyxjQUFjLFVBQVUsU0FBUyxTQUFTLGVBQWU7QUFBQSxVQUM1RDtBQUFBLFVBRUEsQ0FBQyxDQUFDLDZCQUE2QixxQkFBcUIsRUFBRSxTQUFTLFVBQVUsR0FDekU7QUFDQTtBQUFBLFVBQ0Y7QUFFQSxnQkFBTSxxQkFBcUIsa0JBQWtCO0FBQzdDLGdCQUFNLFFBQVEsV0FBVyxTQUFTLGlCQUFpQjtBQUNuRCxnQkFBTSxpQkFBaUIsbUJBQW1CLE9BQU8sQ0FBQyxjQUFjLGFBQWEsV0FBVyxLQUFLLENBQUM7QUFDOUYsY0FBSSxlQUFlLFdBQVcsbUJBQW1CLFFBQVE7QUFDdkQ7QUFBQSxVQUNGO0FBRUEsY0FBSSxlQUFlLFdBQVcsR0FBRztBQUMvQixvQkFBUSxPQUFPO0FBQUEsY0FDYixXQUFXO0FBQUEsY0FDWCxNQUFNO0FBQUEsY0FDTixDQUFDLElBQUksT0FBTztBQUNWLHNCQUFNLE1BQU0sT0FBTyxpQkFBaUI7QUFBQSxjQUN0QztBQUFBLFlBQ0YsQ0FBQztBQUNEO0FBQUEsVUFDRjtBQUVBLGdCQUFNLHFCQUFxQixlQUFlLElBQUksQ0FBQyxjQUFjLFdBQVcsUUFBUSxTQUFTLENBQUM7QUFDMUYsZ0JBQU0sMkJBQTJCLFVBQVUsa0JBQWtCLGVBQWUsU0FBUyxVQUFVLEVBQUUsS0FBSyxtQkFBbUIsS0FBSyxJQUFJLENBQUMsWUFBWSxVQUFVO0FBRXpKLGtCQUFRLE9BQU87QUFBQSxZQUNiLFdBQVc7QUFBQSxZQUNYLE1BQU07QUFBQSxZQUNOLENBQUMsSUFBSSxPQUFPO0FBQ1Ysb0JBQU0sTUFBTSxZQUFZLG1CQUFtQix3QkFBd0I7QUFBQSxZQUNyRTtBQUFBLFVBQ0YsQ0FBQztBQUFBLFFBQ0gsU0FBUyxPQUFPO0FBRWQsa0JBQVEsTUFBTSxtQkFBbUIsTUFBTSxtQkFBbUIsUUFBUSxRQUFRLE1BQU0sS0FBSztBQUNyRixrQkFBUSxPQUFPO0FBQUEsWUFDYixNQUFNO0FBQUEsWUFDTixXQUFXO0FBQUEsWUFDWCxNQUFNO0FBQUEsY0FDSixVQUFVLFFBQVE7QUFBQSxjQUNsQixPQUFPLGlCQUFpQixRQUFRLE1BQU0sU0FBUyxJQUFJLEtBQUssVUFBVSxLQUFLO0FBQUEsWUFDekU7QUFBQSxVQUNGLENBQUM7QUFBQSxRQUNIO0FBQUEsTUFDRjtBQUFBLElBQ0Y7QUFBQSxFQUNGO0FBQ0YsQ0FBQztBQUVELElBQU8sNEJBQVE7IiwKICAibmFtZXMiOiBbXQp9Cg==
@@ -0,0 +1,74 @@
1
+ // src/agent/no-unused-service-variable.ts
2
+ import { strict as assert } from "node:assert";
3
+ import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
4
+ import getDocumentationUrl from "../get-documentation-url.mjs";
5
+ import { getEnclosingScopeNode } from "../library/ts-tree.mjs";
6
+ var ruleId = "no-unused-service-variable";
7
+ var createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
8
+ var rule = createRule({
9
+ name: ruleId,
10
+ meta: {
11
+ type: "suggestion",
12
+ docs: {
13
+ description: "Remove unused service variables."
14
+ },
15
+ messages: {
16
+ removeUnusedServiceVariables: "Removing unused service variables.",
17
+ unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.'
18
+ },
19
+ fixable: "code",
20
+ schema: []
21
+ },
22
+ defaultOptions: [],
23
+ create(context) {
24
+ const sourceCode = context.sourceCode;
25
+ const scopeManager = sourceCode.scopeManager;
26
+ function isVariableUsed(variableIdentifier, scope) {
27
+ const variable = scope.variables.find((variableToCheck) => variableToCheck.name === variableIdentifier.name);
28
+ return variable !== void 0 && variable.references.length > 1;
29
+ }
30
+ return {
31
+ VariableDeclaration(variableDeclaration) {
32
+ try {
33
+ if (variableDeclaration.declarations.length !== 1 || !sourceCode.getText(variableDeclaration).includes(".service.")) {
34
+ return;
35
+ }
36
+ const enclosingScopeNode = getEnclosingScopeNode(variableDeclaration);
37
+ assert.ok(enclosingScopeNode, "enclosingScopeNode is undefined");
38
+ const declarator = variableDeclaration.declarations[0];
39
+ if (declarator.id.type !== TSESTree.AST_NODE_TYPES.Identifier) {
40
+ return;
41
+ }
42
+ const scope = scopeManager?.acquire(enclosingScopeNode);
43
+ assert.ok(scope, "variable declaration is undefined");
44
+ if (isVariableUsed(declarator.id, scope)) {
45
+ return;
46
+ }
47
+ context.report({
48
+ node: variableDeclaration,
49
+ messageId: "removeUnusedServiceVariables",
50
+ fix(fixer) {
51
+ return fixer.remove(variableDeclaration);
52
+ }
53
+ });
54
+ } catch (error) {
55
+ console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
56
+ context.report({
57
+ node: variableDeclaration,
58
+ messageId: "unknownError",
59
+ data: {
60
+ fileName: context.filename,
61
+ error: error instanceof Error ? error.toString() : JSON.stringify(error)
62
+ }
63
+ });
64
+ }
65
+ }
66
+ };
67
+ }
68
+ });
69
+ var no_unused_service_variable_default = rule;
70
+ export {
71
+ no_unused_service_variable_default as default,
72
+ ruleId
73
+ };
74
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2FnZW50L25vLXVudXNlZC1zZXJ2aWNlLXZhcmlhYmxlLnRzIl0sCiAgIm1hcHBpbmdzIjogIjtBQVFBLFNBQVMsVUFBVSxjQUFjO0FBRWpDLFNBQVMsYUFBYSxnQkFBZ0I7QUFHdEMsT0FBTyx5QkFBeUI7QUFDaEMsU0FBUyw2QkFBNkI7QUFFL0IsSUFBTSxTQUFTO0FBRXRCLElBQU0sYUFBYSxZQUFZLFlBQVksQ0FBQyxTQUFTLG9CQUFvQixJQUFJLENBQUM7QUFFOUUsSUFBTSxPQUFnRixXQUFXO0FBQUEsRUFDL0YsTUFBTTtBQUFBLEVBQ04sTUFBTTtBQUFBLElBQ0osTUFBTTtBQUFBLElBQ04sTUFBTTtBQUFBLE1BQ0osYUFBYTtBQUFBLElBQ2Y7QUFBQSxJQUNBLFVBQVU7QUFBQSxNQUNSLDhCQUE4QjtBQUFBLE1BQzlCLGNBQWM7QUFBQSxJQUNoQjtBQUFBLElBQ0EsU0FBUztBQUFBLElBQ1QsUUFBUSxDQUFDO0FBQUEsRUFDWDtBQUFBLEVBQ0EsZ0JBQWdCLENBQUM7QUFBQSxFQUNqQixPQUFPLFNBQVM7QUFDZCxVQUFNLGFBQWEsUUFBUTtBQUMzQixVQUFNLGVBQWUsV0FBVztBQUVoQyxhQUFTLGVBQWUsb0JBQXlDLE9BQTZCO0FBQzVGLFlBQU0sV0FBVyxNQUFNLFVBQVUsS0FBSyxDQUFDLG9CQUFvQixnQkFBZ0IsU0FBUyxtQkFBbUIsSUFBSTtBQUMzRyxhQUFPLGFBQWEsVUFBYSxTQUFTLFdBQVcsU0FBUztBQUFBLElBQ2hFO0FBRUEsV0FBTztBQUFBLE1BQ0wsb0JBQW9CLHFCQUFtRDtBQUNyRSxZQUFJO0FBQ0YsY0FDRSxvQkFBb0IsYUFBYSxXQUFXLEtBQzVDLENBQUMsV0FBVyxRQUFRLG1CQUFtQixFQUFFLFNBQVMsV0FBVyxHQUM3RDtBQUNBO0FBQUEsVUFDRjtBQUVBLGdCQUFNLHFCQUFxQixzQkFBc0IsbUJBQW1CO0FBQ3BFLGlCQUFPLEdBQUcsb0JBQW9CLGlDQUFpQztBQUUvRCxnQkFBTSxhQUFhLG9CQUFvQixhQUFhLENBQUM7QUFDckQsY0FBSSxXQUFXLEdBQUcsU0FBUyxTQUFTLGVBQWUsWUFBWTtBQUM3RDtBQUFBLFVBQ0Y7QUFFQSxnQkFBTSxRQUFRLGNBQWMsUUFBUSxrQkFBa0I7QUFDdEQsaUJBQU8sR0FBRyxPQUFPLG1DQUFtQztBQUNwRCxjQUFJLGVBQWUsV0FBVyxJQUFJLEtBQUssR0FBRztBQUN4QztBQUFBLFVBQ0Y7QUFFQSxrQkFBUSxPQUFPO0FBQUEsWUFDYixNQUFNO0FBQUEsWUFDTixXQUFXO0FBQUEsWUFDWCxJQUFJLE9BQU87QUFDVCxxQkFBTyxNQUFNLE9BQU8sbUJBQW1CO0FBQUEsWUFDekM7QUFBQSxVQUNGLENBQUM7QUFBQSxRQUNILFNBQVMsT0FBTztBQUVkLGtCQUFRLE1BQU0sbUJBQW1CLE1BQU0sbUJBQW1CLFFBQVEsUUFBUSxNQUFNLEtBQUs7QUFDckYsa0JBQVEsT0FBTztBQUFBLFlBQ2IsTUFBTTtBQUFBLFlBQ04sV0FBVztBQUFBLFlBQ1gsTUFBTTtBQUFBLGNBQ0osVUFBVSxRQUFRO0FBQUEsY0FDbEIsT0FBTyxpQkFBaUIsUUFBUSxNQUFNLFNBQVMsSUFBSSxLQUFLLFVBQVUsS0FBSztBQUFBLFlBQ3pFO0FBQUEsVUFDRixDQUFDO0FBQUEsUUFDSDtBQUFBLE1BQ0Y7QUFBQSxJQUNGO0FBQUEsRUFDRjtBQUNGLENBQUM7QUFFRCxJQUFPLHFDQUFROyIsCiAgIm5hbWVzIjogW10KfQo=
@@ -0,0 +1,70 @@
1
+ // src/agent/response-reference.ts
2
+ import { strict as assert } from "node:assert";
3
+ import "eslint";
4
+ import debug from "debug";
5
+ import { getParent } from "../library/tree.mjs";
6
+ var log = debug("eslint-plugin:response-reference");
7
+ function analyzeResponseReferences(variableDeclaration, scopeManager) {
8
+ const results = {
9
+ bodyReferences: [],
10
+ headersReferences: [],
11
+ statusReferences: []
12
+ };
13
+ if (!variableDeclaration) {
14
+ return results;
15
+ }
16
+ const responseVariables = scopeManager.getDeclaredVariables(variableDeclaration);
17
+ for (const responseVariable of responseVariables) {
18
+ const identifier = responseVariable.identifiers[0];
19
+ assert.ok(identifier);
20
+ const identifierParent = getParent(identifier);
21
+ assert.ok(identifierParent);
22
+ if (identifierParent.type === "VariableDeclarator") {
23
+ results.variable = responseVariable;
24
+ const responseReferences = responseVariable.references.map(
25
+ (responseReference) => getParent(responseReference.identifier)
26
+ );
27
+ results.bodyReferences = responseReferences.filter(
28
+ (node) => node?.type === "MemberExpression" && node.property.type === "Identifier" && node.property.name === "body"
29
+ );
30
+ results.headersReferences = responseReferences.filter(
31
+ (node) => node?.type === "MemberExpression" && node.property.type === "Identifier" && (node.property.name === "header" || node.property.name === "headers" || node.property.name === "get")
32
+ );
33
+ results.statusReferences = responseReferences.filter(
34
+ (node) => node?.type === "MemberExpression" && node.property.type === "Identifier" && (node.property.name === "status" || node.property.name === "statusCode")
35
+ );
36
+ } else if (
37
+ // body reference through destruction/renaming, e.g. "const { body } = ..."
38
+ identifierParent.type === "Property" && identifierParent.key.type === "Identifier" && identifierParent.key.name === "body"
39
+ ) {
40
+ results.destructuringBodyVariable = responseVariable;
41
+ } else if (
42
+ // header reference through destruction/renaming, e.g. "const { headers } = ..."
43
+ identifierParent.type === "Property" && identifierParent.key.type === "Identifier" && identifierParent.key.name === "headers"
44
+ ) {
45
+ results.destructuringHeadersVariable = responseVariable;
46
+ results.destructuringHeadersReferences = responseVariable.references.map((reference) => reference.identifier).map(getParent).filter(
47
+ (parent) => parent?.type === "MemberExpression" && parent.property.type === "Identifier" && parent.property.name !== "get" && getParent(parent)?.type !== "CallExpression"
48
+ );
49
+ } else if (identifierParent.type === "Property") {
50
+ const parent = getParent(identifierParent);
51
+ if (parent?.type === "ObjectPattern") {
52
+ const parent2 = getParent(parent);
53
+ if (parent2?.type === "Property" && parent2.key.type === "Identifier" && parent2.key.name === "body") {
54
+ results.destructuringBodyVariable = parent;
55
+ }
56
+ if (parent2?.type === "Property" && parent2.key.type === "Identifier" && (parent2.key.name === "header" || parent2.key.name === "headers")) {
57
+ results.destructuringHeadersVariable = parent;
58
+ }
59
+ }
60
+ } else {
61
+ log("+++++++ can not handle identifierParent", identifierParent);
62
+ throw new Error(`Unknown response variable reference: ${responseVariable.name}`);
63
+ }
64
+ }
65
+ return results;
66
+ }
67
+ export {
68
+ analyzeResponseReferences
69
+ };
70
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2FnZW50L3Jlc3BvbnNlLXJlZmVyZW5jZS50cyJdLAogICJtYXBwaW5ncyI6ICI7QUFRQSxTQUFTLFVBQVUsY0FBYztBQUVqQyxPQUEyQjtBQUMzQixPQUFPLFdBQVc7QUFFbEIsU0FBUyxpQkFBaUI7QUFFMUIsSUFBTSxNQUFNLE1BQU0sa0NBQWtDO0FBTzdDLFNBQVMsMEJBQ2QscUJBQ0EsY0FTQTtBQUNBLFFBQU0sVUFRRjtBQUFBLElBQ0YsZ0JBQWdCLENBQUM7QUFBQSxJQUNqQixtQkFBbUIsQ0FBQztBQUFBLElBQ3BCLGtCQUFrQixDQUFDO0FBQUEsRUFDckI7QUFDQSxNQUFJLENBQUMscUJBQXFCO0FBQ3hCLFdBQU87QUFBQSxFQUNUO0FBRUEsUUFBTSxvQkFBb0IsYUFBYSxxQkFBcUIsbUJBQW1CO0FBQy9FLGFBQVcsb0JBQW9CLG1CQUFtQjtBQUNoRCxVQUFNLGFBQWEsaUJBQWlCLFlBQVksQ0FBQztBQUNqRCxXQUFPLEdBQUcsVUFBVTtBQUNwQixVQUFNLG1CQUFtQixVQUFVLFVBQVU7QUFDN0MsV0FBTyxHQUFHLGdCQUFnQjtBQUMxQixRQUFJLGlCQUFpQixTQUFTLHNCQUFzQjtBQUVsRCxjQUFRLFdBQVc7QUFDbkIsWUFBTSxxQkFBcUIsaUJBQWlCLFdBQVc7QUFBQSxRQUFJLENBQUMsc0JBQzFELFVBQVUsa0JBQWtCLFVBQVU7QUFBQSxNQUN4QztBQUVBLGNBQVEsaUJBQWlCLG1CQUFtQjtBQUFBLFFBQzFDLENBQUMsU0FDQyxNQUFNLFNBQVMsc0JBQXNCLEtBQUssU0FBUyxTQUFTLGdCQUFnQixLQUFLLFNBQVMsU0FBUztBQUFBLE1BQ3ZHO0FBRUEsY0FBUSxvQkFBb0IsbUJBQW1CO0FBQUEsUUFDN0MsQ0FBQyxTQUNDLE1BQU0sU0FBUyxzQkFDZixLQUFLLFNBQVMsU0FBUyxpQkFDdEIsS0FBSyxTQUFTLFNBQVMsWUFBWSxLQUFLLFNBQVMsU0FBUyxhQUFhLEtBQUssU0FBUyxTQUFTO0FBQUEsTUFDbkc7QUFFQSxjQUFRLG1CQUFtQixtQkFBbUI7QUFBQSxRQUM1QyxDQUFDLFNBQ0MsTUFBTSxTQUFTLHNCQUNmLEtBQUssU0FBUyxTQUFTLGlCQUN0QixLQUFLLFNBQVMsU0FBUyxZQUFZLEtBQUssU0FBUyxTQUFTO0FBQUEsTUFDL0Q7QUFBQSxJQUNGO0FBQUE7QUFBQSxNQUVFLGlCQUFpQixTQUFTLGNBQzFCLGlCQUFpQixJQUFJLFNBQVMsZ0JBQzlCLGlCQUFpQixJQUFJLFNBQVM7QUFBQSxNQUM5QjtBQUNBLGNBQVEsNEJBQTRCO0FBQUEsSUFDdEM7QUFBQTtBQUFBLE1BRUUsaUJBQWlCLFNBQVMsY0FDMUIsaUJBQWlCLElBQUksU0FBUyxnQkFDOUIsaUJBQWlCLElBQUksU0FBUztBQUFBLE1BQzlCO0FBQ0EsY0FBUSwrQkFBK0I7QUFDdkMsY0FBUSxpQ0FBaUMsaUJBQWlCLFdBQ3ZELElBQUksQ0FBQyxjQUFjLFVBQVUsVUFBVSxFQUN2QyxJQUFJLFNBQVMsRUFDYjtBQUFBLFFBQ0MsQ0FBQyxXQUNDLFFBQVEsU0FBUyxzQkFDakIsT0FBTyxTQUFTLFNBQVMsZ0JBQ3pCLE9BQU8sU0FBUyxTQUFTLFNBQ3pCLFVBQVUsTUFBTSxHQUFHLFNBQVM7QUFBQSxNQUNoQztBQUFBLElBQ0osV0FBVyxpQkFBaUIsU0FBUyxZQUFZO0FBQy9DLFlBQU0sU0FBUyxVQUFVLGdCQUFnQjtBQUN6QyxVQUFJLFFBQVEsU0FBUyxpQkFBaUI7QUFFcEMsY0FBTSxVQUFVLFVBQVUsTUFBTTtBQUNoQyxZQUFJLFNBQVMsU0FBUyxjQUFjLFFBQVEsSUFBSSxTQUFTLGdCQUFnQixRQUFRLElBQUksU0FBUyxRQUFRO0FBQ3BHLGtCQUFRLDRCQUE0QjtBQUFBLFFBQ3RDO0FBQ0EsWUFDRSxTQUFTLFNBQVMsY0FDbEIsUUFBUSxJQUFJLFNBQVMsaUJBQ3BCLFFBQVEsSUFBSSxTQUFTLFlBQVksUUFBUSxJQUFJLFNBQVMsWUFDdkQ7QUFDQSxrQkFBUSwrQkFBK0I7QUFBQSxRQUN6QztBQUFBLE1BQ0Y7QUFBQSxJQUNGLE9BQU87QUFDTCxVQUFJLDJDQUEyQyxnQkFBZ0I7QUFDL0QsWUFBTSxJQUFJLE1BQU0sd0NBQXdDLGlCQUFpQixJQUFJLEVBQUU7QUFBQSxJQUNqRjtBQUFBLEVBQ0Y7QUFDQSxTQUFPO0FBQ1Q7IiwKICAibmFtZXMiOiBbXQp9Cg==
@@ -0,0 +1,32 @@
1
+ // src/agent/url.ts
2
+ var PLAIN_URL_REGEXP = /^[`']\/\w+(?<serviceNamePart>-\w+)*\/v\d+\/(?<any>.|\r|\n)+[`']$/u;
3
+ var TOKENIZED_URL_REGEXP = /^`\$\{(?<serviceNamePart>[A-Z]+_)*BASE_PATH\}\/(?<any>.|\r|\n)+`$/u;
4
+ function isServiceApiCallUrl(url) {
5
+ return PLAIN_URL_REGEXP.test(url) || TOKENIZED_URL_REGEXP.test(url);
6
+ }
7
+ function replaceEndpointUrlPrefixWithBasePath(url) {
8
+ return url.replace(
9
+ /^(?<quotStart>[`'])\/(?<servicename>\w+(?<parts>-\w+)*)(?<path>\/v\d+\/)(?<endpoint>(?<any>.|\r|\n)+)(?<quotEnd>[`'])$/u,
10
+ // eslint-disable-next-line no-template-curly-in-string
11
+ "`${BASE_PATH}/$5`"
12
+ );
13
+ }
14
+ function replaceEndpointUrlPrefixWithDomain(url) {
15
+ return url.replace(
16
+ /^(?<quotStart>[`'])\/(?<servicename>\w+(?<parts>-\w+)*)(?<path>\/v\d+\/(?<any>.|\r|\n)+(?<quotEnd>[`'])$)/u,
17
+ "$1https://$2.checkdigit/$2$4"
18
+ );
19
+ }
20
+ function addBasePathUrlDomain(url) {
21
+ return url.replace(
22
+ /^(?<quotStart>[`'])\/(?<servicename>\w+(?<parts>-\w+)*)(?<path>\/v\d+(?<quotEnd>[`'])$)/u,
23
+ "$1https://$2.checkdigit/$2$4"
24
+ );
25
+ }
26
+ export {
27
+ addBasePathUrlDomain,
28
+ isServiceApiCallUrl,
29
+ replaceEndpointUrlPrefixWithBasePath,
30
+ replaceEndpointUrlPrefixWithDomain
31
+ };
32
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2FnZW50L3VybC50cyJdLAogICJtYXBwaW5ncyI6ICI7QUFHQSxJQUFNLG1CQUEyQjtBQUVqQyxJQUFNLHVCQUErQjtBQUU5QixTQUFTLG9CQUFvQixLQUFzQjtBQUN4RCxTQUFPLGlCQUFpQixLQUFLLEdBQUcsS0FBSyxxQkFBcUIsS0FBSyxHQUFHO0FBQ3BFO0FBRU8sU0FBUyxxQ0FBcUMsS0FBcUI7QUFDeEUsU0FBTyxJQUFJO0FBQUEsSUFDVDtBQUFBO0FBQUEsSUFFQTtBQUFBLEVBQ0Y7QUFDRjtBQUVPLFNBQVMsbUNBQW1DLEtBQXFCO0FBQ3RFLFNBQU8sSUFBSTtBQUFBLElBQ1Q7QUFBQSxJQUNBO0FBQUEsRUFDRjtBQUNGO0FBRU8sU0FBUyxxQkFBcUIsS0FBcUI7QUFDeEQsU0FBTyxJQUFJO0FBQUEsSUFDVDtBQUFBLElBQ0E7QUFBQSxFQUNGO0FBQ0Y7IiwKICAibmFtZXMiOiBbXQp9Cg==