@checkdigit/eslint-plugin 6.6.0-PR.75-7154 → 6.6.0-PR.75-c68b
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 +367 -311
- package/dist-cjs/metafile.json +29 -5
- package/dist-mjs/fixture/no-service-wrapper.mjs +9 -9
- package/dist-mjs/fixture/no-status-code.mjs +59 -0
- package/dist-mjs/index.mjs +6 -3
- package/dist-types/fixture/no-status-code.d.ts +4 -0
- package/dist-types/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/fixture/no-service-wrapper.ts +8 -16
- package/src/fixture/no-status-code.ts +71 -0
- package/src/index.ts +3 -0
package/dist-cjs/metafile.json
CHANGED
|
@@ -12407,7 +12407,7 @@
|
|
|
12407
12407
|
"format": "esm"
|
|
12408
12408
|
},
|
|
12409
12409
|
"src/fixture/no-service-wrapper.ts": {
|
|
12410
|
-
"bytes":
|
|
12410
|
+
"bytes": 9185,
|
|
12411
12411
|
"imports": [
|
|
12412
12412
|
{
|
|
12413
12413
|
"path": "node_modules/@typescript-eslint/utils/dist/index.js",
|
|
@@ -12447,6 +12447,22 @@
|
|
|
12447
12447
|
],
|
|
12448
12448
|
"format": "esm"
|
|
12449
12449
|
},
|
|
12450
|
+
"src/fixture/no-status-code.ts": {
|
|
12451
|
+
"bytes": 2355,
|
|
12452
|
+
"imports": [
|
|
12453
|
+
{
|
|
12454
|
+
"path": "node_modules/@typescript-eslint/utils/dist/index.js",
|
|
12455
|
+
"kind": "import-statement",
|
|
12456
|
+
"original": "@typescript-eslint/utils"
|
|
12457
|
+
},
|
|
12458
|
+
{
|
|
12459
|
+
"path": "src/get-documentation-url.ts",
|
|
12460
|
+
"kind": "import-statement",
|
|
12461
|
+
"original": "../get-documentation-url"
|
|
12462
|
+
}
|
|
12463
|
+
],
|
|
12464
|
+
"format": "esm"
|
|
12465
|
+
},
|
|
12450
12466
|
"src/file-path-comment.ts": {
|
|
12451
12467
|
"bytes": 2234,
|
|
12452
12468
|
"imports": [],
|
|
@@ -12493,7 +12509,7 @@
|
|
|
12493
12509
|
"format": "esm"
|
|
12494
12510
|
},
|
|
12495
12511
|
"src/index.ts": {
|
|
12496
|
-
"bytes":
|
|
12512
|
+
"bytes": 3689,
|
|
12497
12513
|
"imports": [
|
|
12498
12514
|
{
|
|
12499
12515
|
"path": "src/fixture/fetch-header-getter.ts",
|
|
@@ -12525,6 +12541,11 @@
|
|
|
12525
12541
|
"kind": "import-statement",
|
|
12526
12542
|
"original": "./fixture/no-service-wrapper"
|
|
12527
12543
|
},
|
|
12544
|
+
{
|
|
12545
|
+
"path": "src/fixture/no-status-code.ts",
|
|
12546
|
+
"kind": "import-statement",
|
|
12547
|
+
"original": "./fixture/no-status-code"
|
|
12548
|
+
},
|
|
12528
12549
|
{
|
|
12529
12550
|
"path": "src/file-path-comment.ts",
|
|
12530
12551
|
"kind": "import-statement",
|
|
@@ -15359,7 +15380,7 @@
|
|
|
15359
15380
|
"bytesInOutput": 2218
|
|
15360
15381
|
},
|
|
15361
15382
|
"src/index.ts": {
|
|
15362
|
-
"bytesInOutput":
|
|
15383
|
+
"bytesInOutput": 2434
|
|
15363
15384
|
},
|
|
15364
15385
|
"src/ast/tree.ts": {
|
|
15365
15386
|
"bytesInOutput": 1648
|
|
@@ -15404,7 +15425,10 @@
|
|
|
15404
15425
|
"bytesInOutput": 1849
|
|
15405
15426
|
},
|
|
15406
15427
|
"src/fixture/no-service-wrapper.ts": {
|
|
15407
|
-
"bytesInOutput":
|
|
15428
|
+
"bytesInOutput": 8649
|
|
15429
|
+
},
|
|
15430
|
+
"src/fixture/no-status-code.ts": {
|
|
15431
|
+
"bytesInOutput": 2036
|
|
15408
15432
|
},
|
|
15409
15433
|
"src/file-path-comment.ts": {
|
|
15410
15434
|
"bytesInOutput": 1961
|
|
@@ -15434,7 +15458,7 @@
|
|
|
15434
15458
|
"bytesInOutput": 3362
|
|
15435
15459
|
}
|
|
15436
15460
|
},
|
|
15437
|
-
"bytes":
|
|
15461
|
+
"bytes": 4296362
|
|
15438
15462
|
}
|
|
15439
15463
|
}
|
|
15440
15464
|
}
|
|
@@ -24,7 +24,6 @@ var rule = createRule({
|
|
|
24
24
|
schema: []
|
|
25
25
|
},
|
|
26
26
|
defaultOptions: [],
|
|
27
|
-
// eslint-disable-next-line max-lines-per-function
|
|
28
27
|
create(context) {
|
|
29
28
|
const sourceCode = context.sourceCode;
|
|
30
29
|
const scopeManager = sourceCode.scopeManager;
|
|
@@ -37,13 +36,14 @@ var rule = createRule({
|
|
|
37
36
|
}
|
|
38
37
|
if (urlArgument?.type === AST_NODE_TYPES.Identifier) {
|
|
39
38
|
const foundVariable = scope.variables.find((variable) => variable.name === urlArgument.name);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
if (foundVariable) {
|
|
40
|
+
const variableDefinition = foundVariable.defs.find((def) => def.type === DefinitionType.Variable);
|
|
41
|
+
assert.ok(variableDefinition, `Variable "${urlArgument.name}" not defined in scope`);
|
|
42
|
+
const variableDefinitionNode = variableDefinition.node;
|
|
43
|
+
assert.ok(variableDefinitionNode.type === AST_NODE_TYPES.VariableDeclarator);
|
|
44
|
+
assert.ok(variableDefinitionNode.init, "Variable definition node has no init property");
|
|
45
|
+
return isUrlArgumentValid(variableDefinitionNode.init, scope);
|
|
46
|
+
}
|
|
47
47
|
}
|
|
48
48
|
return false;
|
|
49
49
|
}
|
|
@@ -178,4 +178,4 @@ export {
|
|
|
178
178
|
no_service_wrapper_default as default,
|
|
179
179
|
ruleId
|
|
180
180
|
};
|
|
181
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
181
|
+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2ZpeHR1cmUvbm8tc2VydmljZS13cmFwcGVyLnRzIl0sCiAgIm1hcHBpbmdzIjogIjtBQVFBLFNBQVMsZ0JBQWdCLG1CQUE2QjtBQUN0RCxTQUFTLHNCQUFrQztBQUMzQyxTQUFTLGtCQUFrQixzQkFBc0IsMENBQTBDO0FBQzNGLFNBQVMsVUFBVSxjQUFjO0FBQ2pDLE9BQU8seUJBQXlCO0FBQ2hDLFNBQVMsNkJBQTZCO0FBQ3RDLFNBQVMsc0JBQXNCO0FBRXhCLElBQU0sU0FBUztBQUV0QixJQUFNLGFBQWEsWUFBWSxZQUFZLENBQUMsU0FBUyxvQkFBb0IsSUFBSSxDQUFDO0FBRTlFLElBQU0sT0FBTyxXQUFXO0FBQUEsRUFDdEIsTUFBTTtBQUFBLEVBQ04sTUFBTTtBQUFBLElBQ0osTUFBTTtBQUFBLElBQ04sTUFBTTtBQUFBLE1BQ0osYUFBYTtBQUFBLElBQ2Y7QUFBQSxJQUNBLFVBQVU7QUFBQSxNQUNSLG1CQUFtQjtBQUFBLE1BQ25CLGdCQUNFO0FBQUEsTUFDRixjQUNFO0FBQUEsSUFDSjtBQUFBLElBQ0EsU0FBUztBQUFBLElBQ1QsUUFBUSxDQUFDO0FBQUEsRUFDWDtBQUFBLEVBQ0EsZ0JBQWdCLENBQUM7QUFBQSxFQUNqQixPQUFPLFNBQVM7QUFDZCxVQUFNLGFBQWEsUUFBUTtBQUMzQixVQUFNLGVBQWUsV0FBVztBQUNoQyxVQUFNLGdCQUFnQixZQUFZLGtCQUFrQixPQUFPO0FBQzNELFVBQU0sY0FBYyxjQUFjLFFBQVEsZUFBZTtBQUV6RCxhQUFTLG1CQUFtQixhQUF3QyxPQUFjO0FBQ2hGLFVBQ0csYUFBYSxTQUFTLGVBQWUsV0FBVyxPQUFPLFlBQVksVUFBVSxZQUM5RSxhQUFhLFNBQVMsZUFBZSxpQkFDckM7QUFDQSxjQUFNLFVBQVUsV0FBVyxRQUFRLFdBQVc7QUFDOUMsZUFBTyxpQkFBaUIsS0FBSyxPQUFPLEtBQUsscUJBQXFCLEtBQUssT0FBTztBQUFBLE1BQzVFO0FBRUEsVUFBSSxhQUFhLFNBQVMs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
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/fixture/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: 'Replacing "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,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2ZpeHR1cmUvbm8tc3RhdHVzLWNvZGUudHMiXSwKICAibWFwcGluZ3MiOiAiO0FBUUEsU0FBUyxtQkFBNkI7QUFDdEMsT0FBTyx5QkFBeUI7QUFFekIsSUFBTSxTQUFTO0FBRXRCLElBQU0sYUFBYSxZQUFZLFlBQVksQ0FBQyxTQUFTLG9CQUFvQixJQUFJLENBQUM7QUFFOUUsSUFBTSxPQUFPLFdBQVc7QUFBQSxFQUN0QixNQUFNO0FBQUEsRUFDTixNQUFNO0FBQUEsSUFDSixNQUFNO0FBQUEsSUFDTixNQUFNO0FBQUEsTUFDSixhQUFhO0FBQUEsSUFDZjtBQUFBLElBQ0EsVUFBVTtBQUFBLE1BQ1IsbUJBQW1CO0FBQUEsTUFDbkIsY0FBYztBQUFBLElBQ2hCO0FBQUEsSUFDQSxTQUFTO0FBQUEsSUFDVCxRQUFRLENBQUM7QUFBQSxFQUNYO0FBQUEsRUFDQSxnQkFBZ0IsQ0FBQztBQUFBLEVBQ2pCLE9BQU8sU0FBUztBQUNkLFVBQU0saUJBQWlCLFlBQVksa0JBQWtCLE9BQU87QUFDNUQsVUFBTSxjQUFjLGVBQWUsUUFBUSxlQUFlO0FBRTFELFdBQU87QUFBQSxNQUNMLGdEQUFnRCxDQUFDLHVCQUFrRDtBQUNqRyxZQUFJO0FBQ0YsZ0JBQU0sZUFBZSxlQUFlLHNCQUFzQixJQUFJLG1CQUFtQixNQUFNO0FBQ3ZGLGdCQUFNLGVBQWUsWUFBWSxrQkFBa0IsWUFBWTtBQUUvRCxnQkFBTSxnQkFDSixhQUFhLGNBQWMsRUFBRSxLQUFLLENBQUMsV0FBVyxPQUFPLFNBQVMsUUFBUSxLQUN0RSxDQUFDLGFBQWEsY0FBYyxFQUFFLEtBQUssQ0FBQyxXQUFXLE9BQU8sU0FBUyxZQUFZO0FBRTdFLGNBQUksZUFBZTtBQUNqQixvQkFBUSxPQUFPO0FBQUEsY0FDYixXQUFXO0FBQUEsY0FDWCxNQUFNLG1CQUFtQjtBQUFBLGNBQ3pCLElBQUksT0FBTztBQUNULHVCQUFPLE1BQU0sWUFBWSxtQkFBbUIsVUFBVSxRQUFRO0FBQUEsY0FDaEU7QUFBQSxZQUNGLENBQUM7QUFBQSxVQUNIO0FBQUEsUUFDRixTQUFTLE9BQU87QUFFZCxrQkFBUSxNQUFNLG1CQUFtQixNQUFNLG1CQUFtQixRQUFRLFFBQVEsTUFBTSxLQUFLO0FBQ3JGLGtCQUFRLE9BQU87QUFBQSxZQUNiLE1BQU07QUFBQSxZQUNOLFdBQVc7QUFBQSxZQUNYLE1BQU07QUFBQSxjQUNKLFVBQVUsUUFBUTtBQUFBLGNBQ2xCLE9BQU8saUJBQWlCLFFBQVEsTUFBTSxTQUFTLElBQUksS0FBSyxVQUFVLEtBQUs7QUFBQSxZQUN6RTtBQUFBLFVBQ0YsQ0FBQztBQUFBLFFBQ0g7QUFBQSxNQUNGO0FBQUEsSUFDRjtBQUFBLEVBQ0Y7QUFDRixDQUFDO0FBRUQsSUFBTyx5QkFBUTsiLAogICJuYW1lcyI6IFtdCn0K
|
package/dist-mjs/index.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import invalidJsonStringify, { ruleId as invalidJsonStringifyRuleId } from "./in
|
|
|
5
5
|
import noFixture, { ruleId as noFixtureRuleId } from "./fixture/no-fixture.mjs";
|
|
6
6
|
import noPromiseInstanceMethod, { ruleId as noPromiseInstanceMethodRuleId } from "./no-promise-instance-method.mjs";
|
|
7
7
|
import noServiceWrapper, { ruleId as noServiceWrapperRuleId } from "./fixture/no-service-wrapper.mjs";
|
|
8
|
+
import noStatusCode, { ruleId as noStatusCodeRuleId } from "./fixture/no-status-code.mjs";
|
|
8
9
|
import filePathComment from "./file-path-comment.mjs";
|
|
9
10
|
import noCardNumbers from "./no-card-numbers.mjs";
|
|
10
11
|
import noTestImport from "./no-test-import.mjs";
|
|
@@ -30,7 +31,8 @@ var src_default = {
|
|
|
30
31
|
[noFixtureRuleId]: noFixture,
|
|
31
32
|
[fetchHeaderGetterRuleId]: fetchHeaderGetter,
|
|
32
33
|
[fetchThenRuleId]: fetchThen,
|
|
33
|
-
[noServiceWrapperRuleId]: noServiceWrapper
|
|
34
|
+
[noServiceWrapperRuleId]: noServiceWrapper,
|
|
35
|
+
[noStatusCodeRuleId]: noStatusCode
|
|
34
36
|
},
|
|
35
37
|
configs: {
|
|
36
38
|
all: {
|
|
@@ -49,7 +51,8 @@ var src_default = {
|
|
|
49
51
|
[`@checkdigit/${noFixtureRuleId}`]: "error",
|
|
50
52
|
[`@checkdigit/${fetchHeaderGetterRuleId}`]: "error",
|
|
51
53
|
[`@checkdigit/${fetchThenRuleId}`]: "error",
|
|
52
|
-
[`@checkdigit/${noServiceWrapperRuleId}`]: "error"
|
|
54
|
+
[`@checkdigit/${noServiceWrapperRuleId}`]: "error",
|
|
55
|
+
[`@checkdigit/${noStatusCodeRuleId}`]: "error"
|
|
53
56
|
}
|
|
54
57
|
},
|
|
55
58
|
recommended: {
|
|
@@ -72,4 +75,4 @@ var src_default = {
|
|
|
72
75
|
export {
|
|
73
76
|
src_default as default
|
|
74
77
|
};
|
|
75
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
78
|
+
//# sourceMappingURL=data:application/json;base64,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
|
package/dist-types/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ declare const _default: {
|
|
|
15
15
|
"fetch-header-getter": import("eslint").Rule.RuleModule;
|
|
16
16
|
"fetch-then": import("eslint").Rule.RuleModule;
|
|
17
17
|
"no-service-wrapper": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferNativeFetch" | "unknownError" | "invalidOptions", never[], import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
18
|
+
"no-status-code": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unknownError" | "replaceStatusCode", never[], import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
18
19
|
};
|
|
19
20
|
configs: {
|
|
20
21
|
all: {
|
|
@@ -34,6 +35,7 @@ declare const _default: {
|
|
|
34
35
|
"@checkdigit/fetch-header-getter": string;
|
|
35
36
|
"@checkdigit/fetch-then": string;
|
|
36
37
|
"@checkdigit/no-service-wrapper": string;
|
|
38
|
+
"@checkdigit/no-status-code": string;
|
|
37
39
|
};
|
|
38
40
|
};
|
|
39
41
|
recommended: {
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@checkdigit/eslint-plugin","version":"6.6.0-PR.75-
|
|
1
|
+
{"name":"@checkdigit/eslint-plugin","version":"6.6.0-PR.75-c68b","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"}}
|
|
@@ -36,21 +36,12 @@ const rule = createRule({
|
|
|
36
36
|
schema: [],
|
|
37
37
|
},
|
|
38
38
|
defaultOptions: [],
|
|
39
|
-
// eslint-disable-next-line max-lines-per-function
|
|
40
39
|
create(context) {
|
|
41
40
|
const sourceCode = context.sourceCode;
|
|
42
41
|
const scopeManager = sourceCode.scopeManager;
|
|
43
42
|
const parserService = ESLintUtils.getParserServices(context);
|
|
44
43
|
const typeChecker = parserService.program.getTypeChecker();
|
|
45
44
|
|
|
46
|
-
// function reportUnknownError(node: TSESTree.Node, error: string) {
|
|
47
|
-
// context.report({
|
|
48
|
-
// node,
|
|
49
|
-
// messageId: 'unknownError',
|
|
50
|
-
// data: { error, fileName: context.filename },
|
|
51
|
-
// });
|
|
52
|
-
// }
|
|
53
|
-
|
|
54
45
|
function isUrlArgumentValid(urlArgument: TSESTree.Node | undefined, scope: Scope) {
|
|
55
46
|
if (
|
|
56
47
|
(urlArgument?.type === AST_NODE_TYPES.Literal && typeof urlArgument.value === 'string') ||
|
|
@@ -62,13 +53,14 @@ const rule = createRule({
|
|
|
62
53
|
|
|
63
54
|
if (urlArgument?.type === AST_NODE_TYPES.Identifier) {
|
|
64
55
|
const foundVariable = scope.variables.find((variable) => variable.name === urlArgument.name);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
56
|
+
if (foundVariable) {
|
|
57
|
+
const variableDefinition = foundVariable.defs.find((def) => def.type === DefinitionType.Variable);
|
|
58
|
+
assert.ok(variableDefinition, `Variable "${urlArgument.name}" not defined in scope`);
|
|
59
|
+
const variableDefinitionNode = variableDefinition.node;
|
|
60
|
+
assert.ok(variableDefinitionNode.type === AST_NODE_TYPES.VariableDeclarator);
|
|
61
|
+
assert.ok(variableDefinitionNode.init, 'Variable definition node has no init property');
|
|
62
|
+
return isUrlArgumentValid(variableDefinitionNode.init, scope);
|
|
63
|
+
}
|
|
72
64
|
}
|
|
73
65
|
|
|
74
66
|
return false;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// fixture/no-status-code.ts
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Copyright (c) 2021-2024 Check Digit, LLC
|
|
5
|
+
*
|
|
6
|
+
* This code is licensed under the MIT license (see LICENSE.txt for details).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
|
|
10
|
+
import getDocumentationUrl from '../get-documentation-url';
|
|
11
|
+
|
|
12
|
+
export const ruleId = 'no-status-code';
|
|
13
|
+
|
|
14
|
+
const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
|
|
15
|
+
|
|
16
|
+
const rule = createRule({
|
|
17
|
+
name: ruleId,
|
|
18
|
+
meta: {
|
|
19
|
+
type: 'suggestion',
|
|
20
|
+
docs: {
|
|
21
|
+
description: 'Access the status code property of the fetch Response using "status" instead of "statusCode".',
|
|
22
|
+
},
|
|
23
|
+
messages: {
|
|
24
|
+
replaceStatusCode: 'Replacing "statusCode" with "status".',
|
|
25
|
+
unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.',
|
|
26
|
+
},
|
|
27
|
+
fixable: 'code',
|
|
28
|
+
schema: [],
|
|
29
|
+
},
|
|
30
|
+
defaultOptions: [],
|
|
31
|
+
create(context) {
|
|
32
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
33
|
+
const typeChecker = parserServices.program.getTypeChecker();
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
'MemberExpression[property.name="statusCode"]': (responseStatusCode: TSESTree.MemberExpression) => {
|
|
37
|
+
try {
|
|
38
|
+
const responseNode = parserServices.esTreeNodeToTSNodeMap.get(responseStatusCode.object);
|
|
39
|
+
const responseType = typeChecker.getTypeAtLocation(responseNode);
|
|
40
|
+
|
|
41
|
+
const shouldReplace =
|
|
42
|
+
responseType.getProperties().some((symbol) => symbol.name === 'status') &&
|
|
43
|
+
!responseType.getProperties().some((symbol) => symbol.name === 'statusCode');
|
|
44
|
+
|
|
45
|
+
if (shouldReplace) {
|
|
46
|
+
context.report({
|
|
47
|
+
messageId: 'replaceStatusCode',
|
|
48
|
+
node: responseStatusCode.property,
|
|
49
|
+
fix(fixer) {
|
|
50
|
+
return fixer.replaceText(responseStatusCode.property, 'status');
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
|
|
57
|
+
context.report({
|
|
58
|
+
node: responseStatusCode,
|
|
59
|
+
messageId: 'unknownError',
|
|
60
|
+
data: {
|
|
61
|
+
fileName: context.filename,
|
|
62
|
+
error: error instanceof Error ? error.toString() : JSON.stringify(error),
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export default rule;
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import invalidJsonStringify, { ruleId as invalidJsonStringifyRuleId } from './in
|
|
|
12
12
|
import noFixture, { ruleId as noFixtureRuleId } from './fixture/no-fixture';
|
|
13
13
|
import noPromiseInstanceMethod, { ruleId as noPromiseInstanceMethodRuleId } from './no-promise-instance-method';
|
|
14
14
|
import noServiceWrapper, { ruleId as noServiceWrapperRuleId } from './fixture/no-service-wrapper';
|
|
15
|
+
import noStatusCode, { ruleId as noStatusCodeRuleId } from './fixture/no-status-code';
|
|
15
16
|
import filePathComment from './file-path-comment';
|
|
16
17
|
import noCardNumbers from './no-card-numbers';
|
|
17
18
|
import noTestImport from './no-test-import';
|
|
@@ -39,6 +40,7 @@ export default {
|
|
|
39
40
|
[fetchHeaderGetterRuleId]: fetchHeaderGetter,
|
|
40
41
|
[fetchThenRuleId]: fetchThen,
|
|
41
42
|
[noServiceWrapperRuleId]: noServiceWrapper,
|
|
43
|
+
[noStatusCodeRuleId]: noStatusCode,
|
|
42
44
|
},
|
|
43
45
|
configs: {
|
|
44
46
|
all: {
|
|
@@ -58,6 +60,7 @@ export default {
|
|
|
58
60
|
[`@checkdigit/${fetchHeaderGetterRuleId}`]: 'error',
|
|
59
61
|
[`@checkdigit/${fetchThenRuleId}`]: 'error',
|
|
60
62
|
[`@checkdigit/${noServiceWrapperRuleId}`]: 'error',
|
|
63
|
+
[`@checkdigit/${noStatusCodeRuleId}`]: 'error',
|
|
61
64
|
},
|
|
62
65
|
},
|
|
63
66
|
recommended: {
|