@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.
- package/dist-cjs/index.cjs +2697 -8934
- package/dist-cjs/metafile.json +648 -685
- package/dist-mjs/ast/tree.mjs +2 -2
- package/dist-mjs/fixture/fetch-header-getter.mjs +64 -0
- package/dist-mjs/{no-fixture.mjs → fixture/no-fixture.mjs} +8 -56
- package/dist-mjs/fixture/response-reference.mjs +56 -0
- package/dist-mjs/index.mjs +5 -5
- package/dist-types/fixture/fetch-header-getter.d.ts +4 -0
- package/dist-types/fixture/response-reference.d.ts +16 -0
- package/dist-types/index.d.ts +2 -2
- package/package.json +1 -1
- package/src/ast/tree.ts +1 -1
- package/src/fixture/fetch-header-getter.ts +90 -0
- package/src/{no-fixture.ts → fixture/no-fixture.ts} +7 -110
- package/src/fixture/response-reference.ts +108 -0
- package/src/index.ts +4 -4
- package/dist-mjs/no-fixture-headers.mjs +0 -98
- package/dist-types/no-fixture-headers.d.ts +0 -4
- package/src/no-fixture-headers.ts +0 -134
- /package/dist-types/{no-fixture.d.ts → fixture/no-fixture.d.ts} +0 -0
|
@@ -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 '
|
|
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 '
|
|
23
|
-
import { getIndentation } from '
|
|
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
|
|
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
|
-
|
|
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
|
-
[
|
|
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/${
|
|
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,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vc3JjL25vLWZpeHR1cmUtaGVhZGVycy50cyJdLAogICJtYXBwaW5ncyI6ICI7QUFTQSxTQUFTLHVCQUF1QixpQkFBaUI7QUFDakQsT0FBMEI7QUFDMUIsU0FBUyxVQUFVLGNBQWM7QUFDakMsT0FBTyx5QkFBeUI7QUFFekIsSUFBTSxTQUFTO0FBRXRCLElBQU0sT0FBd0I7QUFBQSxFQUM1QixNQUFNO0FBQUEsSUFDSixNQUFNO0FBQUEsSUFDTixNQUFNO0FBQUEsTUFDSixhQUFhO0FBQUEsTUFDYixLQUFLLG9CQUFvQixNQUFNO0FBQUEsSUFDakM7QUFBQSxJQUNBLFVBQVU7QUFBQSxNQUNSLG1CQUFtQjtBQUFBLE1BQ25CLGNBQ0U7QUFBQSxJQUNKO0FBQUEsSUFDQSxTQUFTO0FBQUEsSUFDVCxRQUFRLENBQUM7QUFBQSxFQUNYO0FBQUE7QUFBQSxFQUVBLE9BQU8sU0FBUztBQUNkLFVBQU0sYUFBYSxRQUFRO0FBQzNCLFVBQU0sZUFBZSxXQUFXO0FBRWhDLFdBQU87QUFBQTtBQUFBLE1BRUwseURBQXlELENBQUMsY0FBa0M7QUFDMUYsWUFBSTtBQUNGLGdCQUFNLHFCQUFxQixzQkFBc0IsU0FBUztBQUMxRCxpQkFBTyxHQUFHLFVBQVUsR0FBRyxTQUFTLFlBQVk7QUFDNUMsZ0JBQU0sb0JBQW9CLFVBQVUsR0FBRztBQUN2QyxpQkFBTyxHQUFHLHVCQUF1QixRQUFXLG1DQUFtQztBQUMvRSxnQkFBTSxRQUFRLGFBQWEsUUFBUSxrQkFBa0I7QUFDckQsZ0JBQU0sbUJBQW1CLE9BQU8sVUFBVSxLQUFLLENBQUMsYUFBYTtBQUMzRCxrQkFBTSxhQUFhLFNBQVMsWUFBWSxDQUFDO0FBQ3pDLG1CQUFPLFlBQVksU0FBUyxnQkFBZ0IsV0FBVyxTQUFTO0FBQUEsVUFDbEUsQ0FBQztBQUNELGNBQUkscUJBQXFCLFFBQVc7QUFDbEM7QUFBQSxVQUNGO0FBRUEsZ0JBQU0sb0JBQW9CLGlCQUFpQixXQUN4QyxJQUFJLENBQUMsY0FBYyxVQUFVLFVB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
|
|
@@ -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;
|
|
File without changes
|