@isentinel/eslint-config 6.2.0 → 6.4.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/dist/lint-cli.mjs CHANGED
@@ -42,7 +42,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
42
42
  }) : target, mod));
43
43
  //#endregion
44
44
  //#region package.json
45
- var version = "6.2.0";
45
+ var version = "6.4.0";
46
46
  //#endregion
47
47
  //#region src/lint-cli/lib/cli/types.ts
48
48
  /**
@@ -1512,6 +1512,65 @@ function quotePosix(token) {
1512
1512
  return `'${token.replaceAll("'", "'\\''")}'`;
1513
1513
  }
1514
1514
  //#endregion
1515
+ //#region src/hybrid-status.ts
1516
+ /**
1517
+ * Set by `isentinel-lint` in its ESLint children. Only those evaluations load
1518
+ * the config the lint run uses, so only they may write the status: tests,
1519
+ * scripts and generators that build other configs in the project root would
1520
+ * otherwise overwrite it.
1521
+ */
1522
+ const HYBRID_STATUS_ENV = "ISENTINEL_WRITE_HYBRID_STATUS";
1523
+ /**
1524
+ * Absolute path to the hybrid-status file for a project root.
1525
+ *
1526
+ * @param cwd - The project root.
1527
+ * @returns The absolute status-file path.
1528
+ */
1529
+ function hybridStatusPath(cwd) {
1530
+ return statePath(cwd, "hybrid-status");
1531
+ }
1532
+ /**
1533
+ * Read the persisted hybrid status, or `undefined` when the file is missing,
1534
+ * unreadable or malformed (the CLI then treats the status as unknown).
1535
+ *
1536
+ * @param cwd - The project root.
1537
+ * @returns The parsed status, or `undefined`.
1538
+ */
1539
+ function readHybridStatus(cwd) {
1540
+ const stored = readState(hybridStatusPath(cwd));
1541
+ return typeof stored?.oxlint === "boolean" ? stored : void 0;
1542
+ }
1543
+ /**
1544
+ * Passively record whether the resolved ESLint config runs in hybrid mode.
1545
+ * Called from the factory on every config evaluation in a lint child, so an
1546
+ * unchanged status is touched rather than rewritten: the file must not churn
1547
+ * as both lint passes re-evaluate the config, yet its mtime must stay ahead of
1548
+ * the config's or the CLI re-runs its ~3s probe every lint. Every failure is
1549
+ * swallowed — config evaluation must never throw for this, and the CLI treats
1550
+ * a missing or stale file as "unknown" and re-probes.
1551
+ *
1552
+ * Skipped entirely when `node_modules` is absent (nothing installed, so no
1553
+ * cache home and no CLI to read it).
1554
+ *
1555
+ * @param cwd - The project root.
1556
+ * @param oxlint - Whether the config enabled hybrid mode.
1557
+ */
1558
+ function writeHybridStatus(cwd, oxlint) {
1559
+ let installed;
1560
+ try {
1561
+ installed = fs.existsSync(path.resolve(cwd, "node_modules"));
1562
+ } catch {
1563
+ return;
1564
+ }
1565
+ if (!installed) return;
1566
+ const filePath = hybridStatusPath(cwd);
1567
+ if (readHybridStatus(cwd)?.oxlint === oxlint) {
1568
+ touchState(filePath);
1569
+ return;
1570
+ }
1571
+ writeState(filePath, { oxlint });
1572
+ }
1573
+ //#endregion
1515
1574
  //#region src/lint-cli/lib/plan/command.ts
