@checkdigit/eslint-plugin 6.6.0-PR.75-1cd1 → 6.6.0-PR.75-20dc

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.
@@ -17,10 +17,11 @@ import type {
17
17
  VariableDeclaration,
18
18
  } from 'estree';
19
19
  import { type Rule, type Scope, SourceCode } from 'eslint';
20
- import { getEnclosingScopeNode, getEnclosingStatement, getParent } from './ast/tree';
20
+ import { getEnclosingScopeNode, getEnclosingStatement, getParent } from '../ast/tree';
21
+ import { analyzeResponseReferences } from './response-reference';
21
22
  import { strict as assert } from 'node:assert';
22
- import getDocumentationUrl from './get-documentation-url';
23
- import { getIndentation } from './ast/format';
23
+ import getDocumentationUrl from '../get-documentation-url';
24
+ import { getIndentation } from '../ast/format';
24
25
 
25
26
  export const ruleId = 'no-fixture';
26
27
 
@@ -42,7 +43,7 @@ function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInfo
42
43
 
43
44
  let nextCall;
44
45
  if (parent.type === 'ReturnStatement') {
45
- // direct return, no variable declaration / await
46
+ // direct return, no variable declaration or await
46
47
  results.fixtureNode = call;
47
48
  results.rootNode = parent;
48
49
  } else if (parent.type === 'AwaitExpression') {
@@ -84,103 +85,13 @@ function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInfo
84
85
  nextCall = setRequestHeaderCall;
85
86
  }
86
87
  } else {
87
- throw new Error(`Unexpected expression in fixture/supertest call ${sourceCode.getText(parent)}`);
88
+ throw new Error(`Unexpected expression in fixture/supertest call ${sourceCode.getText(parent)}.`);
88
89
  }
89
90
  if (nextCall) {
90
91
  analyzeFixtureCall(nextCall, results, sourceCode);
91
92
  }
92
93
  }
93
94
 
94
- // analyze response related variables and their references0
95
- function analyzeResponseReferences(fixtureInformation: FixtureCallInformation, scopeManager: Scope.ScopeManager) {
96
- const results: {
97
- variable?: Scope.Variable;
98
- bodyReferences: MemberExpression[];
99
- headersReferences: MemberExpression[];
100
- statusReferences: MemberExpression[];
101
- destructuringBodyVariable?: Scope.Variable;
102
- destructuringHeadersVariable?: Scope.Variable;
103
- destructuringHeadersReferences?: MemberExpression[] | undefined;
104
- } = {
105
- bodyReferences: [],
106
- headersReferences: [],
107
- statusReferences: [],
108
- };
109
-
110
- if (fixtureInformation.variableDeclaration) {
111
- const responseVariables = scopeManager.getDeclaredVariables(fixtureInformation.variableDeclaration);
112
- for (const responseVariable of responseVariables) {
113
- const scope = responseVariable.scope;
114
- const identifier = responseVariable.identifiers[0];
115
- assert.ok(identifier);
116
- const identifierParent = getParent(identifier);
117
- assert.ok(identifierParent);
118
- if (identifierParent.type === 'VariableDeclarator') {
119
- // e.g. const response = ...
120
- results.variable = responseVariable;
121
- const responseReferences = responseVariable.references.map((responseReference) =>
122
- getParent(responseReference.identifier),
123
- );
124
- // e.g. response.body
125
- results.bodyReferences = responseReferences.filter(
126
- (node): node is MemberExpression =>
127
- node !== null &&
128
- node !== undefined &&
129
- node.type === 'MemberExpression' &&
130
- node.property.type === 'Identifier' &&
131
- node.property.name === 'body',
132
- );
133
- // e.g. response.headers / response.header / response.get()
134
- results.headersReferences = responseReferences.filter(
135
- (node): node is MemberExpression =>
136
- node !== null &&
137
- node !== undefined &&
138
- node.type === 'MemberExpression' &&
139
- node.property.type === 'Identifier' &&
140
- (node.property.name === 'header' || node.property.name === 'headers' || node.property.name === 'get'),
141
- );
142
- // e.g. response.status / response.statusCode
143
- results.statusReferences = responseReferences.filter(
144
- (node): node is MemberExpression =>
145
- node !== null &&
146
- node !== undefined &&
147
- node.type === 'MemberExpression' &&
148
- node.property.type === 'Identifier' &&
149
- (node.property.name === 'status' || node.property.name === 'statusCode'),
150
- );
151
- } else if (
152
- // body reference through destruction/renaming, e.g. "const { body } = ..."
153
- identifierParent.type === 'Property' &&
154
- identifierParent.key.type === 'Identifier' &&
155
- identifierParent.key.name === 'body'
156
- ) {
157
- results.destructuringBodyVariable = responseVariable;
158
- } else if (
159
- // header reference through destruction/renaming, e.g. "const { headers } = ..."
160
- identifierParent.type === 'Property' &&
161
- identifierParent.key.type === 'Identifier' &&
162
- identifierParent.key.name === 'headers'
163
- ) {
164
- results.destructuringHeadersVariable = responseVariable;
165
- results.destructuringHeadersReferences = scope.set
166
- .get(responseVariable.name)
167
- ?.references.map((reference) => reference.identifier)
168
- .map(getParent)
169
- .filter(
170
- (parent): parent is MemberExpression =>
171
- parent?.type === 'MemberExpression' &&
172
- parent.property.type === 'Identifier' &&
173
- parent.property.name !== 'get' &&
174
- getParent(parent)?.type !== 'CallExpression',
175
- );
176
- } else {
177
- throw new Error(`Unknown response variable reference: ${responseVariable.name}`);
178
- }
179
- }
180
- }
181
- return results;
182
- }
183
-
184
95
  // `/sample-service/v1/ping` -> `${BASE_PATH}/ping`
