eslint-cdk-plugin 3.4.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 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.4.0";
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}`
@@ -153,6 +153,46 @@ const SYNTAX_KIND = {
153
153
  PROPERTY_ACCESS_EXPRESSION: 211
154
154
  };
155
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
+
156
196
  const isClassDeclaration = (node) => {
157
197
  return node.kind === SYNTAX_KIND.CLASS_DECLARATION;
158
198
  };
@@ -169,13 +209,6 @@ const isConstructorDeclaration = (node) => {
169
209
  return node.kind === SYNTAX_KIND.CONSTRUCTOR;
170
210
  };
171
211
 
172
- const getSymbol = (type) => {
173
- return type.getSymbol?.() ?? type.symbol;
174
- };
175
- const isClassType = (type) => {
176
- return getSymbol(type)?.flags === SYMBOL_FLAGS.CLASS;
177
- };
178
-
179
212
  const isResourceWithReadonlyInterface = (type) => {
180
213
  if (!isResourceType(type) || !type.symbol?.name) return false;
181
214
  if (isIgnoreClass(type.symbol.name)) return false;
@@ -267,17 +300,45 @@ const noConstructInInterface = createRule({
267
300
  continue;
268
301
  }
269
302
  const type = parserServices.getTypeAtLocation(property);
270
- if (!isClassType(type) || !isResourceWithReadonlyInterface(type)) {
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
+ });
271
312
  continue;
272
313
  }
273
- context.report({
274
- node: property,
275
- messageId: "invalidInterfaceProperty",
276
- data: {
277
- propertyName: property.key.name,
278
- typeName: type.symbol.name
279
- }
280
- });
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
+ }
281
342
  }
282
343
  }
283
344
  };
@@ -327,15 +388,7 @@ const validatePublicPropertyOfConstruct = (node, context, parserServices) => {
327
388
  }
328
389
  if (!property.typeAnnotation) continue;
329
390
  const type = parserServices.getTypeAtLocation(property);
330
- if (!isClassType(type) || !isResourceWithReadonlyInterface(type)) continue;
331
- context.report({
332
- node: property,
333
- messageId: "invalidPublicPropertyOfConstruct",
334
- data: {
335
- propertyName: property.key.name,
336
- typeName: type.symbol.name
337
- }
338
- });
391
+ checkAndReportConstructType(type, property, property.key.name, context);
339
392
  }
340
393
  };
341
394
  const validateConstructorParameterProperty = (constructor, context, parserServices) => {
@@ -348,15 +401,50 @@ const validateConstructorParameterProperty = (constructor, context, parserServic
348
401
  }
349
402
  if (!param.parameter.typeAnnotation) continue;
350
403
  const type = parserServices.getTypeAtLocation(param);
351
- if (!isClassType(type) || !isResourceWithReadonlyInterface(type)) continue;
404
+ checkAndReportConstructType(type, param, param.parameter.name, context);
405
+ }
406
+ };
407
+ const checkAndReportConstructType = (type, node, propertyName, context) => {
408
+ if (isClassType(type) && isResourceWithReadonlyInterface(type)) {
352
409
  context.report({
353
- node: param,
410
+ node,
354
411
  messageId: "invalidPublicPropertyOfConstruct",
355
412
  data: {
356
- propertyName: param.parameter.name,
413
+ propertyName,
357
414
  typeName: type.symbol.name
358
415
  }
359
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
+ });
360
448
  }
361
449
  };
362
450
 
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.4.0";
6
+ var version = "3.4.1";
7
7
 
8
8
  const createRule = ESLintUtils.RuleCreator(
9
9
  (name) => `https://eslint-cdk-plugin.dev/rules/${name}`
@@ -130,6 +130,46 @@ const SYNTAX_KIND = {
130
130
  PROPERTY_ACCESS_EXPRESSION: 211
131
131
  };
132
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
+
133
173
  const isClassDeclaration = (node) => {
134
174
  return node.kind === SYNTAX_KIND.CLASS_DECLARATION;
135
175
  };
@@ -146,13 +186,6 @@ const isConstructorDeclaration = (node) => {
146
186
  return node.kind === SYNTAX_KIND.CONSTRUCTOR;
147
187
  };
148
188
 
