react-doctor 0.9.4 → 0.9.5-dev.2b8bc5c

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/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { FrameworkToken } from "oxlint-plugin-react-doctor/core";
2
+ import fs from "node:fs";
2
3
 
3
4
  //#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Types.d.ts
4
5
  /**
@@ -9833,13 +9834,13 @@ interface InspectResult {
9833
9834
  * Number of files the scan reported. Distinct from
9834
9835
  * `project.sourceFileCount` in diff / staged mode (where only changed
9835
9836
  * files are scanned). Optional so non-orchestrator constructors keep
9836
- * working; the multi-project summary falls back to
9837
+ * working; the multi-project report falls back to
9837
9838
  * `project.sourceFileCount` when absent.
9838
9839
  */
9839
9840
  scannedFileCount?: number;
9840
9841
  /**
9841
9842
  * Absolute paths of every file the scan considered. Lets the
9842
- * multi-project summary count UNIQUE files across projects instead of
9843
+ * multi-project report count UNIQUE files across projects instead of
9843
9844
  * summing per-project counts, which double-counts shared files when one
9844
9845
  * workspace package's tree is nested inside another's.
9845
9846
  */
@@ -9853,8 +9854,8 @@ interface InspectResult {
9853
9854
  /**
9854
9855
  * Wall-clock duration of the scan phase, in milliseconds. Distinct
9855
9856
  * from `elapsedMilliseconds` (which spans the full `inspect()` call
9856
- * including score fetch + rendering). Used by the multi-project
9857
- * summary to report combined scan time.
9857
+ * including score fetch + reporting). Used by the multi-project
9858
+ * report to show combined scan time.
9858
9859
  */
9859
9860
  scanElapsedMilliseconds?: number;
9860
9861
  /**
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e18e3c42-e18e-5087-9b71-eb1bcb203d6b")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="5415308a-0150-547d-9270-806382225dcf")}catch(e){}}();
3
3
  import { i as __toESM$1, r as __require, t as __commonJSMin$1 } from "./chunk-ChKA3wi6.js";
4
4
  import { createRequire } from "node:module";
5
5
  import { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, FRAMEWORK_TOKENS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, REACT_DOCTOR_RULE_REGISTRY, REACT_DOCTOR_SCAN_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor/core";
@@ -17,8 +17,8 @@ import * as NodeUrl from "node:url";
17
17
  import { fileURLToPath } from "node:url";
18
18
  import * as OS from "node:os";
19
19
  import os from "node:os";
20
- import { parseJSON5 } from "confbox";
21
20
  import { createJiti } from "jiti";
21
+ import { parseJSON5 } from "confbox";
22
22
  import { ANALYZED_MANIFEST_FILENAMES, DEFAULT_EXTENSIONS } from "deslop-js/analyzed-inputs";
23
23
  import { convertToTSX } from "@astrojs/compiler/sync";
24
24
  import { gzipSync } from "node:zlib";
@@ -62285,7 +62285,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
62285
62285
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
62286
62286
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
62287
62287
  const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
62288
- const CORE_PACKAGE_VERSION = "0.9.4";
62288
+ const CORE_PACKAGE_VERSION = "0.9.5";
62289
62289
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
62290
62290
  const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
62291
62291
  const SUPPLY_CHAIN_PLUGIN = "socket";
@@ -62298,17 +62298,20 @@ const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
62298
62298
  const isErrnoException = (error) => error instanceof Error && "code" in error;
62299
62299
  const IGNORABLE_READDIR_ERROR_CODES = new Set([
62300
62300
  "EACCES",
62301
+ "EBUSY",
62301
62302
  "EPERM",
62302
62303
  "ENOENT",
62303
62304
  "ENOTDIR",
62304
62305
  "EINVAL",
62305
62306
  "ELOOP",
62306
- "ENAMETOOLONG"
62307
+ "ENAMETOOLONG",
62308
+ "ETIMEDOUT",
62309
+ "UNKNOWN"
62307
62310
  ]);
62308
62311
  const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
62309
62312
  const readDirectoryEntries = (directoryPath) => {
62310
62313
  try {
62311
- return NFS.readdirSync(directoryPath, { withFileTypes: true });
62314
+ return fs.readdirSync(directoryPath, { withFileTypes: true });
62312
62315
  } catch (error) {
62313
62316
  if (isIgnorableReaddirError(error)) return [];
62314
62317
  throw error;
@@ -62316,14 +62319,14 @@ const readDirectoryEntries = (directoryPath) => {
62316
62319
  };
62317
62320
  const isFile = (filePath) => {
62318
62321
  try {
62319
- return NFS.statSync(filePath).isFile();
62322
+ return fs.statSync(filePath).isFile();
62320
62323
  } catch {
62321
62324
  return false;
62322
62325
  }
62323
62326
  };
62324
62327
  const isDirectory = (directoryPath) => {
62325
62328
  try {
62326
- return NFS.statSync(directoryPath).isDirectory();
62329
+ return fs.statSync(directoryPath).isDirectory();
62327
62330
  } catch {
62328
62331
  return false;
62329
62332
  }
@@ -62753,7 +62756,12 @@ const mergeCompilerOptions = (inherited, current) => {
62753
62756
  };
62754
62757
  };
62755
62758
  const readResolvedCompilerOptions = (tsConfigPath, extendsDepth, visitedPaths) => {
62756
- const realPath = NFS.realpathSync.native(tsConfigPath);
62759
+ let realPath;
62760
+ try {
62761
+ realPath = NFS.realpathSync.native(tsConfigPath);
62762
+ } catch {
62763
+ return null;
62764
+ }
62757
62765
  if (visitedPaths.has(realPath)) return null;
62758
62766
  const tsConfig = readTsConfig(realPath);
62759
62767
  if (!tsConfig) return null;
@@ -63281,27 +63289,30 @@ const getSelectedObjectProperty = (expression, propertyName, analysis, visitedEx
63281
63289
  }
63282
63290
  return null;
63283
63291
  };
63284
- const configExpressionMayDefineProperty = (expression, propertyName, analysis) => {
63292
+ const configExpressionMayDefineProperty = (expression, propertyName, analysis, visitedExpressions = /* @__PURE__ */ new Set()) => {
63285
63293
  let resolvedExpression = expression;
63286
63294
  while (ts.isParenthesizedExpression(resolvedExpression) || ts.isAsExpression(resolvedExpression) || ts.isTypeAssertionExpression(resolvedExpression) || ts.isSatisfiesExpression(resolvedExpression) || ts.isNonNullExpression(resolvedExpression)) resolvedExpression = resolvedExpression.expression;
63295
+ if (visitedExpressions.has(resolvedExpression)) return true;
63296
+ const nextVisitedExpressions = new Set(visitedExpressions);
63297
+ nextVisitedExpressions.add(resolvedExpression);
63287
63298
  if (ts.isIdentifier(resolvedExpression)) {
63288
63299
  if (analysis.localBindings.has(resolvedExpression.text)) {
63289
63300
  const localReference = analysis.localBindings.get(resolvedExpression.text);
63290
- return localReference?.expression ? configExpressionMayDefineProperty(localReference.expression, propertyName, localReference.analysis) : true;
63301
+ return localReference?.expression ? configExpressionMayDefineProperty(localReference.expression, propertyName, localReference.analysis, nextVisitedExpressions) : true;
63291
63302
  }
63292
63303
  const scopedBinding = getScopedConfigBinding(resolvedExpression);
63293
- if (scopedBinding.wasFound) return scopedBinding.initializer ? configExpressionMayDefineProperty(scopedBinding.initializer, propertyName, analysis) : true;
63304
+ if (scopedBinding.wasFound) return scopedBinding.initializer ? configExpressionMayDefineProperty(scopedBinding.initializer, propertyName, analysis, nextVisitedExpressions) : true;
63294
63305
  const topLevelBinding = getTopLevelBinding(analysis.sourceFile, resolvedExpression.text);
63295
- return topLevelBinding && ts.isExpression(topLevelBinding) ? configExpressionMayDefineProperty(topLevelBinding, propertyName, analysis) : true;
63306
+ return topLevelBinding && ts.isExpression(topLevelBinding) ? configExpressionMayDefineProperty(topLevelBinding, propertyName, analysis, nextVisitedExpressions) : true;
63296
63307
  }
63297
63308
  if (ts.isConditionalExpression(resolvedExpression)) {
63298
- if (isStaticallyTruthyConfigExpression(resolvedExpression.condition, analysis)) return configExpressionMayDefineProperty(resolvedExpression.whenTrue, propertyName, analysis);
63299
- if (isStaticallyDisabledConfigExpression(resolvedExpression.condition, analysis)) return configExpressionMayDefineProperty(resolvedExpression.whenFalse, propertyName, analysis);
63300
- return configExpressionMayDefineProperty(resolvedExpression.whenTrue, propertyName, analysis) || configExpressionMayDefineProperty(resolvedExpression.whenFalse, propertyName, analysis);
63309
+ if (isStaticallyTruthyConfigExpression(resolvedExpression.condition, analysis)) return configExpressionMayDefineProperty(resolvedExpression.whenTrue, propertyName, analysis, nextVisitedExpressions);
63310
+ if (isStaticallyDisabledConfigExpression(resolvedExpression.condition, analysis)) return configExpressionMayDefineProperty(resolvedExpression.whenFalse, propertyName, analysis, nextVisitedExpressions);
63311
+ return configExpressionMayDefineProperty(resolvedExpression.whenTrue, propertyName, analysis, nextVisitedExpressions) || configExpressionMayDefineProperty(resolvedExpression.whenFalse, propertyName, analysis, nextVisitedExpressions);
63301
63312
  }
63302
63313
  if (!ts.isObjectLiteralExpression(resolvedExpression)) return true;
63303
63314
  return resolvedExpression.properties.some((property) => {
63304
- if (ts.isSpreadAssignment(property)) return configExpressionMayDefineProperty(property.expression, propertyName, analysis);
63315
+ if (ts.isSpreadAssignment(property)) return configExpressionMayDefineProperty(property.expression, propertyName, analysis, nextVisitedExpressions);
63305
63316
  if (ts.isPropertyAssignment(property) || ts.isShorthandPropertyAssignment(property) || ts.isMethodDeclaration(property) || ts.isGetAccessorDeclaration(property) || ts.isSetAccessorDeclaration(property)) {
63306
63317
  const staticPropertyName = getStaticPropertyName$2(property.name);
63307
63318
  return staticPropertyName === null || staticPropertyName === propertyName;
@@ -63377,7 +63388,13 @@ const analyzeConfigModuleExport = (filePath, exportName, allowCompilerTransform,
63377
63388
  if (visitedModules.has(moduleVisitKey)) return false;
63378
63389
  const nextVisitedModules = new Set(visitedModules);
63379
63390
  nextVisitedModules.add(moduleVisitKey);
63380
- return analyzeConfigSourceFileExport(parseConfigSourceFile(filePath, NFS.readFileSync(filePath, "utf-8")), filePath, exportName, allowCompilerTransform, importDepth, nextVisitedModules, argumentsList, argumentAnalysis);
63391
+ let sourceText;
63392
+ try {
63393
+ sourceText = NFS.readFileSync(filePath, "utf-8");
63394
+ } catch {
63395
+ return false;
63396
+ }
63397
+ return analyzeConfigSourceFileExport(parseConfigSourceFile(filePath, sourceText), filePath, exportName, allowCompilerTransform, importDepth, nextVisitedModules, argumentsList, argumentAnalysis);
63381
63398
  };
63382
63399
  const analyzeConfigSourceFileExport = (sourceFile, filePath, exportName, allowCompilerTransform, importDepth, visitedModules, argumentsList = [], argumentAnalysis) => {
63383
63400
  const analysis = {
@@ -66786,6 +66803,11 @@ var DeadCodeResultCacheEnabled = class extends Reference("react-doctor/DeadCodeR
66786
66803
  if (CACHE_DISABLED_VALUES.has(noDeadCodeCache)) return false;
66787
66804
  return true;
66788
66805
  } }) {};
66806
+ const importDefaultExport = async (jitiInstance, filePath) => {
66807
+ const imported = await jitiInstance.import(filePath);
66808
+ return imported?.default ?? imported;
66809
+ };
66810
+ const readJson5File = (filePath) => parseJSON5(NFS.readFileSync(filePath, "utf-8"));
66789
66811
  const DIAGNOSTIC_SURFACES = [
66790
66812
  "cli",
66791
66813
  "prComment",
@@ -66880,6 +66902,43 @@ const validateStringArrayField = (fieldName, value) => {
66880
66902
  return false;
66881
66903
  });
66882
66904
  };
66905
+ const validateIgnoreOverride = (rawOverride, index) => {
66906
+ const fieldName = `ignore.overrides[${index}]`;
66907
+ if (!isRecord(rawOverride)) {
66908
+ warnConfigIssue(`config field "${fieldName}" must be an object (got ${typeof rawOverride}); ignoring this override.`);
66909
+ return;
66910
+ }
66911
+ const files = validateStringArrayField(`${fieldName}.files`, rawOverride.files);
66912
+ if (files === void 0) return void 0;
66913
+ const validatedOverride = { files };
66914
+ if (rawOverride.rules !== void 0) {
66915
+ const rules = validateStringArrayField(`${fieldName}.rules`, rawOverride.rules);
66916
+ if (rules !== void 0) validatedOverride.rules = rules;
66917
+ }
66918
+ return validatedOverride;
66919
+ };
66920
+ const validateIgnoreField = (rawIgnore) => {
66921
+ if (!isRecord(rawIgnore)) {
66922
+ warnConfigIssue(`config field "ignore" must be an object (got ${typeof rawIgnore}); ignoring this field.`);
66923
+ return;
66924
+ }
66925
+ const validatedIgnore = {};
66926
+ for (const fieldName of [
66927
+ "rules",
66928
+ "files",
66929
+ "tags"
66930
+ ]) {
66931
+ if (rawIgnore[fieldName] === void 0) continue;
66932
+ const validatedValues = validateStringArrayField(`ignore.${fieldName}`, rawIgnore[fieldName]);
66933
+ if (validatedValues !== void 0) validatedIgnore[fieldName] = validatedValues;
66934
+ }
66935
+ if (rawIgnore.overrides !== void 0) if (!Array.isArray(rawIgnore.overrides)) warnConfigIssue(`config field "ignore.overrides" must be an array (got ${typeof rawIgnore.overrides}); ignoring this field.`);
66936
+ else validatedIgnore.overrides = rawIgnore.overrides.flatMap((rawOverride, index) => {
66937
+ const validatedOverride = validateIgnoreOverride(rawOverride, index);
66938
+ return validatedOverride ? [validatedOverride] : [];
66939
+ });
66940
+ return validatedIgnore;
66941
+ };
66883
66942
  const validateSurfaceControls = (surface, rawControls) => {
66884
66943
  if (!isRecord(rawControls)) {
66885
66944
  warnConfigIssue(`config field "surfaces.${surface}" must be an object (got ${typeof rawControls}); ignoring this surface.`);
@@ -66955,6 +67014,7 @@ const validateConfigTypes = (config) => {
66955
67014
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
66956
67015
  for (const fieldName of STRING_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateString(fieldName, value));
66957
67016
  for (const fieldName of STRING_ARRAY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateStringArrayField(fieldName, value));
67017
+ applyFieldValidator(config, validated, "ignore", validateIgnoreField);
66958
67018
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
66959
67019
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
66960
67020
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -66978,11 +67038,7 @@ const DATA_CONFIG_EXTENSIONS = new Set(["json", "jsonc"]);
66978
67038
  const PACKAGE_JSON_FILENAME = "package.json";
66979
67039
  const PACKAGE_JSON_CONFIG_KEY = "reactDoctor";
66980
67040
  const LEGACY_CONFIG_FILENAME = "react-doctor.config.json";
66981
- const jiti = createJiti(import.meta.url);
66982
- const importDefaultExport = async (jitiInstance, filePath) => {
66983
- const imported = await jitiInstance.import(filePath);
66984
- return imported?.default ?? imported;
66985
- };
67041
+ const jiti$1 = createJiti(import.meta.url);
66986
67042
  const SELF_PACKAGE_IMPORT_SPECIFIER = "react-doctor/api";
66987
67043
  const SELF_PACKAGE_RESOLVE_TARGETS = ["react-doctor/api", "@react-doctor/core"];
66988
67044
  const MODULE_NOT_FOUND_ERROR_CODES = new Set(["MODULE_NOT_FOUND", "ERR_MODULE_NOT_FOUND"]);
@@ -66990,7 +67046,7 @@ let selfAliasJiti;
66990
67046
  const getSelfAliasJiti = () => {
66991
67047
  if (selfAliasJiti !== void 0) return selfAliasJiti;
66992
67048
  for (const resolveTarget of SELF_PACKAGE_RESOLVE_TARGETS) try {
66993
- const aliasTargetPath = fileURLToPath(jiti.esmResolve(resolveTarget));
67049
+ const aliasTargetPath = fileURLToPath(jiti$1.esmResolve(resolveTarget));
66994
67050
  selfAliasJiti = createJiti(import.meta.url, { alias: { [SELF_PACKAGE_IMPORT_SPECIFIER]: aliasTargetPath } });
66995
67051
  return selfAliasJiti;
66996
67052
  } catch {
@@ -67002,7 +67058,7 @@ const getSelfAliasJiti = () => {
67002
67058
  const isSelfPackageResolutionError = (error) => error instanceof Error && "code" in error && typeof error.code === "string" && MODULE_NOT_FOUND_ERROR_CODES.has(error.code) && error.message.includes(SELF_PACKAGE_IMPORT_SPECIFIER);
67003
67059
  const loadModuleConfig = async (filePath) => {
67004
67060
  try {
67005
- return await importDefaultExport(jiti, filePath);
67061
+ return await importDefaultExport(jiti$1, filePath);
67006
67062
  } catch (error) {
67007
67063
  if (!isSelfPackageResolutionError(error)) throw error;
67008
67064
  const aliasJiti = getSelfAliasJiti();
@@ -67014,12 +67070,11 @@ const loadModuleConfig = async (filePath) => {
67014
67070
  }
67015
67071
  }
67016
67072
  };
67017
- const readDataConfig = (filePath) => parseJSON5(NFS.readFileSync(filePath, "utf-8"));
67018
67073
  const readEmbeddedPackageJsonConfig = (directory) => {
67019
67074
  const packageJsonPath = Path.join(directory, PACKAGE_JSON_FILENAME);
67020
67075
  if (!isFile(packageJsonPath)) return null;
67021
67076
  try {
67022
- const packageJson = parseJSON5(NFS.readFileSync(packageJsonPath, "utf-8"));
67077
+ const packageJson = readJson5File(packageJsonPath);
67023
67078
  if (isPlainObject(packageJson)) {
67024
67079
  const embeddedConfig = packageJson[PACKAGE_JSON_CONFIG_KEY];
67025
67080
  if (isPlainObject(embeddedConfig)) return embeddedConfig;
@@ -67044,7 +67099,7 @@ const loadLegacyConfig = (directory) => {
67044
67099
  loaded: null
67045
67100
  };
67046
67101
  try {
67047
- const parsed = readDataConfig(legacyFilePath);
67102
+ const parsed = readJson5File(legacyFilePath);
67048
67103
  if (isPlainObject(parsed)) {
67049
67104
  warn(`${LEGACY_CONFIG_FILENAME} is deprecated — rename it to ${CONFIG_BASENAME}.json (or author a ${CONFIG_BASENAME}.ts). It is still read for now.`);
67050
67105
  return {
@@ -67073,7 +67128,7 @@ const loadConfigFromDirectory = async (directory) => {
67073
67128
  if (!isFile(filePath)) continue;
67074
67129
  const isDataFile = DATA_CONFIG_EXTENSIONS.has(extension);
67075
67130
  try {
67076
- const parsed = isDataFile ? readDataConfig(filePath) : await loadModuleConfig(filePath);
67131
+ const parsed = isDataFile ? readJson5File(filePath) : await loadModuleConfig(filePath);
67077
67132
  if (isPlainObject(parsed)) return {
67078
67133
  status: "found",
67079
67134
  loaded: {
@@ -68619,7 +68674,7 @@ const collectScriptMotionEvidence = (sourceFile, typeChecker, program) => {
68619
68674
  if (calleeEvidence.isReducedMotionHook && isExpressionValueConsumed(node)) evidence.hasReducedMotionHandling = true;
68620
68675
  }
68621
68676
  if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
68622
- const tagEvidence = ts.isJsxNamespacedName(node.tagName) ? EMPTY_MOTION_EXPRESSION_EVIDENCE : resolveMotionExpressionEvidence(node.tagName, typeChecker, program);
68677
+ const tagEvidence = ts.isIdentifier(node.tagName) || ts.isPropertyAccessExpression(node.tagName) ? resolveMotionExpressionEvidence(node.tagName, typeChecker, program) : EMPTY_MOTION_EXPRESSION_EVIDENCE;
68623
68678
  if (tagEvidence.isMotionComponent || tagEvidence.isMotionComponentFactory) evidence.hasMotionUse = true;
68624
68679
  if (tagEvidence.isMotionConfig && jsxElementHasReducedMotionConfiguration(node.attributes, typeChecker)) evidence.hasReducedMotionHandling = true;
68625
68680
  }
@@ -69275,20 +69330,45 @@ const collectIgnorePatterns = (rootDirectory) => {
69275
69330
  cachedPatternsByRoot.set(rootDirectory, patterns);
69276
69331
  return patterns;
69277
69332
  };
69278
- const failOpenReadJson = (filePath, fallback) => {
69333
+ const KNIP_DATA_CONFIG_FILENAMES = new Set([
69334
+ "knip.json",
69335
+ "knip.jsonc",
69336
+ ".knip.json",
69337
+ ".knip.jsonc"
69338
+ ]);
69339
+ const KNIP_CONFIG_FILENAMES = [
69340
+ ...KNIP_DATA_CONFIG_FILENAMES,
69341
+ "knip.ts",
69342
+ "knip.js",
69343
+ "knip.config.ts",
69344
+ "knip.config.js"
69345
+ ];
69346
+ const jiti = createJiti(import.meta.url, { moduleCache: false });
69347
+ const resolveKnipConfigExport = async (configExport) => {
69348
+ const config = typeof configExport === "function" ? await configExport() : await configExport;
69349
+ return isRecord(config) ? config : null;
69350
+ };
69351
+ const loadKnipConfigFile = async (configFilename, filePath) => {
69279
69352
  try {
69280
- return JSON.parse(NFS.readFileSync(filePath, "utf8"));
69353
+ return await resolveKnipConfigExport(KNIP_DATA_CONFIG_FILENAMES.has(configFilename) ? readJson5File(filePath) : await importDefaultExport(jiti, filePath));
69281
69354
  } catch {
69282
- return fallback;
69355
+ return null;
69283
69356
  }
69284
69357
  };
69285
- const KNIP_JSON_FILENAME = "knip.json";
69286
- const readKnipConfig = (rootDirectory) => {
69287
- const knipJson = failOpenReadJson(path.join(rootDirectory, KNIP_JSON_FILENAME), null);
69288
- if (isRecord(knipJson)) return knipJson;
69289
- const packageJson = failOpenReadJson(path.join(rootDirectory, "package.json"), null);
69290
- const packageKnipConfig = isRecord(packageJson) ? packageJson.knip : null;
69291
- return isRecord(packageKnipConfig) ? packageKnipConfig : null;
69358
+ const loadKnipConfig = async (rootDirectory) => {
69359
+ for (const configFilename of KNIP_CONFIG_FILENAMES) {
69360
+ const filePath = path.join(rootDirectory, configFilename);
69361
+ if (!isFile(filePath)) continue;
69362
+ const config = await loadKnipConfigFile(configFilename, filePath);
69363
+ if (config) return config;
69364
+ }
69365
+ try {
69366
+ const packageJson = readJson5File(path.join(rootDirectory, "package.json"));
69367
+ const packageKnipConfig = isRecord(packageJson) ? packageJson.knip : null;
69368
+ return isRecord(packageKnipConfig) ? packageKnipConfig : null;
69369
+ } catch {
69370
+ return null;
69371
+ }
69292
69372
  };
69293
69373
  const normalizePatternList = (value) => {
69294
69374
  if (typeof value === "string" && value.length > 0) return [value];
@@ -69308,22 +69388,28 @@ const collectKnipWorkspacePatterns = (workspaces, settingName) => {
69308
69388
  }
69309
69389
  return patterns;
69310
69390
  };
69311
- const collectKnipPatterns = (rootDirectory, settingName) => {
69312
- const config = readKnipConfig(rootDirectory);
69391
+ const collectKnipPatterns = (config, settingName) => {
69313
69392
  if (!config) return [];
69314
69393
  return [...normalizePatternList(config[settingName]), ...collectKnipWorkspacePatterns(config.workspaces, settingName)];
69315
69394
  };
69316
- const collectDeadCodeIgnorePatterns = (rootDirectory) => {
69395
+ const collectDeadCodeIgnorePatterns = (rootDirectory, config) => {
69317
69396
  const seen = /* @__PURE__ */ new Set();
69318
69397
  const sources = [
69319
69398
  readIgnoreFile(path.join(rootDirectory, ".gitignore")),
69320
69399
  collectIgnorePatterns(rootDirectory),
69321
- collectKnipPatterns(rootDirectory, "ignore")
69400
+ collectKnipPatterns(config, "ignore")
69322
69401
  ];
69323
69402
  for (const source of sources) for (const pattern of source) seen.add(pattern);
69324
69403
  return [...seen].filter((pattern) => pattern.length > 0);
69325
69404
  };
69326
- const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
69405
+ const collectDeadCodeEntryPatterns = (config) => [...new Set(collectKnipPatterns(config, "entry"))].filter((pattern) => pattern.length > 0);
69406
+ const collectDeadCodePatterns = async (rootDirectory) => {
69407
+ const config = await loadKnipConfig(rootDirectory);
69408
+ return {
69409
+ entryPatterns: collectDeadCodeEntryPatterns(config),
69410
+ ignorePatterns: collectDeadCodeIgnorePatterns(rootDirectory, config)
69411
+ };
69412
+ };
69327
69413
  const atomicWriteJson = (filePath, value) => {
69328
69414
  try {
69329
69415
  NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
@@ -69334,6 +69420,13 @@ const atomicWriteJson = (filePath, value) => {
69334
69420
  return;
69335
69421
  }
69336
69422
  };
69423
+ const failOpenReadJson = (filePath, fallback) => {
69424
+ try {
69425
+ return JSON.parse(NFS.readFileSync(filePath, "utf8"));
69426
+ } catch {
69427
+ return fallback;
69428
+ }
69429
+ };
69337
69430
  const hashFileContents = (filePath) => {
69338
69431
  try {
69339
69432
  return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
@@ -69342,7 +69435,7 @@ const hashFileContents = (filePath) => {
69342
69435
  }
69343
69436
  };
69344
69437
  const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
69345
- const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
69438
+ const CORE_SIDE_MANIFEST_NAMES = [...KNIP_CONFIG_FILENAMES, "deno.lock"];
69346
69439
  const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
69347
69440
  const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
69348
69441
  const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
@@ -69868,8 +69961,7 @@ const runDeadCodeWorkerWithTimeout = (handle, timeoutMs, abortSignal) => new Pro
69868
69961
  const checkDeadCode = async (options) => {
69869
69962
  const rootDirectory = toCanonicalPath(options.rootDirectory);
69870
69963
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
69871
- const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
69872
- const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
69964
+ const { entryPatterns, ignorePatterns } = await collectDeadCodePatterns(rootDirectory);
69873
69965
  const tsConfigPath = resolveTsConfigPath(rootDirectory);
69874
69966
  const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
69875
69967
  const fileStatsSnapshot = options.cacheEnabled ? collectAnalyzedFileStats(rootDirectory) : null;
@@ -70603,7 +70695,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
70603
70695
  changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
70604
70696
  isCurrentChanges: false
70605
70697
  };
70606
- }).pipe(withSpan("Git.diffSelection")),
70698
+ }).pipe(catchReasons("ReactDoctorError", { GitInvocationFailed: () => succeed$2(null) }, (_reason, error) => fail$4(error)), withSpan("Git.diffSelection")),
70607
70699
  stagedFilePaths: (directory) => runGit(directory, [
70608
70700
  "diff",
70609
70701
  "--no-ext-diff",
@@ -70819,6 +70911,33 @@ const detectUserLintConfigPaths = (rootDirectory) => {
70819
70911
  return [];
70820
70912
  };
70821
70913
  const DISABLE_DIRECTIVE_PATTERN = /(eslint|oxlint)-disable/;
70914
+ const neutralizedFileLeases = /* @__PURE__ */ new Map();
70915
+ const activeNeutralizationInitializations = /* @__PURE__ */ new Set();
70916
+ const directoriesOverlap = (left, right) => {
70917
+ const isWithin = (parent, candidate) => {
70918
+ const relativePath = Path.relative(parent, candidate);
70919
+ return relativePath.length === 0 || !relativePath.startsWith(`..${Path.sep}`) && relativePath !== ".." && !Path.isAbsolute(relativePath);
70920
+ };
70921
+ return isWithin(left, right) || isWithin(right, left);
70922
+ };
70923
+ const withOverlappingInitializationLock = async (rootDirectory, operation) => {
70924
+ const blockers = [...activeNeutralizationInitializations].filter((initialization) => directoriesOverlap(initialization.rootDirectory, rootDirectory)).map((initialization) => initialization.done);
70925
+ let release;
70926
+ const initialization = {
70927
+ rootDirectory,
70928
+ done: new Promise((resolve) => {
70929
+ release = resolve;
70930
+ })
70931
+ };
70932
+ activeNeutralizationInitializations.add(initialization);
70933
+ await Promise.all(blockers);
70934
+ try {
70935
+ return await operation();
70936
+ } finally {
70937
+ activeNeutralizationInitializations.delete(initialization);
70938
+ release();
70939
+ }
70940
+ };
70822
70941
  const findFilesWithDisableDirectivesViaGit = async (rootDirectory, includePaths) => {
70823
70942
  const program = gen(function* () {
70824
70943
  return yield* (yield* Git).grep({
@@ -70861,34 +70980,84 @@ const findFilesWithDisableDirectivesViaFilesystem = (rootDirectory, includePaths
70861
70980
  };
70862
70981
  const findFilesWithDisableDirectives = async (rootDirectory, includePaths) => await findFilesWithDisableDirectivesViaGit(rootDirectory, includePaths) ?? findFilesWithDisableDirectivesViaFilesystem(rootDirectory, includePaths);
70863
70982
  const neutralizeContent = (content) => content.replaceAll("eslint-disable", "eslint_disable").replaceAll("oxlint-disable", "oxlint_disable");
70864
- const neutralizeDisableDirectives = async (rootDirectory, includePaths) => {
70865
- const filePaths = await findFilesWithDisableDirectives(rootDirectory, includePaths);
70866
- const originalContents = /* @__PURE__ */ new Map();
70983
+ const collectNeutralizationCandidatePaths = (rootDirectory, discoveredRelativePaths, includePaths) => {
70984
+ const resolvedRootDirectory = NFS.realpathSync(rootDirectory);
70985
+ const requestedPaths = includePaths && includePaths.length > 0 ? new Set(includePaths.flatMap((includePath) => {
70986
+ try {
70987
+ return [NFS.realpathSync(Path.resolve(resolvedRootDirectory, includePath))];
70988
+ } catch {
70989
+ return [];
70990
+ }
70991
+ })) : null;
70992
+ const candidatePaths = /* @__PURE__ */ new Set();
70993
+ for (const relativePath of discoveredRelativePaths) try {
70994
+ candidatePaths.add(NFS.realpathSync(Path.resolve(resolvedRootDirectory, relativePath)));
70995
+ } catch {
70996
+ continue;
70997
+ }
70998
+ for (const leasedPath of neutralizedFileLeases.keys()) {
70999
+ const relativePath = Path.relative(resolvedRootDirectory, leasedPath);
71000
+ if (!(relativePath.length === 0 || !relativePath.startsWith(`..${Path.sep}`) && relativePath !== ".." && !Path.isAbsolute(relativePath)) || requestedPaths && !requestedPaths.has(leasedPath)) continue;
71001
+ candidatePaths.add(leasedPath);
71002
+ }
71003
+ return candidatePaths;
71004
+ };
71005
+ const neutralizeDisableDirectives = async (rootDirectory, includePaths, options = {}) => {
71006
+ const resolvedRootDirectory = NFS.realpathSync(rootDirectory);
71007
+ const leasedPaths = /* @__PURE__ */ new Set();
70867
71008
  let isRestored = false;
70868
71009
  const restore = () => {
70869
71010
  if (isRestored) return;
70870
71011
  isRestored = true;
70871
- for (const [absolutePath, originalContent] of originalContents) try {
70872
- NFS.writeFileSync(absolutePath, originalContent);
70873
- } catch (error) {
70874
- process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${messageFromUnknown(error)}\n[react-doctor] Run: git checkout -- ${absolutePath}\n`);
71012
+ for (const absolutePath of leasedPaths) {
71013
+ const lease = neutralizedFileLeases.get(absolutePath);
71014
+ if (!lease) continue;
71015
+ lease.referenceCount -= 1;
71016
+ if (lease.referenceCount > 0) continue;
71017
+ neutralizedFileLeases.delete(absolutePath);
71018
+ try {
71019
+ NFS.writeFileSync(absolutePath, lease.originalContent);
71020
+ } catch (error) {
71021
+ process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${messageFromUnknown(error)}\n[react-doctor] Run: git checkout -- ${absolutePath}\n`);
71022
+ }
70875
71023
  }
70876
71024
  };
70877
71025
  const onExit = () => restore();
70878
71026
  process.once("exit", onExit);
70879
- for (const relativePath of filePaths) {
70880
- const absolutePath = Path.join(rootDirectory, relativePath);
71027
+ const leaseCandidatePath = (absolutePath) => {
71028
+ if (leasedPaths.has(absolutePath)) return;
71029
+ const existingLease = neutralizedFileLeases.get(absolutePath);
71030
+ if (existingLease) {
71031
+ existingLease.referenceCount += 1;
71032
+ leasedPaths.add(absolutePath);
71033
+ return;
71034
+ }
70881
71035
  let originalContent;
70882
71036
  try {
70883
71037
  originalContent = NFS.readFileSync(absolutePath, "utf-8");
70884
71038
  } catch {
70885
- continue;
71039
+ return;
70886
71040
  }
70887
71041
  const neutralizedContent = neutralizeContent(originalContent);
70888
71042
  if (neutralizedContent !== originalContent) {
70889
- originalContents.set(absolutePath, originalContent);
70890
71043
  NFS.writeFileSync(absolutePath, neutralizedContent);
71044
+ neutralizedFileLeases.set(absolutePath, {
71045
+ originalContent,
71046
+ referenceCount: 1
71047
+ });
71048
+ leasedPaths.add(absolutePath);
70891
71049
  }
71050
+ };
71051
+ try {
71052
+ await withOverlappingInitializationLock(resolvedRootDirectory, async () => {
71053
+ for (const absolutePath of collectNeutralizationCandidatePaths(resolvedRootDirectory, [], includePaths)) if (neutralizedFileLeases.has(absolutePath)) leaseCandidatePath(absolutePath);
71054
+ const discoveredRelativePaths = await (options.findFiles ?? findFilesWithDisableDirectives)(resolvedRootDirectory, includePaths);
71055
+ for (const absolutePath of collectNeutralizationCandidatePaths(resolvedRootDirectory, discoveredRelativePaths, includePaths)) leaseCandidatePath(absolutePath);
71056
+ });
71057
+ } catch (error) {
71058
+ restore();
71059
+ process.removeListener("exit", onExit);
71060
+ throw error;
70892
71061
  }
70893
71062
  return () => {
70894
71063
  restore();
@@ -75223,7 +75392,7 @@ const materializeSourceTree = (input) => gen(function* () {
75223
75392
  const sourcePath = path.join(input.directory, subdirectory, configFilename);
75224
75393
  const targetPath = path.resolve(resolvedTempDirectory, subdirectory, configFilename);
75225
75394
  if (!isPathInsideDirectory(targetPath, resolvedTempDirectory)) continue;
75226
- if (fs.existsSync(sourcePath) && !fs.existsSync(targetPath)) {
75395
+ if (isFile(sourcePath) && !fs.existsSync(targetPath)) {
75227
75396
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
75228
75397
  fs.cpSync(sourcePath, targetPath);
75229
75398
  }
@@ -75564,16 +75733,17 @@ const detectAiTrainingEnvironment = (environment = process.env) => {
75564
75733
  for (const [environmentVariable, label] of AI_TRAINING_BY_ENVIRONMENT_VARIABLE) if (environment[environmentVariable]?.trim()) return label;
75565
75734
  return null;
75566
75735
  };
75567
- const unionValues = (baseValues, overrideValues) => [...new Set([...baseValues, ...overrideValues])];
75736
+ const stringValues = (values) => Array.isArray(values) ? values.filter((value) => typeof value === "string") : [];
75737
+ const unionValues = (baseValues, overrideValues) => [...new Set([...stringValues(baseValues), ...stringValues(overrideValues)])];
75568
75738
  const mergeIgnores = (baseIgnore, overrideIgnore) => {
75569
75739
  const mergedIgnore = {
75570
75740
  ...baseIgnore,
75571
75741
  ...overrideIgnore
75572
75742
  };
75573
- if (baseIgnore.rules && overrideIgnore.rules) mergedIgnore.rules = unionValues(baseIgnore.rules, overrideIgnore.rules);
75574
- if (baseIgnore.files && overrideIgnore.files) mergedIgnore.files = unionValues(baseIgnore.files, overrideIgnore.files);
75575
- if (baseIgnore.tags && overrideIgnore.tags) mergedIgnore.tags = unionValues(baseIgnore.tags, overrideIgnore.tags);
75576
- if (baseIgnore.overrides && overrideIgnore.overrides) mergedIgnore.overrides = [...baseIgnore.overrides, ...overrideIgnore.overrides];
75743
+ if (baseIgnore.rules !== void 0 || overrideIgnore.rules !== void 0) mergedIgnore.rules = unionValues(baseIgnore.rules, overrideIgnore.rules);
75744
+ if (baseIgnore.files !== void 0 || overrideIgnore.files !== void 0) mergedIgnore.files = unionValues(baseIgnore.files, overrideIgnore.files);
75745
+ if (baseIgnore.tags !== void 0 || overrideIgnore.tags !== void 0) mergedIgnore.tags = unionValues(baseIgnore.tags, overrideIgnore.tags);
75746
+ if (baseIgnore.overrides !== void 0 || overrideIgnore.overrides !== void 0) mergedIgnore.overrides = [...Array.isArray(baseIgnore.overrides) ? baseIgnore.overrides : [], ...Array.isArray(overrideIgnore.overrides) ? overrideIgnore.overrides : []];
75577
75747
  return mergedIgnore;
75578
75748
  };
75579
75749
  /**
@@ -75781,4 +75951,4 @@ const toJsonReport = (result, options) => buildJsonReport({
75781
75951
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
75782
75952
 
75783
75953
  //# sourceMappingURL=index.js.map
75784
- //# debugId=e18e3c42-e18e-5087-9b71-eb1bcb203d6b
75954
+ //# debugId=5415308a-0150-547d-9270-806382225dcf