Skip to content

Commit

Permalink
feat: add rule construct props must readonly (#34)
Browse files Browse the repository at this point in the history
* feat: add rule

* feat: add documents

* feat: update rule docs
  • Loading branch information
ren-yamanashi authored Nov 18, 2024
1 parent 9475f3c commit edf7b49
Show file tree
Hide file tree
Showing 27 changed files with 231 additions and 22 deletions.
2 changes: 1 addition & 1 deletion docs/getting-started/example.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - Example for eslint.config.mjs
titleTemplate: ":title"
prev: false
---
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - Getting Started
titleTemplate: ":title"
---

Expand Down
3 changes: 2 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
# https://vitepress.dev/reference/default-theme-home-page
layout: home

title: eslint-plugin-cdk - ESLint plugin for AWS CDK
titleTemplate: ":title"
hero:
name: "eslint-plugin-cdk"
text: "ESLint plugin for AWS CDK"
Expand Down
2 changes: 1 addition & 1 deletion docs/ja/getting-started/example.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - Example for eslint.config.mjs
titleTemplate: ":title"
prev: false
---
Expand Down
2 changes: 1 addition & 1 deletion docs/ja/getting-started/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - Getting Started
titleTemplate: ":title"
---

Expand Down
3 changes: 2 additions & 1 deletion docs/ja/index.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
# https://vitepress.dev/reference/default-theme-home-page
layout: home

title: eslint-plugin-cdk - ESLint plugin for AWS CDK
titleTemplate: ":title"
hero:
name: "eslint-plugin-cdk"
text: "ESLint plugin for AWS CDK"
Expand Down
2 changes: 1 addition & 1 deletion docs/ja/rules/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - Rules
titleTemplate: ":title"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/ja/rules/no-class-in-interface.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - no-class-in-interface
titleTemplate: ":title"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/ja/rules/no-construct-stack-suffix.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - no-construct-stack-suffix
titleTemplate: ":title"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/ja/rules/no-import-private.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - no-import-private
titleTemplate: ":title"
next: false
---
Expand Down
26 changes: 26 additions & 0 deletions docs/ja/rules/no-mutable-props-interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
title: eslint-plugin-cdk - no-mutable-props-interface
titleTemplate: ":title"
---

# no-mutable-props-interface

このルールは、コンストラクトまたはスタックの、`Props`(インターフェース)のパブリック変数を変更可能にすることを禁止します。

Props で変更可能なパブリック変数を指定すると、意図しない副作用を引き起こす可能性があるため推奨されません。

#### ✅ 正しい例

```ts
interface MyConstructProps {
readonly bucket: IBucket;
}
```

#### ❌ 誤った例

```ts
interface MyConstructProps {
bucket: IBucket;
}
```
2 changes: 1 addition & 1 deletion docs/ja/rules/no-mutable-public-fields.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - no-mutable-public-fields
titleTemplate: ":title"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/ja/rules/no-parent-name-construct-id-match.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - no-parent-name-construct-id-match
titleTemplate: ":title"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/ja/rules/no-public-class-fields.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - no-public-class-fields
titleTemplate: ":title"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/ja/rules/pascal-case-construct-id.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - pascal-case-construct-id
titleTemplate: ":title"
prev: false
---
Expand Down
2 changes: 1 addition & 1 deletion docs/rules/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - Rules
titleTemplate: ":title"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/rules/no-class-in-interface.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - no-class-in-interface
titleTemplate: ":title"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/rules/no-construct-stack-suffix.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - no-construct-stack-suffix
titleTemplate: ":title"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/rules/no-import-private.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - no-import-private
titleTemplate: ":title"
next: false
---
Expand Down
26 changes: 26 additions & 0 deletions docs/rules/no-mutable-props-interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
title: eslint-plugin-cdk - no-mutable-props-interface
titleTemplate: ":title"
---

# no-mutable-props-interface

This rule disallow making public properties of constructs or stack `props` (interfaces) mutable.

It is not a good to specify mutable public properties in props, as this can lead to unintended side effects.

#### ✅ Correct Example

```ts
interface MyConstructProps {
readonly bucket: IBucket;
}
```

#### ❌ Incorrect Example

```ts
interface MyConstructProps {
bucket: IBucket;
}
```
2 changes: 1 addition & 1 deletion docs/rules/no-mutable-public-fields.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - no-mutable-public-fields
titleTemplate: ":title"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/rules/no-parent-name-construct-id-match.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - no-parent-name-construct-id-match
titleTemplate: ":title"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/rules/no-public-class-fields.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - no-public-class-fields
titleTemplate: ":title"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/rules/pascal-case-construct-id.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: eslint-plugin-cdk - ESLint plugin for AWS CDK
title: eslint-plugin-cdk - pascal-case-construct-id
titleTemplate: ":title"
prev: false
---
Expand Down
101 changes: 101 additions & 0 deletions src/__tests__/no-mutable-props-interface.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { RuleTester } from "@typescript-eslint/rule-tester";

import { noMutablePropsInterface } from "../no-mutable-props-interface.mjs";

const ruleTester = new RuleTester({
languageOptions: {
parserOptions: {
projectService: {
allowDefaultProject: ["*.ts*"],
},
},
},
});

ruleTester.run("no-mutable-props-interface", noMutablePropsInterface, {
valid: [
// WHEN: All properties are readonly
{
code: `
interface TestProps {
readonly name: string;
readonly age: number;
}
`,
},
// WHEN: Interface name does not end with "Props"
{
code: `
interface Test {
name: string;
age: number;
}
`,
},
// WHEN: Optional properties are readonly
{
code: `
interface UserProps {
readonly name: string;
readonly age?: number;
}
`,
},
],
invalid: [
// WHEN: readonly is not set
{
code: `
interface TestProps {
name: string;
age: number;
}
`,
output: `
interface TestProps {
readonly name: string;
readonly age: number;
}
`,
errors: [
{ messageId: "noMutablePropsInterface" },
{ messageId: "noMutablePropsInterface" },
],
},
// WHEN: Some properties do not have readonly
{
code: `
interface UserProps {
readonly name: string;
age: number;
}
`,
output: `
interface UserProps {
readonly name: string;
readonly age: number;
}
`,
errors: [{ messageId: "noMutablePropsInterface" }],
},
// WHEN: Optional properties do not have readonly
{
code: `
interface ConfigProps {
name: string;
age?: number;
}
`,
output: `
interface ConfigProps {
readonly name: string;
readonly age?: number;
}
`,
errors: [
{ messageId: "noMutablePropsInterface" },
{ messageId: "noMutablePropsInterface" },
],
},
],
});
3 changes: 3 additions & 0 deletions src/index.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { noClassInInterfaceProps } from "./no-class-in-interface.mjs";
import { noConstructStackSuffix } from "./no-construct-stack-suffix.mjs";
import { noImportPrivate } from "./no-import-private.mjs";
import { noMutablePropsInterface } from "./no-mutable-props-interface.mjs";
import { noMutablePublicFields } from "./no-mutable-public-fields.mjs";
import { noParentNameConstructIdMatch } from "./no-parent-name-construct-id-match.mjs";
import { noPublicClassFields } from "./no-public-class-fields.mjs";
Expand All @@ -15,6 +16,7 @@ const plugin = {
"no-public-class-fields": noPublicClassFields,
"pascal-case-construct-id": pascalCaseConstructId,
"no-mutable-public-fields": noMutablePublicFields,
"no-mutable-props-interface": noMutablePropsInterface,
},
configs: {
recommended: {
Expand All @@ -26,6 +28,7 @@ const plugin = {
"cdk/no-public-class-fields": "error",
"cdk/pascal-case-construct-id": "error",
"cdk/no-mutable-public-fields": "warn",
"cdk/no-mutable-props-interface": "warn",
},
},
},
Expand Down
51 changes: 51 additions & 0 deletions src/no-mutable-props-interface.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ESLintUtils } from "@typescript-eslint/utils";

export const noMutablePropsInterface = ESLintUtils.RuleCreator.withoutDocs({
meta: {
type: "problem",
docs: {
description: "Disallow mutable properties in Props interfaces",
},
fixable: "code",
messages: {
noMutablePropsInterface:
"Property '{{ propertyName }}' in Props interface should be readonly.",
},
schema: [],
},
defaultOptions: [],
create(context) {
const sourceCode = context.sourceCode;

return {
TSInterfaceDeclaration(node) {
// NOTE: Interface name check for "Props"
if (!node.id.name.endsWith("Props")) return;

for (const property of node.body.body) {
if (
property.type !== "TSPropertySignature" ||
property.key.type !== "Identifier"
) {
continue;
}

// NOTE: Skip if already readonly
if (property.readonly) continue;

context.report({
node: property,
messageId: "noMutablePropsInterface",
data: {
propertyName: property.key.name,
},
fix: (fixer) => {
const propertyText = sourceCode.getText(property);
return fixer.replaceText(property, `readonly ${propertyText}`);
},
});
}
},
};
},
});

0 comments on commit edf7b49

Please sign in to comment.