eslint-plugin-oxfmt 0.5.0 → 0.6.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
@@ -13,7 +13,7 @@
13
13
  - 🔧 **Auto-fix** - Automatically format code on save or via ESLint's fix command
14
14
  - 🎯 **ESLint Integration** - Seamlessly integrates with ESLint v9+ flat config
15
15
  - 📦 **Zero Config** - Works out of the box with sensible defaults
16
- - 🧩 **Config File Discovery** - Supports `.oxfmtrc.json`, `.oxfmtrc.jsonc`, and `oxfmt.config.ts`
16
+ - 🧩 **Config File Discovery** - Auto-discovers `.oxfmtrc.json`, `.oxfmtrc.jsonc`, and `oxfmt.config.ts`
17
17
  - 📝 **EditorConfig Integration** - Respects a subset of `.editorconfig` options via [oxfmt's strategy](https://oxc.rs/docs/guide/usage/formatter/config#editorconfig)
18
18
  - 🎨 **Highly Configurable** - Supports all oxfmt formatting options
19
19
  - 🌐 **Multi-language Support** - JavaScript, TypeScript, JSX, TSX and [more](https://oxc.rs/docs/guide/usage/formatter.html#supported-languages)
@@ -71,6 +71,20 @@ export default [
71
71
  ]
72
72
  ```
73
73
 
74
+ For CLI-like ignore/config behavior, use `cliParity`:
75
+
76
+ ```js
77
+ // eslint.config.mjs
78
+ import pluginOxfmt from 'eslint-plugin-oxfmt'
79
+
80
+ export default [
81
+ {
82
+ ...pluginOxfmt.configs.cliParity,
83
+ files: ['**/*.{js,ts,mjs,cjs,jsx,tsx,json,jsonc,yaml,yml}'],
84
+ },
85
+ ]
86
+ ```
87
+
74
88
  ### Custom Configuration
75
89
 
76
90
  You can customize the formatting options by configuring the rule:
@@ -157,10 +171,15 @@ All options are optional and default to sensible values.
157
171
 
158
172
  ### Plugin Options
159
173
 
160
- | Option | Type | Default | Description |
161
- | ------------ | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
162
- | `useConfig` | `boolean` | `true` | Load `.oxfmtrc.json`, `.oxfmtrc.jsonc`, or `oxfmt.config.ts` via `load-oxfmt-config` (with `.editorconfig` merge support). Set to `false` to rely only on inline options. |
163
- | `configPath` | `string` | — | Custom path to an oxfmt config file. Resolved from ESLint `cwd` when set. |
174
+ | Option | Type | Default | Description |
175
+ | ---------------------------- | ------------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
176
+ | `useConfig` | `boolean` | `true` | Load `.oxfmtrc.json`, `.oxfmtrc.jsonc`, or `oxfmt.config.*` via `load-oxfmt-config`. Set to `false` to rely only on inline options. |
177
+ | `configPath` | `string` | — | Custom path to an oxfmt config file. Relative paths resolve from ESLint `cwd`; absolute paths are used as-is. Explicit `configPath` accepts `.json`, `.jsonc`, `.ts`, `.mts`, `.cts`, `.js`, `.mjs`, `.cjs`. |
178
+ | `editorconfig` | `boolean \| { onlyCwd?: boolean; cwd?: string }` | `true` | Control `.editorconfig` loading. Use `false` to disable, or object form for advanced resolution strategy. |
179
+ | `ignorePath` | `string \| string[]` | — | Ignore file path(s) for CLI-style ignore resolution (same role as CLI `--ignore-path`). |
180
+ | `withNodeModules` | `boolean` | `false` | Include files under `node_modules` during ignore checks. |
181
+ | `disableNestedConfig` | `boolean` | `false` | Disable nested config discovery and resolve config from `cwd` / `configPath` only. |
182
+ | `respectOxfmtDefaultIgnores` | `boolean` | `true` | Respect oxfmt default ignores (`.gitignore`, `.prettierignore`, default ignored directories, default ignored lockfiles). |
164
183
 
165
184
  > Note: `cwd` is taken from ESLint automatically; you usually do not need to set it manually.
166
185
  > `.editorconfig` merge behavior follows oxfmt's documented strategy: https://oxc.rs/docs/guide/usage/formatter/config#editorconfig
@@ -171,10 +190,15 @@ When `useConfig` is `true`, the plugin loads config using `load-oxfmt-config`.
171
190
 
172
191
  - Config discovery order (from `cwd`, walking upward): `.oxfmtrc.json` → `.oxfmtrc.jsonc` → `oxfmt.config.ts`
173
192
  - `.editorconfig` support: nearest `.editorconfig` (including section overrides) is merged into the final options
193
+ - Set `editorconfig: false` to disable `.editorconfig` merging
194
+ - Set `editorconfig: { onlyCwd: true }` to read only the current `cwd`'s `.editorconfig` (no upward traversal)
195
+ - Set `editorconfig: { cwd: '/path/to/base' }` to customize editorconfig resolution base directory
174
196
  - `configPath` overrides discovery and directly targets the specified config file
197
+ - `configPath` supports explicit file paths with extensions: `.json`, `.jsonc`, `.ts`, `.mts`, `.cts`, `.js`, `.mjs`, `.cjs`
175
198
  - ESLint rule options generally take highest priority because inline rule options are merged after loaded config.
176
- - When `useConfig` is `true`, rule-level `overrides` are ignored. Only `overrides` loaded from the resolved oxfmt config file are applied.
177
- - Rule-level `ignorePatterns` still override config-derived `ignorePatterns` when provided.
199
+ - Rule-level `ignorePatterns` are resolved relative to ESLint `cwd`; config-level `ignorePatterns` are resolved relative to the resolved config file directory.
200
+ - When `useConfig` is `true`, config `overrides` are applied first and rule-level `overrides` are appended after them (later entries win on conflicts).
201
+ - When `useConfig` is `false`, config discovery and config `ignorePatterns` are skipped, while global ignores still apply when `respectOxfmtDefaultIgnores` is enabled.
178
202
 
179
203
  For detailed behavior, see:
180
204
 
@@ -183,14 +207,14 @@ For detailed behavior, see:
183
207
 
184
208
  ### Basic Options
185
209
 
186
- | Option | Type | Default | Description |
187
- | ---------------- | ---------- | ------- | -------------------------------------------------- |
188
- | `semi` | `boolean` | `true` | Add semicolons at the end of statements |
189
- | `singleQuote` | `boolean` | `false` | Use single quotes instead of double quotes |
190
- | `tabWidth` | `number` | `2` | Number of spaces per indentation level |
191
- | `useTabs` | `boolean` | `false` | Use tabs for indentation |
192
- | `printWidth` | `number` | `100` | Maximum line length for wrapping |
193
- | `ignorePatterns` | `string[]` | `[]` | Glob patterns (relative to cwd) to skip formatting |
210
+ | Option | Type | Default | Description |
211
+ | ---------------- | ---------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
212
+ | `semi` | `boolean` | `true` | Add semicolons at the end of statements |
213
+ | `singleQuote` | `boolean` | `false` | Use single quotes instead of double quotes |
214
+ | `tabWidth` | `number` | `2` | Number of spaces per indentation level |
215
+ | `useTabs` | `boolean` | `false` | Use tabs for indentation |
216
+ | `printWidth` | `number` | `100` | Maximum line length for wrapping |
217
+ | `ignorePatterns` | `string[]` | `[]` | Glob patterns to skip formatting. Rule option patterns are resolved from ESLint cwd; config file patterns are resolved from the config file directory. |
194
218
 
195
219
  ### Trailing Commas
196
220
 
@@ -224,7 +248,7 @@ For detailed behavior, see:
224
248
  | ---------------- | ------------------------------------------- | ------------- | ------------------------------------------------ |
225
249
  | `bracketSpacing` | `boolean` | `true` | Print spaces between brackets in object literals |
226
250
  | `quoteProps` | `'as-needed' \| 'consistent' \| 'preserve'` | `'as-needed'` | When to quote object property names |
227
- | `objectWrap` | `'preserve' \| 'collapse' \| 'always'` | `'preserve'` | How to wrap object literals |
251
+ | `objectWrap` | `'preserve' \| 'collapse'` | `'preserve'` | How to wrap object literals |
228
252
 
229
253
  ### Line Endings
230
254
 
@@ -381,12 +405,31 @@ This plugin provides a single rule that formats your code using oxfmt.
381
405
  - Fixable: Yes (automatically applies formatting)
382
406
  - Type: Layout
383
407
 
408
+ ## CLI parity mode
409
+
410
+ `oxfmt/cli-parity` tries to match `oxfmt` CLI behavior for files processed by ESLint.
411
+
412
+ It respects:
413
+
414
+ - `.oxfmtrc.json`
415
+ - `.oxfmtrc.jsonc`
416
+ - `oxfmt.config.*`
417
+ - `.editorconfig`
418
+ - `ignorePatterns`
419
+ - `.gitignore`
420
+ - `.prettierignore`
421
+ - default ignored directories
422
+ - default ignored lockfiles
423
+
424
+ Note: ESLint still controls file discovery. Files excluded by ESLint will never reach this rule.
425
+
384
426
  ## Integration
385
427
 
386
428
  ### Parser Compatibility
387
429
 
388
430
  - `recommended`: forces `eslint-parser-plain` for matched files
389
431
  - `recommendedWithoutParser`: parser-agnostic (safe to compose with language-specific parsers)
432
+ - `cliParity`: parser-agnostic preset tuned for CLI-like config/ignore behavior
390
433
 
391
434
  When composing shareable configs, prefer `recommendedWithoutParser` if parser ownership belongs to another preset.
392
435
 
package/dist/index.d.mts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as _$eslint from "eslint";
2
2
  import { Linter, Rule } from "eslint";
3
-
4
3
  //#region src/types.d.ts
5
4
  interface PluginOxfmt {
6
5
  configs: {
6
+ cliParity: Linter.Config<Linter.RulesRecord>;
7
7
  recommended: Linter.Config<Linter.RulesRecord>;
8
8
  recommendedWithoutParser: Linter.Config<Linter.RulesRecord>;
9
9
  };
@@ -33,9 +33,10 @@ declare const parserPlain: Linter.Parser;
33
33
  //#region src/configs.d.ts
34
34
  declare const recommendedWithoutParser: Linter.Config<Linter.RulesRecord>;
35
35
  declare const recommended: Linter.Config<Linter.RulesRecord>;
36
+ declare const cliParity: Linter.Config<Linter.RulesRecord>;
36
37
  declare const configs: PluginOxfmt['configs'];
37
38
  //#endregion
38
39
  //#region src/index.d.ts
39
40
  declare const plugin: PluginOxfmt;
40
41
  //#endregion
41
- export { configs, plugin as default, plugin, meta, parserPlain, recommended, recommendedWithoutParser, rules };
42
+ export { cliParity, configs, plugin as default, plugin, meta, parserPlain, recommended, recommendedWithoutParser, rules };
package/dist/index.mjs CHANGED
@@ -60,7 +60,18 @@ const recommended = {
60
60
  name: "oxfmt/recommended",
61
61
  languageOptions: { parser: parserPlain }
62
62
  };
63
+ const cliParity = {
64
+ ...recommendedWithoutParser,
65
+ name: "oxfmt/cli-parity",
66
+ rules: { "oxfmt/oxfmt": ["error", {
67
+ disableNestedConfig: false,
68
+ respectOxfmtDefaultIgnores: true,
69
+ useConfig: true,
70
+ withNodeModules: false
71
+ }] }
72
+ };
63
73
  const configs = {
74
+ cliParity,
64
75
  recommended,
65
76
  recommendedWithoutParser
66
77
  };
@@ -68,7 +79,7 @@ const configs = {
68
79
  //#region src/meta.ts
69
80
  const meta = {
70
81
  name: "eslint-plugin-oxfmt",
71
- version: "0.5.0"
82
+ version: "0.6.0"
72
83
  };
73
84
  //#endregion
74
85
  //#region src/dir.ts
@@ -184,7 +195,7 @@ const oxfmtOptionsSchema = {
184
195
  type: "string"
185
196
  },
186
197
  ignorePatterns: {
187
- description: `Ignore files matching these glob patterns. Current working directory is used as the root.`,
198
+ description: `Ignore files matching these glob patterns. Rule-level ignorePatterns are resolved relative to ESLint cwd. Config ignorePatterns are resolved relative to the config file directory.`,
188
199
  type: "array",
189
200
  items: { type: "string" }
190
201
  },
@@ -256,12 +267,8 @@ const oxfmtOptionsSchema = {
256
267
  type: "boolean"
257
268
  },
258
269
  objectWrap: {
259
- description: `How to wrap object literals when they could fit on one line or span multiple lines. (Default: "preserve")\nNOTE: In addition to Prettier's "preserve" and "collapse", we also support "always".`,
260
- enum: [
261
- "preserve",
262
- "collapse",
263
- "always"
264
- ],
270
+ description: `How to wrap object literals when they could fit on one line or span multiple lines. (Default: "preserve")`,
271
+ enum: ["preserve", "collapse"],
265
272
  type: "string"
266
273
  },
267
274
  printWidth: {
@@ -462,8 +469,48 @@ const oxfmtConfigSchema = {
462
469
  description: `Path to Oxfmt configuration file.\nIf you provide an absolute path, Oxfmt will use it directly.\n If not provided, Oxfmt will search for configuration files starting from the current working directory upwards.\n\n- (Default: undefined)`,
463
470
  type: "string"
464
471
  },
472
+ disableNestedConfig: {
473
+ default: false,
474
+ description: `Disable nested config lookup and resolve from cwd/configPath only. (Default: false)`,
475
+ type: "boolean"
476
+ },
477
+ editorconfig: {
478
+ description: `Control .editorconfig reading.\n- true: enable with default behavior.\n- false: disable .editorconfig reading.\n- object: enable with advanced options (onlyCwd/cwd).\n\n- (Default: true)`,
479
+ oneOf: [{ type: "boolean" }, {
480
+ additionalProperties: false,
481
+ type: "object",
482
+ properties: {
483
+ cwd: {
484
+ description: `Override directory used as the start point for .editorconfig resolution.`,
485
+ type: "string"
486
+ },
487
+ onlyCwd: {
488
+ description: `When true, only read .editorconfig from cwd (no upward traversal).`,
489
+ type: "boolean"
490
+ }
491
+ }
492
+ }]
493
+ },
494
+ ignorePath: {
495
+ description: `Path(s) to ignore files used for CLI-style ignore resolution. Accepts a single path or multiple paths. (Default: undefined)`,
496
+ oneOf: [{ type: "string" }, {
497
+ type: "array",
498
+ items: { type: "string" }
499
+ }]
500
+ },
501
+ respectOxfmtDefaultIgnores: {
502
+ default: true,
503
+ description: `Whether to respect oxfmt default ignores (.gitignore, .prettierignore, lockfiles, default directories). (Default: true)`,
504
+ type: "boolean"
505
+ },
465
506
  useConfig: {
466
- description: `Whether to load Oxfmt configuration file.\n\n- (Default: true)`,
507
+ default: true,
508
+ description: `Whether to load Oxfmt configuration file. (Default: true)`,
509
+ type: "boolean"
510
+ },
511
+ withNodeModules: {
512
+ default: false,
513
+ description: `Whether to include files inside node_modules during ignore resolution. (Default: false)`,
467
514
  type: "boolean"
468
515
  }
469
516
  }
@@ -532,6 +579,7 @@ const rules = { oxfmt: {
532
579
  ...context.options?.[0],
533
580
  cwd: context.cwd
534
581
  });
582
+ if (formatResult.ignored) return;
535
583
  if (formatResult.errors?.length) for (const error of formatResult.errors) {
536
584
  const label = error.labels?.[0];
537
585
  if (label) {
@@ -586,4 +634,4 @@ const plugin = {
586
634
  rules
587
635
  };
588
636
  //#endregion
589
- export { configs, plugin as default, plugin, meta, parserPlain, recommended, recommendedWithoutParser, rules };
637
+ export { cliParity, configs, plugin as default, plugin, meta, parserPlain, recommended, recommendedWithoutParser, rules };
@@ -57,7 +57,7 @@ export type OxfmtOxfmt = []|[{
57
57
 
58
58
  jsxSingleQuote?: boolean
59
59
 
60
- objectWrap?: ("preserve" | "collapse" | "always")
60
+ objectWrap?: ("preserve" | "collapse")
61
61
 
62
62
  printWidth?: number
63
63
 
@@ -134,8 +134,23 @@ export type OxfmtOxfmt = []|[{
134
134
 
135
135
  configPath?: string
136
136
 
137
+ disableNestedConfig?: boolean
138
+
139
+ editorconfig?: (boolean | {
140
+
141
+ cwd?: string
142
+
143
+ onlyCwd?: boolean
144
+ })
145
+
146
+ ignorePath?: (string | string[])
147
+
148
+ respectOxfmtDefaultIgnores?: boolean
149
+
137
150
  useConfig?: boolean
138
151
 
152
+ withNodeModules?: boolean
153
+
139
154
  overrides?: {
140
155
 
141
156
  excludeFiles?: string[]
@@ -187,7 +202,7 @@ export type OxfmtOxfmt = []|[{
187
202
 
188
203
  jsxSingleQuote?: boolean
189
204
 
190
- objectWrap?: ("preserve" | "collapse" | "always")
205
+ objectWrap?: ("preserve" | "collapse")
191
206
 
192
207
  printWidth?: number
193
208
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eslint-plugin-oxfmt",
3
3
  "type": "module",
4
- "version": "0.5.0",
4
+ "version": "0.6.0",
5
5
  "description": "An ESLint plugin for formatting code with oxfmt.",
6
6
  "keywords": [
7
7
  "eslint",
@@ -48,17 +48,17 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "generate-differences": "^0.1.1",
51
- "load-oxfmt-config": "^0.4.1",
51
+ "load-oxfmt-config": "^0.7.1",
52
52
  "picomatch": "^4.0.4",
53
53
  "synckit": "^0.11.12"
54
54
  },
55
55
  "devDependencies": {
56
- "@ntnyq/eslint-config": "^6.1.0",
56
+ "@ntnyq/eslint-config": "^6.1.3",
57
57
  "@types/json-schema": "^7.0.15",
58
- "@types/node": "^25.6.0",
59
- "@typescript/native-preview": "^7.0.0-dev.20260429.1",
60
- "bumpp": "^11.0.1",
61
- "eslint": "^10.2.1",
58
+ "@types/node": "^25.6.2",
59
+ "@typescript/native-preview": "^7.0.0-dev.20260507.1",
60
+ "bumpp": "^11.1.0",
61
+ "eslint": "^10.3.0",
62
62
  "eslint-parser-plain": "^0.1.1",
63
63
  "eslint-typegen": "^2.3.1",
64
64
  "eslint-vitest-rule-tester": "^3.1.0",
@@ -66,14 +66,14 @@
66
66
  "jsonc-eslint-parser": "^3.1.0",
67
67
  "nano-staged": "^1.0.2",
68
68
  "npm-run-all2": "^8.0.4",
69
- "oxfmt": "^0.47.0",
69
+ "oxfmt": "^0.48.0",
70
70
  "show-invisibles": "^0.0.2",
71
71
  "tinyglobby": "^0.2.16",
72
- "tsdown": "^0.21.10",
72
+ "tsdown": "^0.22.0",
73
73
  "tsx": "^4.21.0",
74
74
  "typescript": "^6.0.3",
75
75
  "vitest": "^4.1.5",
76
- "eslint-plugin-oxfmt": "0.5.0"
76
+ "eslint-plugin-oxfmt": "0.6.0"
77
77
  },
78
78
  "engines": {
79
79
  "node": "^20.19.0 || >=22.12.0"
@@ -84,12 +84,13 @@
84
84
  },
85
85
  "scripts": {
86
86
  "build": "pnpm run update:rule-options && tsdown",
87
+ "check:schema": "tsx scripts/checkSchemaParity.ts",
87
88
  "dev": "tsdown --watch",
88
89
  "format": "oxfmt",
89
90
  "format:check": "oxfmt --check",
90
91
  "lint": "eslint",
91
92
  "release": "run-s release:check release:version",
92
- "release:check": "run-s format:check lint typecheck test build",
93
+ "release:check": "run-s check:schema format:check lint typecheck test build",
93
94
  "release:version": "bumpp",
94
95
  "test": "vitest",
95
96
  "typecheck": "tsgo --noEmit",
package/workers/oxfmt.mjs CHANGED
@@ -1,508 +1,359 @@
1
1
  // @ts-check
2
2
 
3
- import { dirname, isAbsolute, join, relative } from 'node:path'
4
- import { loadOxfmtConfig, resolveOxfmtrcPath } from 'load-oxfmt-config'
3
+ import { dirname, relative } from 'node:path'
4
+ import { isOxfmtIgnored, loadOxfmtConfigResult } from 'load-oxfmt-config'
5
5
  import { format } from 'oxfmt'
6
6
  import picomatch from 'picomatch'
7
7
  import { runAsWorker } from 'synckit'
8
8
 
9
9
  /**
10
- * @typedef {object} PluginOptions
11
- * @property {boolean} [useConfig] - Whether to use oxfmt configuration file
12
- * @property {string} cwd - Current working directory for resolving configuration
13
- * @property {string} [configPath] - Custom path to oxfmt configuration file
10
+ * @typedef {object} PluginOnlyOptions
11
+ * @property {string} [cwd] - Base working directory used for path resolution.
12
+ * @property {string} [configPath] - Explicit path to oxfmt config file.
13
+ * @property {string | string[]} [ignorePath] - One or more ignore file paths.
14
+ * @property {boolean} [withNodeModules] - Whether node_modules should be included in ignore checks.
15
+ * @property {boolean} [disableNestedConfig] - Disable per-file nested config lookup.
16
+ * @property {boolean} [useCache] - Reuse caches for config/ignore resolution.
17
+ * @property {boolean} [useConfig] - Whether config discovery/loading is enabled.
18
+ * @property {boolean} [respectOxfmtDefaultIgnores] - Whether CLI-like default ignores should apply.
19
+ * @property {import('load-oxfmt-config').LoadOxfmtConfigOptions['editorconfig']} [editorconfig] - EditorConfig loading strategy.
14
20
  */
15
21
 
16
22
  /**
17
- * @typedef {import('load-oxfmt-config').OxfmtConfigOverride} Override
23
+ * @typedef {PluginOnlyOptions & import('load-oxfmt-config').LoadOxfmtConfigOptions & import('oxfmt').FormatConfig} PluginOptions
18
24
  */
25
+
19
26
  /**
20
- * @typedef {import('load-oxfmt-config').OxfmtOptions & PluginOptions} Options
27
+ * @typedef {object} WorkerIgnoredResult
28
+ * @property {true} ignored - Indicates formatting was skipped due to ignore rules.
29
+ * @property {import('load-oxfmt-config').IsOxfmtIgnoredResult['reason']} [reason] - Ignore reason from resolution step.
30
+ * @property {string} code - Unchanged source code.
31
+ * @property {never} [errors] - Not present for ignored results.
21
32
  */
22
33
 
23
34
  /**
24
- * @typedef {object} WorkerOptions
25
- * @property {string} cwd Current working directory for resolving rule-relative inputs.
26
- * @property {string | undefined} configPath Custom path to an oxfmt configuration file.
27
- * @property {string[] | undefined} ignorePatterns Rule-level ignore patterns.
28
- * @property {Override[] | undefined} overrides Rule-level override entries.
29
- * @property {boolean} useConfig Whether config loading is enabled.
30
- * @property {import('oxfmt').FormatConfig} formatOptions Pure formatter options passed to oxfmt.
35
+ * @typedef {object} WorkerFormattedResult
36
+ * @property {false} [ignored] - False or undefined when formatting was attempted.
37
+ * @property {string} code - Formatted source code.
38
+ * @property {unknown[]} [errors] - Optional formatter errors.
31
39
  */
32
40
 
33
41
  /**
34
- * @typedef {object} ResolvedBaseOptions
35
- * @property {import('oxfmt').FormatConfig} formatOptions Resolved base formatter options.
36
- * @property {string[] | undefined} ignorePatterns Resolved ignore patterns from config loading.
37
- * @property {Override[] | undefined} overrides Resolved overrides from config loading.
38
- * @property {string} configDir Directory of the resolved config file, used as base for config-derived glob patterns.
42
+ * @typedef {WorkerIgnoredResult | WorkerFormattedResult} WorkerFormatResult
39
43
  */
40
44
 
41
- const MAX_CACHE_SIZE = 1000
42
-
43
45
  /**
44
- * Evict oldest entries when the cache exceeds MAX_CACHE_SIZE.
45
- * @param cache - The cache map to evict entries from
46
+ * @typedef {object} SplitOptionsResult
47
+ * @property {PluginOnlyOptions} pluginOptions - Plugin orchestration options.
48
+ * @property {import('oxfmt').FormatConfig} formatOptions - Pure oxfmt options.
46
49
  */
47
- function evictCache(
48
- /** @type {Map<string, unknown>} */
49
- cache,
50
- ) {
51
- if (cache.size <= MAX_CACHE_SIZE) {
52
- return
53
- }
54
- const keysToDelete = [...cache.keys()].slice(0, cache.size - MAX_CACHE_SIZE)
55
- for (const key of keysToDelete) {
56
- cache.delete(key)
57
- }
58
- }
59
50
 
60
51
  /**
61
- * JSON.stringify replacer that sorts object keys for stable serialization.
62
- * @param key - The key of the property being processed
63
- * @param value - The value of the property being processed
64
- * @returns - The value to be serialized, with object keys sorted if it's an object
52
+ * @typedef {import('load-oxfmt-config').OxfmtConfigOverride} OxfmtConfigOverride
65
53
  */
66
- function stableReplacer(
67
- /** @type {string} */
68
- key,
69
- /** @type {unknown} */
70
- value,
71
- ) {
72
- if (value && typeof value === 'object' && !Array.isArray(value)) {
73
- return Object.keys(value)
74
- .sort()
75
- .reduce((sorted, k) => {
76
- sorted[k] = /** @type {Record<string, unknown>} */ (value)[k]
77
- return sorted
78
- }, /** @type {Record<string, unknown>} */ ({}))
79
- }
80
- return value
81
- }
82
54
 
83
- /** @type {Map<string, Promise<ResolvedBaseOptions>>} */
84
- const resolvedBaseOptionsCache = new Map()
85
-
86
- /** @type {Map<string, import('oxfmt').FormatConfig>} */
87
- const mergedOptionsCache = new Map()
88
-
89
- /** @type {Map<string, ReturnType<typeof picomatch>>} */
90
- const picomatchCache = new Map()
55
+ const MAX_CACHE_SIZE = 200
56
+ const PLUGIN_ONLY_OPTIONS = new Set([
57
+ 'configPath',
58
+ 'cwd',
59
+ 'disableNestedConfig',
60
+ 'editorconfig',
61
+ 'ignorePath',
62
+ 'respectOxfmtDefaultIgnores',
63
+ 'useCache',
64
+ 'useConfig',
65
+ 'withNodeModules',
66
+ ])
67
+ /** @type {Map<string, import('picomatch').Matcher>} */
68
+ const matcherCache = new Map()
91
69
 
92
70
  /**
93
- * Apply overrides to the base options based on the filename
94
- * @param relativePath - The file path relative to the glob base directory
95
- * @param baseOptions - Base format options
96
- * @param [overrides] - Override configurations
97
- * @returns - Merged options
71
+ * Apply override entries to a base options object.
72
+ * @param {string} relativePath - Relative file path used for matching.
73
+ * @param {import('oxfmt').FormatConfig} baseOptions - Base format options.
74
+ * @param {OxfmtConfigOverride[] | undefined} overrides - Override entries.
75
+ * @returns {import('oxfmt').FormatConfig} Options after override merge.
98
76
  */
99
- function applyOverrides(
100
- /** @type {string} */
101
- relativePath,
102
- /** @type {import('oxfmt').FormatConfig} */
103
- baseOptions,
104
- /** @type {Override[] | undefined} */
105
- overrides,
106
- ) {
107
- if (!overrides || overrides.length === 0) {
77
+ function applyOverrides(relativePath, baseOptions, overrides) {
78
+ if (!overrides?.length) {
108
79
  return baseOptions
109
80
  }
110
81
 
111
- let mergedOptions = baseOptions
112
- let hasOverrides = false
113
-
114
- // Apply overrides in order (later overrides take precedence)
82
+ let merged = baseOptions
115
83
  for (const override of overrides) {
116
- const { excludeFiles, files, options: overrideOptions } = override
84
+ if (!override?.files?.length) {
85
+ continue
86
+ }
117
87
 
118
- // Check if file matches the files patterns
119
- const matches = getCachedMatcher(files)(relativePath)
88
+ const fileMatcher = getCachedMatcher(override.files)
89
+ const matches = !!fileMatcher && fileMatcher(relativePath)
120
90
 
121
- // Check if file is excluded
122
- const excluded =
123
- excludeFiles && excludeFiles.length > 0
124
- ? getCachedMatcher(excludeFiles)(relativePath)
125
- : false
91
+ const excludeMatcher = override.excludeFiles?.length
92
+ ? getCachedMatcher(override.excludeFiles)
93
+ : undefined
94
+ const excluded = excludeMatcher ? excludeMatcher(relativePath) : false
126
95
 
127
- if (matches && !excluded && overrideOptions) {
128
- mergedOptions = { ...mergedOptions, ...overrideOptions }
129
- hasOverrides = true
96
+ if (matches && !excluded && override.options) {
97
+ merged = {
98
+ ...merged,
99
+ ...override.options,
100
+ }
130
101
  }
131
102
  }
132
103
 
133
- return hasOverrides ? mergedOptions : baseOptions
104
+ return merged
134
105
  }
135
106
 
136
107
  /**
137
- * Get or create a cached picomatch matcher for the given patterns.
138
- * @param patterns - Glob patterns to match against file paths
139
- * @returns - Picomatch matcher function
108
+ * Format source text via oxfmt with CLI-parity ignore orchestration.
109
+ * @param {string} filename - File path passed from ESLint.
110
+ * @param {string} sourceText - Source text to format.
111
+ * @param {PluginOptions} [options] - Worker options.
112
+ * @returns {Promise<WorkerFormatResult>} Format result.
140
113
  */
141
- function getCachedMatcher(
142
- /** @type {string | string[]} */
143
- patterns,
144
- ) {
145
- const key = Array.isArray(patterns) ? patterns.join('\0') : patterns
146
- const cached = picomatchCache.get(key)
147
- if (cached) {
148
- return cached
149
- }
150
- const matcher = picomatch(patterns)
151
- picomatchCache.set(key, matcher)
152
- evictCache(picomatchCache)
153
- return matcher
154
- }
114
+ async function formatViaOxfmt(filename, sourceText, options = {}) {
115
+ const { formatOptions: inlineFormatOptions, pluginOptions } =
116
+ splitOptions(options)
117
+ validatePluginOptions(pluginOptions)
155
118
 
156
- /**
157
- * Resolve an effective config path for a file.
158
- * - Absolute paths are returned as-is.
159
- * - Relative paths are resolved from cwd.
160
- *
161
- * @param cwd - Current working directory from ESLint context.
162
- * @param [configPath] - Optional user-provided config path.
163
- * @returns Absolute config path when provided, otherwise undefined.
164
- */
165
- function getConfigPathForFile(
166
- /** @type {string} */
167
- cwd,
168
- /** @type {string | undefined} */
169
- configPath,
170
- ) {
171
- if (!configPath) {
172
- return undefined
173
- }
119
+ const cwd = pluginOptions.cwd
120
+ const useConfig = pluginOptions.useConfig !== false
174
121
 
175
- return isAbsolute(configPath) ? configPath : join(cwd, configPath)
176
- }
122
+ const ruleIgnorePatterns = isStringArray(inlineFormatOptions.ignorePatterns)
123
+ ? inlineFormatOptions.ignorePatterns
124
+ : undefined
177
125
 
178
- /**
179
- * Build cache key for merged options per file invocation.
180
- * The key only includes data that can affect the final formatter options.
181
- *
182
- * @param baseOptions - Resolved base options used for formatting.
183
- * @param relativePath - File path relative to the override base directory.
184
- * @param overrides - Rule-level override entries.
185
- * @returns Serialized cache key.
186
- */
187
- function getMergedOptionsCacheKey(
188
- /** @type {import('oxfmt').FormatConfig} */
189
- baseOptions,
190
- /** @type {string | undefined} */
191
- relativePath,
192
- /** @type {Override[] | undefined} */
193
- overrides,
194
- ) {
195
- const hasOverrides = !!(overrides && overrides.length > 0)
196
-
197
- return JSON.stringify(
198
- {
199
- baseOptions,
200
- overrides: hasOverrides ? overrides : undefined,
201
- relativePath: hasOverrides ? relativePath : undefined,
202
- },
203
- stableReplacer,
204
- )
205
- }
126
+ if (ruleIgnorePatterns?.length && cwd) {
127
+ const ruleRelativePath = getRelativePath(cwd, filename)
128
+ if (shouldIgnoreFile(ruleRelativePath, ruleIgnorePatterns)) {
129
+ return {
130
+ code: sourceText,
131
+ ignored: true,
132
+ }
133
+ }
134
+ }
206
135
 
207
- /**
208
- * Normalize a file path relative to the provided base directory.
209
- * @param baseDir - Base directory used for glob evaluation
210
- * @param filename - Absolute file path
211
- * @returns - Normalized relative path using forward slashes
212
- */
213
- function getRelativePath(
214
- /** @type {string} */
215
- baseDir,
216
- /** @type {string} */
217
- filename,
218
- ) {
219
- return relative(baseDir, filename).replace(/\\/g, '/')
220
- }
136
+ if (pluginOptions.respectOxfmtDefaultIgnores !== false && cwd) {
137
+ /** @type {import('load-oxfmt-config').IsOxfmtIgnoredOptions} */
138
+ const ignoredOptions = {
139
+ configPath: pluginOptions.configPath,
140
+ cwd,
141
+ disableNestedConfig: pluginOptions.disableNestedConfig,
142
+ filepath: filename,
143
+ ignorePath: pluginOptions.ignorePath,
144
+ includeConfigIgnorePatterns: useConfig,
145
+ loadConfigForIgnorePatterns: useConfig,
146
+ useCache: pluginOptions.useCache,
147
+ withNodeModules: pluginOptions.withNodeModules,
148
+ }
149
+ const ignored = await isOxfmtIgnored(ignoredOptions)
150
+
151
+ if (ignored.ignored) {
152
+ if (
153
+ ruleIgnorePatterns?.length &&
154
+ ignored.reason === 'config-ignore-patterns'
155
+ ) {
156
+ // Rule-level ignorePatterns should take precedence over config ignorePatterns.
157
+ } else {
158
+ return {
159
+ code: sourceText,
160
+ ignored: true,
161
+ reason: ignored.reason,
162
+ }
163
+ }
164
+ }
165
+ }
221
166
 
222
- /**
223
- * Build cache key for resolving base formatter options.
224
- * The key includes file directory and all resolution inputs.
225
- *
226
- * @param filename - Current file path.
227
- * @param cwd - Current working directory from ESLint context.
228
- * @param configPath - Optional user-provided config path.
229
- * @param useConfig - Whether config file loading is enabled.
230
- * @param formatOptions - Rule-level format options.
231
- * @returns Serialized cache key.
232
- */
233
- function getResolvedBaseOptionsCacheKey(
167
+ /** @type {OxfmtConfigOverride[] | undefined} */
168
+ let effectiveOverrides
234
169
  /** @type {string} */
235
- filename,
236
- /** @type {string} */
237
- cwd,
238
- /** @type {string | undefined} */
239
- configPath,
240
- /** @type {boolean} */
241
- useConfig,
170
+ let overrideBaseDir = cwd ?? dirname(filename)
242
171
  /** @type {import('oxfmt').FormatConfig} */
243
- formatOptions,
244
- ) {
245
- return JSON.stringify(
246
- {
247
- configPath: configPath || '',
248
- cwd,
249
- fileDir: dirname(filename),
250
- formatOptions,
251
- useConfig,
252
- },
253
- stableReplacer,
172
+ let finalOptions
173
+
174
+ if (useConfig) {
175
+ const configResolutionCwd = pluginOptions.configPath
176
+ ? cwd
177
+ : pluginOptions.disableNestedConfig
178
+ ? cwd
179
+ : dirname(filename)
180
+
181
+ const loaded = await loadOxfmtConfigResult({
182
+ configPath: pluginOptions.configPath,
183
+ cwd: configResolutionCwd,
184
+ editorconfig: pluginOptions.editorconfig,
185
+ useCache: pluginOptions.useCache,
186
+ })
187
+ const { overrides: configOverrides, ...loadedConfig } = loaded.config
188
+ const { overrides: ruleOverrides, ...inlineOptionsWithoutOverrides } =
189
+ inlineFormatOptions
190
+ effectiveOverrides = [
191
+ ...(configOverrides ?? []),
192
+ ...(Array.isArray(ruleOverrides) ? ruleOverrides : []),
193
+ ]
194
+ overrideBaseDir = loaded.dirname ?? overrideBaseDir
195
+
196
+ 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,
208
+ }
209
+ }
210
+
211
+ const overrideRelativePath = getRelativePath(overrideBaseDir, filename)
212
+ finalOptions = applyOverrides(
213
+ overrideRelativePath,
214
+ finalOptions,
215
+ effectiveOverrides,
254
216
  )
217
+
218
+ return format(filename, sourceText, finalOptions)
255
219
  }
256
220
 
257
221
  /**
258
- * Validate and normalize worker invocation options.
259
- * @param options - Raw worker options
260
- * @returns - Validated worker options
222
+ * Get or create a cached picomatch matcher.
223
+ * @param {string[]} patterns - Glob patterns.
224
+ * @returns {import('picomatch').Matcher} Compiled matcher.
261
225
  */
262
- function getWorkerOptions(
263
- /** @type {Options | undefined} */
264
- options,
265
- ) {
266
- if (!options || typeof options !== 'object') {
267
- throw new TypeError('oxfmt worker expected an options object.')
226
+ function getCachedMatcher(patterns) {
227
+ const key = patterns.join('\0')
228
+ const cached = matcherCache.get(key)
229
+ if (cached) {
230
+ return cached
268
231
  }
269
232
 
270
- const {
271
- configPath,
272
- cwd,
273
- ignorePatterns,
274
- overrides,
275
- useConfig = true,
276
- ...formatOptions
277
- } = options
278
-
279
- if (typeof cwd !== 'string' || cwd.length === 0) {
280
- throw new TypeError('oxfmt worker requires a non-empty "cwd" option.')
281
- }
282
- if (configPath != null && typeof configPath !== 'string') {
283
- throw new TypeError(
284
- 'oxfmt worker requires "configPath" to be a string when provided.',
285
- )
286
- }
287
- if (ignorePatterns != null && !isStringArray(ignorePatterns)) {
288
- throw new TypeError(
289
- 'oxfmt worker requires "ignorePatterns" to be an array of strings when provided.',
290
- )
291
- }
292
- if (overrides != null && !Array.isArray(overrides)) {
293
- throw new TypeError(
294
- 'oxfmt worker requires "overrides" to be an array when provided.',
295
- )
296
- }
297
- if (typeof useConfig !== 'boolean') {
298
- throw new TypeError(
299
- 'oxfmt worker requires "useConfig" to be a boolean when provided.',
300
- )
301
- }
233
+ const matcher = picomatch(patterns)
234
+ setCacheEntry(matcherCache, key, matcher)
235
+ return matcher
236
+ }
302
237
 
303
- return {
304
- configPath,
305
- cwd,
306
- formatOptions,
307
- ignorePatterns,
308
- overrides,
309
- useConfig,
310
- }
238
+ /**
239
+ * Normalize a file path relative to the provided base directory.
240
+ * @param {string} baseDir - Base directory used for glob evaluation.
241
+ * @param {string} filename - Absolute file path.
242
+ * @returns {string} Relative path using forward slashes.
243
+ */
244
+ function getRelativePath(baseDir, filename) {
245
+ return relative(baseDir, filename).replace(/\\/g, '/')
311
246
  }
312
247
 
313
248
  /**
314
249
  * Check whether a value is an array of strings.
315
- * @param value - Value to validate
316
- * @returns - Whether the value is a string array
250
+ * @param {unknown} value - Value to validate.
251
+ * @returns {value is string[]} Whether the value is a string array.
317
252
  */
318
- function isStringArray(
319
- /** @type {unknown} */
320
- value,
321
- ) {
253
+ function isStringArray(value) {
322
254
  return Array.isArray(value) && value.every(item => typeof item === 'string')
323
255
  }
324
256
 
325
257
  /**
326
- * Resolve base formatter options for a file and cache the async result.
327
- *
328
- * @param filename - Current file path.
329
- * @param cwd - Current working directory from ESLint context.
330
- * @param configPath - Optional user-provided config path.
331
- * @param useConfig - Whether config file loading is enabled.
332
- * @param formatOptions - Rule-level format options.
333
- * @returns Base options after config loading and inline option merge.
258
+ * Store a value in a FIFO cache map with a bounded size.
259
+ * @template T Cache value type.
260
+ * @param {Map<string, T>} cache - Cache map.
261
+ * @param {string} key - Cache key.
262
+ * @param {T} value - Value to store.
334
263
  */
335
- async function resolveBaseOptions(
336
- /** @type {string} */
337
- filename,
338
- /** @type {string} */
339
- cwd,
340
- /** @type {string | undefined} */
341
- configPath,
342
- /** @type {boolean} */
343
- useConfig,
344
- /** @type {import('oxfmt').FormatConfig} */
345
- formatOptions,
346
- ) {
347
- const cacheKey = getResolvedBaseOptionsCacheKey(
348
- filename,
349
- cwd,
350
- configPath,
351
- useConfig,
352
- formatOptions,
353
- )
354
-
355
- const cachedTask = resolvedBaseOptionsCache.get(cacheKey)
356
- if (cachedTask) {
357
- return cachedTask
358
- }
359
-
360
- /** @type {Promise<ResolvedBaseOptions>} */
361
- const task = (async () => {
362
- const resolveFromDir = dirname(filename)
363
-
364
- if (!useConfig) {
365
- return {
366
- configDir: cwd,
367
- ignorePatterns: undefined,
368
- overrides: undefined,
369
- formatOptions: {
370
- ...formatOptions,
371
- },
372
- }
373
- }
374
-
375
- const resolvedConfigPath = getConfigPathForFile(cwd, configPath)
376
- const resolvedPath = await resolveOxfmtrcPath(
377
- resolveFromDir,
378
- resolvedConfigPath,
379
- )
380
- const configDir = resolvedPath ? dirname(resolvedPath) : cwd
381
- const configOptions = await loadOxfmtConfig({
382
- configPath: resolvedConfigPath,
383
- cwd: resolveFromDir,
384
- })
385
- const { ignorePatterns, overrides, ...loadedFormatOptions } =
386
- configOptions ?? {}
387
-
388
- return {
389
- configDir,
390
- ignorePatterns,
391
- overrides,
392
- formatOptions: {
393
- ...loadedFormatOptions,
394
- ...formatOptions,
395
- },
264
+ function setCacheEntry(cache, key, value) {
265
+ cache.set(key, value)
266
+ if (cache.size > MAX_CACHE_SIZE) {
267
+ const oldestKey = cache.keys().next().value
268
+ if (oldestKey != null) {
269
+ cache.delete(oldestKey)
396
270
  }
397
- })()
398
-
399
- resolvedBaseOptionsCache.set(cacheKey, task)
400
- evictCache(resolvedBaseOptionsCache)
401
-
402
- try {
403
- return await task
404
- } catch (err) {
405
- resolvedBaseOptionsCache.delete(cacheKey)
406
- throw err
407
271
  }
408
272
  }
409
273
 
410
274
  /**
411
- * Check if a file should be ignored based on ignorePatterns
412
- * @param relativePath - The file path relative to the glob base directory
413
- * @param [ignorePatterns] - Ignore patterns
414
- * @returns - Whether the file should be ignored
275
+ * Check if a file should be ignored by provided glob patterns.
276
+ * @param {string} relativePath - Relative path against pattern base.
277
+ * @param {string[] | undefined} ignorePatterns - Glob patterns.
278
+ * @returns {boolean} Whether file is ignored.
415
279
  */
416
- function shouldIgnoreFile(
417
- /** @type {string} */
418
- relativePath,
419
- /** @type {string[] | undefined} */ ignorePatterns,
420
- ) {
421
- if (!ignorePatterns || ignorePatterns.length === 0) {
280
+ function shouldIgnoreFile(relativePath, ignorePatterns) {
281
+ if (!ignorePatterns?.length) {
422
282
  return false
423
283
  }
424
284
 
425
- // Check if file matches any ignore pattern
426
- return getCachedMatcher(ignorePatterns)(relativePath)
285
+ const matcher = getCachedMatcher(ignorePatterns)
286
+ return !!matcher && matcher(relativePath)
427
287
  }
428
288
 
429
- runAsWorker(
430
- async (
431
- /**
432
- * @type {string} filename
433
- */
434
- filename,
435
- /**
436
- * @type {string} source text
437
- */
438
- sourceText,
439
- /**
440
- * @type {Options} format options
441
- */
442
- options,
443
- ) => {
444
- const {
445
- configPath,
446
- cwd,
447
- formatOptions,
448
- ignorePatterns,
449
- overrides,
450
- useConfig,
451
- } = getWorkerOptions(options)
452
-
453
- const {
454
- configDir,
455
- formatOptions: baseFormatOptions,
456
- ignorePatterns: baseIgnorePatterns,
457
- overrides: baseOverrides,
458
- } = await resolveBaseOptions(
459
- filename,
460
- cwd,
461
- configPath,
462
- useConfig,
463
- formatOptions,
464
- )
465
- const effectiveIgnorePatterns = ignorePatterns ?? baseIgnorePatterns
466
- const ignoreBase = ignorePatterns == null ? configDir : cwd
467
- const ignoreRelativePath = effectiveIgnorePatterns?.length
468
- ? getRelativePath(ignoreBase, filename)
469
- : undefined
470
-
471
- if (
472
- ignoreRelativePath &&
473
- shouldIgnoreFile(ignoreRelativePath, effectiveIgnorePatterns)
474
- ) {
475
- return { code: sourceText }
289
+ /**
290
+ * Split worker options into plugin orchestration options and pure format options.
291
+ * @param {PluginOptions} [options] - Raw worker options.
292
+ * @returns {SplitOptionsResult} Split option buckets.
293
+ */
294
+ function splitOptions(options = {}) {
295
+ /** @type {Record<string, unknown>} */
296
+ const pluginOptions = {}
297
+ /** @type {Record<string, unknown>} */
298
+ const formatOptions = {}
299
+
300
+ for (const [key, value] of Object.entries(options)) {
301
+ if (PLUGIN_ONLY_OPTIONS.has(key)) {
302
+ pluginOptions[key] = value
303
+ } else {
304
+ formatOptions[key] = value
476
305
  }
306
+ }
477
307
 
478
- const effectiveOverrides = useConfig ? baseOverrides : overrides
479
- const overrideBase = useConfig ? configDir : cwd
480
- const overrideRelativePath = effectiveOverrides?.length
481
- ? getRelativePath(overrideBase, filename)
482
- : undefined
483
- const mergedOptionsCacheKey = getMergedOptionsCacheKey(
484
- baseFormatOptions,
485
- overrideRelativePath,
486
- effectiveOverrides,
487
- )
308
+ return {
309
+ formatOptions: /** @type {import('oxfmt').FormatConfig} */ (formatOptions),
310
+ pluginOptions: /** @type {PluginOnlyOptions} */ (pluginOptions),
311
+ }
312
+ }
488
313
 
489
- const cachedMergedOptions = mergedOptionsCache.get(mergedOptionsCacheKey)
490
- if (cachedMergedOptions) {
491
- return format(filename, sourceText, cachedMergedOptions)
314
+ /**
315
+ * Validate plugin-only options before dispatching to helper libraries.
316
+ * @param {PluginOnlyOptions} pluginOptions - Plugin-only options.
317
+ */
318
+ function validatePluginOptions(pluginOptions) {
319
+ /** @type {('cwd' | 'configPath')[]} */
320
+ const stringKeys = ['configPath', 'cwd']
321
+ /** @type {('disableNestedConfig' | 'respectOxfmtDefaultIgnores' | 'useConfig' | 'useCache' | 'withNodeModules')[]} */
322
+ const booleanKeys = [
323
+ 'disableNestedConfig',
324
+ 'respectOxfmtDefaultIgnores',
325
+ 'useCache',
326
+ 'useConfig',
327
+ 'withNodeModules',
328
+ ]
329
+
330
+ for (const key of stringKeys) {
331
+ const value = pluginOptions[key]
332
+ if (value != null && typeof value !== 'string') {
333
+ throw new TypeError(
334
+ `oxfmt worker requires "${key}" to be a string when provided.`,
335
+ )
492
336
  }
337
+ }
493
338
 
494
- let mergedOptions = baseFormatOptions
495
- if (overrideRelativePath && effectiveOverrides?.length) {
496
- mergedOptions = applyOverrides(
497
- overrideRelativePath,
498
- mergedOptions,
499
- effectiveOverrides,
339
+ for (const key of booleanKeys) {
340
+ const value = pluginOptions[key]
341
+ if (value != null && typeof value !== 'boolean') {
342
+ throw new TypeError(
343
+ `oxfmt worker requires "${key}" to be a boolean when provided.`,
500
344
  )
501
345
  }
346
+ }
502
347
 
503
- mergedOptionsCache.set(mergedOptionsCacheKey, mergedOptions)
504
- evictCache(mergedOptionsCache)
348
+ if (
349
+ pluginOptions.ignorePath != null &&
350
+ typeof pluginOptions.ignorePath !== 'string' &&
351
+ !isStringArray(pluginOptions.ignorePath)
352
+ ) {
353
+ throw new TypeError(
354
+ 'oxfmt worker requires "ignorePath" to be a string or string array when provided.',
355
+ )
356
+ }
357
+ }
505
358
 
506
- return format(filename, sourceText, mergedOptions)
507
- },
508
- )
359
+ runAsWorker(formatViaOxfmt)