@checkdigit/eslint-plugin 6.6.0-PR.75-3712 → 6.6.0-PR.75-7154

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.
@@ -12272,7 +12272,7 @@
12272
12272
  "format": "esm"
12273
12273
  },
12274
12274
  "src/fixture/url.ts": {
12275
- "bytes": 221,
12275
+ "bytes": 642,
12276
12276
  "imports": [],
12277
12277
  "format": "esm"
12278
12278
  },
@@ -12407,7 +12407,7 @@
12407
12407
  "format": "esm"
12408
12408
  },
12409
12409
  "src/fixture/no-service-wrapper.ts": {
12410
- "bytes": 8166,
12410
+ "bytes": 9507,
12411
12411
  "imports": [
12412
12412
  {
12413
12413
  "path": "node_modules/@typescript-eslint/utils/dist/index.js",
@@ -12419,6 +12419,11 @@
12419
12419
  "kind": "import-statement",
12420
12420
  "original": "@typescript-eslint/scope-manager"
12421
12421
  },
12422
+ {
12423
+ "path": "src/fixture/url.ts",
12424
+ "kind": "import-statement",
12425
+ "original": "./url"
12426
+ },
12422
12427
  {
12423
12428
  "path": "node:assert",
12424
12429
  "kind": "import-statement",
@@ -14883,7 +14888,7 @@
14883
14888
  "bytesInOutput": 626
14884
14889
  },
14885
14890
  "node_modules/@typescript-eslint/scope-manager/dist/definition/DefinitionType.js": {
14886
- "bytesInOutput": 998
14891
+ "bytesInOutput": 1001
14887
14892
  },
14888
14893
  "node_modules/@typescript-eslint/scope-manager/dist/definition/CatchClauseDefinition.js": {
14889
14894
  "bytesInOutput": 739
@@ -15381,7 +15386,7 @@
15381
15386
  "bytesInOutput": 118
15382
15387
  },
15383
15388
  "src/fixture/url.ts": {
15384
- "bytesInOutput": 129
15389
+ "bytesInOutput": 457
15385
15390
  },
15386
15391
  "src/fixture/fetch-then.ts": {
15387
15392
  "bytesInOutput": 12296
@@ -15399,7 +15404,7 @@
15399
15404
  "bytesInOutput": 1849
15400
15405
  },
15401
15406
  "src/fixture/no-service-wrapper.ts": {
15402
- "bytesInOutput": 7100
15407
+ "bytesInOutput": 8758
15403
15408
  },
15404
15409
  "src/file-path-comment.ts": {
15405
15410
  "bytesInOutput": 1961
@@ -15429,7 +15434,7 @@
15429
15434
  "bytesInOutput": 3362
15430
15435
  }
15431
15436
  },
15432
- "bytes": 4289396
15437
+ "bytes": 4292633
15433
15438
  }
15434
15439
  }
15435
15440
  }
@@ -1,6 +1,7 @@
1
1
  // src/fixture/no-service-wrapper.ts
2
2
  import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
3
- import "@typescript-eslint/scope-manager";
3
+ import { DefinitionType } from "@typescript-eslint/scope-manager";
4
+ import { PLAIN_URL_REGEXP, TOKENIZED_URL_REGEXP, replaceEndpointUrlPrefixWithDomain } from "./url.mjs";
4
5
  import { strict as assert } from "node:assert";
5
6
  import getDocumentationUrl from "../get-documentation-url.mjs";
6
7
  import { getEnclosingScopeNode } from "../ast/ts-tree.mjs";
