@lewishowles/lint-config 0.7.0 → 0.9.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/CHANGELOG.md +29 -0
- package/README.md +44 -23
- package/base.json +47 -0
- package/comments/rules/formatting.js +5 -7
- package/comments/rules/function-documentation.js +21 -2
- package/comments/utils/documentation.js +61 -29
- package/comments/utils/jsdoc.js +347 -79
- package/comments/utils/source.js +59 -26
- package/comments/utils/wrap.js +48 -14
- package/comments.json +1 -0
- package/layers.js +60 -0
- package/package.json +10 -5
- package/testing/plugin.js +8 -0
- package/testing/rules/no-text-lookups.js +148 -0
- package/vue.json +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0: 2026-10-07
|
|
4
|
+
|
|
5
|
+
### Changes
|
|
6
|
+
|
|
7
|
+
- `base` now reports an error for TypeScript non-null assertions (`!`) in `.ts` files and Vue `<script lang="ts">` blocks (`typescript/no-non-null-assertion`). Check for `null` or `undefined` before using the value instead. The rule finds nothing in components or helpers, so upgrading needs no fixes there.
|
|
8
|
+
- `vue` now reports an error when a computed property changes state (`vue/no-side-effects-in-computed-properties`) or runs async code (`vue/no-async-in-computed-properties`). Neither rule finds anything in components or helpers, so upgrading needs no fixes there. `vue/no-mutating-props`, `vue/no-use-v-if-with-v-for` and `vue/require-explicit-emits` will follow once Oxlint supports them.
|
|
9
|
+
- `base` now reports an error when a unit test (`*.test.*` or `*.spec.*`) searches an array for an element by reading its visible text (`testing/no-text-lookups`). Find the element by a `data-test` attribute instead. Playwright and Cypress files are unaffected.
|
|
10
|
+
- `comments/function-documentation` is stricter: a parameter written as an array, such as `function f([x, y])` or `function f(...[x, y])`, now needs one `@param` for the whole array. The tag can have any name, as it can for a destructured object, and a missing tag is reported as `options`. The items inside the array still need no `@param` of their own.
|
|
11
|
+
- `comments/function-documentation` is stricter for object rest properties: `{ a, ...rest }` now needs `@param options.rest`, and `{ a: { ...rest } }` needs `@param options.a.rest`. A documented root name replaces `options` in those paths.
|
|
12
|
+
|
|
13
|
+
### Fixes
|
|
14
|
+
|
|
15
|
+
- `comments/function-documentation` now checks properties inside a nested object that has a default value. In `function f({ a: { b = 1 } = {} })`, `b` now needs `@param options.a.b`, as it already did without the `= {}`. Projects that skipped those tags will see new errors.
|
|
16
|
+
- In test files (`*.test.*`, `*.spec.*`, `*.pw.*` and `*.cy.*`), blank lines between calls, awaits, assignments and multiline expressions are now up to you, so you can separate test steps with them. `base` no longer adds or removes those lines in tests. Blank lines around declarations, blocks, `return` and `break` are still checked. If you ran the 0.8 auto-fix on your tests, you may want to restore the blank lines it removed.
|
|
17
|
+
|
|
18
|
+
## 0.8.0: 2026-10-05
|
|
19
|
+
|
|
20
|
+
### Changes
|
|
21
|
+
|
|
22
|
+
- Breaking: `vite-plus` 1.0.0 is now required. Put the selected lint layers in a `vite.config.js` `lint` block; `vp check` and `vp lint` ignore a `.oxlintrc.json` on its own. Use `lintConfig` from `@lewishowles/lint-config/layers` to build the block, with an optional `.oxlintrc.json` for local settings. Vite+ 1.0 also adds its own `vite-plus/prefer-vite-plus-imports` rule, which may report new problems after upgrading.
|
|
23
|
+
- New `./layers` export provides `base`, `vue`, `comments`, and `lintConfig`. The Vue layer includes base, and `lintConfig` carries inherited environments and globals into the Vite+ lint block.
|
|
24
|
+
- `comments/function-documentation` adds an `ignoreInlineArrows` option, enabled by `comments.json` for test files. Arrow functions used as object property values no longer need JSDoc there; other function forms and files keep their existing requirements.
|
|
25
|
+
- `comments/formatting` changes the output of `--fix` for comments. Run `--fix` once after upgrading.
|
|
26
|
+
|
|
27
|
+
### Fixes
|
|
28
|
+
|
|
29
|
+
- `comments/formatting` keeps code spans, quotes, colons, Markdown blocks, and descriptions in mixed-tag JSDoc blocks as written. It places free-form tag text at the comment margin, removes a separator hyphen when a tag description moves onto its own line, keeps wrapped parameter, return, and throw descriptions indented, and preserves line breaks between adjacent `//` comments.
|
|
30
|
+
- The documentation rules for functions, variables, configured API calls and classes recognise comments across tool directives such as `eslint-disable-next-line`. `comments/function-documentation` accepts plain or optional JSDoc names for destructured parameters with defaults.
|
|
31
|
+
|
|
3
32
|
## 0.7.0: 2026-09-30
|
|
4
33
|
|
|
5
34
|
### Changes
|
package/README.md
CHANGED
|
@@ -62,6 +62,14 @@ Add the comments layer alongside the base or Vue layer to enforce the comment-fo
|
|
|
62
62
|
}
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
For functions, variables, configured API calls and classes, a tool directive comment such as `// eslint-disable-next-line` may sit between the documentation comment and the code.
|
|
66
|
+
|
|
67
|
+
For destructured parameters with defaults, document the parent and each property. Plain names such as `result.errors` and optional names such as `[result.errors]` or `[result.errors=[]]` all match `errors = []`. The parent can likewise be `result`, `[result]` or `[result={}]`. A documented default does not have to match the value in the code.
|
|
68
|
+
|
|
69
|
+
Lines below a free-form tag such as `@description` or `@note` start at the comment margin so they can be pasted into Markdown without becoming a code block. `@example` content is left as written. Wrapped `@param`, `@returns` and `@throws` descriptions keep a four-space hanging indent so each description is easy to scan beneath its tag. A description written as `name - description` loses the hyphen when it moves onto its own line.
|
|
70
|
+
|
|
71
|
+
Consecutive `//` comments keep their line breaks, and only a line past 80 columns is wrapped.
|
|
72
|
+
|
|
65
73
|
The Vue component rule reads the raw `.vue` file because Oxlint's JS Plugin API only receives the extracted script block. The comments layer loads its plugin for you, so there's no relative `jsPlugins` path to add. To pick rules yourself instead, add the plugin directly:
|
|
66
74
|
|
|
67
75
|
```json
|
|
@@ -102,36 +110,32 @@ The `comments/variable-declarations` rule accepts a `rootOnly` option to require
|
|
|
102
110
|
}
|
|
103
111
|
```
|
|
104
112
|
|
|
113
|
+
The `comments/function-documentation` rule accepts an `ignoreInlineArrows` option. The comments layer enables it for the same test files, so arrow functions used as object property values do not need JSDoc there. Function declarations, method shorthand, function expression properties, and `const` functions still need JSDoc. Other files keep the default of `false`.
|
|
114
|
+
|
|
105
115
|
The `comments/class-documentation` rule requires an immediately preceding block comment before class declarations and const-assigned class expressions. Constructors, methods, getters, and setters require full JSDoc; constructors never need an `@returns` tag, and getters and setters need one only when they return a value. Instance and static fields require an immediately preceding line comment.
|
|
106
116
|
|
|
107
117
|
### `vp check` configuration
|
|
108
118
|
|
|
109
|
-
|
|
119
|
+
On vite-plus 1.0.0, `vp check` and `vp lint` take their Oxlint settings from the `lint` block in `vite.config.js`. They ignore a `.oxlintrc.json` on its own, even though the Vite+ documentation says `vp lint` finds Oxlint config files by itself. If `vite.config.js` is missing, both commands quietly fall back to default settings. You still see plausible warnings and exit codes, but none of your rules are applied.
|
|
110
120
|
|
|
111
|
-
Each consuming repo needs a `vite.config.js` with a `lint` block
|
|
121
|
+
Each consuming repo needs a `vite.config.js` with a `lint` block. Use `lintConfig` with the layers you want. Oxlint ignores `env` and `globals` in configs listed under `extends`, so `lintConfig` copies them into the `lint` block itself, including values from the layers each one extends. Without that, `no-undef` reports Vue macros such as `defineProps` and browser globals such as `window` (see [docs/limitations.md](docs/limitations.md)). The Vue layer already includes base; add comments only if you want the comment rules. You can pass your project's `.oxlintrc.json` as the second argument to keep its local settings. `lintConfig` ignores the `extends` list in that file, so list every layer you want in the first argument.
|
|
112
122
|
|
|
113
123
|
```js
|
|
114
124
|
import { defineConfig } from "vite-plus";
|
|
115
|
-
import
|
|
116
|
-
import lintConfigComments from "@lewishowles/lint-config/comments.json" with { type: "json" };
|
|
125
|
+
import { base, comments, lintConfig } from "@lewishowles/lint-config/layers";
|
|
117
126
|
import oxlintrc from "./.oxlintrc.json" with { type: "json" };
|
|
118
127
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
ignorePatterns: oxlintrc.ignorePatterns,
|
|
123
|
-
jsPlugins: [...lintConfigBase.jsPlugins, ...lintConfigComments.jsPlugins],
|
|
124
|
-
overrides: [
|
|
125
|
-
...(lintConfigBase.overrides ?? []),
|
|
126
|
-
...(lintConfigComments.overrides ?? []),
|
|
127
|
-
...(oxlintrc.overrides ?? []),
|
|
128
|
-
],
|
|
129
|
-
rules: { ...lintConfigBase.rules, ...lintConfigComments.rules },
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
export default defineConfig({ lint });
|
|
128
|
+
export default defineConfig({
|
|
129
|
+
lint: lintConfig([base, comments], oxlintrc),
|
|
130
|
+
});
|
|
133
131
|
```
|
|
134
132
|
|
|
133
|
+
For a Vue project, use `lintConfig([vue, comments], oxlintrc)` and import `vue` instead of `base`. The local config is optional; `lintConfig([vue, comments])` also works. If you combine layers by hand, use object layers in `extends` and lift their `env` and `globals` to the top level yourself. Spreading JSON layers together replaces earlier `jsPlugins`, `overrides`, and `rules` arrays or objects.
|
|
134
|
+
|
|
135
|
+
Verify the setup with `vp lint --print-config <file>` and check that your real values, not defaults, are active. The printed `rules` leave out every rule that comes from a plugin, such as `comments/*` and `@stylistic/*`, even when those rules are running. To check a plugin layer, confirm the plugin is listed in `jsPlugins`, then lint a file that breaks one of its rules.
|
|
136
|
+
|
|
137
|
+
Vite+ also adds its own `vite-plus` lint plugin, so `vp check` can report rules that this package doesn't define. For example, `vite-plus/prefer-vite-plus-imports` reports imports from `oxlint` packages that Vite+ already provides, such as `oxlint/plugins-dev`.
|
|
138
|
+
|
|
135
139
|
## Customising
|
|
136
140
|
|
|
137
141
|
Your project's `.oxlintrc.json` can override rules, add ignore patterns, add overrides, or add plugins on top of the shared layer.
|
|
@@ -186,11 +190,11 @@ Plugins are additive and deduplicated: your local plugins are added to the share
|
|
|
186
190
|
|
|
187
191
|
## Layers
|
|
188
192
|
|
|
189
|
-
| Layer | File | Contents
|
|
190
|
-
| ---------- | --------------- |
|
|
191
|
-
| `base` | `base.json` | Correctness and formatting rules, import sorting, `import`/`oxc`/`typescript`/`unicorn` plugins |
|
|
192
|
-
| `comments` | `comments.json` | Optional comment-formatting rules, variable-declaration documentation, JSDoc checks
|
|
193
|
-
| `vue` | `vue.json` | Extends `base`, adds the `vue` plugin, Vue compiler macro globals, Vue-specific rules
|
|
193
|
+
| Layer | File | Contents |
|
|
194
|
+
| ---------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
195
|
+
| `base` | `base.json` | Correctness and formatting rules, import sorting, `import`/`oxc`/`typescript`/`unicorn` plugins; errors when unit tests (`*.test.*`, `*.spec.*`) find elements by visible text |
|
|
196
|
+
| `comments` | `comments.json` | Optional comment-formatting rules, variable-declaration documentation, JSDoc checks |
|
|
197
|
+
| `vue` | `vue.json` | Extends `base`, adds the `vue` plugin, Vue compiler macro globals, Vue-specific rules |
|
|
194
198
|
|
|
195
199
|
### Import sorting
|
|
196
200
|
|
|
@@ -214,6 +218,23 @@ This puts named imports first, including `import type { … }` and imports with
|
|
|
214
218
|
|
|
215
219
|
The base layer reports imports from a parent folder (`../`), with no automatic fix. Same-folder (`./`), `@/` alias and package `#` subpath imports are allowed. After upgrading, any existing `../` imports fail lint until they move to an `@/` alias or, in a package without one, to [package subpath imports](https://nodejs.org/api/packages.html#subpath-imports).
|
|
216
220
|
|
|
221
|
+
### Text lookups in unit tests
|
|
222
|
+
|
|
223
|
+
The base layer's `testing/no-text-lookups` rule reports an array search such as `find`, `filter` or `some` when its inline callback reads `.text()`, `.textContent` or `.innerText`. Find the element by a `data-test` attribute instead. It doesn't report data-test lookups, assertions about the text of an element you have already found, or callbacks passed by name. It applies to `*.test.*` and `*.spec.*` files, not Playwright `*.pw.*` or Cypress `*.cy.*` files.
|
|
224
|
+
|
|
225
|
+
To turn it off, add an override for unit tests to your project's `.oxlintrc.json`:
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
{
|
|
229
|
+
"overrides": [
|
|
230
|
+
{
|
|
231
|
+
"files": ["**/*.test.*", "**/*.spec.*"],
|
|
232
|
+
"rules": { "testing/no-text-lookups": "off" }
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
217
238
|
## What stays repo-local
|
|
218
239
|
|
|
219
240
|
- `ignorePatterns`, since every project has different build output and tool directories
|
package/base.json
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
{
|
|
6
6
|
"name": "vite-plus",
|
|
7
7
|
"specifier": "vite-plus/oxlint-plugin"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"name": "testing",
|
|
11
|
+
"specifier": "@lewishowles/lint-config/testing/plugin"
|
|
8
12
|
}
|
|
9
13
|
],
|
|
10
14
|
"categories": {
|
|
@@ -26,6 +30,7 @@
|
|
|
26
30
|
"no-useless-assignment": "error",
|
|
27
31
|
"preserve-caught-error": "error",
|
|
28
32
|
"import/no-relative-parent-imports": "error",
|
|
33
|
+
"typescript/no-non-null-assertion": "error",
|
|
29
34
|
"sort-imports": [
|
|
30
35
|
"error",
|
|
31
36
|
{
|
|
@@ -89,6 +94,48 @@
|
|
|
89
94
|
{
|
|
90
95
|
"files": ["**/vite.config.*", "**/vitest.config.*", "**/playwright*.config.*"],
|
|
91
96
|
"env": { "node": true }
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"files": ["**/*.test.*", "**/*.spec.*", "**/*.pw.*", "**/*.cy.*"],
|
|
100
|
+
"rules": {
|
|
101
|
+
"@stylistic/padding-line-between-statements": [
|
|
102
|
+
"error",
|
|
103
|
+
{ "blankLine": "always", "prev": "*", "next": "break" },
|
|
104
|
+
{ "blankLine": "always", "prev": ["const", "let"], "next": "*" },
|
|
105
|
+
{ "blankLine": "always", "prev": "*", "next": ["const", "let"] },
|
|
106
|
+
{ "blankLine": "always", "prev": "*", "next": "return" },
|
|
107
|
+
{
|
|
108
|
+
"blankLine": "always",
|
|
109
|
+
"prev": [
|
|
110
|
+
"block-like",
|
|
111
|
+
"class",
|
|
112
|
+
"do",
|
|
113
|
+
"for",
|
|
114
|
+
"function",
|
|
115
|
+
"if",
|
|
116
|
+
"switch",
|
|
117
|
+
"try",
|
|
118
|
+
"while"
|
|
119
|
+
],
|
|
120
|
+
"next": "*"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"blankLine": "always",
|
|
124
|
+
"prev": "*",
|
|
125
|
+
"next": ["block-like", "class", "do", "for", "function", "if", "switch", "try", "while"]
|
|
126
|
+
},
|
|
127
|
+
{ "blankLine": "never", "prev": "const", "next": "const" },
|
|
128
|
+
{ "blankLine": "never", "prev": "let", "next": "let" },
|
|
129
|
+
{ "blankLine": "always", "prev": "multiline-const", "next": "*" },
|
|
130
|
+
{ "blankLine": "always", "prev": "*", "next": "multiline-const" }
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"files": ["**/*.test.*", "**/*.spec.*"],
|
|
136
|
+
"rules": {
|
|
137
|
+
"testing/no-text-lookups": "error"
|
|
138
|
+
}
|
|
92
139
|
}
|
|
93
140
|
],
|
|
94
141
|
"options": {
|
|
@@ -656,13 +656,10 @@ function reportLineCommentGroups(context) {
|
|
|
656
656
|
});
|
|
657
657
|
});
|
|
658
658
|
|
|
659
|
-
// The group's lines after refilling words from early-wrapped lines.
|
|
660
|
-
const refilledLines = refillCommentLines(formattedLines, maximumLineLength);
|
|
661
|
-
|
|
662
659
|
// The group's text after applying its final line formatting and
|
|
663
660
|
// placement gap.
|
|
664
661
|
const formattedText =
|
|
665
|
-
|
|
662
|
+
formattedLines
|
|
666
663
|
.map(({ prefix, text }) => (text === "" ? prefix.trimEnd() : `${prefix}${text}`))
|
|
667
664
|
.join(getNewline(context.sourceCode.text)) + (placement?.gap ?? "");
|
|
668
665
|
|
|
@@ -833,9 +830,10 @@ function reportBlockComments(context) {
|
|
|
833
830
|
}
|
|
834
831
|
|
|
835
832
|
/**
|
|
836
|
-
* The comment-formatting rule: punctuates comments as sentences
|
|
837
|
-
*
|
|
838
|
-
*
|
|
833
|
+
* The comment-formatting rule: punctuates comments as sentences and wraps any
|
|
834
|
+
* comment past 80 columns, replacing each comment in one edit. Block comments
|
|
835
|
+
* that wrap too early are refilled. Line-comment groups are reindented, but
|
|
836
|
+
* words never move between their lines.
|
|
839
837
|
*/
|
|
840
838
|
export default {
|
|
841
839
|
meta: {
|
|
@@ -53,6 +53,18 @@ export default {
|
|
|
53
53
|
meta: {
|
|
54
54
|
docs: { description: "Require JSDoc documentation for named functions and methods." },
|
|
55
55
|
type: "suggestion",
|
|
56
|
+
schema: [
|
|
57
|
+
{
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
ignoreInlineArrows: {
|
|
61
|
+
type: "boolean",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
additionalProperties: false,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
defaultOptions: [{ ignoreInlineArrows: false }],
|
|
56
68
|
},
|
|
57
69
|
/**
|
|
58
70
|
* Create the rule's node visitors.
|
|
@@ -77,13 +89,20 @@ export default {
|
|
|
77
89
|
}
|
|
78
90
|
},
|
|
79
91
|
/**
|
|
80
|
-
* Check a first-level object method for documentation.
|
|
92
|
+
* Check a first-level object method for documentation. When the
|
|
93
|
+
* ignoreInlineArrows option is on, arrow function values are
|
|
94
|
+
* skipped, which suits small inline callbacks.
|
|
81
95
|
*
|
|
82
96
|
* @param {object} node
|
|
83
97
|
* The property node.
|
|
84
98
|
*/
|
|
85
99
|
Property(node) {
|
|
86
|
-
if (
|
|
100
|
+
if (
|
|
101
|
+
!isFirstLevelObjectProperty(node) ||
|
|
102
|
+
!isFunctionValue(node.value) ||
|
|
103
|
+
(context.options?.[0]?.ignoreInlineArrows &&
|
|
104
|
+
node.value.type === "ArrowFunctionExpression")
|
|
105
|
+
) {
|
|
87
106
|
return;
|
|
88
107
|
}
|
|
89
108
|
|
|
@@ -2,7 +2,7 @@ import { getJSDocContent, isJSDoc } from "./jsdoc.js";
|
|
|
2
2
|
import {
|
|
3
3
|
getCommentNeighbours,
|
|
4
4
|
getCommentText,
|
|
5
|
-
|
|
5
|
+
getImmediateNonDirectiveComment,
|
|
6
6
|
isLeadingComment,
|
|
7
7
|
} from "./source.js";
|
|
8
8
|
import { getPropertyName } from "./vue-macro.js";
|
|
@@ -45,8 +45,8 @@ export function isFunctionValue(node) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* Return the JSDoc comment
|
|
49
|
-
*
|
|
48
|
+
* Return the JSDoc comment directly above a node, when the comment qualifies as
|
|
49
|
+
* that node's documentation. Tool directive comments may sit between them.
|
|
50
50
|
*
|
|
51
51
|
* @param {object} sourceCode
|
|
52
52
|
* The Oxlint source code object.
|
|
@@ -57,26 +57,21 @@ export function isFunctionValue(node) {
|
|
|
57
57
|
* The qualifying JSDoc comment, or null when none precedes the node.
|
|
58
58
|
*/
|
|
59
59
|
function getDocumentationComment(sourceCode, node) {
|
|
60
|
-
//
|
|
61
|
-
const comment = sourceCode
|
|
62
|
-
.getAllComments()
|
|
63
|
-
.findLast((candidate) => candidate.range[1] <= node.range[0]);
|
|
60
|
+
// The closest comment above the node, looking past tool directive comments.
|
|
61
|
+
const comment = getImmediateNonDirectiveComment(sourceCode, node);
|
|
64
62
|
|
|
65
|
-
if (comment?.type !== "Block"
|
|
63
|
+
if (comment?.type !== "Block") {
|
|
66
64
|
return null;
|
|
67
65
|
}
|
|
68
66
|
|
|
69
67
|
// Checks the comments immediately around the documented node.
|
|
70
68
|
const { next, previous } = getCommentNeighbours(sourceCode, comment);
|
|
71
|
-
// Confirms there is no blank line before the documented node.
|
|
72
|
-
const gap = sourceCode.text.slice(comment.range[1], node.range[0]);
|
|
73
69
|
|
|
74
70
|
if (
|
|
75
71
|
!next ||
|
|
76
72
|
next.range[0] > node.range[0] ||
|
|
77
73
|
next.range[1] > node.range[1] ||
|
|
78
|
-
!isLeadingComment(sourceCode, comment, previous)
|
|
79
|
-
!/^\r?\n[ \t]*$/.test(gap)
|
|
74
|
+
!isLeadingComment(sourceCode, comment, previous)
|
|
80
75
|
) {
|
|
81
76
|
return null;
|
|
82
77
|
}
|
|
@@ -117,6 +112,13 @@ function getObjectPatternPaths(sourceCode, node, parentPath) {
|
|
|
117
112
|
const paths = [parentPath];
|
|
118
113
|
|
|
119
114
|
for (const property of node.properties) {
|
|
115
|
+
// A rest property holds every key not named before it, so it is
|
|
116
|
+
// documented at its own dotted path, such as options.rest.
|
|
117
|
+
if (property.type === "RestElement") {
|
|
118
|
+
paths.push(`${parentPath}.${property.argument.name}`);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
120
122
|
if (property.type !== "Property") {
|
|
121
123
|
continue;
|
|
122
124
|
}
|
|
@@ -140,6 +142,14 @@ function getObjectPatternPaths(sourceCode, node, parentPath) {
|
|
|
140
142
|
|
|
141
143
|
if (value.type === "AssignmentPattern") {
|
|
142
144
|
paths.push(`[${propertyPath}=${getDefaultValue(sourceCode, value.right)}]`);
|
|
145
|
+
|
|
146
|
+
// A defaulted nested object still needs its own properties
|
|
147
|
+
// documented. The first nested path is the object itself, which
|
|
148
|
+
// is already listed above with its default, so it is skipped.
|
|
149
|
+
if (value.left.type === "ObjectPattern") {
|
|
150
|
+
paths.push(...getObjectPatternPaths(sourceCode, value.left, propertyPath).slice(1));
|
|
151
|
+
}
|
|
152
|
+
|
|
143
153
|
continue;
|
|
144
154
|
}
|
|
145
155
|
|
|
@@ -157,8 +167,8 @@ function getObjectPatternPaths(sourceCode, node, parentPath) {
|
|
|
157
167
|
* @param {object} node
|
|
158
168
|
* The parameter node.
|
|
159
169
|
* @param {string} [rootPath]
|
|
160
|
-
* The documented name that starts each path for a destructured
|
|
161
|
-
*
|
|
170
|
+
* The documented name that starts each path for a destructured parameter.
|
|
171
|
+
* Defaults to options.
|
|
162
172
|
*
|
|
163
173
|
* @returns {string[]}
|
|
164
174
|
* The required JSDoc parameter paths.
|
|
@@ -172,6 +182,15 @@ function getParameterPaths(sourceCode, node, rootPath = "options") {
|
|
|
172
182
|
return [node.argument.name];
|
|
173
183
|
}
|
|
174
184
|
|
|
185
|
+
// An array is documented as one value. Its items get no paths of their
|
|
186
|
+
// own because JSDoc has no standard way to name them.
|
|
187
|
+
if (
|
|
188
|
+
node.type === "ArrayPattern" ||
|
|
189
|
+
(node.type === "RestElement" && node.argument.type === "ArrayPattern")
|
|
190
|
+
) {
|
|
191
|
+
return [rootPath];
|
|
192
|
+
}
|
|
193
|
+
|
|
175
194
|
if (node.type === "ObjectPattern") {
|
|
176
195
|
return getObjectPatternPaths(sourceCode, node, rootPath);
|
|
177
196
|
}
|
|
@@ -192,8 +211,9 @@ function getParameterPaths(sourceCode, node, rootPath = "options") {
|
|
|
192
211
|
* The JSDoc comment token.
|
|
193
212
|
*
|
|
194
213
|
* @returns {object}
|
|
195
|
-
* An object with names, the set of every documented parameter path
|
|
196
|
-
* topLevelNames, the top-level
|
|
214
|
+
* An object with names, the set of every documented parameter path with
|
|
215
|
+
* optional brackets and defaults removed, and topLevelNames, the top-level
|
|
216
|
+
* names in the order they are documented.
|
|
197
217
|
*/
|
|
198
218
|
function getDocumentedParameters(sourceCode, comment) {
|
|
199
219
|
// Splits the JSDoc block into its individual lines.
|
|
@@ -205,21 +225,16 @@ function getDocumentedParameters(sourceCode, comment) {
|
|
|
205
225
|
|
|
206
226
|
for (const line of content) {
|
|
207
227
|
// Matches an @param tag and captures its documented path.
|
|
208
|
-
const match = line.trim().match(/^@param(?:\s+\{[^}]+\})?\s+(\
|
|
228
|
+
const match = line.trim().match(/^@param(?:\s+\{[^}]+\})?\s+(\S+)/);
|
|
209
229
|
|
|
210
230
|
if (match) {
|
|
211
|
-
// The documented
|
|
212
|
-
|
|
213
|
-
const name = match[1];
|
|
231
|
+
// The documented path without optional brackets or a default value.
|
|
232
|
+
const name = getPlainParameterPath(match[1]);
|
|
214
233
|
|
|
215
234
|
names.add(name);
|
|
216
235
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const topLevelName = name.replace(/^\[|\]$/g, "").split("=")[0];
|
|
220
|
-
|
|
221
|
-
if (!topLevelName.includes(".")) {
|
|
222
|
-
topLevelNames.push(topLevelName);
|
|
236
|
+
if (!name.includes(".")) {
|
|
237
|
+
topLevelNames.push(name);
|
|
223
238
|
}
|
|
224
239
|
}
|
|
225
240
|
}
|
|
@@ -334,10 +349,12 @@ export function reportFunctionDocumentation(context, node, functionNode, options
|
|
|
334
349
|
);
|
|
335
350
|
|
|
336
351
|
for (const path of parameterPaths) {
|
|
337
|
-
// The optional
|
|
338
|
-
|
|
352
|
+
// The path without optional brackets or a default. Documented defaults
|
|
353
|
+
// are not compared with the code, so result.errors, [result.errors] and
|
|
354
|
+
// [result.errors=[]] all document the same property.
|
|
355
|
+
const plainPath = getPlainParameterPath(path);
|
|
339
356
|
|
|
340
|
-
if (!documentedParameters.has(
|
|
357
|
+
if (!documentedParameters.has(plainPath)) {
|
|
341
358
|
context.report({
|
|
342
359
|
message: `${subject} require an @param for ${path}.`,
|
|
343
360
|
node,
|
|
@@ -366,3 +383,18 @@ export function reportFunctionDocumentation(context, node, functionNode, options
|
|
|
366
383
|
});
|
|
367
384
|
}
|
|
368
385
|
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Reduce a parameter path to its plain name, so a documented path matches the
|
|
389
|
+
* required one however the documentation spells it.
|
|
390
|
+
*
|
|
391
|
+
* @param {string} path
|
|
392
|
+
* The documented or required parameter path.
|
|
393
|
+
*
|
|
394
|
+
* @returns {string}
|
|
395
|
+
* The path with brackets and any default removed, such as result.errors for
|
|
396
|
+
* [result.errors=[]].
|
|
397
|
+
*/
|
|
398
|
+
function getPlainParameterPath(path) {
|
|
399
|
+
return path.replace(/^\[|\]$/g, "").split("=")[0];
|
|
400
|
+
}
|