185
96
  function replaceEndpointUrlPrefixWithBasePath(url: string) {
186
97
  // eslint-disable-next-line no-template-curly-in-string
@@ -353,8 +264,7 @@ const rule: Rule.RuleModule = {
353
264
  statusReferences: responseStatusReferences,
354
265
  destructuringBodyVariable: destructuringResponseBodyVariable,
355
266
  destructuringHeadersVariable: destructuringResponseHeadersVariable,
356
- // destructuringHeadersReferences: destructuringResponseHeadersReferences,
357
- } = analyzeResponseReferences(fixtureCallInformation, scopeManager);
267
+ } = analyzeResponseReferences(fixtureCallInformation.variableDeclaration, scopeManager);
358
268
 
359
269
  // convert url from `/sample-service/v1/ping` to `${BASE_PATH}/ping`
360
270
  const originalUrlArgumentText = sourceCode.getText(urlArgumentNode);
@@ -491,19 +401,6 @@ const rule: Rule.RuleModule = {
491
401
  assert.ok(headerName !== undefined);
492
402
  yield fixer.replaceText(parent, `${responseVariableNameToUse}.headers.get(${headerName})`);
493
403
  }
494
- // if (destructuringResponseHeadersVariable !== undefined) {
495
- // for (const destructuringResponseHeadersReference of destructuringResponseHeadersReferences ?? []) {
496
- // const headerNameNode = destructuringResponseHeadersReference.property;
497
- // const headerName = destructuringResponseHeadersReference.computed
498
- // ? sourceCode.getText(headerNameNode)
499
- // : `'${sourceCode.getText(headerNameNode)}'`;
500
-
501
- // yield fixer.replaceText(
502
- // destructuringResponseHeadersReference,
503
- // `${destructuringResponseHeadersVariable.name}.get(${headerName})`,
504
- // );
505
- // }
506
- // }
507
404
 
508
405
  // convert response.statusCode to response.status