@@ -16,9 +17,8 @@ var rule = createRule({
16
17
  },
17
18
  messages: {
18
19
  preferNativeFetch: "Prefer native fetch over customized service wrapper.",
19
- 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.'
20
- // unknownError:
21
- // 'Unknown error occurred in file "{{fileName}}": {{ error }}. Please manually convert the usage of customized service wrapper call to native fetch.',
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
22
  },
23
23
  fixable: "code",
24
24
  schema: []
@@ -30,8 +30,22 @@ var rule = createRule({
30
30
  const scopeManager = sourceCode.scopeManager;
31
31
  const parserService = ESLintUtils.getParserServices(context);
32
32
  const typeChecker = parserService.program.getTypeChecker();
33
- function isUrlArgumentTemplateLiteral(urlArgument, scope) {
34
- return urlArgument?.type === AST_NODE_TYPES.TemplateLiteral || urlArgument?.type === AST_NODE_TYPES.Identifier && scope.variables.some((variable) => variable.name === urlArgument.name);
33
+ function isUrlArgumentValid(urlArgument, scope) {
34
+ if (urlArgument?.type === AST_NODE_TYPES.Literal && typeof urlArgument.value === "string" || urlArgument?.type === AST_NODE_TYPES.TemplateLiteral) {
35
+ const urlText = sourceCode.getText(urlArgument);
36
+ return PLAIN_URL_REGEXP.test(urlText) || TOKENIZED_URL_REGEXP.test(urlText);
37
+ }
38
+ if (urlArgument?.type === AST_NODE_TYPES.Identifier) {
39
+ const foundVariable = scope.variables.find((variable) => variable.name === urlArgument.name);
40
+ assert.ok(foundVariable, `Variable "${urlArgument.name}" not found in scope`);
41
+ const variableDefinition = foundVariable.defs.find((def) => def.type === DefinitionType.Variable);
42
+ assert.ok(variableDefinition, `Variable "${urlArgument.name}" not defined in scope`);
43
+ const variableDefinitionNode = variableDefinition.node;
44
+ assert.ok(variableDefinitionNode.type === AST_NODE_TYPES.VariableDeclarator);
45
+ assert.ok(variableDefinitionNode.init, "Variable definition node has no init property");
46
+ return isUrlArgumentValid(variableDefinitionNode.init, scope);
47
+ }
48
+ return false;
35
49
  }
36
50
  function getType(identifier) {
37
51
  const variable = parserService.esTreeNodeToTSNodeMap.get(identifier);
@@ -76,7 +90,7 @@ var rule = createRule({
76
90
  }
77
91
  const configuration = services.object;
78
92
  if (configuration.type === AST_NODE_TYPES.Identifier) {
79
- return getType(configuration) === "Configuration";
93
+ return ["Configuration", "Configuration<ResolvedServices>"].includes(getType(configuration));
80
94
  }
81
95
  if (configuration.type !== AST_NODE_TYPES.MemberExpression) {
82
96
  return false;
@@ -89,59 +103,72 @@ var rule = createRule({
89
103
  }
90
104
  return {
91
105
  "CallExpression[callee.property.name=/^(head|get|put|post|del|patch)$/]": (serviceCall) => {
92
- const enclosingScopeNode = getEnclosingScopeNode(serviceCall);
93
- assert.ok(enclosingScopeNode, "enclosingScopeNode is undefined");
94
- const scope = scopeManager?.acquire(enclosingScopeNode);
95
- assert.ok(scope, "scope is undefined");
96
- const urlArgument = serviceCall.arguments[0];
97
- if (!isUrlArgumentTemplateLiteral(urlArgument, scope)) {
98
- return;
99
- }
100
- if (!isCalleeServiceWrapper(serviceCall)) {
101
- return;
102
- }
103
- assert.ok(serviceCall.callee.type === AST_NODE_TYPES.MemberExpression);
104
- assert.ok(serviceCall.callee.property.type === AST_NODE_TYPES.Identifier);
105
- const method = serviceCall.callee.property.name;
106
- const requestBodyProperty = ["put", "post", "options"].includes(method) ? serviceCall.arguments[1] : void 0;
107
- const optionsArgument = ["get", "head", "del"].includes(method) ? serviceCall.arguments[1] : serviceCall.arguments[2];
108
- if (optionsArgument === void 0 || optionsArgument.type !== AST_NODE_TYPES.ObjectExpression) {
106
+ try {
107
+ const enclosingScopeNode = getEnclosingScopeNode(serviceCall);
108
+ assert.ok(enclosingScopeNode, "enclosingScopeNode is undefined");
109
+ const scope = scopeManager?.acquire(enclosingScopeNode);
110
+ assert.ok(scope, "scope is undefined");
111
+ const urlArgument = serviceCall.arguments[0];
112
+ if (!isUrlArgumentValid(urlArgument, scope)) {
113
+ return;
114
+ }
115
+ if (!isCalleeServiceWrapper(serviceCall)) {
116
+ return;
117
+ }
118
+ assert.ok(serviceCall.callee.type === AST_NODE_TYPES.MemberExpression);
119
+ assert.ok(serviceCall.callee.property.type === AST_NODE_TYPES.Identifier);
120
+ const method = serviceCall.callee.property.name;
121
+ const requestBodyProperty = ["put", "post", "options"].includes(method) ? serviceCall.arguments[1] : void 0;
122
+ const optionsArgument = ["get", "head", "del"].includes(method) ? serviceCall.arguments[1] : serviceCall.arguments[2];
123
+ if (optionsArgument === void 0 || optionsArgument.type !== AST_NODE_TYPES.ObjectExpression) {
124
+ context.report({
125
+ node: serviceCall,
126
+ messageId: "invalidOptions"
127
+ });
128
+ return;
129
+ }
130
+ const resolveWithFullResponseProperty = optionsArgument.properties.find(
131
+ (property) => property.type === AST_NODE_TYPES.Property && property.key.type === AST_NODE_TYPES.Identifier && property.key.name === "resolveWithFullResponse"
132
+ );
133
+ if (resolveWithFullResponseProperty?.type !== AST_NODE_TYPES.Property || resolveWithFullResponseProperty.value.type !== AST_NODE_TYPES.Literal || resolveWithFullResponseProperty.value.value !== true) {
134
+ context.report({
135
+ node: optionsArgument,
136
+ messageId: "invalidOptions"
137
+ });
138
+ return;
139
+ }
140
+ const requestHeadersProperty = optionsArgument.properties.find(
141
+ (property) => property.type === AST_NODE_TYPES.Property && property.key.type === AST_NODE_TYPES.Identifier && property.key.name === "headers"
142
+ );
109
143
  context.report({
144
+ messageId: "preferNativeFetch",
110
145
  node: serviceCall,
111
- messageId: "invalidOptions"
146
+ fix(fixer) {
147
+ const url = sourceCode.getText(urlArgument);
148
+ const replacedUrl = replaceEndpointUrlPrefixWithDomain(url);
149
+ const indentation = getIndentation(serviceCall, sourceCode);
150
+ const fetchText = [
151
+ `fetch(${replacedUrl}, {`,
152
+ ` method: '${method.toUpperCase()}',`,
153
+ ...requestHeadersProperty ? [` ${sourceCode.getText(requestHeadersProperty)},`] : [],
154
+ ...requestBodyProperty ? [` body: JSON.stringify(${sourceCode.getText(requestBodyProperty)}),`] : [],
155
+ "})"
156
+ ].join(`
157
+ ${indentation}`);
158
+ return fixer.replaceText(serviceCall, fetchText);
159
+ }
112
160
  });
113
- return;
114
- }
115
- const resolveWithFullResponseProperty = optionsArgument.properties.find(
116
- (property) => property.type === AST_NODE_TYPES.Property && property.key.type === AST_NODE_TYPES.Identifier && property.key.name === "resolveWithFullResponse"
117
- );
118
- if (resolveWithFullResponseProperty?.type !== AST_NODE_TYPES.Property || resolveWithFullResponseProperty.value.type !== AST_NODE_TYPES.Literal || resolveWithFullResponseProperty.value.value !== true) {
161
+ } catch (error) {
162
+ console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
119
163
  context.report({
120
- node: optionsArgument,
121
- messageId: "invalidOptions"
164
+ node: serviceCall,
165
+ messageId: "unknownError",
166
+ data: {
167
+ fileName: context.filename,
168
+ error: error instanceof Error ? error.toString() : JSON.stringify(error)
169
+ }
122
170
  });
123
- return;
124
171
  }
125
- const requestHeadersProperty = optionsArgument.properties.find(
126
- (property) => property.type === AST_NODE_TYPES.Property && property.key.type === AST_NODE_TYPES.Identifier && property.key.name === "headers"
127
- );
128
- context.report({
129
- messageId: "preferNativeFetch",
130
- node: serviceCall,
131
- fix(fixer) {
132
- const url = sourceCode.getText(urlArgument);
133
- const indentation = getIndentation(serviceCall, sourceCode);
134
- const fetchText = [
135
- `fetch(${url}, {`,
136
- ` method: '${method.toUpperCase()}',`,
137
- ...requestHeadersProperty ? [` ${sourceCode.getText(requestHeadersProperty)},`] : [],
138
- ...requestBodyProperty ? [` body: JSON.stringify(${sourceCode.getText(requestBodyProperty)}),`] : [],
139
- "})"
140
- ].join(`
141
- ${indentation}`);
142
- return fixer.replaceText(serviceCall, fetchText);
143
- }
144
- });
145
172
  }
146
173
  };
147
174
  }
@@ -151,4 +178,4 @@ export {
151
178
  no_service_wrapper_default as default,
152
179
  ruleId
153
180
  };
154
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2ZpeHR1cmUvbm8tc2VydmljZS13cmFwcGVyLnRzIl0sCiAgIm1hcHBpbmdzIjogIjtBQVFBLFNBQVMsZ0JBQWdCLG1CQUE2QjtBQUN0RCxPQUEyQjtBQUMzQixTQUFTLFVBQVUsY0FBYztBQUNqQyxPQUFPLHlCQUF5QjtBQUNoQyxTQUFTLDZCQUE2QjtBQUN0QyxTQUFTLHNCQUFzQjtBQUV4QixJQUFNLFNBQVM7QUFjdEIsSUFBTSxhQUFhLFlBQVksWUFBWSxDQUFDLFNBQVMsb0JBQW9CLElBQUksQ0FBQztBQUU5RSxJQUFNLE9BQU8sV0FBVztBQUFBLEVBQ3RCLE1BQU07QUFBQSxFQUNOLE1BQU07QUFBQSxJQUNKLE1BQU07QUFBQSxJQUNOLE1BQU07QUFBQSxNQUNKLGFBQWE7QUFBQSxJQUNmO0FBQUEsSUFDQSxVQUFVO0FBQUEsTUFDUixtQkFBbUI7QUFBQSxNQUNuQixnQkFDRTtBQUFBO0FBQUE7QUFBQSxJQUdKO0FBQUEsSUFDQSxTQUFTO0FBQUEsSUFDVCxRQUFRLENBQUM7QUFBQSxFQUNYO0FBQUEsRUFDQSxnQkFBZ0IsQ0FBQztBQUFBO0FBQUEsRUFFakIsT0FBTyxTQUFTO0FBQ2QsVUFBTSxhQUFhLFFBQVE7QUFDM0IsVUFBTSxlQUFlLFdBQVc7QUFDaEMsVUFBTSxnQkFBZ0IsWUFBWSxrQkFBa0IsT0FBTztBQUMzRCxVQUFNLGNBQWMsY0FBYyxRQUFRLGVBQWU7QUFVekQsYUFBUyw2QkFBNkIsYUFBd0MsT0FBYztBQUMxRixhQUNFLGFBQWEsU0FBUyxlQUFlLG1CQUNwQyxhQUFhLFNBQVMsZUFBZSxjQUNwQyxNQUFNLFVBQVUsS0FBSyxDQUFDLGFBQWEsU0FBUyxTQUFTLFlBQVksSUFBSTtBQUFBLElBRTNFO0FBRUEsYUFBUyxRQUFRLFlBQWlDO0FBQ2hELFlBQU0sV0FBVyxjQUFjLHNCQUFzQixJQUFJLFVBQVU7QUFDbkUsWUFBTSxlQUFlLFlBQVksa0JBQWtCLFFBQVE7QUFDM0QsYUFBTyxZQUFZLGFBQWEs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
181
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2ZpeHR1cmUvbm8tc2VydmljZS13cmFwcGVyLnRzIl0sCiAgIm1hcHBpbmdzIjogIjtBQVFBLFNBQVMsZ0JBQWdCLG1CQUE2QjtBQUN0RCxTQUFTLHNCQUFrQztBQUMzQyxTQUFTLGtCQUFrQixzQkFBc0IsMENBQTBDO0FBQzNGLFNBQVMsVUFBVSxjQUFjO0FBQ2pDLE9BQU8seUJBQXlCO0FBQ2hDLFNBQVMsNkJBQTZCO0FBQ3RDLFNBQVMsc0JBQXNCO0FBRXhCLElBQU0sU0FBUztBQUV0QixJQUFNLGFBQWEsWUFBWSxZQUFZLENBQUMsU0FBUyxvQkFBb0IsSUFBSSxDQUFDO0FBRTlFLElBQU0sT0FBTyxXQUFXO0FBQUEsRUFDdEIsTUFBTTtBQUFBLEVBQ04sTUFBTTtBQUFBLElBQ0osTUFBTTtBQUFBLElBQ04sTUFBTTtBQUFBLE1BQ0osYUFBYTtBQUFBLElBQ2Y7QUFBQSxJQUNBLFVBQVU7QUFBQSxNQUNSLG1CQUFtQjtBQUFBLE1BQ25CLGdCQUNFO0FBQUEsTUFDRixjQUNFO0FBQUEsSUFDSjtBQUFBLElBQ0EsU0FBUztBQUFBLElBQ1QsUUFBUSxDQUFDO0FBQUEsRUFDWDtBQUFBLEVBQ0EsZ0JBQWdCLENBQUM7QUFBQTtBQUFBLEVBRWpCLE9BQU8sU0FBUztBQUNkLFVBQU0sYUFBYSxRQUFRO0FBQzNCLFVBQU0sZUFBZSxXQUFXO0FBQ2hDLFVBQU0sZ0JBQWdCLFlBQVksa0JBQWtCLE9BQU87QUFDM0QsVUFBTSxjQUFjLGNBQWMsUUFBUSxlQUFlO0FBVXpELGFBQVMsbUJBQW1CLGFBQXdDLE9BQWM7QUFDaEYsVUFDRyxhQUFhLFNBQVMsZUFBZSxXQUFXLE9BQU8sWUFBWSxVQUFVLFlBQzlFLGFBQWEsU0FBUyxlQUFlLGlCQUNyQztBQUNBLGNBQU0sVUFBVSxXQUFXLFFBQVEsV0FBVztBQUM5QyxlQUFPLGlCQUFpQixLQUFLLE9BQU8sS0FBSyxxQkFBcUIsS0FBSyxPQUFPO0FBQUEsTUFDNUU7QUFFQSxVQUFJLGFBQWEs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
@@ -1,8 +1,16 @@
1
1
  // src/fixture/url.ts
2
+ var PLAIN_URL_REGEXP = /^[`']\/\w+(?<serviceNamePart>-\w+)*\/v\d+\/.+[`']$/u;
3
+ var TOKENIZED_URL_REGEXP = /^`\$\{(?<serviceNamePart>[A-Z]+_)*BASE_PATH\}\/.+`$/u;
2
4
  function replaceEndpointUrlPrefixWithBasePath(url) {
3
- return url.replace(/`\/\w+(?<parts>-\w+)*\/v\d+\//u, "`${BASE_PATH}/");
5
+ return url.replace(/^`\/\w+(?<parts>-\w+)*\/v\d+\//u, "`${BASE_PATH}/");
6
+ }
7
+ function replaceEndpointUrlPrefixWithDomain(url) {
8
+ return url.replace(/\/(?<servicename>\w+(?<parts>-\w+)*)(?<path>\/v\d+\/.*$)/u, "https://$1.checkdigit/$1$3");
4
9
  }
5
10
  export {
6
- replaceEndpointUrlPrefixWithBasePath
11
+ PLAIN_URL_REGEXP,
12
+ TOKENIZED_URL_REGEXP,
13
+ replaceEndpointUrlPrefixWithBasePath,
14
+ replaceEndpointUrlPrefixWithDomain
7
15
  };
8
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2ZpeHR1cmUvdXJsLnRzIl0sCiAgIm1hcHBpbmdzIjogIjtBQUVPLFNBQVMscUNBQXFDLEtBQWE7QUFFaEUsU0FBTyxJQUFJLFFBQVEsa0NBQWtDLGdCQUFnQjtBQUN2RTsiLAogICJuYW1lcyI6IFtdCn0K
16
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2ZpeHR1cmUvdXJsLnRzIl0sCiAgIm1hcHBpbmdzIjogIjtBQUVPLElBQU0sbUJBQW1CO0FBQ3pCLElBQU0sdUJBQXVCO0FBRTdCLFNBQVMscUNBQXFDLEtBQWE7QUFFaEUsU0FBTyxJQUFJLFFBQVEsbUNBQW1DLGdCQUFnQjtBQUN4RTtBQUVPLFNBQVMsbUNBQW1DLEtBQWE7QUFFOUQsU0FBTyxJQUFJLFFBQVEsNkRBQTZELDRCQUE0QjtBQUM5RzsiLAogICJuYW1lcyI6IFtdCn0K
@@ -1,4 +1,4 @@
1
1
  import { ESLintUtils } from '@typescript-eslint/utils';
2
2
  export declare const ruleId = "no-service-wrapper";
3
- declare const rule: ESLintUtils.RuleModule<"preferNativeFetch" | "invalidOptions", never[], ESLintUtils.RuleListener>;
3
+ declare const rule: ESLintUtils.RuleModule<"preferNativeFetch" | "unknownError" | "invalidOptions", never[], ESLintUtils.RuleListener>;
4
4
  export default rule;
@@ -1 +1,4 @@
1
+ export declare const PLAIN_URL_REGEXP: RegExp;
2
+ export declare const TOKENIZED_URL_REGEXP: RegExp;
1
3
  export declare function replaceEndpointUrlPrefixWithBasePath(url: string): string;
4
+ export declare function replaceEndpointUrlPrefixWithDomain(url: string): string;
@@ -14,7 +14,7 @@ declare const _default: {
14
14
  "no-fixture": import("eslint").Rule.RuleModule;
15
15
  "fetch-header-getter": import("eslint").Rule.RuleModule;
16
16
  "fetch-then": import("eslint").Rule.RuleModule;
17
- "no-service-wrapper": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferNativeFetch" | "invalidOptions", never[], import("@typescript-eslint/utils/ts-eslint").RuleListener>;
17
+ "no-service-wrapper": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferNativeFetch" | "unknownError" | "invalidOptions", never[], import("@typescript-eslint/utils/ts-eslint").RuleListener>;
18
18
  };
19
19
  configs: {
20
20
  all: {
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@checkdigit/eslint-plugin","version":"6.6.0-PR.75-3712","description":"Check Digit eslint plugins","keywords":["eslint","eslintplugin"],"homepage":"https://github.com/checkdigit/eslint-plugin#readme","bugs":{"url":"https://github.com/checkdigit/eslint-plugin/issues"},"repository":{"type":"git","url":"https://github.com/checkdigit/eslint-plugin"},"license":"MIT","author":"Check Digit, LLC","sideEffects":false,"type":"module","exports":{".":{"types":"./dist-types/index.d.ts","require":"./dist-cjs/index.cjs","import":"./dist-mjs/index.mjs","default":"./dist-mjs/index.mjs"}},"files":["src","dist-types","dist-cjs","dist-mjs","!src/**/*.test.ts","!src/**/*.spec.ts","!dist-types/**/*.test.d.ts","!dist-types/**/*.spec.d.ts","!dist-cjs/**/*.test.cjs","!dist-cjs/**/*.spec.cjs","!dist-mjs/**/*.test.mjs","!dist-mjs/**/*.spec.mjs","SECURITY.md"],"scripts":{"build:dist-cjs":"rimraf dist-cjs && npx builder --type=commonjs --sourceMap --entryPoint=index.ts --outDir=dist-cjs --outFile=index.cjs --external=espree && echo \"module.exports = module.exports.default;\" >> dist-cjs/index.cjs","build:dist-mjs":"rimraf dist-mjs && npx builder --type=module --sourceMap --outDir=dist-mjs && node dist-mjs/index.mjs","build:dist-types":"rimraf dist-types && npx builder --type=types --outDir=dist-types","ci:compile":"tsc --noEmit","ci:coverage":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=true","ci:lint":"npm run lint","ci:style":"npm run prettier","ci:test":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=false","lint":"eslint --max-warnings 0 --ignore-path .gitignore .","lint:fix":"eslint --ignore-path .gitignore . --fix","prepublishOnly":"npm run build:dist-types && npm run build:dist-cjs && npm run build:dist-mjs","prettier":"prettier --ignore-path .gitignore --list-different .","prettier:fix":"prettier --ignore-path .gitignore --write .","test":"npm run ci:compile && npm run ci:test && npm run ci:lint && npm run ci:style"},"prettier":"@checkdigit/prettier-config","jest":{"preset":"@checkdigit/jest-config"},"dependencies":{"@typescript-eslint/type-utils":"7.18.0","@typescript-eslint/utils":"7.18.0","ts-api-utils":"^1.3.0"},"devDependencies":{"@checkdigit/jest-config":"^6.0.2","@checkdigit/prettier-config":"^5.5.0","@checkdigit/typescript-config":"6.0.0","@types/eslint":"^8.56.10","@typescript-eslint/eslint-plugin":"^7.18.0","@typescript-eslint/parser":"^7.18.0","@typescript-eslint/rule-tester":"7.18.0","eslint-config-prettier":"^9.1.0","eslint-plugin-eslint-plugin":"^6.2.0","eslint-plugin-import":"^2.29.1","eslint-plugin-no-only-tests":"^3.1.0","eslint-plugin-no-secrets":"^1.0.2","eslint-plugin-node":"^11.1.0","eslint-plugin-sonarjs":"0.24.0"},"peerDependencies":{"eslint":">=8 <9"},"engines":{"node":">=20.14"}}
1
+ {"name":"@checkdigit/eslint-plugin","version":"6.6.0-PR.75-7154","description":"Check Digit eslint plugins","keywords":["eslint","eslintplugin"],"homepage":"https://github.com/checkdigit/eslint-plugin#readme","bugs":{"url":"https://github.com/checkdigit/eslint-plugin/issues"},"repository":{"type":"git","url":"https://github.com/checkdigit/eslint-plugin"},"license":"MIT","author":"Check Digit, LLC","sideEffects":false,"type":"module","exports":{".":{"types":"./dist-types/index.d.ts","require":"./dist-cjs/index.cjs","import":"./dist-mjs/index.mjs","default":"./dist-mjs/index.mjs"}},"files":["src","dist-types","dist-cjs","dist-mjs","!src/**/*.test.ts","!src/**/*.spec.ts","!dist-types/**/*.test.d.ts","!dist-types/**/*.spec.d.ts","!dist-cjs/**/*.test.cjs","!dist-cjs/**/*.spec.cjs","!dist-mjs/**/*.test.mjs","!dist-mjs/**/*.spec.mjs","SECURITY.md"],"scripts":{"build:dist-cjs":"rimraf dist-cjs && npx builder --type=commonjs --sourceMap --entryPoint=index.ts --outDir=dist-cjs --outFile=index.cjs --external=espree && echo \"module.exports = module.exports.default;\" >> dist-cjs/index.cjs","build:dist-mjs":"rimraf dist-mjs && npx builder --type=module --sourceMap --outDir=dist-mjs && node dist-mjs/index.mjs","build:dist-types":"rimraf dist-types && npx builder --type=types --outDir=dist-types","ci:compile":"tsc --noEmit","ci:coverage":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=true","ci:lint":"npm run lint","ci:style":"npm run prettier","ci:test":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=false","lint":"eslint --max-warnings 0 --ignore-path .gitignore .","lint:fix":"eslint --ignore-path .gitignore . --fix","prepublishOnly":"npm run build:dist-types && npm run build:dist-cjs && npm run build:dist-mjs","prettier":"prettier --ignore-path .gitignore --list-different .","prettier:fix":"prettier --ignore-path .gitignore --write .","test":"npm run ci:compile && npm run ci:test && npm run ci:lint && npm run ci:style"},"prettier":"@checkdigit/prettier-config","jest":{"preset":"@checkdigit/jest-config"},"dependencies":{"@typescript-eslint/type-utils":"7.18.0","@typescript-eslint/utils":"7.18.0","ts-api-utils":"^1.3.0"},"devDependencies":{"@checkdigit/jest-config":"^6.0.2","@checkdigit/prettier-config":"^5.5.0","@checkdigit/typescript-config":"6.0.0","@types/eslint":"^8.56.10","@typescript-eslint/eslint-plugin":"^7.18.0","@typescript-eslint/parser":"^7.18.0","@typescript-eslint/rule-tester":"7.18.0","eslint-config-prettier":"^9.1.0","eslint-plugin-eslint-plugin":"^6.2.0","eslint-plugin-import":"^2.29.1","eslint-plugin-no-only-tests":"^3.1.0","eslint-plugin-no-secrets":"^1.0.2","eslint-plugin-node":"^11.1.0","eslint-plugin-sonarjs":"0.24.0"},"peerDependencies":{"eslint":">=8 <9"},"engines":{"node":">=20.14"}}
@@ -7,7 +7,8 @@
7
7
  */
8
8
 
9
9
  import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils';
10
- import { type Scope } from '@typescript-eslint/scope-manager';
10
+ import { DefinitionType, type Scope } from '@typescript-eslint/scope-manager';
11
+ import { PLAIN_URL_REGEXP, TOKENIZED_URL_REGEXP, replaceEndpointUrlPrefixWithDomain } from './url';
11
12
  import { strict as assert } from 'node:assert';
12
13
  import getDocumentationUrl from '../get-documentation-url';
13
14
  import { getEnclosingScopeNode } from '../ast/ts-tree';
@@ -15,18 +16,6 @@ import { getIndentation } from '../ast/format';
15
16
 
16
17
  export const ruleId = 'no-service-wrapper';
17
18
 
18
- // interface ServiceCallInformation {
19
- // rootNode:
20
- // | TSESTree.AwaitExpression
21
- // | TSESTree.ReturnStatement
22
- // | TSESTree.VariableDeclaration
23
- // | TSESTree.CallExpression;
24
- // fixtureNode: TSESTree.AwaitExpression | TSESTree.CallExpression;
25
- // variableDeclaration?: TSESTree.VariableDeclaration;
26
- // requestBody?: TSESTree.Expression;
27
- // requestHeaders?: TSESTree.Expression;
28
- // }
29
-
30
19
  const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
31
20
 
32
21
  const rule = createRule({
@@ -40,8 +29,8 @@ const rule = createRule({
40
29
  preferNativeFetch: 'Prefer native fetch over customized service wrapper.',
41
30
  invalidOptions:
42
31
  '"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.',
43
- // unknownError:
44
- // 'Unknown error occurred in file "{{fileName}}": {{ error }}. Please manually convert the usage of customized service wrapper call to native fetch.',
32
+ unknownError:
33
+ 'Unknown error occurred in file "{{fileName}}": {{ error }}. Please manually convert the usage of customized service wrapper call to native fetch.',
45
34
  },
46
35
  fixable: 'code',
47
36
  schema: [],
@@ -62,12 +51,27 @@ const rule = createRule({
62
51
  // });
63
52
  // }
64
53
 
65
- function isUrlArgumentTemplateLiteral(urlArgument: TSESTree.Node | undefined, scope: Scope) {
66
- return (
67
- urlArgument?.type === AST_NODE_TYPES.TemplateLiteral ||
68
- (urlArgument?.type === AST_NODE_TYPES.Identifier &&
69
- scope.variables.some((variable) => variable.name === urlArgument.name))
70
- );
54
+ function isUrlArgumentValid(urlArgument: TSESTree.Node | undefined, scope: Scope) {
55
+ if (
56
+ (urlArgument?.type === AST_NODE_TYPES.Literal && typeof urlArgument.value === 'string') ||
57
+ urlArgument?.type === AST_NODE_TYPES.TemplateLiteral
58
+ ) {
59
+ const urlText = sourceCode.getText(urlArgument);
60
+ return PLAIN_URL_REGEXP.test(urlText) || TOKENIZED_URL_REGEXP.test(urlText);
61
+ }
62
+
63
+ if (urlArgument?.type === AST_NODE_TYPES.Identifier) {
64
+ const foundVariable = scope.variables.find((variable) => variable.name === urlArgument.name);
65
+ assert.ok(foundVariable, `Variable "${urlArgument.name}" not found in scope`);
66
+ const variableDefinition = foundVariable.defs.find((def) => def.type === DefinitionType.Variable);
67
+ assert.ok(variableDefinition, `Variable "${urlArgument.name}" not defined in scope`);
68
+ const variableDefinitionNode = variableDefinition.node;
69
+ assert.ok(variableDefinitionNode.type === AST_NODE_TYPES.VariableDeclarator);
70
+ assert.ok(variableDefinitionNode.init, 'Variable definition node has no init property');
71
+ return isUrlArgumentValid(variableDefinitionNode.init, scope);
72
+ }
73
+
74
+ return false;
71
75
  }
72
76
 
73
77
  function getType(identifier: TSESTree.Identifier) {
@@ -119,7 +123,7 @@ const rule = createRule({
119
123
  }
120
124
  const configuration = services.object;
121
125
  if (configuration.type === AST_NODE_TYPES.Identifier) {
122
- return getType(configuration) === 'Configuration';
126
+ return ['Configuration', 'Configuration<ResolvedServices>'].includes(getType(configuration));
123
127
  }
124
128
 
125
129
  // following applies only to test code (fixture)
@@ -138,83 +142,100 @@ const rule = createRule({
138
142
  'CallExpression[callee.property.name=/^(head|get|put|post|del|patch)$/]': (
139
143
  serviceCall: TSESTree.CallExpression,
140
144
  ) => {
141
- const enclosingScopeNode = getEnclosingScopeNode(serviceCall);
142
- assert.ok(enclosingScopeNode, 'enclosingScopeNode is undefined');
143
- const scope = scopeManager?.acquire(enclosingScopeNode);
144
- assert.ok(scope, 'scope is undefined');
145
-
146
- const urlArgument = serviceCall.arguments[0];
147
-
148
- if (!isUrlArgumentTemplateLiteral(urlArgument, scope)) {
149
- return;
150
- }
145
+ try {
146
+ const enclosingScopeNode = getEnclosingScopeNode(serviceCall);
147
+ assert.ok(enclosingScopeNode, 'enclosingScopeNode is undefined');
148
+ const scope = scopeManager?.acquire(enclosingScopeNode);
149
+ assert.ok(scope, 'scope is undefined');
150
+
151
+ const urlArgument = serviceCall.arguments[0];
152
+
153
+ if (!isUrlArgumentValid(urlArgument, scope)) {
154
+ return;
155
+ }
156
+
157
+ if (!isCalleeServiceWrapper(serviceCall)) {
158
+ return;
159
+ }
160
+
161
+ assert.ok(serviceCall.callee.type === AST_NODE_TYPES.MemberExpression);
162
+ assert.ok(serviceCall.callee.property.type === AST_NODE_TYPES.Identifier);
163
+
164
+ // method
165
+ const method = serviceCall.callee.property.name;
166
+
167
+ // body
168
+ const requestBodyProperty = ['put', 'post', 'options'].includes(method)
169
+ ? serviceCall.arguments[1]
170
+ : undefined;
171
+
172
+ // options
173
+ const optionsArgument = ['get', 'head', 'del'].includes(method)
174
+ ? serviceCall.arguments[1]
175
+ : serviceCall.arguments[2];
176
+ if (optionsArgument === undefined || optionsArgument.type !== AST_NODE_TYPES.ObjectExpression) {
177
+ context.report({
178
+ node: serviceCall,
179
+ messageId: 'invalidOptions',
180
+ });
181
+ return;
182
+ }
183
+ const resolveWithFullResponseProperty = optionsArgument.properties.find(
184
+ (property) =>
185
+ property.type === AST_NODE_TYPES.Property &&
186
+ property.key.type === AST_NODE_TYPES.Identifier &&
187
+ property.key.name === 'resolveWithFullResponse',
188
+ );
189
+ if (
190
+ resolveWithFullResponseProperty?.type !== AST_NODE_TYPES.Property ||
191
+ resolveWithFullResponseProperty.value.type !== AST_NODE_TYPES.Literal ||
192
+ resolveWithFullResponseProperty.value.value !== true
193
+ ) {
194
+ context.report({
195
+ node: optionsArgument,
196
+ messageId: 'invalidOptions',
197
+ });
198
+ return;
199
+ }
200
+
201
+ // headers
202
+ const requestHeadersProperty = optionsArgument.properties.find(
203
+ (property) =>
204
+ property.type === AST_NODE_TYPES.Property &&
205
+ property.key.type === AST_NODE_TYPES.Identifier &&
206
+ property.key.name === 'headers',
207
+ );
151
208
 
152
- if (!isCalleeServiceWrapper(serviceCall)) {
153
- return;
154
- }
155
-
156
- assert.ok(serviceCall.callee.type === AST_NODE_TYPES.MemberExpression);
157
- assert.ok(serviceCall.callee.property.type === AST_NODE_TYPES.Identifier);
158
-
159
- // method
160
- const method = serviceCall.callee.property.name;
161
-
162
- // body
163
- const requestBodyProperty = ['put', 'post', 'options'].includes(method) ? serviceCall.arguments[1] : undefined;
164
-
165
- // options
166
- const optionsArgument = ['get', 'head', 'del'].includes(method)
167
- ? serviceCall.arguments[1]
168
- : serviceCall.arguments[2];
169
- if (optionsArgument === undefined || optionsArgument.type !== AST_NODE_TYPES.ObjectExpression) {
170
209
  context.report({
210
+ messageId: 'preferNativeFetch',
171
211
  node: serviceCall,
172
- messageId: 'invalidOptions',
212
+ fix(fixer) {
213
+ const url = sourceCode.getText(urlArgument);
214
+ const replacedUrl = replaceEndpointUrlPrefixWithDomain(url);
215
+ const indentation = getIndentation(serviceCall, sourceCode);
216
+
217
+ const fetchText = [
218
+ `fetch(${replacedUrl}, {`,
219
+ ` method: '${method.toUpperCase()}',`,
220
+ ...(requestHeadersProperty ? [` ${sourceCode.getText(requestHeadersProperty)},`] : []),
221
+ ...(requestBodyProperty ? [` body: JSON.stringify(${sourceCode.getText(requestBodyProperty)}),`] : []),
222
+ '})',
223
+ ].join(`\n${indentation}`);
224
+ return fixer.replaceText(serviceCall, fetchText);
225
+ },
173
226
  });
174
- return;
175
- }
176
- const resolveWithFullResponseProperty = optionsArgument.properties.find(
177
- (property) =>
178
- property.type === AST_NODE_TYPES.Property &&
179
- property.key.type === AST_NODE_TYPES.Identifier &&
180
- property.key.name === 'resolveWithFullResponse',
181
- );
182
- if (
183
- resolveWithFullResponseProperty?.type !== AST_NODE_TYPES.Property ||
184
- resolveWithFullResponseProperty.value.type !== AST_NODE_TYPES.Literal ||
185
- resolveWithFullResponseProperty.value.value !== true
186
- ) {
227
+ } catch (error) {
228
+ // eslint-disable-next-line no-console
229
+ console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
187
230
  context.report({
188
- node: optionsArgument,
189
- messageId: 'invalidOptions',
231
+ node: serviceCall,
232
+ messageId: 'unknownError',
233
+ data: {
234
+ fileName: context.filename,
235
+ error: error instanceof Error ? error.toString() : JSON.stringify(error),
236
+ },
190
237
  });
191
- return;
192
238
  }
193
-
194
- // headers
195
- const requestHeadersProperty = optionsArgument.properties.find(
196
- (property) =>
197
- property.type === AST_NODE_TYPES.Property &&
198
- property.key.type === AST_NODE_TYPES.Identifier &&
199
- property.key.name === 'headers',
200
- );
201
-
202
- context.report({
203
- messageId: 'preferNativeFetch',
204
- node: serviceCall,
205
- fix(fixer) {
206
- const url = sourceCode.getText(urlArgument);
207
- const indentation = getIndentation(serviceCall, sourceCode);
208
- const fetchText = [
209
- `fetch(${url}, {`,
210
- ` method: '${method.toUpperCase()}',`,
211
- ...(requestHeadersProperty ? [` ${sourceCode.getText(requestHeadersProperty)},`] : []),
212
- ...(requestBodyProperty ? [` body: JSON.stringify(${sourceCode.getText(requestBodyProperty)}),`] : []),
213
- '})',
214
- ].join(`\n${indentation}`);
215
- return fixer.replaceText(serviceCall, fetchText);
216
- },
217
- });
218
239
  },
219
240
  };
220
241
  },
@@ -1,6 +1,14 @@
1
1
  // fixture/url.ts
2
2
 
3
+ export const PLAIN_URL_REGEXP = /^[`']\/\w+(?<serviceNamePart>-\w+)*\/v\d+\/.+[`']$/u;
4
+ export const TOKENIZED_URL_REGEXP = /^`\$\{(?<serviceNamePart>[A-Z]+_)*BASE_PATH\}\/.+`$/u;
5
+
3
6
  export function replaceEndpointUrlPrefixWithBasePath(url: string) {
4
7
  // eslint-disable-next-line no-template-curly-in-string
5
- return url.replace(/`\/\w+(?<parts>-\w+)*\/v\d+\//u, '`${BASE_PATH}/');
8
+ return url.replace(/^`\/\w+(?<parts>-\w+)*\/v\d+\//u, '`${BASE_PATH}/');
9
+ }
10
+
11
+ export function replaceEndpointUrlPrefixWithDomain(url: string) {
12
+ // eslint-disable-next-line no-template-curly-in-string
13
+ return url.replace(/\/(?<servicename>\w+(?<parts>-\w+)*)(?<path>\/v\d+\/.*$)/u, 'https://$1.checkdigit/$1$3');
6
14
  }