eslint-plugin-lube 0.7.0 → 0.8.1
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/CHANGELOG.md +23 -0
- package/README.md +2 -0
- package/docs/prefer-param-tag.md +25 -0
- package/docs/svelte-naming-convention.md +1 -0
- package/package.json +2 -7
- package/src/index.js +3 -0
- package/src/rules/ascii-order.js +2 -7
- package/src/rules/prefer-param-tag.js +252 -0
- package/src/rules/pretty-imports.js +0 -3
- package/src/rules/pretty-jsdoc-casting.js +6 -14
- package/src/rules/pretty-sequence.js +3 -8
- package/src/rules/pretty-ternary.js +4 -12
- package/src/rules/svelte-naming-convention.js +37 -30
- package/src/utils/skip_hole_commas.js +4 -6
- package/types/index.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.1
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `svelte-naming-convention` no longer reports or renames an ambient TypeScript declaration (`declare function`, `declare const`, `declare let`, `declare var`, `declare class`, and anything inside `declare global { }` or `declare module "m" { }`) or its usages, since the name belongs to the host that injects it.
|
|
8
|
+
- `svelte-naming-convention` renames every overload signature and the implementation of a function together with its calls, instead of only the calls.
|
|
9
|
+
|
|
10
|
+
## 0.8.0
|
|
11
|
+
|
|
12
|
+
### Breaking
|
|
13
|
+
|
|
14
|
+
- **The strict config moves inline parameter types into `@param` tags** with the new
|
|
15
|
+
`prefer-param-tag`: `function f(/** @type {T} */ a) {}` becomes `@param {T} a` in the JSDoc block of
|
|
16
|
+
the declaration. It has a fix, so run `eslint --fix` once; a destructured parameter is left to you.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `prefer-param-tag`, in the strict config: reports a function declaration whose parameters are typed
|
|
21
|
+
with inline comments, `function f(/** @type {T} */ a) {}`, and fixes it into `@param {T} a` tags in the
|
|
22
|
+
JSDoc block of the declaration, creating the block if there is none, each tag at the position of its
|
|
23
|
+
parameter among the existing `@param` tags. Arrow functions and function expressions stay, and a
|
|
24
|
+
destructured parameter is reported without a fix.
|
|
25
|
+
|
|
3
26
|
## 0.7.0
|
|
4
27
|
|
|
5
28
|
### Breaking
|
package/README.md
CHANGED
|
@@ -89,6 +89,7 @@ Both configs are flat config objects with `plugins` and `rules`, and differ in h
|
|
|
89
89
|
|
|
90
90
|
**`configs.strict`** is the house style of this repository, and several of its rules cannot be fixed. It is everything in `recommended` plus:
|
|
91
91
|
- `svelte-naming-convention`, which renames declarations to snake_case.
|
|
92
|
+
- `prefer-param-tag`, which moves the inline `/** @type {T} */` comments of a function declaration's parameters into `@param` tags on the declaration ([prefer-param-tag](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/prefer-param-tag.md)).
|
|
92
93
|
- `prefer-type-tag`, which moves the cast of a declaration's value into a `@type` tag on the declaration, in a project linted with type information ([prefer-type-tag](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/prefer-type-tag.md) shows the parser options).
|
|
93
94
|
- `ascii-order` for import statements, object keys, function declarations and test cases too (see below).
|
|
94
95
|
- `pretty-sequence` and `pretty-imports` at 30 characters.
|
|
@@ -111,6 +112,7 @@ Override a rule after the spread: `rules: { ...lube.configs.recommended.rules, "
|
|
|
111
112
|
| --- | --- | --- |
|
|
112
113
|
| [ascii-order](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/ascii-order.md) | ASCII order of import statements, import and export names, object keys, function declarations and test cases | names only |
|
|
113
114
|
| [curly](https://eslint.org/docs/latest/rules/curly) | Braces around the bodies of `if`, `else`, `for`, `while` and `do`, as ESLint's own rule does, with a fix that keeps a comment in front of the body, such as a JSDoc cast, inside the braces | no |
|
|
115
|
+
| [prefer-param-tag](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/prefer-param-tag.md) | JSDoc `@param` tags on a function declaration instead of inline `/** @type {T} */` comments in its parameter list | no |
|
|
114
116
|
| [prefer-type-tag](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/prefer-type-tag.md) | A JSDoc `@type` tag on a declaration instead of a cast of its value, wherever the tag would type check (needs type information) | no |
|
|
115
117
|
| [pretty-imports](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/pretty-imports.md) | Import and export names on one line when they fit, or one per line | yes |
|
|
116
118
|
| [pretty-jsdoc-casting](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/pretty-jsdoc-casting.md) | JSDoc type casts written as `/** @type {T} */(value)/**/` | yes |
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# prefer-param-tag
|
|
2
|
+
Enforces JSDoc `@param` tags on a function declaration, instead of inline `/** @type {T} */` comments inside its parameter list.
|
|
3
|
+
|
|
4
|
+
- Only function declarations are checked, `export` and `export default` ones too. Arrow functions and function expressions, such as `items.map((/** @type {string} */ x) => x)`, have no declaration to carry a tag and are left alone, and so are casts in a body.
|
|
5
|
+
- The fix removes the inline comments and adds one `@param {T} name` line per parameter to the JSDoc block above the declaration, in parameter order, after the existing `@param` tags and before `@returns` and the like. It creates the block when there is none, and keeps the indentation of the declaration. A `@param` tag that already names the parameter is not repeated.
|
|
6
|
+
- A default value, `/** @type {number} */ n = 1`, is typed by `@param {number} n` too.
|
|
7
|
+
- A destructured or rest parameter has no simple name to write in a tag and is reported without a fix. So is a declaration that does not start its line, where a block cannot be added.
|
|
8
|
+
|
|
9
|
+
## options
|
|
10
|
+
None.
|
|
11
|
+
|
|
12
|
+
## Valid
|
|
13
|
+
```js
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} tx
|
|
16
|
+
*/
|
|
17
|
+
function use(tx) {}
|
|
18
|
+
items.map((/** @type {string} */ x) => x)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Invalid
|
|
22
|
+
```js
|
|
23
|
+
function use(/** @type {string} */ tx) {}
|
|
24
|
+
async function source(/** @type {unknown} */ n, /** @type {number} */ m = 1) {}
|
|
25
|
+
```
|
|
@@ -6,6 +6,7 @@ The strict config turns it on and the recommended config leaves it off, since it
|
|
|
6
6
|
- The message names the expected name, `Rename 'camelCase' to 'camel_case'`, or, for a name without a snake_case form such as `NO_SNAKE__CASE`, asks for snake_case, UPPER_SNAKE_CASE or PascalCase.
|
|
7
7
|
- Usages such as assignments, function bodies, `extends` clauses, JSX and TypeScript expressions are only renamed along with their declaration; `fixSameNames` sets the order.
|
|
8
8
|
- Usages are matched to declarations by scope, so a global, an import or an exported camelCase function of the same name is left as it is.
|
|
9
|
+
- An ambient TypeScript declaration, `declare function acquireVsCodeApi(): Api`, `declare const`, `declare class`, or anything inside `declare global { }` or `declare module "m" { }`, names a binding the code does not own: it is neither reported nor renamed, and neither are its usages.
|
|
9
10
|
- A declaration exported with `export { camelCase }` is renamed together with the export specifier, `export { camel_case as camelCase }`.
|
|
10
11
|
- The JSDoc of a renamed parameter follows it: `@param {T} [camelCase]`, `{camelCase is T}` and `{typeof camelCase}`.
|
|
11
12
|
- In a `.svelte` file, the store subscriptions of a renamed store are renamed with it, `$camelCase` into `$camel_case`, in the same fix, and so are the usages in the markup: mustache tags, attributes, directives and the expression of `{#if}`, `{#each}`, `{#await}` and `{#key}`. A shorthand attribute is written out, `{camelCase}` into `camelCase={camel_case}`, so the name of the property stays. Names the markup declares, the context of `{#each}`, the value of `{#await}` and a `let:` property, are not reported.
|
package/package.json
CHANGED
|
@@ -7,11 +7,6 @@
|
|
|
7
7
|
"@stylistic/eslint-plugin": "^5.10.0"
|
|
8
8
|
},
|
|
9
9
|
"description": "Fixable ESLint rules that format code without Prettier",
|
|
10
|
-
"devDependencies": {
|
|
11
|
-
"@types/estree": "^1.0.9",
|
|
12
|
-
"@typescript/native": "npm:typescript@^7.0.2",
|
|
13
|
-
"eslint": "^10.10.0"
|
|
14
|
-
},
|
|
15
10
|
"engines": {
|
|
16
11
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
17
12
|
},
|
|
@@ -53,10 +48,10 @@
|
|
|
53
48
|
},
|
|
54
49
|
"type": "module",
|
|
55
50
|
"types": "types/index.d.ts",
|
|
56
|
-
"version": "0.
|
|
51
|
+
"version": "0.8.1",
|
|
57
52
|
"scripts": {
|
|
58
53
|
"dts": "node ../../scripts/tsc_with_clean.js eslint-plugin-lube",
|
|
59
54
|
"lint": "pnpm -w lint",
|
|
60
|
-
"test": "pnpm -w exec vitest run --coverage test/eslint-plugin-lube"
|
|
55
|
+
"test": "pnpm -w exec vitest run --coverage --coverage.include=packages/eslint-plugin-lube/src/** test/eslint-plugin-lube"
|
|
61
56
|
}
|
|
62
57
|
}
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ascii_order from "./rules/ascii-order.js"
|
|
2
|
+
import prefer_param_tag from "./rules/prefer-param-tag.js"
|
|
2
3
|
import prefer_type_tag from "./rules/prefer-type-tag.js"
|
|
3
4
|
import pretty_imports from "./rules/pretty-imports.js"
|
|
4
5
|
import pretty_jsdoc_casting from "./rules/pretty-jsdoc-casting.js"
|
|
@@ -152,6 +153,7 @@ const strict_rules = {
|
|
|
152
153
|
}
|
|
153
154
|
],
|
|
154
155
|
"lube/curly": [ "error", "all" ],
|
|
156
|
+
"lube/prefer-param-tag": "error",
|
|
155
157
|
"lube/prefer-type-tag": "error",
|
|
156
158
|
"lube/pretty-imports": "error",
|
|
157
159
|
"lube/pretty-jsdoc-casting": "error",
|
|
@@ -246,6 +248,7 @@ const plugin = {
|
|
|
246
248
|
"curly": keep_body_comments(
|
|
247
249
|
/** @type {import("eslint").Rule.RuleModule} */(builtinRules.get("curly"))/**/
|
|
248
250
|
),
|
|
251
|
+
"prefer-param-tag": prefer_param_tag,
|
|
249
252
|
"prefer-type-tag": prefer_type_tag,
|
|
250
253
|
"pretty-imports": pretty_imports,
|
|
251
254
|
"pretty-jsdoc-casting": pretty_jsdoc_casting,
|
package/src/rules/ascii-order.js
CHANGED
|
@@ -251,13 +251,8 @@ function create_reorder(context) {
|
|
|
251
251
|
* @returns {string}
|
|
252
252
|
*/
|
|
253
253
|
function get_snippet(node) {
|
|
254
|
-
const value = node.type == "Property"
|
|
255
|
-
? node.computed && node.key.type != "Literal"
|
|
256
|
-
? node.key
|
|
257
|
-
: node.value
|
|
258
|
-
: node
|
|
259
254
|
const snippet = source_code.getText(
|
|
260
|
-
/** @type {import("estree").
|
|
255
|
+
/** @type {import("estree").Property} */(node)/**/.value
|
|
261
256
|
).replace(/\s+/g, " ")
|
|
262
257
|
return snippet.length > 30 ? snippet.slice(0, 29) + "…" : snippet
|
|
263
258
|
}
|
|
@@ -341,7 +336,7 @@ function get_name(node) {
|
|
|
341
336
|
case "TemplateLiteral":
|
|
342
337
|
return node.expressions.length
|
|
343
338
|
? undefined
|
|
344
|
-
: /** @type {import("estree").TemplateElement} */(node.quasis[0])/**/.value.cooked
|
|
339
|
+
: /** @type {string} */(/** @type {import("estree").TemplateElement} */(node.quasis[0])/**/.value.cooked)/**/
|
|
345
340
|
}
|
|
346
341
|
return undefined
|
|
347
342
|
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
const blank_regex = /^\s*\*?\s*$/
|
|
2
|
+
const directive_regex = /^\s*(?:eslint-disable-next-line\b|@ts-(?:expect-error|ignore)\b)/
|
|
3
|
+
const line_break_regex = /\s*(?:\r\n|[\n\r\p{Zl}\p{Zp}])\s*/gu
|
|
4
|
+
const line_terminator_regex = /\r\n|[\n\r\p{Zl}\p{Zp}]/u
|
|
5
|
+
const param_tag_regex = /^\s*\*\s*@param\b/
|
|
6
|
+
const space_regex = /^\s*$/
|
|
7
|
+
const tag_after_param_regex = /^\s*\*\s*@(?:deprecated|example|returns?|see|throws|yields?)\b/
|
|
8
|
+
const tag_regex = /^\s*\*\s*@/
|
|
9
|
+
const type_regex = /^\*\s*@type\s*(\{[^]*\})\s*$/
|
|
10
|
+
const white_space_regex = /\s/
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} text
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
function escape_regex(text) {
|
|
16
|
+
return text.replace(/[$()*+.?[\\\]^{|}]/g, "\\$&")
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* @param {import("estree").Comment} comment
|
|
20
|
+
* @param {string} indent
|
|
21
|
+
* @param {string} eol
|
|
22
|
+
* @param {{ index: number, tag: string }[]} tags
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
function jsdoc_with(comment, indent, eol, tags) {
|
|
26
|
+
let rows = `/*${comment.value}*/`.split(line_terminator_regex)
|
|
27
|
+
/**
|
|
28
|
+
* @param {number} index
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
function row_at(index) {
|
|
32
|
+
return /** @type {string} */(rows[index])/**/
|
|
33
|
+
}
|
|
34
|
+
if (rows.length < 2 || !/^\/\*\*\s*$/.test(row_at(0)) || !/^\s*\*\/$/.test(row_at(rows.length - 1))) {
|
|
35
|
+
const body = comment.value.slice(1).split(line_terminator_regex)
|
|
36
|
+
.map(
|
|
37
|
+
row => row.replace(/^\s*\*?\s?/, "").trimEnd()
|
|
38
|
+
)
|
|
39
|
+
while (body[0] === "") {
|
|
40
|
+
body.shift()
|
|
41
|
+
}
|
|
42
|
+
while (body.at(-1) === "") {
|
|
43
|
+
body.pop()
|
|
44
|
+
}
|
|
45
|
+
rows = [
|
|
46
|
+
"/**",
|
|
47
|
+
...body.map(
|
|
48
|
+
row => `${indent} * ${row}`.trimEnd()
|
|
49
|
+
),
|
|
50
|
+
`${indent} */`
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
for (const { index, tag } of tags) {
|
|
54
|
+
const rows_end = rows.length - 1
|
|
55
|
+
/** @type {{ end: number, start: number }[]} */
|
|
56
|
+
const blocks = []
|
|
57
|
+
for (let i = 1; i < rows_end; i++) {
|
|
58
|
+
if (param_tag_regex.test(row_at(i))) {
|
|
59
|
+
let end = i + 1
|
|
60
|
+
while (end < rows_end && !tag_regex.test(row_at(end)) && !blank_regex.test(row_at(end))) {
|
|
61
|
+
end++
|
|
62
|
+
}
|
|
63
|
+
blocks.push({ end, start: i })
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
let at = rows_end
|
|
67
|
+
const first = blocks[0]
|
|
68
|
+
if (first) {
|
|
69
|
+
at = index == 0
|
|
70
|
+
? first.start
|
|
71
|
+
: /** @type {{ end: number, start: number }} */(blocks[Math.min(index, blocks.length) - 1])/**/.end
|
|
72
|
+
} else {
|
|
73
|
+
const tag_at = rows.findIndex(
|
|
74
|
+
row => tag_after_param_regex.test(row)
|
|
75
|
+
)
|
|
76
|
+
if (tag_at >= 0) {
|
|
77
|
+
at = tag_at
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const added = [ `${indent} * ${tag}` ]
|
|
81
|
+
if (at == rows_end && at > 1 && !tag_regex.test(row_at(at - 1)) && !blank_regex.test(row_at(at - 1))) {
|
|
82
|
+
added.unshift(`${indent} *`)
|
|
83
|
+
}
|
|
84
|
+
rows.splice(at, 0, ...added)
|
|
85
|
+
}
|
|
86
|
+
return rows.join(eol)
|
|
87
|
+
}
|
|
88
|
+
/** @type {import("eslint").Rule.RuleModule} */
|
|
89
|
+
export default {
|
|
90
|
+
create(context) {
|
|
91
|
+
const source_code = context.sourceCode
|
|
92
|
+
const text = source_code.text
|
|
93
|
+
/**
|
|
94
|
+
* @param {import("estree").Comment} comment
|
|
95
|
+
* @returns {import("eslint").AST.Range}
|
|
96
|
+
*/
|
|
97
|
+
function comment_range(comment) {
|
|
98
|
+
return /** @type {import("eslint").AST.Range} */(comment.range)/**/
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* @param {number} position
|
|
102
|
+
* @returns {number}
|
|
103
|
+
*/
|
|
104
|
+
function line_start_of(position) {
|
|
105
|
+
let start = position
|
|
106
|
+
while (start > 0 && !line_terminator_regex.test(text.charAt(start - 1))) {
|
|
107
|
+
start--
|
|
108
|
+
}
|
|
109
|
+
return start
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* @param {import("estree").Comment} comment
|
|
113
|
+
* @returns {boolean}
|
|
114
|
+
*/
|
|
115
|
+
function starts_line(comment) {
|
|
116
|
+
const start = comment_range(comment)[0]
|
|
117
|
+
return space_regex.test(
|
|
118
|
+
text.slice(line_start_of(start), start)
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
FunctionDeclaration(node) {
|
|
123
|
+
/** @type {{ comment: import("estree").Comment, index: number, name: string, type: string }[]} */
|
|
124
|
+
const named = []
|
|
125
|
+
const data = {
|
|
126
|
+
name: node.id?.name ?? "default"
|
|
127
|
+
}
|
|
128
|
+
for (const [ index, param ] of node.params.entries()) {
|
|
129
|
+
const comment = source_code.getCommentsBefore(param).findLast(
|
|
130
|
+
item => item.type == "Block" && type_regex.test(item.value)
|
|
131
|
+
)
|
|
132
|
+
if (!comment) {
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
const target = param.type == "AssignmentPattern" ? param.left : param
|
|
136
|
+
if (target.type == "Identifier") {
|
|
137
|
+
named.push(
|
|
138
|
+
{
|
|
139
|
+
comment,
|
|
140
|
+
index,
|
|
141
|
+
name: target.name,
|
|
142
|
+
type: /** @type {string} */(/** @type {RegExpExecArray} */(type_regex.exec(comment.value))/**/[1])/**/.replace(line_break_regex, " ")
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
} else {
|
|
146
|
+
context.report(
|
|
147
|
+
{
|
|
148
|
+
data,
|
|
149
|
+
loc: /** @type {import("estree").SourceLocation} */(comment.loc)/**/,
|
|
150
|
+
messageId: "name"
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const first = named[0]
|
|
156
|
+
if (!first) {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
const loc = /** @type {import("estree").SourceLocation} */(first.comment.loc)/**/
|
|
160
|
+
const statement = node.parent.type == "ExportNamedDeclaration" || node.parent.type == "ExportDefaultDeclaration"
|
|
161
|
+
? node.parent
|
|
162
|
+
: node
|
|
163
|
+
const leading = source_code.getCommentsBefore(statement)
|
|
164
|
+
let start = /** @type {import("eslint").AST.Range} */(statement.range)/**/[0]
|
|
165
|
+
let last = leading.length - 1
|
|
166
|
+
while (last >= 0) {
|
|
167
|
+
const item = /** @type {import("estree").Comment} */(leading[last])/**/
|
|
168
|
+
if (!starts_line(item) || !directive_regex.test(item.value)) {
|
|
169
|
+
break
|
|
170
|
+
}
|
|
171
|
+
start = comment_range(item)[0]
|
|
172
|
+
last--
|
|
173
|
+
}
|
|
174
|
+
const candidate = leading[last]
|
|
175
|
+
const jsdoc = candidate?.type == "Block" && candidate.value.startsWith("*")
|
|
176
|
+
? candidate
|
|
177
|
+
: undefined
|
|
178
|
+
const anchor = jsdoc ? comment_range(jsdoc)[0] : start
|
|
179
|
+
const indent = text.slice(line_start_of(anchor), anchor)
|
|
180
|
+
if (!space_regex.test(indent)) {
|
|
181
|
+
context.report(
|
|
182
|
+
{ data, loc, messageId: "hand" }
|
|
183
|
+
)
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
const eol = text.match(line_terminator_regex)?.[0] ?? "\n"
|
|
187
|
+
const tags = named.filter(
|
|
188
|
+
item => !jsdoc || !new RegExp(
|
|
189
|
+
`@param\\s+(?:\\{[^]*?\\}\\s+)?\\[?${escape_regex(item.name)}(?![\\w$])`
|
|
190
|
+
).test(jsdoc.value)
|
|
191
|
+
).map(
|
|
192
|
+
item => (
|
|
193
|
+
{
|
|
194
|
+
index: item.index,
|
|
195
|
+
tag: `@param ${item.type} ${item.name}`
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
context.report(
|
|
200
|
+
{
|
|
201
|
+
data,
|
|
202
|
+
fix(fixer) {
|
|
203
|
+
const fixes = named.map(
|
|
204
|
+
({ comment }) => {
|
|
205
|
+
const [ begin, close ] = comment_range(comment)
|
|
206
|
+
let end = close
|
|
207
|
+
while (white_space_regex.test(text.charAt(end))) {
|
|
208
|
+
end++
|
|
209
|
+
}
|
|
210
|
+
return fixer.removeRange([ begin, end ])
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
if (jsdoc) {
|
|
214
|
+
fixes.push(
|
|
215
|
+
fixer.replaceTextRange(
|
|
216
|
+
comment_range(jsdoc),
|
|
217
|
+
jsdoc_with(jsdoc, indent, eol, tags)
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
} else {
|
|
221
|
+
fixes.push(
|
|
222
|
+
fixer.insertTextBeforeRange(
|
|
223
|
+
[ start, start ],
|
|
224
|
+
`/**${eol}${tags.map(({ tag }) => `${indent} * ${tag}${eol}`).join("")}${indent} */${eol}${indent}`
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
return fixes
|
|
229
|
+
},
|
|
230
|
+
loc,
|
|
231
|
+
messageId: "tag"
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
meta: {
|
|
238
|
+
docs: {
|
|
239
|
+
description: "Enforces JSDoc `@param` tags on a function declaration instead of inline `/** @type {T} */` comments inside its parameter list.",
|
|
240
|
+
recommended: false,
|
|
241
|
+
url: "https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/prefer-param-tag.md"
|
|
242
|
+
},
|
|
243
|
+
fixable: "code",
|
|
244
|
+
messages: {
|
|
245
|
+
hand: "Type the parameters of `{{name}}` with `@param` tags on the declaration instead of inline comments; it is not fixed automatically because the declaration or its JSDoc comment does not start its line, so write the tags by hand",
|
|
246
|
+
name: "Type this parameter of `{{name}}` with a `@param` tag on the declaration instead of an inline comment; it is not fixed automatically because the parameter has no simple name to write in the tag, so document it by hand",
|
|
247
|
+
tag: "Type the parameters of `{{name}}` with `@param` tags on the declaration instead of inline `@type` comments"
|
|
248
|
+
},
|
|
249
|
+
schema: [],
|
|
250
|
+
type: "suggestion"
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -36,10 +36,7 @@ export default {
|
|
|
36
36
|
* @returns {boolean}
|
|
37
37
|
*/
|
|
38
38
|
function is_arguments(position) {
|
|
39
|
-
const node = /** @type {import("../../private.js").AstNode
|
|
40
|
-
if (!node) {
|
|
41
|
-
return false
|
|
42
|
-
}
|
|
39
|
+
const node = /** @type {import("../../private.js").AstNode} */(/** @type {unknown} */(source_code.getNodeByRangeIndex(position)))/**/
|
|
43
40
|
const type = node.type
|
|
44
41
|
if (type != "CallExpression" && type != "ImportExpression" && type != "NewExpression") {
|
|
45
42
|
return false
|
|
@@ -73,7 +70,7 @@ export default {
|
|
|
73
70
|
* @param {number} start
|
|
74
71
|
* @param {number} end
|
|
75
72
|
* @param {[number, number][]} removed
|
|
76
|
-
* @returns {string
|
|
73
|
+
* @returns {string}
|
|
77
74
|
*/
|
|
78
75
|
function render_without(start, end, removed) {
|
|
79
76
|
let position = start
|
|
@@ -82,9 +79,6 @@ export default {
|
|
|
82
79
|
if (removed_end <= start || removed_start >= end) {
|
|
83
80
|
continue
|
|
84
81
|
}
|
|
85
|
-
if (source.crosses_edit(removed_start) || source.crosses_edit(removed_end)) {
|
|
86
|
-
return null
|
|
87
|
-
}
|
|
88
82
|
let chunk_end = removed_start
|
|
89
83
|
if (text[removed_start] == ")") {
|
|
90
84
|
while (chunk_end > position && is_space(text.charCodeAt(chunk_end - 1))) {
|
|
@@ -239,12 +233,10 @@ export default {
|
|
|
239
233
|
const prefix = render_without(left, content_start, removed)
|
|
240
234
|
const suffix = render_without(content_end, right, removed)
|
|
241
235
|
if (
|
|
242
|
-
prefix
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
text.slice(Math.max(0, left - 7), left)
|
|
247
|
-
)
|
|
236
|
+
line_break_regex.test(prefix)
|
|
237
|
+
&& restricted_regex.test(
|
|
238
|
+
text.slice(Math.max(0, left - 7), left)
|
|
239
|
+
)
|
|
248
240
|
) {
|
|
249
241
|
return
|
|
250
242
|
}
|
|
@@ -73,12 +73,8 @@ export default {
|
|
|
73
73
|
if (left <= start || text[left - 1] != "(") {
|
|
74
74
|
break
|
|
75
75
|
}
|
|
76
|
-
const right = source.skip_right(code_end, end)
|
|
77
|
-
if (right >= end || text[right] != ")") {
|
|
78
|
-
break
|
|
79
|
-
}
|
|
80
76
|
code_start = left - 1
|
|
81
|
-
code_end =
|
|
77
|
+
code_end = source.skip_right(code_end, end) + 1
|
|
82
78
|
}
|
|
83
79
|
const right = source.skip_right(code_end, end)
|
|
84
80
|
let last = right
|
|
@@ -197,10 +193,10 @@ export default {
|
|
|
197
193
|
node.properties,
|
|
198
194
|
object_curly_spacing
|
|
199
195
|
)
|
|
200
|
-
|
|
196
|
+
default:
|
|
201
197
|
return {
|
|
202
198
|
end: node.range[1],
|
|
203
|
-
items: node
|
|
199
|
+
items: /** @type {import("../../private.js").AstNode & import("estree").SequenceExpression} */(node)/**/.expressions,
|
|
204
200
|
line_indent: source.get_line_indent(
|
|
205
201
|
node,
|
|
206
202
|
source.get_anchor(node.range[0]),
|
|
@@ -210,7 +206,6 @@ export default {
|
|
|
210
206
|
start: node.range[0]
|
|
211
207
|
}
|
|
212
208
|
}
|
|
213
|
-
return undefined
|
|
214
209
|
}
|
|
215
210
|
/**
|
|
216
211
|
* @param {import("../../private.js").AstNode} node
|
|
@@ -23,26 +23,18 @@ export default {
|
|
|
23
23
|
*/
|
|
24
24
|
function collect(node, depth, gaps) {
|
|
25
25
|
const parts = get_parts(node)
|
|
26
|
-
const question = source_code.getTokenAfter(
|
|
26
|
+
const question = /** @type {import("eslint").AST.Token} */(source_code.getTokenAfter(
|
|
27
27
|
/** @type {import("estree").Node} */(/** @type {unknown} */(parts.test))/**/,
|
|
28
28
|
{
|
|
29
29
|
filter: token => token.value == "?"
|
|
30
30
|
}
|
|
31
|
-
)
|
|
32
|
-
const colon =
|
|
31
|
+
))/**/
|
|
32
|
+
const colon = /** @type {import("eslint").AST.Token} */(source_code.getTokenAfter(
|
|
33
33
|
/** @type {import("estree").Node} */(/** @type {unknown} */(parts.consequent))/**/,
|
|
34
34
|
{
|
|
35
35
|
filter: token => token.value == ":"
|
|
36
36
|
}
|
|
37
|
-
)
|
|
38
|
-
if (
|
|
39
|
-
!question
|
|
40
|
-
|| !colon
|
|
41
|
-
|| question.range[1] > parts.consequent.range[0]
|
|
42
|
-
|| colon.range[1] > parts.alternate.range[0]
|
|
43
|
-
) {
|
|
44
|
-
return false
|
|
45
|
-
}
|
|
37
|
+
))/**/
|
|
46
38
|
for (const operator of [ question, colon ]) {
|
|
47
39
|
const before = /** @type {import("eslint").AST.Token | import("estree").Comment} */(source_code.getTokenBefore(
|
|
48
40
|
operator,
|
|
@@ -160,16 +160,14 @@ export default {
|
|
|
160
160
|
function declaration_edits(node, resolution) {
|
|
161
161
|
/** @type {import("../../private.js").RenameEdit[]} */
|
|
162
162
|
const edits = [ rename_edit(node) ]
|
|
163
|
-
const block = /** @type {import("../../private.js").AstNode
|
|
163
|
+
const block = /** @type {import("../../private.js").AstNode} */(/** @type {import("eslint").Scope.Variable} */(resolution.get(node))/**/.scope.block)/**/
|
|
164
164
|
const func = function_of_param(node)
|
|
165
165
|
const leading = func && leading_comment(func)
|
|
166
166
|
/** @type {Set<import("../../private.js").Comment>} */
|
|
167
167
|
const comments = new Set(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
)
|
|
172
|
-
: []
|
|
168
|
+
/** @type {import("../../private.js").Comment[]} */(source.getAllComments())/**/.filter(
|
|
169
|
+
comment => block.type == "Program" || comment.range[0] >= block.range[0] && comment.range[1] <= block.range[1]
|
|
170
|
+
)
|
|
173
171
|
)
|
|
174
172
|
if (leading) {
|
|
175
173
|
comments.add(leading)
|
|
@@ -190,7 +188,9 @@ export default {
|
|
|
190
188
|
* @returns {void}
|
|
191
189
|
*/
|
|
192
190
|
function declare(node) {
|
|
193
|
-
|
|
191
|
+
if (!is_ambient(node)) {
|
|
192
|
+
add(declarations, node)
|
|
193
|
+
}
|
|
194
194
|
}
|
|
195
195
|
/**
|
|
196
196
|
* @param {import("../../private.js").AstNode & import("estree").Identifier} node
|
|
@@ -236,12 +236,28 @@ export default {
|
|
|
236
236
|
}
|
|
237
237
|
return depth > 0
|
|
238
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* @param {import("../../private.js").AstNode} node
|
|
241
|
+
* @returns {boolean}
|
|
242
|
+
*/
|
|
243
|
+
function is_ambient(node) {
|
|
244
|
+
for (
|
|
245
|
+
let current = /** @type {import("../../private.js").AstNode | undefined} */(node)/**/;
|
|
246
|
+
current;
|
|
247
|
+
current = current.parent
|
|
248
|
+
) {
|
|
249
|
+
if ("declare" in current && current.declare === true) {
|
|
250
|
+
return true
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return false
|
|
254
|
+
}
|
|
239
255
|
/**
|
|
240
256
|
* @param {import("../../private.js").AstNode} node
|
|
241
257
|
* @returns {boolean}
|
|
242
258
|
*/
|
|
243
259
|
function is_declaration(node) {
|
|
244
|
-
for (let parent = node.parent;
|
|
260
|
+
for (let parent = node.parent; ; parent = parent.parent) {
|
|
245
261
|
switch (parent.type) {
|
|
246
262
|
case "ArrayPattern":
|
|
247
263
|
case "AssignmentPattern":
|
|
@@ -257,7 +273,6 @@ export default {
|
|
|
257
273
|
return true
|
|
258
274
|
}
|
|
259
275
|
}
|
|
260
|
-
return true
|
|
261
276
|
}
|
|
262
277
|
/**
|
|
263
278
|
* @param {import("../../private.js").AstNode} node
|
|
@@ -308,9 +323,7 @@ export default {
|
|
|
308
323
|
anchor = parent
|
|
309
324
|
break
|
|
310
325
|
case "VariableDeclarator":
|
|
311
|
-
|
|
312
|
-
anchor = parent.parent
|
|
313
|
-
}
|
|
326
|
+
anchor = parent.parent
|
|
314
327
|
}
|
|
315
328
|
if (anchor.parent.type == "ExportDefaultDeclaration" || anchor.parent.type == "ExportNamedDeclaration") {
|
|
316
329
|
anchor = anchor.parent
|
|
@@ -771,7 +784,7 @@ export default {
|
|
|
771
784
|
const resolution = new Map()
|
|
772
785
|
/** @type {Map<string, import("eslint").Scope.Variable[]>} */
|
|
773
786
|
const variables_by_name = new Map()
|
|
774
|
-
const scopes = source.scopeManager
|
|
787
|
+
const scopes = /** @type {import("eslint").Scope.ScopeManager} */(source.scopeManager)/**/.scopes
|
|
775
788
|
for (const scope of scopes) {
|
|
776
789
|
for (const variable of scope.variables) {
|
|
777
790
|
const same_names = variables_by_name.get(variable.name)
|
|
@@ -808,10 +821,9 @@ export default {
|
|
|
808
821
|
for (const nodes of declarations.values()) {
|
|
809
822
|
for (const node of nodes) {
|
|
810
823
|
declared.add(node)
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}
|
|
824
|
+
renamable.add(
|
|
825
|
+
/** @type {import("eslint").Scope.Variable} */(resolution.get(node))/**/
|
|
826
|
+
)
|
|
815
827
|
}
|
|
816
828
|
}
|
|
817
829
|
/** @type {Map<string, Set<import("eslint").Scope.Scope>>} */
|
|
@@ -859,7 +871,7 @@ export default {
|
|
|
859
871
|
if (!others.length) {
|
|
860
872
|
return false
|
|
861
873
|
}
|
|
862
|
-
for (const variable of variables_by_name.get(name)
|
|
874
|
+
for (const variable of /** @type {import("eslint").Scope.Variable[]} */(variables_by_name.get(name))/**/) {
|
|
863
875
|
if (!renamable.has(variable)) {
|
|
864
876
|
continue
|
|
865
877
|
}
|
|
@@ -895,7 +907,7 @@ export default {
|
|
|
895
907
|
* @returns {boolean}
|
|
896
908
|
*/
|
|
897
909
|
function has_foreign_declaration(name) {
|
|
898
|
-
return (variables_by_name.get(name)
|
|
910
|
+
return /** @type {import("eslint").Scope.Variable[]} */(variables_by_name.get(name))/**/.some(
|
|
899
911
|
variable => renamable.has(variable) && variable.identifiers.some(
|
|
900
912
|
identifier => !declared.has(
|
|
901
913
|
/** @type {import("../../private.js").AstNode} */(/** @type {unknown} */(identifier))/**/
|
|
@@ -922,7 +934,7 @@ export default {
|
|
|
922
934
|
plans.set(
|
|
923
935
|
name,
|
|
924
936
|
{
|
|
925
|
-
atomic: !fix_same_names || variables_by_name.has(target) || nested_targets.has(target) || store_usages.has(name),
|
|
937
|
+
atomic: !fix_same_names || nodes.length > 1 || variables_by_name.has(target) || nested_targets.has(target) || store_usages.has(name),
|
|
926
938
|
exported: (export_locals.get(name) ?? []).filter(belongs),
|
|
927
939
|
fixable: !blocked.has(name)
|
|
928
940
|
&& fixable_name_regex.test(name)
|
|
@@ -936,21 +948,16 @@ export default {
|
|
|
936
948
|
}
|
|
937
949
|
/**
|
|
938
950
|
* @param {import("../../private.js").NamingIdentifier} node
|
|
939
|
-
* @param {import("../../private.js").NamingIdentifier[]} nodes
|
|
940
951
|
* @param {import("../../private.js").NamingIdentifier[]} usages_held
|
|
941
952
|
* @returns {import("../../private.js").RenameEdit[]}
|
|
942
953
|
*/
|
|
943
|
-
function declaration_fix(node,
|
|
954
|
+
function declaration_fix(node, usages_held) {
|
|
944
955
|
const variable = resolution.get(node)
|
|
945
956
|
return [
|
|
946
957
|
...declaration_edits(node, resolution),
|
|
947
|
-
...
|
|
948
|
-
|
|
949
|
-
)
|
|
950
|
-
? usages_held.filter(
|
|
951
|
-
usage => resolution.get(usage) == variable
|
|
952
|
-
).map(rename_edit)
|
|
953
|
-
: []
|
|
958
|
+
...usages_held.filter(
|
|
959
|
+
usage => resolution.get(usage) == variable
|
|
960
|
+
).map(rename_edit)
|
|
954
961
|
]
|
|
955
962
|
}
|
|
956
963
|
/** @type {import("../../private.js").RenameEdit[]} */
|
|
@@ -996,7 +1003,7 @@ export default {
|
|
|
996
1003
|
for (const node of nodes) {
|
|
997
1004
|
report(
|
|
998
1005
|
node,
|
|
999
|
-
fixable && !used.length ? declaration_fix(node,
|
|
1006
|
+
fixable && !used.length ? declaration_fix(node, usages_held) : null
|
|
1000
1007
|
)
|
|
1001
1008
|
}
|
|
1002
1009
|
} else {
|
|
@@ -13,14 +13,12 @@ function skip_hole_commas(rule) {
|
|
|
13
13
|
{
|
|
14
14
|
report: {
|
|
15
15
|
value: (
|
|
16
|
-
/** @type {import("eslint").Rule.ReportDescriptor & { node
|
|
16
|
+
/** @type {import("eslint").Rule.ReportDescriptor & { node: import("eslint").AST.Token }} */ descriptor
|
|
17
17
|
) => {
|
|
18
18
|
const token = descriptor.node
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return
|
|
23
|
-
}
|
|
19
|
+
const before = source_code.getTokenBefore(token)
|
|
20
|
+
if (before?.value == "," && before.loc.end.line < token.loc.start.line) {
|
|
21
|
+
return
|
|
24
22
|
}
|
|
25
23
|
context.report(descriptor)
|
|
26
24
|
}
|
package/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AA2NA;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,MAAM,EAbF,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,GAAG;IACtC,OAAO,EAAE;QACP,WAAW,EAAE,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,GAAG;YAC5C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;YACvD,KAAK,EAAE,OAAO,QAAQ,EAAE,MAAM,CAAC,WAAW,CAAA;SAC3C,CAAA;QACD,MAAM,EAAE,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,GAAG;YACvC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;YACvD,KAAK,EAAE,OAAO,QAAQ,EAAE,MAAM,CAAC,WAAW,CAAA;SAC3C,CAAA;KACF,CAAA;CA4BL,CAAA;eAmDc,MAAM;AACrB,OAAO,EAAE,MAAM,IAAI,gBAAgB,EAAE,CAAA"}
|