react-doctor 0.5.8-dev.fff9466 → 0.6.0-dev.0748a4b
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 +9 -3
- package/dist/cli.js +9497 -1677
- package/dist/index.d.ts +21 -0
- package/dist/index.js +409 -302
- package/dist/lsp.js +437 -326
- package/package.json +7 -6
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]="
|
|
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]="62cf4740-7938-5db0-8b80-dc81b60af36a")}catch(e){}}();
|
|
3
3
|
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import * as NFS from "node:fs";
|
|
@@ -7,7 +7,7 @@ import fs from "node:fs";
|
|
|
7
7
|
import * as Path from "node:path";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import * as NodeChildProcess from "node:child_process";
|
|
10
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
10
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
11
11
|
import * as ts from "typescript";
|
|
12
12
|
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
13
13
|
import * as OS from "node:os";
|
|
@@ -19364,6 +19364,15 @@ var JsonReportV1 = class extends Class("JsonReportV1")({
|
|
|
19364
19364
|
ok: Boolean$1,
|
|
19365
19365
|
directory: String$1,
|
|
19366
19366
|
mode: JsonReportMode,
|
|
19367
|
+
/**
|
|
19368
|
+
* True when a `changed` (compare) run was intended but the base couldn't be
|
|
19369
|
+
* resolved — a shallow CI checkout with no merge base, or a failed base/head
|
|
19370
|
+
* lint. The report then lists every finding in the changed files (mode
|
|
19371
|
+
* downgrades to `diff`, the `baseline` block is dropped, the CI gate is
|
|
19372
|
+
* skipped) rather than only the ones the change introduced. Absent on a
|
|
19373
|
+
* successful `baseline` run (`schemaVersion: 2`) and on non-compare scopes.
|
|
19374
|
+
*/
|
|
19375
|
+
baselineDegraded: optional(Boolean$1),
|
|
19367
19376
|
diff: NullOr(JsonReportDiffInfo),
|
|
19368
19377
|
projects: ArraySchema(JsonReportProjectEntry),
|
|
19369
19378
|
diagnostics: ArraySchema(Diagnostic),
|
|
@@ -32780,12 +32789,31 @@ const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
|
|
|
32780
32789
|
const MINIFIED_SNIFF_BYTES = 65536;
|
|
32781
32790
|
const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
32782
32791
|
const IGNORED_DIRECTORIES = new Set([
|
|
32792
|
+
".angular",
|
|
32793
|
+
".astro",
|
|
32794
|
+
".cache",
|
|
32795
|
+
".contentlayer",
|
|
32796
|
+
".direnv",
|
|
32797
|
+
".docusaurus",
|
|
32798
|
+
".expo",
|
|
32799
|
+
".firebase",
|
|
32783
32800
|
".git",
|
|
32801
|
+
".gradle",
|
|
32802
|
+
".hg",
|
|
32784
32803
|
".next",
|
|
32785
32804
|
".nuxt",
|
|
32805
|
+
".nx",
|
|
32786
32806
|
".output",
|
|
32807
|
+
".parcel-cache",
|
|
32808
|
+
".serverless",
|
|
32787
32809
|
".svelte-kit",
|
|
32810
|
+
".svn",
|
|
32811
|
+
".terraform",
|
|
32788
32812
|
".turbo",
|
|
32813
|
+
".venv",
|
|
32814
|
+
".vercel",
|
|
32815
|
+
".wrangler",
|
|
32816
|
+
".yarn",
|
|
32789
32817
|
"build",
|
|
32790
32818
|
"coverage",
|
|
32791
32819
|
"dist",
|
|
@@ -32793,6 +32821,119 @@ const IGNORED_DIRECTORIES = new Set([
|
|
|
32793
32821
|
"out",
|
|
32794
32822
|
"storybook-static"
|
|
32795
32823
|
]);
|
|
32824
|
+
const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
32825
|
+
const MILLISECONDS_PER_SECOND = 1e3;
|
|
32826
|
+
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
32827
|
+
const FETCH_TIMEOUT_MS = 1e4;
|
|
32828
|
+
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
32829
|
+
const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
|
|
32830
|
+
const SPAWN_ARGS_MAX_LENGTH_CHARS_POSIX = 15e5;
|
|
32831
|
+
const SPAWN_ARGS_MAX_LENGTH_CHARS_DARWIN = 8e5;
|
|
32832
|
+
const NODE_VERSION_PROBE_TIMEOUT_MS = 5e3;
|
|
32833
|
+
const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
|
|
32834
|
+
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
32835
|
+
const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
|
|
32836
|
+
const GIT_SHOW_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
32837
|
+
const ES_TARGET_YEAR_BY_NAME = {
|
|
32838
|
+
es3: 1999,
|
|
32839
|
+
es5: 2009,
|
|
32840
|
+
es6: 2015,
|
|
32841
|
+
es2015: 2015,
|
|
32842
|
+
es2016: 2016,
|
|
32843
|
+
es2017: 2017,
|
|
32844
|
+
es2018: 2018,
|
|
32845
|
+
es2019: 2019,
|
|
32846
|
+
es2020: 2020,
|
|
32847
|
+
es2021: 2021,
|
|
32848
|
+
es2022: 2022,
|
|
32849
|
+
es2023: 2023,
|
|
32850
|
+
es2024: 2024,
|
|
32851
|
+
es2025: 2025,
|
|
32852
|
+
esnext: 9999
|
|
32853
|
+
};
|
|
32854
|
+
/**
|
|
32855
|
+
* tsconfig filenames probed when resolving a project's TypeScript
|
|
32856
|
+
* compiler options — the root config first, then a monorepo base config.
|
|
32857
|
+
*/
|
|
32858
|
+
const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
|
|
32859
|
+
/**
|
|
32860
|
+
* Project-config files that `StagedFiles.materialize` copies into
|
|
32861
|
+
* the temp directory alongside staged sources so oxlint resolves
|
|
32862
|
+
* `tsconfig` / `package.json` / lint configs the same way it would
|
|
32863
|
+
* in the working tree. Hoisted out of the staged-files helper so
|
|
32864
|
+
* the constant lives next to the rest of the IO budget knobs.
|
|
32865
|
+
*/
|
|
32866
|
+
const STAGED_FILES_PROJECT_CONFIG_FILENAMES = [
|
|
32867
|
+
"tsconfig.json",
|
|
32868
|
+
"tsconfig.base.json",
|
|
32869
|
+
"package.json",
|
|
32870
|
+
"doctor.config.ts",
|
|
32871
|
+
"doctor.config.mts",
|
|
32872
|
+
"doctor.config.cts",
|
|
32873
|
+
"doctor.config.js",
|
|
32874
|
+
"doctor.config.mjs",
|
|
32875
|
+
"doctor.config.cjs",
|
|
32876
|
+
"doctor.config.json",
|
|
32877
|
+
"doctor.config.jsonc",
|
|
32878
|
+
"oxlint.json",
|
|
32879
|
+
".oxlintrc.json"
|
|
32880
|
+
];
|
|
32881
|
+
const OXLINT_OUTPUT_MAX_BYTES = 50 * 1024 * 1024;
|
|
32882
|
+
const OXLINT_SPAWN_TIMEOUT_MS = 6e4;
|
|
32883
|
+
const NODE_COMPILE_CACHE_DIR_NAME = "node-compile-cache";
|
|
32884
|
+
const DEAD_CODE_WORKER_TIMEOUT_MS = 12e4;
|
|
32885
|
+
const OXLINT_SPLIT_TOTAL_BUDGET_MS = 18e4;
|
|
32886
|
+
const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
32887
|
+
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
32888
|
+
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
32889
|
+
const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
|
|
32890
|
+
const DEAD_CODE_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
|
|
32891
|
+
const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
|
|
32892
|
+
const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
|
|
32893
|
+
const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
|
|
32894
|
+
const RECOMMENDED_PNPM_MINIMUM_RELEASE_AGE_MINUTES = 10080;
|
|
32895
|
+
const REACT_SERVER_DOM_PACKAGES = [
|
|
32896
|
+
"react-server-dom-webpack",
|
|
32897
|
+
"react-server-dom-parcel",
|
|
32898
|
+
"react-server-dom-turbopack"
|
|
32899
|
+
];
|
|
32900
|
+
const REACT_BLOG_RSC_ADVISORY_URL = "https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components";
|
|
32901
|
+
const VERCEL_NEXTJS_SECURITY_RELEASE_URL = "https://vercel.com/changelog/next-js-may-2026-security-release";
|
|
32902
|
+
const DIAGNOSTIC_CATEGORY_BUCKETS = [
|
|
32903
|
+
"Security",
|
|
32904
|
+
"Bugs",
|
|
32905
|
+
"Performance",
|
|
32906
|
+
"Accessibility",
|
|
32907
|
+
"Maintainability"
|
|
32908
|
+
];
|
|
32909
|
+
const APP_ONLY_RULE_KEYS = new Set([
|
|
32910
|
+
"react-hooks-js/static-components",
|
|
32911
|
+
"react-doctor/no-render-prop-children",
|
|
32912
|
+
"react-doctor/prefer-explicit-variants"
|
|
32913
|
+
]);
|
|
32914
|
+
const COMPILER_CLEANUP_BUCKET = "compiler-cleanup";
|
|
32915
|
+
const COMPILER_CLEANUP_RULE_KEYS = new Set(["react-doctor/react-compiler-no-manual-memoization"]);
|
|
32916
|
+
const ROOT_CAUSE_GROUPABLE_RULE_KEYS = new Set([
|
|
32917
|
+
"react-doctor/no-derived-state",
|
|
32918
|
+
"react-doctor/no-derived-state-effect",
|
|
32919
|
+
"react-doctor/no-derived-useState",
|
|
32920
|
+
"react-doctor/no-adjust-state-on-prop-change",
|
|
32921
|
+
"react-doctor/no-reset-all-state-on-prop-change"
|
|
32922
|
+
]);
|
|
32923
|
+
const MAX_GLOB_PATTERN_LENGTH_CHARS = 1024;
|
|
32924
|
+
const CONFIG_CACHE_TTL_MS = 300 * 1e3;
|
|
32925
|
+
const SOCKET_FREE_PURL_API_BASE = "https://firewall-api.socket.dev/purl";
|
|
32926
|
+
const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
32927
|
+
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
32928
|
+
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32929
|
+
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32930
|
+
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
32931
|
+
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
32932
|
+
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
32933
|
+
const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
32934
|
+
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
32935
|
+
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
32936
|
+
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => IGNORED_DIRECTORIES.has(segment));
|
|
32796
32937
|
const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath);
|
|
32797
32938
|
const isMinifiedSource = (absolutePath) => {
|
|
32798
32939
|
let fileDescriptor;
|
|
@@ -32849,23 +32990,38 @@ const readDirectoryEntries = (directoryPath) => {
|
|
|
32849
32990
|
throw error;
|
|
32850
32991
|
}
|
|
32851
32992
|
};
|
|
32852
|
-
|
|
32853
|
-
let count = 0;
|
|
32993
|
+
function* walkSourceTreeFiles(rootDirectory) {
|
|
32854
32994
|
const stack = [rootDirectory];
|
|
32855
32995
|
while (stack.length > 0) {
|
|
32856
32996
|
const currentDirectory = stack.pop();
|
|
32857
|
-
|
|
32858
|
-
for (const entry of
|
|
32997
|
+
if (currentDirectory === void 0) continue;
|
|
32998
|
+
for (const entry of readDirectoryEntries(currentDirectory)) {
|
|
32999
|
+
const absolutePath = Path.join(currentDirectory, entry.name);
|
|
32859
33000
|
if (entry.isDirectory()) {
|
|
32860
|
-
if (!
|
|
33001
|
+
if (!IGNORED_DIRECTORIES.has(entry.name)) stack.push(absolutePath);
|
|
32861
33002
|
continue;
|
|
32862
33003
|
}
|
|
32863
|
-
if (entry.isFile()
|
|
33004
|
+
if (entry.isFile()) yield {
|
|
33005
|
+
absolutePath,
|
|
33006
|
+
name: entry.name
|
|
33007
|
+
};
|
|
32864
33008
|
}
|
|
32865
33009
|
}
|
|
32866
|
-
|
|
33010
|
+
}
|
|
33011
|
+
const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
|
|
33012
|
+
const entries = [];
|
|
33013
|
+
for (const relativePath of relativePaths) {
|
|
33014
|
+
const absolutePath = Path.resolve(rootDirectory, relativePath);
|
|
33015
|
+
const sizeBytes = statSourceFileSize(absolutePath);
|
|
33016
|
+
if (isLargeMinifiedFile(absolutePath, sizeBytes)) continue;
|
|
33017
|
+
entries.push({
|
|
33018
|
+
path: relativePath,
|
|
33019
|
+
sizeBytes: sizeBytes ?? 0
|
|
33020
|
+
});
|
|
33021
|
+
}
|
|
33022
|
+
return entries;
|
|
32867
33023
|
};
|
|
32868
|
-
const
|
|
33024
|
+
const listSourceFilesViaGit = (rootDirectory) => {
|
|
32869
33025
|
const result = spawnSync("git", [
|
|
32870
33026
|
"ls-files",
|
|
32871
33027
|
"-z",
|
|
@@ -32878,9 +33034,16 @@ const countSourceFilesViaGit = (rootDirectory) => {
|
|
|
32878
33034
|
maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES
|
|
32879
33035
|
});
|
|
32880
33036
|
if (result.error || result.status !== 0) return null;
|
|
32881
|
-
return result.stdout.split("\0").filter((filePath) => filePath.length > 0 && isLintableSourceFile(filePath) && !
|
|
33037
|
+
return result.stdout.split("\0").filter((filePath) => filePath.length > 0 && isLintableSourceFile(filePath) && !hasIgnoredPathSegment(filePath));
|
|
33038
|
+
};
|
|
33039
|
+
const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
33040
|
+
const filePaths = [];
|
|
33041
|
+
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) if (isLintableSourceFile(name)) filePaths.push(Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/"));
|
|
33042
|
+
return filePaths;
|
|
32882
33043
|
};
|
|
32883
|
-
const
|
|
33044
|
+
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
|
|
33045
|
+
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
33046
|
+
const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
|
|
32884
33047
|
const cachedPackageJsons = /* @__PURE__ */ new Map();
|
|
32885
33048
|
const clearPackageJsonCache = () => {
|
|
32886
33049
|
cachedPackageJsons.clear();
|
|
@@ -33689,115 +33852,6 @@ const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
|
33689
33852
|
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
33690
33853
|
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
33691
33854
|
};
|
|
33692
|
-
const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
33693
|
-
const MILLISECONDS_PER_SECOND = 1e3;
|
|
33694
|
-
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
33695
|
-
const FETCH_TIMEOUT_MS = 1e4;
|
|
33696
|
-
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
33697
|
-
const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
|
|
33698
|
-
const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
|
|
33699
|
-
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
33700
|
-
const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
|
|
33701
|
-
const GIT_SHOW_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
33702
|
-
const ES_TARGET_YEAR_BY_NAME = {
|
|
33703
|
-
es3: 1999,
|
|
33704
|
-
es5: 2009,
|
|
33705
|
-
es6: 2015,
|
|
33706
|
-
es2015: 2015,
|
|
33707
|
-
es2016: 2016,
|
|
33708
|
-
es2017: 2017,
|
|
33709
|
-
es2018: 2018,
|
|
33710
|
-
es2019: 2019,
|
|
33711
|
-
es2020: 2020,
|
|
33712
|
-
es2021: 2021,
|
|
33713
|
-
es2022: 2022,
|
|
33714
|
-
es2023: 2023,
|
|
33715
|
-
es2024: 2024,
|
|
33716
|
-
es2025: 2025,
|
|
33717
|
-
esnext: 9999
|
|
33718
|
-
};
|
|
33719
|
-
/**
|
|
33720
|
-
* tsconfig filenames probed when resolving a project's TypeScript
|
|
33721
|
-
* compiler options — the root config first, then a monorepo base config.
|
|
33722
|
-
*/
|
|
33723
|
-
const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
|
|
33724
|
-
/**
|
|
33725
|
-
* Project-config files that `StagedFiles.materialize` copies into
|
|
33726
|
-
* the temp directory alongside staged sources so oxlint resolves
|
|
33727
|
-
* `tsconfig` / `package.json` / lint configs the same way it would
|
|
33728
|
-
* in the working tree. Hoisted out of the staged-files helper so
|
|
33729
|
-
* the constant lives next to the rest of the IO budget knobs.
|
|
33730
|
-
*/
|
|
33731
|
-
const STAGED_FILES_PROJECT_CONFIG_FILENAMES = [
|
|
33732
|
-
"tsconfig.json",
|
|
33733
|
-
"tsconfig.base.json",
|
|
33734
|
-
"package.json",
|
|
33735
|
-
"doctor.config.ts",
|
|
33736
|
-
"doctor.config.mts",
|
|
33737
|
-
"doctor.config.cts",
|
|
33738
|
-
"doctor.config.js",
|
|
33739
|
-
"doctor.config.mjs",
|
|
33740
|
-
"doctor.config.cjs",
|
|
33741
|
-
"doctor.config.json",
|
|
33742
|
-
"doctor.config.jsonc",
|
|
33743
|
-
"oxlint.json",
|
|
33744
|
-
".oxlintrc.json"
|
|
33745
|
-
];
|
|
33746
|
-
const OXLINT_OUTPUT_MAX_BYTES = 50 * 1024 * 1024;
|
|
33747
|
-
const OXLINT_SPAWN_TIMEOUT_MS = 6e4;
|
|
33748
|
-
const NODE_COMPILE_CACHE_DIR_NAME = "node-compile-cache";
|
|
33749
|
-
const DEAD_CODE_WORKER_TIMEOUT_MS = 12e4;
|
|
33750
|
-
const OXLINT_SPLIT_TOTAL_BUDGET_MS = 18e4;
|
|
33751
|
-
const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
33752
|
-
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
33753
|
-
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
33754
|
-
const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
|
|
33755
|
-
const DEAD_CODE_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
|
|
33756
|
-
const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
|
|
33757
|
-
const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
|
|
33758
|
-
const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
|
|
33759
|
-
const RECOMMENDED_PNPM_MINIMUM_RELEASE_AGE_MINUTES = 10080;
|
|
33760
|
-
const REACT_SERVER_DOM_PACKAGES = [
|
|
33761
|
-
"react-server-dom-webpack",
|
|
33762
|
-
"react-server-dom-parcel",
|
|
33763
|
-
"react-server-dom-turbopack"
|
|
33764
|
-
];
|
|
33765
|
-
const REACT_BLOG_RSC_ADVISORY_URL = "https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components";
|
|
33766
|
-
const VERCEL_NEXTJS_SECURITY_RELEASE_URL = "https://vercel.com/changelog/next-js-may-2026-security-release";
|
|
33767
|
-
const DIAGNOSTIC_CATEGORY_BUCKETS = [
|
|
33768
|
-
"Security",
|
|
33769
|
-
"Bugs",
|
|
33770
|
-
"Performance",
|
|
33771
|
-
"Accessibility",
|
|
33772
|
-
"Maintainability"
|
|
33773
|
-
];
|
|
33774
|
-
const APP_ONLY_RULE_KEYS = new Set([
|
|
33775
|
-
"react-hooks-js/static-components",
|
|
33776
|
-
"react-doctor/no-render-prop-children",
|
|
33777
|
-
"react-doctor/prefer-explicit-variants"
|
|
33778
|
-
]);
|
|
33779
|
-
const COMPILER_CLEANUP_BUCKET = "compiler-cleanup";
|
|
33780
|
-
const COMPILER_CLEANUP_RULE_KEYS = new Set(["react-doctor/react-compiler-no-manual-memoization"]);
|
|
33781
|
-
const ROOT_CAUSE_GROUPABLE_RULE_KEYS = new Set([
|
|
33782
|
-
"react-doctor/no-derived-state",
|
|
33783
|
-
"react-doctor/no-derived-state-effect",
|
|
33784
|
-
"react-doctor/no-derived-useState",
|
|
33785
|
-
"react-doctor/no-adjust-state-on-prop-change",
|
|
33786
|
-
"react-doctor/no-reset-all-state-on-prop-change"
|
|
33787
|
-
]);
|
|
33788
|
-
const MAX_GLOB_PATTERN_LENGTH_CHARS = 1024;
|
|
33789
|
-
const CONFIG_CACHE_TTL_MS = 300 * 1e3;
|
|
33790
|
-
const SOCKET_FREE_PURL_API_BASE = "https://firewall-api.socket.dev/purl";
|
|
33791
|
-
const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
33792
|
-
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
33793
|
-
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
33794
|
-
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
33795
|
-
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
33796
|
-
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
33797
|
-
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
33798
|
-
const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
33799
|
-
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
33800
|
-
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
33801
33855
|
const TSCONFIG_FILENAME = "tsconfig.json";
|
|
33802
33856
|
const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
|
|
33803
33857
|
const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
|
|
@@ -34519,7 +34573,7 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
34519
34573
|
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34520
34574
|
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34521
34575
|
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34522
|
-
const buildHint = (tool, token, ruleId) => `oxlint matches plugin rules only by their full name, so \`${token}\` in your ${tool}-disable comment does not silence \`${ruleId}\` —
|
|
34576
|
+
const buildHint = (tool, token, ruleId) => `oxlint matches plugin rules only by their full name, so \`${token}\` in your ${tool}-disable comment does not silence \`${ruleId}\` — add \`${ruleId}\` to the comment (e.g. \`${token}, ${ruleId}\`).`;
|
|
34523
34577
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34524
34578
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34525
34579
|
const candidates = [{
|
|
@@ -34999,6 +35053,7 @@ const collectStringSet = (values) => {
|
|
|
34999
35053
|
if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
|
|
35000
35054
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35001
35055
|
};
|
|
35056
|
+
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35002
35057
|
/**
|
|
35003
35058
|
* Pre-compiles every stateful filter and returns a single
|
|
35004
35059
|
* `apply(diagnostic)` closure that runs (in order):
|
|
@@ -35032,11 +35087,28 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35032
35087
|
const compiledOverrides = compileIgnoreOverrides(userConfig);
|
|
35033
35088
|
const textComponentNames = collectStringSet(userConfig?.textComponents);
|
|
35034
35089
|
const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
|
|
35090
|
+
const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
|
|
35035
35091
|
const hasTextComponents = textComponentNames.size > 0;
|
|
35036
35092
|
const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
|
|
35093
|
+
const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
|
|
35037
35094
|
const fileLinesCache = /* @__PURE__ */ new Map();
|
|
35038
35095
|
const fileContextCache = /* @__PURE__ */ new Map();
|
|
35039
35096
|
const libraryFileCache = /* @__PURE__ */ new Map();
|
|
35097
|
+
const suppressions = /* @__PURE__ */ new Map();
|
|
35098
|
+
const suppress = (diagnostic, source) => {
|
|
35099
|
+
const { ruleKey } = getDiagnosticRuleIdentity(diagnostic);
|
|
35100
|
+
const suppressionKey = `${ruleKey}\u0000${source}`;
|
|
35101
|
+
const existing = suppressions.get(suppressionKey);
|
|
35102
|
+
suppressions.set(suppressionKey, existing ? {
|
|
35103
|
+
...existing,
|
|
35104
|
+
count: existing.count + 1
|
|
35105
|
+
} : {
|
|
35106
|
+
rule: ruleKey,
|
|
35107
|
+
source,
|
|
35108
|
+
count: 1
|
|
35109
|
+
});
|
|
35110
|
+
return null;
|
|
35111
|
+
};
|
|
35040
35112
|
const isLibraryFile = (filePath) => {
|
|
35041
35113
|
let cached = libraryFileCache.get(filePath);
|
|
35042
35114
|
if (cached === void 0) {
|
|
@@ -35085,50 +35157,60 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35085
35157
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35086
35158
|
return false;
|
|
35087
35159
|
};
|
|
35088
|
-
|
|
35089
|
-
if (
|
|
35090
|
-
|
|
35091
|
-
|
|
35092
|
-
|
|
35093
|
-
|
|
35094
|
-
|
|
35095
|
-
|
|
35096
|
-
|
|
35097
|
-
|
|
35098
|
-
|
|
35099
|
-
|
|
35100
|
-
if (
|
|
35101
|
-
|
|
35102
|
-
|
|
35103
|
-
|
|
35104
|
-
|
|
35105
|
-
|
|
35106
|
-
|
|
35107
|
-
|
|
35108
|
-
|
|
35109
|
-
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35110
|
-
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
|
|
35111
|
-
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35112
|
-
}
|
|
35113
|
-
if (respectInlineDisables && current.line > 0) {
|
|
35114
|
-
const lines = getFileLines(current.filePath);
|
|
35115
|
-
if (lines) {
|
|
35116
|
-
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35117
|
-
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35118
|
-
if (evaluation.isSuppressed) return null;
|
|
35119
|
-
if (evaluation.nearMissHint) current = {
|
|
35120
|
-
...current,
|
|
35121
|
-
suppressionHint: evaluation.nearMissHint
|
|
35122
|
-
};
|
|
35160
|
+
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
35161
|
+
if (!hasRuntimeGlobals) return false;
|
|
35162
|
+
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
35163
|
+
const identifierMatch = JSX_NO_UNDEF_IDENTIFIER_PATTERN.exec(diagnostic.message);
|
|
35164
|
+
return identifierMatch !== null && runtimeGlobalNames.has(identifierMatch[1]);
|
|
35165
|
+
};
|
|
35166
|
+
return {
|
|
35167
|
+
apply: (diagnostic) => {
|
|
35168
|
+
if (shouldAutoSuppress(diagnostic)) return null;
|
|
35169
|
+
let current = diagnostic;
|
|
35170
|
+
let explicitSeverityOverride;
|
|
35171
|
+
let explicitRuleOverride;
|
|
35172
|
+
if (severityControls) {
|
|
35173
|
+
const { ruleKey, category } = getDiagnosticRuleIdentity(current);
|
|
35174
|
+
explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey }, severityControls);
|
|
35175
|
+
explicitSeverityOverride = resolveRuleSeverityOverride({
|
|
35176
|
+
ruleKey,
|
|
35177
|
+
category
|
|
35178
|
+
}, severityControls);
|
|
35179
|
+
if (explicitSeverityOverride === "off") return suppress(current, "config");
|
|
35180
|
+
if (explicitSeverityOverride !== void 0) current = restampSeverity(current, explicitSeverityOverride);
|
|
35123
35181
|
}
|
|
35124
|
-
|
|
35125
|
-
|
|
35126
|
-
|
|
35127
|
-
|
|
35128
|
-
|
|
35129
|
-
|
|
35130
|
-
|
|
35131
|
-
|
|
35182
|
+
if (explicitRuleOverride === void 0) {
|
|
35183
|
+
if (isAppOnlyRule(`${current.plugin}/${current.rule}`) && isLibraryFile(current.filePath)) return null;
|
|
35184
|
+
}
|
|
35185
|
+
if (!showWarnings && current.severity === "warning" && explicitSeverityOverride !== "warn") return null;
|
|
35186
|
+
if (userConfig) {
|
|
35187
|
+
if (isRuleIgnored(`${current.plugin}/${current.rule}`)) return suppress(current, "config");
|
|
35188
|
+
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35189
|
+
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return suppress(current, "override");
|
|
35190
|
+
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35191
|
+
if (isJsxNoUndefSuppressedByConfig(current)) return null;
|
|
35192
|
+
}
|
|
35193
|
+
if (respectInlineDisables && current.line > 0) {
|
|
35194
|
+
const lines = getFileLines(current.filePath);
|
|
35195
|
+
if (lines) {
|
|
35196
|
+
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35197
|
+
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35198
|
+
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
35199
|
+
if (evaluation.nearMissHint) current = {
|
|
35200
|
+
...current,
|
|
35201
|
+
suppressionHint: evaluation.nearMissHint
|
|
35202
|
+
};
|
|
35203
|
+
}
|
|
35204
|
+
}
|
|
35205
|
+
const fileContext = getFileContext(current.filePath);
|
|
35206
|
+
if (fileContext !== "production") current = {
|
|
35207
|
+
...current,
|
|
35208
|
+
fileContext
|
|
35209
|
+
};
|
|
35210
|
+
return current;
|
|
35211
|
+
},
|
|
35212
|
+
summarizeSuppressions: () => [...suppressions.values()]
|
|
35213
|
+
};
|
|
35132
35214
|
};
|
|
35133
35215
|
const OxlintUnavailableKind = Literals(["binary-not-found", "native-binding-missing"]);
|
|
35134
35216
|
var OxlintUnavailable = class extends TaggedErrorClass()("OxlintUnavailable", {
|
|
@@ -35432,13 +35514,7 @@ var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { de
|
|
|
35432
35514
|
* without recompiling react-doctor. Tests override via
|
|
35433
35515
|
* `Layer.succeed(SupplyChainOverlapTimeoutMs, ...)`.
|
|
35434
35516
|
*/
|
|
35435
|
-
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => {
|
|
35436
|
-
const raw = process.env["REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS"];
|
|
35437
|
-
if (raw === void 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35438
|
-
const parsed = Number(raw);
|
|
35439
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35440
|
-
return parsed;
|
|
35441
|
-
} }) {};
|
|
35517
|
+
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS", SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS) }) {};
|
|
35442
35518
|
/**
|
|
35443
35519
|
* Hard cap on combined stdout+stderr bytes per oxlint batch. The
|
|
35444
35520
|
* subprocess gets SIGKILL'd if it produces more; the recovery path
|
|
@@ -35586,6 +35662,7 @@ const STRING_ARRAY_FIELD_NAMES = [
|
|
|
35586
35662
|
"projects",
|
|
35587
35663
|
"textComponents",
|
|
35588
35664
|
"rawTextWrapperComponents",
|
|
35665
|
+
"runtimeGlobals",
|
|
35589
35666
|
"serverAuthFunctionNames"
|
|
35590
35667
|
];
|
|
35591
35668
|
const SURFACE_CONTROL_FIELD_NAMES = [
|
|
@@ -36722,14 +36799,17 @@ const checkReactServerComponentsAdvisory = (scanDirectory, project) => {
|
|
|
36722
36799
|
return diagnostics;
|
|
36723
36800
|
};
|
|
36724
36801
|
const REDUCED_MOTION_GREP_PATTERN = "prefers-reduced-motion|useReducedMotion|MotionConfig|reducedMotion";
|
|
36725
|
-
const
|
|
36726
|
-
|
|
36727
|
-
"
|
|
36728
|
-
"
|
|
36729
|
-
"
|
|
36730
|
-
"
|
|
36731
|
-
"
|
|
36732
|
-
|
|
36802
|
+
const REDUCED_MOTION_PATTERN = new RegExp(REDUCED_MOTION_GREP_PATTERN);
|
|
36803
|
+
const REDUCED_MOTION_FILE_EXTENSIONS = new Set([
|
|
36804
|
+
".ts",
|
|
36805
|
+
".tsx",
|
|
36806
|
+
".js",
|
|
36807
|
+
".jsx",
|
|
36808
|
+
".css",
|
|
36809
|
+
".scss"
|
|
36810
|
+
]);
|
|
36811
|
+
const REDUCED_MOTION_FILE_GLOBS = [...REDUCED_MOTION_FILE_EXTENSIONS].map((extension) => `*${extension}`);
|
|
36812
|
+
const GIT_GREP_NO_MATCH_STATUS = 1;
|
|
36733
36813
|
const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
|
|
36734
36814
|
filePath: "package.json",
|
|
36735
36815
|
plugin: "react-doctor",
|
|
@@ -36741,6 +36821,19 @@ const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
|
|
|
36741
36821
|
column: 0,
|
|
36742
36822
|
category: "Accessibility"
|
|
36743
36823
|
};
|
|
36824
|
+
const hasReducedMotionHandlingViaFilesystem = (rootDirectory) => {
|
|
36825
|
+
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
|
|
36826
|
+
if (!REDUCED_MOTION_FILE_EXTENSIONS.has(Path.extname(name))) continue;
|
|
36827
|
+
let content;
|
|
36828
|
+
try {
|
|
36829
|
+
content = NFS.readFileSync(absolutePath, "utf-8");
|
|
36830
|
+
} catch {
|
|
36831
|
+
continue;
|
|
36832
|
+
}
|
|
36833
|
+
if (REDUCED_MOTION_PATTERN.test(content)) return true;
|
|
36834
|
+
}
|
|
36835
|
+
return false;
|
|
36836
|
+
};
|
|
36744
36837
|
const checkReducedMotion = (rootDirectory) => {
|
|
36745
36838
|
const packageJsonPath = Path.join(rootDirectory, "package.json");
|
|
36746
36839
|
if (!isFile(packageJsonPath)) return [];
|
|
@@ -36759,22 +36852,21 @@ const checkReducedMotion = (rootDirectory) => {
|
|
|
36759
36852
|
const result = spawnSync("git", [
|
|
36760
36853
|
"grep",
|
|
36761
36854
|
"--untracked",
|
|
36762
|
-
"-
|
|
36763
|
-
"-E",
|
|
36855
|
+
"-lE",
|
|
36764
36856
|
REDUCED_MOTION_GREP_PATTERN,
|
|
36765
36857
|
"--",
|
|
36766
36858
|
...REDUCED_MOTION_FILE_GLOBS
|
|
36767
36859
|
], {
|
|
36768
36860
|
cwd: rootDirectory,
|
|
36861
|
+
encoding: "utf-8",
|
|
36769
36862
|
stdio: [
|
|
36770
36863
|
"ignore",
|
|
36771
36864
|
"pipe",
|
|
36772
36865
|
"pipe"
|
|
36773
36866
|
]
|
|
36774
36867
|
});
|
|
36775
|
-
if (result.error) return [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
36776
|
-
|
|
36777
|
-
return [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
36868
|
+
if (!(!result.error && (result.status === 0 || result.status === GIT_GREP_NO_MATCH_STATUS))) return hasReducedMotionHandlingViaFilesystem(rootDirectory) ? [] : [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
36869
|
+
return result.status === 0 && result.stdout.split("\n").some((filePath) => filePath.length > 0 && !hasIgnoredPathSegment(filePath)) ? [] : [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
36778
36870
|
};
|
|
36779
36871
|
const buildSecurityScanDiagnostic = (finding, entry, relativePath) => ({
|
|
36780
36872
|
filePath: relativePath,
|
|
@@ -37670,6 +37762,7 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
|
|
|
37670
37762
|
return true;
|
|
37671
37763
|
};
|
|
37672
37764
|
const filterDiagnosticsForSurface = (diagnostics, surface, config) => diagnostics.filter((diagnostic) => isDiagnosticOnSurface(diagnostic, surface, config));
|
|
37765
|
+
const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs - Date.now(), 0);
|
|
37673
37766
|
/**
|
|
37674
37767
|
* Budget for the dead-code phase, scaled to the work. deslop's graph build is
|
|
37675
37768
|
* CPU-bound and roughly linear in file count, so a fixed 120s cap is too tight
|
|
@@ -37688,53 +37781,6 @@ const resolveDeadCodeTimeout = (input) => {
|
|
|
37688
37781
|
phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
|
|
37689
37782
|
};
|
|
37690
37783
|
};
|
|
37691
|
-
const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
|
|
37692
|
-
const entries = [];
|
|
37693
|
-
for (const relativePath of relativePaths) {
|
|
37694
|
-
const absolutePath = Path.resolve(rootDirectory, relativePath);
|
|
37695
|
-
const sizeBytes = statSourceFileSize(absolutePath);
|
|
37696
|
-
if (isLargeMinifiedFile(absolutePath, sizeBytes)) continue;
|
|
37697
|
-
entries.push({
|
|
37698
|
-
path: relativePath,
|
|
37699
|
-
sizeBytes: sizeBytes ?? 0
|
|
37700
|
-
});
|
|
37701
|
-
}
|
|
37702
|
-
return entries;
|
|
37703
|
-
};
|
|
37704
|
-
const listSourceFilesViaGit = (rootDirectory) => {
|
|
37705
|
-
const result = spawnSync("git", [
|
|
37706
|
-
"ls-files",
|
|
37707
|
-
"-z",
|
|
37708
|
-
"--cached",
|
|
37709
|
-
"--others",
|
|
37710
|
-
"--exclude-standard"
|
|
37711
|
-
], {
|
|
37712
|
-
cwd: rootDirectory,
|
|
37713
|
-
encoding: "utf-8",
|
|
37714
|
-
maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES
|
|
37715
|
-
});
|
|
37716
|
-
if (result.error || result.status !== 0) return null;
|
|
37717
|
-
return result.stdout.split("\0").filter((filePath) => filePath.length > 0 && isLintableSourceFile(filePath));
|
|
37718
|
-
};
|
|
37719
|
-
const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
37720
|
-
const filePaths = [];
|
|
37721
|
-
const stack = [rootDirectory];
|
|
37722
|
-
while (stack.length > 0) {
|
|
37723
|
-
const currentDirectory = stack.pop();
|
|
37724
|
-
const entries = readDirectoryEntries(currentDirectory);
|
|
37725
|
-
for (const entry of entries) {
|
|
37726
|
-
const absolutePath = Path.join(currentDirectory, entry.name);
|
|
37727
|
-
if (entry.isDirectory()) {
|
|
37728
|
-
if (!entry.name.startsWith(".") && !IGNORED_DIRECTORIES.has(entry.name)) stack.push(absolutePath);
|
|
37729
|
-
continue;
|
|
37730
|
-
}
|
|
37731
|
-
if (entry.isFile() && isLintableSourceFile(entry.name)) filePaths.push(Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/"));
|
|
37732
|
-
}
|
|
37733
|
-
}
|
|
37734
|
-
return filePaths;
|
|
37735
|
-
};
|
|
37736
|
-
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory));
|
|
37737
|
-
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
37738
37784
|
const resolveLintIncludePaths = (rootDirectory, userConfig, project) => {
|
|
37739
37785
|
if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
|
|
37740
37786
|
const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
|
|
@@ -37779,7 +37825,6 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
|
37779
37825
|
return yield* tryPromise({
|
|
37780
37826
|
try: (signal) => checkDeadCode({
|
|
37781
37827
|
rootDirectory: input.rootDirectory,
|
|
37782
|
-
userConfig: input.userConfig,
|
|
37783
37828
|
parseConcurrency: input.parseConcurrency,
|
|
37784
37829
|
workerTimeoutMs: input.workerTimeoutMs,
|
|
37785
37830
|
abortSignal: signal
|
|
@@ -37902,6 +37947,11 @@ const isSafeGitRevision = (candidate) => {
|
|
|
37902
37947
|
if (candidate.includes("..") || candidate.includes("@{")) return false;
|
|
37903
37948
|
return SAFE_GIT_REVISION_PATTERN.test(candidate);
|
|
37904
37949
|
};
|
|
37950
|
+
const resolveSpawnArgsLengthCap = () => {
|
|
37951
|
+
if (process.platform === "win32") return SPAWN_ARGS_MAX_LENGTH_CHARS;
|
|
37952
|
+
if (process.platform === "darwin") return SPAWN_ARGS_MAX_LENGTH_CHARS_DARWIN;
|
|
37953
|
+
return SPAWN_ARGS_MAX_LENGTH_CHARS_POSIX;
|
|
37954
|
+
};
|
|
37905
37955
|
/**
|
|
37906
37956
|
* Splits a git revision range into its endpoints: three-dot `A...B`
|
|
37907
37957
|
* (symmetric, merge-base) or two-dot `A..B` (direct). Returns `null`
|
|
@@ -37986,7 +38036,8 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37986
38036
|
return scoped(gen(function* () {
|
|
37987
38037
|
if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
|
|
37988
38038
|
const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
|
|
37989
|
-
|
|
38039
|
+
const spawnArgsLengthCap = resolveSpawnArgsLengthCap();
|
|
38040
|
+
if (argvLengthChars > spawnArgsLengthCap) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${spawnArgsLengthCap})`);
|
|
37990
38041
|
const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
|
|
37991
38042
|
cwd: input.directory,
|
|
37992
38043
|
env: input.env,
|
|
@@ -38155,6 +38206,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38155
38206
|
if (resolvedCurrentBranch === null && explicitBaseBranch === void 0) return null;
|
|
38156
38207
|
const baseBranch = explicitBaseBranch ?? (yield* defaultBranch(directory));
|
|
38157
38208
|
if (baseBranch === null) return null;
|
|
38209
|
+
if (!isSafeGitRevision(baseBranch)) return null;
|
|
38158
38210
|
if (explicitBaseBranch !== void 0) {
|
|
38159
38211
|
if (!(yield* branchExists(directory, explicitBaseBranch))) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchMissing({ branch: explicitBaseBranch }) }));
|
|
38160
38212
|
}
|
|
@@ -38425,12 +38477,12 @@ const findFilesWithDisableDirectivesViaGit = async (rootDirectory, includePaths)
|
|
|
38425
38477
|
return null;
|
|
38426
38478
|
}
|
|
38427
38479
|
if (grepResult === null) return null;
|
|
38428
|
-
return grepResult.stdout.split("\n").filter((filePath) => filePath.length > 0 && isLintableSourceFile(filePath));
|
|
38480
|
+
return grepResult.stdout.split("\n").filter((filePath) => filePath.length > 0 && isLintableSourceFile(filePath) && !hasIgnoredPathSegment(filePath));
|
|
38429
38481
|
};
|
|
38430
38482
|
const findFilesWithDisableDirectivesViaFilesystem = (rootDirectory, includePaths) => {
|
|
38431
38483
|
const matches = [];
|
|
38432
38484
|
const checkFile = (relativePath) => {
|
|
38433
|
-
if (!isLintableSourceFile(relativePath)) return;
|
|
38485
|
+
if (!isLintableSourceFile(relativePath) || hasIgnoredPathSegment(relativePath)) return;
|
|
38434
38486
|
const absolutePath = Path.join(rootDirectory, relativePath);
|
|
38435
38487
|
let content;
|
|
38436
38488
|
try {
|
|
@@ -38444,22 +38496,7 @@ const findFilesWithDisableDirectivesViaFilesystem = (rootDirectory, includePaths
|
|
|
38444
38496
|
for (const candidate of includePaths) checkFile(candidate);
|
|
38445
38497
|
return matches;
|
|
38446
38498
|
}
|
|
38447
|
-
const
|
|
38448
|
-
while (stack.length > 0) {
|
|
38449
|
-
const current = stack.pop();
|
|
38450
|
-
if (current === void 0) continue;
|
|
38451
|
-
const entries = readDirectoryEntries(current);
|
|
38452
|
-
for (const entry of entries) {
|
|
38453
|
-
if (entry.isDirectory()) {
|
|
38454
|
-
if (entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
38455
|
-
stack.push(Path.join(current, entry.name));
|
|
38456
|
-
continue;
|
|
38457
|
-
}
|
|
38458
|
-
if (!entry.isFile()) continue;
|
|
38459
|
-
const absolute = Path.join(current, entry.name);
|
|
38460
|
-
checkFile(Path.relative(rootDirectory, absolute));
|
|
38461
|
-
}
|
|
38462
|
-
}
|
|
38499
|
+
for (const { absolutePath } of walkSourceTreeFiles(rootDirectory)) checkFile(Path.relative(rootDirectory, absolutePath));
|
|
38463
38500
|
return matches;
|
|
38464
38501
|
};
|
|
38465
38502
|
const findFilesWithDisableDirectives = async (rootDirectory, includePaths) => await findFilesWithDisableDirectivesViaGit(rootDirectory, includePaths) ?? findFilesWithDisableDirectivesViaFilesystem(rootDirectory, includePaths);
|
|
@@ -38505,7 +38542,7 @@ const normalizeConfigForHash = (config) => {
|
|
|
38505
38542
|
if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
|
|
38506
38543
|
return clone;
|
|
38507
38544
|
};
|
|
38508
|
-
const computeRulesetHash = (input) => crypto.createHash("sha1").update(JSON.stringify(normalizeConfigForHash(input.config))).update("\0").update([...input.toolchainVersions].join("\0")).update("\0").update([...input.ignorePatterns].join("\n")).update("
|
|
38545
|
+
const computeRulesetHash = (input) => crypto.createHash("sha1").update(JSON.stringify(normalizeConfigForHash(input.config))).update("\0").update([...input.toolchainVersions].join("\0")).update("\0").update([...input.ignorePatterns].join("\n")).update("\0").update(input.tsconfigContent ?? "").digest("hex");
|
|
38509
38546
|
/**
|
|
38510
38547
|
* Loads a plugin module via the local require resolver and extracts
|
|
38511
38548
|
* `(name, ruleNames)` from either `module.exports.meta + rules` or
|
|
@@ -38676,12 +38713,12 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38676
38713
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38677
38714
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
38678
38715
|
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledBy)) continue;
|
|
38679
|
-
const
|
|
38716
|
+
const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
|
|
38717
|
+
if (rule.defaultEnabled === false && explicitRuleOverride === void 0) continue;
|
|
38718
|
+
const severity = resolveRuleSeverityOverride({
|
|
38680
38719
|
ruleKey: registryEntry.key,
|
|
38681
38720
|
category: rule.category
|
|
38682
|
-
}, severityControls);
|
|
38683
|
-
if (rule.defaultEnabled === false && explicitSeverity === void 0) continue;
|
|
38684
|
-
const severity = explicitSeverity ?? resolveCompilerCleanupBucketSeverity(registryEntry.key, severityControls) ?? rule.severity;
|
|
38721
|
+
}, severityControls) ?? resolveCompilerCleanupBucketSeverity(registryEntry.key, severityControls) ?? rule.severity;
|
|
38685
38722
|
if (severity === "off") continue;
|
|
38686
38723
|
enabledReactDoctorRules[registryEntry.key] = severity;
|
|
38687
38724
|
}
|
|
@@ -38783,24 +38820,38 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38783
38820
|
updatedAtMs: Date.now(),
|
|
38784
38821
|
files: Object.fromEntries(cappedEntries)
|
|
38785
38822
|
};
|
|
38786
|
-
const keptHashes = Object.entries(rulesets).sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8).map(([hash]) => hash);
|
|
38787
|
-
const prunedRulesets = {};
|
|
38788
|
-
for (const hash of keptHashes) prunedRulesets[hash] = rulesets[hash];
|
|
38789
38823
|
atomicWriteJson(cacheFilePath, {
|
|
38790
38824
|
version: 1,
|
|
38791
|
-
rulesets:
|
|
38825
|
+
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
38792
38826
|
});
|
|
38793
38827
|
}
|
|
38794
38828
|
};
|
|
38795
38829
|
};
|
|
38796
38830
|
const bundledRequire = createRequire(import.meta.url);
|
|
38831
|
+
const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
|
|
38832
|
+
const resolveChildNodeVersion = (nodeBinaryPath) => {
|
|
38833
|
+
if (nodeBinaryPath === process.execPath) return process.version;
|
|
38834
|
+
const cachedVersion = childNodeVersionByBinaryPath.get(nodeBinaryPath);
|
|
38835
|
+
if (cachedVersion !== void 0) return cachedVersion;
|
|
38836
|
+
let version;
|
|
38837
|
+
try {
|
|
38838
|
+
version = execFileSync(nodeBinaryPath, ["--version"], {
|
|
38839
|
+
encoding: "utf8",
|
|
38840
|
+
timeout: NODE_VERSION_PROBE_TIMEOUT_MS
|
|
38841
|
+
}).trim();
|
|
38842
|
+
} catch {
|
|
38843
|
+
version = `unknown:${nodeBinaryPath}`;
|
|
38844
|
+
}
|
|
38845
|
+
childNodeVersionByBinaryPath.set(nodeBinaryPath, version);
|
|
38846
|
+
return version;
|
|
38847
|
+
};
|
|
38797
38848
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
38798
38849
|
"oxlint/package.json",
|
|
38799
38850
|
"oxlint-plugin-react-doctor/package.json",
|
|
38800
38851
|
"eslint-plugin-react-hooks/package.json"
|
|
38801
38852
|
];
|
|
38802
|
-
const resolveOxlintToolchainVersions = () => {
|
|
38803
|
-
const versions = [`node=${
|
|
38853
|
+
const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
|
|
38854
|
+
const versions = [`node=${resolveChildNodeVersion(nodeBinaryPath)}`];
|
|
38804
38855
|
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
38805
38856
|
const packageJson = bundledRequire(specifier);
|
|
38806
38857
|
const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
@@ -39334,11 +39385,12 @@ const REACT_COMPILER_TITLE = "React Compiler can't optimize this";
|
|
|
39334
39385
|
const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
|
|
39335
39386
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
39336
39387
|
const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
|
|
39388
|
+
const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
|
|
39337
39389
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
39338
|
-
const buildReactCompilerMessage = (reasonSummary) => {
|
|
39390
|
+
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
39339
39391
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
39340
|
-
if (!normalizedSummary) return
|
|
39341
|
-
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${
|
|
39392
|
+
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
39393
|
+
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
39342
39394
|
};
|
|
39343
39395
|
const PLUGIN_CATEGORY_MAP = {
|
|
39344
39396
|
react: "Bugs",
|
|
@@ -39387,7 +39439,7 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
39387
39439
|
const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
|
|
39388
39440
|
const reasonDetail = reasonDetailLines.join("\n").trim();
|
|
39389
39441
|
return {
|
|
39390
|
-
message: buildReactCompilerMessage(reasonSummary.trim()),
|
|
39442
|
+
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
39391
39443
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
39392
39444
|
};
|
|
39393
39445
|
}
|
|
@@ -39648,28 +39700,46 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
39648
39700
|
* loop with a slimmer config in that case.
|
|
39649
39701
|
*/
|
|
39650
39702
|
const spawnLintBatches = async (input) => {
|
|
39651
|
-
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal } = input;
|
|
39703
|
+
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
|
|
39652
39704
|
const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
|
|
39653
39705
|
const totalFileCount = fileBatches.reduce((sum, batch) => sum + batch.length, 0);
|
|
39654
39706
|
const runBatchPass = async (concurrency) => {
|
|
39655
39707
|
const allDiagnostics = [];
|
|
39656
39708
|
const droppedFiles = [];
|
|
39709
|
+
const deadlineSkippedFiles = [];
|
|
39710
|
+
const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0;
|
|
39657
39711
|
let firstDropReason = null;
|
|
39658
|
-
const
|
|
39659
|
-
|
|
39712
|
+
const spawnLintBatch = async (batch, depth, batchState) => {
|
|
39713
|
+
if (isPastDeadline()) {
|
|
39714
|
+
deadlineSkippedFiles.push(...batch);
|
|
39715
|
+
batchState.deadlineSkippedFileCount += batch.length;
|
|
39716
|
+
return [];
|
|
39717
|
+
}
|
|
39660
39718
|
const batchArgs = [...baseArgs, ...batch];
|
|
39661
39719
|
try {
|
|
39662
39720
|
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
|
|
39663
39721
|
} catch (error) {
|
|
39664
39722
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
39665
|
-
|
|
39666
|
-
|
|
39723
|
+
if (isPastDeadline()) {
|
|
39724
|
+
deadlineSkippedFiles.push(...batch);
|
|
39725
|
+
batchState.deadlineSkippedFileCount += batch.length;
|
|
39726
|
+
return [];
|
|
39727
|
+
}
|
|
39728
|
+
batchState.deadlineMs ??= Date.now() + splitTotalBudgetMs;
|
|
39729
|
+
const isBudgetElapsed = Date.now() >= batchState.deadlineMs;
|
|
39730
|
+
const isDepthCapReached = depth >= splitMaxDepth;
|
|
39731
|
+
if (batch.length <= 1 || isBudgetElapsed || isDepthCapReached) {
|
|
39667
39732
|
droppedFiles.push(...batch);
|
|
39668
|
-
if (firstDropReason === null)
|
|
39733
|
+
if (firstDropReason === null) {
|
|
39734
|
+
let limitHint = "";
|
|
39735
|
+
if (isDepthCapReached) limitHint = ` (split depth cap of ${splitMaxDepth} levels reached)`;
|
|
39736
|
+
else if (isBudgetElapsed) limitHint = ` (split budget of ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s exhausted at depth ${depth})`;
|
|
39737
|
+
firstDropReason = batch.length > 1 ? `${error.message}${limitHint}` : error.message;
|
|
39738
|
+
}
|
|
39669
39739
|
return [];
|
|
39670
39740
|
}
|
|
39671
39741
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
39672
|
-
return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1)];
|
|
39742
|
+
return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1, batchState), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1, batchState)];
|
|
39673
39743
|
}
|
|
39674
39744
|
};
|
|
39675
39745
|
let startedFileCount = 0;
|
|
@@ -39685,9 +39755,17 @@ const spawnLintBatches = async (input) => {
|
|
|
39685
39755
|
progressTimer?.unref?.();
|
|
39686
39756
|
try {
|
|
39687
39757
|
const batchResults = await mapWithConcurrency(fileBatches, concurrency, async (batch) => {
|
|
39758
|
+
if (isPastDeadline()) {
|
|
39759
|
+
deadlineSkippedFiles.push(...batch);
|
|
39760
|
+
return [];
|
|
39761
|
+
}
|
|
39688
39762
|
startedFileCount += batch.length;
|
|
39689
|
-
const
|
|
39690
|
-
|
|
39763
|
+
const batchState = {
|
|
39764
|
+
deadlineMs: null,
|
|
39765
|
+
deadlineSkippedFileCount: 0
|
|
39766
|
+
};
|
|
39767
|
+
const batchDiagnostics = await spawnLintBatch(batch, 0, batchState);
|
|
39768
|
+
scannedFileCount += batch.length - batchState.deadlineSkippedFileCount;
|
|
39691
39769
|
if (onFileProgress) {
|
|
39692
39770
|
displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
|
|
39693
39771
|
onFileProgress(displayedFileCount, totalFileCount);
|
|
@@ -39698,12 +39776,13 @@ const spawnLintBatches = async (input) => {
|
|
|
39698
39776
|
} finally {
|
|
39699
39777
|
if (progressTimer !== null) clearInterval(progressTimer);
|
|
39700
39778
|
}
|
|
39701
|
-
|
|
39702
|
-
|
|
39703
|
-
|
|
39704
|
-
|
|
39705
|
-
|
|
39706
|
-
}
|
|
39779
|
+
const reportSkippedFiles = (skippedFiles, buildMessage) => {
|
|
39780
|
+
if (skippedFiles.length === 0 || !onPartialFailure) return;
|
|
39781
|
+
onPartialFailure(buildMessage(`${skippedFiles.slice(0, 3).join(", ")}${skippedFiles.length > 3 ? `, +${skippedFiles.length - 3} more` : ""}`));
|
|
39782
|
+
};
|
|
39783
|
+
const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
|
|
39784
|
+
reportSkippedFiles(droppedFiles, (fileListPreview) => `${droppedFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
|
|
39785
|
+
reportSkippedFiles(deadlineSkippedFiles, (fileListPreview) => `${deadlineSkippedFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
|
|
39707
39786
|
return allDiagnostics;
|
|
39708
39787
|
};
|
|
39709
39788
|
let diagnostics;
|
|
@@ -39760,7 +39839,14 @@ const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
|
39760
39839
|
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
39761
39840
|
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
39762
39841
|
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
39763
|
-
return Path.join(os.tmpdir(),
|
|
39842
|
+
return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
|
|
39843
|
+
};
|
|
39844
|
+
const resolveUserCacheScope = () => {
|
|
39845
|
+
try {
|
|
39846
|
+
return String(process.getuid?.() ?? os.userInfo().username);
|
|
39847
|
+
} catch {
|
|
39848
|
+
return "shared";
|
|
39849
|
+
}
|
|
39764
39850
|
};
|
|
39765
39851
|
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39766
39852
|
/**
|
|
@@ -39900,7 +39986,8 @@ const runOxlint = async (options) => {
|
|
|
39900
39986
|
spawnTimeoutMs,
|
|
39901
39987
|
outputMaxBytes,
|
|
39902
39988
|
concurrency: options.concurrency,
|
|
39903
|
-
signal: options.signal
|
|
39989
|
+
signal: options.signal,
|
|
39990
|
+
deadlineEpochMs: options.deadlineEpochMs
|
|
39904
39991
|
});
|
|
39905
39992
|
writeOxlintConfig(passConfigPath, buildConfigForPass({}));
|
|
39906
39993
|
try {
|
|
@@ -39928,7 +40015,7 @@ const runOxlint = async (options) => {
|
|
|
39928
40015
|
extendsPaths: [],
|
|
39929
40016
|
ruleSelection: "cacheable"
|
|
39930
40017
|
}),
|
|
39931
|
-
toolchainVersions: resolveOxlintToolchainVersions(),
|
|
40018
|
+
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
39932
40019
|
ignorePatterns: combinedPatterns,
|
|
39933
40020
|
tsconfigContent
|
|
39934
40021
|
});
|
|
@@ -39942,7 +40029,7 @@ const runOxlint = async (options) => {
|
|
|
39942
40029
|
missFiles.push(candidateFile);
|
|
39943
40030
|
continue;
|
|
39944
40031
|
}
|
|
39945
|
-
const cacheKey = `${candidateFile.replaceAll("\\", "/")}
|
|
40032
|
+
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
39946
40033
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
39947
40034
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
39948
40035
|
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
@@ -39999,7 +40086,8 @@ const runOxlint = async (options) => {
|
|
|
39999
40086
|
spawnTimeoutMs,
|
|
40000
40087
|
outputMaxBytes,
|
|
40001
40088
|
concurrency: options.concurrency,
|
|
40002
|
-
signal: options.signal
|
|
40089
|
+
signal: options.signal,
|
|
40090
|
+
deadlineEpochMs: options.deadlineEpochMs
|
|
40003
40091
|
});
|
|
40004
40092
|
writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
|
|
40005
40093
|
try {
|
|
@@ -40103,7 +40191,8 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40103
40191
|
outputMaxBytes,
|
|
40104
40192
|
concurrency,
|
|
40105
40193
|
signal,
|
|
40106
|
-
lintBatchOrdering
|
|
40194
|
+
lintBatchOrdering,
|
|
40195
|
+
deadlineEpochMs: input.deadlineEpochMs
|
|
40107
40196
|
}),
|
|
40108
40197
|
catch: ensureReactDoctorError
|
|
40109
40198
|
});
|
|
@@ -40258,7 +40347,7 @@ const ScoreApiResponseSchema = Struct({
|
|
|
40258
40347
|
})))
|
|
40259
40348
|
});
|
|
40260
40349
|
const parseScoreResult = (value) => getOrNull(decodeUnknownOption(ScoreApiResponseSchema)(value));
|
|
40261
|
-
const stripLocalFileFields = (diagnostics) => diagnostics.map(({ filePath: _filePath, fileContext: _fileContext, ...rest }) => rest);
|
|
40350
|
+
const stripLocalFileFields = (diagnostics) => diagnostics.map(({ filePath: _filePath, fileContext: _fileContext, fixGroupId: _fixGroupId, ...rest }) => rest);
|
|
40262
40351
|
const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
40263
40352
|
const describeFailure = (error) => {
|
|
40264
40353
|
if (isAbortError(error)) return `timed out after ${FETCH_TIMEOUT_MS / 1e3}s`;
|
|
@@ -40562,7 +40651,10 @@ const ALERT_SEVERITY_RANK = {
|
|
|
40562
40651
|
medium: 2,
|
|
40563
40652
|
low: 1
|
|
40564
40653
|
};
|
|
40565
|
-
const severityRank = (severity) =>
|
|
40654
|
+
const severityRank = (severity) => {
|
|
40655
|
+
const normalized = severity.toLowerCase();
|
|
40656
|
+
return Object.hasOwn(ALERT_SEVERITY_RANK, normalized) ? ALERT_SEVERITY_RANK[normalized] : 0;
|
|
40657
|
+
};
|
|
40566
40658
|
const displaySeverity = (severity) => {
|
|
40567
40659
|
const normalized = sanitizeTerminalText(severity.toLowerCase());
|
|
40568
40660
|
return normalized === "middle" ? "medium" : normalized;
|
|
@@ -40579,7 +40671,7 @@ const ALERT_TYPE_LABELS = {
|
|
|
40579
40671
|
typosquat: "possible typosquat"
|
|
40580
40672
|
};
|
|
40581
40673
|
const humanizeAlertType = (type) => type.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase().trim();
|
|
40582
|
-
const friendlyAlertType = (type) => ALERT_TYPE_LABELS[type]
|
|
40674
|
+
const friendlyAlertType = (type) => Object.hasOwn(ALERT_TYPE_LABELS, type) ? ALERT_TYPE_LABELS[type] : sanitizeTerminalText(humanizeAlertType(type));
|
|
40583
40675
|
const summarizeAlertNote = (note) => {
|
|
40584
40676
|
const collapsed = sanitizeTerminalText(note.replace(/\s+/g, " ").trim());
|
|
40585
40677
|
const firstSentence = collapsed.split(/(?<=\.)\s/)[0] || collapsed;
|
|
@@ -40674,7 +40766,10 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
40674
40766
|
* outage.
|
|
40675
40767
|
*/
|
|
40676
40768
|
var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
|
|
40677
|
-
static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(
|
|
40769
|
+
static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(flatMap$2(SupplyChainOverlapTimeoutMs, (totalTimeoutMs) => checkSupplyChain({
|
|
40770
|
+
...input,
|
|
40771
|
+
totalTimeoutMs
|
|
40772
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
|
|
40678
40773
|
static layerOf = (diagnostics) => succeed$3(SupplyChain, SupplyChain.of({ run: () => fromIterable$1(diagnostics) }));
|
|
40679
40774
|
};
|
|
40680
40775
|
const getObjectProperty = (value, propertyName) => {
|
|
@@ -40822,10 +40917,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40822
40917
|
...checkExpoProject(scanDirectory, project),
|
|
40823
40918
|
...checkReactNativeProject(scanDirectory, project)
|
|
40824
40919
|
])));
|
|
40825
|
-
const
|
|
40920
|
+
const securityScanFailedRef = yield* make$13(false);
|
|
40921
|
+
const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
|
|
40826
40922
|
project,
|
|
40827
40923
|
ignoredTags: input.ignoredTags
|
|
40828
|
-
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)))))).pipe(withSpan("SecurityScan.run")));
|
|
40924
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
|
|
40829
40925
|
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
40830
40926
|
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
40831
40927
|
const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
|
|
@@ -40856,6 +40952,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40856
40952
|
const deadCodePhaseTimeoutMs = yield* DeadCodePhaseTimeoutMs;
|
|
40857
40953
|
const resolveDeadCodePhaseTimeoutMs = (scaledPhaseTimeoutMs) => deadCodePhaseTimeoutMs === 15e4 ? scaledPhaseTimeoutMs : deadCodePhaseTimeoutMs;
|
|
40858
40954
|
const workerCountSuffix = scanConcurrency > 1 ? ` ${highlighter.dim(`[~${scanConcurrency} workers]`)}` : "";
|
|
40955
|
+
const capToDeadline = (phaseTimeoutMs) => input.deadlineEpochMs === void 0 ? phaseTimeoutMs : Math.min(phaseTimeoutMs, remainingDeadlineBudgetMs(input.deadlineEpochMs));
|
|
40859
40956
|
const shouldRunDeadCode = input.runDeadCode && !isDiffMode && (showWarnings || deadCodeMaySurfaceWhenWarningsHidden(resolvedConfig.config));
|
|
40860
40957
|
const deadCodeOverlapMode = yield* DeadCodeOverlap;
|
|
40861
40958
|
const shouldOverlapDeadCode = shouldRunDeadCode && deadCodeOverlapMode === "on";
|
|
@@ -40863,7 +40960,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40863
40960
|
const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
|
|
40864
40961
|
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
40865
40962
|
rootDirectory: scanDirectory,
|
|
40866
|
-
userConfig: resolvedConfig.config,
|
|
40867
40963
|
parseConcurrency: deadCodeParseConcurrency,
|
|
40868
40964
|
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
40869
40965
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
@@ -40886,7 +40982,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40886
40982
|
});
|
|
40887
40983
|
const deadCodeFiber = shouldOverlapDeadCode ? yield* forkChild(buildCollectDeadCode({
|
|
40888
40984
|
workerTimeoutMs: overlapDeadCodeTimeout.workerTimeoutMs,
|
|
40889
|
-
phaseTimeoutMs: resolveDeadCodePhaseTimeoutMs(overlapDeadCodeTimeout.phaseTimeoutMs)
|
|
40985
|
+
phaseTimeoutMs: capToDeadline(resolveDeadCodePhaseTimeoutMs(overlapDeadCodeTimeout.phaseTimeoutMs))
|
|
40890
40986
|
})) : null;
|
|
40891
40987
|
const scanProgress = yield* progressService.start("Scanning...");
|
|
40892
40988
|
const scanStartTime = Date.now();
|
|
@@ -40911,7 +41007,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40911
41007
|
onCacheStats: (cacheHitFileCount, totalConsideredFileCount) => {
|
|
40912
41008
|
lintCacheHitFileCount = cacheHitFileCount;
|
|
40913
41009
|
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
40914
|
-
}
|
|
41010
|
+
},
|
|
41011
|
+
deadlineEpochMs: input.deadlineEpochMs
|
|
40915
41012
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
40916
41013
|
yield* set(lintFailure, {
|
|
40917
41014
|
didFail: true,
|
|
@@ -40938,7 +41035,13 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40938
41035
|
let deadCodeCollected = [];
|
|
40939
41036
|
if (lintFailureState.didFail) {
|
|
40940
41037
|
if (deadCodeFiber !== null) yield* interrupt(deadCodeFiber);
|
|
40941
|
-
} else if (shouldRunDeadCode) {
|
|
41038
|
+
} else if (shouldRunDeadCode) if (input.deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(input.deadlineEpochMs) === 0) {
|
|
41039
|
+
if (deadCodeFiber !== null) yield* interrupt(deadCodeFiber);
|
|
41040
|
+
yield* set(deadCodeFailure, {
|
|
41041
|
+
didFail: true,
|
|
41042
|
+
reason: "Dead-code analysis skipped — max scan duration reached."
|
|
41043
|
+
});
|
|
41044
|
+
} else {
|
|
40942
41045
|
yield* scanProgress.update(`Scanned ${scannedFilesLabel}, analyzing dead code...`);
|
|
40943
41046
|
const sequentialDeadCodeTimeout = resolveDeadCodeTimeout({
|
|
40944
41047
|
sourceFileCount: totalFileCount,
|
|
@@ -40947,7 +41050,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40947
41050
|
});
|
|
40948
41051
|
deadCodeCollected = deadCodeFiber !== null ? yield* join(deadCodeFiber) : yield* buildCollectDeadCode({
|
|
40949
41052
|
workerTimeoutMs: sequentialDeadCodeTimeout.workerTimeoutMs,
|
|
40950
|
-
phaseTimeoutMs: resolveDeadCodePhaseTimeoutMs(sequentialDeadCodeTimeout.phaseTimeoutMs)
|
|
41053
|
+
phaseTimeoutMs: capToDeadline(resolveDeadCodePhaseTimeoutMs(sequentialDeadCodeTimeout.phaseTimeoutMs))
|
|
40951
41054
|
});
|
|
40952
41055
|
}
|
|
40953
41056
|
const deadCodeFailureState = lintFailureState.didFail ? {
|
|
@@ -40985,12 +41088,13 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40985
41088
|
};
|
|
40986
41089
|
const scoreSurface = input.scoreSurface ?? "score";
|
|
40987
41090
|
const scoreDiagnostics = filterDiagnosticsForSurface([...finalDiagnostics], scoreSurface, resolvedConfig.config);
|
|
40988
|
-
const score = lintFailureState.didFail ? null : yield* scoreService.compute({
|
|
41091
|
+
const score = lintFailureState.didFail || deadCodeFailureState.didFail ? null : yield* scoreService.compute({
|
|
40989
41092
|
diagnostics: scoreDiagnostics,
|
|
40990
41093
|
isCi: input.isCi,
|
|
40991
41094
|
metadata: scoreMetadata
|
|
40992
41095
|
});
|
|
40993
41096
|
const lintPartialFailures = yield* get$2(partialFailuresRef);
|
|
41097
|
+
const securityScanFailed = yield* get$2(securityScanFailedRef);
|
|
40994
41098
|
return {
|
|
40995
41099
|
project,
|
|
40996
41100
|
userConfig: resolvedConfig.config,
|
|
@@ -41011,8 +41115,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41011
41115
|
scanElapsedMilliseconds,
|
|
41012
41116
|
scanConcurrency,
|
|
41013
41117
|
supplyChainOverlapTimedOut: supplyChainResult.timedOut,
|
|
41118
|
+
securityScanFailed,
|
|
41014
41119
|
lintCacheHitFileCount,
|
|
41015
|
-
lintCacheTotalFileCount
|
|
41120
|
+
lintCacheTotalFileCount,
|
|
41121
|
+
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
41016
41122
|
};
|
|
41017
41123
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
41018
41124
|
"inspect.directory": input.directory,
|
|
@@ -41351,6 +41457,7 @@ const buildJsonReport = (input) => {
|
|
|
41351
41457
|
return {
|
|
41352
41458
|
schemaVersion: 1,
|
|
41353
41459
|
mode: input.mode,
|
|
41460
|
+
...input.baselineDegraded ? { baselineDegraded: true } : {},
|
|
41354
41461
|
...shared
|
|
41355
41462
|
};
|
|
41356
41463
|
};
|
|
@@ -41651,4 +41758,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
41651
41758
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
41652
41759
|
|
|
41653
41760
|
//# sourceMappingURL=index.js.map
|
|
41654
|
-
//# debugId=
|
|
41761
|
+
//# debugId=62cf4740-7938-5db0-8b80-dc81b60af36a
|