eslint-cdk-plugin 3.3.0 → 3.4.1
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/index.cjs +213 -37
- package/dist/index.mjs +213 -37
- package/package.json +6 -8
- package/src/constants/tsInternalFlags.ts +4 -0
- package/src/rules/construct-constructor-property.ts +1 -1
- package/src/rules/no-construct-in-interface.ts +57 -17
- package/src/rules/no-construct-in-public-property-of-construct.ts +69 -27
- package/src/rules/no-construct-stack-suffix.ts +1 -1
- package/src/rules/no-mutable-public-property-of-construct.ts +1 -1
- package/src/rules/no-parent-name-construct-id-match.ts +4 -1
- package/src/rules/no-unused-props.ts +1 -1
- package/src/rules/no-variable-construct-id.ts +1 -1
- package/src/rules/pascal-case-construct-id.ts +1 -1
- package/src/rules/props-name-convention.ts +1 -1
- package/src/rules/require-jsdoc.ts +1 -1
- package/src/rules/require-passing-this.ts +1 -1
- package/src/utils/getArrayElementType.ts +14 -0
- package/src/utils/getGenericTypeArgument.ts +47 -0
- package/src/utils/getPropertyNames.ts +5 -32
- package/src/utils/is-resource-with-readonly-interface.ts +165 -0
- package/src/utils/{typeCheck.ts → typecheck/cdk.ts} +9 -1
- package/src/utils/typecheck/ts-node.ts +42 -0
- package/src/utils/typecheck/ts-type.ts +27 -0
package/dist/index.cjs
CHANGED
|
@@ -26,7 +26,7 @@ function _interopNamespaceDefault(e) {
|
|
|
26
26
|
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
27
27
|
|
|
28
28
|
var name = "eslint-cdk-plugin";
|
|
29
|
-
var version = "3.
|
|
29
|
+
var version = "3.4.1";
|
|
30
30
|
|
|
31
31
|
const createRule = utils.ESLintUtils.RuleCreator(
|
|
32
32
|
(name) => `https://eslint-cdk-plugin.dev/rules/${name}`
|
|
@@ -55,6 +55,10 @@ const isConstructType = (type, ignoredClasses = [
|
|
|
55
55
|
if (ignoredClasses.includes(type.symbol?.name ?? "")) return false;
|
|
56
56
|
return isTargetSuperClassType(type, ["Construct"], isConstructType);
|
|
57
57
|
};
|
|
58
|
+
const isResourceType = (type, ignoredClasses = []) => {
|
|
59
|
+
if (ignoredClasses.includes(type.symbol?.name ?? "")) return false;
|
|
60
|
+
return isTargetSuperClassType(type, ["Resource"], isResourceType);
|
|
61
|
+
};
|
|
58
62
|
const isTargetSuperClassType = (type, targetSuperClasses, typeCheckFunction) => {
|
|
59
63
|
if (!type.symbol) return false;
|
|
60
64
|
if (targetSuperClasses.some((suffix) => type.symbol.name === suffix)) {
|
|
@@ -140,11 +144,138 @@ const validateConstructorProperty = (constructor, context, parserServices) => {
|
|
|
140
144
|
};
|
|
141
145
|
|
|
142
146
|
const SYMBOL_FLAGS = {
|
|
143
|
-
CLASS: 32
|
|
144
|
-
};
|
|
147
|
+
CLASS: 32};
|
|
145
148
|
const SYNTAX_KIND = {
|
|
146
149
|
CLASS_DECLARATION: 263,
|
|
147
|
-
CONSTRUCTOR: 176
|
|
150
|
+
CONSTRUCTOR: 176,
|
|
151
|
+
IMPLEMENTS_KEYWORD: 119,
|
|
152
|
+
IDENTIFIER: 80,
|
|
153
|
+
PROPERTY_ACCESS_EXPRESSION: 211
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const getSymbol = (type) => {
|
|
157
|
+
return type.getSymbol?.() ?? type.symbol;
|
|
158
|
+
};
|
|
159
|
+
const isClassType = (type) => {
|
|
160
|
+
return getSymbol(type)?.flags === SYMBOL_FLAGS.CLASS;
|
|
161
|
+
};
|
|
162
|
+
const isArrayType = (type) => {
|
|
163
|
+
const symbol = getSymbol(type);
|
|
164
|
+
if (symbol?.name === "Array") return true;
|
|
165
|
+
if ("target" in type && type.target) {
|
|
166
|
+
const targetSymbol = getSymbol(type.target);
|
|
167
|
+
return targetSymbol?.name === "Array";
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const getArrayElementType = (type) => {
|
|
173
|
+
if (!isArrayType(type)) return void 0;
|
|
174
|
+
if ("typeArguments" in type && Array.isArray(type.typeArguments)) {
|
|
175
|
+
return type.typeArguments[0];
|
|
176
|
+
}
|
|
177
|
+
return void 0;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const getGenericTypeArgument = (type) => {
|
|
181
|
+
if ("aliasSymbol" in type && type.aliasSymbol && "aliasTypeArguments" in type && type.aliasTypeArguments?.length) {
|
|
182
|
+
return type.aliasTypeArguments[0];
|
|
183
|
+
}
|
|
184
|
+
if ("typeArguments" in type && Array.isArray(type.typeArguments) && type.typeArguments?.length) {
|
|
185
|
+
return type.typeArguments[0];
|
|
186
|
+
}
|
|
187
|
+
if ("target" in type && type.target && "typeArguments" in type && Array.isArray(type.typeArguments) && type.typeArguments?.length) {
|
|
188
|
+
return type.typeArguments[0];
|
|
189
|
+
}
|
|
190
|
+
if ("modifiersType" in type && type.modifiersType) {
|
|
191
|
+
return type.modifiersType;
|
|
192
|
+
}
|
|
193
|
+
return void 0;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const isClassDeclaration = (node) => {
|
|
197
|
+
return node.kind === SYNTAX_KIND.CLASS_DECLARATION;
|
|
198
|
+
};
|
|
199
|
+
const isIdentifier = (node) => {
|
|
200
|
+
return node.kind === SYNTAX_KIND.IDENTIFIER;
|
|
201
|
+
};
|
|
202
|
+
const isPropertyAccessExpression = (node) => {
|
|
203
|
+
return node.kind === SYNTAX_KIND.PROPERTY_ACCESS_EXPRESSION;
|
|
204
|
+
};
|
|
205
|
+
const checkHeritageClauseIsImplements = (node) => {
|
|
206
|
+
return node.token === SYNTAX_KIND.IMPLEMENTS_KEYWORD;
|
|
207
|
+
};
|
|
208
|
+
const isConstructorDeclaration = (node) => {
|
|
209
|
+
return node.kind === SYNTAX_KIND.CONSTRUCTOR;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const isResourceWithReadonlyInterface = (type) => {
|
|
213
|
+
if (!isResourceType(type) || !type.symbol?.name) return false;
|
|
214
|
+
if (isIgnoreClass(type.symbol.name)) return false;
|
|
215
|
+
return hasMatchingInterfaceInHierarchy(type);
|
|
216
|
+
};
|
|
217
|
+
const hasMatchingInterfaceInHierarchy = (type) => {
|
|
218
|
+
const processedTypes = /* @__PURE__ */ new Set();
|
|
219
|
+
const checkTypeAndBases = (currentType) => {
|
|
220
|
+
const symbol = currentType.getSymbol?.() ?? currentType.symbol;
|
|
221
|
+
if (!symbol?.name) return false;
|
|
222
|
+
if (processedTypes.has(symbol.name)) return false;
|
|
223
|
+
processedTypes.add(symbol.name);
|
|
224
|
+
const currentClassName = symbol.name;
|
|
225
|
+
if (isIgnoreClass(currentClassName)) return false;
|
|
226
|
+
const directInterfaces = getDirectImplementedInterfaceNames(currentType);
|
|
227
|
+
if (directInterfaces.some(
|
|
228
|
+
(interfaceName) => checkInterfaceMatchClassName(interfaceName, currentClassName)
|
|
229
|
+
)) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
const baseTypes = currentType.getBaseTypes?.() ?? [];
|
|
233
|
+
return baseTypes.some(
|
|
234
|
+
(baseType) => isClassType(baseType) && checkTypeAndBases(baseType)
|
|
235
|
+
);
|
|
236
|
+
};
|
|
237
|
+
return checkTypeAndBases(type);
|
|
238
|
+
};
|
|
239
|
+
const checkInterfaceMatchClassName = (interfaceName, classname) => {
|
|
240
|
+
const simpleInterfaceName = interfaceName.includes(".") ? interfaceName.split(".").pop() ?? interfaceName : interfaceName;
|
|
241
|
+
if (simpleInterfaceName === `I${classname}`) return true;
|
|
242
|
+
const classNameWithoutBase = classname.replace(/^Base|Base$/g, "");
|
|
243
|
+
if (classNameWithoutBase && simpleInterfaceName === `I${classNameWithoutBase}`) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
const baseVMatch = /^(.+)BaseV(\d+)$/.exec(classname);
|
|
247
|
+
if (!baseVMatch) return false;
|
|
248
|
+
const [, baseName, version] = baseVMatch;
|
|
249
|
+
return simpleInterfaceName === `I${baseName}V${version}`;
|
|
250
|
+
};
|
|
251
|
+
const getDirectImplementedInterfaceNames = (type) => {
|
|
252
|
+
const symbol = type.getSymbol?.() ?? type.symbol;
|
|
253
|
+
if (!symbol?.name) return [];
|
|
254
|
+
const declarations = symbol.getDeclarations ? symbol.getDeclarations() : symbol.declarations;
|
|
255
|
+
if (!declarations?.length) return [];
|
|
256
|
+
return declarations.reduce((acc, decl) => {
|
|
257
|
+
if (!isClassDeclaration(decl)) return acc;
|
|
258
|
+
const heritageClauses = decl.heritageClauses;
|
|
259
|
+
if (!heritageClauses) return acc;
|
|
260
|
+
return heritageClauses.reduce((hcAcc, hc) => {
|
|
261
|
+
if (!checkHeritageClauseIsImplements(hc)) return hcAcc;
|
|
262
|
+
return hc.types.reduce((typeAcc, type2) => {
|
|
263
|
+
const expression = type2.expression;
|
|
264
|
+
if (!expression) return typeAcc;
|
|
265
|
+
if (isIdentifier(expression)) return [...typeAcc, expression.text];
|
|
266
|
+
if (!isPropertyAccessExpression(expression)) return typeAcc;
|
|
267
|
+
const namespace = expression.expression;
|
|
268
|
+
const interfaceName = expression.name;
|
|
269
|
+
if (isIdentifier(namespace) && isIdentifier(interfaceName)) {
|
|
270
|
+
return [...typeAcc, `${namespace.text}.${interfaceName.text}`];
|
|
271
|
+
}
|
|
272
|
+
return typeAcc;
|
|
273
|
+
}, []);
|
|
274
|
+
}, []);
|
|
275
|
+
}, []);
|
|
276
|
+
};
|
|
277
|
+
const isIgnoreClass = (className) => {
|
|
278
|
+
return className === "Resource" || className === "Construct";
|
|
148
279
|
};
|
|
149
280
|
|
|
150
281
|
const noConstructInInterface = createRule({
|
|
@@ -169,17 +300,45 @@ const noConstructInInterface = createRule({
|
|
|
169
300
|
continue;
|
|
170
301
|
}
|
|
171
302
|
const type = parserServices.getTypeAtLocation(property);
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
303
|
+
if (isClassType(type) && isResourceWithReadonlyInterface(type)) {
|
|
304
|
+
context.report({
|
|
305
|
+
node: property,
|
|
306
|
+
messageId: "invalidInterfaceProperty",
|
|
307
|
+
data: {
|
|
308
|
+
propertyName: property.key.name,
|
|
309
|
+
typeName: type.symbol.name
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const elementType = getArrayElementType(type);
|
|
315
|
+
if (elementType && isClassType(elementType) && isResourceWithReadonlyInterface(elementType)) {
|
|
316
|
+
context.report({
|
|
317
|
+
node: property,
|
|
318
|
+
messageId: "invalidInterfaceProperty",
|
|
319
|
+
data: {
|
|
320
|
+
propertyName: property.key.name,
|
|
321
|
+
typeName: `${elementType.symbol.name}[]`
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const genericArgument = getGenericTypeArgument(type);
|
|
327
|
+
if (genericArgument && isClassType(genericArgument) && isResourceWithReadonlyInterface(genericArgument)) {
|
|
328
|
+
const wrapperName = (() => {
|
|
329
|
+
if (type.aliasSymbol) return type.aliasSymbol.name;
|
|
330
|
+
if (type.symbol?.name) return type.symbol.name;
|
|
331
|
+
return void 0;
|
|
332
|
+
})();
|
|
333
|
+
context.report({
|
|
334
|
+
node: property,
|
|
335
|
+
messageId: "invalidInterfaceProperty",
|
|
336
|
+
data: {
|
|
337
|
+
propertyName: property.key.name,
|
|
338
|
+
typeName: wrapperName ? `${wrapperName}<${genericArgument.symbol.name}>` : genericArgument.symbol.name
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
183
342
|
}
|
|
184
343
|
}
|
|
185
344
|
};
|
|
@@ -229,17 +388,7 @@ const validatePublicPropertyOfConstruct = (node, context, parserServices) => {
|
|
|
229
388
|
}
|
|
230
389
|
if (!property.typeAnnotation) continue;
|
|
231
390
|
const type = parserServices.getTypeAtLocation(property);
|
|
232
|
-
|
|
233
|
-
const isClass = type.symbol.flags === SYMBOL_FLAGS.CLASS;
|
|
234
|
-
if (!isClass) continue;
|
|
235
|
-
context.report({
|
|
236
|
-
node: property,
|
|
237
|
-
messageId: "invalidPublicPropertyOfConstruct",
|
|
238
|
-
data: {
|
|
239
|
-
propertyName: property.key.name,
|
|
240
|
-
typeName: type.symbol.name
|
|
241
|
-
}
|
|
242
|
-
});
|
|
391
|
+
checkAndReportConstructType(type, property, property.key.name, context);
|
|
243
392
|
}
|
|
244
393
|
};
|
|
245
394
|
const validateConstructorParameterProperty = (constructor, context, parserServices) => {
|
|
@@ -252,17 +401,50 @@ const validateConstructorParameterProperty = (constructor, context, parserServic
|
|
|
252
401
|
}
|
|
253
402
|
if (!param.parameter.typeAnnotation) continue;
|
|
254
403
|
const type = parserServices.getTypeAtLocation(param);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
404
|
+
checkAndReportConstructType(type, param, param.parameter.name, context);
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
const checkAndReportConstructType = (type, node, propertyName, context) => {
|
|
408
|
+
if (isClassType(type) && isResourceWithReadonlyInterface(type)) {
|
|
258
409
|
context.report({
|
|
259
|
-
node
|
|
410
|
+
node,
|
|
260
411
|
messageId: "invalidPublicPropertyOfConstruct",
|
|
261
412
|
data: {
|
|
262
|
-
propertyName
|
|
413
|
+
propertyName,
|
|
263
414
|
typeName: type.symbol.name
|
|
264
415
|
}
|
|
265
416
|
});
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const elementType = getArrayElementType(type);
|
|
420
|
+
if (elementType && isClassType(elementType) && isResourceWithReadonlyInterface(elementType)) {
|
|
421
|
+
context.report({
|
|
422
|
+
node,
|
|
423
|
+
messageId: "invalidPublicPropertyOfConstruct",
|
|
424
|
+
data: {
|
|
425
|
+
propertyName,
|
|
426
|
+
typeName: `${elementType.symbol.name}[]`
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const genericArgument = getGenericTypeArgument(type);
|
|
432
|
+
if (genericArgument && isClassType(genericArgument) && isResourceWithReadonlyInterface(genericArgument)) {
|
|
433
|
+
const wrapperName = (() => {
|
|
434
|
+
if ("aliasSymbol" in type && type.aliasSymbol) {
|
|
435
|
+
return type.aliasSymbol.name;
|
|
436
|
+
}
|
|
437
|
+
if (type.symbol?.name) return type.symbol.name;
|
|
438
|
+
return void 0;
|
|
439
|
+
})();
|
|
440
|
+
context.report({
|
|
441
|
+
node,
|
|
442
|
+
messageId: "invalidPublicPropertyOfConstruct",
|
|
443
|
+
data: {
|
|
444
|
+
propertyName,
|
|
445
|
+
typeName: wrapperName ? `${wrapperName}<${genericArgument.symbol.name}>` : genericArgument.symbol.name
|
|
446
|
+
}
|
|
447
|
+
});
|
|
266
448
|
}
|
|
267
449
|
};
|
|
268
450
|
|
|
@@ -291,12 +473,6 @@ const getConstructorPropertyNames = (type) => {
|
|
|
291
473
|
if (!constructor?.parameters.length) return [];
|
|
292
474
|
return constructor.parameters.map((param) => param.name.getText());
|
|
293
475
|
};
|
|
294
|
-
const isClassDeclaration = (declaration) => {
|
|
295
|
-
return declaration.kind === SYNTAX_KIND.CLASS_DECLARATION;
|
|
296
|
-
};
|
|
297
|
-
const isConstructorDeclaration = (node) => {
|
|
298
|
-
return node.kind === SYNTAX_KIND.CONSTRUCTOR;
|
|
299
|
-
};
|
|
300
476
|
|
|
301
477
|
const SUFFIX_TYPE = {
|
|
302
478
|
CONSTRUCT: "Construct",
|
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { ESLintUtils, AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
|
|
5
5
|
var name = "eslint-cdk-plugin";
|
|
6
|
-
var version = "3.
|
|
6
|
+
var version = "3.4.1";
|
|
7
7
|
|
|
8
8
|
const createRule = ESLintUtils.RuleCreator(
|
|
9
9
|
(name) => `https://eslint-cdk-plugin.dev/rules/${name}`
|
|
@@ -32,6 +32,10 @@ const isConstructType = (type, ignoredClasses = [
|
|
|
32
32
|
if (ignoredClasses.includes(type.symbol?.name ?? "")) return false;
|
|
33
33
|
return isTargetSuperClassType(type, ["Construct"], isConstructType);
|
|
34
34
|
};
|
|
35
|
+
const isResourceType = (type, ignoredClasses = []) => {
|
|
36
|
+
if (ignoredClasses.includes(type.symbol?.name ?? "")) return false;
|
|
37
|
+
return isTargetSuperClassType(type, ["Resource"], isResourceType);
|
|
38
|
+
};
|
|
35
39
|
const isTargetSuperClassType = (type, targetSuperClasses, typeCheckFunction) => {
|
|
36
40
|
if (!type.symbol) return false;
|
|
37
41
|
if (targetSuperClasses.some((suffix) => type.symbol.name === suffix)) {
|
|
@@ -117,11 +121,138 @@ const validateConstructorProperty = (constructor, context, parserServices) => {
|
|
|
117
121
|
};
|
|
118
122
|
|
|
119
123
|
const SYMBOL_FLAGS = {
|
|
120
|
-
CLASS: 32
|
|
121
|
-
};
|
|
124
|
+
CLASS: 32};
|
|
122
125
|
const SYNTAX_KIND = {
|
|
123
126
|
CLASS_DECLARATION: 263,
|
|
124
|
-
CONSTRUCTOR: 176
|
|
127
|
+
CONSTRUCTOR: 176,
|
|
128
|
+
IMPLEMENTS_KEYWORD: 119,
|
|
129
|
+
IDENTIFIER: 80,
|
|
130
|
+
PROPERTY_ACCESS_EXPRESSION: 211
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const getSymbol = (type) => {
|
|
134
|
+
return type.getSymbol?.() ?? type.symbol;
|
|
135
|
+
};
|
|
136
|
+
const isClassType = (type) => {
|
|
137
|
+
return getSymbol(type)?.flags === SYMBOL_FLAGS.CLASS;
|
|
138
|
+
};
|
|
139
|
+
const isArrayType = (type) => {
|
|
140
|
+
const symbol = getSymbol(type);
|
|
141
|
+
if (symbol?.name === "Array") return true;
|
|
142
|
+
if ("target" in type && type.target) {
|
|
143
|
+
const targetSymbol = getSymbol(type.target);
|
|
144
|
+
return targetSymbol?.name === "Array";
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const getArrayElementType = (type) => {
|
|
150
|
+
if (!isArrayType(type)) return void 0;
|
|
151
|
+
if ("typeArguments" in type && Array.isArray(type.typeArguments)) {
|
|
152
|
+
return type.typeArguments[0];
|
|
153
|
+
}
|
|
154
|
+
return void 0;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const getGenericTypeArgument = (type) => {
|
|
158
|
+
if ("aliasSymbol" in type && type.aliasSymbol && "aliasTypeArguments" in type && type.aliasTypeArguments?.length) {
|
|
159
|
+
return type.aliasTypeArguments[0];
|
|
160
|
+
}
|
|
161
|
+
if ("typeArguments" in type && Array.isArray(type.typeArguments) && type.typeArguments?.length) {
|
|
162
|
+
return type.typeArguments[0];
|
|
163
|
+
}
|
|
164
|
+
if ("target" in type && type.target && "typeArguments" in type && Array.isArray(type.typeArguments) && type.typeArguments?.length) {
|
|
165
|
+
return type.typeArguments[0];
|
|
166
|
+
}
|
|
167
|
+
if ("modifiersType" in type && type.modifiersType) {
|
|
168
|
+
return type.modifiersType;
|
|
169
|
+
}
|
|
170
|
+
return void 0;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const isClassDeclaration = (node) => {
|
|
174
|
+
return node.kind === SYNTAX_KIND.CLASS_DECLARATION;
|
|
175
|
+
};
|
|
176
|
+
const isIdentifier = (node) => {
|
|
177
|
+
return node.kind === SYNTAX_KIND.IDENTIFIER;
|
|
178
|
+
};
|
|
179
|
+
const isPropertyAccessExpression = (node) => {
|
|
180
|
+
return node.kind === SYNTAX_KIND.PROPERTY_ACCESS_EXPRESSION;
|
|
181
|
+
};
|
|
182
|
+
const checkHeritageClauseIsImplements = (node) => {
|
|
183
|
+
return node.token === SYNTAX_KIND.IMPLEMENTS_KEYWORD;
|
|
184
|
+
};
|
|
185
|
+
const isConstructorDeclaration = (node) => {
|
|
186
|
+
return node.kind === SYNTAX_KIND.CONSTRUCTOR;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const isResourceWithReadonlyInterface = (type) => {
|
|
190
|
+
if (!isResourceType(type) || !type.symbol?.name) return false;
|
|
191
|
+
if (isIgnoreClass(type.symbol.name)) return false;
|
|
192
|
+
return hasMatchingInterfaceInHierarchy(type);
|
|
193
|
+
};
|
|
194
|
+
const hasMatchingInterfaceInHierarchy = (type) => {
|
|
195
|
+
const processedTypes = /* @__PURE__ */ new Set();
|
|
196
|
+
const checkTypeAndBases = (currentType) => {
|
|
197
|
+
const symbol = currentType.getSymbol?.() ?? currentType.symbol;
|
|
198
|
+
if (!symbol?.name) return false;
|
|
199
|
+
if (processedTypes.has(symbol.name)) return false;
|
|
200
|
+
processedTypes.add(symbol.name);
|
|
201
|
+
const currentClassName = symbol.name;
|
|
202
|
+
if (isIgnoreClass(currentClassName)) return false;
|
|
203
|
+
const directInterfaces = getDirectImplementedInterfaceNames(currentType);
|
|
204
|
+
if (directInterfaces.some(
|
|
205
|
+
(interfaceName) => checkInterfaceMatchClassName(interfaceName, currentClassName)
|
|
206
|
+
)) {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
const baseTypes = currentType.getBaseTypes?.() ?? [];
|
|
210
|
+
return baseTypes.some(
|
|
211
|
+
(baseType) => isClassType(baseType) && checkTypeAndBases(baseType)
|
|
212
|
+
);
|
|
213
|
+
};
|
|
214
|
+
return checkTypeAndBases(type);
|
|
215
|
+
};
|
|
216
|
+
const checkInterfaceMatchClassName = (interfaceName, classname) => {
|
|
217
|
+
const simpleInterfaceName = interfaceName.includes(".") ? interfaceName.split(".").pop() ?? interfaceName : interfaceName;
|
|
218
|
+
if (simpleInterfaceName === `I${classname}`) return true;
|
|
219
|
+
const classNameWithoutBase = classname.replace(/^Base|Base$/g, "");
|
|
220
|
+
if (classNameWithoutBase && simpleInterfaceName === `I${classNameWithoutBase}`) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
const baseVMatch = /^(.+)BaseV(\d+)$/.exec(classname);
|
|
224
|
+
if (!baseVMatch) return false;
|
|
225
|
+
const [, baseName, version] = baseVMatch;
|
|
226
|
+
return simpleInterfaceName === `I${baseName}V${version}`;
|
|
227
|
+
};
|
|
228
|
+
const getDirectImplementedInterfaceNames = (type) => {
|
|
229
|
+
const symbol = type.getSymbol?.() ?? type.symbol;
|
|
230
|
+
if (!symbol?.name) return [];
|
|
231
|
+
const declarations = symbol.getDeclarations ? symbol.getDeclarations() : symbol.declarations;
|
|
232
|
+
if (!declarations?.length) return [];
|
|
233
|
+
return declarations.reduce((acc, decl) => {
|
|
234
|
+
if (!isClassDeclaration(decl)) return acc;
|
|
235
|
+
const heritageClauses = decl.heritageClauses;
|
|
236
|
+
if (!heritageClauses) return acc;
|
|
237
|
+
return heritageClauses.reduce((hcAcc, hc) => {
|
|
238
|
+
if (!checkHeritageClauseIsImplements(hc)) return hcAcc;
|
|
239
|
+
return hc.types.reduce((typeAcc, type2) => {
|
|
240
|
+
const expression = type2.expression;
|
|
241
|
+
if (!expression) return typeAcc;
|
|
242
|
+
if (isIdentifier(expression)) return [...typeAcc, expression.text];
|
|
243
|
+
if (!isPropertyAccessExpression(expression)) return typeAcc;
|
|
244
|
+
const namespace = expression.expression;
|
|
245
|
+
const interfaceName = expression.name;
|
|
246
|
+
if (isIdentifier(namespace) && isIdentifier(interfaceName)) {
|
|
247
|
+
return [...typeAcc, `${namespace.text}.${interfaceName.text}`];
|
|
248
|
+
}
|
|
249
|
+
return typeAcc;
|
|
250
|
+
}, []);
|
|
251
|
+
}, []);
|
|
252
|
+
}, []);
|
|
253
|
+
};
|
|
254
|
+
const isIgnoreClass = (className) => {
|
|
255
|
+
return className === "Resource" || className === "Construct";
|
|
125
256
|
};
|
|
126
257
|
|
|
127
258
|
const noConstructInInterface = createRule({
|
|
@@ -146,17 +277,45 @@ const noConstructInInterface = createRule({
|
|
|
146
277
|
continue;
|
|
147
278
|
}
|
|
148
279
|
const type = parserServices.getTypeAtLocation(property);
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
280
|
+
if (isClassType(type) && isResourceWithReadonlyInterface(type)) {
|
|
281
|
+
context.report({
|
|
282
|
+
node: property,
|
|
283
|
+
messageId: "invalidInterfaceProperty",
|
|
284
|
+
data: {
|
|
285
|
+
propertyName: property.key.name,
|
|
286
|
+
typeName: type.symbol.name
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const elementType = getArrayElementType(type);
|
|
292
|
+
if (elementType && isClassType(elementType) && isResourceWithReadonlyInterface(elementType)) {
|
|
293
|
+
context.report({
|
|
294
|
+
node: property,
|
|
295
|
+
messageId: "invalidInterfaceProperty",
|
|
296
|
+
data: {
|
|
297
|
+
propertyName: property.key.name,
|
|
298
|
+
typeName: `${elementType.symbol.name}[]`
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const genericArgument = getGenericTypeArgument(type);
|
|
304
|
+
if (genericArgument && isClassType(genericArgument) && isResourceWithReadonlyInterface(genericArgument)) {
|
|
305
|
+
const wrapperName = (() => {
|
|
306
|
+
if (type.aliasSymbol) return type.aliasSymbol.name;
|
|
307
|
+
if (type.symbol?.name) return type.symbol.name;
|
|
308
|
+
return void 0;
|
|
309
|
+
})();
|
|
310
|
+
context.report({
|
|
311
|
+
node: property,
|
|
312
|
+
messageId: "invalidInterfaceProperty",
|
|
313
|
+
data: {
|
|
314
|
+
propertyName: property.key.name,
|
|
315
|
+
typeName: wrapperName ? `${wrapperName}<${genericArgument.symbol.name}>` : genericArgument.symbol.name
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
160
319
|
}
|
|
161
320
|
}
|
|
162
321
|
};
|
|
@@ -206,17 +365,7 @@ const validatePublicPropertyOfConstruct = (node, context, parserServices) => {
|
|
|
206
365
|
}
|
|
207
366
|
if (!property.typeAnnotation) continue;
|
|
208
367
|
const type = parserServices.getTypeAtLocation(property);
|
|
209
|
-
|
|
210
|
-
const isClass = type.symbol.flags === SYMBOL_FLAGS.CLASS;
|
|
211
|
-
if (!isClass) continue;
|
|
212
|
-
context.report({
|
|
213
|
-
node: property,
|
|
214
|
-
messageId: "invalidPublicPropertyOfConstruct",
|
|
215
|
-
data: {
|
|
216
|
-
propertyName: property.key.name,
|
|
217
|
-
typeName: type.symbol.name
|
|
218
|
-
}
|
|
219
|
-
});
|
|
368
|
+
checkAndReportConstructType(type, property, property.key.name, context);
|
|
220
369
|
}
|
|
221
370
|
};
|
|
222
371
|
const validateConstructorParameterProperty = (constructor, context, parserServices) => {
|
|
@@ -229,17 +378,50 @@ const validateConstructorParameterProperty = (constructor, context, parserServic
|
|
|
229
378
|
}
|
|
230
379
|
if (!param.parameter.typeAnnotation) continue;
|
|
231
380
|
const type = parserServices.getTypeAtLocation(param);
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
381
|
+
checkAndReportConstructType(type, param, param.parameter.name, context);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
const checkAndReportConstructType = (type, node, propertyName, context) => {
|
|
385
|
+
if (isClassType(type) && isResourceWithReadonlyInterface(type)) {
|
|
235
386
|
context.report({
|
|
236
|
-
node
|
|
387
|
+
node,
|
|
237
388
|
messageId: "invalidPublicPropertyOfConstruct",
|
|
238
389
|
data: {
|
|
239
|
-
propertyName
|
|
390
|
+
propertyName,
|
|
240
391
|
typeName: type.symbol.name
|
|
241
392
|
}
|
|
242
393
|
});
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const elementType = getArrayElementType(type);
|
|
397
|
+
if (elementType && isClassType(elementType) && isResourceWithReadonlyInterface(elementType)) {
|
|
398
|
+
context.report({
|
|
399
|
+
node,
|
|
400
|
+
messageId: "invalidPublicPropertyOfConstruct",
|
|
401
|
+
data: {
|
|
402
|
+
propertyName,
|
|
403
|
+
typeName: `${elementType.symbol.name}[]`
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const genericArgument = getGenericTypeArgument(type);
|
|
409
|
+
if (genericArgument && isClassType(genericArgument) && isResourceWithReadonlyInterface(genericArgument)) {
|
|
410
|
+
const wrapperName = (() => {
|
|
411
|
+
if ("aliasSymbol" in type && type.aliasSymbol) {
|
|
412
|
+
return type.aliasSymbol.name;
|
|
413
|
+
}
|
|
414
|
+
if (type.symbol?.name) return type.symbol.name;
|
|
415
|
+
return void 0;
|
|
416
|
+
})();
|
|
417
|
+
context.report({
|
|
418
|
+
node,
|
|
419
|
+
messageId: "invalidPublicPropertyOfConstruct",
|
|
420
|
+
data: {
|
|
421
|
+
propertyName,
|
|
422
|
+
typeName: wrapperName ? `${wrapperName}<${genericArgument.symbol.name}>` : genericArgument.symbol.name
|
|
423
|
+
}
|
|
424
|
+
});
|
|
243
425
|
}
|
|
244
426
|
};
|
|
245
427
|
|
|
@@ -268,12 +450,6 @@ const getConstructorPropertyNames = (type) => {
|
|
|
268
450
|
if (!constructor?.parameters.length) return [];
|
|
269
451
|
return constructor.parameters.map((param) => param.name.getText());
|
|
270
452
|
};
|
|
271
|
-
const isClassDeclaration = (declaration) => {
|
|
272
|
-
return declaration.kind === SYNTAX_KIND.CLASS_DECLARATION;
|
|
273
|
-
};
|
|
274
|
-
const isConstructorDeclaration = (node) => {
|
|
275
|
-
return node.kind === SYNTAX_KIND.CONSTRUCTOR;
|
|
276
|
-
};
|
|
277
453
|
|
|
278
454
|
const SUFFIX_TYPE = {
|
|
279
455
|
CONSTRUCT: "Construct",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-cdk-plugin",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"description": "eslint plugin for AWS CDK projects",
|
|
5
5
|
"main": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -22,24 +22,22 @@
|
|
|
22
22
|
"lint": "eslint --fix --config eslint.config.js",
|
|
23
23
|
"check": "tsc --noEmit",
|
|
24
24
|
"pack": "pnpm run build && npm pack",
|
|
25
|
-
"release:minor": "standard-version --release-as minor",
|
|
26
|
-
"release:major": "standard-version --release-as major",
|
|
27
|
-
"release:patch": "standard-version --release-as patch",
|
|
28
25
|
"docs:dev": "cd ./docs && pnpm install && pnpm run dev",
|
|
29
26
|
"docs:build": "cd ./docs && pnpm install && pnpm run build",
|
|
30
27
|
"docs:preview": "cd ./docs && pnpm install && pnpm run preview"
|
|
31
28
|
},
|
|
32
29
|
"devDependencies": {
|
|
33
30
|
"@eslint/js": "^9.26.0",
|
|
31
|
+
"@secretlint/secretlint-rule-preset-recommend": "^11.2.4",
|
|
34
32
|
"@types/node": "^22.15.0",
|
|
35
33
|
"@typescript-eslint/rule-tester": "^8.32.1",
|
|
36
|
-
"eslint": "9.
|
|
37
|
-
"eslint-plugin-import": "^2.
|
|
34
|
+
"eslint": "9.36.0",
|
|
35
|
+
"eslint-plugin-import": "^2.32.0",
|
|
38
36
|
"pkgroll": "^2.12.2",
|
|
39
|
-
"
|
|
37
|
+
"secretlint": "^11.2.4",
|
|
40
38
|
"typescript": "^5.8.3",
|
|
41
39
|
"typescript-eslint": "^8.32.1",
|
|
42
|
-
"vitest": "^3.
|
|
40
|
+
"vitest": "^3.2.4"
|
|
43
41
|
},
|
|
44
42
|
"dependencies": {
|
|
45
43
|
"@typescript-eslint/parser": "^8.32.1",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const SYMBOL_FLAGS = {
|
|
5
5
|
CLASS: 32,
|
|
6
|
+
Interface: 64,
|
|
6
7
|
} as const;
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -11,4 +12,7 @@ export const SYMBOL_FLAGS = {
|
|
|
11
12
|
export const SYNTAX_KIND = {
|
|
12
13
|
CLASS_DECLARATION: 263,
|
|
13
14
|
CONSTRUCTOR: 176,
|
|
15
|
+
IMPLEMENTS_KEYWORD: 119,
|
|
16
|
+
IDENTIFIER: 80,
|
|
17
|
+
PROPERTY_ACCESS_EXPRESSION: 211,
|
|
14
18
|
} as const;
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
|
|
9
9
|
import { createRule } from "../utils/createRule";
|
|
10
10
|
import { getConstructor } from "../utils/getConstructor";
|
|
11
|
-
import { isConstructType } from "../utils/
|
|
11
|
+
import { isConstructType } from "../utils/typecheck/cdk";
|
|
12
12
|
|
|
13
13
|
type Context = TSESLint.RuleContext<
|
|
14
14
|
| "invalidConstructorProperty"
|