@lewishowles/lint-config 0.6.1 → 0.8.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 +22 -0
- package/README.md +46 -24
- package/base.json +20 -2
- package/comments/rules/class-documentation.js +5 -2
- package/comments/rules/configured-api-calls.js +2 -2
- package/comments/rules/formatting.js +9 -12
- package/comments/rules/function-documentation.js +22 -3
- package/comments/rules/variable-declarations.js +2 -2
- package/comments/rules/vue-component-documentation.js +1 -1
- package/comments/rules/vue-emit-documentation.js +6 -2
- package/comments/rules/vue-prop-documentation.js +6 -2
- package/comments/utils/documentation.js +36 -29
- package/comments/utils/jsdoc.js +348 -79
- package/comments/utils/source.js +59 -26
- package/comments/utils/wrap.js +48 -14
- package/comments.json +1 -0
- package/imports.json +13 -0
- package/layers.js +60 -0
- package/package.json +11 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0: 2026-10-05
|
|
4
|
+
|
|
5
|
+
### Changes
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
- 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.
|
|
9
|
+
- `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.
|
|
10
|
+
- `comments/formatting` changes the output of `--fix` for comments. Run `--fix` once after upgrading.
|
|
11
|
+
|
|
12
|
+
### Fixes
|
|
13
|
+
|
|
14
|
+
- `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.
|
|
15
|
+
- 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.
|
|
16
|
+
|
|
17
|
+
## 0.7.0: 2026-09-30
|
|
18
|
+
|
|
19
|
+
### Changes
|
|
20
|
+
|
|
21
|
+
- Breaking: `base` now reports an error for imports that reach into a parent folder, such as `../utils`. Use the package's subpath imports or an alias instead; `--fix` can't rewrite these.
|
|
22
|
+
- Breaking: `base` now expects a blank line before an `await` statement that follows other code, and around `const` declarations that span several lines. Run `--fix` once to update existing code.
|
|
23
|
+
- New opt-in `imports.json` layer with formatter settings that sort imports. See the README for how to add it.
|
|
24
|
+
|
|
3
25
|
## 0.6.1: 2026-09-21
|
|
4
26
|
|
|
5
27
|
### Fixes
|
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,15 +190,33 @@ 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, `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 |
|
|
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
|
|
|
197
|
-
The base layer sorts named members within each import statement
|
|
201
|
+
The base lint layer sorts named members within each import statement. To sort whole import statements, opt in to `imports.json`. It contains Oxfmt settings, not an Oxlint layer, so add it to the `fmt` block in `vite.config.js`:
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
import { defineConfig } from "vite-plus";
|
|
205
|
+
import importFormat from "@lewishowles/lint-config/imports.json" with { type: "json" };
|
|
206
|
+
import oxfmtrc from "./.oxfmtrc.json" with { type: "json" };
|
|
207
|
+
|
|
208
|
+
export default defineConfig({
|
|
209
|
+
fmt: { ...oxfmtrc, ...importFormat },
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
If your `vite.config.js` already has a `lint` block, add `fmt` to the same `defineConfig` call: `defineConfig({ lint, fmt: { ...oxfmtrc, ...importFormat } })`.
|
|
214
|
+
|
|
215
|
+
This puts named imports first, including `import type { … }` and imports with both default and named members. Other default imports come second, along with `import type` default imports and namespace imports (`import * as`). `.vue` imports come last. Oxfmt sorts each group by module path, ignoring letter case, and separates the groups with blank lines. Side-effect imports keep their written order and position, so keep them at the top or bottom of your imports: one written among the other imports stays where it is and splits the group around it.
|
|
216
|
+
|
|
217
|
+
### Parent-folder imports
|
|
218
|
+
|
|
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).
|
|
198
220
|
|
|
199
221
|
## What stays repo-local
|
|
200
222
|
|
package/base.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"plugins": ["oxc", "typescript", "unicorn"],
|
|
2
|
+
"plugins": ["import", "oxc", "typescript", "unicorn"],
|
|
3
3
|
"jsPlugins": [
|
|
4
4
|
"@stylistic/eslint-plugin",
|
|
5
5
|
{
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"no-unexpected-multiline": "error",
|
|
26
26
|
"no-useless-assignment": "error",
|
|
27
27
|
"preserve-caught-error": "error",
|
|
28
|
+
"import/no-relative-parent-imports": "error",
|
|
28
29
|
"sort-imports": [
|
|
29
30
|
"error",
|
|
30
31
|
{
|
|
@@ -62,7 +63,24 @@
|
|
|
62
63
|
{ "blankLine": "never", "prev": "const", "next": "const" },
|
|
63
64
|
{ "blankLine": "never", "prev": "let", "next": "let" },
|
|
64
65
|
{ "blankLine": "always", "prev": "multiline-const", "next": "*" },
|
|
65
|
-
{ "blankLine": "always", "prev": "*", "next": "multiline-const" }
|
|
66
|
+
{ "blankLine": "always", "prev": "*", "next": "multiline-const" },
|
|
67
|
+
{
|
|
68
|
+
"blankLine": "always",
|
|
69
|
+
"prev": "*",
|
|
70
|
+
"next": { "selector": "ExpressionStatement[expression.type=\"AwaitExpression\"]" }
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"blankLine": "always",
|
|
74
|
+
"prev": { "selector": "ExpressionStatement[expression.type=\"AwaitExpression\"]" },
|
|
75
|
+
"next": "*"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"blankLine": "never",
|
|
79
|
+
"prev": { "selector": "ExpressionStatement[expression.type=\"AwaitExpression\"]" },
|
|
80
|
+
"next": { "selector": "ExpressionStatement[expression.type=\"AwaitExpression\"]" }
|
|
81
|
+
},
|
|
82
|
+
{ "blankLine": "always", "prev": "multiline-expression", "next": "*" },
|
|
83
|
+
{ "blankLine": "always", "prev": "*", "next": "multiline-expression" }
|
|
66
84
|
],
|
|
67
85
|
|
|
68
86
|
"vite-plus/prefer-vite-plus-imports": "error"
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
getDocumentationNode,
|
|
3
|
+
reportFunctionDocumentation,
|
|
4
|
+
} from "#comments/utils/documentation.js";
|
|
5
|
+
import { hasImmediateBlockComment, hasImmediateLineComment } from "#comments/utils/source.js";
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Create the class-documentation rule.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getDocumentationNode } from "
|
|
2
|
-
import { hasImmediateLineComment } from "
|
|
1
|
+
import { getDocumentationNode } from "#comments/utils/documentation.js";
|
|
2
|
+
import { hasImmediateLineComment } from "#comments/utils/source.js";
|
|
3
3
|
|
|
4
4
|
// The built-in APIs that require a preceding comment by default.
|
|
5
5
|
const builtInApis = new Set([
|
|
@@ -5,8 +5,7 @@ import {
|
|
|
5
5
|
formatJSDocWrapping,
|
|
6
6
|
hasTargetJSDocTag,
|
|
7
7
|
isJSDoc,
|
|
8
|
-
} from "
|
|
9
|
-
|
|
8
|
+
} from "#comments/utils/jsdoc.js";
|
|
10
9
|
import {
|
|
11
10
|
getCommentNeighbours,
|
|
12
11
|
getCommentText,
|
|
@@ -18,15 +17,14 @@ import {
|
|
|
18
17
|
isDirectiveComment,
|
|
19
18
|
isLeadingComment,
|
|
20
19
|
replaceMinimalComment,
|
|
21
|
-
} from "
|
|
22
|
-
|
|
20
|
+
} from "#comments/utils/source.js";
|
|
23
21
|
import {
|
|
24
22
|
addTerminalPunctuation,
|
|
25
23
|
capitaliseSentence,
|
|
26
24
|
formatSentence,
|
|
27
25
|
refillCommentLines,
|
|
28
26
|
wrapWords,
|
|
29
|
-
} from "
|
|
27
|
+
} from "#comments/utils/wrap.js";
|
|
30
28
|
|
|
31
29
|
// The line length this rule wraps comments to.
|
|
32
30
|
const maximumLineLength = 80;
|
|
@@ -207,6 +205,7 @@ function formatOrdinaryBlockComment(sourceCode, comment) {
|
|
|
207
205
|
formattedLines[firstProseLine],
|
|
208
206
|
capitaliseSentence,
|
|
209
207
|
);
|
|
208
|
+
|
|
210
209
|
formattedLines[lastProseLine] = formatBlockCommentLine(
|
|
211
210
|
formattedLines[lastProseLine],
|
|
212
211
|
addTerminalPunctuation,
|
|
@@ -657,13 +656,10 @@ function reportLineCommentGroups(context) {
|
|
|
657
656
|
});
|
|
658
657
|
});
|
|
659
658
|
|
|
660
|
-
// The group's lines after refilling words from early-wrapped lines.
|
|
661
|
-
const refilledLines = refillCommentLines(formattedLines, maximumLineLength);
|
|
662
|
-
|
|
663
659
|
// The group's text after applying its final line formatting and
|
|
664
660
|
// placement gap.
|
|
665
661
|
const formattedText =
|
|
666
|
-
|
|
662
|
+
formattedLines
|
|
667
663
|
.map(({ prefix, text }) => (text === "" ? prefix.trimEnd() : `${prefix}${text}`))
|
|
668
664
|
.join(getNewline(context.sourceCode.text)) + (placement?.gap ?? "");
|
|
669
665
|
|
|
@@ -834,9 +830,10 @@ function reportBlockComments(context) {
|
|
|
834
830
|
}
|
|
835
831
|
|
|
836
832
|
/**
|
|
837
|
-
* The comment-formatting rule: punctuates comments as sentences
|
|
838
|
-
*
|
|
839
|
-
*
|
|
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.
|
|
840
837
|
*/
|
|
841
838
|
export default {
|
|
842
839
|
meta: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isFunctionValue, reportFunctionDocumentation } from "
|
|
1
|
+
import { isFunctionValue, reportFunctionDocumentation } from "#comments/utils/documentation.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Return the declaration node that owns the documentation position.
|
|
@@ -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
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getDocumentationNode, isFunctionValue } from "
|
|
2
|
-
import { hasImmediateLineComment } from "
|
|
1
|
+
import { getDocumentationNode, isFunctionValue } from "#comments/utils/documentation.js";
|
|
2
|
+
import { hasImmediateLineComment } from "#comments/utils/source.js";
|
|
3
3
|
|
|
4
4
|
// Declaration kinds that require an immediately preceding line comment.
|
|
5
5
|
const documentedKinds = new Set(["await using", "const", "let", "using"]);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { isDirectiveComment } from "#comments/utils/source.js";
|
|
1
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { isDirectiveComment } from "../utils/source.js";
|
|
3
3
|
|
|
4
4
|
// Captures the opening tag's attributes separately, so the setup attribute can
|
|
5
5
|
// be tested without the tag being available from Oxlint's extracted AST.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
getCommentNeighbours,
|
|
3
|
+
isDirectiveComment,
|
|
4
|
+
isLeadingComment,
|
|
5
|
+
} from "#comments/utils/source.js";
|
|
6
|
+
import { getObjectArgument, getObjectProperties, isNamedCall } from "#comments/utils/vue-macro.js";
|
|
3
7
|
|
|
4
8
|
// Matches the single newline and indentation allowed between a comment and an
|
|
5
9
|
// event.
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCommentNeighbours,
|
|
3
|
+
isDirectiveComment,
|
|
4
|
+
isLeadingComment,
|
|
5
|
+
} from "#comments/utils/source.js";
|
|
1
6
|
import {
|
|
2
7
|
getObjectArgument,
|
|
3
8
|
getObjectProperties,
|
|
4
9
|
getPropertyName,
|
|
5
10
|
isNamedCall,
|
|
6
|
-
} from "
|
|
7
|
-
import { getCommentNeighbours, isDirectiveComment, isLeadingComment } from "../utils/source.js";
|
|
11
|
+
} from "#comments/utils/vue-macro.js";
|
|
8
12
|
|
|
9
13
|
// Matches the single newline and indentation allowed between a comment and a
|
|
10
14
|
// prop.
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { getJSDocContent, isJSDoc } from "./jsdoc.js";
|
|
2
|
-
import { getPropertyName } from "./vue-macro.js";
|
|
3
|
-
|
|
4
2
|
import {
|
|
5
3
|
getCommentNeighbours,
|
|
6
4
|
getCommentText,
|
|
7
|
-
|
|
5
|
+
getImmediateNonDirectiveComment,
|
|
8
6
|
isLeadingComment,
|
|
9
7
|
} from "./source.js";
|
|
8
|
+
import { getPropertyName } from "./vue-macro.js";
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* Return the declaration node that owns the documentation position.
|
|
@@ -46,8 +45,8 @@ export function isFunctionValue(node) {
|
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
/**
|
|
49
|
-
* Return the JSDoc comment
|
|
50
|
-
*
|
|
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.
|
|
51
50
|
*
|
|
52
51
|
* @param {object} sourceCode
|
|
53
52
|
* The Oxlint source code object.
|
|
@@ -58,26 +57,21 @@ export function isFunctionValue(node) {
|
|
|
58
57
|
* The qualifying JSDoc comment, or null when none precedes the node.
|
|
59
58
|
*/
|
|
60
59
|
function getDocumentationComment(sourceCode, node) {
|
|
61
|
-
//
|
|
62
|
-
const comment = sourceCode
|
|
63
|
-
.getAllComments()
|
|
64
|
-
.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);
|
|
65
62
|
|
|
66
|
-
if (comment?.type !== "Block"
|
|
63
|
+
if (comment?.type !== "Block") {
|
|
67
64
|
return null;
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
// Checks the comments immediately around the documented node.
|
|
71
68
|
const { next, previous } = getCommentNeighbours(sourceCode, comment);
|
|
72
|
-
// Confirms there is no blank line before the documented node.
|
|
73
|
-
const gap = sourceCode.text.slice(comment.range[1], node.range[0]);
|
|
74
69
|
|
|
75
70
|
if (
|
|
76
71
|
!next ||
|
|
77
72
|
next.range[0] > node.range[0] ||
|
|
78
73
|
next.range[1] > node.range[1] ||
|
|
79
|
-
!isLeadingComment(sourceCode, comment, previous)
|
|
80
|
-
!/^\r?\n[ \t]*$/.test(gap)
|
|
74
|
+
!isLeadingComment(sourceCode, comment, previous)
|
|
81
75
|
) {
|
|
82
76
|
return null;
|
|
83
77
|
}
|
|
@@ -193,8 +187,9 @@ function getParameterPaths(sourceCode, node, rootPath = "options") {
|
|
|
193
187
|
* The JSDoc comment token.
|
|
194
188
|
*
|
|
195
189
|
* @returns {object}
|
|
196
|
-
* An object with names, the set of every documented parameter path
|
|
197
|
-
* topLevelNames, the top-level
|
|
190
|
+
* An object with names, the set of every documented parameter path with
|
|
191
|
+
* optional brackets and defaults removed, and topLevelNames, the top-level
|
|
192
|
+
* names in the order they are documented.
|
|
198
193
|
*/
|
|
199
194
|
function getDocumentedParameters(sourceCode, comment) {
|
|
200
195
|
// Splits the JSDoc block into its individual lines.
|
|
@@ -206,21 +201,16 @@ function getDocumentedParameters(sourceCode, comment) {
|
|
|
206
201
|
|
|
207
202
|
for (const line of content) {
|
|
208
203
|
// Matches an @param tag and captures its documented path.
|
|
209
|
-
const match = line.trim().match(/^@param(?:\s+\{[^}]+\})?\s+(\
|
|
204
|
+
const match = line.trim().match(/^@param(?:\s+\{[^}]+\})?\s+(\S+)/);
|
|
210
205
|
|
|
211
206
|
if (match) {
|
|
212
|
-
// The documented
|
|
213
|
-
|
|
214
|
-
const name = match[1];
|
|
207
|
+
// The documented path without optional brackets or a default value.
|
|
208
|
+
const name = getPlainParameterPath(match[1]);
|
|
215
209
|
|
|
216
210
|
names.add(name);
|
|
217
211
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const topLevelName = name.replace(/^\[|\]$/g, "").split("=")[0];
|
|
221
|
-
|
|
222
|
-
if (!topLevelName.includes(".")) {
|
|
223
|
-
topLevelNames.push(topLevelName);
|
|
212
|
+
if (!name.includes(".")) {
|
|
213
|
+
topLevelNames.push(name);
|
|
224
214
|
}
|
|
225
215
|
}
|
|
226
216
|
}
|
|
@@ -335,10 +325,12 @@ export function reportFunctionDocumentation(context, node, functionNode, options
|
|
|
335
325
|
);
|
|
336
326
|
|
|
337
327
|
for (const path of parameterPaths) {
|
|
338
|
-
// The optional
|
|
339
|
-
|
|
328
|
+
// The path without optional brackets or a default. Documented defaults
|
|
329
|
+
// are not compared with the code, so result.errors, [result.errors] and
|
|
330
|
+
// [result.errors=[]] all document the same property.
|
|
331
|
+
const plainPath = getPlainParameterPath(path);
|
|
340
332
|
|
|
341
|
-
if (!documentedParameters.has(
|
|
333
|
+
if (!documentedParameters.has(plainPath)) {
|
|
342
334
|
context.report({
|
|
343
335
|
message: `${subject} require an @param for ${path}.`,
|
|
344
336
|
node,
|
|
@@ -367,3 +359,18 @@ export function reportFunctionDocumentation(context, node, functionNode, options
|
|
|
367
359
|
});
|
|
368
360
|
}
|
|
369
361
|
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Reduce a parameter path to its plain name, so a documented path matches the
|
|
365
|
+
* required one however the documentation spells it.
|
|
366
|
+
*
|
|
367
|
+
* @param {string} path
|
|
368
|
+
* The documented or required parameter path.
|
|
369
|
+
*
|
|
370
|
+
* @returns {string}
|
|
371
|
+
* The path with brackets and any default removed, such as result.errors for
|
|
372
|
+
* [result.errors=[]].
|
|
373
|
+
*/
|
|
374
|
+
function getPlainParameterPath(path) {
|
|
375
|
+
return path.replace(/^\[|\]$/g, "").split("=")[0];
|
|
376
|
+
}
|