@tai-kun/vp-lint-plugins 0.0.6 → 0.0.7
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/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/plugin.js +2 -0
- package/dist/src/rules/_comment-utils.d.ts.map +1 -1
- package/dist/src/rules/_comment-utils.js +2 -1
- package/dist/src/rules/no-await-in-expect.d.ts +10 -0
- package/dist/src/rules/no-await-in-expect.d.ts.map +1 -0
- package/dist/src/rules/no-await-in-expect.js +115 -0
- package/dist/src/rules/spaced-comment.js +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/plugin.ts +2 -0
- package/src/rules/_comment-utils.ts +2 -1
- package/src/rules/no-await-in-expect.ts +139 -0
- package/src/rules/spaced-comment.ts +1 -1
package/dist/src/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { default as jsdocTagLines } from "./rules/jsdoc-tag-lines.js";
|
|
|
6
6
|
export { default as jsdocTagOrder } from "./rules/jsdoc-tag-order.js";
|
|
7
7
|
export { default as memberOrdering } from "./rules/member-ordering.js";
|
|
8
8
|
export { default as mergeImports } from "./rules/merge-imports.js";
|
|
9
|
+
export { default as noAwaitInExpect } from "./rules/no-await-in-expect.js";
|
|
9
10
|
export { default as objectPropertiesNewline } from "./rules/object-properties-newline.js";
|
|
10
11
|
export { default as overloadSignaturesAdjacent } from "./rules/overload-signatures-adjacent.js";
|
|
11
12
|
export { default as sortImportExportSpecifiers } from "./rules/sort-import-export-specifiers.js";
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AACxF,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,0CAA0C,CAAC;AAChG,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAC1F,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,yCAAyC,CAAC;AAChG,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,0CAA0C,CAAC;AACjG,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,4BAA4B,EAAE,MAAM,4CAA4C,CAAC;AAErG,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AACxF,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,0CAA0C,CAAC;AAChG,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAC1F,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,yCAAyC,CAAC;AAChG,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,0CAA0C,CAAC;AACjG,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,4BAA4B,EAAE,MAAM,4CAA4C,CAAC;AAErG,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/src/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { default as jsdocTagLines } from "./rules/jsdoc-tag-lines.js";
|
|
|
6
6
|
export { default as jsdocTagOrder } from "./rules/jsdoc-tag-order.js";
|
|
7
7
|
export { default as memberOrdering } from "./rules/member-ordering.js";
|
|
8
8
|
export { default as mergeImports } from "./rules/merge-imports.js";
|
|
9
|
+
export { default as noAwaitInExpect } from "./rules/no-await-in-expect.js";
|
|
9
10
|
export { default as objectPropertiesNewline } from "./rules/object-properties-newline.js";
|
|
10
11
|
export { default as overloadSignaturesAdjacent } from "./rules/overload-signatures-adjacent.js";
|
|
11
12
|
export { default as sortImportExportSpecifiers } from "./rules/sort-import-export-specifiers.js";
|
package/dist/src/plugin.js
CHANGED
|
@@ -7,6 +7,7 @@ import jsdocTagLines from "./rules/jsdoc-tag-lines.js";
|
|
|
7
7
|
import jsdocTagOrder from "./rules/jsdoc-tag-order.js";
|
|
8
8
|
import memberOrdering from "./rules/member-ordering.js";
|
|
9
9
|
import mergeImports from "./rules/merge-imports.js";
|
|
10
|
+
import noAwaitInExpect from "./rules/no-await-in-expect.js";
|
|
10
11
|
import objectPropertiesNewline from "./rules/object-properties-newline.js";
|
|
11
12
|
import overloadSignaturesAdjacent from "./rules/overload-signatures-adjacent.js";
|
|
12
13
|
import sortImportExportSpecifiers from "./rules/sort-import-export-specifiers.js";
|
|
@@ -27,6 +28,7 @@ export default definePlugin({
|
|
|
27
28
|
"member-ordering": memberOrdering,
|
|
28
29
|
"jsdoc-param-order": jsdocParamOrder,
|
|
29
30
|
"sort-type-members": sortTypeMembers,
|
|
31
|
+
"no-await-in-expect": noAwaitInExpect,
|
|
30
32
|
"sort-object-properties": sortObjectProperties,
|
|
31
33
|
"comment-japanese-spacing": commentJapaneseSpacing,
|
|
32
34
|
"object-properties-newline": objectPropertiesNewline,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_comment-utils.d.ts","sourceRoot":"","sources":["../../../src/rules/_comment-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAElE,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;
|
|
1
|
+
{"version":3,"file":"_comment-utils.d.ts","sourceRoot":"","sources":["../../../src/rules/_comment-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAElE,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AA8BD,iBAAS,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtC;AAED,iBAAS,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI1C;AAED,iBAAS,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI1C;AAED,iBAAS,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpC;AAED,iBAAS,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQzC;AAED,iBAAS,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAItC;AAED,iBAAS,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI5C;AAED;;GAEG;AACH,iBAAS,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAcnD;AAiDD,iBAAS,iBAAiB,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,UAAU,EAAE,UAAU,GAAG,WAAW,EAAE,CAe3F;AAiBD;;;;GAIG;AACH,iBAAS,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,EAAE,CAgB7F;AAED,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,OAAO,EACL,KAAK,EACL,OAAO,EACP,OAAO,EACP,UAAU,EACV,WAAW,EACX,WAAW,EACX,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,GACrB,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ラベル行とは別に、閉じ括弧付きのコロン終わりも文末とします。
|
|
2
|
-
const SENTENCE_END = /[。..!?!?::]$/u;
|
|
2
|
+
const SENTENCE_END = /[。..!?!?::)…]$/u;
|
|
3
3
|
const TRAILING_CLOSERS = /[」』】)》)〕〉]}"'”’]+$/u;
|
|
4
4
|
const DIRECTIVE_PREFIXES = [
|
|
5
5
|
"#!",
|
|
@@ -13,6 +13,7 @@ const DIRECTIVE_PREFIXES = [
|
|
|
13
13
|
"@link",
|
|
14
14
|
"TODO",
|
|
15
15
|
"FIXME",
|
|
16
|
+
"#__PURE__",
|
|
16
17
|
"NOTE",
|
|
17
18
|
"HACK",
|
|
18
19
|
"XXX",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `expect(await ...)` の代わりに `await expect(...).resolves` / `.rejects` を使います。
|
|
3
|
+
*
|
|
4
|
+
* `expect()` の引数で `await` すると、拒否された Promise を `.rejects` で検証できません。
|
|
5
|
+
* 成功の検証には `await expect(...).resolves` を、失敗の検証には `await expect(...).rejects` を使ってください。
|
|
6
|
+
* 自動修正では `.resolves` を付けて書き換えるため、拒否の検証が必要な場合は手で `.rejects` に直してください。
|
|
7
|
+
*/
|
|
8
|
+
declare const noAwaitInExpect: import("vite-plus/lint/plugins").Rule;
|
|
9
|
+
export default noAwaitInExpect;
|
|
10
|
+
//# sourceMappingURL=no-await-in-expect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-await-in-expect.d.ts","sourceRoot":"","sources":["../../../src/rules/no-await-in-expect.ts"],"names":[],"mappings":"AAoEA;;;;;;GAMG;AACH,QAAA,MAAM,eAAe,uCA6DnB,CAAC;eAEY,eAAe"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { defineRule } from "vite-plus/lint/plugins";
|
|
2
|
+
/**
|
|
3
|
+
* 括弧で包まれた式を剥がして中身を返します。
|
|
4
|
+
*/
|
|
5
|
+
function unwrapParenthesized(node) {
|
|
6
|
+
let current = node;
|
|
7
|
+
while (current.type === "ParenthesizedExpression") {
|
|
8
|
+
current = current.expression;
|
|
9
|
+
}
|
|
10
|
+
return current;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `expect(...)` から `.resolves` / `.rejects` に続くチェーンかどうかを返します。
|
|
14
|
+
*/
|
|
15
|
+
function usesResolvesStyle(node) {
|
|
16
|
+
let current = node;
|
|
17
|
+
let parent = node.parent;
|
|
18
|
+
while (parent !== undefined &&
|
|
19
|
+
parent.type === "MemberExpression" &&
|
|
20
|
+
!parent.computed &&
|
|
21
|
+
parent.object === current &&
|
|
22
|
+
parent.property.type === "Identifier") {
|
|
23
|
+
if (parent.property.name === "resolves" || parent.property.name === "rejects") {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
current = parent;
|
|
27
|
+
parent = parent.parent;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `expect(...)` を含む式全体がすでに `await` されているかどうかを返します。
|
|
33
|
+
*
|
|
34
|
+
* `await expect(await ...).toBe(1)` のように、チェーンの外側で待たれている場合は `await` を追加しません。
|
|
35
|
+
*/
|
|
36
|
+
function isAlreadyAwaited(node) {
|
|
37
|
+
let outer = node;
|
|
38
|
+
let parent = node.parent;
|
|
39
|
+
while (parent !== undefined) {
|
|
40
|
+
if (parent.type === "ParenthesizedExpression" || parent.type === "ChainExpression") {
|
|
41
|
+
outer = parent;
|
|
42
|
+
parent = parent.parent;
|
|
43
|
+
}
|
|
44
|
+
else if (parent.type === "MemberExpression" && parent.object === outer) {
|
|
45
|
+
outer = parent;
|
|
46
|
+
parent = parent.parent;
|
|
47
|
+
}
|
|
48
|
+
else if (parent.type === "CallExpression" && parent.callee === outer) {
|
|
49
|
+
outer = parent;
|
|
50
|
+
parent = parent.parent;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return parent !== undefined && parent.type === "AwaitExpression";
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* `expect(await ...)` の代わりに `await expect(...).resolves` / `.rejects` を使います。
|
|
60
|
+
*
|
|
61
|
+
* `expect()` の引数で `await` すると、拒否された Promise を `.rejects` で検証できません。
|
|
62
|
+
* 成功の検証には `await expect(...).resolves` を、失敗の検証には `await expect(...).rejects` を使ってください。
|
|
63
|
+
* 自動修正では `.resolves` を付けて書き換えるため、拒否の検証が必要な場合は手で `.rejects` に直してください。
|
|
64
|
+
*/
|
|
65
|
+
const noAwaitInExpect = defineRule({
|
|
66
|
+
meta: {
|
|
67
|
+
docs: {
|
|
68
|
+
description: "Require `await expect(...).resolves` or `.rejects` instead of `expect(await ...)`.",
|
|
69
|
+
},
|
|
70
|
+
type: "problem",
|
|
71
|
+
fixable: "code",
|
|
72
|
+
messages: {
|
|
73
|
+
noAwaitInExpect: "expect() の中で await しないでください。await expect(...).resolves / .rejects を使ってください。",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
create(context) {
|
|
77
|
+
const sourceCode = context.sourceCode;
|
|
78
|
+
return {
|
|
79
|
+
CallExpression(node) {
|
|
80
|
+
if (node.callee.type !== "Identifier" || node.callee.name !== "expect") {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const first = node.arguments[0];
|
|
84
|
+
if (first === undefined || first.type === "SpreadElement") {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const awaited = unwrapParenthesized(first);
|
|
88
|
+
if (awaited.type !== "AwaitExpression") {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// 入れ子の await はいちばん内側まで剥がして 1 回で修正します。
|
|
92
|
+
let inner = unwrapParenthesized(awaited.argument);
|
|
93
|
+
while (inner.type === "AwaitExpression") {
|
|
94
|
+
inner = unwrapParenthesized(inner.argument);
|
|
95
|
+
}
|
|
96
|
+
const resolvesStyle = usesResolvesStyle(node);
|
|
97
|
+
const prefix = resolvesStyle || isAlreadyAwaited(node) ? "" : "await ";
|
|
98
|
+
const text = sourceCode.text;
|
|
99
|
+
const replacement = prefix +
|
|
100
|
+
text.slice(node.range[0], awaited.range[0]) +
|
|
101
|
+
text.slice(inner.range[0], inner.range[1]) +
|
|
102
|
+
text.slice(awaited.range[1], node.range[1]) +
|
|
103
|
+
(resolvesStyle ? "" : ".resolves");
|
|
104
|
+
context.report({
|
|
105
|
+
fix(fixer) {
|
|
106
|
+
return fixer.replaceTextRange([node.range[0], node.range[1]], replacement);
|
|
107
|
+
},
|
|
108
|
+
node,
|
|
109
|
+
messageId: "noAwaitInExpect",
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
export default noAwaitInExpect;
|
|
@@ -13,7 +13,7 @@ function isExemptLineComment(raw) {
|
|
|
13
13
|
return DECORATIVE_LINE.test(raw.slice(2));
|
|
14
14
|
}
|
|
15
15
|
function isExemptBlockComment(raw) {
|
|
16
|
-
return raw.startsWith("/*!");
|
|
16
|
+
return raw.startsWith("/*!") || raw.startsWith("/*#");
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* コメントの開始記号の直後と終了記号の直前に空白を要求します。
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { default as jsdocTagLines } from "./rules/jsdoc-tag-lines.js";
|
|
|
6
6
|
export { default as jsdocTagOrder } from "./rules/jsdoc-tag-order.js";
|
|
7
7
|
export { default as memberOrdering } from "./rules/member-ordering.js";
|
|
8
8
|
export { default as mergeImports } from "./rules/merge-imports.js";
|
|
9
|
+
export { default as noAwaitInExpect } from "./rules/no-await-in-expect.js";
|
|
9
10
|
export { default as objectPropertiesNewline } from "./rules/object-properties-newline.js";
|
|
10
11
|
export { default as overloadSignaturesAdjacent } from "./rules/overload-signatures-adjacent.js";
|
|
11
12
|
export { default as sortImportExportSpecifiers } from "./rules/sort-import-export-specifiers.js";
|
package/src/plugin.ts
CHANGED
|
@@ -8,6 +8,7 @@ import jsdocTagLines from "./rules/jsdoc-tag-lines.js";
|
|
|
8
8
|
import jsdocTagOrder from "./rules/jsdoc-tag-order.js";
|
|
9
9
|
import memberOrdering from "./rules/member-ordering.js";
|
|
10
10
|
import mergeImports from "./rules/merge-imports.js";
|
|
11
|
+
import noAwaitInExpect from "./rules/no-await-in-expect.js";
|
|
11
12
|
import objectPropertiesNewline from "./rules/object-properties-newline.js";
|
|
12
13
|
import overloadSignaturesAdjacent from "./rules/overload-signatures-adjacent.js";
|
|
13
14
|
import sortImportExportSpecifiers from "./rules/sort-import-export-specifiers.js";
|
|
@@ -29,6 +30,7 @@ export default definePlugin({
|
|
|
29
30
|
"member-ordering": memberOrdering,
|
|
30
31
|
"jsdoc-param-order": jsdocParamOrder,
|
|
31
32
|
"sort-type-members": sortTypeMembers,
|
|
33
|
+
"no-await-in-expect": noAwaitInExpect,
|
|
32
34
|
"sort-object-properties": sortObjectProperties,
|
|
33
35
|
"comment-japanese-spacing": commentJapaneseSpacing,
|
|
34
36
|
"object-properties-newline": objectPropertiesNewline,
|
|
@@ -9,7 +9,7 @@ interface CommentLine {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
// ラベル行とは別に、閉じ括弧付きのコロン終わりも文末とします。
|
|
12
|
-
const SENTENCE_END = /[。..!?!?::]$/u;
|
|
12
|
+
const SENTENCE_END = /[。..!?!?::)…]$/u;
|
|
13
13
|
const TRAILING_CLOSERS = /[」』】)》)〕〉]}"'”’]+$/u;
|
|
14
14
|
const DIRECTIVE_PREFIXES = [
|
|
15
15
|
"#!",
|
|
@@ -23,6 +23,7 @@ const DIRECTIVE_PREFIXES = [
|
|
|
23
23
|
"@link",
|
|
24
24
|
"TODO",
|
|
25
25
|
"FIXME",
|
|
26
|
+
"#__PURE__",
|
|
26
27
|
"NOTE",
|
|
27
28
|
"HACK",
|
|
28
29
|
"XXX",
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { defineRule } from "vite-plus/lint/plugins";
|
|
2
|
+
import type { ESTree } from "vite-plus/lint/plugins";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 括弧で包まれた式を剥がして中身を返します。
|
|
6
|
+
*/
|
|
7
|
+
function unwrapParenthesized(node: ESTree.Expression): ESTree.Expression {
|
|
8
|
+
let current = node;
|
|
9
|
+
|
|
10
|
+
while (current.type === "ParenthesizedExpression") {
|
|
11
|
+
current = current.expression;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return current;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* `expect(...)` から `.resolves` / `.rejects` に続くチェーンかどうかを返します。
|
|
19
|
+
*/
|
|
20
|
+
function usesResolvesStyle(node: ESTree.CallExpression): boolean {
|
|
21
|
+
let current: ESTree.Node = node;
|
|
22
|
+
let parent: ESTree.Node | undefined = node.parent;
|
|
23
|
+
|
|
24
|
+
while (
|
|
25
|
+
parent !== undefined &&
|
|
26
|
+
parent.type === "MemberExpression" &&
|
|
27
|
+
!parent.computed &&
|
|
28
|
+
parent.object === current &&
|
|
29
|
+
parent.property.type === "Identifier"
|
|
30
|
+
) {
|
|
31
|
+
if (parent.property.name === "resolves" || parent.property.name === "rejects") {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
current = parent;
|
|
36
|
+
parent = parent.parent;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* `expect(...)` を含む式全体がすでに `await` されているかどうかを返します。
|
|
44
|
+
*
|
|
45
|
+
* `await expect(await ...).toBe(1)` のように、チェーンの外側で待たれている場合は `await` を追加しません。
|
|
46
|
+
*/
|
|
47
|
+
function isAlreadyAwaited(node: ESTree.CallExpression): boolean {
|
|
48
|
+
let outer: ESTree.Node = node;
|
|
49
|
+
let parent: ESTree.Node | undefined = node.parent;
|
|
50
|
+
|
|
51
|
+
while (parent !== undefined) {
|
|
52
|
+
if (parent.type === "ParenthesizedExpression" || parent.type === "ChainExpression") {
|
|
53
|
+
outer = parent;
|
|
54
|
+
parent = parent.parent;
|
|
55
|
+
} else if (parent.type === "MemberExpression" && parent.object === outer) {
|
|
56
|
+
outer = parent;
|
|
57
|
+
parent = parent.parent;
|
|
58
|
+
} else if (parent.type === "CallExpression" && parent.callee === outer) {
|
|
59
|
+
outer = parent;
|
|
60
|
+
parent = parent.parent;
|
|
61
|
+
} else {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return parent !== undefined && parent.type === "AwaitExpression";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* `expect(await ...)` の代わりに `await expect(...).resolves` / `.rejects` を使います。
|
|
71
|
+
*
|
|
72
|
+
* `expect()` の引数で `await` すると、拒否された Promise を `.rejects` で検証できません。
|
|
73
|
+
* 成功の検証には `await expect(...).resolves` を、失敗の検証には `await expect(...).rejects` を使ってください。
|
|
74
|
+
* 自動修正では `.resolves` を付けて書き換えるため、拒否の検証が必要な場合は手で `.rejects` に直してください。
|
|
75
|
+
*/
|
|
76
|
+
const noAwaitInExpect = defineRule({
|
|
77
|
+
meta: {
|
|
78
|
+
docs: {
|
|
79
|
+
description:
|
|
80
|
+
"Require `await expect(...).resolves` or `.rejects` instead of `expect(await ...)`.",
|
|
81
|
+
},
|
|
82
|
+
type: "problem",
|
|
83
|
+
fixable: "code",
|
|
84
|
+
messages: {
|
|
85
|
+
noAwaitInExpect:
|
|
86
|
+
"expect() の中で await しないでください。await expect(...).resolves / .rejects を使ってください。",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
create(context) {
|
|
90
|
+
const sourceCode = context.sourceCode;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
CallExpression(node): void {
|
|
94
|
+
if (node.callee.type !== "Identifier" || node.callee.name !== "expect") {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const first = node.arguments[0];
|
|
99
|
+
|
|
100
|
+
if (first === undefined || first.type === "SpreadElement") {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const awaited = unwrapParenthesized(first);
|
|
105
|
+
|
|
106
|
+
if (awaited.type !== "AwaitExpression") {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 入れ子の await はいちばん内側まで剥がして 1 回で修正します。
|
|
111
|
+
let inner = unwrapParenthesized(awaited.argument);
|
|
112
|
+
|
|
113
|
+
while (inner.type === "AwaitExpression") {
|
|
114
|
+
inner = unwrapParenthesized(inner.argument);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const resolvesStyle = usesResolvesStyle(node);
|
|
118
|
+
const prefix = resolvesStyle || isAlreadyAwaited(node) ? "" : "await ";
|
|
119
|
+
|
|
120
|
+
const text = sourceCode.text;
|
|
121
|
+
const replacement =
|
|
122
|
+
prefix +
|
|
123
|
+
text.slice(node.range[0], awaited.range[0]) +
|
|
124
|
+
text.slice(inner.range[0], inner.range[1]) +
|
|
125
|
+
text.slice(awaited.range[1], node.range[1]) +
|
|
126
|
+
(resolvesStyle ? "" : ".resolves");
|
|
127
|
+
context.report({
|
|
128
|
+
fix(fixer) {
|
|
129
|
+
return fixer.replaceTextRange([node.range[0], node.range[1]], replacement);
|
|
130
|
+
},
|
|
131
|
+
node,
|
|
132
|
+
messageId: "noAwaitInExpect",
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
export default noAwaitInExpect;
|