@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 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
- The `vp check` and `vp lint` commands read Oxlint settings only from a `lint` block in `vite.config.js`; they do not read `.oxlintrc.json` directly. If `vite.config.js` is missing, they silently use an unrelated default configuration. You may still see plausible warnings and exit codes, but none of your rules are applied.
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 built from the config layer(s) and the repo's `.oxlintrc.json`, imported as JSON. Concatenate the `overrides` arrays from every imported layer before the local overrides so shared file-specific rules still apply. Verify the setup with `vp lint --print-config <file>` and check that your real values, not defaults, are active.
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 lintConfigBase from "@lewishowles/lint-config/base.json" with { type: "json" };
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
- const lint = {
120
- ...lintConfigBase,
121
- env: oxlintrc.env,
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, but leaves declaration order (which import comes first) to Oxfmt: enable Oxfmt's `sortImports` option in your local `.oxfmtrc.json` if you want that sorted and fixed automatically.
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 { getDocumentationNode, reportFunctionDocumentation } from "../utils/documentation.js";
2
- import { hasImmediateBlockComment, hasImmediateLineComment } from "../utils/source.js";
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 "../utils/documentation.js";
2
- import { hasImmediateLineComment } from "../utils/source.js";
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 "../utils/jsdoc.js";
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 "../utils/source.js";
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 "../utils/wrap.js";
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
- refilledLines
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, refills
838
- * early-wrapped lines, reindents line-comment groups and wraps any comment past
839
- * 80 columns, replacing each comment in one edit.
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 "../utils/documentation.js";
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 (!isFirstLevelObjectProperty(node) || !isFunctionValue(node.value)) {
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 "../utils/documentation.js";
2
- import { hasImmediateLineComment } from "../utils/source.js";
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 { getCommentNeighbours, isDirectiveComment, isLeadingComment } from "../utils/source.js";
2
- import { getObjectArgument, getObjectProperties, isNamedCall } from "../utils/vue-macro.js";
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 "../utils/vue-macro.js";
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
- isDirectiveComment,
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 immediately preceding a node, when the comment
50
- * qualifies as that node's documentation.
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
- // Finds the closest preceding comment.
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" || isDirectiveComment(comment)) {
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, and
197
- * topLevelNames, the top-level names in the order they are documented.
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+(\[[^\]]+\]|\S+)/);
204
+ const match = line.trim().match(/^@param(?:\s+\{[^}]+\})?\s+(\S+)/);
210
205
 
211
206
  if (match) {
212
- // The documented name as written, including optional brackets and
213
- // any default value.
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
- // Removes optional and default-value syntax before checking for a
219
- // nested path.
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 JSDoc spelling for this parameter path.
339
- const optionalPath = `[${path}]`;
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(path) && !documentedParameters.has(optionalPath)) {
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
+ }