eslint-plugin-oxfmt 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,14 @@
22
22
 
23
23
  - **ESLint**: `>= 9.0.0` (Only supports ESLint flat config)
24
24
  - **Node.js**: `^20.19.0 || >=22.12.0`
25
- - **oxfmt**: `>= 0.65.0`
25
+ - **oxfmt**: `>= 0.67.0`
26
+
27
+ Oxfmt 0.67.0 preserves JSDoc cast parentheses containing comments, fixes
28
+ CSS/SCSS/Less comment and list formatting, and moves GraphQL comments after
29
+ an opening brace onto their own line. Existing files may receive formatting
30
+ fixes after upgrading. No formatting options were renamed or removed, and
31
+ config merge precedence is unchanged. See the
32
+ [upstream release notes](https://github.com/oxc-project/oxc/releases/tag/oxfmt_v0.67.0).
26
33
 
27
34
  ## Installation
28
35
 
@@ -359,6 +366,8 @@ You can pass the Tailwind plugin options to control which attributes/functions a
359
366
  }
360
367
  ```
361
368
 
369
+ Paths in `sortTailwindcss.config` and `sortTailwindcss.stylesheet` are resolved relative to the oxfmt config file when set in that file or its overrides. Paths set in ESLint rule options or rule-level overrides are resolved relative to ESLint `cwd`. Absolute paths are used directly.
370
+
362
371
  ## File-specific overrides
363
372
 
364
373
  Use `overrides` to apply different oxfmt options per file glob (later entries win on conflicts):
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { Linter, Rule } from "eslint";
1
+ import { Linter, Rule as Rule$1 } from "eslint";
2
2
  import "load-oxfmt-config";
3
3
  import "oxfmt";
4
4
  //#region src/types.d.ts
@@ -13,18 +13,18 @@ interface PluginOxfmt {
13
13
  version: string;
14
14
  };
15
15
  rules: {
16
- oxfmt: Rule.RuleModule;
16
+ oxfmt: Rule$1.RuleModule;
17
17
  };
18
18
  }
19
19
  //#endregion
20
20
  //#region src/meta.d.ts
21
- declare const meta: {
21
+ export declare const meta: {
22
22
  name: string;
23
23
  version: string;
24
24
  };
25
25
  //#endregion
26
26
  //#region src/rules/index.d.ts
27
- declare const rules: {
27
+ export declare const rules: {
28
28
  oxfmt: import("eslint").Rule.RuleModule;
29
29
  };
30
30
  //#endregion
@@ -32,12 +32,12 @@ declare const rules: {
32
32
  declare const parserPlain: Linter.Parser;
33
33
  //#endregion
34
34
  //#region src/configs.d.ts
35
- declare const recommendedWithoutParser: Linter.Config<Linter.RulesRecord>;
36
- declare const recommended: Linter.Config<Linter.RulesRecord>;
37
- declare const cliParity: Linter.Config<Linter.RulesRecord>;
38
- declare const configs: PluginOxfmt['configs'];
35
+ export declare const recommendedWithoutParser: Linter.Config<Linter.RulesRecord>;
36
+ export declare const recommended: Linter.Config<Linter.RulesRecord>;
37
+ export declare const cliParity: Linter.Config<Linter.RulesRecord>;
38
+ export declare const configs: PluginOxfmt['configs'];
39
39
  //#endregion
40
40
  //#region src/index.d.ts
41
- declare const plugin: PluginOxfmt;
41
+ export declare const plugin: PluginOxfmt;
42
42
  //#endregion
43
- export { cliParity, configs, plugin as default, plugin, meta, parserPlain, recommended, recommendedWithoutParser, rules };
43
+ export { plugin as default, parserPlain };
package/dist/index.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { Buffer as Buffer$1 } from "node:buffer";
1
2
  import { join } from "node:path";
2
3
  import { createSyncFn } from "synckit";
3
4
  import { URL, fileURLToPath } from "node:url";
@@ -79,7 +80,7 @@ const configs = {
79
80
  //#region src/meta.ts
80
81
  const meta = {
81
82
  name: "eslint-plugin-oxfmt",
82
- version: "0.20.0"
83
+ version: "0.22.0"
83
84
  };
84
85
  //#endregion
85
86
  //#region src/dir.ts
@@ -317,7 +318,7 @@ const oxfmtOptionsSchema = {
317
318
  type: "object",
318
319
  properties: {
319
320
  customGroups: {
320
- description: `Define your own groups for matching very specific imports.\n\nThe customGroups list is ordered: The first definition that matches an element will be used.\nCustom groups have a higher priority than any predefined group.\n\nIf you want a predefined group to take precedence over a custom group,\nyou must write a custom group definition that does the same as what the predefined group does, and put it first in the list.\n\n- (Default: [])`,
321
+ description: `Define your own groups for matching very specific imports.\n\nThe customGroups list is ordered: The first definition that matches an element will be used.\nCustom groups have a higher priority than any predefined group.\n\nIf you want a predefined group to take precedence over a custom group,\nyou must write a custom group definition that does the same as what the predefined group does, and put it first in the list.\n\nIf you specify multiple conditions like elementNamePattern, selector, and modifiers,\nall conditions must be met for an import to match the custom group (AND logic).\n\nNOTE: Predefined group names (e.g. side_effect, external) and unknown are reserved and cannot be used as groupName.\n\n- (Default: [])`,
321
322
  type: "array",
322
323
  items: {
323
324
  additionalProperties: false,
@@ -608,33 +609,35 @@ const rules = { oxfmt: {
608
609
  cwd: context.cwd
609
610
  });
610
611
  if (formatResult.ignored) return;
611
- if (formatResult.errors?.length) for (const error of formatResult.errors) {
612
- const label = error.labels?.[0];
613
- if (label) {
614
- const start = context.sourceCode.getLocFromIndex(label.start);
615
- const end = context.sourceCode.getLocFromIndex(label.end);
616
- context.report({
612
+ if (formatResult.errors?.length) {
613
+ const sourceBytes = Buffer$1.from(sourceText);
614
+ for (const error of formatResult.errors) {
615
+ const label = error.labels?.[0];
616
+ if (label) {
617
+ const start = context.sourceCode.getLocFromIndex(sourceBytes.toString("utf8", 0, label.start).length);
618
+ const end = context.sourceCode.getLocFromIndex(sourceBytes.toString("utf8", 0, label.end).length);
619
+ context.report({
620
+ loc: {
621
+ end,
622
+ start
623
+ },
624
+ message: `Failed to format code: ${error.message}`
625
+ });
626
+ } else context.report({
627
+ message: `Failed to format code: ${error.message}`,
617
628
  loc: {
618
- end,
619
- start
620
- },
621
- message: `Failed to format code: ${error.message}`
622
- });
623
- } else context.report({
624
- message: `Failed to format code: ${error.message}`,
625
- loc: {
626
- end: {
627
- column: 0,
628
- line: 1
629
- },
630
- start: {
631
- column: 0,
632
- line: 1
629
+ end: {
630
+ column: 0,
631
+ line: 1
632
+ },
633
+ start: {
634
+ column: 0,
635
+ line: 1
636
+ }
633
637
  }
634
- }
635
- });
636
- }
637
- else reportDifferences(context, sourceText, formatResult.code);
638
+ });
639
+ }
640
+ } else reportDifferences(context, sourceText, formatResult.code);
638
641
  } catch (error) {
639
642
  const details = error instanceof Error ? `: ${error.message}` : "";
640
643
  context.report({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eslint-plugin-oxfmt",
3
3
  "type": "module",
4
- "version": "0.20.0",
4
+ "version": "0.22.0",
5
5
  "description": "An ESLint plugin for formatting code with oxfmt.",
6
6
  "keywords": [
7
7
  "eslint",
@@ -19,7 +19,7 @@
19
19
  "homepage": "https://github.com/ntnyq/eslint-plugin-oxfmt#readme",
20
20
  "repository": {
21
21
  "type": "git",
22
- "url": "ntnyq/eslint-plugin-oxfmt"
22
+ "url": "git+https://github.com/ntnyq/eslint-plugin-oxfmt.git"
23
23
  },
24
24
  "bugs": {
25
25
  "url": "https://github.com/ntnyq/eslint-plugin-oxfmt/issues"
@@ -47,7 +47,7 @@
47
47
  "sideEffects": false,
48
48
  "peerDependencies": {
49
49
  "eslint": "^9.5.0 || ^10.0.0",
50
- "oxfmt": ">=0.65.0",
50
+ "oxfmt": ">=0.67.0",
51
51
  "svelte": "*"
52
52
  },
53
53
  "peerDependenciesMeta": {
@@ -57,34 +57,34 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "generate-differences": "^0.1.1",
60
- "ignore": "^7.0.6",
61
- "load-oxfmt-config": "^0.21.0",
60
+ "ignore": "^7.0.8",
61
+ "load-oxfmt-config": "^0.23.0",
62
62
  "picomatch": "^4.0.7",
63
63
  "synckit": "^0.11.13"
64
64
  },
65
65
  "devDependencies": {
66
- "@ntnyq/eslint-config": "^7.0.0-beta.17",
66
+ "@ntnyq/eslint-config": "^7.0.0-beta.19",
67
67
  "@types/json-schema": "^7.0.15",
68
- "@types/node": "^26.3.0",
68
+ "@types/node": "^26.5.0",
69
69
  "@typescript/native-preview": "^7.0.0-dev.20260707.2",
70
- "bumpp": "^12.2.1",
71
- "eslint": "^10.9.1",
70
+ "bumpp": "^12.3.0",
71
+ "eslint": "^10.10.0",
72
72
  "eslint-parser-plain": "^0.1.1",
73
- "eslint-plugin-oxfmt": "0.20.0",
73
+ "eslint-plugin-oxfmt": "0.22.0",
74
74
  "eslint-typegen": "^2.3.1",
75
75
  "eslint-vitest-rule-tester": "^3.1.0",
76
76
  "husky": "^9.1.7",
77
77
  "jsonc-eslint-parser": "^3.3.0",
78
78
  "nano-staged": "^1.0.2",
79
79
  "npm-run-all2": "^9.0.3",
80
- "oxfmt": "^0.65.0",
80
+ "oxfmt": "^0.67.0",
81
81
  "show-invisibles": "^0.0.2",
82
- "svelte": "^5.56.10",
82
+ "svelte": "^5.57.0",
83
83
  "tinyglobby": "^0.2.17",
84
- "tsdown": "^0.22.14",
85
- "tsx": "^4.23.12",
84
+ "tsdown": "^0.23.0",
85
+ "tsx": "^4.23.13",
86
86
  "typescript": "^6.0.3",
87
- "vitest": "^4.1.11"
87
+ "vitest": "^5.0.0"
88
88
  },
89
89
  "engines": {
90
90
  "node": "^20.19.0 || >=22.12.0"
package/workers/oxfmt.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // @ts-check
2
2
 
3
- import { dirname, extname, relative } from 'node:path'
3
+ import { dirname, extname, relative, resolve } from 'node:path'
4
4
  import ignore from 'ignore'
5
5
  import { isOxfmtIgnored, loadOxfmtConfig } from 'load-oxfmt-config'
6
6
  import { format } from 'oxfmt'
@@ -8,6 +8,7 @@ import picomatch from 'picomatch'
8
8
  import { runAsWorker } from 'synckit'
9
9
 
10
10
  /**
11
+ * Options consumed by the worker instead of the formatter.
11
12
  * @typedef {object} PluginOnlyOptions
12
13
  * @property {string} [cwd] - Base working directory used for path resolution.
13
14
  * @property {string} [configPath] - Explicit path to oxfmt config file.
@@ -21,10 +22,17 @@ import { runAsWorker } from 'synckit'
21
22
  */
22
23
 
23
24
  /**
24
- * @typedef {PluginOnlyOptions & import('load-oxfmt-config').LoadOxfmtConfigOptions & import('oxfmt').FormatConfig} PluginOptions
25
+ * Formatting options, ignore patterns, and file-specific overrides.
26
+ * @typedef {import('load-oxfmt-config').OxfmtOptions} OxfmtOptions
25
27
  */
26
28
 
27
29
  /**
30
+ * Combined rule options accepted by the worker.
31
+ * @typedef {PluginOnlyOptions & import('load-oxfmt-config').LoadOxfmtConfigOptions & OxfmtOptions} PluginOptions
32
+ */
33
+
34
+ /**
35
+ * Result returned when ignore handling skips a file.
28
36
  * @typedef {object} WorkerIgnoredResult
29
37
  * @property {true} ignored - Indicates formatting was skipped due to ignore rules.
30
38
  * @property {import('load-oxfmt-config').IsOxfmtIgnoredResult['reason']} [reason] - Ignore reason from resolution step.
@@ -33,6 +41,7 @@ import { runAsWorker } from 'synckit'
33
41
  */
34
42
 
35
43
  /**
44
+ * Result returned after attempting to format a file.
36
45
  * @typedef {object} WorkerFormattedResult
37
46
  * @property {false} [ignored] - False or undefined when formatting was attempted.
38
47
  * @property {string} code - Formatted source code.
@@ -40,20 +49,31 @@ import { runAsWorker } from 'synckit'
40
49
  */
41
50
 
42
51
  /**
52
+ * Worker response for either ignored or formatted source text.
43
53
  * @typedef {WorkerIgnoredResult | WorkerFormattedResult} WorkerFormatResult
44
54
  */
45
55
 
46
56
  /**
57
+ * Worker controls separated from formatting and file-matching options.
47
58
  * @typedef {object} SplitOptionsResult
48
59
  * @property {PluginOnlyOptions} pluginOptions - Plugin orchestration options.
49
- * @property {import('oxfmt').FormatConfig} formatOptions - Pure oxfmt options.
60
+ * @property {OxfmtOptions} formatOptions - Oxfmt options including ignorePatterns and overrides.
50
61
  */
51
62
 
52
63
  /**
64
+ * A file-matching override loaded from config or supplied inline.
53
65
  * @typedef {import('load-oxfmt-config').OxfmtConfigOverride} OxfmtConfigOverride
54
66
  */
55
67
 
68
+ /**
69
+ * Maximum number of compiled matchers retained in each worker cache.
70
+ * @type {number}
71
+ */
56
72
  const MAX_CACHE_SIZE = 200
73
+ /**
74
+ * Option names consumed by worker orchestration.
75
+ * @type {Set<string>}
76
+ */
57
77
  const PLUGIN_ONLY_OPTIONS = new Set([
58
78
  'configPath',
59
79
  'cwd',
@@ -65,9 +85,15 @@ const PLUGIN_ONLY_OPTIONS = new Set([
65
85
  'useConfig',
66
86
  'withNodeModules',
67
87
  ])
68
- /** @type {Map<string, import('picomatch').Matcher>} */
88
+ /**
89
+ * Compiled override globs keyed by their ordered pattern list.
90
+ * @type {Map<string, import('picomatch').Matcher>}
91
+ */
69
92
  const overrideMatcherCache = new Map()
70
- /** @type {Map<string, import('ignore').Ignore>} */
93
+ /**
94
+ * Compiled gitignore-style rules keyed by their ordered pattern list.
95
+ * @type {Map<string, import('ignore').Ignore>}
96
+ */
71
97
  const ignoreMatcherCache = new Map()
72
98
 
73
99
  /**
@@ -75,13 +101,18 @@ const ignoreMatcherCache = new Map()
75
101
  * @param {string} relativePath - Relative file path used for matching.
76
102
  * @param {import('oxfmt').FormatConfig} baseOptions - Base format options.
77
103
  * @param {OxfmtConfigOverride[] | undefined} overrides - Override entries.
104
+ * @param {string} optionsBaseDir - Base directory for paths inside override options.
78
105
  * @returns {import('oxfmt').FormatConfig} Options after override merge.
79
106
  */
80
- function applyOverrides(relativePath, baseOptions, overrides) {
107
+ function applyOverrides(relativePath, baseOptions, overrides, optionsBaseDir) {
81
108
  if (!overrides?.length) {
82
109
  return baseOptions
83
110
  }
84
111
 
112
+ /**
113
+ * Format options after applying each matching override in order.
114
+ * @type {import('oxfmt').FormatConfig}
115
+ */
85
116
  let merged = baseOptions
86
117
  for (const override of overrides) {
87
118
  if (!override?.files?.length) {
@@ -99,7 +130,7 @@ function applyOverrides(relativePath, baseOptions, overrides) {
99
130
  if (matches && !excluded && override.options) {
100
131
  merged = {
101
132
  ...merged,
102
- ...override.options,
133
+ ...resolveTailwindPaths(override.options, optionsBaseDir),
103
134
  }
104
135
  }
105
136
  }
@@ -123,6 +154,10 @@ async function formatViaOxfmt(filename, sourceText, options = {}) {
123
154
  const useConfig = pluginOptions.useConfig !== false
124
155
  const useCache = getUseCacheOption(pluginOptions)
125
156
 
157
+ /**
158
+ * Inline ignore patterns matched relative to ESLint cwd.
159
+ * @type {string[] | undefined}
160
+ */
126
161
  const ruleIgnorePatterns = isStringArray(inlineFormatOptions.ignorePatterns)
127
162
  ? inlineFormatOptions.ignorePatterns
128
163
  : undefined
@@ -138,7 +173,10 @@ async function formatViaOxfmt(filename, sourceText, options = {}) {
138
173
  }
139
174
 
140
175
  if (pluginOptions.respectOxfmtDefaultIgnores !== false && cwd) {
141
- /** @type {import('load-oxfmt-config').IsOxfmtIgnoredOptions} */
176
+ /**
177
+ * Config and ignore resolution settings for the current file.
178
+ * @type {import('load-oxfmt-config').IsOxfmtIgnoredOptions}
179
+ */
142
180
  const ignoredOptions = {
143
181
  configPath: pluginOptions.configPath,
144
182
  cwd,
@@ -168,12 +206,30 @@ async function formatViaOxfmt(filename, sourceText, options = {}) {
168
206
  }
169
207
  }
170
208
 
171
- /** @type {OxfmtConfigOverride[] | undefined} */
172
- let effectiveOverrides
173
- /** @type {string} */
174
- let overrideBaseDir = cwd ?? dirname(filename)
175
- /** @type {import('oxfmt').FormatConfig} */
176
- let finalOptions
209
+ /**
210
+ * Base directory for paths supplied in inline rule options.
211
+ * @type {string}
212
+ */
213
+ const inlineBaseDir = cwd ?? dirname(filename)
214
+ /**
215
+ * Base for override globs and config-derived paths, updated after discovery.
216
+ * @type {string}
217
+ */
218
+ let overrideBaseDir = inlineBaseDir
219
+ /**
220
+ * Inline overrides separated from root options to preserve merge order.
221
+ * @type {OxfmtOptions}
222
+ */
223
+ const { overrides: ruleOverrides, ...inlineOptionsWithoutOverrides } =
224
+ inlineFormatOptions
225
+ /**
226
+ * Effective formatter options with paths resolved against their source.
227
+ * @type {import('oxfmt').FormatConfig}
228
+ */
229
+ let finalOptions = resolveTailwindPaths(
230
+ inlineOptionsWithoutOverrides,
231
+ inlineBaseDir,
232
+ )
177
233
 
178
234
  if (useConfig) {
179
235
  const loaded = await loadOxfmtConfig({
@@ -185,34 +241,26 @@ async function formatViaOxfmt(filename, sourceText, options = {}) {
185
241
  useCache,
186
242
  })
187
243
  const { overrides: configOverrides, ...loadedConfig } = loaded.config
188
- const { overrides: ruleOverrides, ...inlineOptionsWithoutOverrides } =
189
- inlineFormatOptions
190
- effectiveOverrides = [
191
- ...(configOverrides ?? []),
192
- ...(Array.isArray(ruleOverrides) ? ruleOverrides : []),
193
- ]
194
244
  overrideBaseDir = loaded.dirname ?? overrideBaseDir
195
245
 
196
246
  finalOptions = {
197
- ...loadedConfig,
198
- ...inlineOptionsWithoutOverrides,
199
- }
200
- } else {
201
- const { overrides: ruleOverrides, ...inlineOptionsWithoutOverrides } =
202
- inlineFormatOptions
203
- effectiveOverrides = Array.isArray(ruleOverrides)
204
- ? ruleOverrides
205
- : undefined
206
- finalOptions = {
207
- ...inlineOptionsWithoutOverrides,
247
+ ...resolveTailwindPaths(loadedConfig, overrideBaseDir),
248
+ ...finalOptions,
208
249
  }
250
+ finalOptions = applyOverrides(
251
+ getRelativePath(overrideBaseDir, filename),
252
+ finalOptions,
253
+ configOverrides,
254
+ overrideBaseDir,
255
+ )
209
256
  }
210
257
 
211
258
  const overrideRelativePath = getRelativePath(overrideBaseDir, filename)
212
259
  finalOptions = applyOverrides(
213
260
  overrideRelativePath,
214
261
  finalOptions,
215
- effectiveOverrides,
262
+ Array.isArray(ruleOverrides) ? ruleOverrides : undefined,
263
+ inlineBaseDir,
216
264
  )
217
265
 
218
266
  return format(filename, sourceText, finalOptions)
@@ -239,6 +287,7 @@ function getCachedIgnoreMatcher(patterns) {
239
287
  * Get or create a cached picomatch matcher for oxfmt override globs.
240
288
  * @param {string[]} patterns - Glob patterns.
241
289
  * @returns {import('picomatch').Matcher} Compiled matcher.
290
+ * @throws {Error} If a pattern contains invalid glob syntax.
242
291
  */
243
292
  function getCachedOverrideMatcher(patterns) {
244
293
  const key = patterns.join('\0')
@@ -247,6 +296,10 @@ function getCachedOverrideMatcher(patterns) {
247
296
  return cached
248
297
  }
249
298
 
299
+ /**
300
+ * Match oxfmt's dotfile and literal-extglob behavior.
301
+ * @type {{dot: boolean, noextglob: boolean, strictBrackets: boolean}}
302
+ */
250
303
  const matcherOptions = {
251
304
  dot: true,
252
305
  noextglob: true,
@@ -306,6 +359,33 @@ function isStringArray(value) {
306
359
  return Array.isArray(value) && value.every(item => typeof item === 'string')
307
360
  }
308
361
 
362
+ /**
363
+ * Resolve Tailwind paths before merging options from different sources.
364
+ * @param {import('oxfmt').FormatConfig} options - Format options to normalize.
365
+ * @param {string} baseDir - Config directory or ESLint cwd for inline options.
366
+ * @returns {import('oxfmt').FormatConfig} Options with absolute Tailwind paths.
367
+ */
368
+ function resolveTailwindPaths(options, baseDir) {
369
+ const { sortTailwindcss } = options
370
+ if (!sortTailwindcss || typeof sortTailwindcss !== 'object') {
371
+ return options
372
+ }
373
+
374
+ /**
375
+ * Copy paths before resolving them to avoid mutating cached config objects.
376
+ * @type {import('oxfmt').SortTailwindcssConfig}
377
+ */
378
+ const resolved = { ...sortTailwindcss }
379
+ if (resolved.config) {
380
+ resolved.config = resolve(baseDir, resolved.config)
381
+ }
382
+ if (resolved.stylesheet) {
383
+ resolved.stylesheet = resolve(baseDir, resolved.stylesheet)
384
+ }
385
+
386
+ return { ...options, sortTailwindcss: resolved }
387
+ }
388
+
309
389
  /**
310
390
  * Store a value in a FIFO cache map with a bounded size.
311
391
  * @template T Cache value type.
@@ -324,9 +404,9 @@ function setCacheEntry(cache, key, value) {
324
404
  }
325
405
 
326
406
  /**
327
- * Check if a file should be ignored by provided glob patterns.
407
+ * Check if a file should be ignored by provided gitignore-style patterns.
328
408
  * @param {string} relativePath - Relative path against pattern base.
329
- * @param {string[] | undefined} ignorePatterns - Glob patterns.
409
+ * @param {string[] | undefined} ignorePatterns - Ordered gitignore-style patterns.
330
410
  * @returns {boolean} Whether file is ignored.
331
411
  */
332
412
  function shouldIgnoreFile(relativePath, ignorePatterns) {
@@ -339,14 +419,20 @@ function shouldIgnoreFile(relativePath, ignorePatterns) {
339
419
  }
340
420
 
341
421
  /**
342
- * Split worker options into plugin orchestration options and pure format options.
422
+ * Split worker controls from oxfmt options, ignore patterns, and overrides.
343
423
  * @param {PluginOptions} [options] - Raw worker options.
344
424
  * @returns {SplitOptionsResult} Split option buckets.
345
425
  */
346
426
  function splitOptions(options = {}) {
347
- /** @type {Record<string, unknown>} */
427
+ /**
428
+ * Options consumed by config loading and ignore orchestration.
429
+ * @type {Record<string, unknown>}
430
+ */
348
431
  const pluginOptions = {}
349
- /** @type {Record<string, unknown>} */
432
+ /**
433
+ * Remaining oxfmt options, including file-matching configuration.
434
+ * @type {Record<string, unknown>}
435
+ */
350
436
  const formatOptions = {}
351
437
 
352
438
  for (const [key, value] of Object.entries(options)) {
@@ -358,7 +444,7 @@ function splitOptions(options = {}) {
358
444
  }
359
445
 
360
446
  return {
361
- formatOptions: /** @type {import('oxfmt').FormatConfig} */ (formatOptions),
447
+ formatOptions: /** @type {OxfmtOptions} */ (formatOptions),
362
448
  pluginOptions: /** @type {PluginOnlyOptions} */ (pluginOptions),
363
449
  }
364
450
  }
@@ -366,11 +452,19 @@ function splitOptions(options = {}) {
366
452
  /**
367
453
  * Validate plugin-only options before dispatching to helper libraries.
368
454
  * @param {PluginOnlyOptions} pluginOptions - Plugin-only options.
455
+ * @returns {void}
456
+ * @throws {TypeError} If a supported plugin option has an invalid value type.
369
457
  */
370
458
  function validatePluginOptions(pluginOptions) {
371
- /** @type {('cwd' | 'configPath')[]} */
459
+ /**
460
+ * Plugin options validated as strings when provided.
461
+ * @type {('cwd' | 'configPath')[]}
462
+ */
372
463
  const stringKeys = ['configPath', 'cwd']
373
- /** @type {('disableNestedConfig' | 'respectOxfmtDefaultIgnores' | 'useConfig' | 'useCache' | 'withNodeModules')[]} */
464
+ /**
465
+ * Plugin switches validated as booleans when provided.
466
+ * @type {('disableNestedConfig' | 'respectOxfmtDefaultIgnores' | 'useConfig' | 'useCache' | 'withNodeModules')[]}
467
+ */
374
468
  const booleanKeys = [
375
469
  'disableNestedConfig',
376
470
  'respectOxfmtDefaultIgnores',