1516
1575
  /**
1517
1576
  * Compose the oxlint child command.
@@ -1578,7 +1637,10 @@ function composeEslintCommand(options, context) {
1578
1637
  if (options.agents) args.push("--format", context.agentsFormatterPath);
1579
1638
  if (context.fix) args.push("--fix");
1580
1639
  args.push(...options.eslintArgs, ...pathArguments(context.paths));
1581
- const environment = { ESLINT_TYPE_AWARE: context.typeAwareEnv };
1640
+ const environment = {
1641
+ ESLINT_TYPE_AWARE: context.typeAwareEnv,
1642
+ [HYBRID_STATUS_ENV]: "1"
1643
+ };
1582
1644
  if (context.quietWarnings === true) environment[QUIET_WARNINGS_ENV] = "1";
1583
1645
  if (context.heap?.nodeOptions !== void 0) environment["NODE_OPTIONS"] = context.heap.nodeOptions;
1584
1646
  return {
@@ -4965,58 +5027,6 @@ function queryIgnoredFiles(cwd, targets) {
4965
5027
  }
4966
5028
  }
4967
5029
  //#endregion
4968
- //#region src/hybrid-status.ts
4969
- /**
4970
- * Absolute path to the hybrid-status file for a project root.
4971
- *
4972
- * @param cwd - The project root.
4973
- * @returns The absolute status-file path.
4974
- */
4975
- function hybridStatusPath(cwd) {
4976
- return statePath(cwd, "hybrid-status");
4977
- }
4978
- /**
4979
- * Read the persisted hybrid status, or `undefined` when the file is missing,
4980
- * unreadable or malformed (the CLI then treats the status as unknown).
4981
- *
4982
- * @param cwd - The project root.
4983
- * @returns The parsed status, or `undefined`.
4984
- */
4985
- function readHybridStatus(cwd) {
4986
- const stored = readState(hybridStatusPath(cwd));
4987
- return typeof stored?.oxlint === "boolean" ? stored : void 0;
4988
- }
4989
- /**
4990
- * Passively record whether the resolved ESLint config runs in hybrid mode.
4991
- * Called from the factory on every config evaluation, so an unchanged status is
4992
- * touched rather than rewritten: the file must not churn as editors and both
4993
- * lint passes re-evaluate the config, yet its mtime must stay ahead of the
4994
- * config's or the CLI re-runs its ~3s probe every lint. Every failure is
4995
- * swallowed — config evaluation must never throw for this, and the CLI treats a
4996
- * missing or stale file as "unknown" and re-probes.
4997
- *
4998
- * Skipped entirely when `node_modules` is absent (nothing installed, so no
4999
- * cache home and no CLI to read it).
5000
- *
5001
- * @param cwd - The project root.
5002
- * @param oxlint - Whether the config enabled hybrid mode.
5003
- */
5004
- function writeHybridStatus(cwd, oxlint) {
5005
- let installed;
5006
- try {
5007
- installed = fs.existsSync(path.resolve(cwd, "node_modules"));
5008
- } catch {
5009
- return;
5010
- }
5011
- if (!installed) return;
5012
- const filePath = hybridStatusPath(cwd);
5013
- if (readHybridStatus(cwd)?.oxlint === oxlint) {
5014
- touchState(filePath);
5015
- return;
5016
- }
5017
- writeState(filePath, { oxlint });
5018
- }
5019
- //#endregion
5020
5030
  //#region src/lint-cli/lib/hybrid/probe.ts
