@checkdigit/eslint-plugin 7.17.1 → 8.0.0-PR.141-56ee
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-mjs/aws/is-aws-sdk-v3-used.mjs +7 -3
- package/dist-mjs/aws/require-aws-bare-bones.mjs +23 -5
- package/dist-mjs/aws/require-aws-config.mjs +5 -2
- package/dist-mjs/aws/require-consistent-read.mjs +12 -4
- package/dist-mjs/file-path-comment.mjs +15 -6
- package/dist-mjs/index.mjs +25 -9
- package/dist-mjs/invalid-json-stringify.mjs +8 -3
- package/dist-mjs/library/format.mjs +1 -1
- package/dist-mjs/library/tree.mjs +7 -2
- package/dist-mjs/library/ts-tree.mjs +7 -2
- package/dist-mjs/no-card-numbers.mjs +1 -1
- package/dist-mjs/no-duplicated-imports.mjs +25 -7
- package/dist-mjs/no-legacy-service-typing.mjs +18 -7
- package/dist-mjs/no-promise-instance-method.mjs +1 -1
- package/dist-mjs/no-random-v4-uuid.mjs +5 -2
- package/dist-mjs/no-side-effects.mjs +11 -7
- package/dist-mjs/no-status-code-assert.mjs +5 -2
- package/dist-mjs/no-test-import.mjs +5 -2
- package/dist-mjs/no-util.mjs +1 -1
- package/dist-mjs/no-uuid.mjs +1 -1
- package/dist-mjs/no-wallaby-comment.mjs +25 -7
- package/dist-mjs/object-literal-response.mjs +1 -1
- package/dist-mjs/regular-expression-comment.mjs +4 -2
- package/dist-mjs/require-assert-message.mjs +1 -1
- package/dist-mjs/require-assert-predicate-rejects-throws.mjs +1 -1
- package/dist-mjs/require-fixed-services-import.mjs +21 -6
- package/dist-mjs/require-resolve-full-response.mjs +32 -11
- package/dist-mjs/require-service-call-response-declaration.mjs +12 -4
- package/dist-mjs/require-strict-assert.mjs +5 -2
- package/dist-mjs/require-ts-extension-imports-exports.mjs +1 -1
- package/dist-mjs/require-type-out-of-type-only-imports.mjs +6 -2
- package/dist-types/no-legacy-service-typing.d.ts +3 -1
- package/package.json +1 -96
- package/src/aws/is-aws-sdk-v3-used.ts +6 -2
- package/src/aws/require-aws-bare-bones.ts +65 -42
- package/src/aws/require-aws-config.ts +85 -63
- package/src/aws/require-consistent-read.ts +97 -60
- package/src/file-path-comment.ts +12 -3
- package/src/index.ts +28 -10
- package/src/invalid-json-stringify.ts +9 -3
- package/src/library/format.ts +4 -1
- package/src/library/tree.ts +8 -2
- package/src/library/ts-tree.ts +24 -7
- package/src/no-card-numbers.ts +6 -1
- package/src/no-duplicated-imports.ts +48 -18
- package/src/no-legacy-service-typing.ts +25 -8
- package/src/no-promise-instance-method.ts +8 -3
- package/src/no-random-v4-uuid.ts +36 -11
- package/src/no-side-effects.ts +78 -29
- package/src/no-status-code-assert.ts +21 -7
- package/src/no-test-import.ts +8 -2
- package/src/no-util.ts +3 -1
- package/src/no-uuid.ts +8 -2
- package/src/no-wallaby-comment.ts +40 -9
- package/src/object-literal-response.ts +25 -9
- package/src/regular-expression-comment.ts +9 -3
- package/src/require-assert-message.ts +13 -5
- package/src/require-assert-predicate-rejects-throws.ts +6 -3
- package/src/require-fixed-services-import.ts +31 -10
- package/src/require-resolve-full-response.ts +221 -172
- package/src/require-service-call-response-declaration.ts +23 -8
- package/src/require-strict-assert.ts +21 -8
- package/src/require-ts-extension-imports-exports.ts +19 -6
- package/src/require-type-out-of-type-only-imports.ts +12 -4
|
@@ -22,11 +22,13 @@ const rule: TSESLint.RuleModule<string, unknown[]> = createRule({
|
|
|
22
22
|
meta: {
|
|
23
23
|
type: 'problem',
|
|
24
24
|
docs: {
|
|
25
|
-
description:
|
|
25
|
+
description:
|
|
26
|
+
'Validate that message argument is always supplied to node:assert methods',
|
|
26
27
|
},
|
|
27
28
|
schema: [],
|
|
28
29
|
messages: {
|
|
29
|
-
[MISSING_ASSERT_MESSAGE]:
|
|
30
|
+
[MISSING_ASSERT_MESSAGE]:
|
|
31
|
+
'Missing message argument in {{methodName}}() method.',
|
|
30
32
|
},
|
|
31
33
|
},
|
|
32
34
|
defaultOptions: [],
|
|
@@ -39,7 +41,8 @@ const rule: TSESLint.RuleModule<string, unknown[]> = createRule({
|
|
|
39
41
|
if (node.source.value === 'node:assert') {
|
|
40
42
|
const specifier = node.specifiers.find(
|
|
41
43
|
(importSpecifier) =>
|
|
42
|
-
importSpecifier.type ===
|
|
44
|
+
importSpecifier.type ===
|
|
45
|
+
TSESTree.AST_NODE_TYPES.ImportDefaultSpecifier ||
|
|
43
46
|
importSpecifier.type === TSESTree.AST_NODE_TYPES.ImportSpecifier,
|
|
44
47
|
);
|
|
45
48
|
if (specifier) {
|
|
@@ -61,11 +64,16 @@ const rule: TSESLint.RuleModule<string, unknown[]> = createRule({
|
|
|
61
64
|
if (!(methodName in messageIndexCache)) {
|
|
62
65
|
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
63
66
|
const signature = checker.getResolvedSignature(tsNode);
|
|
64
|
-
messageIndexCache[methodName] = signature
|
|
67
|
+
messageIndexCache[methodName] = signature
|
|
68
|
+
?.getParameters()
|
|
69
|
+
.findIndex((param) => param.name === 'message');
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
const messageIndex = messageIndexCache[methodName];
|
|
68
|
-
if (
|
|
73
|
+
if (
|
|
74
|
+
messageIndex !== undefined &&
|
|
75
|
+
node.arguments.length <= messageIndex
|
|
76
|
+
) {
|
|
69
77
|
context.report({
|
|
70
78
|
node,
|
|
71
79
|
messageId: MISSING_ASSERT_MESSAGE,
|
|
@@ -12,7 +12,8 @@ export default {
|
|
|
12
12
|
meta: {
|
|
13
13
|
type: 'problem',
|
|
14
14
|
docs: {
|
|
15
|
-
description:
|
|
15
|
+
description:
|
|
16
|
+
'Validate that assert Predicate is always supplied to node:assert rejects,throws methods',
|
|
16
17
|
url: 'https://github.com/checkdigit/eslint-plugin',
|
|
17
18
|
},
|
|
18
19
|
},
|
|
@@ -39,7 +40,8 @@ export default {
|
|
|
39
40
|
callee.object.type === 'Identifier' &&
|
|
40
41
|
callee.object.name === assertIdentifier &&
|
|
41
42
|
callee.property.type === 'Identifier' &&
|
|
42
|
-
(callee.property.name === 'rejects' ||
|
|
43
|
+
(callee.property.name === 'rejects' ||
|
|
44
|
+
callee.property.name === 'throws') &&
|
|
43
45
|
node.arguments.length >= 1
|
|
44
46
|
) {
|
|
45
47
|
const assertPredicate = node.arguments[1];
|
|
@@ -54,7 +56,8 @@ export default {
|
|
|
54
56
|
) {
|
|
55
57
|
context.report({
|
|
56
58
|
node,
|
|
57
|
-
message:
|
|
59
|
+
message:
|
|
60
|
+
'Second argument in {{method}} method should be of type AssertPredicate.',
|
|
58
61
|
data: {
|
|
59
62
|
method: callee.property.name,
|
|
60
63
|
},
|
|
@@ -9,31 +9,43 @@
|
|
|
9
9
|
import { strict as assert } from 'node:assert';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
AST_NODE_TYPES,
|
|
14
|
+
ESLintUtils,
|
|
15
|
+
TSESTree,
|
|
16
|
+
} from '@typescript-eslint/utils';
|
|
13
17
|
|
|
14
18
|
import getDocumentationUrl from './get-documentation-url.ts';
|
|
15
19
|
|
|
16
20
|
export const ruleId = 'require-fixed-services-import';
|
|
17
21
|
|
|
18
22
|
const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
|
|
19
|
-
const SERVICE_TYPINGS_IMPORT_PATH =
|
|
23
|
+
const SERVICE_TYPINGS_IMPORT_PATH =
|
|
24
|
+
/(?<path>\.\.\/)+services(?!\/index(?:\.ts)?)\/.*/u;
|
|
20
25
|
const SERVICE_TYPINGS_IMPORT_PATH_WITH_VERSION =
|
|
26
|
+
// Groups are destructed in the code
|
|
27
|
+
// eslint-disable-next-line sonarjs/unused-named-groups
|
|
21
28
|
/(?<path>\.\.\/)+services\/(?<service>\w+)\/(?<version>v\d+)(?<index>\/index(?:\.ts)?)?/u;
|
|
22
29
|
|
|
23
30
|
const rule: ESLintUtils.RuleModule<
|
|
24
|
-
|
|
31
|
+
| 'updateServicesImportSpecifier'
|
|
32
|
+
| 'updateServicesImportSource'
|
|
33
|
+
| 'renameServiceTypeReference'
|
|
25
34
|
> = createRule({
|
|
26
35
|
name: ruleId,
|
|
27
36
|
meta: {
|
|
28
37
|
type: 'suggestion',
|
|
29
38
|
docs: {
|
|
30
|
-
description:
|
|
39
|
+
description:
|
|
40
|
+
'Require fixed "from" with service typing imports from "src/services".',
|
|
31
41
|
},
|
|
32
42
|
messages: {
|
|
33
43
|
updateServicesImportSpecifier:
|
|
34
44
|
'Update service typing import specifiers to be from the corresponding service version namespace.',
|
|
35
|
-
updateServicesImportSource:
|
|
36
|
-
|
|
45
|
+
updateServicesImportSource:
|
|
46
|
+
'Update service typing imports to be from the fixed "src/services" path.',
|
|
47
|
+
renameServiceTypeReference:
|
|
48
|
+
'Rename service type reference using the corresponding service version namespace.',
|
|
37
49
|
},
|
|
38
50
|
fixable: 'code',
|
|
39
51
|
schema: [],
|
|
@@ -53,7 +65,8 @@ const rule: ESLintUtils.RuleModule<
|
|
|
53
65
|
|
|
54
66
|
if (SERVICE_TYPINGS_IMPORT_PATH.test(moduleName)) {
|
|
55
67
|
// make sure that it matches only the src/services path
|
|
56
|
-
const match =
|
|
68
|
+
const match =
|
|
69
|
+
SERVICE_TYPINGS_IMPORT_PATH_WITH_VERSION.exec(moduleName);
|
|
57
70
|
if (match?.groups) {
|
|
58
71
|
// need to import the service typings from the fixed path, and also apply the namespace to the referenced types
|
|
59
72
|
const { service, version } = match.groups;
|
|
@@ -65,7 +78,10 @@ const rule: ESLintUtils.RuleModule<
|
|
|
65
78
|
specifier.type === AST_NODE_TYPES.ImportSpecifier &&
|
|
66
79
|
specifier.imported.type === AST_NODE_TYPES.Identifier
|
|
67
80
|
) {
|
|
68
|
-
importedServiceTypeMapping.set(
|
|
81
|
+
importedServiceTypeMapping.set(
|
|
82
|
+
specifier.local.name,
|
|
83
|
+
`${service}.${specifier.imported.name}`,
|
|
84
|
+
);
|
|
69
85
|
}
|
|
70
86
|
}
|
|
71
87
|
|
|
@@ -78,7 +94,10 @@ const rule: ESLintUtils.RuleModule<
|
|
|
78
94
|
messageId: 'updateServicesImportSpecifier',
|
|
79
95
|
node: importDeclaration.source,
|
|
80
96
|
*fix(fixer) {
|
|
81
|
-
yield fixer.replaceTextRange(
|
|
97
|
+
yield fixer.replaceTextRange(
|
|
98
|
+
[rangeStart, rangeEnd],
|
|
99
|
+
`${service}V${version.slice(1)} as ${service}`,
|
|
100
|
+
);
|
|
82
101
|
},
|
|
83
102
|
});
|
|
84
103
|
}
|
|
@@ -102,7 +121,9 @@ const rule: ESLintUtils.RuleModule<
|
|
|
102
121
|
typeReference.typeName.type === AST_NODE_TYPES.Identifier &&
|
|
103
122
|
importedServiceTypeMapping.has(typeReference.typeName.name)
|
|
104
123
|
) {
|
|
105
|
-
const renamedTypeName = importedServiceTypeMapping.get(
|
|
124
|
+
const renamedTypeName = importedServiceTypeMapping.get(
|
|
125
|
+
typeReference.typeName.name,
|
|
126
|
+
);
|
|
106
127
|
assert.ok(renamedTypeName !== undefined);
|
|
107
128
|
context.report({
|
|
108
129
|
messageId: 'renameServiceTypeReference',
|
|
@@ -7,210 +7,259 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { strict as assert } from 'node:assert';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
AST_NODE_TYPES,
|
|
12
|
+
ESLintUtils,
|
|
13
|
+
TSESTree,
|
|
14
|
+
} from '@typescript-eslint/utils';
|
|
11
15
|
import { DefinitionType, type Scope } from '@typescript-eslint/scope-manager';
|
|
12
16
|
import getDocumentationUrl from './get-documentation-url.ts';
|
|
13
17
|
import { getEnclosingScopeNode } from './library/ts-tree.ts';
|
|
14
18
|
|
|
15
19
|
export const ruleId = 'require-resolve-full-response';
|
|
16
20
|
// eslint-disable-next-line @typescript-eslint/no-inferrable-types
|
|
17
|
-
export const PLAIN_URL_REGEXP: RegExp =
|
|
21
|
+
export const PLAIN_URL_REGEXP: RegExp =
|
|
22
|
+
/^[`']\/\w+(?<serviceNamePart>-\w+)*\/v\d+\/(?<any>.|\r|\n)+[`']$/u;
|
|
18
23
|
// eslint-disable-next-line @typescript-eslint/no-inferrable-types
|
|
19
|
-
export const TOKENIZED_URL_REGEXP: RegExp =
|
|
24
|
+
export const TOKENIZED_URL_REGEXP: RegExp =
|
|
25
|
+
/^`\$\{(?<serviceNamePart>[A-Z]+_)*BASE_PATH\}\/(?<any>.|\r|\n)+`$/u;
|
|
20
26
|
|
|
21
27
|
const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
|
|
22
28
|
|
|
23
|
-
const rule: ESLintUtils.RuleModule<'invalidOptions' | 'unknownError'> =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
const rule: ESLintUtils.RuleModule<'invalidOptions' | 'unknownError'> =
|
|
30
|
+
createRule({
|
|
31
|
+
name: ruleId,
|
|
32
|
+
meta: {
|
|
33
|
+
type: 'suggestion',
|
|
34
|
+
docs: {
|
|
35
|
+
description: 'Prefer native fetch over customized service wrapper.',
|
|
36
|
+
},
|
|
37
|
+
messages: {
|
|
38
|
+
invalidOptions:
|
|
39
|
+
'"options" argument should be provided with "resolveWithFullResponse" property set as "true". Otherwise, it indicates that the response body will be obtained without status code assertion which could result in unexpected issue.',
|
|
40
|
+
unknownError:
|
|
41
|
+
'Unknown error occurred in file "{{fileName}}": {{ error }}.',
|
|
42
|
+
},
|
|
43
|
+
schema: [],
|
|
34
44
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
(urlArgument?.type === AST_NODE_TYPES.Literal && typeof urlArgument.value === 'string') ||
|
|
47
|
-
urlArgument?.type === AST_NODE_TYPES.TemplateLiteral
|
|
45
|
+
// eslint-disable-next-line max-lines-per-function
|
|
46
|
+
create(context) {
|
|
47
|
+
const sourceCode = context.sourceCode;
|
|
48
|
+
const scopeManager = sourceCode.scopeManager;
|
|
49
|
+
const parserService = ESLintUtils.getParserServices(context);
|
|
50
|
+
const typeChecker = parserService.program.getTypeChecker();
|
|
51
|
+
|
|
52
|
+
function isUrlArgumentValid(
|
|
53
|
+
urlArgument: TSESTree.Node | undefined,
|
|
54
|
+
scope: Scope,
|
|
48
55
|
) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
if (
|
|
57
|
+
(urlArgument?.type === AST_NODE_TYPES.Literal &&
|
|
58
|
+
typeof urlArgument.value === 'string') ||
|
|
59
|
+
urlArgument?.type === AST_NODE_TYPES.TemplateLiteral
|
|
60
|
+
) {
|
|
61
|
+
const urlText = sourceCode.getText(urlArgument);
|
|
62
|
+
return (
|
|
63
|
+
PLAIN_URL_REGEXP.test(urlText) || TOKENIZED_URL_REGEXP.test(urlText)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
52
66
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
if (urlArgument?.type === AST_NODE_TYPES.Identifier) {
|
|
68
|
+
const foundVariable = scope.variables.find(
|
|
69
|
+
(variable) => variable.name === urlArgument.name,
|
|
70
|
+
);
|
|
71
|
+
if (foundVariable) {
|
|
72
|
+
const variableDefinition = foundVariable.defs.find(
|
|
73
|
+
(def) => def.type === DefinitionType.Variable,
|
|
74
|
+
);
|
|
75
|
+
if (variableDefinition) {
|
|
76
|
+
const variableDefinitionNode = variableDefinition.node;
|
|
77
|
+
assert.ok(
|
|
78
|
+
variableDefinitionNode.init,
|
|
79
|
+
'Variable definition node has no init property',
|
|
80
|
+
);
|
|
81
|
+
return isUrlArgumentValid(variableDefinitionNode.init, scope);
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
61
84
|
}
|
|
62
|
-
return true;
|
|
63
85
|
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
86
|
|
|
69
|
-
function getType(identifier: TSESTree.Identifier) {
|
|
70
|
-
const variable = parserService.esTreeNodeToTSNodeMap.get(identifier);
|
|
71
|
-
const variableType = typeChecker.getTypeAtLocation(variable);
|
|
72
|
-
return typeChecker.typeToString(variableType);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function isServiceLikeName(name: string) {
|
|
76
|
-
return /.*[Ss]ervice$/u.test(name);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function isCalleeServiceWrapper(serviceCall: TSESTree.CallExpression) {
|
|
80
|
-
const callee = serviceCall.callee;
|
|
81
|
-
if (callee.type !== AST_NODE_TYPES.MemberExpression) {
|
|
82
87
|
return false;
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (endpoint.type !== AST_NODE_TYPES.CallExpression) {
|
|
90
|
-
return false;
|
|
90
|
+
function getType(identifier: TSESTree.Identifier) {
|
|
91
|
+
const variable = parserService.esTreeNodeToTSNodeMap.get(identifier);
|
|
92
|
+
const variableType = typeChecker.getTypeAtLocation(variable);
|
|
93
|
+
return typeChecker.typeToString(variableType);
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
if (contextArgument.name !== 'EMPTY_CONTEXT' && getType(contextArgument) !== 'InboundContext') {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
const service = endpoint.callee;
|
|
101
|
-
if (service.type === AST_NODE_TYPES.Identifier) {
|
|
102
|
-
return getType(service) === 'ResolvedService';
|
|
96
|
+
function isServiceLikeName(name: string) {
|
|
97
|
+
// eslint-disable-next-line sonarjs/slow-regex
|
|
98
|
+
return /.*[Ss]ervice$/u.test(name);
|
|
103
99
|
}
|
|
104
100
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return getType(services) === 'ResolvedServices';
|
|
111
|
-
}
|
|
101
|
+
function isCalleeServiceWrapper(serviceCall: TSESTree.CallExpression) {
|
|
102
|
+
const callee = serviceCall.callee;
|
|
103
|
+
if (callee.type !== AST_NODE_TYPES.MemberExpression) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
112
106
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
107
|
+
const endpoint = callee.object;
|
|
108
|
+
if (endpoint.type === AST_NODE_TYPES.Identifier) {
|
|
109
|
+
return (
|
|
110
|
+
getType(endpoint) === 'Endpoint' || isServiceLikeName(endpoint.name)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (endpoint.type !== AST_NODE_TYPES.CallExpression) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const [contextArgument] = endpoint.arguments;
|
|
118
|
+
if (contextArgument?.type !== AST_NODE_TYPES.Identifier) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (
|
|
122
|
+
contextArgument.name !== 'EMPTY_CONTEXT' &&
|
|
123
|
+
getType(contextArgument) !== 'InboundContext'
|
|
124
|
+
) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const service = endpoint.callee;
|
|
128
|
+
if (service.type === AST_NODE_TYPES.Identifier) {
|
|
129
|
+
return getType(service) === 'ResolvedService';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (service.type !== AST_NODE_TYPES.MemberExpression) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const services = service.object;
|
|
136
|
+
if (services.type === AST_NODE_TYPES.Identifier) {
|
|
137
|
+
return getType(services) === 'ResolvedServices';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (services.type !== AST_NODE_TYPES.MemberExpression) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
const configuration = services.object;
|
|
144
|
+
if (configuration.type === AST_NODE_TYPES.Identifier) {
|
|
145
|
+
return ['Configuration', 'Configuration<ResolvedServices>'].includes(
|
|
146
|
+
getType(configuration),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// following applies only to test code (fixture)
|
|
151
|
+
if (configuration.type !== AST_NODE_TYPES.MemberExpression) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
const fixture = configuration.object;
|
|
155
|
+
if (fixture.type === AST_NODE_TYPES.Identifier) {
|
|
156
|
+
return fixture.name === 'fixture' || getType(fixture) === 'Fixture';
|
|
157
|
+
}
|
|
120
158
|
|
|
121
|
-
// following applies only to test code (fixture)
|
|
122
|
-
if (configuration.type !== AST_NODE_TYPES.MemberExpression) {
|
|
123
159
|
return false;
|
|
124
160
|
}
|
|
125
|
-
const fixture = configuration.object;
|
|
126
|
-
if (fixture.type === AST_NODE_TYPES.Identifier) {
|
|
127
|
-
return fixture.name === 'fixture' || getType(fixture) === 'Fixture';
|
|
128
|
-
}
|
|
129
161
|
|
|
130
|
-
return
|
|
131
|
-
|
|
162
|
+
return {
|
|
163
|
+
'CallExpression[callee.property.name=/^(head|get|put|post|del|patch)$/]':
|
|
164
|
+
(serviceCall: TSESTree.CallExpression) => {
|
|
165
|
+
try {
|
|
166
|
+
if (!isCalleeServiceWrapper(serviceCall)) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
132
169
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
170
|
+
const enclosingScopeNode = getEnclosingScopeNode(serviceCall);
|
|
171
|
+
assert.ok(enclosingScopeNode, 'enclosingScopeNode is undefined');
|
|
172
|
+
const scope = scopeManager?.acquire(enclosingScopeNode);
|
|
173
|
+
assert.ok(scope, 'scope is undefined');
|
|
174
|
+
const urlArgument = serviceCall.arguments[0];
|
|
175
|
+
if (!isUrlArgumentValid(urlArgument, scope)) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
141
178
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
179
|
+
assert.ok(
|
|
180
|
+
serviceCall.callee.type === AST_NODE_TYPES.MemberExpression,
|
|
181
|
+
);
|
|
182
|
+
assert.ok(
|
|
183
|
+
serviceCall.callee.property.type === AST_NODE_TYPES.Identifier,
|
|
184
|
+
);
|
|
150
185
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// method
|
|
155
|
-
const method = serviceCall.callee.property.name;
|
|
156
|
-
|
|
157
|
-
// options
|
|
158
|
-
const optionsArgument = ['get', 'head', 'del'].includes(method)
|
|
159
|
-
? serviceCall.arguments[1]
|
|
160
|
-
: serviceCall.arguments[2];
|
|
161
|
-
if (optionsArgument === undefined) {
|
|
162
|
-
context.report({
|
|
163
|
-
node: serviceCall,
|
|
164
|
-
messageId: 'invalidOptions',
|
|
165
|
-
});
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
186
|
+
// method
|
|
187
|
+
const method = serviceCall.callee.property.name;
|
|
168
188
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
// options
|
|
190
|
+
const optionsArgument = ['get', 'head', 'del'].includes(method)
|
|
191
|
+
? serviceCall.arguments[1]
|
|
192
|
+
: serviceCall.arguments[2];
|
|
193
|
+
if (optionsArgument === undefined) {
|
|
194
|
+
context.report({
|
|
195
|
+
node: serviceCall,
|
|
196
|
+
messageId: 'invalidOptions',
|
|
197
|
+
});
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (optionsArgument.type === AST_NODE_TYPES.Identifier) {
|
|
202
|
+
const optionsTypeString = getType(optionsArgument);
|
|
203
|
+
if (optionsTypeString === 'FullResponseOptions') {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const variable =
|
|
207
|
+
parserService.esTreeNodeToTSNodeMap.get(optionsArgument);
|
|
208
|
+
const optionType = typeChecker.getTypeAtLocation(variable);
|
|
209
|
+
const resolveWithFullResponseProperty = optionType.getProperty(
|
|
210
|
+
'resolveWithFullResponse',
|
|
211
|
+
);
|
|
212
|
+
if (
|
|
213
|
+
resolveWithFullResponseProperty?.declarations?.[0]?.getText() ===
|
|
214
|
+
'resolveWithFullResponse: true'
|
|
215
|
+
) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
} else if (
|
|
219
|
+
optionsArgument.type === AST_NODE_TYPES.ObjectExpression
|
|
220
|
+
) {
|
|
221
|
+
const resolveWithFullResponseProperty =
|
|
222
|
+
optionsArgument.properties.find(
|
|
223
|
+
(property) =>
|
|
224
|
+
property.type === AST_NODE_TYPES.Property &&
|
|
225
|
+
property.key.type === AST_NODE_TYPES.Identifier &&
|
|
226
|
+
property.key.name === 'resolveWithFullResponse',
|
|
227
|
+
);
|
|
228
|
+
if (
|
|
229
|
+
resolveWithFullResponseProperty?.type ===
|
|
230
|
+
AST_NODE_TYPES.Property &&
|
|
231
|
+
resolveWithFullResponseProperty.value.type ===
|
|
232
|
+
AST_NODE_TYPES.Literal &&
|
|
233
|
+
resolveWithFullResponseProperty.value.value === true
|
|
234
|
+
) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
context.report({
|
|
239
|
+
node: optionsArgument,
|
|
240
|
+
messageId: 'invalidOptions',
|
|
241
|
+
});
|
|
242
|
+
} catch (error) {
|
|
243
|
+
// eslint-disable-next-line no-console
|
|
244
|
+
console.error(
|
|
245
|
+
`Failed to apply ${ruleId} rule for file "${context.filename}":`,
|
|
246
|
+
error,
|
|
247
|
+
);
|
|
248
|
+
context.report({
|
|
249
|
+
node: serviceCall,
|
|
250
|
+
messageId: 'unknownError',
|
|
251
|
+
data: {
|
|
252
|
+
fileName: context.filename,
|
|
253
|
+
error:
|
|
254
|
+
error instanceof Error
|
|
255
|
+
? error.toString()
|
|
256
|
+
: JSON.stringify(error),
|
|
257
|
+
},
|
|
258
|
+
});
|
|
193
259
|
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
});
|
|
199
|
-
} catch (error) {
|
|
200
|
-
// eslint-disable-next-line no-console
|
|
201
|
-
console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
|
|
202
|
-
context.report({
|
|
203
|
-
node: serviceCall,
|
|
204
|
-
messageId: 'unknownError',
|
|
205
|
-
data: {
|
|
206
|
-
fileName: context.filename,
|
|
207
|
-
error: error instanceof Error ? error.toString() : JSON.stringify(error),
|
|
208
|
-
},
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
},
|
|
212
|
-
};
|
|
213
|
-
},
|
|
214
|
-
});
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
},
|
|
263
|
+
});
|
|
215
264
|
|
|
216
265
|
export default rule;
|