@checkdigit/eslint-plugin 7.17.1 → 8.0.0-PR.141-7ea9
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
|
@@ -6,12 +6,18 @@
|
|
|
6
6
|
* This code is licensed under the MIT license (see LICENSE.txt for details).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
AST_NODE_TYPES,
|
|
11
|
+
ESLintUtils,
|
|
12
|
+
TSESTree,
|
|
13
|
+
} from '@typescript-eslint/utils';
|
|
10
14
|
import getDocumentationUrl from '../get-documentation-url.ts';
|
|
11
15
|
|
|
12
16
|
export const ruleId = 'require-consistent-read';
|
|
13
|
-
export const MESSAGE_ID_CONSISTENT_READ_TRUE =
|
|
14
|
-
|
|
17
|
+
export const MESSAGE_ID_CONSISTENT_READ_TRUE =
|
|
18
|
+
'MESSAGE_ID_CONSISTENT_READ_TRUE';
|
|
19
|
+
export const MESSAGE_ID_CONSISTENT_READ_FALSE =
|
|
20
|
+
'MESSAGE_ID_CONSISTENT_READ_FALSE';
|
|
15
21
|
|
|
16
22
|
interface ReadCommandInfo {
|
|
17
23
|
type: 'Get' | 'Query' | 'BatchGet';
|
|
@@ -27,12 +33,15 @@ function getPropertyName(property: TSESTree.Property): string | undefined {
|
|
|
27
33
|
// eslint-disable-next-line no-nested-ternary
|
|
28
34
|
return propertyKey.type === AST_NODE_TYPES.Identifier
|
|
29
35
|
? propertyKey.name
|
|
30
|
-
:
|
|
36
|
+
: // eslint-disable-next-line sonarjs/no-nested-conditional
|
|
37
|
+
typeof propertyKey.value === 'string'
|
|
31
38
|
? propertyKey.value
|
|
32
39
|
: undefined;
|
|
33
40
|
}
|
|
34
41
|
|
|
35
|
-
function extractObjectProperties(
|
|
42
|
+
function extractObjectProperties(
|
|
43
|
+
objectExpression: TSESTree.ObjectExpression,
|
|
44
|
+
): Record<string, TSESTree.Property> {
|
|
36
45
|
const objectProperties: Record<string, TSESTree.Property> = {};
|
|
37
46
|
for (const property of objectExpression.properties) {
|
|
38
47
|
if (property.type !== AST_NODE_TYPES.Property || property.computed) {
|
|
@@ -46,7 +55,9 @@ function extractObjectProperties(objectExpression: TSESTree.ObjectExpression): R
|
|
|
46
55
|
return objectProperties;
|
|
47
56
|
}
|
|
48
57
|
|
|
49
|
-
function existsRequestItemsProperty(
|
|
58
|
+
function existsRequestItemsProperty(
|
|
59
|
+
extractedObjectProperties: Record<string, TSESTree.Property>,
|
|
60
|
+
): boolean {
|
|
50
61
|
const requestItemProperty = extractedObjectProperties['RequestItems'];
|
|
51
62
|
if (requestItemProperty?.type !== AST_NODE_TYPES.Property) {
|
|
52
63
|
return false;
|
|
@@ -60,17 +71,25 @@ function existsRequestItemsProperty(extractedObjectProperties: Record<string, TS
|
|
|
60
71
|
(property) =>
|
|
61
72
|
property.type === AST_NODE_TYPES.Property &&
|
|
62
73
|
property.value.type === AST_NODE_TYPES.ObjectExpression &&
|
|
63
|
-
extractObjectProperties(property.value)['Keys']?.value.type ===
|
|
74
|
+
extractObjectProperties(property.value)['Keys']?.value.type ===
|
|
75
|
+
AST_NODE_TYPES.ArrayExpression,
|
|
64
76
|
);
|
|
65
77
|
}
|
|
66
78
|
|
|
67
|
-
function existsKeyProperty(
|
|
79
|
+
function existsKeyProperty(
|
|
80
|
+
extractedObjectProperties: Record<string, TSESTree.Property>,
|
|
81
|
+
): boolean {
|
|
68
82
|
const keyProperty = extractedObjectProperties['Key'];
|
|
69
83
|
// Relaxed: just ensure it's an object (works for both low-level and DocumentClient)
|
|
70
|
-
return
|
|
84
|
+
return (
|
|
85
|
+
keyProperty?.type === AST_NODE_TYPES.Property &&
|
|
86
|
+
keyProperty.value.type === AST_NODE_TYPES.ObjectExpression
|
|
87
|
+
);
|
|
71
88
|
}
|
|
72
89
|
|
|
73
|
-
export function getReadCommandInfo(
|
|
90
|
+
export function getReadCommandInfo(
|
|
91
|
+
objectExpression: TSESTree.ObjectExpression,
|
|
92
|
+
): ReadCommandInfo | undefined {
|
|
74
93
|
const extractedProperties = extractObjectProperties(objectExpression);
|
|
75
94
|
let readCommandInfo: ReadCommandInfo | undefined;
|
|
76
95
|
|
|
@@ -82,13 +101,19 @@ export function getReadCommandInfo(objectExpression: TSESTree.ObjectExpression):
|
|
|
82
101
|
if (hasTableName && hasKey) {
|
|
83
102
|
// make sure it is not an update or conditional write;
|
|
84
103
|
// we can't really tell if it's a delete, but we simply ignore that case assuming they'll never be used
|
|
85
|
-
if (
|
|
104
|
+
if (
|
|
105
|
+
!('UpdateExpression' in extractedProperties) &&
|
|
106
|
+
!('ConditionExpression' in extractedProperties)
|
|
107
|
+
) {
|
|
86
108
|
readCommandInfo = { type: 'Get' };
|
|
87
109
|
}
|
|
88
110
|
} else {
|
|
89
|
-
const hasKeyCondExpr =
|
|
111
|
+
const hasKeyCondExpr =
|
|
112
|
+
extractedProperties['KeyConditionExpression']?.value.type ===
|
|
113
|
+
AST_NODE_TYPES.Literal;
|
|
90
114
|
const hasLegacyKeyConditions =
|
|
91
|
-
extractedProperties['KeyConditions']?.value.type ===
|
|
115
|
+
extractedProperties['KeyConditions']?.value.type ===
|
|
116
|
+
AST_NODE_TYPES.ObjectExpression;
|
|
92
117
|
if (hasTableName && (hasKeyCondExpr || hasLegacyKeyConditions)) {
|
|
93
118
|
readCommandInfo = { type: 'Query' };
|
|
94
119
|
}
|
|
@@ -122,54 +147,66 @@ export function getReadCommandInfo(objectExpression: TSESTree.ObjectExpression):
|
|
|
122
147
|
|
|
123
148
|
const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
|
|
124
149
|
|
|
125
|
-
const rule: ESLintUtils.RuleModule<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
[MESSAGE_ID_CONSISTENT_READ_TRUE]:
|
|
136
|
-
'ConsistentRead option should always be set as true for {{readCommandType}} command.',
|
|
137
|
-
[MESSAGE_ID_CONSISTENT_READ_FALSE]:
|
|
138
|
-
'ConsistentRead option should not be set as true for {{readCommandType}} command when using a global secondary index.',
|
|
139
|
-
},
|
|
140
|
-
schema: [],
|
|
150
|
+
const rule: ESLintUtils.RuleModule<
|
|
151
|
+
| typeof MESSAGE_ID_CONSISTENT_READ_TRUE
|
|
152
|
+
| typeof MESSAGE_ID_CONSISTENT_READ_FALSE
|
|
153
|
+
> = createRule({
|
|
154
|
+
name: ruleId,
|
|
155
|
+
meta: {
|
|
156
|
+
type: 'problem',
|
|
157
|
+
docs: {
|
|
158
|
+
description:
|
|
159
|
+
'For AWS dynamodb commands Query/Get/BatchGet, ConsistentRead option should always be set as true unless global index is used. This will make the service more robust at the ignorable cost of RCU.',
|
|
141
160
|
},
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
ObjectExpression(node) {
|
|
148
|
-
// Quick prefilter: only look at objects that mention table/read-ish keys
|
|
149
|
-
const text = sourceCode.getText(node);
|
|
150
|
-
if (!/TableName|RequestItems|KeyConditionExpression|KeyConditions/u.test(text)) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const readCommandInfo = getReadCommandInfo(node);
|
|
155
|
-
if (readCommandInfo !== undefined) {
|
|
156
|
-
if (readCommandInfo.usedIndex !== true && readCommandInfo.consistentRead !== true) {
|
|
157
|
-
context.report({
|
|
158
|
-
node,
|
|
159
|
-
messageId: MESSAGE_ID_CONSISTENT_READ_TRUE,
|
|
160
|
-
data: { readCommandType: readCommandInfo.type },
|
|
161
|
-
});
|
|
162
|
-
} else if (readCommandInfo.usedIndex === true && readCommandInfo.consistentRead !== false) {
|
|
163
|
-
context.report({
|
|
164
|
-
node,
|
|
165
|
-
messageId: MESSAGE_ID_CONSISTENT_READ_FALSE,
|
|
166
|
-
data: { readCommandType: readCommandInfo.type },
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
};
|
|
161
|
+
messages: {
|
|
162
|
+
[MESSAGE_ID_CONSISTENT_READ_TRUE]:
|
|
163
|
+
'ConsistentRead option should always be set as true for {{readCommandType}} command.',
|
|
164
|
+
[MESSAGE_ID_CONSISTENT_READ_FALSE]:
|
|
165
|
+
'ConsistentRead option should not be set as true for {{readCommandType}} command when using a global secondary index.',
|
|
172
166
|
},
|
|
173
|
-
|
|
167
|
+
schema: [],
|
|
168
|
+
},
|
|
169
|
+
defaultOptions: [],
|
|
170
|
+
create(context) {
|
|
171
|
+
const sourceCode = context.sourceCode;
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
ObjectExpression(node) {
|
|
175
|
+
// Quick prefilter: only look at objects that mention table/read-ish keys
|
|
176
|
+
const text = sourceCode.getText(node);
|
|
177
|
+
if (
|
|
178
|
+
!/TableName|RequestItems|KeyConditionExpression|KeyConditions/u.test(
|
|
179
|
+
text,
|
|
180
|
+
)
|
|
181
|
+
) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const readCommandInfo = getReadCommandInfo(node);
|
|
186
|
+
if (readCommandInfo !== undefined) {
|
|
187
|
+
if (
|
|
188
|
+
readCommandInfo.usedIndex !== true &&
|
|
189
|
+
readCommandInfo.consistentRead !== true
|
|
190
|
+
) {
|
|
191
|
+
context.report({
|
|
192
|
+
node,
|
|
193
|
+
messageId: MESSAGE_ID_CONSISTENT_READ_TRUE,
|
|
194
|
+
data: { readCommandType: readCommandInfo.type },
|
|
195
|
+
});
|
|
196
|
+
} else if (
|
|
197
|
+
readCommandInfo.usedIndex === true &&
|
|
198
|
+
readCommandInfo.consistentRead !== false
|
|
199
|
+
) {
|
|
200
|
+
context.report({
|
|
201
|
+
node,
|
|
202
|
+
messageId: MESSAGE_ID_CONSISTENT_READ_FALSE,
|
|
203
|
+
data: { readCommandType: readCommandInfo.type },
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
});
|
|
174
211
|
|
|
175
212
|
export default rule;
|
package/src/file-path-comment.ts
CHANGED
|
@@ -40,7 +40,10 @@ export default {
|
|
|
40
40
|
},
|
|
41
41
|
message: 'first line cannot be a block comment',
|
|
42
42
|
fix(fixer: Rule.RuleFixer) {
|
|
43
|
-
return fixer.insertTextBeforeRange(
|
|
43
|
+
return fixer.insertTextBeforeRange(
|
|
44
|
+
[0, 0],
|
|
45
|
+
`// ${expectedPath}\n\n`,
|
|
46
|
+
);
|
|
44
47
|
},
|
|
45
48
|
});
|
|
46
49
|
} else {
|
|
@@ -57,7 +60,10 @@ export default {
|
|
|
57
60
|
},
|
|
58
61
|
message: 'first line is not a comment with the file path',
|
|
59
62
|
fix(fixer: Rule.RuleFixer) {
|
|
60
|
-
return fixer.insertTextBeforeRange(
|
|
63
|
+
return fixer.insertTextBeforeRange(
|
|
64
|
+
[0, 0],
|
|
65
|
+
`// ${expectedPath}\n\n`,
|
|
66
|
+
);
|
|
61
67
|
},
|
|
62
68
|
});
|
|
63
69
|
}
|
|
@@ -77,7 +83,10 @@ export default {
|
|
|
77
83
|
},
|
|
78
84
|
message: 'first line is a comment but is not a path to the file',
|
|
79
85
|
fix(fixer: Rule.RuleFixer) {
|
|
80
|
-
return fixer.replaceTextRange(
|
|
86
|
+
return fixer.replaceTextRange(
|
|
87
|
+
[0, firstLine.length],
|
|
88
|
+
`// ${expectedPath}`,
|
|
89
|
+
);
|
|
81
90
|
},
|
|
82
91
|
});
|
|
83
92
|
}
|
package/src/index.ts
CHANGED
|
@@ -8,10 +8,18 @@
|
|
|
8
8
|
|
|
9
9
|
import type { TSESLint } from '@typescript-eslint/utils';
|
|
10
10
|
|
|
11
|
-
import invalidJsonStringify, {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import
|
|
11
|
+
import invalidJsonStringify, {
|
|
12
|
+
ruleId as invalidJsonStringifyRuleId,
|
|
13
|
+
} from './invalid-json-stringify.ts';
|
|
14
|
+
import noDuplicatedImports, {
|
|
15
|
+
ruleId as noDuplicatedImportsRuleId,
|
|
16
|
+
} from './no-duplicated-imports.ts';
|
|
17
|
+
import noLegacyServiceTyping, {
|
|
18
|
+
ruleId as noLegacyServiceTypingRuleId,
|
|
19
|
+
} from './no-legacy-service-typing.ts';
|
|
20
|
+
import noPromiseInstanceMethod, {
|
|
21
|
+
ruleId as noPromiseInstanceMethodRuleId,
|
|
22
|
+
} from './no-promise-instance-method.ts';
|
|
15
23
|
import noStatusCodeAssert from './no-status-code-assert.ts';
|
|
16
24
|
import requireFixedServicesImport, {
|
|
17
25
|
ruleId as requireFixedServicesImportRuleId,
|
|
@@ -22,13 +30,21 @@ import requireResolveFullResponse, {
|
|
|
22
30
|
import requireTypeOutOfTypeOnlyImports, {
|
|
23
31
|
ruleId as requireTypeOutOfTypeOnlyImportsRuleId,
|
|
24
32
|
} from './require-type-out-of-type-only-imports.ts';
|
|
25
|
-
import noServeRuntime, {
|
|
33
|
+
import noServeRuntime, {
|
|
34
|
+
ruleId as noServeRuntimeRuleId,
|
|
35
|
+
} from './no-serve-runtime.ts';
|
|
26
36
|
import requireServiceCallResponseDeclaration, {
|
|
27
37
|
ruleId as requireServiceCallResponseDeclarationRuleId,
|
|
28
38
|
} from './require-service-call-response-declaration.ts';
|
|
29
|
-
import requireAwsConfig, {
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
import requireAwsConfig, {
|
|
40
|
+
ruleId as requireAwsConfigRuleId,
|
|
41
|
+
} from './aws/require-aws-config.ts';
|
|
42
|
+
import requireAWSBareBones, {
|
|
43
|
+
ruleId as requireAWSBareBonesRuleId,
|
|
44
|
+
} from './aws/require-aws-bare-bones.ts';
|
|
45
|
+
import requireConsistentRead, {
|
|
46
|
+
ruleId as requireConsistentReadRuleId,
|
|
47
|
+
} from './aws/require-consistent-read.ts';
|
|
32
48
|
import filePathComment from './file-path-comment.ts';
|
|
33
49
|
import noCardNumbers from './no-card-numbers.ts';
|
|
34
50
|
import noEnum from './no-enum.ts';
|
|
@@ -62,7 +78,8 @@ const rules: Record<string, TSESLint.LooseRuleDefinition> = {
|
|
|
62
78
|
'no-wallaby-comment': noWallabyComment,
|
|
63
79
|
'no-side-effects': noSideEffects,
|
|
64
80
|
'regular-expression-comment': regexComment,
|
|
65
|
-
'require-assert-predicate-rejects-throws':
|
|
81
|
+
'require-assert-predicate-rejects-throws':
|
|
82
|
+
requireAssertPredicateRejectsThrows,
|
|
66
83
|
'object-literal-response': objectLiteralResponse,
|
|
67
84
|
[invalidJsonStringifyRuleId]: invalidJsonStringify,
|
|
68
85
|
[noPromiseInstanceMethodRuleId]: noPromiseInstanceMethod,
|
|
@@ -70,7 +87,8 @@ const rules: Record<string, TSESLint.LooseRuleDefinition> = {
|
|
|
70
87
|
[requireResolveFullResponseRuleId]: requireResolveFullResponse,
|
|
71
88
|
[noDuplicatedImportsRuleId]: noDuplicatedImports,
|
|
72
89
|
[noServeRuntimeRuleId]: noServeRuntime,
|
|
73
|
-
[requireServiceCallResponseDeclarationRuleId]:
|
|
90
|
+
[requireServiceCallResponseDeclarationRuleId]:
|
|
91
|
+
requireServiceCallResponseDeclaration,
|
|
74
92
|
[requireAwsConfigRuleId]: requireAwsConfig,
|
|
75
93
|
[requireFixedServicesImportRuleId]: requireFixedServicesImport,
|
|
76
94
|
[requireTypeOutOfTypeOnlyImportsRuleId]: requireTypeOutOfTypeOnlyImports,
|
|
@@ -25,7 +25,8 @@ export default {
|
|
|
25
25
|
{
|
|
26
26
|
type: 'array',
|
|
27
27
|
items: {
|
|
28
|
-
description:
|
|
28
|
+
description:
|
|
29
|
+
'Regular expression pattern to match the name of the first parameter of JSON.stringify().',
|
|
29
30
|
type: 'string',
|
|
30
31
|
minItems: 1,
|
|
31
32
|
},
|
|
@@ -39,7 +40,9 @@ export default {
|
|
|
39
40
|
},
|
|
40
41
|
create(context) {
|
|
41
42
|
const options = (context.options[0] ?? DEFAULT_OPTIONS) as string[];
|
|
42
|
-
const invalidParameterNamePatterns = options.map(
|
|
43
|
+
const invalidParameterNamePatterns = options.map(
|
|
44
|
+
(option) => new RegExp(option, 'u'),
|
|
45
|
+
);
|
|
43
46
|
|
|
44
47
|
return {
|
|
45
48
|
CallExpression(node) {
|
|
@@ -67,7 +70,10 @@ export default {
|
|
|
67
70
|
parameterName: argument.name,
|
|
68
71
|
},
|
|
69
72
|
fix(fixer) {
|
|
70
|
-
return fixer.replaceText(
|
|
73
|
+
return fixer.replaceText(
|
|
74
|
+
node,
|
|
75
|
+
`String(${argument.name})`,
|
|
76
|
+
);
|
|
71
77
|
},
|
|
72
78
|
},
|
|
73
79
|
],
|
package/src/library/format.ts
CHANGED
|
@@ -11,7 +11,10 @@ import { TSESLint, TSESTree } from '@typescript-eslint/utils';
|
|
|
11
11
|
import type { Node } from 'estree';
|
|
12
12
|
import type { SourceCode } from 'eslint';
|
|
13
13
|
|
|
14
|
-
export function getIndentation(
|
|
14
|
+
export function getIndentation(
|
|
15
|
+
node: Node | TSESTree.Node,
|
|
16
|
+
sourceCode: SourceCode | TSESLint.SourceCode,
|
|
17
|
+
): string {
|
|
15
18
|
assert.ok(node.loc);
|
|
16
19
|
const line = sourceCode.lines[node.loc.start.line - 1];
|
|
17
20
|
assert.ok(line !== undefined);
|
package/src/library/tree.ts
CHANGED
|
@@ -48,7 +48,12 @@ export function getEnclosingStatement(node: Node): Node | undefined {
|
|
|
48
48
|
|
|
49
49
|
export function getEnclosingScopeNode(node: Node): Node | undefined {
|
|
50
50
|
return getAncestor(node, (parentNode) =>
|
|
51
|
-
[
|
|
51
|
+
[
|
|
52
|
+
'FunctionExpression',
|
|
53
|
+
'FunctionDeclaration',
|
|
54
|
+
'ArrowFunctionExpression',
|
|
55
|
+
'Program',
|
|
56
|
+
].includes(parentNode.type),
|
|
52
57
|
);
|
|
53
58
|
}
|
|
54
59
|
|
|
@@ -65,7 +70,8 @@ export function isUsedInArrayOrAsArgument(node: Node): boolean {
|
|
|
65
70
|
if (
|
|
66
71
|
parent.type === 'ArrayExpression' ||
|
|
67
72
|
parent.type === 'ArrowFunctionExpression' ||
|
|
68
|
-
(parent.type === 'CallExpression' &&
|
|
73
|
+
(parent.type === 'CallExpression' &&
|
|
74
|
+
parent.arguments.includes(node as Expression))
|
|
69
75
|
) {
|
|
70
76
|
return true;
|
|
71
77
|
}
|
package/src/library/ts-tree.ts
CHANGED
|
@@ -14,7 +14,9 @@ interface NodeParentExtension {
|
|
|
14
14
|
parent: NodeParent;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function getParent(
|
|
17
|
+
export function getParent(
|
|
18
|
+
node: TSESTree.Node,
|
|
19
|
+
): TSESTree.Node | undefined | null {
|
|
18
20
|
return (node as unknown as NodeParentExtension).parent;
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -42,13 +44,22 @@ export function isBlockStatement(node: TSESTree.Node): boolean {
|
|
|
42
44
|
return node.type.endsWith('Statement') || node.type.endsWith('Declaration');
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
export function getEnclosingStatement(
|
|
47
|
+
export function getEnclosingStatement(
|
|
48
|
+
node: TSESTree.Node,
|
|
49
|
+
): TSESTree.Node | undefined {
|
|
46
50
|
return getAncestor(node, isBlockStatement);
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
export function getEnclosingScopeNode(
|
|
53
|
+
export function getEnclosingScopeNode(
|
|
54
|
+
node: TSESTree.Node,
|
|
55
|
+
): TSESTree.Node | undefined {
|
|
50
56
|
return getAncestor(node, (parentNode) =>
|
|
51
|
-
[
|
|
57
|
+
[
|
|
58
|
+
'FunctionExpression',
|
|
59
|
+
'FunctionDeclaration',
|
|
60
|
+
'ArrowFunctionExpression',
|
|
61
|
+
'Program',
|
|
62
|
+
].includes(parentNode.type),
|
|
52
63
|
);
|
|
53
64
|
}
|
|
54
65
|
|
|
@@ -64,7 +75,8 @@ export function isUsedInArrayOrAsArgument(node: TSESTree.Node): boolean {
|
|
|
64
75
|
|
|
65
76
|
if (
|
|
66
77
|
parent.type === AST_NODE_TYPES.ArrayExpression ||
|
|
67
|
-
(parent.type === AST_NODE_TYPES.CallExpression &&
|
|
78
|
+
(parent.type === AST_NODE_TYPES.CallExpression &&
|
|
79
|
+
parent.arguments.includes(node as TSESTree.Expression))
|
|
68
80
|
) {
|
|
69
81
|
return true;
|
|
70
82
|
}
|
|
@@ -75,7 +87,11 @@ export function isUsedInArrayOrAsArgument(node: TSESTree.Node): boolean {
|
|
|
75
87
|
|
|
76
88
|
export function getEnclosingFunction(
|
|
77
89
|
node: TSESTree.Node,
|
|
78
|
-
):
|
|
90
|
+
):
|
|
91
|
+
| TSESTree.ArrowFunctionExpression
|
|
92
|
+
| TSESTree.FunctionDeclaration
|
|
93
|
+
| TSESTree.FunctionExpression
|
|
94
|
+
| undefined {
|
|
79
95
|
if (
|
|
80
96
|
node.type === AST_NODE_TYPES.FunctionDeclaration ||
|
|
81
97
|
node.type === AST_NODE_TYPES.FunctionExpression ||
|
|
@@ -97,7 +113,8 @@ export function getTypeParentNode(
|
|
|
97
113
|
if (!node) {
|
|
98
114
|
return undefined;
|
|
99
115
|
}
|
|
100
|
-
return node.type === AST_NODE_TYPES.TSTypeAnnotation ||
|
|
116
|
+
return node.type === AST_NODE_TYPES.TSTypeAnnotation ||
|
|
117
|
+
node.type === AST_NODE_TYPES.TSAsExpression
|
|
101
118
|
? node
|
|
102
119
|
: getTypeParentNode(node.parent);
|
|
103
120
|
}
|
package/src/no-card-numbers.ts
CHANGED
|
@@ -44,7 +44,12 @@ function luhnCheck(cardNumber: string) {
|
|
|
44
44
|
);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function checkForCardNumbers(
|
|
47
|
+
function checkForCardNumbers(
|
|
48
|
+
value: string,
|
|
49
|
+
context: Rule.RuleContext,
|
|
50
|
+
node?: Node,
|
|
51
|
+
loc?: SourceLocation,
|
|
52
|
+
) {
|
|
48
53
|
const matches = value.match(cardNumberRegex);
|
|
49
54
|
if (matches === null) {
|
|
50
55
|
return;
|
|
@@ -24,7 +24,8 @@ const rule: ESLintUtils.RuleModule<'mergeDuplicatedImports'> = createRule({
|
|
|
24
24
|
description: 'Merge duplicated import statements with the same "from".',
|
|
25
25
|
},
|
|
26
26
|
messages: {
|
|
27
|
-
mergeDuplicatedImports:
|
|
27
|
+
mergeDuplicatedImports:
|
|
28
|
+
'Merge duplicated import statements with the same "from".',
|
|
28
29
|
},
|
|
29
30
|
fixable: 'code',
|
|
30
31
|
schema: [],
|
|
@@ -45,11 +46,16 @@ const rule: ESLintUtils.RuleModule<'mergeDuplicatedImports'> = createRule({
|
|
|
45
46
|
declarations.push(node);
|
|
46
47
|
},
|
|
47
48
|
'Program:exit'() {
|
|
48
|
-
for (const [
|
|
49
|
+
for (const [
|
|
50
|
+
moduleName,
|
|
51
|
+
allDeclarations,
|
|
52
|
+
] of importDeclarations.entries()) {
|
|
49
53
|
const declarations = allDeclarations.filter(
|
|
50
54
|
(declaration) =>
|
|
51
55
|
!declaration.specifiers.some(
|
|
52
|
-
(specifier) =>
|
|
56
|
+
(specifier) =>
|
|
57
|
+
specifier.type ===
|
|
58
|
+
TSESTree.AST_NODE_TYPES.ImportNamespaceSpecifier,
|
|
53
59
|
),
|
|
54
60
|
);
|
|
55
61
|
if (declarations.length <= 1) {
|
|
@@ -64,7 +70,8 @@ const rule: ESLintUtils.RuleModule<'mergeDuplicatedImports'> = createRule({
|
|
|
64
70
|
declaration.importKind === 'type' ||
|
|
65
71
|
declaration.specifiers.every(
|
|
66
72
|
(specifier) =>
|
|
67
|
-
specifier.type === TSESTree.AST_NODE_TYPES.ImportSpecifier &&
|
|
73
|
+
specifier.type === TSESTree.AST_NODE_TYPES.ImportSpecifier &&
|
|
74
|
+
specifier.importKind === 'type',
|
|
68
75
|
),
|
|
69
76
|
);
|
|
70
77
|
|
|
@@ -76,30 +83,48 @@ const rule: ESLintUtils.RuleModule<'mergeDuplicatedImports'> = createRule({
|
|
|
76
83
|
|
|
77
84
|
const defaultSpecifier = declarations
|
|
78
85
|
.flatMap((declaration) =>
|
|
86
|
+
// eslint-disable-next-line sonarjs/no-nested-functions
|
|
79
87
|
declaration.specifiers.map((specifier) =>
|
|
80
|
-
specifier.type ===
|
|
88
|
+
specifier.type ===
|
|
89
|
+
TSESTree.AST_NODE_TYPES.ImportDefaultSpecifier
|
|
90
|
+
? specifier
|
|
91
|
+
: undefined,
|
|
81
92
|
),
|
|
82
93
|
)
|
|
83
94
|
.filter(Boolean);
|
|
84
|
-
const defaultSpecifierText = defaultSpecifier[0]
|
|
95
|
+
const defaultSpecifierText = defaultSpecifier[0]
|
|
96
|
+
? sourceCode.getText(defaultSpecifier[0])
|
|
97
|
+
: undefined;
|
|
85
98
|
|
|
86
99
|
const mergedSpecifiers = declarations.flatMap((declaration) => {
|
|
87
100
|
const isCurrentDeclarationTypeOnly =
|
|
88
101
|
declaration.importKind === 'type' ||
|
|
89
102
|
declaration.specifiers.every(
|
|
103
|
+
// eslint-disable-next-line sonarjs/no-nested-functions
|
|
90
104
|
(specifier) =>
|
|
91
|
-
specifier.type ===
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
.filter((specifier) => specifier.type !== TSESTree.AST_NODE_TYPES.ImportDefaultSpecifier)
|
|
95
|
-
.map((specifier) =>
|
|
96
|
-
// eslint-disable-next-line no-nested-ternary
|
|
97
|
-
isAllTypeOnly
|
|
98
|
-
? sourceCode.getText(specifier).replace('type ', '')
|
|
99
|
-
: isCurrentDeclarationTypeOnly
|
|
100
|
-
? `type ${sourceCode.getText(specifier)}`
|
|
101
|
-
: sourceCode.getText(specifier),
|
|
105
|
+
specifier.type ===
|
|
106
|
+
TSESTree.AST_NODE_TYPES.ImportSpecifier &&
|
|
107
|
+
specifier.importKind === 'type',
|
|
102
108
|
);
|
|
109
|
+
return (
|
|
110
|
+
declaration.specifiers
|
|
111
|
+
.filter(
|
|
112
|
+
// eslint-disable-next-line sonarjs/no-nested-functions
|
|
113
|
+
(specifier) =>
|
|
114
|
+
specifier.type !==
|
|
115
|
+
TSESTree.AST_NODE_TYPES.ImportDefaultSpecifier,
|
|
116
|
+
)
|
|
117
|
+
// eslint-disable-next-line sonarjs/no-nested-functions
|
|
118
|
+
.map((specifier) =>
|
|
119
|
+
// eslint-disable-next-line no-nested-ternary
|
|
120
|
+
isAllTypeOnly
|
|
121
|
+
? sourceCode.getText(specifier).replace('type ', '')
|
|
122
|
+
: // eslint-disable-next-line sonarjs/no-nested-conditional
|
|
123
|
+
isCurrentDeclarationTypeOnly
|
|
124
|
+
? `type ${sourceCode.getText(specifier)}`
|
|
125
|
+
: sourceCode.getText(specifier),
|
|
126
|
+
)
|
|
127
|
+
);
|
|
103
128
|
});
|
|
104
129
|
const mergedSpecifiersText = `${isAllTypeOnly ? 'type ' : ''}{ ${mergedSpecifiers.join(', ')} }`;
|
|
105
130
|
|
|
@@ -109,7 +134,12 @@ const rule: ESLintUtils.RuleModule<'mergeDuplicatedImports'> = createRule({
|
|
|
109
134
|
|
|
110
135
|
// Remove the remaining imports
|
|
111
136
|
declarations.slice(1).forEach((declaration) => {
|
|
112
|
-
fixes.push(
|
|
137
|
+
fixes.push(
|
|
138
|
+
fixer.removeRange([
|
|
139
|
+
declaration.range[0],
|
|
140
|
+
declaration.range[1] + 1,
|
|
141
|
+
]),
|
|
142
|
+
);
|
|
113
143
|
});
|
|
114
144
|
|
|
115
145
|
return fixes;
|
|
@@ -6,16 +6,26 @@
|
|
|
6
6
|
* This code is licensed under the MIT license (see LICENSE.txt for details).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
AST_NODE_TYPES,
|
|
11
|
+
ESLintUtils,
|
|
12
|
+
TSESTree,
|
|
13
|
+
} from '@typescript-eslint/utils';
|
|
10
14
|
import getDocumentationUrl from './get-documentation-url.ts';
|
|
11
15
|
|
|
12
16
|
export const ruleId = 'no-legacy-service-typing';
|
|
13
17
|
|
|
14
18
|
const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
|
|
15
19
|
|
|
16
|
-
const DISALLOWED_SERVICE_TYPINGS: string[] | undefined = [
|
|
20
|
+
const DISALLOWED_SERVICE_TYPINGS: string[] | undefined = [
|
|
21
|
+
'FullResponse',
|
|
22
|
+
'Endpoint',
|
|
23
|
+
];
|
|
17
24
|
|
|
18
|
-
const rule: ESLintUtils.RuleModule<
|
|
25
|
+
const rule: ESLintUtils.RuleModule<
|
|
26
|
+
'noLegacyServiceTyping',
|
|
27
|
+
[typeof DISALLOWED_SERVICE_TYPINGS]
|
|
28
|
+
> = createRule({
|
|
19
29
|
name: ruleId,
|
|
20
30
|
meta: {
|
|
21
31
|
type: 'problem',
|
|
@@ -23,18 +33,25 @@ const rule: ESLintUtils.RuleModule<'noLegacyServiceTyping', [typeof DISALLOWED_S
|
|
|
23
33
|
description: 'Legacy service typings should not be used.',
|
|
24
34
|
},
|
|
25
35
|
messages: {
|
|
26
|
-
noLegacyServiceTyping:
|
|
36
|
+
noLegacyServiceTyping:
|
|
37
|
+
'Please remove the usage of legacy service typings.',
|
|
27
38
|
},
|
|
28
|
-
|
|
39
|
+
|
|
40
|
+
schema: [
|
|
41
|
+
{
|
|
42
|
+
type: 'array',
|
|
43
|
+
items: { type: 'string' },
|
|
44
|
+
description: 'Legacy service typings should not be used.',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
defaultOptions: [DISALLOWED_SERVICE_TYPINGS],
|
|
29
48
|
},
|
|
30
|
-
defaultOptions: [DISALLOWED_SERVICE_TYPINGS],
|
|
31
49
|
create(context) {
|
|
32
50
|
return {
|
|
33
51
|
TSTypeReference: (typeReference: TSESTree.TSTypeReference) => {
|
|
34
52
|
if (
|
|
35
53
|
typeReference.typeName.type === AST_NODE_TYPES.Identifier &&
|
|
36
|
-
|
|
37
|
-
(context.options[0] ?? DISALLOWED_SERVICE_TYPINGS).includes(typeReference.typeName.name)
|
|
54
|
+
context.options[0].includes(typeReference.typeName.name)
|
|
38
55
|
) {
|
|
39
56
|
context.report({
|
|
40
57
|
messageId: 'noLegacyServiceTyping',
|