149
- const getSymbol = (type) => {
150
- return type.getSymbol?.() ?? type.symbol;
151
- };
152
- const isClassType = (type) => {
153
- return getSymbol(type)?.flags === SYMBOL_FLAGS.CLASS;
154
- };
155
-
156
189
  const isResourceWithReadonlyInterface = (type) => {
157
190
  if (!isResourceType(type) || !type.symbol?.name) return false;
158
191
  if (isIgnoreClass(type.symbol.name)) return false;
@@ -244,17 +277,45 @@ const noConstructInInterface = createRule({
244
277
  continue;
245
278
  }
246
279
  const type = parserServices.getTypeAtLocation(property);
247
- if (!isClassType(type) || !isResourceWithReadonlyInterface(type)) {
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
+ });
248
289
  continue;
249
290
  }
250
- context.report({
251
- node: property,
252
- messageId: "invalidInterfaceProperty",
253
- data: {
254
- propertyName: property.key.name,
255
- typeName: type.symbol.name
256
- }
257
- });
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
+ }
258
319
  }
259
320
  }
260
321
  };
@@ -304,15 +365,7 @@ const validatePublicPropertyOfConstruct = (node, context, parserServices) => {
304
365
  }
305
366
  if (!property.typeAnnotation) continue;
306
367
  const type = parserServices.getTypeAtLocation(property);
307
- if (!isClassType(type) || !isResourceWithReadonlyInterface(type)) continue;
308
- context.report({
309
- node: property,
310
- messageId: "invalidPublicPropertyOfConstruct",
311
- data: {
312
- propertyName: property.key.name,
313
- typeName: type.symbol.name
314
- }
315
- });
368
+ checkAndReportConstructType(type, property, property.key.name, context);
316
369
  }
317
370
  };
318
371
  const validateConstructorParameterProperty = (constructor, context, parserServices) => {
@@ -325,15 +378,50 @@ const validateConstructorParameterProperty = (constructor, context, parserServic
325
378
  }
326
379
  if (!param.parameter.typeAnnotation) continue;
327
380
  const type = parserServices.getTypeAtLocation(param);
328
- if (!isClassType(type) || !isResourceWithReadonlyInterface(type)) continue;
381
+ checkAndReportConstructType(type, param, param.parameter.name, context);
382
+ }
383
+ };
384
+ const checkAndReportConstructType = (type, node, propertyName, context) => {
385
+ if (isClassType(type) && isResourceWithReadonlyInterface(type)) {
329
386
  context.report({
330
- node: param,
387
+ node,
331
388
  messageId: "invalidPublicPropertyOfConstruct",
332
389
  data: {
333
- propertyName: param.parameter.name,
390
+ propertyName,
334
391
  typeName: type.symbol.name
335
392
  }
336
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
+ });
337
425
  }
338
426
  };
339
427
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-cdk-plugin",
3
- "version": "3.4.0",
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.22.0",
37
- "eslint-plugin-import": "^2.31.0",
34
+ "eslint": "9.36.0",
35
+ "eslint-plugin-import": "^2.32.0",
38
36
  "pkgroll": "^2.12.2",
39
- "standard-version": "^9.5.0",
37
+ "secretlint": "^11.2.4",
40
38
  "typescript": "^5.8.3",
41
39
  "typescript-eslint": "^8.32.1",
42
- "vitest": "^3.1.3"
40
+ "vitest": "^3.2.4"
43
41
  },
