eslint-cdk-plugin 1.0.5 → 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 +163 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +164 -3
- package/package.json +1 -1
- package/src/index.ts +10 -0
- package/src/rules/props-name-convention.ts +75 -0
- package/src/rules/require-jsdoc.ts +99 -0
- package/src/rules/require-props-default-doc.ts +72 -0
package/dist/index.cjs
CHANGED
|
@@ -194402,6 +194402,118 @@ const validateConstructId = (node, context) => {
|
|
|
194402
194402
|
});
|
|
194403
194403
|
};
|
|
194404
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
|
+
|
|
194405
194517
|
const requirePassingThis = utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
194406
194518
|
meta: {
|
|
194407
194519
|
type: "problem",
|
|
@@ -194437,6 +194549,48 @@ const requirePassingThis = utils.ESLintUtils.RuleCreator.withoutDocs({
|
|
|
194437
194549
|
}
|
|
194438
194550
|
});
|
|
194439
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
|
+
|
|
194440
194594
|
const rules = {
|
|
194441
194595
|
"no-class-in-interface": noClassInInterface,
|
|
194442
194596
|
"no-construct-stack-suffix": noConstructStackSuffix,
|
|
@@ -194447,6 +194601,9 @@ const rules = {
|
|
|
194447
194601
|
"no-mutable-props-interface": noMutablePropsInterface,
|
|
194448
194602
|
"require-passing-this": requirePassingThis,
|
|
194449
194603
|
"no-variable-construct-id": noVariableConstructId,
|
|
194604
|
+
"require-jsdoc": requireJSDoc,
|
|
194605
|
+
"require-props-default-doc": requirePropsDefaultDoc,
|
|
194606
|
+
"props-name-convention": propsNameConvention,
|
|
194450
194607
|
"no-import-private": noImportPrivate
|
|
194451
194608
|
};
|
|
194452
194609
|
const configs = {
|
|
@@ -194461,7 +194618,8 @@ const configs = {
|
|
|
194461
194618
|
"cdk/require-passing-this": "error",
|
|
194462
194619
|
"cdk/no-variable-construct-id": "error",
|
|
194463
194620
|
"cdk/no-mutable-public-fields": "warn",
|
|
194464
|
-
"cdk/no-mutable-props-interface": "warn"
|
|
194621
|
+
"cdk/no-mutable-props-interface": "warn",
|
|
194622
|
+
"cdk/props-name-convention": "warn"
|
|
194465
194623
|
}
|
|
194466
194624
|
},
|
|
194467
194625
|
strict: {
|
|
@@ -194476,7 +194634,10 @@ const configs = {
|
|
|
194476
194634
|
"cdk/no-variable-construct-id": "error",
|
|
194477
194635
|
"cdk/no-mutable-public-fields": "error",
|
|
194478
194636
|
"cdk/no-mutable-props-interface": "error",
|
|
194479
|
-
"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"
|
|
194480
194641
|
}
|
|
194481
194642
|
}
|
|
194482
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';
|
|
@@ -194379,6 +194379,118 @@ const validateConstructId = (node, context) => {
|
|
|
194379
194379
|
});
|
|
194380
194380
|
};
|
|
194381
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
|
+
|
|
194382
194494
|
const requirePassingThis = ESLintUtils.RuleCreator.withoutDocs({
|
|
194383
194495
|
meta: {
|
|
194384
194496
|
type: "problem",
|
|
@@ -194414,6 +194526,48 @@ const requirePassingThis = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
194414
194526
|
}
|
|
194415
194527
|
});
|
|
194416
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
|
+
|
|
194417
194571
|
const rules = {
|
|
194418
194572
|
"no-class-in-interface": noClassInInterface,
|
|
194419
194573
|
"no-construct-stack-suffix": noConstructStackSuffix,
|
|
@@ -194424,6 +194578,9 @@ const rules = {
|
|
|
194424
194578
|
"no-mutable-props-interface": noMutablePropsInterface,
|
|
194425
194579
|
"require-passing-this": requirePassingThis,
|
|
194426
194580
|
"no-variable-construct-id": noVariableConstructId,
|
|
194581
|
+
"require-jsdoc": requireJSDoc,
|
|
194582
|
+
"require-props-default-doc": requirePropsDefaultDoc,
|
|
194583
|
+
"props-name-convention": propsNameConvention,
|
|
194427
194584
|
"no-import-private": noImportPrivate
|
|
194428
194585
|
};
|
|
194429
194586
|
const configs = {
|
|
@@ -194438,7 +194595,8 @@ const configs = {
|
|
|
194438
194595
|
"cdk/require-passing-this": "error",
|
|
194439
194596
|
"cdk/no-variable-construct-id": "error",
|
|
194440
194597
|
"cdk/no-mutable-public-fields": "warn",
|
|
194441
|
-
"cdk/no-mutable-props-interface": "warn"
|
|
194598
|
+
"cdk/no-mutable-props-interface": "warn",
|
|
194599
|
+
"cdk/props-name-convention": "warn"
|
|
194442
194600
|
}
|
|
194443
194601
|
},
|
|
194444
194602
|
strict: {
|
|
@@ -194453,7 +194611,10 @@ const configs = {
|
|
|
194453
194611
|
"cdk/no-variable-construct-id": "error",
|
|
194454
194612
|
"cdk/no-mutable-public-fields": "error",
|
|
194455
194613
|
"cdk/no-mutable-props-interface": "error",
|
|
194456
|
-
"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"
|
|
194457
194618
|
}
|
|
194458
194619
|
}
|
|
194459
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
|
};
|
|
@@ -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
|
+
});
|
|
@@ -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
|
+
});
|