eslint-plugin-ocavue 0.0.1 → 0.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/README.md +34 -25
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -3
- package/dist/index.js.map +1 -1
- package/package.json +13 -7
- package/src/index.ts +17 -2
- package/src/rules/no-implicit-arrow-linebreak.test.ts +53 -0
- package/src/rules/no-implicit-arrow-linebreak.ts +65 -0
- package/src/index.test.ts +0 -7
package/README.md
CHANGED
|
@@ -2,43 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/eslint-plugin-ocavue)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Custom [ESLint](https://eslint.org/) rules from [ocavue](https://github.com/ocavue).
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Usage
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Requires ESLint 10 with flat config.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add -D eslint-plugin-ocavue
|
|
13
|
+
```
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
```js
|
|
16
|
+
// eslint.config.js
|
|
17
|
+
import ocavue from 'eslint-plugin-ocavue'
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
export default [
|
|
20
|
+
{
|
|
21
|
+
plugins: { ocavue },
|
|
22
|
+
rules: {
|
|
23
|
+
'ocavue/no-implicit-arrow-linebreak': 'error',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
]
|
|
27
|
+
```
|
|
17
28
|
|
|
18
|
-
|
|
19
|
-
| ---------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
20
|
-
| `pnpm dev` | Rebuild on change (`tsdown --watch`) |
|
|
21
|
-
| `pnpm build` | Bundle the library to `dist/` |
|
|
22
|
-
| `pnpm test` | Run tests with [Vitest](https://vitest.dev/) |
|
|
23
|
-
| `pnpm lint` | Lint with [ESLint](https://eslint.org/), [oxfmt](https://oxc.rs/docs/guide/usage/formatter), and [knip](https://knip.dev/) |
|
|
24
|
-
| `pnpm fix` | Auto-fix formatting and lint issues |
|
|
25
|
-
| `pnpm typecheck` | Type-check with `tsc` |
|
|
29
|
+
## Rules
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
### `no-implicit-arrow-linebreak`
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
Disallow a linebreak between `=>` and an implicit arrow function body. Unlike [`@stylistic/implicit-arrow-linebreak`](https://eslint.style/rules/implicit-arrow-linebreak), whose fixer joins the two lines into one (which Prettier then breaks again whenever the joined line exceeds `printWidth`, so `eslint --fix` and the formatter never converge), this rule's fixer converts the body to a block body with `return`. A block body is a stable shape for Prettier and oxfmt, so one round of `eslint --fix` plus the formatter converges.
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
```js
|
|
36
|
+
// Incorrect
|
|
37
|
+
const items = Array.from({ length: count }, (_, index) =>
|
|
38
|
+
createItem(index === 0 ? 'first item label' : `item number ${index}`),
|
|
39
|
+
)
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
pnpm build
|
|
36
|
-
pnpm publish
|
|
37
|
-
```
|
|
41
|
+
// Correct
|
|
42
|
+
const short = (index) => createItem(`item number ${index}`)
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
// Correct (what the fixer produces, after formatting)
|
|
45
|
+
const items = Array.from({ length: count }, (_, index) => {
|
|
46
|
+
return createItem(index === 0 ? 'first item label' : `item number ${index}`)
|
|
47
|
+
})
|
|
48
|
+
```
|
|
40
49
|
|
|
41
|
-
|
|
50
|
+
When a comment sits between `=>` and the body, the rule reports without autofixing so the fix never drops a comment.
|
|
42
51
|
|
|
43
52
|
## Sponsors
|
|
44
53
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
//#region src/index.d.ts
|
|
2
|
-
declare const
|
|
3
|
-
|
|
2
|
+
declare const plugin: {
|
|
3
|
+
meta: {
|
|
4
|
+
name: string;
|
|
5
|
+
version: string;
|
|
6
|
+
};
|
|
7
|
+
rules: {
|
|
8
|
+
'no-implicit-arrow-linebreak': import("eslint").Rule.RuleModule;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
4
11
|
//#endregion
|
|
5
|
-
export {
|
|
12
|
+
export { plugin as default };
|
|
6
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";cAMM"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,45 @@
|
|
|
1
|
+
//#endregion
|
|
1
2
|
//#region src/index.ts
|
|
2
|
-
const
|
|
3
|
-
|
|
3
|
+
const plugin = {
|
|
4
|
+
meta: {
|
|
5
|
+
name: "eslint-plugin-ocavue",
|
|
6
|
+
version: "0.1.0"
|
|
7
|
+
},
|
|
8
|
+
rules: { "no-implicit-arrow-linebreak": {
|
|
9
|
+
meta: {
|
|
10
|
+
type: "layout",
|
|
11
|
+
fixable: "code",
|
|
12
|
+
docs: {
|
|
13
|
+
description: "disallow a linebreak between `=>` and an implicit arrow function body, fixing it to a block body with `return`",
|
|
14
|
+
url: "https://github.com/ocavue/eslint-plugin-ocavue#no-implicit-arrow-linebreak"
|
|
15
|
+
},
|
|
16
|
+
messages: { linebreak: "Implicit arrow body must not start on a new line. Use a block body with `return` instead." },
|
|
17
|
+
schema: []
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
const sourceCode = context.sourceCode;
|
|
21
|
+
return { ArrowFunctionExpression(node) {
|
|
22
|
+
if (node.body.type === "BlockStatement") return;
|
|
23
|
+
const arrowToken = sourceCode.getTokenBefore(node.body, { filter: (token) => token.type === "Punctuator" && token.value === "=>" });
|
|
24
|
+
if (!arrowToken) return;
|
|
25
|
+
const firstBodyToken = sourceCode.getTokenAfter(arrowToken);
|
|
26
|
+
const lastToken = sourceCode.getLastToken(node);
|
|
27
|
+
if (!firstBodyToken || !lastToken) return;
|
|
28
|
+
if (arrowToken.loc.end.line === firstBodyToken.loc.start.line) return;
|
|
29
|
+
const canFix = !sourceCode.commentsExistBetween(arrowToken, firstBodyToken);
|
|
30
|
+
context.report({
|
|
31
|
+
node: node.body,
|
|
32
|
+
messageId: "linebreak",
|
|
33
|
+
fix: canFix ? (fixer) => {
|
|
34
|
+
const bodyText = sourceCode.text.slice(firstBodyToken.range[0], lastToken.range[1]);
|
|
35
|
+
return fixer.replaceTextRange([arrowToken.range[1], lastToken.range[1]], ` { return ${bodyText} }`);
|
|
36
|
+
} : void 0
|
|
37
|
+
});
|
|
38
|
+
} };
|
|
39
|
+
}
|
|
40
|
+
} }
|
|
41
|
+
};
|
|
4
42
|
//#endregion
|
|
5
|
-
export {
|
|
43
|
+
export { plugin as default };
|
|
6
44
|
|
|
7
45
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"index.js","names":["noImplicitArrowLinebreak"],"sources":["../package.json","../src/rules/no-implicit-arrow-linebreak.ts","../src/index.ts"],"sourcesContent":["","import type { Rule } from 'eslint'\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'layout',\n fixable: 'code',\n docs: {\n description:\n 'disallow a linebreak between `=>` and an implicit arrow function body, fixing it to a block body with `return`',\n url: 'https://github.com/ocavue/eslint-plugin-ocavue#no-implicit-arrow-linebreak',\n },\n messages: {\n linebreak:\n 'Implicit arrow body must not start on a new line. Use a block body with `return` instead.',\n },\n schema: [],\n },\n create(context) {\n const sourceCode = context.sourceCode\n return {\n ArrowFunctionExpression(node) {\n if (node.body.type === 'BlockStatement') {\n return\n }\n\n const arrowToken = sourceCode.getTokenBefore(node.body, {\n filter: (token) => token.type === 'Punctuator' && token.value === '=>',\n })\n if (!arrowToken) {\n return\n }\n const firstBodyToken = sourceCode.getTokenAfter(arrowToken)\n // The last token of the arrow function. When the body is wrapped in\n // parentheses, this is the closing `)`, so the fix keeps the parens.\n const lastToken = sourceCode.getLastToken(node)\n if (!firstBodyToken || !lastToken) {\n return\n }\n if (arrowToken.loc.end.line === firstBodyToken.loc.start.line) {\n return\n }\n\n // The fix rewrites the region between `=>` and the body, so bail out\n // of fixing (but still report) if that region contains comments.\n const canFix = !sourceCode.commentsExistBetween(arrowToken, firstBodyToken)\n\n context.report({\n node: node.body,\n messageId: 'linebreak',\n fix: canFix\n ? (fixer) => {\n const bodyText = sourceCode.text.slice(firstBodyToken.range[0], lastToken.range[1])\n return fixer.replaceTextRange(\n [arrowToken.range[1], lastToken.range[1]],\n ` { return ${bodyText} }`,\n )\n }\n : undefined,\n })\n },\n }\n },\n}\n\nexport default rule\n","import type { ESLint } from 'eslint'\n\nimport { version } from '../package.json'\n\nimport noImplicitArrowLinebreak from './rules/no-implicit-arrow-linebreak'\n\nconst plugin = {\n meta: {\n name: 'eslint-plugin-ocavue',\n version,\n },\n rules: {\n 'no-implicit-arrow-linebreak': noImplicitArrowLinebreak,\n },\n} satisfies ESLint.Plugin\n\nexport default plugin\n"],"mappings":";;AEMA,MAAM,SAAS;CACb,MAAM;EACJ,MAAM;EACN;CACF;CACA,OAAO,EACL,+BAA+BA;EDTjC,MAAM;GACJ,MAAM;GACN,SAAS;GACT,MAAM;IACJ,aACE;IACF,KAAK;GACP;GACA,UAAU,EACR,WACE,4FACJ;GACA,QAAQ,CAAC;EACX;EACA,OAAO,SAAS;GACd,MAAM,aAAa,QAAQ;GAC3B,OAAO,EACL,wBAAwB,MAAM;IAC5B,IAAI,KAAK,KAAK,SAAS,kBACrB;IAGF,MAAM,aAAa,WAAW,eAAe,KAAK,MAAM,EACtD,SAAS,UAAU,MAAM,SAAS,gBAAgB,MAAM,UAAU,KACpE,CAAC;IACD,IAAI,CAAC,YACH;IAEF,MAAM,iBAAiB,WAAW,cAAc,UAAU;IAG1D,MAAM,YAAY,WAAW,aAAa,IAAI;IAC9C,IAAI,CAAC,kBAAkB,CAAC,WACtB;IAEF,IAAI,WAAW,IAAI,IAAI,SAAS,eAAe,IAAI,MAAM,MACvD;IAKF,MAAM,SAAS,CAAC,WAAW,qBAAqB,YAAY,cAAc;IAE1E,QAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,KAAK,UACA,UAAU;MACT,MAAM,WAAW,WAAW,KAAK,MAAM,eAAe,MAAM,IAAI,UAAU,MAAM,EAAE;MAClF,OAAO,MAAM,iBACX,CAAC,WAAW,MAAM,IAAI,UAAU,MAAM,EAAE,GACxC,aAAa,SAAS,GACxB;KACF,IACA,KAAA;IACN,CAAC;GACH,EACF;EACF;CCjDiCA,EACjC;AACF"}
|
package/package.json
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-ocavue",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Custom ESLint rules from ocavue",
|
|
6
6
|
"author": "ocavue <ocavue@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"funding": "https://github.com/sponsors/ocavue",
|
|
9
|
-
"homepage": "https://github.com/ocavue/
|
|
9
|
+
"homepage": "https://github.com/ocavue/eslint-plugin-ocavue#readme",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "https://github.com/ocavue/
|
|
12
|
+
"url": "https://github.com/ocavue/eslint-plugin-ocavue.git"
|
|
13
13
|
},
|
|
14
|
-
"bugs": "https://github.com/ocavue/
|
|
15
|
-
"keywords": [
|
|
14
|
+
"bugs": "https://github.com/ocavue/eslint-plugin-ocavue/issues",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"eslint",
|
|
17
|
+
"eslintplugin",
|
|
18
|
+
"eslint-plugin"
|
|
19
|
+
],
|
|
16
20
|
"sideEffects": false,
|
|
17
21
|
"main": "./dist/index.js",
|
|
18
22
|
"module": "./dist/index.js",
|
|
@@ -27,6 +31,9 @@
|
|
|
27
31
|
"dist",
|
|
28
32
|
"src"
|
|
29
33
|
],
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"eslint": "^10.0.0"
|
|
36
|
+
},
|
|
30
37
|
"devDependencies": {
|
|
31
38
|
"@ocavue/eslint-config": "^4.13.5",
|
|
32
39
|
"@ocavue/tsconfig": "^0.7.1",
|
|
@@ -36,7 +43,6 @@
|
|
|
36
43
|
"oxfmt": "^0.65.0",
|
|
37
44
|
"tsdown": "^0.22.14",
|
|
38
45
|
"typescript": "^6.0.3",
|
|
39
|
-
"vite": "^8.2.2",
|
|
40
46
|
"vitest": "^4.1.11"
|
|
41
47
|
},
|
|
42
48
|
"scripts": {
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { ESLint } from 'eslint'
|
|
2
|
+
|
|
3
|
+
import { version } from '../package.json'
|
|
4
|
+
|
|
5
|
+
import noImplicitArrowLinebreak from './rules/no-implicit-arrow-linebreak'
|
|
6
|
+
|
|
7
|
+
const plugin = {
|
|
8
|
+
meta: {
|
|
9
|
+
name: 'eslint-plugin-ocavue',
|
|
10
|
+
version,
|
|
11
|
+
},
|
|
12
|
+
rules: {
|
|
13
|
+
'no-implicit-arrow-linebreak': noImplicitArrowLinebreak,
|
|
14
|
+
},
|
|
15
|
+
} satisfies ESLint.Plugin
|
|
16
|
+
|
|
17
|
+
export default plugin
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { RuleTester } from 'eslint'
|
|
2
|
+
import { test } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import rule from './no-implicit-arrow-linebreak'
|
|
5
|
+
|
|
6
|
+
test('no-implicit-arrow-linebreak', () => {
|
|
7
|
+
const tester = new RuleTester({
|
|
8
|
+
languageOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
tester.run('no-implicit-arrow-linebreak', rule, {
|
|
12
|
+
valid: [
|
|
13
|
+
'const f = () => fn(123)',
|
|
14
|
+
'const f = () => {\n return fn(123)\n}',
|
|
15
|
+
'const f = () => (\n fn(123)\n)',
|
|
16
|
+
'const f = async () => fn(123)',
|
|
17
|
+
'const f = (a) => (b) => a + b',
|
|
18
|
+
],
|
|
19
|
+
invalid: [
|
|
20
|
+
{
|
|
21
|
+
code: 'const f = () =>\n fn(123)',
|
|
22
|
+
output: 'const f = () => { return fn(123) }',
|
|
23
|
+
errors: [{ messageId: 'linebreak' }],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
code: 'const f = () =>\n (fn(123))',
|
|
27
|
+
output: 'const f = () => { return (fn(123)) }',
|
|
28
|
+
errors: [{ messageId: 'linebreak' }],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
code: 'const f = (a, b) =>\n a + b',
|
|
32
|
+
output: 'const f = (a, b) => { return a + b }',
|
|
33
|
+
errors: [{ messageId: 'linebreak' }],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
code: 'const f = async (a) =>\n await fn(a)',
|
|
37
|
+
output: 'const f = async (a) => { return await fn(a) }',
|
|
38
|
+
errors: [{ messageId: 'linebreak' }],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
code: 'const f = (a) => (b) =>\n a + b',
|
|
42
|
+
output: 'const f = (a) => (b) => { return a + b }',
|
|
43
|
+
errors: [{ messageId: 'linebreak' }],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
// No autofix when a comment sits between `=>` and the body.
|
|
47
|
+
code: 'const f = () => // note\n fn(123)',
|
|
48
|
+
output: null,
|
|
49
|
+
errors: [{ messageId: 'linebreak' }],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Rule } from 'eslint'
|
|
2
|
+
|
|
3
|
+
const rule: Rule.RuleModule = {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'layout',
|
|
6
|
+
fixable: 'code',
|
|
7
|
+
docs: {
|
|
8
|
+
description:
|
|
9
|
+
'disallow a linebreak between `=>` and an implicit arrow function body, fixing it to a block body with `return`',
|
|
10
|
+
url: 'https://github.com/ocavue/eslint-plugin-ocavue#no-implicit-arrow-linebreak',
|
|
11
|
+
},
|
|
12
|
+
messages: {
|
|
13
|
+
linebreak:
|
|
14
|
+
'Implicit arrow body must not start on a new line. Use a block body with `return` instead.',
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
const sourceCode = context.sourceCode
|
|
20
|
+
return {
|
|
21
|
+
ArrowFunctionExpression(node) {
|
|
22
|
+
if (node.body.type === 'BlockStatement') {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const arrowToken = sourceCode.getTokenBefore(node.body, {
|
|
27
|
+
filter: (token) => token.type === 'Punctuator' && token.value === '=>',
|
|
28
|
+
})
|
|
29
|
+
if (!arrowToken) {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
const firstBodyToken = sourceCode.getTokenAfter(arrowToken)
|
|
33
|
+
// The last token of the arrow function. When the body is wrapped in
|
|
34
|
+
// parentheses, this is the closing `)`, so the fix keeps the parens.
|
|
35
|
+
const lastToken = sourceCode.getLastToken(node)
|
|
36
|
+
if (!firstBodyToken || !lastToken) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
if (arrowToken.loc.end.line === firstBodyToken.loc.start.line) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The fix rewrites the region between `=>` and the body, so bail out
|
|
44
|
+
// of fixing (but still report) if that region contains comments.
|
|
45
|
+
const canFix = !sourceCode.commentsExistBetween(arrowToken, firstBodyToken)
|
|
46
|
+
|
|
47
|
+
context.report({
|
|
48
|
+
node: node.body,
|
|
49
|
+
messageId: 'linebreak',
|
|
50
|
+
fix: canFix
|
|
51
|
+
? (fixer) => {
|
|
52
|
+
const bodyText = sourceCode.text.slice(firstBodyToken.range[0], lastToken.range[1])
|
|
53
|
+
return fixer.replaceTextRange(
|
|
54
|
+
[arrowToken.range[1], lastToken.range[1]],
|
|
55
|
+
` { return ${bodyText} }`,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
: undefined,
|
|
59
|
+
})
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default rule
|