509
406
  for (const responseStatusReference of responseStatusReferences) {
@@ -0,0 +1,108 @@
1
+ // no-fixture.ts
2
+
3
+ /*
4
+ * Copyright (c) 2021-2024 Check Digit, LLC
5
+ *
6
+ * This code is licensed under the MIT license (see LICENSE.txt for details).
7
+ */
8
+
9
+ import type { MemberExpression, VariableDeclaration } from 'estree';
10
+ import { type Scope } from 'eslint';
11
+ import { strict as assert } from 'node:assert';
12
+ import { getParent } from '../ast/tree';
13
+
14
+ /**
15
+ * analyze response related variables and their references
16
+ * the implementation is for fixture API, but it can be used for fetch API as well since the tree structure is similar
17
+ * @param variableDeclaration - variable declaration node
18
+ */
19
+ export function analyzeResponseReferences(
20
+ variableDeclaration: VariableDeclaration | undefined,
21
+ scopeManager: Scope.ScopeManager,
22
+ ) {
23
+ const results: {
24
+ variable?: Scope.Variable;
25
+ bodyReferences: MemberExpression[];
26
+ headersReferences: MemberExpression[];
27
+ statusReferences: MemberExpression[];
28
+ destructuringBodyVariable?: Scope.Variable;
29
+ destructuringHeadersVariable?: Scope.Variable;
30
+ destructuringHeadersReferences?: MemberExpression[] | undefined;
31
+ } = {
32
+ bodyReferences: [],
33
+ headersReferences: [],
34
+ statusReferences: [],
35
+ };
36
+ if (!variableDeclaration) {
37
+ return results;
38
+ }
39
+
40
+ const responseVariables = scopeManager.getDeclaredVariables(variableDeclaration);
41
+ for (const responseVariable of responseVariables) {
42
+ const identifier = responseVariable.identifiers[0];
43
+ assert.ok(identifier);
44
+ const identifierParent = getParent(identifier);
45
+ assert.ok(identifierParent);
46
+ if (identifierParent.type === 'VariableDeclarator') {
47
+ // e.g. const response = ...
48
+ results.variable = responseVariable;
49
+ const responseReferences = responseVariable.references.map((responseReference) =>
50
+ getParent(responseReference.identifier),
51
+ );
52
+ // e.g. response.body
53
+ results.bodyReferences = responseReferences.filter(
54
+ (node): node is MemberExpression =>
55
+ node !== null &&
56
+ node !== undefined &&
57
+ node.type === 'MemberExpression' &&
58
+ node.property.type === 'Identifier' &&
59
+ node.property.name === 'body',
60
+ );
61
+ // e.g. response.headers / response.header / response.get()
62
+ results.headersReferences = responseReferences.filter(
63
+ (node): node is MemberExpression =>
64
+ node !== null &&
65
+ node !== undefined &&
66
+ node.type === 'MemberExpression' &&
67
+ node.property.type === 'Identifier' &&
68
+ (node.property.name === 'header' || node.property.name === 'headers' || node.property.name === 'get'),
69
+ );
70
+ // e.g. response.status / response.statusCode
71
+ results.statusReferences = responseReferences.filter(
72
+ (node): node is MemberExpression =>
73
+ node !== null &&
74
+ node !== undefined &&
75
+ node.type === 'MemberExpression' &&
76
+ node.property.type === 'Identifier' &&
77
+ (node.property.name === 'status' || node.property.name === 'statusCode'),
78
+ );
79
+ } else if (
80
+ // body reference through destruction/renaming, e.g. "const { body } = ..."
81
+ identifierParent.type === 'Property' &&
82
+ identifierParent.key.type === 'Identifier' &&
83
+ identifierParent.key.name === 'body'
84
+ ) {
85
+ results.destructuringBodyVariable = responseVariable;
86
+ } else if (
87
+ // header reference through destruction/renaming, e.g. "const { headers } = ..."
88
+ identifierParent.type === 'Property' &&
89
+ identifierParent.key.type === 'Identifier' &&
90
+ identifierParent.key.name === 'headers'
91
+ ) {
92
+ results.destructuringHeadersVariable = responseVariable;
93
+ results.destructuringHeadersReferences = responseVariable.references
94
+ .map((reference) => reference.identifier)
95
+ .map(getParent)
96
+ .filter(
97
+ (parent): parent is MemberExpression =>
98
+ parent?.type === 'MemberExpression' &&
99
+ parent.property.type === 'Identifier' &&
100
+ parent.property.name !== 'get' &&
101
+ getParent(parent)?.type !== 'CallExpression',
102
+ );
103
+ } else {
104
+ throw new Error(`Unknown response variable reference: ${responseVariable.name}`);
105
+ }
106
+ }
107
+ return results;
108
+ }
package/src/index.ts CHANGED
@@ -6,9 +6,9 @@
6
6
  * This code is licensed under the MIT license (see LICENSE.txt for details).
7
7
  */
8
8
 
9
+ import fetchHeaderGetter, { ruleId as fetchHeaderGetterRuleId } from './fixture/fetch-header-getter';
9
10
  import invalidJsonStringify, { ruleId as invalidJsonStringifyRuleId } from './invalid-json-stringify';
10
- import noFixture, { ruleId as noFixtureRuleId } from './no-fixture';
11
- import noFixtureHeaders, { ruleId as noFixtureHeadersRuleId } from './no-fixture-headers';
11
+ import noFixture, { ruleId as noFixtureRuleId } from './fixture/no-fixture';
12
12
  import noPromiseInstanceMethod, { ruleId as noPromiseInstanceMethodRuleId } from './no-promise-instance-method';
13
13
  import filePathComment from './file-path-comment';
14
14
  import noCardNumbers from './no-card-numbers';
@@ -34,7 +34,7 @@ export default {
34
34
  [invalidJsonStringifyRuleId]: invalidJsonStringify,
35
35
  [noPromiseInstanceMethodRuleId]: noPromiseInstanceMethod,
36
36
  [noFixtureRuleId]: noFixture,
37
- [noFixtureHeadersRuleId]: noFixtureHeaders,
37
+ [fetchHeaderGetterRuleId]: fetchHeaderGetter,
38
38
  },
39
39
  configs: {
40
40
  all: {
@@ -51,7 +51,7 @@ export default {
51
51
  [`@checkdigit/${invalidJsonStringifyRuleId}`]: 'error',
52
52
  [`@checkdigit/${noPromiseInstanceMethodRuleId}`]: 'error',
53
53
  [`@checkdigit/${noFixtureRuleId}`]: 'error',
54
- [`@checkdigit/${noFixtureHeadersRuleId}`]: 'error',
54
+ [`@checkdigit/${fetchHeaderGetterRuleId}`]: 'error',
55
55
  },
56
56
  },
57
57
  recommended: {
@@ -1,98 +0,0 @@
1
- // src/no-fixture-headers.ts
2
- import { getEnclosingScopeNode, getParent } from "./ast/tree.mjs";
3
- import "eslint";
4
- import { strict as assert } from "node:assert";
5
- import getDocumentationUrl from "./get-documentation-url.mjs";
6
- var ruleId = "no-fixture-headers";
7
- var rule = {
8
- meta: {
9
- type: "suggestion",
10
- docs: {
11
- description: "Prefer native fetch API over customized fixture API.",
12
- url: getDocumentationUrl(ruleId)
13
- },
14
- messages: {
15
- preferNativeFetch: "Prefer native fetch API over customized fixture API.",
16
- unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}. Please manually convert the fixture API call to fetch API call.'
17
- },
18
- fixable: "code",
19
- schema: []
20
- },
21
- // eslint-disable-next-line max-lines-per-function
22
- create(context) {
23
- const sourceCode = context.sourceCode;
24
- const scopeManager = sourceCode.scopeManager;
25
- return {
26
- // eslint-disable-next-line max-lines-per-function
27
- 'VariableDeclarator[init.argument.callee.name="fetch"]': (fetchCall) => {
28
- try {
29
- const enclosingScopeNode = getEnclosingScopeNode(fetchCall);
30
- assert.ok(fetchCall.id.type === "Identifier");
31
- const fetchVariableName = fetchCall.id.name;
32
- assert.ok(enclosingScopeNode !== void 0, "enclosing scope node should exist");
33
- const scope = scopeManager.acquire(enclosingScopeNode);
34
- const responseVariable = scope?.variables.find((variable) => {
35
- const identifier = variable.identifiers[0];
36
- return identifier?.type === "Identifier" && identifier.name === fetchVariableName;
37
- });
38
- if (responseVariable === void 0) {
39
- return;
40
- }
41
- const headersReferences = responseVariable.references.map((reference) => getParent(reference.identifier)).filter(
42
- (parent) => parent?.type === "MemberExpression" && parent.property.type === "Identifier" && parent.property.name === "headers"
43
- );
44
- const directHeadersReferences = headersReferences.map(getParent).filter(
45
- (parent) => parent?.type === "MemberExpression" && !(parent.property.type === "Identifier" && parent.property.name === "get")
46
- );
47
- directHeadersReferences.map((reference) => sourceCode.getText(reference));
48
- const reDeclaredHeadersVariableNames = headersReferences.map((reference) => getParent(reference)).filter((parent) => parent?.type === "VariableDeclarator").map((declarator) => declarator.id.name);
49
- const indirectHeadersReferences = reDeclaredHeadersVariableNames.map((variableName) => {
50
- const headersVariable = scope?.variables.find((variable) => {
51
- const identifier = variable.identifiers[0];
52
- return identifier?.type === "Identifier" && identifier.name === variableName;
53
- });
54
- return headersVariable?.references.map((reference) => getParent(reference.identifier)).filter(
55
- (parent) => parent?.type === "MemberExpression" && !(parent.property.type === "Identifier" && parent.property.name === "get")
56
- ) ?? [];
57
- }).flat();
58
- indirectHeadersReferences.map((reference) => sourceCode.getText(reference));
59
- const invalidHeadersReferences = [...directHeadersReferences, ...indirectHeadersReferences].map((reference) => {
60
- sourceCode.getText(reference);
61
- const headerNameNode = reference.property;
62
- const headerName = (
63
- // eslint-disable-next-line no-nested-ternary, @typescript-eslint/restrict-template-expressions
64
- reference.computed ? sourceCode.getText(headerNameNode) : `'${sourceCode.getText(headerNameNode)}'`
65
- );
66
- const replacementText = `${sourceCode.getText(reference.object)}.get(${headerName})`;
67
- return [reference, replacementText];
68
- });
69
- context.report({
70
- node: fetchCall,
71
- messageId: "preferNativeFetch",
72
- *fix(fixer) {
73
- for (const [node, replacementText] of invalidHeadersReferences) {
74
- yield fixer.replaceText(node, replacementText);
75
- }
76
- }
77
- });
78
- } catch (error) {
79
- console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
80
- context.report({
81
- node: fetchCall,
82
- messageId: "unknownError",
83
- data: {
84
- fileName: context.filename,
85
- error: error instanceof Error ? error.toString() : JSON.stringify(error)
86
- }
87
- });
88
- }
89
- }
90
- };
91
- }
92
- };
93
- var no_fixture_headers_default = rule;
94
- export {
95
- no_fixture_headers_default as default,
96
- ruleId
97
- };
98
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vc3JjL25vLWZpeHR1cmUtaGVhZGVycy50cyJdLAogICJtYXBwaW5ncyI6ICI7QUFTQSxTQUFTLHVCQUF1QixpQkFBaUI7QUFDakQsT0FBMEI7QUFDMUIsU0FBUyxVQUFVLGNBQWM7QUFDakMsT0FBTyx5QkFBeUI7QUFFekIsSUFBTSxTQUFTO0FBRXRCLElBQU0sT0FBd0I7QUFBQSxFQUM1QixNQUFNO0FBQUEsSUFDSixNQUFNO0FBQUEsSUFDTixNQUFNO0FBQUEsTUFDSixhQUFhO0FBQUEsTUFDYixLQUFLLG9CQUFvQixNQUFNO0FBQUEsSUFDakM7QUFBQSxJQUNBLFVBQVU7QUFBQSxNQUNSLG1CQUFtQjtBQUFBLE1BQ25CLGNBQ0U7QUFBQSxJQUNKO0FBQUEsSUFDQSxTQUFTO0FBQUEsSUFDVCxRQUFRLENBQUM7QUFBQSxFQUNYO0FBQUE7QUFBQSxFQUVBLE9BQU8sU0FBUztBQUNkLFVBQU0sYUFBYSxRQUFRO0FBQzNCLFVBQU0sZUFBZSxXQUFXO0FBRWhDLFdBQU87QUFBQTtBQUFBLE1BRUwseURBQXlELENBQUMsY0FBa0M7QUFDMUYsWUFBSTtBQUNGLGdCQUFNLHFCQUFxQixzQkFBc0IsU0FBUztBQUMxRCxpQkFBTyxHQUFHLFVBQVUsR0FBRyxTQUFTLFlBQVk7QUFDNUMsZ0JBQU0sb0JBQW9CLFVBQVUsR0FBRztBQUN2QyxpQkFBTyxHQUFHLHVCQUF1QixRQUFXLG1DQUFtQztBQUMvRSxnQkFBTSxRQUFRLGFBQWEsUUFBUSxrQkFBa0I7QUFDckQsZ0JBQU0sbUJBQW1CLE9BQU8sVUFBVSxLQUFLLENBQUMsYUFBYTtBQUMzRCxrQkFBTSxhQUFhLFNBQVMsWUFBWSxDQUFDO0FBQ3pDLG1CQUFPLFlBQVksU0FBUyxnQkFBZ0IsV0FBVyxTQUFTO0FBQUEsVUFDbEUsQ0FBQztBQUNELGNBQUkscUJBQXFCLFFBQVc7QUFDbEM7QUFBQSxVQUNGO0FBRUEsZ0JBQU0sb0JBQW9CLGlCQUFpQixXQUN4QyxJQUFJLENBQUMsY0FBYyxVQUFVLFVBQVUsVUFBVSxDQUFDLEVBQ2xEO0FBQUEsWUFDQyxDQUFDLFdBQ0MsUUFBUSxTQUFTLHNCQUNqQixPQUFPLFNBQVMsU0FBUyxnQkFDekIsT0FBTyxTQUFTLFNBQVM7QUFBQSxVQUM3QjtBQUNGLGdCQUFNLDBCQUEwQixrQkFDN0IsSUFBSSxTQUFTLEVBQ2I7QUFBQSxZQUNDLENBQUMsV0FDQyxRQUFRLFNBQVMsc0JBQ2pCLEVBQUUsT0FBTyxTQUFTLFNBQVMsZ0JBQWdCLE9BQU8sU0FBUyxTQUFTO0FBQUEsVUFDeEU7QUFDRixrQ0FBd0IsSUFBSSxDQUFDLGNBQWMsV0FBVyxRQUFRLFNBQVMsQ0FBQztBQUV4RSxnQkFBTSxpQ0FBaUMsa0JBQ3BDLElBQUksQ0FBQyxjQUFjLFVBQVUsU0FBUyxDQUFDLEVBQ3ZDLE9BQU8sQ0FBQyxXQUF5QyxRQUFRLFNBQVMsb0JBQW9CLEVBQ3RGLElBQUksQ0FBQyxlQUFnQixXQUFXLEdBQWtCLElBQUk7QUFFekQsZ0JBQU0sNEJBQTRCLCtCQUMvQixJQUFJLENBQUMsaUJBQWlCO0FBQ3JCLGtCQUFNLGtCQUFrQixPQUFPLFVBQVUsS0FBSyxDQUFDLGFBQWE7QUFDMUQsb0JBQU0sYUFBYSxTQUFTLFlBQVksQ0FBQztBQUN6QyxxQkFBTyxZQUFZLFNBQVMsZ0JBQWdCLFdBQVcsU0FBUztBQUFBLFlBQ2xFLENBQUM7QUFDRCxtQkFDRSxpQkFBaUIsV0FDZCxJQUFJLENBQUMsY0FBYyxVQUFVLFVBQVUsVUFBVSxDQUFDLEVBQ2xEO0FBQUEsY0FDQyxDQUFDLFdBQ0MsUUFBUSxTQUFTLHNCQUNqQixFQUFFLE9BQU8sU0FBUyxTQUFTLGdCQUFnQixPQUFPLFNBQVMsU0FBUztBQUFBLFlBQ3hFLEtBQUssQ0FBQztBQUFBLFVBRVosQ0FBQyxFQUNBLEtBQUs7QUFDUixvQ0FBMEIsSUFBSSxDQUFDLGNBQWMsV0FBVyxRQUFRLFNBQVMsQ0FBQztBQUUxRSxnQkFBTSwyQkFBMkIsQ0FBQyxHQUFHLHlCQUF5QixHQUFHLHlCQUF5QixFQUFFLElBRTFGLENBQUMsY0FBYztBQUNmLHVCQUFXLFFBQVEsU0FBUztBQUM1QixrQkFBTSxpQkFBaUIsVUFBVTtBQUNqQyxrQkFBTTtBQUFBO0FBQUEsY0FFSixVQUFVLFdBQVcsV0FBVyxRQUFRLGNBQWMsSUFBSSxJQUFJLFdBQVcsUUFBUSxjQUFjLENBQUM7QUFBQTtBQUNsRyxrQkFBTSxrQkFBa0IsR0FBRyxXQUFXLFFBQVEsVUFBVSxNQUFNLENBQUMsUUFBUSxVQUFVO0FBQ2pGLG1CQUFPLENBQUMsV0FBVyxlQUFlO0FBQUEsVUFDcEMsQ0FBQztBQUVELGtCQUFRLE9BQU87QUFBQSxZQUNiLE1BQU07QUFBQSxZQUNOLFdBQVc7QUFBQSxZQUNYLENBQUMsSUFBSSxPQUFPO0FBRVYseUJBQVcsQ0FBQyxNQUFNLGVBQWUsS0FBSywwQkFBMEI7QUFDOUQsc0JBQU0sTUFBTSxZQUFZLE1BQU0sZUFBZTtBQUFBLGNBQy9DO0FBQUEsWUFDRjtBQUFBLFVBQ0YsQ0FBQztBQUFBLFFBQ0gsU0FBUyxPQUFPO0FBRWQsa0JBQVEsTUFBTSxtQkFBbUIsTUFBTSxtQkFBbUIsUUFBUSxRQUFRLE1BQU0sS0FBSztBQUNyRixrQkFBUSxPQUFPO0FBQUEsWUFDYixNQUFNO0FBQUEsWUFDTixXQUFXO0FBQUEsWUFDWCxNQUFNO0FBQUEsY0FDSixVQUFVLFFBQVE7QUFBQSxjQUNsQixPQUFPLGlCQUFpQixRQUFRLE1BQU0sU0FBUyxJQUFJLEtBQUssVUFBVSxLQUFLO0FBQUEsWUFDekU7QUFBQSxVQUNGLENBQUM7QUFBQSxRQUNIO0FBQUEsTUFDRjtBQUFBLElBQ0Y7QUFBQSxFQUNGO0FBQ0Y7QUFFQSxJQUFPLDZCQUFROyIsCiAgIm5hbWVzIjogW10KfQo=
@@ -1,4 +0,0 @@
1
- import { type Rule } from 'eslint';
2
- export declare const ruleId = "no-fixture-headers";
3
- declare const rule: Rule.RuleModule;
4
- export default rule;
@@ -1,134 +0,0 @@
1
- // no-fixture.ts
2
-
3
- /*
4
- * Copyright (c) 2021-2024 Check Digit, LLC
5
- *
6
- * This code is licensed under the MIT license (see LICENSE.txt for details).
7
- */
8
-
9
- import type { Identifier, MemberExpression, VariableDeclarator } from 'estree';
10
- import { getEnclosingScopeNode, getParent } from './ast/tree';
11
- import { type Rule } from 'eslint';
12
- import { strict as assert } from 'node:assert';
13
- import getDocumentationUrl from './get-documentation-url';
14
-
15
- export const ruleId = 'no-fixture-headers';
16
-
17
- const rule: Rule.RuleModule = {
18
- meta: {
19
- type: 'suggestion',
20
- docs: {
21
- description: 'Prefer native fetch API over customized fixture API.',
22
- url: getDocumentationUrl(ruleId),
23
- },
24
- messages: {
25
- preferNativeFetch: 'Prefer native fetch API over customized fixture API.',
26
- unknownError:
27
- 'Unknown error occurred in file "{{fileName}}": {{ error }}. Please manually convert the fixture API call to fetch API call.',
28
- },
29
- fixable: 'code',
30
- schema: [],
31
- },
32
- // eslint-disable-next-line max-lines-per-function
33
- create(context) {
34
- const sourceCode = context.sourceCode;
35
- const scopeManager = sourceCode.scopeManager;
36
-
37
- return {
38
- // eslint-disable-next-line max-lines-per-function
39
- 'VariableDeclarator[init.argument.callee.name="fetch"]': (fetchCall: VariableDeclarator) => {
40
- try {
41
- const enclosingScopeNode = getEnclosingScopeNode(fetchCall);
42
- assert.ok(fetchCall.id.type === 'Identifier');
43
- const fetchVariableName = fetchCall.id.name; /*?*/
44
- assert.ok(enclosingScopeNode !== undefined, 'enclosing scope node should exist');
45
- const scope = scopeManager.acquire(enclosingScopeNode);
46
- const responseVariable = scope?.variables.find((variable) => {
47
- const identifier = variable.identifiers[0];
48
- return identifier?.type === 'Identifier' && identifier.name === fetchVariableName;
49
- });
50
- if (responseVariable === undefined) {
51
- return;
52
- }
53
-
54
- const headersReferences = responseVariable.references
55
- .map((reference) => getParent(reference.identifier))
56
- .filter(
57
- (parent): parent is MemberExpression =>
58
- parent?.type === 'MemberExpression' &&
59
- parent.property.type === 'Identifier' &&
60
- parent.property.name === 'headers',
61
- );
62
- const directHeadersReferences = headersReferences
63
- .map(getParent)
64
- .filter(
65
- (parent): parent is MemberExpression =>
66
- parent?.type === 'MemberExpression' &&
67
- !(parent.property.type === 'Identifier' && parent.property.name === 'get'),
68
- );
69
- directHeadersReferences.map((reference) => sourceCode.getText(reference)); /*?*/
70
-
71
- const reDeclaredHeadersVariableNames = headersReferences
72
- .map((reference) => getParent(reference))
73
- .filter((parent): parent is VariableDeclarator => parent?.type === 'VariableDeclarator')
74
- .map((declarator) => (declarator.id as Identifier).name);
75
-
76
- const indirectHeadersReferences = reDeclaredHeadersVariableNames
77
- .map((variableName) => {
78
- const headersVariable = scope?.variables.find((variable) => {
79
- const identifier = variable.identifiers[0];
80
- return identifier?.type === 'Identifier' && identifier.name === variableName;
81
- });
82
- return (
83
- headersVariable?.references
84
- .map((reference) => getParent(reference.identifier))
85
- .filter(
86
- (parent): parent is MemberExpression =>
87
- parent?.type === 'MemberExpression' &&
88
- !(parent.property.type === 'Identifier' && parent.property.name === 'get'),
89
- ) ?? []
90
- );
91
- })
92
- .flat();
93
- indirectHeadersReferences.map((reference) => sourceCode.getText(reference)); /*?*/
94
-
95
- const invalidHeadersReferences = [...directHeadersReferences, ...indirectHeadersReferences].map<
96
- [MemberExpression, string]
97
- >((reference) => {
98
- sourceCode.getText(reference); /*?*/
99
- const headerNameNode = reference.property; /*?*/
100
- const headerName =
101
- // eslint-disable-next-line no-nested-ternary, @typescript-eslint/restrict-template-expressions
102
- reference.computed ? sourceCode.getText(headerNameNode) : `'${sourceCode.getText(headerNameNode)}'`; /*?*/
103
- const replacementText = `${sourceCode.getText(reference.object)}.get(${headerName})`;
104
- return [reference, replacementText];
105
- });
106
-
107
- context.report({
108
- node: fetchCall,
109
- messageId: 'preferNativeFetch',
110
- *fix(fixer) {
111
- // handle response headers references
112
- for (const [node, replacementText] of invalidHeadersReferences) {
113
- yield fixer.replaceText(node, replacementText);
114
- }
115
- },
116
- });
117
- } catch (error) {
118
- // eslint-disable-next-line no-console
119
- console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
120
- context.report({
121
- node: fetchCall,
122
- messageId: 'unknownError',
123
- data: {
124
- fileName: context.filename,
125
- error: error instanceof Error ? error.toString() : JSON.stringify(error),
126
- },
127
- });
128
- }
129
- },
130
- };
131
- },
132
- };
133
-
134
- export default rule;