5021
5031
  /**
5022
5032
  * Read the hybrid marker from `eslint --print-config` stdout. Config evaluation
package/dist/oxfmt.mjs CHANGED
@@ -315,15 +315,15 @@ function parseJsonLike(contents) {
315
315
  return;
316
316
  }
317
317
  }
318
- function isRecord(value) {
318
+ function isRecord$1(value) {
319
319
  return typeof value === "object" && value !== null && !Array.isArray(value);
320
320
  }
321
321
  function readPackageJsonPrettierKey(filePath) {
322
322
  const contents = readFileOrUndefined(filePath);
323
323
  if (contents === void 0) return;
324
324
  const parsed = parseJsonLike(contents);
325
- const prettierKey = isRecord(parsed) ? parsed["prettier"] : void 0;
326
- return isRecord(prettierKey) ? prettierKey : void 0;
325
+ const prettierKey = isRecord$1(parsed) ? parsed["prettier"] : void 0;
326
+ return isRecord$1(prettierKey) ? prettierKey : void 0;
327
327
  }
328
328
  /**
329
329
  * Load a JS Prettier config. Node's `require` handles ESM entry points
@@ -336,8 +336,8 @@ function readPackageJsonPrettierKey(filePath) {
336
336
  function requireConfigModule(filePath) {
337
337
  try {
338
338
  const loaded = createRequire(filePath)(filePath);
339
- const value = isRecord(loaded) && "default" in loaded ? loaded["default"] : loaded;
340
- return isRecord(value) ? value : void 0;
339
+ const value = isRecord$1(loaded) && "default" in loaded ? loaded["default"] : loaded;
340
+ return isRecord$1(value) ? value : void 0;
341
341
  } catch {
342
342
  return;
343
343
  }
@@ -355,7 +355,7 @@ function readConfigFile(filePath) {
355
355
  const contents = readFileOrUndefined(filePath);
356
356
  if (contents === void 0) return;
357
357
  const parsed = parseJsonLike(contents) ?? parseYamlLike(contents);
358
- return isRecord(parsed) ? parsed : void 0;
358
+ return isRecord$1(parsed) ? parsed : void 0;
359
359
  }
360
360
  function ancestorDirectories(cwd) {
361
361
  const directories = [];
@@ -481,6 +481,17 @@ function resolveEditorConfigOptions(cwd) {
481
481
  return editorConfigToPrettier(properties);
482
482
  }
483
483
  /**
484
+ * Read a numeric `.editorconfig` value. An absent or empty value sets nothing.
485
+ *
486
+ * @param value - The raw property value.
487
+ * @returns The finite number, or `undefined`.
488
+ */
489
+ function editorConfigNumber(value) {
490
+ if (value === void 0 || value === "") return;
491
+ const parsed = Number(value);
492
+ return Number.isFinite(parsed) ? parsed : void 0;
493
+ }
494
+ /**
484
495
  * Map one `.editorconfig` section to oxfmt options, as the rule's config
485
496
  * loader does.
486
497
  *
@@ -495,17 +506,34 @@ function editorConfigToOxfmt(properties) {
495
506
  if (indentStyle === "tab" || indentStyle === "space") options["useTabs"] = indentStyle === "tab";
496
507
  const quoteType = properties["quote_type"];
497
508
  if (quoteType === "single" || quoteType === "double") options["singleQuote"] = quoteType === "single";
498
- const indentSize = Number(properties["indent_size"]);
499
- const tabWidth = Number(properties["tab_width"]);
500
- if (options["useTabs"] === false && properties["indent_size"] && Number.isFinite(indentSize)) options["tabWidth"] = indentSize;
501
- else if (properties["tab_width"] && Number.isFinite(tabWidth)) options["tabWidth"] = tabWidth;
502
- const printWidth = Number(properties["max_line_length"]);
503
- if (properties["max_line_length"] && Number.isFinite(printWidth)) options["printWidth"] = printWidth;
509
+ const indentSize = editorConfigNumber(properties["indent_size"]);
510
+ const tabWidth = editorConfigNumber(properties["tab_width"]);
511
+ if (indentSize !== void 0 && options["useTabs"] === false) options["tabWidth"] = indentSize;
512
+ else if (tabWidth !== void 0) options["tabWidth"] = tabWidth;
513
+ const printWidth = editorConfigNumber(properties["max_line_length"]);
514
+ if (printWidth !== void 0) options["printWidth"] = printWidth;
504
515
  const finalNewline = properties["insert_final_newline"];
505
516
  if (finalNewline === "true" || finalNewline === "false") options["insertFinalNewline"] = finalNewline === "true";
506
517
  return options;
507
518
  }
508
519
  //#endregion
520
+ //#region src/guards.ts
521
+ /**
522
+ * Internal runtime type guards. Prefer these over `as` assertions so values
523
+ * crossing untyped boundaries (`JSON.parse`, dynamic `import`, plugin objects)
524
+ * are validated at runtime rather than asserted away.
525
+ */
526
+ /**
527
+ * Whether a value is a non-null, non-array object usable as a string-keyed
528
+ * record. Narrows `unknown` without an assertion.
529
+ *
530
+ * @param value - The value to test.
531
+ * @returns Whether the value is a plain object.
532
+ */
533
+ function isRecord(value) {
534
+ return typeof value === "object" && value !== null && !Array.isArray(value);
535
+ }
536
+ //#endregion
509
537
  //#region src/rules/oxfmt.ts
