eslint-cdk-plugin 1.0.4 → 1.1.0
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 +169 -13
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +170 -14
- package/package.json +1 -1
- package/src/index.ts +10 -0
- package/src/rules/no-variable-construct-id.ts +2 -8
- package/src/rules/props-name-convention.ts +75 -0
- package/src/rules/require-jsdoc.ts +99 -0
- package/src/rules/require-passing-this.ts +2 -8
- package/src/rules/require-props-default-doc.ts +72 -0
- package/src/utils/typeCheck.ts +12 -11
package/dist/index.cjs
CHANGED
|
@@ -193779,19 +193779,18 @@ const getConstructorPropertyNames = (type) => {
|
|
|
193779
193779
|
return constructor.parameters.map((param) => param.name.getText());
|
|
193780
193780
|
};
|
|
193781
193781
|
|
|
193782
|
-
const isConstructOrStackType = (type) => {
|
|
193782
|
+
const isConstructOrStackType = (type, ignoredClasses = ["App", "Stage"]) => {
|
|
193783
|
+
if (ignoredClasses.includes(type.symbol?.name ?? "")) return false;
|
|
193783
193784
|
return isTargetSuperClassType(
|
|
193784
193785
|
type,
|
|
193785
193786
|
["Construct", "Stack"],
|
|
193786
193787
|
isConstructOrStackType
|
|
193787
193788
|
);
|
|
193788
193789
|
};
|
|
193789
|
-
const isConstructType = (type) => {
|
|
193790
|
+
const isConstructType = (type, ignoredClasses = ["App", "Stage", "Stack"]) => {
|
|
193791
|
+
if (ignoredClasses.includes(type.symbol?.name ?? "")) return false;
|
|
193790
193792
|
return isTargetSuperClassType(type, ["Construct"], isConstructType);
|
|
193791
193793
|
};
|
|
193792
|
-
const isStackType = (type) => {
|
|
193793
|
-
return isTargetSuperClassType(type, ["Stack"], isStackType);
|
|
193794
|
-
};
|
|
193795
193794
|
const isTargetSuperClassType = (type, targetSuperClasses, typeCheckFunction) => {
|
|
193796
193795
|
if (!type.symbol) return false;
|
|
193797
193796
|
if (targetSuperClasses.some((suffix) => type.symbol.name.endsWith(suffix))) {
|
|
@@ -194317,9 +194316,7 @@ const noVariableConstructId = utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
|
194317
194316
|
return {
|
|
194318
194317
|
NewExpression(node) {
|
|
194319
194318
|
const type = parserServices.getTypeAtLocation(node);
|
|
194320
|
-
if (!isConstructType(type) ||
|
|
194321
|
-
return;
|
|
194322
|
-
}
|
|
194319
|
+
if (!isConstructType(type) || node.arguments.length < 2) return;
|
|
194323
194320
|
const constructorPropertyNames = getConstructorPropertyNames(type);
|
|
194324
194321
|
if (constructorPropertyNames[1] !== "id") return;
|
|
194325
194322
|
validateConstructId$1(node, context);
|
|
@@ -194405,6 +194402,118 @@ const validateConstructId = (node, context) => {
|
|
|
194405
194402
|
});
|
|
194406
194403
|
};
|
|
194407
194404
|
|
|
194405
|
+
const propsNameConvention = utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
194406
|
+
meta: {
|
|
194407
|
+
type: "problem",
|
|
194408
|
+
docs: {
|
|
194409
|
+
description: "Enforce props interface name to follow ${ConstructName}Props format"
|
|
194410
|
+
},
|
|
194411
|
+
schema: [],
|
|
194412
|
+
messages: {
|
|
194413
|
+
invalidPropsName: "Props interface name '{{ interfaceName }}' should follow '${ConstructName}Props' format. Expected '{{ expectedName }}'."
|
|
194414
|
+
}
|
|
194415
|
+
},
|
|
194416
|
+
defaultOptions: [],
|
|
194417
|
+
create(context) {
|
|
194418
|
+
const parserServices = utils.ESLintUtils.getParserServices(context);
|
|
194419
|
+
return {
|
|
194420
|
+
ClassDeclaration(node) {
|
|
194421
|
+
if (!node.id || !node.superClass) return;
|
|
194422
|
+
const type = parserServices.getTypeAtLocation(node.superClass);
|
|
194423
|
+
if (!isConstructType(type)) return;
|
|
194424
|
+
const constructor = node.body.body.find(
|
|
194425
|
+
(member) => member.type === utils.AST_NODE_TYPES.MethodDefinition && member.kind === "constructor"
|
|
194426
|
+
);
|
|
194427
|
+
const propsParam = constructor?.value.params?.[2];
|
|
194428
|
+
if (propsParam?.type !== utils.AST_NODE_TYPES.Identifier) return;
|
|
194429
|
+
const typeAnnotation = propsParam.typeAnnotation;
|
|
194430
|
+
if (typeAnnotation?.type !== utils.AST_NODE_TYPES.TSTypeAnnotation) return;
|
|
194431
|
+
const typeNode = typeAnnotation.typeAnnotation;
|
|
194432
|
+
if (typeNode.type !== utils.AST_NODE_TYPES.TSTypeReference) return;
|
|
194433
|
+
const propsTypeName = typeNode.typeName;
|
|
194434
|
+
if (propsTypeName.type !== utils.AST_NODE_TYPES.Identifier) return;
|
|
194435
|
+
const constructName = node.id.name;
|
|
194436
|
+
const expectedPropsName = `${constructName}Props`;
|
|
194437
|
+
if (propsTypeName.name !== expectedPropsName) {
|
|
194438
|
+
context.report({
|
|
194439
|
+
node: propsTypeName,
|
|
194440
|
+
messageId: "invalidPropsName",
|
|
194441
|
+
data: {
|
|
194442
|
+
interfaceName: propsTypeName.name,
|
|
194443
|
+
expectedName: expectedPropsName
|
|
194444
|
+
}
|
|
194445
|
+
});
|
|
194446
|
+
}
|
|
194447
|
+
}
|
|
194448
|
+
};
|
|
194449
|
+
}
|
|
194450
|
+
});
|
|
194451
|
+
|
|
194452
|
+
const requireJSDoc = utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
194453
|
+
meta: {
|
|
194454
|
+
type: "problem",
|
|
194455
|
+
docs: {
|
|
194456
|
+
description: "Require JSDoc comments for interface properties and public properties in Construct classes"
|
|
194457
|
+
},
|
|
194458
|
+
messages: {
|
|
194459
|
+
missingJSDoc: "Property '{{ propertyName }}' should have a JSDoc comment."
|
|
194460
|
+
},
|
|
194461
|
+
schema: []
|
|
194462
|
+
},
|
|
194463
|
+
defaultOptions: [],
|
|
194464
|
+
create(context) {
|
|
194465
|
+
const parserServices = utils.ESLintUtils.getParserServices(context);
|
|
194466
|
+
return {
|
|
194467
|
+
TSPropertySignature(node) {
|
|
194468
|
+
if (node.key.type !== utils.AST_NODE_TYPES.Identifier || node.parent?.type !== utils.AST_NODE_TYPES.TSInterfaceBody) {
|
|
194469
|
+
return;
|
|
194470
|
+
}
|
|
194471
|
+
const sourceCode = context.sourceCode;
|
|
194472
|
+
const comments = sourceCode.getCommentsBefore(node);
|
|
194473
|
+
const hasJSDoc = comments.some(
|
|
194474
|
+
({ type, value }) => type === utils.AST_TOKEN_TYPES.Block && value.startsWith("*")
|
|
194475
|
+
);
|
|
194476
|
+
if (!hasJSDoc) {
|
|
194477
|
+
context.report({
|
|
194478
|
+
node,
|
|
194479
|
+
messageId: "missingJSDoc",
|
|
194480
|
+
data: {
|
|
194481
|
+
propertyName: node.key.name
|
|
194482
|
+
}
|
|
194483
|
+
});
|
|
194484
|
+
}
|
|
194485
|
+
},
|
|
194486
|
+
PropertyDefinition(node) {
|
|
194487
|
+
if (node.key.type !== utils.AST_NODE_TYPES.Identifier || node.parent.type !== utils.AST_NODE_TYPES.ClassBody) {
|
|
194488
|
+
return;
|
|
194489
|
+
}
|
|
194490
|
+
const classDeclaration = node.parent.parent;
|
|
194491
|
+
if (classDeclaration.type !== utils.AST_NODE_TYPES.ClassDeclaration || !classDeclaration.superClass) {
|
|
194492
|
+
return;
|
|
194493
|
+
}
|
|
194494
|
+
const classType = parserServices.getTypeAtLocation(classDeclaration);
|
|
194495
|
+
if (!isConstructType(classType) || node.accessibility !== "public") {
|
|
194496
|
+
return;
|
|
194497
|
+
}
|
|
194498
|
+
const sourceCode = context.sourceCode;
|
|
194499
|
+
const comments = sourceCode.getCommentsBefore(node);
|
|
194500
|
+
const hasJSDoc = comments.some(
|
|
194501
|
+
({ type, value }) => type === utils.AST_TOKEN_TYPES.Block && value.startsWith("*")
|
|
194502
|
+
);
|
|
194503
|
+
if (!hasJSDoc) {
|
|
194504
|
+
context.report({
|
|
194505
|
+
node,
|
|
194506
|
+
messageId: "missingJSDoc",
|
|
194507
|
+
data: {
|
|
194508
|
+
propertyName: node.key.name
|
|
194509
|
+
}
|
|
194510
|
+
});
|
|
194511
|
+
}
|
|
194512
|
+
}
|
|
194513
|
+
};
|
|
194514
|
+
}
|
|
194515
|
+
});
|
|
194516
|
+
|
|
194408
194517
|
const requirePassingThis = utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
194409
194518
|
meta: {
|
|
194410
194519
|
type: "problem",
|
|
@@ -194423,9 +194532,7 @@ const requirePassingThis = utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
|
194423
194532
|
return {
|
|
194424
194533
|
NewExpression(node) {
|
|
194425
194534
|
const type = parserServices.getTypeAtLocation(node);
|
|
194426
|
-
if (!isConstructType(type) ||
|
|
194427
|
-
return;
|
|
194428
|
-
}
|
|
194535
|
+
if (!isConstructType(type) || !node.arguments.length) return;
|
|
194429
194536
|
const argument = node.arguments[0];
|
|
194430
194537
|
if (argument.type === utils.AST_NODE_TYPES.ThisExpression) return;
|
|
194431
194538
|
const constructorPropertyNames = getConstructorPropertyNames(type);
|
|
@@ -194442,6 +194549,48 @@ const requirePassingThis = utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
|
194442
194549
|
}
|
|
194443
194550
|
});
|
|
194444
194551
|
|
|
194552
|
+
const requirePropsDefaultDoc = utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
194553
|
+
meta: {
|
|
194554
|
+
type: "problem",
|
|
194555
|
+
docs: {
|
|
194556
|
+
description: "Require @default JSDoc for optional properties in interfaces ending with 'Props'"
|
|
194557
|
+
},
|
|
194558
|
+
schema: [],
|
|
194559
|
+
messages: {
|
|
194560
|
+
missingDefaultDoc: "Optional property '{{ propertyName }}' in Props interface must have @default JSDoc documentation"
|
|
194561
|
+
}
|
|
194562
|
+
},
|
|
194563
|
+
defaultOptions: [],
|
|
194564
|
+
create(context) {
|
|
194565
|
+
return {
|
|
194566
|
+
TSPropertySignature(node) {
|
|
194567
|
+
if (!node.optional) return;
|
|
194568
|
+
const parent = node.parent?.parent;
|
|
194569
|
+
if (parent?.type !== utils.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
194570
|
+
return;
|
|
194571
|
+
}
|
|
194572
|
+
if (parent.id.type !== utils.AST_NODE_TYPES.Identifier || !parent.id.name.endsWith("Props")) {
|
|
194573
|
+
return;
|
|
194574
|
+
}
|
|
194575
|
+
const sourceCode = context.sourceCode;
|
|
194576
|
+
const comments = sourceCode.getCommentsBefore(node);
|
|
194577
|
+
const hasDefaultDoc = comments.some(
|
|
194578
|
+
(comment) => comment.type === utils.AST_TOKEN_TYPES.Block && comment.value.includes("*") && comment.value.includes("@default")
|
|
194579
|
+
);
|
|
194580
|
+
if (!hasDefaultDoc) {
|
|
194581
|
+
context.report({
|
|
194582
|
+
node,
|
|
194583
|
+
messageId: "missingDefaultDoc",
|
|
194584
|
+
data: {
|
|
194585
|
+
propertyName: node.key.type === utils.AST_NODE_TYPES.Identifier ? node.key.name : "unknown"
|
|
194586
|
+
}
|
|
194587
|
+
});
|
|
194588
|
+
}
|
|
194589
|
+
}
|
|
194590
|
+
};
|
|
194591
|
+
}
|
|
194592
|
+
});
|
|
194593
|
+
|
|
194445
194594
|
const rules = {
|
|
194446
194595
|
"no-class-in-interface": noClassInInterface,
|
|
194447
194596
|
"no-construct-stack-suffix": noConstructStackSuffix,
|
|
@@ -194452,6 +194601,9 @@ const rules = {
|
|
|
194452
194601
|
"no-mutable-props-interface": noMutablePropsInterface,
|
|
194453
194602
|
"require-passing-this": requirePassingThis,
|
|
194454
194603
|
"no-variable-construct-id": noVariableConstructId,
|
|
194604
|
+
"require-jsdoc": requireJSDoc,
|
|
194605
|
+
"require-props-default-doc": requirePropsDefaultDoc,
|
|
194606
|
+
"props-name-convention": propsNameConvention,
|
|
194455
194607
|
"no-import-private": noImportPrivate
|
|
194456
194608
|
};
|
|
194457
194609
|
const configs = {
|
|
@@ -194466,7 +194618,8 @@ const configs = {
|
|
|
194466
194618
|
"cdk/require-passing-this": "error",
|
|
194467
194619
|
"cdk/no-variable-construct-id": "error",
|
|
194468
194620
|
"cdk/no-mutable-public-fields": "warn",
|
|
194469
|
-
"cdk/no-mutable-props-interface": "warn"
|
|
194621
|
+
"cdk/no-mutable-props-interface": "warn",
|
|
194622
|
+
"cdk/props-name-convention": "warn"
|
|
194470
194623
|
}
|
|
194471
194624
|
},
|
|
194472
194625
|
strict: {
|
|
@@ -194481,7 +194634,10 @@ const configs = {
|
|
|
194481
194634
|
"cdk/no-variable-construct-id": "error",
|
|
194482
194635
|
"cdk/no-mutable-public-fields": "error",
|
|
194483
194636
|
"cdk/no-mutable-props-interface": "error",
|
|
194484
|
-
"cdk/no-import-private": "error"
|
|
194637
|
+
"cdk/no-import-private": "error",
|
|
194638
|
+
"cdk/require-props-default-doc": "error",
|
|
194639
|
+
"cdk/props-name-convention": "error",
|
|
194640
|
+
"cdk/require-jsdoc": "error"
|
|
194485
194641
|
}
|
|
194486
194642
|
}
|
|
194487
194643
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ declare const rules: {
|
|
|
8
8
|
"no-mutable-props-interface": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noMutablePropsInterface", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
9
9
|
"require-passing-this": import("@typescript-eslint/utils/ts-eslint").RuleModule<"requirePassingThis", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
10
10
|
"no-variable-construct-id": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noVariableConstructId", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
11
|
+
"require-jsdoc": import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingJSDoc", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
12
|
+
"require-props-default-doc": import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingDefaultDoc", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
13
|
+
"props-name-convention": import("@typescript-eslint/utils/ts-eslint").RuleModule<"invalidPropsName", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
11
14
|
"no-import-private": import("eslint").Rule.RuleModule;
|
|
12
15
|
};
|
|
13
16
|
declare const configs: {
|
|
@@ -23,6 +26,7 @@ declare const configs: {
|
|
|
23
26
|
"cdk/no-variable-construct-id": string;
|
|
24
27
|
"cdk/no-mutable-public-fields": string;
|
|
25
28
|
"cdk/no-mutable-props-interface": string;
|
|
29
|
+
"cdk/props-name-convention": string;
|
|
26
30
|
};
|
|
27
31
|
};
|
|
28
32
|
strict: {
|
|
@@ -38,6 +42,9 @@ declare const configs: {
|
|
|
38
42
|
"cdk/no-mutable-public-fields": string;
|
|
39
43
|
"cdk/no-mutable-props-interface": string;
|
|
40
44
|
"cdk/no-import-private": string;
|
|
45
|
+
"cdk/require-props-default-doc": string;
|
|
46
|
+
"cdk/props-name-convention": string;
|
|
47
|
+
"cdk/require-jsdoc": string;
|
|
41
48
|
};
|
|
42
49
|
};
|
|
43
50
|
};
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,QAAA,MAAM,KAAK;;;;;;;;;;;;;;CAcV,CAAC;AAEF,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCZ,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAE1B,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,OAAO,EAAE,OAAO,OAAO,CAAC;CACzB;AAED,QAAA,MAAM,eAAe,EAAE,eAGtB,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
1
|
+
import { ESLintUtils, AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils';
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
3
|
import require$$1 from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
@@ -193756,19 +193756,18 @@ const getConstructorPropertyNames = (type) => {
|
|
|
193756
193756
|
return constructor.parameters.map((param) => param.name.getText());
|
|
193757
193757
|
};
|
|
193758
193758
|
|
|
193759
|
-
const isConstructOrStackType = (type) => {
|
|
193759
|
+
const isConstructOrStackType = (type, ignoredClasses = ["App", "Stage"]) => {
|
|
193760
|
+
if (ignoredClasses.includes(type.symbol?.name ?? "")) return false;
|
|
193760
193761
|
return isTargetSuperClassType(
|
|
193761
193762
|
type,
|
|
193762
193763
|
["Construct", "Stack"],
|
|
193763
193764
|
isConstructOrStackType
|
|
193764
193765
|
);
|
|
193765
193766
|
};
|
|
193766
|
-
const isConstructType = (type) => {
|
|
193767
|
+
const isConstructType = (type, ignoredClasses = ["App", "Stage", "Stack"]) => {
|
|
193768
|
+
if (ignoredClasses.includes(type.symbol?.name ?? "")) return false;
|
|
193767
193769
|
return isTargetSuperClassType(type, ["Construct"], isConstructType);
|
|
193768
193770
|
};
|
|
193769
|
-
const isStackType = (type) => {
|
|
193770
|
-
return isTargetSuperClassType(type, ["Stack"], isStackType);
|
|
193771
|
-
};
|
|
193772
193771
|
const isTargetSuperClassType = (type, targetSuperClasses, typeCheckFunction) => {
|
|
193773
193772
|
if (!type.symbol) return false;
|
|
193774
193773
|
if (targetSuperClasses.some((suffix) => type.symbol.name.endsWith(suffix))) {
|
|
@@ -194294,9 +194293,7 @@ const noVariableConstructId = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
194294
194293
|
return {
|
|
194295
194294
|
NewExpression(node) {
|
|
194296
194295
|
const type = parserServices.getTypeAtLocation(node);
|
|
194297
|
-
if (!isConstructType(type) ||
|
|
194298
|
-
return;
|
|
194299
|
-
}
|
|
194296
|
+
if (!isConstructType(type) || node.arguments.length < 2) return;
|
|
194300
194297
|
const constructorPropertyNames = getConstructorPropertyNames(type);
|
|
194301
194298
|
if (constructorPropertyNames[1] !== "id") return;
|
|
194302
194299
|
validateConstructId$1(node, context);
|
|
@@ -194382,6 +194379,118 @@ const validateConstructId = (node, context) => {
|
|
|
194382
194379
|
});
|
|
194383
194380
|
};
|
|
194384
194381
|
|
|
194382
|
+
const propsNameConvention = ESLintUtils.RuleCreator.withoutDocs({
|
|
194383
|
+
meta: {
|
|
194384
|
+
type: "problem",
|
|
194385
|
+
docs: {
|
|
194386
|
+
description: "Enforce props interface name to follow ${ConstructName}Props format"
|
|
194387
|
+
},
|
|
194388
|
+
schema: [],
|
|
194389
|
+
messages: {
|
|
194390
|
+
invalidPropsName: "Props interface name '{{ interfaceName }}' should follow '${ConstructName}Props' format. Expected '{{ expectedName }}'."
|
|
194391
|
+
}
|
|
194392
|
+
},
|
|
194393
|
+
defaultOptions: [],
|
|
194394
|
+
create(context) {
|
|
194395
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
194396
|
+
return {
|
|
194397
|
+
ClassDeclaration(node) {
|
|
194398
|
+
if (!node.id || !node.superClass) return;
|
|
194399
|
+
const type = parserServices.getTypeAtLocation(node.superClass);
|
|
194400
|
+
if (!isConstructType(type)) return;
|
|
194401
|
+
const constructor = node.body.body.find(
|
|
194402
|
+
(member) => member.type === AST_NODE_TYPES.MethodDefinition && member.kind === "constructor"
|
|
194403
|
+
);
|
|
194404
|
+
const propsParam = constructor?.value.params?.[2];
|
|
194405
|
+
if (propsParam?.type !== AST_NODE_TYPES.Identifier) return;
|
|
194406
|
+
const typeAnnotation = propsParam.typeAnnotation;
|
|
194407
|
+
if (typeAnnotation?.type !== AST_NODE_TYPES.TSTypeAnnotation) return;
|
|
194408
|
+
const typeNode = typeAnnotation.typeAnnotation;
|
|
194409
|
+
if (typeNode.type !== AST_NODE_TYPES.TSTypeReference) return;
|
|
194410
|
+
const propsTypeName = typeNode.typeName;
|
|
194411
|
+
if (propsTypeName.type !== AST_NODE_TYPES.Identifier) return;
|
|
194412
|
+
const constructName = node.id.name;
|
|
194413
|
+
const expectedPropsName = `${constructName}Props`;
|
|
194414
|
+
if (propsTypeName.name !== expectedPropsName) {
|
|
194415
|
+
context.report({
|
|
194416
|
+
node: propsTypeName,
|
|
194417
|
+
messageId: "invalidPropsName",
|
|
194418
|
+
data: {
|
|
194419
|
+
interfaceName: propsTypeName.name,
|
|
194420
|
+
expectedName: expectedPropsName
|
|
194421
|
+
}
|
|
194422
|
+
});
|
|
194423
|
+
}
|
|
194424
|
+
}
|
|
194425
|
+
};
|
|
194426
|
+
}
|
|
194427
|
+
});
|
|
194428
|
+
|
|
194429
|
+
const requireJSDoc = ESLintUtils.RuleCreator.withoutDocs({
|
|
194430
|
+
meta: {
|
|
194431
|
+
type: "problem",
|
|
194432
|
+
docs: {
|
|
194433
|
+
description: "Require JSDoc comments for interface properties and public properties in Construct classes"
|
|
194434
|
+
},
|
|
194435
|
+
messages: {
|
|
194436
|
+
missingJSDoc: "Property '{{ propertyName }}' should have a JSDoc comment."
|
|
194437
|
+
},
|
|
194438
|
+
schema: []
|
|
194439
|
+
},
|
|
194440
|
+
defaultOptions: [],
|
|
194441
|
+
create(context) {
|
|
194442
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
194443
|
+
return {
|
|
194444
|
+
TSPropertySignature(node) {
|
|
194445
|
+
if (node.key.type !== AST_NODE_TYPES.Identifier || node.parent?.type !== AST_NODE_TYPES.TSInterfaceBody) {
|
|
194446
|
+
return;
|
|
194447
|
+
}
|
|
194448
|
+
const sourceCode = context.sourceCode;
|
|
194449
|
+
const comments = sourceCode.getCommentsBefore(node);
|
|
194450
|
+
const hasJSDoc = comments.some(
|
|
194451
|
+
({ type, value }) => type === AST_TOKEN_TYPES.Block && value.startsWith("*")
|
|
194452
|
+
);
|
|
194453
|
+
if (!hasJSDoc) {
|
|
194454
|
+
context.report({
|
|
194455
|
+
node,
|
|
194456
|
+
messageId: "missingJSDoc",
|
|
194457
|
+
data: {
|
|
194458
|
+
propertyName: node.key.name
|
|
194459
|
+
}
|
|
194460
|
+
});
|
|
194461
|
+
}
|
|
194462
|
+
},
|
|
194463
|
+
PropertyDefinition(node) {
|
|
194464
|
+
if (node.key.type !== AST_NODE_TYPES.Identifier || node.parent.type !== AST_NODE_TYPES.ClassBody) {
|
|
194465
|
+
return;
|
|
194466
|
+
}
|
|
194467
|
+
const classDeclaration = node.parent.parent;
|
|
194468
|
+
if (classDeclaration.type !== AST_NODE_TYPES.ClassDeclaration || !classDeclaration.superClass) {
|
|
194469
|
+
return;
|
|
194470
|
+
}
|
|
194471
|
+
const classType = parserServices.getTypeAtLocation(classDeclaration);
|
|
194472
|
+
if (!isConstructType(classType) || node.accessibility !== "public") {
|
|
194473
|
+
return;
|
|
194474
|
+
}
|
|
194475
|
+
const sourceCode = context.sourceCode;
|
|
194476
|
+
const comments = sourceCode.getCommentsBefore(node);
|
|
194477
|
+
const hasJSDoc = comments.some(
|
|
194478
|
+
({ type, value }) => type === AST_TOKEN_TYPES.Block && value.startsWith("*")
|
|
194479
|
+
);
|
|
194480
|
+
if (!hasJSDoc) {
|
|
194481
|
+
context.report({
|
|
194482
|
+
node,
|
|
194483
|
+
messageId: "missingJSDoc",
|
|
194484
|
+
data: {
|
|
194485
|
+
propertyName: node.key.name
|
|
194486
|
+
}
|
|
194487
|
+
});
|
|
194488
|
+
}
|
|
194489
|
+
}
|
|
194490
|
+
};
|
|
194491
|
+
}
|
|
194492
|
+
});
|
|
194493
|
+
|
|
194385
194494
|
const requirePassingThis = ESLintUtils.RuleCreator.withoutDocs({
|
|
194386
194495
|
meta: {
|
|
194387
194496
|
type: "problem",
|
|
@@ -194400,9 +194509,7 @@ const requirePassingThis = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
194400
194509
|
return {
|
|
194401
194510
|
NewExpression(node) {
|
|
194402
194511
|
const type = parserServices.getTypeAtLocation(node);
|
|
194403
|
-
if (!isConstructType(type) ||
|
|
194404
|
-
return;
|
|
194405
|
-
}
|
|
194512
|
+
if (!isConstructType(type) || !node.arguments.length) return;
|
|
194406
194513
|
const argument = node.arguments[0];
|
|
194407
194514
|
if (argument.type === AST_NODE_TYPES.ThisExpression) return;
|
|
194408
194515
|
const constructorPropertyNames = getConstructorPropertyNames(type);
|
|
@@ -194419,6 +194526,48 @@ const requirePassingThis = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
194419
194526
|
}
|
|
194420
194527
|
});
|
|
194421
194528
|
|
|
194529
|
+
const requirePropsDefaultDoc = ESLintUtils.RuleCreator.withoutDocs({
|
|
194530
|
+
meta: {
|
|
194531
|
+
type: "problem",
|
|
194532
|
+
docs: {
|
|
194533
|
+
description: "Require @default JSDoc for optional properties in interfaces ending with 'Props'"
|
|
194534
|
+
},
|
|
194535
|
+
schema: [],
|
|
194536
|
+
messages: {
|
|
194537
|
+
missingDefaultDoc: "Optional property '{{ propertyName }}' in Props interface must have @default JSDoc documentation"
|
|
194538
|
+
}
|
|
194539
|
+
},
|
|
194540
|
+
defaultOptions: [],
|
|
194541
|
+
create(context) {
|
|
194542
|
+
return {
|
|
194543
|
+
TSPropertySignature(node) {
|
|
194544
|
+
if (!node.optional) return;
|
|
194545
|
+
const parent = node.parent?.parent;
|
|
194546
|
+
if (parent?.type !== AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
194547
|
+
return;
|
|
194548
|
+
}
|
|
194549
|
+
if (parent.id.type !== AST_NODE_TYPES.Identifier || !parent.id.name.endsWith("Props")) {
|
|
194550
|
+
return;
|
|
194551
|
+
}
|
|
194552
|
+
const sourceCode = context.sourceCode;
|
|
194553
|
+
const comments = sourceCode.getCommentsBefore(node);
|
|
194554
|
+
const hasDefaultDoc = comments.some(
|
|
194555
|
+
(comment) => comment.type === AST_TOKEN_TYPES.Block && comment.value.includes("*") && comment.value.includes("@default")
|
|
194556
|
+
);
|
|
194557
|
+
if (!hasDefaultDoc) {
|
|
194558
|
+
context.report({
|
|
194559
|
+
node,
|
|
194560
|
+
messageId: "missingDefaultDoc",
|
|
194561
|
+
data: {
|
|
194562
|
+
propertyName: node.key.type === AST_NODE_TYPES.Identifier ? node.key.name : "unknown"
|
|
194563
|
+
}
|
|
194564
|
+
});
|
|
194565
|
+
}
|
|
194566
|
+
}
|
|
194567
|
+
};
|
|
194568
|
+
}
|
|
194569
|
+
});
|
|
194570
|
+
|
|
194422
194571
|
const rules = {
|
|
194423
194572
|
"no-class-in-interface": noClassInInterface,
|
|
194424
194573
|
"no-construct-stack-suffix": noConstructStackSuffix,
|
|
@@ -194429,6 +194578,9 @@ const rules = {
|
|
|
194429
194578
|
"no-mutable-props-interface": noMutablePropsInterface,
|
|
194430
194579
|
"require-passing-this": requirePassingThis,
|
|
194431
194580
|
"no-variable-construct-id": noVariableConstructId,
|
|
194581
|
+
"require-jsdoc": requireJSDoc,
|
|
194582
|
+
"require-props-default-doc": requirePropsDefaultDoc,
|
|
194583
|
+
"props-name-convention": propsNameConvention,
|
|
194432
194584
|
"no-import-private": noImportPrivate
|
|
194433
194585
|
};
|
|
194434
194586
|
const configs = {
|
|
@@ -194443,7 +194595,8 @@ const configs = {
|
|
|
194443
194595
|
"cdk/require-passing-this": "error",
|
|
194444
194596
|
"cdk/no-variable-construct-id": "error",
|
|
194445
194597
|
"cdk/no-mutable-public-fields": "warn",
|
|
194446
|
-
"cdk/no-mutable-props-interface": "warn"
|
|
194598
|
+
"cdk/no-mutable-props-interface": "warn",
|
|
194599
|
+
"cdk/props-name-convention": "warn"
|
|
194447
194600
|
}
|
|
194448
194601
|
},
|
|
194449
194602
|
strict: {
|
|
@@ -194458,7 +194611,10 @@ const configs = {
|
|
|
194458
194611
|
"cdk/no-variable-construct-id": "error",
|
|
194459
194612
|
"cdk/no-mutable-public-fields": "error",
|
|
194460
194613
|
"cdk/no-mutable-props-interface": "error",
|
|
194461
|
-
"cdk/no-import-private": "error"
|
|
194614
|
+
"cdk/no-import-private": "error",
|
|
194615
|
+
"cdk/require-props-default-doc": "error",
|
|
194616
|
+
"cdk/props-name-convention": "error",
|
|
194617
|
+
"cdk/require-jsdoc": "error"
|
|
194462
194618
|
}
|
|
194463
194619
|
}
|
|
194464
194620
|
};
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -7,7 +7,10 @@ import { noParentNameConstructIdMatch } from "./rules/no-parent-name-construct-i
|
|
|
7
7
|
import { noPublicClassFields } from "./rules/no-public-class-fields";
|
|
8
8
|
import { noVariableConstructId } from "./rules/no-variable-construct-id";
|
|
9
9
|
import { pascalCaseConstructId } from "./rules/pascal-case-construct-id";
|
|
10
|
+
import { propsNameConvention } from "./rules/props-name-convention";
|
|
11
|
+
import { requireJSDoc } from "./rules/require-jsdoc";
|
|
10
12
|
import { requirePassingThis } from "./rules/require-passing-this";
|
|
13
|
+
import { requirePropsDefaultDoc } from "./rules/require-props-default-doc";
|
|
11
14
|
|
|
12
15
|
const rules = {
|
|
13
16
|
"no-class-in-interface": noClassInInterface,
|
|
@@ -19,6 +22,9 @@ const rules = {
|
|
|
19
22
|
"no-mutable-props-interface": noMutablePropsInterface,
|
|
20
23
|
"require-passing-this": requirePassingThis,
|
|
21
24
|
"no-variable-construct-id": noVariableConstructId,
|
|
25
|
+
"require-jsdoc": requireJSDoc,
|
|
26
|
+
"require-props-default-doc": requirePropsDefaultDoc,
|
|
27
|
+
"props-name-convention": propsNameConvention,
|
|
22
28
|
"no-import-private": noImportPrivate,
|
|
23
29
|
};
|
|
24
30
|
|
|
@@ -35,6 +41,7 @@ const configs = {
|
|
|
35
41
|
"cdk/no-variable-construct-id": "error",
|
|
36
42
|
"cdk/no-mutable-public-fields": "warn",
|
|
37
43
|
"cdk/no-mutable-props-interface": "warn",
|
|
44
|
+
"cdk/props-name-convention": "warn",
|
|
38
45
|
},
|
|
39
46
|
},
|
|
40
47
|
strict: {
|
|
@@ -50,6 +57,9 @@ const configs = {
|
|
|
50
57
|
"cdk/no-mutable-public-fields": "error",
|
|
51
58
|
"cdk/no-mutable-props-interface": "error",
|
|
52
59
|
"cdk/no-import-private": "error",
|
|
60
|
+
"cdk/require-props-default-doc": "error",
|
|
61
|
+
"cdk/props-name-convention": "error",
|
|
62
|
+
"cdk/require-jsdoc": "error",
|
|
53
63
|
},
|
|
54
64
|
},
|
|
55
65
|
};
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "@typescript-eslint/utils";
|
|
7
7
|
|
|
8
8
|
import { getConstructorPropertyNames } from "../utils/parseType";
|
|
9
|
-
import { isConstructType
|
|
9
|
+
import { isConstructType } from "../utils/typeCheck";
|
|
10
10
|
|
|
11
11
|
type Context = TSESLint.RuleContext<"noVariableConstructId", []>;
|
|
12
12
|
|
|
@@ -33,13 +33,7 @@ export const noVariableConstructId = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
33
33
|
NewExpression(node) {
|
|
34
34
|
const type = parserServices.getTypeAtLocation(node);
|
|
35
35
|
|
|
36
|
-
if (
|
|
37
|
-
!isConstructType(type) ||
|
|
38
|
-
isStackType(type) ||
|
|
39
|
-
node.arguments.length < 2
|
|
40
|
-
) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
36
|
+
if (!isConstructType(type) || node.arguments.length < 2) return;
|
|
43
37
|
|
|
44
38
|
const constructorPropertyNames = getConstructorPropertyNames(type);
|
|
45
39
|
if (constructorPropertyNames[1] !== "id") return;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AST_NODE_TYPES,
|
|
3
|
+
ESLintUtils,
|
|
4
|
+
TSESTree,
|
|
5
|
+
} from "@typescript-eslint/utils";
|
|
6
|
+
|
|
7
|
+
import { isConstructType } from "../utils/typeCheck";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Enforces a naming convention for props interfaces in Construct classes
|
|
11
|
+
* @param context - The rule context provided by ESLint
|
|
12
|
+
* @returns An object containing the AST visitor functions
|
|
13
|
+
* @see {@link https://eslint-cdk-plugin.dev/rules/props-name-convention} - Documentation
|
|
14
|
+
*/
|
|
15
|
+
export const propsNameConvention = ESLintUtils.RuleCreator.withoutDocs({
|
|
16
|
+
meta: {
|
|
17
|
+
type: "problem",
|
|
18
|
+
docs: {
|
|
19
|
+
description:
|
|
20
|
+
"Enforce props interface name to follow ${ConstructName}Props format",
|
|
21
|
+
},
|
|
22
|
+
schema: [],
|
|
23
|
+
messages: {
|
|
24
|
+
invalidPropsName:
|
|
25
|
+
"Props interface name '{{ interfaceName }}' should follow '${ConstructName}Props' format. Expected '{{ expectedName }}'.",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultOptions: [],
|
|
29
|
+
create(context) {
|
|
30
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
31
|
+
return {
|
|
32
|
+
ClassDeclaration(node) {
|
|
33
|
+
if (!node.id || !node.superClass) return;
|
|
34
|
+
|
|
35
|
+
const type = parserServices.getTypeAtLocation(node.superClass);
|
|
36
|
+
if (!isConstructType(type)) return;
|
|
37
|
+
|
|
38
|
+
// NOTE: check constructor parameter
|
|
39
|
+
const constructor = node.body.body.find(
|
|
40
|
+
(member): member is TSESTree.MethodDefinition =>
|
|
41
|
+
member.type === AST_NODE_TYPES.MethodDefinition &&
|
|
42
|
+
member.kind === "constructor"
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const propsParam = constructor?.value.params?.[2];
|
|
46
|
+
if (propsParam?.type !== AST_NODE_TYPES.Identifier) return;
|
|
47
|
+
|
|
48
|
+
const typeAnnotation = propsParam.typeAnnotation;
|
|
49
|
+
if (typeAnnotation?.type !== AST_NODE_TYPES.TSTypeAnnotation) return;
|
|
50
|
+
|
|
51
|
+
const typeNode = typeAnnotation.typeAnnotation;
|
|
52
|
+
if (typeNode.type !== AST_NODE_TYPES.TSTypeReference) return;
|
|
53
|
+
|
|
54
|
+
const propsTypeName = typeNode.typeName;
|
|
55
|
+
if (propsTypeName.type !== AST_NODE_TYPES.Identifier) return;
|
|
56
|
+
|
|
57
|
+
// NOTE: create valid props name
|
|
58
|
+
const constructName = node.id.name;
|
|
59
|
+
const expectedPropsName = `${constructName}Props`;
|
|
60
|
+
|
|
61
|
+
// NOTE: error when props name is not expected format
|
|
62
|
+
if (propsTypeName.name !== expectedPropsName) {
|
|
63
|
+
context.report({
|
|
64
|
+
node: propsTypeName,
|
|
65
|
+
messageId: "invalidPropsName",
|
|
66
|
+
data: {
|
|
67
|
+
interfaceName: propsTypeName.name,
|
|
68
|
+
expectedName: expectedPropsName,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AST_NODE_TYPES,
|
|
3
|
+
AST_TOKEN_TYPES,
|
|
4
|
+
ESLintUtils,
|
|
5
|
+
} from "@typescript-eslint/utils";
|
|
6
|
+
|
|
7
|
+
import { isConstructType } from "../utils/typeCheck";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Require JSDoc comments for interface properties and public properties in Construct classes
|
|
11
|
+
* @param context - The rule context provided by ESLint
|
|
12
|
+
* @returns An object containing the AST visitor functions
|
|
13
|
+
* @see {@link https://eslint-cdk-plugin.dev/rules/require-jsdoc} - Documentation
|
|
14
|
+
*/
|
|
15
|
+
export const requireJSDoc = ESLintUtils.RuleCreator.withoutDocs({
|
|
16
|
+
meta: {
|
|
17
|
+
type: "problem",
|
|
18
|
+
docs: {
|
|
19
|
+
description:
|
|
20
|
+
"Require JSDoc comments for interface properties and public properties in Construct classes",
|
|
21
|
+
},
|
|
22
|
+
messages: {
|
|
23
|
+
missingJSDoc:
|
|
24
|
+
"Property '{{ propertyName }}' should have a JSDoc comment.",
|
|
25
|
+
},
|
|
26
|
+
schema: [],
|
|
27
|
+
},
|
|
28
|
+
defaultOptions: [],
|
|
29
|
+
create(context) {
|
|
30
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
31
|
+
return {
|
|
32
|
+
TSPropertySignature(node) {
|
|
33
|
+
if (
|
|
34
|
+
node.key.type !== AST_NODE_TYPES.Identifier ||
|
|
35
|
+
node.parent?.type !== AST_NODE_TYPES.TSInterfaceBody
|
|
36
|
+
) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const sourceCode = context.sourceCode;
|
|
41
|
+
const comments = sourceCode.getCommentsBefore(node);
|
|
42
|
+
const hasJSDoc = comments.some(
|
|
43
|
+
({ type, value }) =>
|
|
44
|
+
type === AST_TOKEN_TYPES.Block && value.startsWith("*")
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (!hasJSDoc) {
|
|
48
|
+
context.report({
|
|
49
|
+
node,
|
|
50
|
+
messageId: "missingJSDoc",
|
|
51
|
+
data: {
|
|
52
|
+
propertyName: node.key.name,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
PropertyDefinition(node) {
|
|
58
|
+
if (
|
|
59
|
+
node.key.type !== AST_NODE_TYPES.Identifier ||
|
|
60
|
+
node.parent.type !== AST_NODE_TYPES.ClassBody
|
|
61
|
+
) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// NOTE: Check if the class extends Construct
|
|
66
|
+
const classDeclaration = node.parent.parent;
|
|
67
|
+
if (
|
|
68
|
+
classDeclaration.type !== AST_NODE_TYPES.ClassDeclaration ||
|
|
69
|
+
!classDeclaration.superClass
|
|
70
|
+
) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// NOTE: Check if the class extends Construct and the property is public
|
|
75
|
+
const classType = parserServices.getTypeAtLocation(classDeclaration);
|
|
76
|
+
if (!isConstructType(classType) || node.accessibility !== "public") {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const sourceCode = context.sourceCode;
|
|
81
|
+
const comments = sourceCode.getCommentsBefore(node);
|
|
82
|
+
const hasJSDoc = comments.some(
|
|
83
|
+
({ type, value }) =>
|
|
84
|
+
type === AST_TOKEN_TYPES.Block && value.startsWith("*")
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (!hasJSDoc) {
|
|
88
|
+
context.report({
|
|
89
|
+
node,
|
|
90
|
+
messageId: "missingJSDoc",
|
|
91
|
+
data: {
|
|
92
|
+
propertyName: node.key.name,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
|
|
2
2
|
|
|
3
3
|
import { getConstructorPropertyNames } from "../utils/parseType";
|
|
4
|
-
import { isConstructType
|
|
4
|
+
import { isConstructType } from "../utils/typeCheck";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Enforces that `this` is passed to the constructor
|
|
@@ -28,13 +28,7 @@ export const requirePassingThis = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
28
28
|
NewExpression(node) {
|
|
29
29
|
const type = parserServices.getTypeAtLocation(node);
|
|
30
30
|
|
|
31
|
-
if (
|
|
32
|
-
!isConstructType(type) ||
|
|
33
|
-
isStackType(type) ||
|
|
34
|
-
!node.arguments.length
|
|
35
|
-
) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
31
|
+
if (!isConstructType(type) || !node.arguments.length) return;
|
|
38
32
|
|
|
39
33
|
const argument = node.arguments[0];
|
|
40
34
|
if (argument.type === AST_NODE_TYPES.ThisExpression) return;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AST_NODE_TYPES,
|
|
3
|
+
AST_TOKEN_TYPES,
|
|
4
|
+
ESLintUtils,
|
|
5
|
+
} from "@typescript-eslint/utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Requires @default JSDoc documentation for optional properties in interfaces ending with 'Props'
|
|
9
|
+
* @param context - The rule context provided by ESLint
|
|
10
|
+
* @returns An object containing the AST visitor functions
|
|
11
|
+
* @see {@link https://eslint-cdk-plugin.dev/rules/require-props-default-doc} - Documentation
|
|
12
|
+
*/
|
|
13
|
+
export const requirePropsDefaultDoc = ESLintUtils.RuleCreator.withoutDocs({
|
|
14
|
+
meta: {
|
|
15
|
+
type: "problem",
|
|
16
|
+
docs: {
|
|
17
|
+
description:
|
|
18
|
+
"Require @default JSDoc for optional properties in interfaces ending with 'Props'",
|
|
19
|
+
},
|
|
20
|
+
schema: [],
|
|
21
|
+
messages: {
|
|
22
|
+
missingDefaultDoc:
|
|
23
|
+
"Optional property '{{ propertyName }}' in Props interface must have @default JSDoc documentation",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultOptions: [],
|
|
27
|
+
create(context) {
|
|
28
|
+
return {
|
|
29
|
+
TSPropertySignature(node) {
|
|
30
|
+
// NOTE: Check if the property is optional
|
|
31
|
+
if (!node.optional) return;
|
|
32
|
+
|
|
33
|
+
// NOTE: Check if the parent is an interface
|
|
34
|
+
const parent = node.parent?.parent;
|
|
35
|
+
if (parent?.type !== AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// NOTE: Check if the interface name ends with 'Props'
|
|
40
|
+
if (
|
|
41
|
+
parent.id.type !== AST_NODE_TYPES.Identifier ||
|
|
42
|
+
!parent.id.name.endsWith("Props")
|
|
43
|
+
) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// NOTE: Get JSDoc comments
|
|
48
|
+
const sourceCode = context.sourceCode;
|
|
49
|
+
const comments = sourceCode.getCommentsBefore(node);
|
|
50
|
+
const hasDefaultDoc = comments.some(
|
|
51
|
+
(comment) =>
|
|
52
|
+
comment.type === AST_TOKEN_TYPES.Block &&
|
|
53
|
+
comment.value.includes("*") &&
|
|
54
|
+
comment.value.includes("@default")
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (!hasDefaultDoc) {
|
|
58
|
+
context.report({
|
|
59
|
+
node,
|
|
60
|
+
messageId: "missingDefaultDoc",
|
|
61
|
+
data: {
|
|
62
|
+
propertyName:
|
|
63
|
+
node.key.type === AST_NODE_TYPES.Identifier
|
|
64
|
+
? node.key.name
|
|
65
|
+
: "unknown",
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
});
|
package/src/utils/typeCheck.ts
CHANGED
|
@@ -5,9 +5,14 @@ type SuperClassType = "Construct" | "Stack";
|
|
|
5
5
|
/**
|
|
6
6
|
* Check if the type extends Construct or Stack
|
|
7
7
|
* @param type - The type to check
|
|
8
|
+
* @param ignoredClasses - Classes that inherit from Construct Class or Stack Class but do not want to be treated as Construct Class or Stack Class
|
|
8
9
|
* @returns True if the type extends Construct or Stack, otherwise false
|
|
9
10
|
*/
|
|
10
|
-
export const isConstructOrStackType = (
|
|
11
|
+
export const isConstructOrStackType = (
|
|
12
|
+
type: Type,
|
|
13
|
+
ignoredClasses: readonly string[] = ["App", "Stage"] as const
|
|
14
|
+
): boolean => {
|
|
15
|
+
if (ignoredClasses.includes(type.symbol?.name ?? "")) return false;
|
|
11
16
|
return isTargetSuperClassType(
|
|
12
17
|
type,
|
|
13
18
|
["Construct", "Stack"],
|
|
@@ -18,21 +23,17 @@ export const isConstructOrStackType = (type: Type): boolean => {
|
|
|
18
23
|
/**
|
|
19
24
|
* Check if the type extends Construct
|
|
20
25
|
* @param type - The type to check
|
|
26
|
+
* @param ignoredClasses - Classes that inherit from Construct Class but do not want to be treated as Construct Class
|
|
21
27
|
* @returns True if the type extends Construct, otherwise false
|
|
22
28
|
*/
|
|
23
|
-
export const isConstructType = (
|
|
29
|
+
export const isConstructType = (
|
|
30
|
+
type: Type,
|
|
31
|
+
ignoredClasses: readonly string[] = ["App", "Stage", "Stack"] as const
|
|
32
|
+
): boolean => {
|
|
33
|
+
if (ignoredClasses.includes(type.symbol?.name ?? "")) return false;
|
|
24
34
|
return isTargetSuperClassType(type, ["Construct"], isConstructType);
|
|
25
35
|
};
|
|
26
36
|
|
|
27
|
-
/**
|
|
28
|
-
* Check if the type extends Stack
|
|
29
|
-
* @param type - The type to check
|
|
30
|
-
* @returns True if the type extends Stack, otherwise false
|
|
31
|
-
*/
|
|
32
|
-
export const isStackType = (type: Type): boolean => {
|
|
33
|
-
return isTargetSuperClassType(type, ["Stack"], isStackType);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
37
|
/**
|
|
37
38
|
* Check if the type extends target super class
|
|
38
39
|
* @param type - The type to check
|