44
42
  "dependencies": {
45
43
  "@typescript-eslint/parser": "^8.32.1",
@@ -1,6 +1,8 @@
1
1
  import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
2
2
 
3
3
  import { createRule } from "../utils/createRule";
4
+ import { getArrayElementType } from "../utils/getArrayElementType";
5
+ import { getGenericTypeArgument } from "../utils/getGenericTypeArgument";
4
6
  import { isResourceWithReadonlyInterface } from "../utils/is-resource-with-readonly-interface";
5
7
  import { isClassType } from "../utils/typecheck/ts-type";
6
8
 
@@ -36,18 +38,61 @@ export const noConstructInInterface = createRule({
36
38
  }
37
39
 
38
40
  const type = parserServices.getTypeAtLocation(property);
39
- if (!isClassType(type) || !isResourceWithReadonlyInterface(type)) {
41
+
42
+ // NOTE: Check if it's a direct class type
43
+ if (isClassType(type) && isResourceWithReadonlyInterface(type)) {
44
+ context.report({
45
+ node: property,
46
+ messageId: "invalidInterfaceProperty",
47
+ data: {
48
+ propertyName: property.key.name,
49
+ typeName: type.symbol.name,
50
+ },
51
+ });
40
52
  continue;
41
53
  }
42
54
 
43
- context.report({
44
- node: property,
45
- messageId: "invalidInterfaceProperty",
46
- data: {
47
- propertyName: property.key.name,
48
- typeName: type.symbol.name,
49
- },
50
- });
55
+ // NOTE: Check if it's an array of class types
56
+ const elementType = getArrayElementType(type);
57
+ if (
58
+ elementType &&
59
+ isClassType(elementType) &&
60
+ isResourceWithReadonlyInterface(elementType)
61
+ ) {
62
+ context.report({
63
+ node: property,
64
+ messageId: "invalidInterfaceProperty",
65
+ data: {
66
+ propertyName: property.key.name,
67
+ typeName: `${elementType.symbol.name}[]`,
68
+ },
69
+ });
70
+ continue;
71
+ }
72
+
73
+ // NOTE: Check if it's a generic type wrapping a class type
74
+ const genericArgument = getGenericTypeArgument(type);
75
+ if (
76
+ genericArgument &&
77
+ isClassType(genericArgument) &&
78
+ isResourceWithReadonlyInterface(genericArgument)
79
+ ) {
80
+ const wrapperName = (() => {
81
+ if (type.aliasSymbol) return type.aliasSymbol.name; // For type aliases like Readonly<T>, Partial<T>
82
+ if (type.symbol?.name) return type.symbol.name; // For other generic types like Array<T>
83
+ return undefined;
84
+ })();
85
+ context.report({
86
+ node: property,
87
+ messageId: "invalidInterfaceProperty",
88
+ data: {
89
+ propertyName: property.key.name,
90
+ typeName: wrapperName
91
+ ? `${wrapperName}<${genericArgument.symbol.name}>`
92
+ : genericArgument.symbol.name,
93
+ },
94
+ });
95
+ }
51
96
  }
52
97
  },
53
98
  };
@@ -5,9 +5,12 @@ import {
5
5
  TSESLint,
6
6
  TSESTree,
7
7
  } from "@typescript-eslint/utils";
8
+ import { Type } from "typescript";
8
9
 
9
10
  import { createRule } from "../utils/createRule";
11
+ import { getArrayElementType } from "../utils/getArrayElementType";
10
12
  import { getConstructor } from "../utils/getConstructor";
13
+ import { getGenericTypeArgument } from "../utils/getGenericTypeArgument";
11
14
  import { isResourceWithReadonlyInterface } from "../utils/is-resource-with-readonly-interface";
12
15
  import { isConstructOrStackType } from "../utils/typecheck/cdk";
13
16
  import { isClassType } from "../utils/typecheck/ts-type";
@@ -88,16 +91,7 @@ const validatePublicPropertyOfConstruct = (
88
91
  if (!property.typeAnnotation) continue;
89
92
 
90
93
  const type = parserServices.getTypeAtLocation(property);
91
- if (!isClassType(type) || !isResourceWithReadonlyInterface(type)) continue;
92
-
93
- context.report({
94
- node: property,
95
- messageId: "invalidPublicPropertyOfConstruct",
96
- data: {
97
- propertyName: property.key.name,
98
- typeName: type.symbol.name,
99
- },
100
- });
94
+ checkAndReportConstructType(type, property, property.key.name, context);
101
95
  }
102
96
  };
103
97
 
@@ -127,15 +121,74 @@ const validateConstructorParameterProperty = (
127
121
  if (!param.parameter.typeAnnotation) continue;
128
122
 
129
123
  const type = parserServices.getTypeAtLocation(param);
130
- if (!isClassType(type) || !isResourceWithReadonlyInterface(type)) continue;
124
+ checkAndReportConstructType(type, param, param.parameter.name, context);
125
+ }
126
+ };
131
127
 
