Skip to content

Commit deb0666

Browse files
cm-ayfHiroshiba
andauthored
右辺がundefinedやnullである厳密等価演算子を等価演算子に自動で修正できる設定を追加 (#1752)
* setup eslint plugin for this repository * add rule no-strict-nullable * separate createRule to a file * add documentation for rule no-strict-nullable * fix rule documentation url * add trailing newline to eslint-plugin/package.json --------- Co-authored-by: Hiroshiba <[email protected]>
1 parent 1ca1585 commit deb0666

8 files changed

+121
-27
lines changed

.eslintrc.js

+1-27
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module.exports = {
1111
"@vue/prettier",
1212
"@vue/eslint-config-typescript/recommended",
1313
"@vue/eslint-config-prettier",
14+
"plugin:@voicevox/all",
1415
],
1516
plugins: ["import"],
1617
parser: "vue-eslint-parser",
@@ -62,33 +63,6 @@ module.exports = {
6263
},
6364
],
6465
"import/order": "error",
65-
"no-restricted-syntax": [
66-
"warn",
67-
{
68-
selector:
69-
"BinaryExpression[operator='==='][right.type='Literal'][right.value=null]",
70-
message:
71-
"'=== null'ではなく'== null'を使用してください。詳細: https://github.com/VOICEVOX/voicevox/issues/1513",
72-
},
73-
{
74-
selector:
75-
"BinaryExpression[operator='!=='][right.type='Literal'][right.value=null]",
76-
message:
77-
"'!== null'ではなく'!= null'を使用してください。詳細: https://github.com/VOICEVOX/voicevox/issues/1513",
78-
},
79-
{
80-
selector:
81-
"BinaryExpression[operator='==='][right.type='Identifier'][right.name=undefined]",
82-
message:
83-
"'=== undefined'ではなく'== undefined'を使用してください。詳細: https://github.com/VOICEVOX/voicevox/issues/1513",
84-
},
85-
{
86-
selector:
87-
"BinaryExpression[operator='!=='][right.type='Identifier'][right.name=undefined]",
88-
message:
89-
"'!== undefined'ではなく'!= undefined'を使用してください。詳細: https://github.com/VOICEVOX/voicevox/issues/1513",
90-
},
91-
],
9266
},
9367
overrides: [
9468
{

eslint-plugin/create-rule.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const { ESLintUtils } = require("@typescript-eslint/utils");
2+
3+
exports.createRule = ESLintUtils.RuleCreator(
4+
(name) =>
5+
`https://github.com/VOICEVOX/voicevox/blob/main/eslint-plugin/${name}.md`
6+
);

eslint-plugin/index.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @ts-check
2+
module.exports = {
3+
configs: {
4+
all: {
5+
plugins: ["@voicevox"],
6+
rules: {
7+
"@voicevox/no-strict-nullable": "error",
8+
},
9+
},
10+
},
11+
rules: {
12+
"no-strict-nullable": require("./no-strict-nullable"),
13+
},
14+
};

eslint-plugin/no-strict-nullable.js

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// @ts-check
2+
const { AST_NODE_TYPES } = require("@typescript-eslint/utils");
3+
const { createRule } = require("./create-rule");
4+
5+
/**
6+
* @param {import("@typescript-eslint/types").TSESTree.BinaryExpression["left"]} node
7+
*/
8+
function isNull(node) {
9+
return node.type === AST_NODE_TYPES.Literal && node.value == null;
10+
}
11+
12+
/**
13+
* @param {import("@typescript-eslint/types").TSESTree.BinaryExpression["right"]} node
14+
*/
15+
function isUndefined(node) {
16+
return node.type === AST_NODE_TYPES.Identifier && node.name === "undefined";
17+
}
18+
19+
module.exports = createRule({
20+
create(context) {
21+
return {
22+
BinaryExpression(node) {
23+
if (node.operator !== "===" && node.operator !== "!==") return;
24+
if (!isNull(node.right) && !isUndefined(node.right)) return;
25+
26+
context.report({
27+
node,
28+
messageId: "report",
29+
data: {
30+
operator: node.operator.slice(0, 2),
31+
expression: context.getSourceCode().getText(node.right),
32+
},
33+
fix(fixer) {
34+
return fixer.replaceTextRange(
35+
[node.left.range[1], node.right.range[0]],
36+
node.operator.slice(0, 2) + " "
37+
);
38+
},
39+
});
40+
},
41+
};
42+
},
43+
name: "no-strict-nullable",
44+
meta: {
45+
type: "problem",
46+
docs: {
47+
description: "undefinedとnullと比較する際に厳密等価演算子を使わない",
48+
recommended: "error",
49+
},
50+
messages: {
51+
report:
52+
"'{{ operator }}= {{ expression }}'ではなく'{{ operator }} {{ expression }}'を使用してください。",
53+
},
54+
schema: [],
55+
fixable: "code",
56+
},
57+
defaultOptions: [],
58+
});

eslint-plugin/no-strict-nullable.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# no-strict-nullable
2+
3+
厳密等価演算子`===`は有用ですが,`undefined``null`を右辺に持ってくる時,左辺が`undefined`を取りうるのか,それとも`null`を取りうるのかを考える必要があります.
4+
一方,等価演算子`==``undefined``null`を区別しないため,このような場合に`==`を使うようにすることで,左辺が取る値を考える必要がなくなります.
5+
6+
このルールでは,右辺が`null`または`undefined`の場合に,厳密等価演算子`===`を使うことを禁止し,代わりに等価演算子`==`を使うようにします.
7+
8+
```ts
9+
const a = fuga === undefined;
10+
// ^^^^^^^^^^^^^^^^^^ '=== null'ではなく'== null'を使用してください。
11+
const button = { text: null };
12+
const c = button.text !== null;
13+
// ^^^^^^^^^^^^^^^^^^^^ '!== null'ではなく'!= null'を使用してください。
14+
```
15+
16+
## リンク
17+
18+
https://github.com/VOICEVOX/voicevox/issues/1513

eslint-plugin/package.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "@voicevox/eslint-plugin",
3+
"version": "0.0.0",
4+
"description": "eslint plugin for voicevox",
5+
"main": "index.js"
6+
}

package-lock.json

+15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@
8787
"@types/wicg-file-system-access": "2020.9.6",
8888
"@typescript-eslint/eslint-plugin": "5.38.1",
8989
"@typescript-eslint/parser": "5.38.1",
90+
"@typescript-eslint/types": "5.38.1",
91+
"@typescript-eslint/utils": "5.38.1",
9092
"@vitejs/plugin-vue": "4.0.0",
93+
"@voicevox/eslint-plugin": "file:./eslint-plugin",
9194
"@vue/eslint-config-prettier": "7.0.0",
9295
"@vue/eslint-config-typescript": "11.0.2",
9396
"@vue/test-utils": "2.3.0",

0 commit comments

Comments
 (0)