@checkdigit/eslint-plugin 7.4.1 → 7.5.0-PR.75-0252
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/README.md +1 -1
- package/dist-mjs/agent/add-assert-import.mjs +58 -0
- package/dist-mjs/agent/add-base-path-const.mjs +65 -0
- package/dist-mjs/agent/add-base-path-import.mjs +60 -0
- package/dist-mjs/agent/add-url-domain.mjs +61 -0
- package/dist-mjs/agent/agent-test-wiring.mjs +221 -0
- package/dist-mjs/agent/fetch-response-body-json.mjs +146 -0
- package/dist-mjs/agent/fetch-response-header-getter.mjs +117 -0
- package/dist-mjs/agent/fetch-response-status.mjs +66 -0
- package/dist-mjs/agent/fetch-then.mjs +269 -0
- package/dist-mjs/agent/fetch.mjs +38 -0
- package/dist-mjs/agent/file.mjs +43 -0
- package/dist-mjs/agent/fix-function-call-arguments.mjs +153 -0
- package/dist-mjs/agent/no-fixture.mjs +361 -0
- package/dist-mjs/agent/no-mapped-response.mjs +75 -0
- package/dist-mjs/agent/no-service-wrapper.mjs +185 -0
- package/dist-mjs/agent/no-status-code.mjs +59 -0
- package/dist-mjs/agent/no-unused-function-argument.mjs +79 -0
- package/dist-mjs/agent/no-unused-imports.mjs +81 -0
- package/dist-mjs/agent/no-unused-service-variable.mjs +74 -0
- package/dist-mjs/agent/response-reference.mjs +70 -0
- package/dist-mjs/agent/url.mjs +32 -0
- package/dist-mjs/index.mjs +205 -51
- package/dist-mjs/library/tree.mjs +2 -2
- package/dist-mjs/no-legacy-service-typing.mjs +39 -0
- package/dist-mjs/{agent/no-serve-runtime.mjs → no-serve-runtime.mjs} +3 -3
- package/dist-mjs/require-fixed-services-import.mjs +1 -1
- package/dist-mjs/require-resolve-full-response.mjs +30 -15
- package/dist-types/agent/add-assert-import.d.ts +4 -0
- package/dist-types/agent/add-base-path-const.d.ts +4 -0
- package/dist-types/agent/add-base-path-import.d.ts +4 -0
- package/dist-types/agent/add-url-domain.d.ts +4 -0
- package/dist-types/agent/agent-test-wiring.d.ts +4 -0
- package/dist-types/agent/fetch-response-body-json.d.ts +4 -0
- package/dist-types/agent/fetch-response-header-getter.d.ts +4 -0
- package/dist-types/agent/fetch-response-status.d.ts +4 -0
- package/dist-types/agent/fetch-then.d.ts +4 -0
- package/dist-types/agent/fetch.d.ts +5 -0
- package/dist-types/agent/file.d.ts +7 -0
- package/dist-types/agent/fix-function-call-arguments.d.ts +9 -0
- package/dist-types/agent/no-fixture.d.ts +4 -0
- package/dist-types/agent/no-mapped-response.d.ts +4 -0
- package/dist-types/agent/no-service-wrapper.d.ts +4 -0
- package/dist-types/agent/no-status-code.d.ts +4 -0
- package/dist-types/agent/no-unused-function-argument.d.ts +4 -0
- package/dist-types/agent/no-unused-imports.d.ts +4 -0
- package/dist-types/agent/no-unused-service-variable.d.ts +4 -0
- package/dist-types/agent/response-reference.d.ts +16 -0
- package/dist-types/agent/url.d.ts +4 -0
- package/dist-types/index.d.ts +4 -2
- package/dist-types/no-legacy-service-typing.d.ts +5 -0
- package/package.json +1 -96
- package/src/agent/add-assert-import.ts +74 -0
- package/src/agent/add-base-path-const.ts +81 -0
- package/src/agent/add-base-path-import.ts +69 -0
- package/src/agent/add-url-domain.ts +76 -0
- package/src/agent/agent-test-wiring.ts +273 -0
- package/src/agent/fetch-response-body-json.ts +197 -0
- package/src/agent/fetch-response-header-getter.ts +148 -0
- package/src/agent/fetch-response-status.ts +87 -0
- package/src/agent/fetch-then.ts +357 -0
- package/src/agent/fetch.ts +57 -0
- package/src/agent/file.ts +42 -0
- package/src/agent/fix-function-call-arguments.ts +200 -0
- package/src/agent/no-fixture.ts +521 -0
- package/src/agent/no-mapped-response.ts +84 -0
- package/src/agent/no-service-wrapper.ts +241 -0
- package/src/agent/no-status-code.ts +72 -0
- package/src/agent/no-unused-function-argument.ts +98 -0
- package/src/agent/no-unused-imports.ts +103 -0
- package/src/agent/no-unused-service-variable.ts +93 -0
- package/src/agent/response-reference.ts +129 -0
- package/src/agent/url.ts +32 -0
- package/src/index.ts +206 -50
- package/src/library/tree.ts +1 -0
- package/src/no-legacy-service-typing.ts +49 -0
- package/src/{agent/no-serve-runtime.ts → no-serve-runtime.ts} +2 -2
- package/src/require-fixed-services-import.ts +1 -1
- package/src/require-resolve-full-response.ts +38 -21
- package/dist-mjs/agent/no-full-response.mjs +0 -35
- package/dist-types/agent/no-full-response.d.ts +0 -4
- package/src/agent/no-full-response.ts +0 -41
- /package/dist-types/{agent/no-serve-runtime.d.ts → no-serve-runtime.d.ts} +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// agent/response-reference.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 { strict as assert } from 'node:assert';
|
|
10
|
+
import type { MemberExpression, ObjectPattern, VariableDeclaration } from 'estree';
|
|
11
|
+
import { type Scope } from 'eslint';
|
|
12
|
+
import debug from 'debug';
|
|
13
|
+
|
|
14
|
+
import { getParent } from '../library/tree';
|
|
15
|
+
|
|
16
|
+
const log = debug('eslint-plugin:response-reference');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* analyze response related variables and their references
|
|
20
|
+
* the implementation is for fixture API, but it can be used for fetch API as well since the tree structure is similar
|
|
21
|
+
* @param variableDeclaration - variable declaration node
|
|
22
|
+
*/
|
|
23
|
+
export function analyzeResponseReferences(
|
|
24
|
+
variableDeclaration: VariableDeclaration | undefined,
|
|
25
|
+
scopeManager: Scope.ScopeManager,
|
|
26
|
+
): {
|
|
27
|
+
variable?: Scope.Variable;
|
|
28
|
+
bodyReferences: MemberExpression[];
|
|
29
|
+
headersReferences: MemberExpression[];
|
|
30
|
+
statusReferences: MemberExpression[];
|
|
31
|
+
destructuringBodyVariable?: Scope.Variable | ObjectPattern;
|
|
32
|
+
destructuringHeadersVariable?: Scope.Variable | ObjectPattern;
|
|
33
|
+
destructuringHeadersReferences?: MemberExpression[] | undefined;
|
|
34
|
+
} {
|
|
35
|
+
const results: {
|
|
36
|
+
variable?: Scope.Variable;
|
|
37
|
+
bodyReferences: MemberExpression[];
|
|
38
|
+
headersReferences: MemberExpression[];
|
|
39
|
+
statusReferences: MemberExpression[];
|
|
40
|
+
destructuringBodyVariable?: Scope.Variable | ObjectPattern;
|
|
41
|
+
destructuringHeadersVariable?: Scope.Variable | ObjectPattern;
|
|
42
|
+
destructuringHeadersReferences?: MemberExpression[] | undefined;
|
|
43
|
+
} = {
|
|
44
|
+
bodyReferences: [],
|
|
45
|
+
headersReferences: [],
|
|
46
|
+
statusReferences: [],
|
|
47
|
+
};
|
|
48
|
+
if (!variableDeclaration) {
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const responseVariables = scopeManager.getDeclaredVariables(variableDeclaration);
|
|
53
|
+
for (const responseVariable of responseVariables) {
|
|
54
|
+
const identifier = responseVariable.identifiers[0];
|
|
55
|
+
assert.ok(identifier);
|
|
56
|
+
const identifierParent = getParent(identifier);
|
|
57
|
+
assert.ok(identifierParent);
|
|
58
|
+
if (identifierParent.type === 'VariableDeclarator') {
|
|
59
|
+
// e.g. const response = ...
|
|
60
|
+
results.variable = responseVariable;
|
|
61
|
+
const responseReferences = responseVariable.references.map((responseReference) =>
|
|
62
|
+
getParent(responseReference.identifier),
|
|
63
|
+
);
|
|
64
|
+
// e.g. response.body
|
|
65
|
+
results.bodyReferences = responseReferences.filter(
|
|
66
|
+
(node): node is MemberExpression =>
|
|
67
|
+
node?.type === 'MemberExpression' && node.property.type === 'Identifier' && node.property.name === 'body',
|
|
68
|
+
);
|
|
69
|
+
// e.g. response.headers / response.header / response.get()
|
|
70
|
+
results.headersReferences = responseReferences.filter(
|
|
71
|
+
(node): node is MemberExpression =>
|
|
72
|
+
node?.type === 'MemberExpression' &&
|
|
73
|
+
node.property.type === 'Identifier' &&
|
|
74
|
+
(node.property.name === 'header' || node.property.name === 'headers' || node.property.name === 'get'),
|
|
75
|
+
);
|
|
76
|
+
// e.g. response.status / response.statusCode
|
|
77
|
+
results.statusReferences = responseReferences.filter(
|
|
78
|
+
(node): node is MemberExpression =>
|
|
79
|
+
node?.type === 'MemberExpression' &&
|
|
80
|
+
node.property.type === 'Identifier' &&
|
|
81
|
+
(node.property.name === 'status' || node.property.name === 'statusCode'),
|
|
82
|
+
);
|
|
83
|
+
} else if (
|
|
84
|
+
// body reference through destruction/renaming, e.g. "const { body } = ..."
|
|
85
|
+
identifierParent.type === 'Property' &&
|
|
86
|
+
identifierParent.key.type === 'Identifier' &&
|
|
87
|
+
identifierParent.key.name === 'body'
|
|
88
|
+
) {
|
|
89
|
+
results.destructuringBodyVariable = responseVariable;
|
|
90
|
+
} else if (
|
|
91
|
+
// header reference through destruction/renaming, e.g. "const { headers } = ..."
|
|
92
|
+
identifierParent.type === 'Property' &&
|
|
93
|
+
identifierParent.key.type === 'Identifier' &&
|
|
94
|
+
identifierParent.key.name === 'headers'
|
|
95
|
+
) {
|
|
96
|
+
results.destructuringHeadersVariable = responseVariable;
|
|
97
|
+
results.destructuringHeadersReferences = responseVariable.references
|
|
98
|
+
.map((reference) => reference.identifier)
|
|
99
|
+
.map(getParent)
|
|
100
|
+
.filter(
|
|
101
|
+
(parent): parent is MemberExpression =>
|
|
102
|
+
parent?.type === 'MemberExpression' &&
|
|
103
|
+
parent.property.type === 'Identifier' &&
|
|
104
|
+
parent.property.name !== 'get' &&
|
|
105
|
+
getParent(parent)?.type !== 'CallExpression',
|
|
106
|
+
);
|
|
107
|
+
} else if (identifierParent.type === 'Property') {
|
|
108
|
+
const parent = getParent(identifierParent);
|
|
109
|
+
if (parent?.type === 'ObjectPattern') {
|
|
110
|
+
// body reference through nested destruction, e.g. "const { body: {bodyPropertyName: renamedBodyPropertyName}, headers: {headerPropertyName: renamedHeaderPropertyName} } = ..."
|
|
111
|
+
const parent2 = getParent(parent);
|
|
112
|
+
if (parent2?.type === 'Property' && parent2.key.type === 'Identifier' && parent2.key.name === 'body') {
|
|
113
|
+
results.destructuringBodyVariable = parent;
|
|
114
|
+
}
|
|
115
|
+
if (
|
|
116
|
+
parent2?.type === 'Property' &&
|
|
117
|
+
parent2.key.type === 'Identifier' &&
|
|
118
|
+
(parent2.key.name === 'header' || parent2.key.name === 'headers')
|
|
119
|
+
) {
|
|
120
|
+
results.destructuringHeadersVariable = parent;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
log('+++++++ can not handle identifierParent', identifierParent);
|
|
125
|
+
throw new Error(`Unknown response variable reference: ${responseVariable.name}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return results;
|
|
129
|
+
}
|
package/src/agent/url.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// agent/url.ts
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-inferrable-types
|
|
4
|
+
const PLAIN_URL_REGEXP: RegExp = /^[`']\/\w+(?<serviceNamePart>-\w+)*\/v\d+\/(?<any>.|\r|\n)+[`']$/u;
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-inferrable-types
|
|
6
|
+
const TOKENIZED_URL_REGEXP: RegExp = /^`\$\{(?<serviceNamePart>[A-Z]+_)*BASE_PATH\}\/(?<any>.|\r|\n)+`$/u;
|
|
7
|
+
|
|
8
|
+
export function isServiceApiCallUrl(url: string): boolean {
|
|
9
|
+
return PLAIN_URL_REGEXP.test(url) || TOKENIZED_URL_REGEXP.test(url);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function replaceEndpointUrlPrefixWithBasePath(url: string): string {
|
|
13
|
+
return url.replace(
|
|
14
|
+
/^(?<quotStart>[`'])\/(?<servicename>\w+(?<parts>-\w+)*)(?<path>\/v\d+\/)(?<endpoint>(?<any>.|\r|\n)+)(?<quotEnd>[`'])$/u,
|
|
15
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
16
|
+
'`${BASE_PATH}/$5`',
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function replaceEndpointUrlPrefixWithDomain(url: string): string {
|
|
21
|
+
return url.replace(
|
|
22
|
+
/^(?<quotStart>[`'])\/(?<servicename>\w+(?<parts>-\w+)*)(?<path>\/v\d+\/(?<any>.|\r|\n)+(?<quotEnd>[`'])$)/u,
|
|
23
|
+
'$1https://$2.checkdigit/$2$4',
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function addBasePathUrlDomain(url: string): string {
|
|
28
|
+
return url.replace(
|
|
29
|
+
/^(?<quotStart>[`'])\/(?<servicename>\w+(?<parts>-\w+)*)(?<path>\/v\d+(?<quotEnd>[`'])$)/u,
|
|
30
|
+
'$1https://$2.checkdigit/$2$4',
|
|
31
|
+
);
|
|
32
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -8,10 +8,30 @@
|
|
|
8
8
|
|
|
9
9
|
import type { TSESLint } from '@typescript-eslint/utils';
|
|
10
10
|
|
|
11
|
+
import addUrlDomain, { ruleId as addUrlDomainRuleId } from './agent/add-url-domain';
|
|
12
|
+
import agentTestWiring, { ruleId as agentTestWiringRuleId } from './agent/agent-test-wiring';
|
|
13
|
+
import fetchResponseBodyJson, { ruleId as fetchResponseBodyJsonRuleId } from './agent/fetch-response-body-json';
|
|
14
|
+
import fetchResponseHeaderGetter, {
|
|
15
|
+
ruleId as fetchResponseHeaderGetterRuleId,
|
|
16
|
+
} from './agent/fetch-response-header-getter';
|
|
17
|
+
import fetchResponseStatus, { ruleId as fetchResponseStatusRuleId } from './agent/fetch-response-status';
|
|
18
|
+
import fetchThen, { ruleId as fetchThenRuleId } from './agent/fetch-then';
|
|
19
|
+
import fixFunctionCallArguments, {
|
|
20
|
+
ruleId as fixFunctionCallArgumentsRuleId,
|
|
21
|
+
} from './agent/fix-function-call-arguments';
|
|
11
22
|
import invalidJsonStringify, { ruleId as invalidJsonStringifyRuleId } from './invalid-json-stringify';
|
|
12
23
|
import noDuplicatedImports, { ruleId as noDuplicatedImportsRuleId } from './no-duplicated-imports';
|
|
13
|
-
import
|
|
24
|
+
import noFixture, { ruleId as noFixtureRuleId } from './agent/no-fixture';
|
|
25
|
+
import noLegacyServiceTyping, { ruleId as noLegacyServiceTypingRuleId } from './no-legacy-service-typing';
|
|
26
|
+
import noMappedResponse, { ruleId as noMappedResponseRuleId } from './agent/no-mapped-response';
|
|
14
27
|
import noPromiseInstanceMethod, { ruleId as noPromiseInstanceMethodRuleId } from './no-promise-instance-method';
|
|
28
|
+
import noServiceWrapper, { ruleId as noServiceWrapperRuleId } from './agent/no-service-wrapper';
|
|
29
|
+
import noStatusCode, { ruleId as noStatusCodeRuleId } from './agent/no-status-code';
|
|
30
|
+
import noUnusedFunctionArguments, {
|
|
31
|
+
ruleId as noUnusedFunctionArgumentsRuleId,
|
|
32
|
+
} from './agent/no-unused-function-argument';
|
|
33
|
+
import noUnusedImports, { ruleId as noUnusedImportsRuleId } from './agent/no-unused-imports';
|
|
34
|
+
import noUnusedServiceVariables, { ruleId as noUnusedServiceVariablesRuleId } from './agent/no-unused-service-variable';
|
|
15
35
|
import requireFixedServicesImport, {
|
|
16
36
|
ruleId as requireFixedServicesImportRuleId,
|
|
17
37
|
} from './require-fixed-services-import';
|
|
@@ -21,7 +41,10 @@ import requireResolveFullResponse, {
|
|
|
21
41
|
import requireTypeOutOfTypeOnlyImports, {
|
|
22
42
|
ruleId as requireTypeOutOfTypeOnlyImportsRuleId,
|
|
23
43
|
} from './require-type-out-of-type-only-imports';
|
|
24
|
-
import noServeRuntime, { ruleId as noServeRuntimeRuleId } from './
|
|
44
|
+
import noServeRuntime, { ruleId as noServeRuntimeRuleId } from './no-serve-runtime';
|
|
45
|
+
import addBasePathConst, { ruleId as addBasePathConstRuleId } from './agent/add-base-path-const';
|
|
46
|
+
import addBasePathImport, { ruleId as addBasePathImportRuleId } from './agent/add-base-path-import';
|
|
47
|
+
import addAssertImport, { ruleId as addAssertImportRuleId } from './agent/add-assert-import';
|
|
25
48
|
import filePathComment from './file-path-comment';
|
|
26
49
|
import noCardNumbers from './no-card-numbers';
|
|
27
50
|
import noSideEffects from './no-side-effects';
|
|
@@ -48,69 +71,202 @@ const rules: Record<string, TSESLint.LooseRuleDefinition> = {
|
|
|
48
71
|
'object-literal-response': objectLiteralResponse,
|
|
49
72
|
[invalidJsonStringifyRuleId]: invalidJsonStringify,
|
|
50
73
|
[noPromiseInstanceMethodRuleId]: noPromiseInstanceMethod,
|
|
51
|
-
[
|
|
74
|
+
[noFixtureRuleId]: noFixture,
|
|
75
|
+
[fetchThenRuleId]: fetchThen,
|
|
76
|
+
[noServiceWrapperRuleId]: noServiceWrapper,
|
|
77
|
+
[noStatusCodeRuleId]: noStatusCode,
|
|
78
|
+
[fetchResponseBodyJsonRuleId]: fetchResponseBodyJson,
|
|
79
|
+
[fetchResponseHeaderGetterRuleId]: fetchResponseHeaderGetter,
|
|
80
|
+
[fetchResponseStatusRuleId]: fetchResponseStatus,
|
|
81
|
+
[addUrlDomainRuleId]: addUrlDomain,
|
|
82
|
+
[noLegacyServiceTypingRuleId]: noLegacyServiceTyping,
|
|
83
|
+
[noMappedResponseRuleId]: noMappedResponse,
|
|
52
84
|
[requireResolveFullResponseRuleId]: requireResolveFullResponse,
|
|
53
|
-
[requireTypeOutOfTypeOnlyImportsRuleId]: requireTypeOutOfTypeOnlyImports,
|
|
54
85
|
[noDuplicatedImportsRuleId]: noDuplicatedImports,
|
|
55
|
-
[requireFixedServicesImportRuleId]: requireFixedServicesImport,
|
|
56
86
|
[noServeRuntimeRuleId]: noServeRuntime,
|
|
87
|
+
[addBasePathConstRuleId]: addBasePathConst,
|
|
88
|
+
[addBasePathImportRuleId]: addBasePathImport,
|
|
89
|
+
[addAssertImportRuleId]: addAssertImport,
|
|
90
|
+
[requireFixedServicesImportRuleId]: requireFixedServicesImport,
|
|
91
|
+
[requireTypeOutOfTypeOnlyImportsRuleId]: requireTypeOutOfTypeOnlyImports,
|
|
92
|
+
[noUnusedFunctionArgumentsRuleId]: noUnusedFunctionArguments,
|
|
93
|
+
[noUnusedServiceVariablesRuleId]: noUnusedServiceVariables,
|
|
94
|
+
[noUnusedImportsRuleId]: noUnusedImports,
|
|
95
|
+
[fixFunctionCallArgumentsRuleId]: fixFunctionCallArguments,
|
|
96
|
+
[agentTestWiringRuleId]: agentTestWiring,
|
|
57
97
|
};
|
|
58
98
|
|
|
59
99
|
const plugin: TSESLint.FlatConfig.Plugin = {
|
|
60
100
|
rules,
|
|
61
101
|
};
|
|
62
102
|
|
|
63
|
-
const configs: Record<string, TSESLint.FlatConfig.Config> = {
|
|
64
|
-
all:
|
|
65
|
-
|
|
66
|
-
'
|
|
103
|
+
const configs: Record<string, TSESLint.FlatConfig.Config | TSESLint.FlatConfig.Config[]> = {
|
|
104
|
+
all: [
|
|
105
|
+
{
|
|
106
|
+
files: ['**/*.ts'],
|
|
107
|
+
plugins: {
|
|
108
|
+
'@checkdigit': plugin,
|
|
109
|
+
},
|
|
110
|
+
rules: {
|
|
111
|
+
'@checkdigit/no-card-numbers': 'error',
|
|
112
|
+
'@checkdigit/file-path-comment': 'error',
|
|
113
|
+
'@checkdigit/no-random-v4-uuid': 'error',
|
|
114
|
+
'@checkdigit/no-uuid': 'error',
|
|
115
|
+
'@checkdigit/require-strict-assert': 'error',
|
|
116
|
+
'@checkdigit/no-wallaby-comment': 'error',
|
|
117
|
+
'@checkdigit/no-side-effects': ['error', { excludedIdentifiers: ['assert', 'debug', 'log', 'promisify'] }],
|
|
118
|
+
'@checkdigit/regular-expression-comment': 'error',
|
|
119
|
+
'@checkdigit/require-assert-predicate-rejects-throws': 'error',
|
|
120
|
+
'@checkdigit/object-literal-response': 'error',
|
|
121
|
+
'@checkdigit/no-test-import': 'error',
|
|
122
|
+
[`@checkdigit/${invalidJsonStringifyRuleId}`]: 'error',
|
|
123
|
+
[`@checkdigit/${noPromiseInstanceMethodRuleId}`]: 'error',
|
|
124
|
+
[`@checkdigit/${noLegacyServiceTypingRuleId}`]: 'error',
|
|
125
|
+
[`@checkdigit/${requireResolveFullResponseRuleId}`]: 'error',
|
|
126
|
+
[`@checkdigit/${noDuplicatedImportsRuleId}`]: 'error',
|
|
127
|
+
[`@checkdigit/${requireFixedServicesImportRuleId}`]: 'error',
|
|
128
|
+
[`@checkdigit/${requireTypeOutOfTypeOnlyImportsRuleId}`]: 'error',
|
|
129
|
+
[`@checkdigit/${noServeRuntimeRuleId}`]: 'error',
|
|
130
|
+
// --- agent rules BEGIN ---
|
|
131
|
+
[`@checkdigit/${noMappedResponseRuleId}`]: 'off',
|
|
132
|
+
[`@checkdigit/${addUrlDomainRuleId}`]: 'off',
|
|
133
|
+
[`@checkdigit/${noFixtureRuleId}`]: 'off',
|
|
134
|
+
[`@checkdigit/${noServiceWrapperRuleId}`]: 'off',
|
|
135
|
+
[`@checkdigit/${noStatusCodeRuleId}`]: 'off',
|
|
136
|
+
[`@checkdigit/${fetchResponseBodyJsonRuleId}`]: 'off',
|
|
137
|
+
[`@checkdigit/${fetchResponseHeaderGetterRuleId}`]: 'off',
|
|
138
|
+
[`@checkdigit/${fetchResponseStatusRuleId}`]: 'off',
|
|
139
|
+
[`@checkdigit/${fetchThenRuleId}`]: 'off',
|
|
140
|
+
[`@checkdigit/${noUnusedFunctionArgumentsRuleId}`]: 'off',
|
|
141
|
+
[`@checkdigit/${noUnusedServiceVariablesRuleId}`]: 'off',
|
|
142
|
+
[`@checkdigit/${noUnusedImportsRuleId}`]: 'off',
|
|
143
|
+
[`@checkdigit/${fixFunctionCallArgumentsRuleId}`]: 'off',
|
|
144
|
+
[`@checkdigit/${agentTestWiringRuleId}`]: 'off',
|
|
145
|
+
[`@checkdigit/${addBasePathConstRuleId}`]: 'off',
|
|
146
|
+
[`@checkdigit/${addBasePathImportRuleId}`]: 'off',
|
|
147
|
+
[`@checkdigit/${addAssertImportRuleId}`]: 'off',
|
|
148
|
+
// --- agent rules END ---
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
recommended: [
|
|
153
|
+
{
|
|
154
|
+
files: ['**/*.ts'],
|
|
155
|
+
plugins: {
|
|
156
|
+
'@checkdigit': plugin,
|
|
157
|
+
},
|
|
158
|
+
rules: {
|
|
159
|
+
'@checkdigit/no-card-numbers': 'error',
|
|
160
|
+
'@checkdigit/file-path-comment': 'off',
|
|
161
|
+
'@checkdigit/no-random-v4-uuid': 'error',
|
|
162
|
+
'@checkdigit/no-uuid': 'error',
|
|
163
|
+
'@checkdigit/require-strict-assert': 'error',
|
|
164
|
+
'@checkdigit/no-wallaby-comment': 'off',
|
|
165
|
+
'@checkdigit/no-side-effects': 'error',
|
|
166
|
+
'@checkdigit/regular-expression-comment': 'error',
|
|
167
|
+
'@checkdigit/require-assert-predicate-rejects-throws': 'error',
|
|
168
|
+
'@checkdigit/object-literal-response': 'error',
|
|
169
|
+
'@checkdigit/no-test-import': 'error',
|
|
170
|
+
[`@checkdigit/${invalidJsonStringifyRuleId}`]: 'error',
|
|
171
|
+
[`@checkdigit/${noPromiseInstanceMethodRuleId}`]: 'off',
|
|
172
|
+
[`@checkdigit/${noLegacyServiceTypingRuleId}`]: 'off',
|
|
173
|
+
[`@checkdigit/${requireResolveFullResponseRuleId}`]: 'off',
|
|
174
|
+
[`@checkdigit/${noDuplicatedImportsRuleId}`]: 'error',
|
|
175
|
+
[`@checkdigit/${requireFixedServicesImportRuleId}`]: 'off',
|
|
176
|
+
[`@checkdigit/${requireTypeOutOfTypeOnlyImportsRuleId}`]: 'error',
|
|
177
|
+
[`@checkdigit/${noServeRuntimeRuleId}`]: 'off',
|
|
178
|
+
// --- agent rules BEGIN ---
|
|
179
|
+
[`@checkdigit/${noMappedResponseRuleId}`]: 'off',
|
|
180
|
+
[`@checkdigit/${addUrlDomainRuleId}`]: 'off',
|
|
181
|
+
[`@checkdigit/${noFixtureRuleId}`]: 'off',
|
|
182
|
+
[`@checkdigit/${noServiceWrapperRuleId}`]: 'off',
|
|
183
|
+
[`@checkdigit/${noStatusCodeRuleId}`]: 'off',
|
|
184
|
+
[`@checkdigit/${fetchResponseBodyJsonRuleId}`]: 'off',
|
|
185
|
+
[`@checkdigit/${fetchResponseHeaderGetterRuleId}`]: 'off',
|
|
186
|
+
[`@checkdigit/${fetchResponseStatusRuleId}`]: 'off',
|
|
187
|
+
[`@checkdigit/${fetchThenRuleId}`]: 'off',
|
|
188
|
+
[`@checkdigit/${noUnusedFunctionArgumentsRuleId}`]: 'off',
|
|
189
|
+
[`@checkdigit/${noUnusedServiceVariablesRuleId}`]: 'off',
|
|
190
|
+
[`@checkdigit/${noUnusedImportsRuleId}`]: 'off',
|
|
191
|
+
[`@checkdigit/${fixFunctionCallArgumentsRuleId}`]: 'off',
|
|
192
|
+
[`@checkdigit/${agentTestWiringRuleId}`]: 'off',
|
|
193
|
+
[`@checkdigit/${addBasePathConstRuleId}`]: 'off',
|
|
194
|
+
[`@checkdigit/${addBasePathImportRuleId}`]: 'off',
|
|
195
|
+
[`@checkdigit/${addAssertImportRuleId}`]: 'off',
|
|
196
|
+
// --- agent rules END ---
|
|
197
|
+
},
|
|
67
198
|
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
'
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
199
|
+
],
|
|
200
|
+
'agent-phase-1-test': [
|
|
201
|
+
{
|
|
202
|
+
files: ['**/*.spec.ts', '**/*.test.ts', 'src/api/v*/index.ts'],
|
|
203
|
+
// eslint-disable-next-line sonarjs/no-duplicate-string
|
|
204
|
+
ignores: ['src/plugin/**'],
|
|
205
|
+
plugins: {
|
|
206
|
+
'@checkdigit': plugin,
|
|
207
|
+
},
|
|
208
|
+
rules: {
|
|
209
|
+
[`@checkdigit/${noMappedResponseRuleId}`]: 'error',
|
|
210
|
+
[`@checkdigit/${addUrlDomainRuleId}`]: 'error',
|
|
211
|
+
[`@checkdigit/${noServiceWrapperRuleId}`]: 'error',
|
|
212
|
+
[`@checkdigit/${noStatusCodeRuleId}`]: 'error',
|
|
213
|
+
[`@checkdigit/${fetchResponseBodyJsonRuleId}`]: 'error',
|
|
214
|
+
[`@checkdigit/${fetchResponseHeaderGetterRuleId}`]: 'error',
|
|
215
|
+
[`@checkdigit/${fetchResponseStatusRuleId}`]: 'error',
|
|
216
|
+
[`@checkdigit/${fetchThenRuleId}`]: 'error',
|
|
217
|
+
[`@checkdigit/${noUnusedFunctionArgumentsRuleId}`]: 'error',
|
|
218
|
+
[`@checkdigit/${noUnusedServiceVariablesRuleId}`]: 'error',
|
|
219
|
+
[`@checkdigit/${noUnusedImportsRuleId}`]: 'error',
|
|
220
|
+
[`@checkdigit/${fixFunctionCallArgumentsRuleId}`]: 'error',
|
|
221
|
+
[`@checkdigit/${addBasePathConstRuleId}`]: 'error',
|
|
222
|
+
[`@checkdigit/${addBasePathImportRuleId}`]: 'error',
|
|
223
|
+
[`@checkdigit/${addAssertImportRuleId}`]: 'error',
|
|
224
|
+
},
|
|
88
225
|
},
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
226
|
+
{
|
|
227
|
+
files: ['**/*.spec.ts'],
|
|
228
|
+
ignores: ['src/plugin/**'],
|
|
229
|
+
plugins: {
|
|
230
|
+
'@checkdigit': plugin,
|
|
231
|
+
},
|
|
232
|
+
rules: {
|
|
233
|
+
[`@checkdigit/${agentTestWiringRuleId}`]: 'error',
|
|
234
|
+
[`@checkdigit/${noFixtureRuleId}`]: 'error',
|
|
235
|
+
},
|
|
93
236
|
},
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
237
|
+
],
|
|
238
|
+
'agent-phase-2-production': [
|
|
239
|
+
{
|
|
240
|
+
files: ['**/*.ts'],
|
|
241
|
+
ignores: ['src/plugin/**'],
|
|
242
|
+
plugins: {
|
|
243
|
+
'@checkdigit': plugin,
|
|
244
|
+
},
|
|
245
|
+
rules: {
|
|
246
|
+
[`@checkdigit/${noMappedResponseRuleId}`]: 'error',
|
|
247
|
+
[`@checkdigit/${addUrlDomainRuleId}`]: 'error',
|
|
248
|
+
[`@checkdigit/${noServiceWrapperRuleId}`]: 'error',
|
|
249
|
+
[`@checkdigit/${noStatusCodeRuleId}`]: 'error',
|
|
250
|
+
[`@checkdigit/${fetchResponseBodyJsonRuleId}`]: 'error',
|
|
251
|
+
[`@checkdigit/${fetchResponseHeaderGetterRuleId}`]: 'error',
|
|
252
|
+
[`@checkdigit/${fetchResponseStatusRuleId}`]: 'error',
|
|
253
|
+
[`@checkdigit/${fetchThenRuleId}`]: 'error',
|
|
254
|
+
[`@checkdigit/${noUnusedFunctionArgumentsRuleId}`]: 'error',
|
|
255
|
+
[`@checkdigit/${noUnusedServiceVariablesRuleId}`]: 'error',
|
|
256
|
+
[`@checkdigit/${noUnusedImportsRuleId}`]: 'error',
|
|
257
|
+
[`@checkdigit/${fixFunctionCallArgumentsRuleId}`]: 'error',
|
|
258
|
+
[`@checkdigit/${addBasePathConstRuleId}`]: 'error',
|
|
259
|
+
[`@checkdigit/${addBasePathImportRuleId}`]: 'error',
|
|
260
|
+
[`@checkdigit/${addAssertImportRuleId}`]: 'error',
|
|
261
|
+
},
|
|
108
262
|
},
|
|
109
|
-
|
|
263
|
+
],
|
|
110
264
|
};
|
|
111
265
|
|
|
112
|
-
const
|
|
266
|
+
const defaultToExport: Exclude<TSESLint.FlatConfig.Plugin, 'config'> & {
|
|
267
|
+
configs: Record<string, TSESLint.FlatConfig.Config | TSESLint.FlatConfig.Config[]>;
|
|
268
|
+
} = {
|
|
113
269
|
...plugin,
|
|
114
270
|
configs,
|
|
115
271
|
};
|
|
116
|
-
export default
|
|
272
|
+
export default defaultToExport;
|
package/src/library/tree.ts
CHANGED
|
@@ -64,6 +64,7 @@ export function isUsedInArrayOrAsArgument(node: Node): boolean {
|
|
|
64
64
|
|
|
65
65
|
if (
|
|
66
66
|
parent.type === 'ArrayExpression' ||
|
|
67
|
+
parent.type === 'ArrowFunctionExpression' ||
|
|
67
68
|
(parent.type === 'CallExpression' && parent.arguments.includes(node as Expression))
|
|
68
69
|
) {
|
|
69
70
|
return true;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// no-legacy-service-typing.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 { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils';
|
|
10
|
+
import getDocumentationUrl from './get-documentation-url';
|
|
11
|
+
|
|
12
|
+
export const ruleId = 'no-legacy-service-typing';
|
|
13
|
+
|
|
14
|
+
const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
|
|
15
|
+
|
|
16
|
+
const DISALLOWED_SERVICE_TYPINGS: string[] | undefined = ['FullResponse', 'Endpoint'];
|
|
17
|
+
|
|
18
|
+
const rule: ESLintUtils.RuleModule<'noLegacyServiceTyping', [typeof DISALLOWED_SERVICE_TYPINGS]> = createRule({
|
|
19
|
+
name: ruleId,
|
|
20
|
+
meta: {
|
|
21
|
+
type: 'problem',
|
|
22
|
+
docs: {
|
|
23
|
+
description: 'Legacy service typings should not be used.',
|
|
24
|
+
},
|
|
25
|
+
messages: {
|
|
26
|
+
noLegacyServiceTyping: 'Please remove the usage of legacy service typings.',
|
|
27
|
+
},
|
|
28
|
+
schema: [{ type: 'array', items: { type: 'string' } }],
|
|
29
|
+
},
|
|
30
|
+
defaultOptions: [DISALLOWED_SERVICE_TYPINGS],
|
|
31
|
+
create(context) {
|
|
32
|
+
return {
|
|
33
|
+
TSTypeReference: (typeReference: TSESTree.TSTypeReference) => {
|
|
34
|
+
if (
|
|
35
|
+
typeReference.typeName.type === AST_NODE_TYPES.Identifier &&
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
37
|
+
(context.options[0] ?? DISALLOWED_SERVICE_TYPINGS).includes(typeReference.typeName.name)
|
|
38
|
+
) {
|
|
39
|
+
context.report({
|
|
40
|
+
messageId: 'noLegacyServiceTyping',
|
|
41
|
+
node: typeReference,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export default rule;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// no-serve-runtime.ts
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
* Copyright (c) 2021-2024 Check Digit, LLC
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
|
|
10
|
-
import getDocumentationUrl from '
|
|
10
|
+
import getDocumentationUrl from './get-documentation-url';
|
|
11
11
|
|
|
12
12
|
export const ruleId = 'no-serve-runtime';
|
|
13
13
|
|
|
@@ -13,7 +13,7 @@ import getDocumentationUrl from './get-documentation-url';
|
|
|
13
13
|
export const ruleId = 'require-fixed-services-import';
|
|
14
14
|
|
|
15
15
|
const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
|
|
16
|
-
const SERVICE_TYPINGS_IMPORT_PATH_PREFIX = /(?<path>\.\.\/)+services
|
|
16
|
+
const SERVICE_TYPINGS_IMPORT_PATH_PREFIX = /(?<path>\.\.\/)+services\/.*/u;
|
|
17
17
|
|
|
18
18
|
const rule: ESLintUtils.RuleModule<'updateServicesImportFrom'> = createRule({
|
|
19
19
|
name: ruleId,
|
|
@@ -54,10 +54,12 @@ const rule: ESLintUtils.RuleModule<'invalidOptions' | 'unknownError'> = createRu
|
|
|
54
54
|
const foundVariable = scope.variables.find((variable) => variable.name === urlArgument.name);
|
|
55
55
|
if (foundVariable) {
|
|
56
56
|
const variableDefinition = foundVariable.defs.find((def) => def.type === DefinitionType.Variable);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
if (variableDefinition) {
|
|
58
|
+
const variableDefinitionNode = variableDefinition.node;
|
|
59
|
+
assert.ok(variableDefinitionNode.init, 'Variable definition node has no init property');
|
|
60
|
+
return isUrlArgumentValid(variableDefinitionNode.init, scope);
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
|
|
@@ -156,7 +158,7 @@ const rule: ESLintUtils.RuleModule<'invalidOptions' | 'unknownError'> = createRu
|
|
|
156
158
|
const optionsArgument = ['get', 'head', 'del'].includes(method)
|
|
157
159
|
? serviceCall.arguments[1]
|
|
158
160
|
: serviceCall.arguments[2];
|
|
159
|
-
if (optionsArgument === undefined
|
|
161
|
+
if (optionsArgument === undefined) {
|
|
160
162
|
context.report({
|
|
161
163
|
node: serviceCall,
|
|
162
164
|
messageId: 'invalidOptions',
|
|
@@ -164,23 +166,38 @@ const rule: ESLintUtils.RuleModule<'invalidOptions' | 'unknownError'> = createRu
|
|
|
164
166
|
return;
|
|
165
167
|
}
|
|
166
168
|
|
|
167
|
-
|
|
168
|
-
(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
resolveWithFullResponseProperty
|
|
176
|
-
resolveWithFullResponseProperty
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
169
|
+
if (optionsArgument.type === AST_NODE_TYPES.Identifier) {
|
|
170
|
+
const optionsTypeString = getType(optionsArgument);
|
|
171
|
+
if (optionsTypeString === 'FullResponseOptions') {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const variable = parserService.esTreeNodeToTSNodeMap.get(optionsArgument);
|
|
176
|
+
const optionType = typeChecker.getTypeAtLocation(variable);
|
|
177
|
+
const resolveWithFullResponseProperty = optionType.getProperty('resolveWithFullResponse');
|
|
178
|
+
if (resolveWithFullResponseProperty?.declarations?.[0]?.getText() === 'resolveWithFullResponse: true') {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
} else if (optionsArgument.type === AST_NODE_TYPES.ObjectExpression) {
|
|
182
|
+
const resolveWithFullResponseProperty = optionsArgument.properties.find(
|
|
183
|
+
(property) =>
|
|
184
|
+
property.type === AST_NODE_TYPES.Property &&
|
|
185
|
+
property.key.type === AST_NODE_TYPES.Identifier &&
|
|
186
|
+
property.key.name === 'resolveWithFullResponse',
|
|
187
|
+
);
|
|
188
|
+
if (
|
|
189
|
+
resolveWithFullResponseProperty?.type === AST_NODE_TYPES.Property &&
|
|
190
|
+
resolveWithFullResponseProperty.value.type === AST_NODE_TYPES.Literal &&
|
|
191
|
+
resolveWithFullResponseProperty.value.value === true
|
|
192
|
+
) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
183
195
|
}
|
|
196
|
+
|
|
197
|
+
context.report({
|
|
198
|
+
node: optionsArgument,
|
|
199
|
+
messageId: 'invalidOptions',
|
|
200
|
+
});
|
|
184
201
|
} catch (error) {
|
|
185
202
|
// eslint-disable-next-line no-console
|
|
186
203
|
console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
|