510
538
  const UNSUPPORTED_PRETTIER_KEYS = /* @__PURE__ */ new Set([
511
539
  "experimentalOperatorPosition",
@@ -572,12 +600,12 @@ function migratePrettierOptions(prettierOptions) {
572
600
  function migratePrettierOverrides(overrides) {
573
601
  if (!Array.isArray(overrides)) return [];
574
602
  return overrides.flatMap((override) => {
575
- if (typeof override !== "object" || override === null) return [];
603
+ if (!isRecord(override)) return [];
576
604
  const { excludeFiles, files, options } = override;
577
605
  return [{
578
606
  ...excludeFiles === void 0 ? {} : { excludeFiles: stringsOf(excludeFiles) },
579
607
  files: stringsOf(files),
580
- options: typeof options === "object" && options !== null ? migratePrettierOptions(options) : {}
608
+ options: isRecord(options) ? migratePrettierOptions(options) : {}
581
609
  }];
582
610
  });
583
611
  }
@@ -592,17 +620,11 @@ function migratePrettierOverrides(overrides) {
592
620
  * @returns The overrides with anchored globs.
593
621
  */
594
622
  function anchorOverrideGlobs(overrides) {
595
- function anchor(glob) {
596
- const negated = glob.startsWith("!");
597
- const body = negated ? glob.slice(1) : glob;
598
- const anchored = body.includes("/") ? body : `./${body}`;
599
- return negated ? `!${anchored}` : anchored;
600
- }
601
623
  return overrides.map((override) => {
602
624
  return {
603
625
  ...override,
604
- ...override.excludeFiles ? { excludeFiles: override.excludeFiles.map(anchor) } : {},
605
- files: override.files.map(anchor)
626
+ ...override.excludeFiles ? { excludeFiles: override.excludeFiles.map(anchorGlob) } : {},
627
+ files: override.files.map(anchorGlob)
606
628
  };
607
629
  });
608
630
  }
@@ -627,11 +649,16 @@ function buildOxfmtOptions({ oxfmtConfigOptions = {}, oxfmtOptions, prettierOpti
627
649
  * ESLint config and the standalone oxfmt config so both format Markdown the
628
650
  * same way.
629
651
  *
652
+ * Oxfmt would format code blocks with these options too, at the prose width.
653
+ * It leaves them alone instead, and ESLint formats each block with the options
654
+ * of its own language (see `isentinel/oxfmt/markdown-code/*`).
655
+ *
630
656
  * @param jsdocPrintWidth - Width for Markdown prose; 80 when unset.
631
657
  * @returns Options applied on top of the base for Markdown files.
632
658
  */
633
659
  function markdownOxfmtOptions(jsdocPrintWidth) {
634
660
  return {
661
+ embeddedLanguageFormatting: "off",
635
662
  printWidth: Number(jsdocPrintWidth) || 80,
636
663
  proseWrap: "always",
637
664
  tabWidth: 2
@@ -651,6 +678,18 @@ const YAML_OXFMT_OPTIONS = {
651
678
  tabWidth: 2,
652
679
  useTabs: false
653
680
  };
681
+ /**
682
+ * Anchor one slash-less glob to the config directory, keeping its negation.
683
+ *
684
+ * @param glob - An override glob.
685
+ * @returns The anchored glob.
686
+ */
687
+ function anchorGlob(glob) {
688
+ const negated = glob.startsWith("!");
689
+ const body = negated ? glob.slice(1) : glob;
690
+ const anchored = body.includes("/") ? body : `./${body}`;
691
+ return negated ? `!${anchored}` : anchored;
692
+ }
654
693
  //#endregion
655
694
  //#region src/oxfmt/index.ts
656
695
  /**