128
+ /**
129
+ * Common validation logic for checking if a type is a Construct type
130
+ */
131
+ const checkAndReportConstructType = (
132
+ type: Type,
133
+ node: TSESTree.Node,
134
+ propertyName: string,
135
+ context: Context
136
+ ): void => {
137
+ // NOTE: Check if it's a direct class type
138
+ if (isClassType(type) && isResourceWithReadonlyInterface(type)) {
132
139
  context.report({
133
- node: param,
140
+ node,
134
141
  messageId: "invalidPublicPropertyOfConstruct",
135
142
  data: {
136
- propertyName: param.parameter.name,
143
+ propertyName,
137
144
  typeName: type.symbol.name,
138
145
  },
139
146
  });
147
+ return;
148
+ }
149
+
150
+ // NOTE: Check if it's an array of class types
151
+ const elementType = getArrayElementType(type);
152
+ if (
153
+ elementType &&
154
+ isClassType(elementType) &&
155
+ isResourceWithReadonlyInterface(elementType)
156
+ ) {
157
+ context.report({
158
+ node,
159
+ messageId: "invalidPublicPropertyOfConstruct",
160
+ data: {
161
+ propertyName,
162
+ typeName: `${elementType.symbol.name}[]`,
163
+ },
164
+ });
165
+ return;
166
+ }
167
+
168
+ // NOTE: Check if it's a generic type wrapping a class type
169
+ const genericArgument = getGenericTypeArgument(type);
170
+ if (
171
+ genericArgument &&
172
+ isClassType(genericArgument) &&
173
+ isResourceWithReadonlyInterface(genericArgument)
174
+ ) {
175
+ const wrapperName = (() => {
176
+ if ("aliasSymbol" in type && type.aliasSymbol) {
177
+ return type.aliasSymbol.name; // For type aliases like Readonly<T>, Partial<T>
178
+ }
179
+ if (type.symbol?.name) return type.symbol.name; // For other generic types like Array<T>
180
+ return undefined;
181
+ })();
182
+
183
+ context.report({
184
+ node,
185
+ messageId: "invalidPublicPropertyOfConstruct",
186
+ data: {
187
+ propertyName,
188
+ typeName: wrapperName
189
+ ? `${wrapperName}<${genericArgument.symbol.name}>`
190
+ : genericArgument.symbol.name,
191
+ },
192
+ });
140
193
  }
141
194
  };
@@ -0,0 +1,14 @@
1
+ import { Type } from "typescript";
2
+
3
+ import { isArrayType } from "./typecheck/ts-type";
4
+
5
+ export const getArrayElementType = (type: Type): Type | undefined => {
6
+ if (!isArrayType(type)) return undefined;
7
+
8
+ // Get type arguments for Array<T>
9
+ if ("typeArguments" in type && Array.isArray(type.typeArguments)) {
10
+ return (type.typeArguments as Type[])[0];
11
+ }
12
+
13
+ return undefined;
14
+ };
@@ -0,0 +1,47 @@
1
+ import { Type } from "typescript";
2
+
3
+ /**
4
+ * Extracts the type argument from a generic type reference
5
+ * @param type - The type to check
6
+ * @returns The first type argument if it's a generic type reference, undefined otherwise
7
+ */
8
+ export const getGenericTypeArgument = (type: Type): Type | undefined => {
9
+ // NOTE: Check for type alias (e.g. Readonly<T>, Partial<T>)
10
+ if (
11
+ "aliasSymbol" in type &&
12
+ type.aliasSymbol &&
13
+ "aliasTypeArguments" in type &&
14
+ type.aliasTypeArguments?.length
15
+ ) {
16
+ return type.aliasTypeArguments[0];
17
+ }
18
+
19
+ // NOTE: Check if type has typeArguments (generic types like Array<T>, etc.)
20
+ // This works for TypeReference types
21
+ if (
22
+ "typeArguments" in type &&
23
+ Array.isArray(type.typeArguments) &&
24
+ type.typeArguments?.length
25
+ ) {
26
+ return type.typeArguments[0] as Type;
27
+ }
28
+
29
+ // NOTE: Alternative approach: check for target property (some generic types have this)
30
+ if (
31
+ "target" in type &&
32
+ type.target &&
33
+ "typeArguments" in type &&
34
+ Array.isArray(type.typeArguments) &&
35
+ type.typeArguments?.length
36
+ ) {
37
+ return type.typeArguments[0] as Type;
38
+ }
39
+
40
+ // NOTE: For mapped types like Readonly<T> and Partial<T>
41
+ // These are represented differently in TypeScript's type system
42
+ if ("modifiersType" in type && type.modifiersType) {
43
+ return type.modifiersType as Type;
44
+ }
45
+
46
+ return undefined;
47
+ };
@@ -13,3 +13,15 @@ const getSymbol = (type: Type): Symbol | undefined => {
13
13
  export const isClassType = (type: Type): boolean => {
14
14
  return getSymbol(type)?.flags === SYMBOL_FLAGS.CLASS;
15
15
  };
16
+
17
+ export const isArrayType = (type: Type): boolean => {
18
+ const symbol = getSymbol(type);
19
+ if (symbol?.name === "Array") return true;
20
+
21
+ if ("target" in type && type.target) {
22
+ const targetSymbol = getSymbol(type.target as Type);
23
+ return targetSymbol?.name === "Array";
24
+ }
25
+
26
+ return false;
27
+ };