react-doctor 0.5.8-dev.970babc → 0.5.8-dev.988ce57
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/LICENSE +14 -1
- package/README.md +9 -3
- package/dist/cli.js +9969 -1862
- package/dist/index.d.ts +23 -2
- package/dist/index.js +604 -329
- package/dist/lsp.js +571 -353
- package/package.json +7 -5
package/dist/lsp.js
CHANGED
|
@@ -6,7 +6,7 @@ import os from "node:os";
|
|
|
6
6
|
import * as Path from "node:path";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import * as NodeChildProcess from "node:child_process";
|
|
9
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
9
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
10
10
|
import * as ts from "typescript";
|
|
11
11
|
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";
|
|
12
12
|
import { parseJSON5 } from "confbox";
|
|
@@ -6021,7 +6021,7 @@ const composePassthrough = /* @__PURE__ */ dual(2, (left, right) => (input) => {
|
|
|
6021
6021
|
* @since 2.0.0
|
|
6022
6022
|
*/
|
|
6023
6023
|
const Scheduler = /* @__PURE__ */ Reference("effect/Scheduler", { defaultValue: () => new MixedScheduler() });
|
|
6024
|
-
const setImmediate = "setImmediate" in globalThis ? (f) => {
|
|
6024
|
+
const setImmediate$1 = "setImmediate" in globalThis ? (f) => {
|
|
6025
6025
|
const timer = globalThis.setImmediate(f);
|
|
6026
6026
|
return () => globalThis.clearImmediate(timer);
|
|
6027
6027
|
} : (f) => {
|
|
@@ -6065,7 +6065,7 @@ var PriorityBuckets = class {
|
|
|
6065
6065
|
var MixedScheduler = class {
|
|
6066
6066
|
executionMode;
|
|
6067
6067
|
setImmediate;
|
|
6068
|
-
constructor(executionMode = "async", setImmediateFn = setImmediate) {
|
|
6068
|
+
constructor(executionMode = "async", setImmediateFn = setImmediate$1) {
|
|
6069
6069
|
this.executionMode = executionMode;
|
|
6070
6070
|
this.setImmediate = setImmediateFn;
|
|
6071
6071
|
}
|
|
@@ -6090,7 +6090,7 @@ var MixedSchedulerDispatcher = class {
|
|
|
6090
6090
|
tasks = /* @__PURE__ */ new PriorityBuckets();
|
|
6091
6091
|
running = void 0;
|
|
6092
6092
|
setImmediate;
|
|
6093
|
-
constructor(setImmediateFn = setImmediate) {
|
|
6093
|
+
constructor(setImmediateFn = setImmediate$1) {
|
|
6094
6094
|
this.setImmediate = setImmediateFn;
|
|
6095
6095
|
}
|
|
6096
6096
|
/**
|
|
@@ -19400,6 +19400,15 @@ var JsonReportV1 = class extends Class("JsonReportV1")({
|
|
|
19400
19400
|
ok: Boolean$1,
|
|
19401
19401
|
directory: String$1,
|
|
19402
19402
|
mode: JsonReportMode,
|
|
19403
|
+
/**
|
|
19404
|
+
* True when a `changed` (compare) run was intended but the base couldn't be
|
|
19405
|
+
* resolved — a shallow CI checkout with no merge base, or a failed base/head
|
|
19406
|
+
* lint. The report then lists every finding in the changed files (mode
|
|
19407
|
+
* downgrades to `diff`, the `baseline` block is dropped, the CI gate is
|
|
19408
|
+
* skipped) rather than only the ones the change introduced. Absent on a
|
|
19409
|
+
* successful `baseline` run (`schemaVersion: 2`) and on non-compare scopes.
|
|
19410
|
+
*/
|
|
19411
|
+
baselineDegraded: optional(Boolean$1),
|
|
19403
19412
|
diff: NullOr(JsonReportDiffInfo),
|
|
19404
19413
|
projects: ArraySchema(JsonReportProjectEntry),
|
|
19405
19414
|
diagnostics: ArraySchema(Diagnostic),
|
|
@@ -32816,12 +32825,31 @@ const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
|
|
|
32816
32825
|
const MINIFIED_SNIFF_BYTES = 65536;
|
|
32817
32826
|
const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
32818
32827
|
const IGNORED_DIRECTORIES = new Set([
|
|
32828
|
+
".angular",
|
|
32829
|
+
".astro",
|
|
32830
|
+
".cache",
|
|
32831
|
+
".contentlayer",
|
|
32832
|
+
".direnv",
|
|
32833
|
+
".docusaurus",
|
|
32834
|
+
".expo",
|
|
32835
|
+
".firebase",
|
|
32819
32836
|
".git",
|
|
32837
|
+
".gradle",
|
|
32838
|
+
".hg",
|
|
32820
32839
|
".next",
|
|
32821
32840
|
".nuxt",
|
|
32841
|
+
".nx",
|
|
32822
32842
|
".output",
|
|
32843
|
+
".parcel-cache",
|
|
32844
|
+
".serverless",
|
|
32823
32845
|
".svelte-kit",
|
|
32846
|
+
".svn",
|
|
32847
|
+
".terraform",
|
|
32824
32848
|
".turbo",
|
|
32849
|
+
".venv",
|
|
32850
|
+
".vercel",
|
|
32851
|
+
".wrangler",
|
|
32852
|
+
".yarn",
|
|
32825
32853
|
"build",
|
|
32826
32854
|
"coverage",
|
|
32827
32855
|
"dist",
|
|
@@ -32829,6 +32857,142 @@ const IGNORED_DIRECTORIES = new Set([
|
|
|
32829
32857
|
"out",
|
|
32830
32858
|
"storybook-static"
|
|
32831
32859
|
]);
|
|
32860
|
+
const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
32861
|
+
const MILLISECONDS_PER_SECOND = 1e3;
|
|
32862
|
+
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
32863
|
+
const FETCH_TIMEOUT_MS = 1e4;
|
|
32864
|
+
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
32865
|
+
const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
|
|
32866
|
+
const SPAWN_ARGS_MAX_LENGTH_CHARS_POSIX = 15e5;
|
|
32867
|
+
const SPAWN_ARGS_MAX_LENGTH_CHARS_DARWIN = 8e5;
|
|
32868
|
+
const NODE_VERSION_PROBE_TIMEOUT_MS = 5e3;
|
|
32869
|
+
const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
|
|
32870
|
+
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
32871
|
+
const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
|
|
32872
|
+
const GIT_SHOW_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
32873
|
+
const ES_TARGET_YEAR_BY_NAME = {
|
|
32874
|
+
es3: 1999,
|
|
32875
|
+
es5: 2009,
|
|
32876
|
+
es6: 2015,
|
|
32877
|
+
es2015: 2015,
|
|
32878
|
+
es2016: 2016,
|
|
32879
|
+
es2017: 2017,
|
|
32880
|
+
es2018: 2018,
|
|
32881
|
+
es2019: 2019,
|
|
32882
|
+
es2020: 2020,
|
|
32883
|
+
es2021: 2021,
|
|
32884
|
+
es2022: 2022,
|
|
32885
|
+
es2023: 2023,
|
|
32886
|
+
es2024: 2024,
|
|
32887
|
+
es2025: 2025,
|
|
32888
|
+
esnext: 9999
|
|
32889
|
+
};
|
|
32890
|
+
/**
|
|
32891
|
+
* tsconfig filenames probed when resolving a project's TypeScript
|
|
32892
|
+
* compiler options — the root config first, then a monorepo base config.
|
|
32893
|
+
*/
|
|
32894
|
+
const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
|
|
32895
|
+
/**
|
|
32896
|
+
* Project-config files that `StagedFiles.materialize` copies into
|
|
32897
|
+
* the temp directory alongside staged sources so oxlint resolves
|
|
32898
|
+
* `tsconfig` / `package.json` / lint configs the same way it would
|
|
32899
|
+
* in the working tree. Hoisted out of the staged-files helper so
|
|
32900
|
+
* the constant lives next to the rest of the IO budget knobs.
|
|
32901
|
+
*/
|
|
32902
|
+
const STAGED_FILES_PROJECT_CONFIG_FILENAMES = [
|
|
32903
|
+
"tsconfig.json",
|
|
32904
|
+
"tsconfig.base.json",
|
|
32905
|
+
"package.json",
|
|
32906
|
+
"doctor.config.ts",
|
|
32907
|
+
"doctor.config.mts",
|
|
32908
|
+
"doctor.config.cts",
|
|
32909
|
+
"doctor.config.js",
|
|
32910
|
+
"doctor.config.mjs",
|
|
32911
|
+
"doctor.config.cjs",
|
|
32912
|
+
"doctor.config.json",
|
|
32913
|
+
"doctor.config.jsonc",
|
|
32914
|
+
"oxlint.json",
|
|
32915
|
+
".oxlintrc.json"
|
|
32916
|
+
];
|
|
32917
|
+
const CONFIG_FINGERPRINT_FILENAMES = [
|
|
32918
|
+
"doctor.config.ts",
|
|
32919
|
+
"doctor.config.mts",
|
|
32920
|
+
"doctor.config.cts",
|
|
32921
|
+
"doctor.config.js",
|
|
32922
|
+
"doctor.config.mjs",
|
|
32923
|
+
"doctor.config.cjs",
|
|
32924
|
+
"doctor.config.json",
|
|
32925
|
+
"doctor.config.jsonc",
|
|
32926
|
+
"react-doctor.config.json",
|
|
32927
|
+
"package.json",
|
|
32928
|
+
"pnpm-workspace.yaml",
|
|
32929
|
+
"tsconfig.json",
|
|
32930
|
+
"tsconfig.base.json",
|
|
32931
|
+
".oxlintrc.json",
|
|
32932
|
+
".eslintrc.json",
|
|
32933
|
+
"pnpm-lock.yaml",
|
|
32934
|
+
"package-lock.json",
|
|
32935
|
+
"yarn.lock",
|
|
32936
|
+
"bun.lock",
|
|
32937
|
+
"bun.lockb",
|
|
32938
|
+
".gitignore"
|
|
32939
|
+
];
|
|
32940
|
+
const OXLINT_OUTPUT_MAX_BYTES = 50 * 1024 * 1024;
|
|
32941
|
+
const OXLINT_SPAWN_TIMEOUT_MS = 6e4;
|
|
32942
|
+
const NODE_COMPILE_CACHE_DIR_NAME = "node-compile-cache";
|
|
32943
|
+
const DEAD_CODE_WORKER_TIMEOUT_MS = 12e4;
|
|
32944
|
+
const OXLINT_SPLIT_TOTAL_BUDGET_MS = 18e4;
|
|
32945
|
+
const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
32946
|
+
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
32947
|
+
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
32948
|
+
const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
|
|
32949
|
+
const DEAD_CODE_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
|
|
32950
|
+
const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
|
|
32951
|
+
const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
|
|
32952
|
+
const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
|
|
32953
|
+
const RECOMMENDED_PNPM_MINIMUM_RELEASE_AGE_MINUTES = 10080;
|
|
32954
|
+
const REACT_SERVER_DOM_PACKAGES = [
|
|
32955
|
+
"react-server-dom-webpack",
|
|
32956
|
+
"react-server-dom-parcel",
|
|
32957
|
+
"react-server-dom-turbopack"
|
|
32958
|
+
];
|
|
32959
|
+
const REACT_BLOG_RSC_ADVISORY_URL = "https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components";
|
|
32960
|
+
const VERCEL_NEXTJS_SECURITY_RELEASE_URL = "https://vercel.com/changelog/next-js-may-2026-security-release";
|
|
32961
|
+
const DIAGNOSTIC_CATEGORY_BUCKETS = [
|
|
32962
|
+
"Security",
|
|
32963
|
+
"Bugs",
|
|
32964
|
+
"Performance",
|
|
32965
|
+
"Accessibility",
|
|
32966
|
+
"Maintainability"
|
|
32967
|
+
];
|
|
32968
|
+
const APP_ONLY_RULE_KEYS = new Set([
|
|
32969
|
+
"react-hooks-js/static-components",
|
|
32970
|
+
"react-doctor/no-render-prop-children",
|
|
32971
|
+
"react-doctor/prefer-explicit-variants"
|
|
32972
|
+
]);
|
|
32973
|
+
const COMPILER_CLEANUP_BUCKET = "compiler-cleanup";
|
|
32974
|
+
const COMPILER_CLEANUP_RULE_KEYS = new Set(["react-doctor/react-compiler-no-manual-memoization"]);
|
|
32975
|
+
const ROOT_CAUSE_GROUPABLE_RULE_KEYS = new Set([
|
|
32976
|
+
"react-doctor/no-derived-state",
|
|
32977
|
+
"react-doctor/no-derived-state-effect",
|
|
32978
|
+
"react-doctor/no-derived-useState",
|
|
32979
|
+
"react-doctor/no-adjust-state-on-prop-change",
|
|
32980
|
+
"react-doctor/no-reset-all-state-on-prop-change"
|
|
32981
|
+
]);
|
|
32982
|
+
const MAX_GLOB_PATTERN_LENGTH_CHARS = 1024;
|
|
32983
|
+
const CONFIG_CACHE_TTL_MS = 300 * 1e3;
|
|
32984
|
+
const SOCKET_FREE_PURL_API_BASE = "https://firewall-api.socket.dev/purl";
|
|
32985
|
+
const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
32986
|
+
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
32987
|
+
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32988
|
+
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32989
|
+
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
32990
|
+
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
32991
|
+
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
32992
|
+
const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
32993
|
+
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
32994
|
+
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
32995
|
+
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => IGNORED_DIRECTORIES.has(segment));
|
|
32832
32996
|
const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath);
|
|
32833
32997
|
const isMinifiedSource = (absolutePath) => {
|
|
32834
32998
|
let fileDescriptor;
|
|
@@ -32885,23 +33049,38 @@ const readDirectoryEntries = (directoryPath) => {
|
|
|
32885
33049
|
throw error;
|
|
32886
33050
|
}
|
|
32887
33051
|
};
|
|
32888
|
-
|
|
32889
|
-
let count = 0;
|
|
33052
|
+
function* walkSourceTreeFiles(rootDirectory) {
|
|
32890
33053
|
const stack = [rootDirectory];
|
|
32891
33054
|
while (stack.length > 0) {
|
|
32892
33055
|
const currentDirectory = stack.pop();
|
|
32893
|
-
|
|
32894
|
-
for (const entry of
|
|
33056
|
+
if (currentDirectory === void 0) continue;
|
|
33057
|
+
for (const entry of readDirectoryEntries(currentDirectory)) {
|
|
33058
|
+
const absolutePath = Path.join(currentDirectory, entry.name);
|
|
32895
33059
|
if (entry.isDirectory()) {
|
|
32896
|
-
if (!
|
|
33060
|
+
if (!IGNORED_DIRECTORIES.has(entry.name)) stack.push(absolutePath);
|
|
32897
33061
|
continue;
|
|
32898
33062
|
}
|
|
32899
|
-
if (entry.isFile()
|
|
33063
|
+
if (entry.isFile()) yield {
|
|
33064
|
+
absolutePath,
|
|
33065
|
+
name: entry.name
|
|
33066
|
+
};
|
|
32900
33067
|
}
|
|
32901
33068
|
}
|
|
32902
|
-
|
|
33069
|
+
}
|
|
33070
|
+
const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
|
|
33071
|
+
const entries = [];
|
|
33072
|
+
for (const relativePath of relativePaths) {
|
|
33073
|
+
const absolutePath = Path.resolve(rootDirectory, relativePath);
|
|
33074
|
+
const sizeBytes = statSourceFileSize(absolutePath);
|
|
33075
|
+
if (isLargeMinifiedFile(absolutePath, sizeBytes)) continue;
|
|
33076
|
+
entries.push({
|
|
33077
|
+
path: relativePath,
|
|
33078
|
+
sizeBytes: sizeBytes ?? 0
|
|
33079
|
+
});
|
|
33080
|
+
}
|
|
33081
|
+
return entries;
|
|
32903
33082
|
};
|
|
32904
|
-
const
|
|
33083
|
+
const listSourceFilesViaGit = (rootDirectory) => {
|
|
32905
33084
|
const result = spawnSync("git", [
|
|
32906
33085
|
"ls-files",
|
|
32907
33086
|
"-z",
|
|
@@ -32914,9 +33093,16 @@ const countSourceFilesViaGit = (rootDirectory) => {
|
|
|
32914
33093
|
maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES
|
|
32915
33094
|
});
|
|
32916
33095
|
if (result.error || result.status !== 0) return null;
|
|
32917
|
-
return result.stdout.split("\0").filter((filePath) => filePath.length > 0 && isLintableSourceFile(filePath) && !
|
|
33096
|
+
return result.stdout.split("\0").filter((filePath) => filePath.length > 0 && isLintableSourceFile(filePath) && !hasIgnoredPathSegment(filePath));
|
|
32918
33097
|
};
|
|
32919
|
-
const
|
|
33098
|
+
const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
33099
|
+
const filePaths = [];
|
|
33100
|
+
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) if (isLintableSourceFile(name)) filePaths.push(Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/"));
|
|
33101
|
+
return filePaths;
|
|
33102
|
+
};
|
|
33103
|
+
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
|
|
33104
|
+
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
33105
|
+
const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
|
|
32920
33106
|
const cachedPackageJsons = /* @__PURE__ */ new Map();
|
|
32921
33107
|
const clearPackageJsonCache = () => {
|
|
32922
33108
|
cachedPackageJsons.clear();
|
|
@@ -33725,137 +33911,6 @@ const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
|
33725
33911
|
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
33726
33912
|
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
33727
33913
|
};
|
|
33728
|
-
const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
33729
|
-
const MILLISECONDS_PER_SECOND = 1e3;
|
|
33730
|
-
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
33731
|
-
const FETCH_TIMEOUT_MS = 1e4;
|
|
33732
|
-
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
33733
|
-
const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
|
|
33734
|
-
const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
|
|
33735
|
-
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
33736
|
-
const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
|
|
33737
|
-
const GIT_SHOW_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
33738
|
-
const ES_TARGET_YEAR_BY_NAME = {
|
|
33739
|
-
es3: 1999,
|
|
33740
|
-
es5: 2009,
|
|
33741
|
-
es6: 2015,
|
|
33742
|
-
es2015: 2015,
|
|
33743
|
-
es2016: 2016,
|
|
33744
|
-
es2017: 2017,
|
|
33745
|
-
es2018: 2018,
|
|
33746
|
-
es2019: 2019,
|
|
33747
|
-
es2020: 2020,
|
|
33748
|
-
es2021: 2021,
|
|
33749
|
-
es2022: 2022,
|
|
33750
|
-
es2023: 2023,
|
|
33751
|
-
es2024: 2024,
|
|
33752
|
-
es2025: 2025,
|
|
33753
|
-
esnext: 9999
|
|
33754
|
-
};
|
|
33755
|
-
/**
|
|
33756
|
-
* tsconfig filenames probed when resolving a project's TypeScript
|
|
33757
|
-
* compiler options — the root config first, then a monorepo base config.
|
|
33758
|
-
*/
|
|
33759
|
-
const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
|
|
33760
|
-
/**
|
|
33761
|
-
* Project-config files that `StagedFiles.materialize` copies into
|
|
33762
|
-
* the temp directory alongside staged sources so oxlint resolves
|
|
33763
|
-
* `tsconfig` / `package.json` / lint configs the same way it would
|
|
33764
|
-
* in the working tree. Hoisted out of the staged-files helper so
|
|
33765
|
-
* the constant lives next to the rest of the IO budget knobs.
|
|
33766
|
-
*/
|
|
33767
|
-
const STAGED_FILES_PROJECT_CONFIG_FILENAMES = [
|
|
33768
|
-
"tsconfig.json",
|
|
33769
|
-
"tsconfig.base.json",
|
|
33770
|
-
"package.json",
|
|
33771
|
-
"doctor.config.ts",
|
|
33772
|
-
"doctor.config.mts",
|
|
33773
|
-
"doctor.config.cts",
|
|
33774
|
-
"doctor.config.js",
|
|
33775
|
-
"doctor.config.mjs",
|
|
33776
|
-
"doctor.config.cjs",
|
|
33777
|
-
"doctor.config.json",
|
|
33778
|
-
"doctor.config.jsonc",
|
|
33779
|
-
"oxlint.json",
|
|
33780
|
-
".oxlintrc.json"
|
|
33781
|
-
];
|
|
33782
|
-
const CONFIG_FINGERPRINT_FILENAMES = [
|
|
33783
|
-
"doctor.config.ts",
|
|
33784
|
-
"doctor.config.mts",
|
|
33785
|
-
"doctor.config.cts",
|
|
33786
|
-
"doctor.config.js",
|
|
33787
|
-
"doctor.config.mjs",
|
|
33788
|
-
"doctor.config.cjs",
|
|
33789
|
-
"doctor.config.json",
|
|
33790
|
-
"doctor.config.jsonc",
|
|
33791
|
-
"react-doctor.config.json",
|
|
33792
|
-
"package.json",
|
|
33793
|
-
"pnpm-workspace.yaml",
|
|
33794
|
-
"tsconfig.json",
|
|
33795
|
-
"tsconfig.base.json",
|
|
33796
|
-
".oxlintrc.json",
|
|
33797
|
-
".eslintrc.json",
|
|
33798
|
-
"pnpm-lock.yaml",
|
|
33799
|
-
"package-lock.json",
|
|
33800
|
-
"yarn.lock",
|
|
33801
|
-
"bun.lock",
|
|
33802
|
-
"bun.lockb",
|
|
33803
|
-
".gitignore"
|
|
33804
|
-
];
|
|
33805
|
-
const OXLINT_OUTPUT_MAX_BYTES = 50 * 1024 * 1024;
|
|
33806
|
-
const OXLINT_SPAWN_TIMEOUT_MS = 6e4;
|
|
33807
|
-
const NODE_COMPILE_CACHE_DIR_NAME = "node-compile-cache";
|
|
33808
|
-
const DEAD_CODE_WORKER_TIMEOUT_MS = 12e4;
|
|
33809
|
-
const OXLINT_SPLIT_TOTAL_BUDGET_MS = 18e4;
|
|
33810
|
-
const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
33811
|
-
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
33812
|
-
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
33813
|
-
const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
|
|
33814
|
-
const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
|
|
33815
|
-
const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
|
|
33816
|
-
const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
|
|
33817
|
-
const RECOMMENDED_PNPM_MINIMUM_RELEASE_AGE_MINUTES = 10080;
|
|
33818
|
-
const REACT_SERVER_DOM_PACKAGES = [
|
|
33819
|
-
"react-server-dom-webpack",
|
|
33820
|
-
"react-server-dom-parcel",
|
|
33821
|
-
"react-server-dom-turbopack"
|
|
33822
|
-
];
|
|
33823
|
-
const REACT_BLOG_RSC_ADVISORY_URL = "https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components";
|
|
33824
|
-
const VERCEL_NEXTJS_SECURITY_RELEASE_URL = "https://vercel.com/changelog/next-js-may-2026-security-release";
|
|
33825
|
-
const DIAGNOSTIC_CATEGORY_BUCKETS = [
|
|
33826
|
-
"Security",
|
|
33827
|
-
"Bugs",
|
|
33828
|
-
"Performance",
|
|
33829
|
-
"Accessibility",
|
|
33830
|
-
"Maintainability"
|
|
33831
|
-
];
|
|
33832
|
-
const APP_ONLY_RULE_KEYS = new Set([
|
|
33833
|
-
"react-hooks-js/static-components",
|
|
33834
|
-
"react-doctor/no-render-prop-children",
|
|
33835
|
-
"react-doctor/prefer-explicit-variants"
|
|
33836
|
-
]);
|
|
33837
|
-
const COMPILER_CLEANUP_BUCKET = "compiler-cleanup";
|
|
33838
|
-
const COMPILER_CLEANUP_RULE_KEYS = new Set(["react-doctor/react-compiler-no-manual-memoization"]);
|
|
33839
|
-
const ROOT_CAUSE_GROUPABLE_RULE_KEYS = new Set([
|
|
33840
|
-
"react-doctor/no-derived-state",
|
|
33841
|
-
"react-doctor/no-derived-state-effect",
|
|
33842
|
-
"react-doctor/no-derived-useState",
|
|
33843
|
-
"react-doctor/no-adjust-state-on-prop-change",
|
|
33844
|
-
"react-doctor/no-reset-all-state-on-prop-change"
|
|
33845
|
-
]);
|
|
33846
|
-
const MAX_GLOB_PATTERN_LENGTH_CHARS = 1024;
|
|
33847
|
-
const CONFIG_CACHE_TTL_MS = 300 * 1e3;
|
|
33848
|
-
const SOCKET_FREE_PURL_API_BASE = "https://firewall-api.socket.dev/purl";
|
|
33849
|
-
const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
33850
|
-
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
33851
|
-
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
33852
|
-
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
33853
|
-
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
33854
|
-
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
33855
|
-
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
33856
|
-
const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
33857
|
-
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
33858
|
-
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
33859
33914
|
const TSCONFIG_FILENAME = "tsconfig.json";
|
|
33860
33915
|
const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
|
|
33861
33916
|
const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
|
|
@@ -34577,7 +34632,7 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
34577
34632
|
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34578
34633
|
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34579
34634
|
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34580
|
-
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}\` —
|
|
34635
|
+
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}\`).`;
|
|
34581
34636
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34582
34637
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34583
34638
|
const candidates = [{
|
|
@@ -35053,6 +35108,7 @@ const collectStringSet = (values) => {
|
|
|
35053
35108
|
if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
|
|
35054
35109
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35055
35110
|
};
|
|
35111
|
+
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35056
35112
|
/**
|
|
35057
35113
|
* Pre-compiles every stateful filter and returns a single
|
|
35058
35114
|
* `apply(diagnostic)` closure that runs (in order):
|
|
@@ -35086,11 +35142,28 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35086
35142
|
const compiledOverrides = compileIgnoreOverrides(userConfig);
|
|
35087
35143
|
const textComponentNames = collectStringSet(userConfig?.textComponents);
|
|
35088
35144
|
const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
|
|
35145
|
+
const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
|
|
35089
35146
|
const hasTextComponents = textComponentNames.size > 0;
|
|
35090
35147
|
const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
|
|
35148
|
+
const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
|
|
35091
35149
|
const fileLinesCache = /* @__PURE__ */ new Map();
|
|
35092
35150
|
const fileContextCache = /* @__PURE__ */ new Map();
|
|
35093
35151
|
const libraryFileCache = /* @__PURE__ */ new Map();
|
|
35152
|
+
const suppressions = /* @__PURE__ */ new Map();
|
|
35153
|
+
const suppress = (diagnostic, source) => {
|
|
35154
|
+
const { ruleKey } = getDiagnosticRuleIdentity(diagnostic);
|
|
35155
|
+
const suppressionKey = `${ruleKey}\u0000${source}`;
|
|
35156
|
+
const existing = suppressions.get(suppressionKey);
|
|
35157
|
+
suppressions.set(suppressionKey, existing ? {
|
|
35158
|
+
...existing,
|
|
35159
|
+
count: existing.count + 1
|
|
35160
|
+
} : {
|
|
35161
|
+
rule: ruleKey,
|
|
35162
|
+
source,
|
|
35163
|
+
count: 1
|
|
35164
|
+
});
|
|
35165
|
+
return null;
|
|
35166
|
+
};
|
|
35094
35167
|
const isLibraryFile = (filePath) => {
|
|
35095
35168
|
let cached = libraryFileCache.get(filePath);
|
|
35096
35169
|
if (cached === void 0) {
|
|
@@ -35139,50 +35212,60 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35139
35212
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35140
35213
|
return false;
|
|
35141
35214
|
};
|
|
35142
|
-
|
|
35143
|
-
if (
|
|
35144
|
-
|
|
35145
|
-
|
|
35146
|
-
|
|
35147
|
-
|
|
35148
|
-
|
|
35149
|
-
|
|
35150
|
-
|
|
35151
|
-
|
|
35152
|
-
|
|
35153
|
-
|
|
35154
|
-
if (
|
|
35155
|
-
|
|
35156
|
-
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
|
|
35160
|
-
|
|
35161
|
-
|
|
35162
|
-
|
|
35163
|
-
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35164
|
-
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
|
|
35165
|
-
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35166
|
-
}
|
|
35167
|
-
if (respectInlineDisables && current.line > 0) {
|
|
35168
|
-
const lines = getFileLines(current.filePath);
|
|
35169
|
-
if (lines) {
|
|
35170
|
-
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35171
|
-
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35172
|
-
if (evaluation.isSuppressed) return null;
|
|
35173
|
-
if (evaluation.nearMissHint) current = {
|
|
35174
|
-
...current,
|
|
35175
|
-
suppressionHint: evaluation.nearMissHint
|
|
35176
|
-
};
|
|
35215
|
+
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
35216
|
+
if (!hasRuntimeGlobals) return false;
|
|
35217
|
+
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
35218
|
+
const identifierMatch = JSX_NO_UNDEF_IDENTIFIER_PATTERN.exec(diagnostic.message);
|
|
35219
|
+
return identifierMatch !== null && runtimeGlobalNames.has(identifierMatch[1]);
|
|
35220
|
+
};
|
|
35221
|
+
return {
|
|
35222
|
+
apply: (diagnostic) => {
|
|
35223
|
+
if (shouldAutoSuppress(diagnostic)) return null;
|
|
35224
|
+
let current = diagnostic;
|
|
35225
|
+
let explicitSeverityOverride;
|
|
35226
|
+
let explicitRuleOverride;
|
|
35227
|
+
if (severityControls) {
|
|
35228
|
+
const { ruleKey, category } = getDiagnosticRuleIdentity(current);
|
|
35229
|
+
explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey }, severityControls);
|
|
35230
|
+
explicitSeverityOverride = resolveRuleSeverityOverride({
|
|
35231
|
+
ruleKey,
|
|
35232
|
+
category
|
|
35233
|
+
}, severityControls);
|
|
35234
|
+
if (explicitSeverityOverride === "off") return suppress(current, "config");
|
|
35235
|
+
if (explicitSeverityOverride !== void 0) current = restampSeverity(current, explicitSeverityOverride);
|
|
35177
35236
|
}
|
|
35178
|
-
|
|
35179
|
-
|
|
35180
|
-
|
|
35181
|
-
|
|
35182
|
-
|
|
35183
|
-
|
|
35184
|
-
|
|
35185
|
-
|
|
35237
|
+
if (explicitRuleOverride === void 0) {
|
|
35238
|
+
if (isAppOnlyRule(`${current.plugin}/${current.rule}`) && isLibraryFile(current.filePath)) return null;
|
|
35239
|
+
}
|
|
35240
|
+
if (!showWarnings && current.severity === "warning" && explicitSeverityOverride !== "warn") return null;
|
|
35241
|
+
if (userConfig) {
|
|
35242
|
+
if (isRuleIgnored(`${current.plugin}/${current.rule}`)) return suppress(current, "config");
|
|
35243
|
+
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35244
|
+
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return suppress(current, "override");
|
|
35245
|
+
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35246
|
+
if (isJsxNoUndefSuppressedByConfig(current)) return null;
|
|
35247
|
+
}
|
|
35248
|
+
if (respectInlineDisables && current.line > 0) {
|
|
35249
|
+
const lines = getFileLines(current.filePath);
|
|
35250
|
+
if (lines) {
|
|
35251
|
+
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35252
|
+
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35253
|
+
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
35254
|
+
if (evaluation.nearMissHint) current = {
|
|
35255
|
+
...current,
|
|
35256
|
+
suppressionHint: evaluation.nearMissHint
|
|
35257
|
+
};
|
|
35258
|
+
}
|
|
35259
|
+
}
|
|
35260
|
+
const fileContext = getFileContext(current.filePath);
|
|
35261
|
+
if (fileContext !== "production") current = {
|
|
35262
|
+
...current,
|
|
35263
|
+
fileContext
|
|
35264
|
+
};
|
|
35265
|
+
return current;
|
|
35266
|
+
},
|
|
35267
|
+
summarizeSuppressions: () => [...suppressions.values()]
|
|
35268
|
+
};
|
|
35186
35269
|
};
|
|
35187
35270
|
const OxlintUnavailableKind = Literals(["binary-not-found", "native-binding-missing"]);
|
|
35188
35271
|
var OxlintUnavailable = class extends TaggedErrorClass()("OxlintUnavailable", {
|
|
@@ -35402,7 +35485,7 @@ const resolveScanConcurrency = (requested) => {
|
|
|
35402
35485
|
if (!Number.isFinite(requested) || requested < 1) return 1;
|
|
35403
35486
|
return Math.min(Math.floor(requested), 32);
|
|
35404
35487
|
};
|
|
35405
|
-
const readSystemFacts = () => ({
|
|
35488
|
+
const readSystemFacts$1 = () => ({
|
|
35406
35489
|
availableCores: os.availableParallelism(),
|
|
35407
35490
|
totalMemoryBytes: os.totalmem(),
|
|
35408
35491
|
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
@@ -35423,7 +35506,7 @@ const readSystemFacts = () => ({
|
|
|
35423
35506
|
* `facts` is injectable so tests exercise core-bound, memory-bound, cgroup-
|
|
35424
35507
|
* limited, and ceiling cases without mocking `os` or the filesystem.
|
|
35425
35508
|
*/
|
|
35426
|
-
const resolveAutoScanConcurrency = (facts = readSystemFacts()) => {
|
|
35509
|
+
const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
35427
35510
|
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
35428
35511
|
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
|
|
35429
35512
|
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
@@ -35464,13 +35547,7 @@ var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { de
|
|
|
35464
35547
|
* without recompiling react-doctor. Tests override via
|
|
35465
35548
|
* `Layer.succeed(SupplyChainOverlapTimeoutMs, ...)`.
|
|
35466
35549
|
*/
|
|
35467
|
-
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => {
|
|
35468
|
-
const raw = process.env["REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS"];
|
|
35469
|
-
if (raw === void 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35470
|
-
const parsed = Number(raw);
|
|
35471
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35472
|
-
return parsed;
|
|
35473
|
-
} }) {};
|
|
35550
|
+
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS", SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS) }) {};
|
|
35474
35551
|
/**
|
|
35475
35552
|
* Hard cap on combined stdout+stderr bytes per oxlint batch. The
|
|
35476
35553
|
* subprocess gets SIGKILL'd if it produces more; the recovery path
|
|
@@ -35618,6 +35695,7 @@ const STRING_ARRAY_FIELD_NAMES = [
|
|
|
35618
35695
|
"projects",
|
|
35619
35696
|
"textComponents",
|
|
35620
35697
|
"rawTextWrapperComponents",
|
|
35698
|
+
"runtimeGlobals",
|
|
35621
35699
|
"serverAuthFunctionNames"
|
|
35622
35700
|
];
|
|
35623
35701
|
const SURFACE_CONTROL_FIELD_NAMES = [
|
|
@@ -36706,14 +36784,17 @@ const checkReactServerComponentsAdvisory = (scanDirectory, project) => {
|
|
|
36706
36784
|
return diagnostics;
|
|
36707
36785
|
};
|
|
36708
36786
|
const REDUCED_MOTION_GREP_PATTERN = "prefers-reduced-motion|useReducedMotion|MotionConfig|reducedMotion";
|
|
36709
|
-
const
|
|
36710
|
-
|
|
36711
|
-
"
|
|
36712
|
-
"
|
|
36713
|
-
"
|
|
36714
|
-
"
|
|
36715
|
-
"
|
|
36716
|
-
|
|
36787
|
+
const REDUCED_MOTION_PATTERN = new RegExp(REDUCED_MOTION_GREP_PATTERN);
|
|
36788
|
+
const REDUCED_MOTION_FILE_EXTENSIONS = new Set([
|
|
36789
|
+
".ts",
|
|
36790
|
+
".tsx",
|
|
36791
|
+
".js",
|
|
36792
|
+
".jsx",
|
|
36793
|
+
".css",
|
|
36794
|
+
".scss"
|
|
36795
|
+
]);
|
|
36796
|
+
const REDUCED_MOTION_FILE_GLOBS = [...REDUCED_MOTION_FILE_EXTENSIONS].map((extension) => `*${extension}`);
|
|
36797
|
+
const GIT_GREP_NO_MATCH_STATUS = 1;
|
|
36717
36798
|
const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
|
|
36718
36799
|
filePath: "package.json",
|
|
36719
36800
|
plugin: "react-doctor",
|
|
@@ -36725,6 +36806,19 @@ const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
|
|
|
36725
36806
|
column: 0,
|
|
36726
36807
|
category: "Accessibility"
|
|
36727
36808
|
};
|
|
36809
|
+
const hasReducedMotionHandlingViaFilesystem = (rootDirectory) => {
|
|
36810
|
+
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
|
|
36811
|
+
if (!REDUCED_MOTION_FILE_EXTENSIONS.has(Path.extname(name))) continue;
|
|
36812
|
+
let content;
|
|
36813
|
+
try {
|
|
36814
|
+
content = NFS.readFileSync(absolutePath, "utf-8");
|
|
36815
|
+
} catch {
|
|
36816
|
+
continue;
|
|
36817
|
+
}
|
|
36818
|
+
if (REDUCED_MOTION_PATTERN.test(content)) return true;
|
|
36819
|
+
}
|
|
36820
|
+
return false;
|
|
36821
|
+
};
|
|
36728
36822
|
const checkReducedMotion = (rootDirectory) => {
|
|
36729
36823
|
const packageJsonPath = Path.join(rootDirectory, "package.json");
|
|
36730
36824
|
if (!isFile(packageJsonPath)) return [];
|
|
@@ -36743,22 +36837,21 @@ const checkReducedMotion = (rootDirectory) => {
|
|
|
36743
36837
|
const result = spawnSync("git", [
|
|
36744
36838
|
"grep",
|
|
36745
36839
|
"--untracked",
|
|
36746
|
-
"-
|
|
36747
|
-
"-E",
|
|
36840
|
+
"-lE",
|
|
36748
36841
|
REDUCED_MOTION_GREP_PATTERN,
|
|
36749
36842
|
"--",
|
|
36750
36843
|
...REDUCED_MOTION_FILE_GLOBS
|
|
36751
36844
|
], {
|
|
36752
36845
|
cwd: rootDirectory,
|
|
36846
|
+
encoding: "utf-8",
|
|
36753
36847
|
stdio: [
|
|
36754
36848
|
"ignore",
|
|
36755
36849
|
"pipe",
|
|
36756
36850
|
"pipe"
|
|
36757
36851
|
]
|
|
36758
36852
|
});
|
|
36759
|
-
if (result.error) return [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
36760
|
-
|
|
36761
|
-
return [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
36853
|
+
if (!(!result.error && (result.status === 0 || result.status === GIT_GREP_NO_MATCH_STATUS))) return hasReducedMotionHandlingViaFilesystem(rootDirectory) ? [] : [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
36854
|
+
return result.status === 0 && result.stdout.split("\n").some((filePath) => filePath.length > 0 && !hasIgnoredPathSegment(filePath)) ? [] : [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
36762
36855
|
};
|
|
36763
36856
|
const buildSecurityScanDiagnostic = (finding, entry, relativePath) => ({
|
|
36764
36857
|
filePath: relativePath,
|
|
@@ -36908,7 +37001,10 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy)
|
|
|
36908
37001
|
}
|
|
36909
37002
|
return true;
|
|
36910
37003
|
};
|
|
36911
|
-
const
|
|
37004
|
+
const yieldToEventLoop = () => new Promise((resolve) => {
|
|
37005
|
+
setImmediate(resolve);
|
|
37006
|
+
});
|
|
37007
|
+
const createSecurityScanSession = (rootDirectory, options) => {
|
|
36912
37008
|
const capabilities = options.project ? buildCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
36913
37009
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
36914
37010
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
@@ -36923,7 +37019,7 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
36923
37019
|
committedFilesOnly: rule.committedFilesOnly === true
|
|
36924
37020
|
}];
|
|
36925
37021
|
});
|
|
36926
|
-
if (enabledScanRules.length === 0) return
|
|
37022
|
+
if (enabledScanRules.length === 0) return null;
|
|
36927
37023
|
const diagnostics = [];
|
|
36928
37024
|
const seen = /* @__PURE__ */ new Set();
|
|
36929
37025
|
const gitIgnoredCache = /* @__PURE__ */ new Map();
|
|
@@ -36935,15 +37031,34 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
36935
37031
|
}
|
|
36936
37032
|
return status === true;
|
|
36937
37033
|
};
|
|
36938
|
-
|
|
36939
|
-
|
|
36940
|
-
|
|
36941
|
-
|
|
36942
|
-
|
|
36943
|
-
|
|
36944
|
-
|
|
37034
|
+
const scanFile = (file) => {
|
|
37035
|
+
for (const { entry, scan, committedFilesOnly } of enabledScanRules) for (const finding of scan(file)) {
|
|
37036
|
+
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
37037
|
+
const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
|
|
37038
|
+
const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
|
|
37039
|
+
if (seen.has(key)) continue;
|
|
37040
|
+
seen.add(key);
|
|
37041
|
+
diagnostics.push(diagnostic);
|
|
37042
|
+
}
|
|
37043
|
+
};
|
|
37044
|
+
return {
|
|
37045
|
+
scanFile,
|
|
37046
|
+
diagnostics
|
|
37047
|
+
};
|
|
37048
|
+
};
|
|
37049
|
+
const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
|
|
37050
|
+
const session = createSecurityScanSession(rootDirectory, options);
|
|
37051
|
+
if (session === null) return [];
|
|
37052
|
+
let filesSinceYield = 0;
|
|
37053
|
+
for (const file of collectSecurityScanFiles(rootDirectory)) {
|
|
37054
|
+
session.scanFile(file);
|
|
37055
|
+
filesSinceYield += 1;
|
|
37056
|
+
if (filesSinceYield >= 16) {
|
|
37057
|
+
filesSinceYield = 0;
|
|
37058
|
+
await yieldToEventLoop();
|
|
37059
|
+
}
|
|
36945
37060
|
}
|
|
36946
|
-
return diagnostics;
|
|
37061
|
+
return session.diagnostics;
|
|
36947
37062
|
};
|
|
36948
37063
|
var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
36949
37064
|
let p = process || {}, argv = p.argv || [], env = p.env || {};
|
|
@@ -37160,6 +37275,74 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
|
37160
37275
|
return [...seen].filter((pattern) => pattern.length > 0);
|
|
37161
37276
|
};
|
|
37162
37277
|
const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
|
|
37278
|
+
const readSystemFacts = () => ({
|
|
37279
|
+
availableCores: os.availableParallelism(),
|
|
37280
|
+
totalMemoryBytes: os.totalmem(),
|
|
37281
|
+
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
37282
|
+
});
|
|
37283
|
+
/**
|
|
37284
|
+
* How many real deslop dead-code child processes may run at once, across the
|
|
37285
|
+
* concurrent per-project `runInspect` fibers of one CLI run. The cap is the
|
|
37286
|
+
* smaller of the core count and the number of `DEAD_CODE_WORKER_MEM_BUDGET_BYTES`
|
|
37287
|
+
* workers that fit in available memory, floored at 1.
|
|
37288
|
+
*
|
|
37289
|
+
* On a roomy dev box / CI runner this resolves high enough that every
|
|
37290
|
+
* concurrently-scanned project still spawns its own worker (no serialization vs
|
|
37291
|
+
* the prior uncapped behavior); on a memory-constrained runner it collapses
|
|
37292
|
+
* toward 1, so the `withDeadCodeWorkerSlot` semaphore serializes the spawns
|
|
37293
|
+
* instead of oversubscribing memory with N simultaneous children — the global
|
|
37294
|
+
* cap the per-project spawn path lacked.
|
|
37295
|
+
*
|
|
37296
|
+
* Mirrors `resolveAutoScanConcurrency` (lint), but budgets memory per the
|
|
37297
|
+
* heavier dead-code worker. `facts` is injectable for tests.
|
|
37298
|
+
*/
|
|
37299
|
+
const resolveDeadCodeConcurrency = (facts = readSystemFacts()) => {
|
|
37300
|
+
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
37301
|
+
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / DEAD_CODE_WORKER_MEM_BUDGET_BYTES);
|
|
37302
|
+
return Math.max(1, Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
37303
|
+
};
|
|
37304
|
+
let availableSlots = -1;
|
|
37305
|
+
const waiters = [];
|
|
37306
|
+
const releaseSlot = () => {
|
|
37307
|
+
const nextWaiter = waiters.shift();
|
|
37308
|
+
if (nextWaiter !== void 0) nextWaiter();
|
|
37309
|
+
else availableSlots += 1;
|
|
37310
|
+
};
|
|
37311
|
+
/**
|
|
37312
|
+
* Runs `task` once a dead-code worker slot is free, releasing the slot when the
|
|
37313
|
+
* task settles (success or failure). With a high cap (roomy machine) every
|
|
37314
|
+
* caller proceeds immediately; with a low cap (constrained runner) callers
|
|
37315
|
+
* queue and run as slots free.
|
|
37316
|
+
*
|
|
37317
|
+
* `abortSignal` short-circuits the WAIT: if it's already aborted, or fires while
|
|
37318
|
+
* this caller is queued, the call rejects without acquiring a slot or running
|
|
37319
|
+
* `task` — so a cancelled scan (e.g. lint failed) doesn't sit in the queue and
|
|
37320
|
+
* then spawn a child only to tear it down. A queued caller that aborts removes
|
|
37321
|
+
* its own waiter so a later release never hands a slot to a dead request.
|
|
37322
|
+
*/
|
|
37323
|
+
const withDeadCodeWorkerSlot = async (task, abortSignal) => {
|
|
37324
|
+
if (abortSignal?.aborted) throw new Error("Dead-code worker aborted.");
|
|
37325
|
+
if (availableSlots < 0) availableSlots = resolveDeadCodeConcurrency();
|
|
37326
|
+
if (availableSlots > 0) availableSlots -= 1;
|
|
37327
|
+
else await new Promise((resolve, reject) => {
|
|
37328
|
+
const waiter = () => {
|
|
37329
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
37330
|
+
resolve();
|
|
37331
|
+
};
|
|
37332
|
+
const onAbort = () => {
|
|
37333
|
+
const queuedIndex = waiters.indexOf(waiter);
|
|
37334
|
+
if (queuedIndex !== -1) waiters.splice(queuedIndex, 1);
|
|
37335
|
+
reject(/* @__PURE__ */ new Error("Dead-code worker aborted."));
|
|
37336
|
+
};
|
|
37337
|
+
waiters.push(waiter);
|
|
37338
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
37339
|
+
});
|
|
37340
|
+
try {
|
|
37341
|
+
return await task();
|
|
37342
|
+
} finally {
|
|
37343
|
+
releaseSlot();
|
|
37344
|
+
}
|
|
37345
|
+
};
|
|
37163
37346
|
/**
|
|
37164
37347
|
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
37165
37348
|
* the input when it cannot be realpath'd (broken symlink, permission
|
|
@@ -37184,6 +37367,11 @@ const toCanonicalPath = (filePath) => {
|
|
|
37184
37367
|
};
|
|
37185
37368
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
37186
37369
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
37370
|
+
const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
|
|
37371
|
+
"react-doctor",
|
|
37372
|
+
"eslint-plugin-react-doctor",
|
|
37373
|
+
"oxlint-plugin-react-doctor"
|
|
37374
|
+
]);
|
|
37187
37375
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
37188
37376
|
const inputChunks = [];
|
|
37189
37377
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -37450,14 +37638,17 @@ const checkDeadCode = async (options) => {
|
|
|
37450
37638
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
37451
37639
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
37452
37640
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37453
|
-
const
|
|
37454
|
-
|
|
37455
|
-
|
|
37456
|
-
|
|
37457
|
-
|
|
37458
|
-
|
|
37459
|
-
|
|
37460
|
-
|
|
37641
|
+
const spawnAndRun = () => {
|
|
37642
|
+
return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37643
|
+
rootDirectory,
|
|
37644
|
+
entryPatterns,
|
|
37645
|
+
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
37646
|
+
ignorePatterns,
|
|
37647
|
+
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
|
|
37648
|
+
parseConcurrency: options.parseConcurrency
|
|
37649
|
+
}), options.workerTimeoutMs ?? 12e4, options.abortSignal);
|
|
37650
|
+
};
|
|
37651
|
+
const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
|
|
37461
37652
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37462
37653
|
const diagnostics = [];
|
|
37463
37654
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37486,6 +37677,7 @@ const checkDeadCode = async (options) => {
|
|
|
37486
37677
|
});
|
|
37487
37678
|
}
|
|
37488
37679
|
for (const unusedDependency of result.unusedDependencies) {
|
|
37680
|
+
if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
|
|
37489
37681
|
const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
|
|
37490
37682
|
diagnostics.push({
|
|
37491
37683
|
filePath: "package.json",
|
|
@@ -37555,6 +37747,7 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
|
|
|
37555
37747
|
return true;
|
|
37556
37748
|
};
|
|
37557
37749
|
const filterDiagnosticsForSurface = (diagnostics, surface, config) => diagnostics.filter((diagnostic) => isDiagnosticOnSurface(diagnostic, surface, config));
|
|
37750
|
+
const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs - Date.now(), 0);
|
|
37558
37751
|
/**
|
|
37559
37752
|
* Budget for the dead-code phase, scaled to the work. deslop's graph build is
|
|
37560
37753
|
* CPU-bound and roughly linear in file count, so a fixed 120s cap is too tight
|
|
@@ -37573,53 +37766,6 @@ const resolveDeadCodeTimeout = (input) => {
|
|
|
37573
37766
|
phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
|
|
37574
37767
|
};
|
|
37575
37768
|
};
|
|
37576
|
-
const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
|
|
37577
|
-
const entries = [];
|
|
37578
|
-
for (const relativePath of relativePaths) {
|
|
37579
|
-
const absolutePath = Path.resolve(rootDirectory, relativePath);
|
|
37580
|
-
const sizeBytes = statSourceFileSize(absolutePath);
|
|
37581
|
-
if (isLargeMinifiedFile(absolutePath, sizeBytes)) continue;
|
|
37582
|
-
entries.push({
|
|
37583
|
-
path: relativePath,
|
|
37584
|
-
sizeBytes: sizeBytes ?? 0
|
|
37585
|
-
});
|
|
37586
|
-
}
|
|
37587
|
-
return entries;
|
|
37588
|
-
};
|
|
37589
|
-
const listSourceFilesViaGit = (rootDirectory) => {
|
|
37590
|
-
const result = spawnSync("git", [
|
|
37591
|
-
"ls-files",
|
|
37592
|
-
"-z",
|
|
37593
|
-
"--cached",
|
|
37594
|
-
"--others",
|
|
37595
|
-
"--exclude-standard"
|
|
37596
|
-
], {
|
|
37597
|
-
cwd: rootDirectory,
|
|
37598
|
-
encoding: "utf-8",
|
|
37599
|
-
maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES
|
|
37600
|
-
});
|
|
37601
|
-
if (result.error || result.status !== 0) return null;
|
|
37602
|
-
return result.stdout.split("\0").filter((filePath) => filePath.length > 0 && isLintableSourceFile(filePath));
|
|
37603
|
-
};
|
|
37604
|
-
const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
37605
|
-
const filePaths = [];
|
|
37606
|
-
const stack = [rootDirectory];
|
|
37607
|
-
while (stack.length > 0) {
|
|
37608
|
-
const currentDirectory = stack.pop();
|
|
37609
|
-
const entries = readDirectoryEntries(currentDirectory);
|
|
37610
|
-
for (const entry of entries) {
|
|
37611
|
-
const absolutePath = Path.join(currentDirectory, entry.name);
|
|
37612
|
-
if (entry.isDirectory()) {
|
|
37613
|
-
if (!entry.name.startsWith(".") && !IGNORED_DIRECTORIES.has(entry.name)) stack.push(absolutePath);
|
|
37614
|
-
continue;
|
|
37615
|
-
}
|
|
37616
|
-
if (entry.isFile() && isLintableSourceFile(entry.name)) filePaths.push(Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/"));
|
|
37617
|
-
}
|
|
37618
|
-
}
|
|
37619
|
-
return filePaths;
|
|
37620
|
-
};
|
|
37621
|
-
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory));
|
|
37622
|
-
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
37623
37769
|
const resolveLintIncludePaths = (rootDirectory, userConfig, project) => {
|
|
37624
37770
|
if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
|
|
37625
37771
|
const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
|
|
@@ -37664,7 +37810,6 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
|
37664
37810
|
return yield* tryPromise({
|
|
37665
37811
|
try: (signal) => checkDeadCode({
|
|
37666
37812
|
rootDirectory: input.rootDirectory,
|
|
37667
|
-
userConfig: input.userConfig,
|
|
37668
37813
|
parseConcurrency: input.parseConcurrency,
|
|
37669
37814
|
workerTimeoutMs: input.workerTimeoutMs,
|
|
37670
37815
|
abortSignal: signal
|
|
@@ -37787,6 +37932,11 @@ const isSafeGitRevision = (candidate) => {
|
|
|
37787
37932
|
if (candidate.includes("..") || candidate.includes("@{")) return false;
|
|
37788
37933
|
return SAFE_GIT_REVISION_PATTERN.test(candidate);
|
|
37789
37934
|
};
|
|
37935
|
+
const resolveSpawnArgsLengthCap = () => {
|
|
37936
|
+
if (process.platform === "win32") return SPAWN_ARGS_MAX_LENGTH_CHARS;
|
|
37937
|
+
if (process.platform === "darwin") return SPAWN_ARGS_MAX_LENGTH_CHARS_DARWIN;
|
|
37938
|
+
return SPAWN_ARGS_MAX_LENGTH_CHARS_POSIX;
|
|
37939
|
+
};
|
|
37790
37940
|
/**
|
|
37791
37941
|
* Splits a git revision range into its endpoints: three-dot `A...B`
|
|
37792
37942
|
* (symmetric, merge-base) or two-dot `A..B` (direct). Returns `null`
|
|
@@ -37871,7 +38021,8 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37871
38021
|
return scoped(gen(function* () {
|
|
37872
38022
|
if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
|
|
37873
38023
|
const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
|
|
37874
|
-
|
|
38024
|
+
const spawnArgsLengthCap = resolveSpawnArgsLengthCap();
|
|
38025
|
+
if (argvLengthChars > spawnArgsLengthCap) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${spawnArgsLengthCap})`);
|
|
37875
38026
|
const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
|
|
37876
38027
|
cwd: input.directory,
|
|
37877
38028
|
env: input.env,
|
|
@@ -38040,6 +38191,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38040
38191
|
if (resolvedCurrentBranch === null && explicitBaseBranch === void 0) return null;
|
|
38041
38192
|
const baseBranch = explicitBaseBranch ?? (yield* defaultBranch(directory));
|
|
38042
38193
|
if (baseBranch === null) return null;
|
|
38194
|
+
if (!isSafeGitRevision(baseBranch)) return null;
|
|
38043
38195
|
if (explicitBaseBranch !== void 0) {
|
|
38044
38196
|
if (!(yield* branchExists(directory, explicitBaseBranch))) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchMissing({ branch: explicitBaseBranch }) }));
|
|
38045
38197
|
}
|
|
@@ -38310,12 +38462,12 @@ const findFilesWithDisableDirectivesViaGit = async (rootDirectory, includePaths)
|
|
|
38310
38462
|
return null;
|
|
38311
38463
|
}
|
|
38312
38464
|
if (grepResult === null) return null;
|
|
38313
|
-
return grepResult.stdout.split("\n").filter((filePath) => filePath.length > 0 && isLintableSourceFile(filePath));
|
|
38465
|
+
return grepResult.stdout.split("\n").filter((filePath) => filePath.length > 0 && isLintableSourceFile(filePath) && !hasIgnoredPathSegment(filePath));
|
|
38314
38466
|
};
|
|
38315
38467
|
const findFilesWithDisableDirectivesViaFilesystem = (rootDirectory, includePaths) => {
|
|
38316
38468
|
const matches = [];
|
|
38317
38469
|
const checkFile = (relativePath) => {
|
|
38318
|
-
if (!isLintableSourceFile(relativePath)) return;
|
|
38470
|
+
if (!isLintableSourceFile(relativePath) || hasIgnoredPathSegment(relativePath)) return;
|
|
38319
38471
|
const absolutePath = Path.join(rootDirectory, relativePath);
|
|
38320
38472
|
let content;
|
|
38321
38473
|
try {
|
|
@@ -38329,22 +38481,7 @@ const findFilesWithDisableDirectivesViaFilesystem = (rootDirectory, includePaths
|
|
|
38329
38481
|
for (const candidate of includePaths) checkFile(candidate);
|
|
38330
38482
|
return matches;
|
|
38331
38483
|
}
|
|
38332
|
-
const
|
|
38333
|
-
while (stack.length > 0) {
|
|
38334
|
-
const current = stack.pop();
|
|
38335
|
-
if (current === void 0) continue;
|
|
38336
|
-
const entries = readDirectoryEntries(current);
|
|
38337
|
-
for (const entry of entries) {
|
|
38338
|
-
if (entry.isDirectory()) {
|
|
38339
|
-
if (entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
38340
|
-
stack.push(Path.join(current, entry.name));
|
|
38341
|
-
continue;
|
|
38342
|
-
}
|
|
38343
|
-
if (!entry.isFile()) continue;
|
|
38344
|
-
const absolute = Path.join(current, entry.name);
|
|
38345
|
-
checkFile(Path.relative(rootDirectory, absolute));
|
|
38346
|
-
}
|
|
38347
|
-
}
|
|
38484
|
+
for (const { absolutePath } of walkSourceTreeFiles(rootDirectory)) checkFile(Path.relative(rootDirectory, absolutePath));
|
|
38348
38485
|
return matches;
|
|
38349
38486
|
};
|
|
38350
38487
|
const findFilesWithDisableDirectives = async (rootDirectory, includePaths) => await findFilesWithDisableDirectivesViaGit(rootDirectory, includePaths) ?? findFilesWithDisableDirectivesViaFilesystem(rootDirectory, includePaths);
|
|
@@ -38390,7 +38527,7 @@ const normalizeConfigForHash = (config) => {
|
|
|
38390
38527
|
if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
|
|
38391
38528
|
return clone;
|
|
38392
38529
|
};
|
|
38393
|
-
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("
|
|
38530
|
+
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");
|
|
38394
38531
|
/**
|
|
38395
38532
|
* Loads a plugin module via the local require resolver and extracts
|
|
38396
38533
|
* `(name, ruleNames)` from either `module.exports.meta + rules` or
|
|
@@ -38561,12 +38698,12 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38561
38698
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38562
38699
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
38563
38700
|
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledBy)) continue;
|
|
38564
|
-
const
|
|
38701
|
+
const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
|
|
38702
|
+
if (rule.defaultEnabled === false && explicitRuleOverride === void 0) continue;
|
|
38703
|
+
const severity = resolveRuleSeverityOverride({
|
|
38565
38704
|
ruleKey: registryEntry.key,
|
|
38566
38705
|
category: rule.category
|
|
38567
|
-
}, severityControls);
|
|
38568
|
-
if (rule.defaultEnabled === false && explicitSeverity === void 0) continue;
|
|
38569
|
-
const severity = explicitSeverity ?? resolveCompilerCleanupBucketSeverity(registryEntry.key, severityControls) ?? rule.severity;
|
|
38706
|
+
}, severityControls) ?? resolveCompilerCleanupBucketSeverity(registryEntry.key, severityControls) ?? rule.severity;
|
|
38570
38707
|
if (severity === "off") continue;
|
|
38571
38708
|
enabledReactDoctorRules[registryEntry.key] = severity;
|
|
38572
38709
|
}
|
|
@@ -38668,24 +38805,38 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38668
38805
|
updatedAtMs: Date.now(),
|
|
38669
38806
|
files: Object.fromEntries(cappedEntries)
|
|
38670
38807
|
};
|
|
38671
|
-
const keptHashes = Object.entries(rulesets).sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8).map(([hash]) => hash);
|
|
38672
|
-
const prunedRulesets = {};
|
|
38673
|
-
for (const hash of keptHashes) prunedRulesets[hash] = rulesets[hash];
|
|
38674
38808
|
atomicWriteJson(cacheFilePath, {
|
|
38675
38809
|
version: 1,
|
|
38676
|
-
rulesets:
|
|
38810
|
+
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
38677
38811
|
});
|
|
38678
38812
|
}
|
|
38679
38813
|
};
|
|
38680
38814
|
};
|
|
38681
38815
|
const bundledRequire = createRequire(import.meta.url);
|
|
38816
|
+
const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
|
|
38817
|
+
const resolveChildNodeVersion = (nodeBinaryPath) => {
|
|
38818
|
+
if (nodeBinaryPath === process.execPath) return process.version;
|
|
38819
|
+
const cachedVersion = childNodeVersionByBinaryPath.get(nodeBinaryPath);
|
|
38820
|
+
if (cachedVersion !== void 0) return cachedVersion;
|
|
38821
|
+
let version;
|
|
38822
|
+
try {
|
|
38823
|
+
version = execFileSync(nodeBinaryPath, ["--version"], {
|
|
38824
|
+
encoding: "utf8",
|
|
38825
|
+
timeout: NODE_VERSION_PROBE_TIMEOUT_MS
|
|
38826
|
+
}).trim();
|
|
38827
|
+
} catch {
|
|
38828
|
+
version = `unknown:${nodeBinaryPath}`;
|
|
38829
|
+
}
|
|
38830
|
+
childNodeVersionByBinaryPath.set(nodeBinaryPath, version);
|
|
38831
|
+
return version;
|
|
38832
|
+
};
|
|
38682
38833
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
38683
38834
|
"oxlint/package.json",
|
|
38684
38835
|
"oxlint-plugin-react-doctor/package.json",
|
|
38685
38836
|
"eslint-plugin-react-hooks/package.json"
|
|
38686
38837
|
];
|
|
38687
|
-
const resolveOxlintToolchainVersions = () => {
|
|
38688
|
-
const versions = [`node=${
|
|
38838
|
+
const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
|
|
38839
|
+
const versions = [`node=${resolveChildNodeVersion(nodeBinaryPath)}`];
|
|
38689
38840
|
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
38690
38841
|
const packageJson = bundledRequire(specifier);
|
|
38691
38842
|
const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
@@ -39219,11 +39370,12 @@ const REACT_COMPILER_TITLE = "React Compiler can't optimize this";
|
|
|
39219
39370
|
const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
|
|
39220
39371
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
39221
39372
|
const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
|
|
39373
|
+
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`.";
|
|
39222
39374
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
39223
|
-
const buildReactCompilerMessage = (reasonSummary) => {
|
|
39375
|
+
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
39224
39376
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
39225
|
-
if (!normalizedSummary) return
|
|
39226
|
-
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${
|
|
39377
|
+
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
39378
|
+
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
39227
39379
|
};
|
|
39228
39380
|
const PLUGIN_CATEGORY_MAP = {
|
|
39229
39381
|
react: "Bugs",
|
|
@@ -39272,7 +39424,7 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
39272
39424
|
const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
|
|
39273
39425
|
const reasonDetail = reasonDetailLines.join("\n").trim();
|
|
39274
39426
|
return {
|
|
39275
|
-
message: buildReactCompilerMessage(reasonSummary.trim()),
|
|
39427
|
+
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
39276
39428
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
39277
39429
|
};
|
|
39278
39430
|
}
|
|
@@ -39533,28 +39685,46 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
39533
39685
|
* loop with a slimmer config in that case.
|
|
39534
39686
|
*/
|
|
39535
39687
|
const spawnLintBatches = async (input) => {
|
|
39536
|
-
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal } = input;
|
|
39688
|
+
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
|
|
39537
39689
|
const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
|
|
39538
39690
|
const totalFileCount = fileBatches.reduce((sum, batch) => sum + batch.length, 0);
|
|
39539
39691
|
const runBatchPass = async (concurrency) => {
|
|
39540
39692
|
const allDiagnostics = [];
|
|
39541
39693
|
const droppedFiles = [];
|
|
39694
|
+
const deadlineSkippedFiles = [];
|
|
39695
|
+
const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0;
|
|
39542
39696
|
let firstDropReason = null;
|
|
39543
|
-
const
|
|
39544
|
-
|
|
39697
|
+
const spawnLintBatch = async (batch, depth, batchState) => {
|
|
39698
|
+
if (isPastDeadline()) {
|
|
39699
|
+
deadlineSkippedFiles.push(...batch);
|
|
39700
|
+
batchState.deadlineSkippedFileCount += batch.length;
|
|
39701
|
+
return [];
|
|
39702
|
+
}
|
|
39545
39703
|
const batchArgs = [...baseArgs, ...batch];
|
|
39546
39704
|
try {
|
|
39547
39705
|
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
|
|
39548
39706
|
} catch (error) {
|
|
39549
39707
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
39550
|
-
|
|
39551
|
-
|
|
39708
|
+
if (isPastDeadline()) {
|
|
39709
|
+
deadlineSkippedFiles.push(...batch);
|
|
39710
|
+
batchState.deadlineSkippedFileCount += batch.length;
|
|
39711
|
+
return [];
|
|
39712
|
+
}
|
|
39713
|
+
batchState.deadlineMs ??= Date.now() + splitTotalBudgetMs;
|
|
39714
|
+
const isBudgetElapsed = Date.now() >= batchState.deadlineMs;
|
|
39715
|
+
const isDepthCapReached = depth >= splitMaxDepth;
|
|
39716
|
+
if (batch.length <= 1 || isBudgetElapsed || isDepthCapReached) {
|
|
39552
39717
|
droppedFiles.push(...batch);
|
|
39553
|
-
if (firstDropReason === null)
|
|
39718
|
+
if (firstDropReason === null) {
|
|
39719
|
+
let limitHint = "";
|
|
39720
|
+
if (isDepthCapReached) limitHint = ` (split depth cap of ${splitMaxDepth} levels reached)`;
|
|
39721
|
+
else if (isBudgetElapsed) limitHint = ` (split budget of ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s exhausted at depth ${depth})`;
|
|
39722
|
+
firstDropReason = batch.length > 1 ? `${error.message}${limitHint}` : error.message;
|
|
39723
|
+
}
|
|
39554
39724
|
return [];
|
|
39555
39725
|
}
|
|
39556
39726
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
39557
|
-
return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1)];
|
|
39727
|
+
return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1, batchState), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1, batchState)];
|
|
39558
39728
|
}
|
|
39559
39729
|
};
|
|
39560
39730
|
let startedFileCount = 0;
|
|
@@ -39570,9 +39740,17 @@ const spawnLintBatches = async (input) => {
|
|
|
39570
39740
|
progressTimer?.unref?.();
|
|
39571
39741
|
try {
|
|
39572
39742
|
const batchResults = await mapWithConcurrency(fileBatches, concurrency, async (batch) => {
|
|
39743
|
+
if (isPastDeadline()) {
|
|
39744
|
+
deadlineSkippedFiles.push(...batch);
|
|
39745
|
+
return [];
|
|
39746
|
+
}
|
|
39573
39747
|
startedFileCount += batch.length;
|
|
39574
|
-
const
|
|
39575
|
-
|
|
39748
|
+
const batchState = {
|
|
39749
|
+
deadlineMs: null,
|
|
39750
|
+
deadlineSkippedFileCount: 0
|
|
39751
|
+
};
|
|
39752
|
+
const batchDiagnostics = await spawnLintBatch(batch, 0, batchState);
|
|
39753
|
+
scannedFileCount += batch.length - batchState.deadlineSkippedFileCount;
|
|
39576
39754
|
if (onFileProgress) {
|
|
39577
39755
|
displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
|
|
39578
39756
|
onFileProgress(displayedFileCount, totalFileCount);
|
|
@@ -39583,12 +39761,13 @@ const spawnLintBatches = async (input) => {
|
|
|
39583
39761
|
} finally {
|
|
39584
39762
|
if (progressTimer !== null) clearInterval(progressTimer);
|
|
39585
39763
|
}
|
|
39586
|
-
|
|
39587
|
-
|
|
39588
|
-
|
|
39589
|
-
|
|
39590
|
-
|
|
39591
|
-
}
|
|
39764
|
+
const reportSkippedFiles = (skippedFiles, buildMessage) => {
|
|
39765
|
+
if (skippedFiles.length === 0 || !onPartialFailure) return;
|
|
39766
|
+
onPartialFailure(buildMessage(`${skippedFiles.slice(0, 3).join(", ")}${skippedFiles.length > 3 ? `, +${skippedFiles.length - 3} more` : ""}`));
|
|
39767
|
+
};
|
|
39768
|
+
const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
|
|
39769
|
+
reportSkippedFiles(droppedFiles, (fileListPreview) => `${droppedFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
|
|
39770
|
+
reportSkippedFiles(deadlineSkippedFiles, (fileListPreview) => `${deadlineSkippedFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
|
|
39592
39771
|
return allDiagnostics;
|
|
39593
39772
|
};
|
|
39594
39773
|
let diagnostics;
|
|
@@ -39645,7 +39824,14 @@ const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
|
39645
39824
|
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
39646
39825
|
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
39647
39826
|
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
39648
|
-
return Path.join(os.tmpdir(),
|
|
39827
|
+
return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
|
|
39828
|
+
};
|
|
39829
|
+
const resolveUserCacheScope = () => {
|
|
39830
|
+
try {
|
|
39831
|
+
return String(process.getuid?.() ?? os.userInfo().username);
|
|
39832
|
+
} catch {
|
|
39833
|
+
return "shared";
|
|
39834
|
+
}
|
|
39649
39835
|
};
|
|
39650
39836
|
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39651
39837
|
/**
|
|
@@ -39785,7 +39971,8 @@ const runOxlint = async (options) => {
|
|
|
39785
39971
|
spawnTimeoutMs,
|
|
39786
39972
|
outputMaxBytes,
|
|
39787
39973
|
concurrency: options.concurrency,
|
|
39788
|
-
signal: options.signal
|
|
39974
|
+
signal: options.signal,
|
|
39975
|
+
deadlineEpochMs: options.deadlineEpochMs
|
|
39789
39976
|
});
|
|
39790
39977
|
writeOxlintConfig(passConfigPath, buildConfigForPass({}));
|
|
39791
39978
|
try {
|
|
@@ -39813,7 +40000,7 @@ const runOxlint = async (options) => {
|
|
|
39813
40000
|
extendsPaths: [],
|
|
39814
40001
|
ruleSelection: "cacheable"
|
|
39815
40002
|
}),
|
|
39816
|
-
toolchainVersions: resolveOxlintToolchainVersions(),
|
|
40003
|
+
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
39817
40004
|
ignorePatterns: combinedPatterns,
|
|
39818
40005
|
tsconfigContent
|
|
39819
40006
|
});
|
|
@@ -39827,7 +40014,7 @@ const runOxlint = async (options) => {
|
|
|
39827
40014
|
missFiles.push(candidateFile);
|
|
39828
40015
|
continue;
|
|
39829
40016
|
}
|
|
39830
|
-
const cacheKey = `${candidateFile.replaceAll("\\", "/")}
|
|
40017
|
+
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
39831
40018
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
39832
40019
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
39833
40020
|
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
@@ -39884,7 +40071,8 @@ const runOxlint = async (options) => {
|
|
|
39884
40071
|
spawnTimeoutMs,
|
|
39885
40072
|
outputMaxBytes,
|
|
39886
40073
|
concurrency: options.concurrency,
|
|
39887
|
-
signal: options.signal
|
|
40074
|
+
signal: options.signal,
|
|
40075
|
+
deadlineEpochMs: options.deadlineEpochMs
|
|
39888
40076
|
});
|
|
39889
40077
|
writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
|
|
39890
40078
|
try {
|
|
@@ -39988,7 +40176,8 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
39988
40176
|
outputMaxBytes,
|
|
39989
40177
|
concurrency,
|
|
39990
40178
|
signal,
|
|
39991
|
-
lintBatchOrdering
|
|
40179
|
+
lintBatchOrdering,
|
|
40180
|
+
deadlineEpochMs: input.deadlineEpochMs
|
|
39992
40181
|
}),
|
|
39993
40182
|
catch: ensureReactDoctorError
|
|
39994
40183
|
});
|
|
@@ -40143,7 +40332,7 @@ const ScoreApiResponseSchema = Struct({
|
|
|
40143
40332
|
})))
|
|
40144
40333
|
});
|
|
40145
40334
|
const parseScoreResult = (value) => getOrNull(decodeUnknownOption(ScoreApiResponseSchema)(value));
|
|
40146
|
-
const stripLocalFileFields = (diagnostics) => diagnostics.map(({ filePath: _filePath, fileContext: _fileContext, ...rest }) => rest);
|
|
40335
|
+
const stripLocalFileFields = (diagnostics) => diagnostics.map(({ filePath: _filePath, fileContext: _fileContext, fixGroupId: _fixGroupId, ...rest }) => rest);
|
|
40147
40336
|
const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
40148
40337
|
const describeFailure = (error) => {
|
|
40149
40338
|
if (isAbortError(error)) return `timed out after ${FETCH_TIMEOUT_MS / 1e3}s`;
|
|
@@ -40447,7 +40636,10 @@ const ALERT_SEVERITY_RANK = {
|
|
|
40447
40636
|
medium: 2,
|
|
40448
40637
|
low: 1
|
|
40449
40638
|
};
|
|
40450
|
-
const severityRank = (severity) =>
|
|
40639
|
+
const severityRank = (severity) => {
|
|
40640
|
+
const normalized = severity.toLowerCase();
|
|
40641
|
+
return Object.hasOwn(ALERT_SEVERITY_RANK, normalized) ? ALERT_SEVERITY_RANK[normalized] : 0;
|
|
40642
|
+
};
|
|
40451
40643
|
const displaySeverity = (severity) => {
|
|
40452
40644
|
const normalized = sanitizeTerminalText(severity.toLowerCase());
|
|
40453
40645
|
return normalized === "middle" ? "medium" : normalized;
|
|
@@ -40464,7 +40656,7 @@ const ALERT_TYPE_LABELS = {
|
|
|
40464
40656
|
typosquat: "possible typosquat"
|
|
40465
40657
|
};
|
|
40466
40658
|
const humanizeAlertType = (type) => type.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase().trim();
|
|
40467
|
-
const friendlyAlertType = (type) => ALERT_TYPE_LABELS[type]
|
|
40659
|
+
const friendlyAlertType = (type) => Object.hasOwn(ALERT_TYPE_LABELS, type) ? ALERT_TYPE_LABELS[type] : sanitizeTerminalText(humanizeAlertType(type));
|
|
40468
40660
|
const summarizeAlertNote = (note) => {
|
|
40469
40661
|
const collapsed = sanitizeTerminalText(note.replace(/\s+/g, " ").trim());
|
|
40470
40662
|
const firstSentence = collapsed.split(/(?<=\.)\s/)[0] || collapsed;
|
|
@@ -40559,7 +40751,10 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
40559
40751
|
* outage.
|
|
40560
40752
|
*/
|
|
40561
40753
|
var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
|
|
40562
|
-
static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(
|
|
40754
|
+
static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(flatMap$2(SupplyChainOverlapTimeoutMs, (totalTimeoutMs) => checkSupplyChain({
|
|
40755
|
+
...input,
|
|
40756
|
+
totalTimeoutMs
|
|
40757
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
|
|
40563
40758
|
static layerOf = (diagnostics) => succeed$3(SupplyChain, SupplyChain.of({ run: () => fromIterable$1(diagnostics) }));
|
|
40564
40759
|
};
|
|
40565
40760
|
const getObjectProperty = (value, propertyName) => {
|
|
@@ -40621,7 +40816,10 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
40621
40816
|
* diagnostics).
|
|
40622
40817
|
* 2. beforeLint hook (e.g. CLI renders the project-detection block)
|
|
40623
40818
|
* 3. environment checks (reduced-motion + pnpm hardening +
|
|
40624
|
-
* expo/react-native
|
|
40819
|
+
* expo/react-native), collected synchronously. The heavier
|
|
40820
|
+
* content-regex security scan is forked instead (like supply-chain
|
|
40821
|
+
* below) and joined before the concat, so its CPU overlaps lint
|
|
40822
|
+
* rather than blocking the event loop before it.
|
|
40625
40823
|
* 4. The supply-chain check (Socket.dev) is forked onto a background
|
|
40626
40824
|
* fiber so its ~100% network-bound time overlaps the ~100%
|
|
40627
40825
|
* CPU/subprocess-bound lint pass below, collapsing two serial
|
|
@@ -40641,7 +40839,7 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
40641
40839
|
* order, so terminal output is identical either way; supply-chain
|
|
40642
40840
|
* rides alongside without a spinner.
|
|
40643
40841
|
* 6. Join the supply-chain fiber, then assemble the diagnostics in a
|
|
40644
|
-
* FIXED order (env, supply-chain, lint, dead-code) so the output is
|
|
40842
|
+
* FIXED order (env, security-scan, supply-chain, lint, dead-code) so the output is
|
|
40645
40843
|
* byte-identical regardless of which fiber settled first. The
|
|
40646
40844
|
* viewer-permission fiber is joined later, during score-metadata
|
|
40647
40845
|
* assembly (it feeds score metadata, not diagnostics). The per-element
|
|
@@ -40702,12 +40900,13 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40702
40900
|
...checkPnpmHardening(scanDirectory),
|
|
40703
40901
|
...checkReactServerComponentsAdvisory(scanDirectory, project),
|
|
40704
40902
|
...checkExpoProject(scanDirectory, project),
|
|
40705
|
-
...checkReactNativeProject(scanDirectory, project)
|
|
40706
|
-
...checkSecurityScan(scanDirectory, {
|
|
40707
|
-
project,
|
|
40708
|
-
ignoredTags: input.ignoredTags
|
|
40709
|
-
})
|
|
40903
|
+
...checkReactNativeProject(scanDirectory, project)
|
|
40710
40904
|
])));
|
|
40905
|
+
const securityScanFailedRef = yield* make$13(false);
|
|
40906
|
+
const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
|
|
40907
|
+
project,
|
|
40908
|
+
ignoredTags: input.ignoredTags
|
|
40909
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
|
|
40711
40910
|
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
40712
40911
|
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
40713
40912
|
const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
|
|
@@ -40738,6 +40937,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40738
40937
|
const deadCodePhaseTimeoutMs = yield* DeadCodePhaseTimeoutMs;
|
|
40739
40938
|
const resolveDeadCodePhaseTimeoutMs = (scaledPhaseTimeoutMs) => deadCodePhaseTimeoutMs === 15e4 ? scaledPhaseTimeoutMs : deadCodePhaseTimeoutMs;
|
|
40740
40939
|
const workerCountSuffix = scanConcurrency > 1 ? ` ${highlighter.dim(`[~${scanConcurrency} workers]`)}` : "";
|
|
40940
|
+
const capToDeadline = (phaseTimeoutMs) => input.deadlineEpochMs === void 0 ? phaseTimeoutMs : Math.min(phaseTimeoutMs, remainingDeadlineBudgetMs(input.deadlineEpochMs));
|
|
40741
40941
|
const shouldRunDeadCode = input.runDeadCode && !isDiffMode && (showWarnings || deadCodeMaySurfaceWhenWarningsHidden(resolvedConfig.config));
|
|
40742
40942
|
const deadCodeOverlapMode = yield* DeadCodeOverlap;
|
|
40743
40943
|
const shouldOverlapDeadCode = shouldRunDeadCode && deadCodeOverlapMode === "on";
|
|
@@ -40745,7 +40945,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40745
40945
|
const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
|
|
40746
40946
|
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
40747
40947
|
rootDirectory: scanDirectory,
|
|
40748
|
-
userConfig: resolvedConfig.config,
|
|
40749
40948
|
parseConcurrency: deadCodeParseConcurrency,
|
|
40750
40949
|
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
40751
40950
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
@@ -40768,7 +40967,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40768
40967
|
});
|
|
40769
40968
|
const deadCodeFiber = shouldOverlapDeadCode ? yield* forkChild(buildCollectDeadCode({
|
|
40770
40969
|
workerTimeoutMs: overlapDeadCodeTimeout.workerTimeoutMs,
|
|
40771
|
-
phaseTimeoutMs: resolveDeadCodePhaseTimeoutMs(overlapDeadCodeTimeout.phaseTimeoutMs)
|
|
40970
|
+
phaseTimeoutMs: capToDeadline(resolveDeadCodePhaseTimeoutMs(overlapDeadCodeTimeout.phaseTimeoutMs))
|
|
40772
40971
|
})) : null;
|
|
40773
40972
|
const scanProgress = yield* progressService.start("Scanning...");
|
|
40774
40973
|
const scanStartTime = Date.now();
|
|
@@ -40793,7 +40992,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40793
40992
|
onCacheStats: (cacheHitFileCount, totalConsideredFileCount) => {
|
|
40794
40993
|
lintCacheHitFileCount = cacheHitFileCount;
|
|
40795
40994
|
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
40796
|
-
}
|
|
40995
|
+
},
|
|
40996
|
+
deadlineEpochMs: input.deadlineEpochMs
|
|
40797
40997
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
40798
40998
|
yield* set(lintFailure, {
|
|
40799
40999
|
didFail: true,
|
|
@@ -40820,7 +41020,13 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40820
41020
|
let deadCodeCollected = [];
|
|
40821
41021
|
if (lintFailureState.didFail) {
|
|
40822
41022
|
if (deadCodeFiber !== null) yield* interrupt(deadCodeFiber);
|
|
40823
|
-
} else if (shouldRunDeadCode) {
|
|
41023
|
+
} else if (shouldRunDeadCode) if (input.deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(input.deadlineEpochMs) === 0) {
|
|
41024
|
+
if (deadCodeFiber !== null) yield* interrupt(deadCodeFiber);
|
|
41025
|
+
yield* set(deadCodeFailure, {
|
|
41026
|
+
didFail: true,
|
|
41027
|
+
reason: "Dead-code analysis skipped — max scan duration reached."
|
|
41028
|
+
});
|
|
41029
|
+
} else {
|
|
40824
41030
|
yield* scanProgress.update(`Scanned ${scannedFilesLabel}, analyzing dead code...`);
|
|
40825
41031
|
const sequentialDeadCodeTimeout = resolveDeadCodeTimeout({
|
|
40826
41032
|
sourceFileCount: totalFileCount,
|
|
@@ -40829,7 +41035,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40829
41035
|
});
|
|
40830
41036
|
deadCodeCollected = deadCodeFiber !== null ? yield* join(deadCodeFiber) : yield* buildCollectDeadCode({
|
|
40831
41037
|
workerTimeoutMs: sequentialDeadCodeTimeout.workerTimeoutMs,
|
|
40832
|
-
phaseTimeoutMs: resolveDeadCodePhaseTimeoutMs(sequentialDeadCodeTimeout.phaseTimeoutMs)
|
|
41038
|
+
phaseTimeoutMs: capToDeadline(resolveDeadCodePhaseTimeoutMs(sequentialDeadCodeTimeout.phaseTimeoutMs))
|
|
40833
41039
|
});
|
|
40834
41040
|
}
|
|
40835
41041
|
const deadCodeFailureState = lintFailureState.didFail ? {
|
|
@@ -40843,9 +41049,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40843
41049
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
40844
41050
|
const supplyChainResult = yield* join(supplyChainFiber);
|
|
40845
41051
|
const supplyChainCollected = supplyChainResult.diagnostics;
|
|
41052
|
+
const securityScanCollected = yield* join(securityScanFiber);
|
|
40846
41053
|
yield* reporterService.finalize;
|
|
40847
41054
|
const finalDiagnostics = sortDiagnosticsStable(assignFixGroups([
|
|
40848
41055
|
...envCollected,
|
|
41056
|
+
...securityScanCollected,
|
|
40849
41057
|
...supplyChainCollected,
|
|
40850
41058
|
...lintCollected,
|
|
40851
41059
|
...deadCodeCollected
|
|
@@ -40865,12 +41073,13 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40865
41073
|
};
|
|
40866
41074
|
const scoreSurface = input.scoreSurface ?? "score";
|
|
40867
41075
|
const scoreDiagnostics = filterDiagnosticsForSurface([...finalDiagnostics], scoreSurface, resolvedConfig.config);
|
|
40868
|
-
const score = lintFailureState.didFail ? null : yield* scoreService.compute({
|
|
41076
|
+
const score = lintFailureState.didFail || deadCodeFailureState.didFail ? null : yield* scoreService.compute({
|
|
40869
41077
|
diagnostics: scoreDiagnostics,
|
|
40870
41078
|
isCi: input.isCi,
|
|
40871
41079
|
metadata: scoreMetadata
|
|
40872
41080
|
});
|
|
40873
41081
|
const lintPartialFailures = yield* get$2(partialFailuresRef);
|
|
41082
|
+
const securityScanFailed = yield* get$2(securityScanFailedRef);
|
|
40874
41083
|
return {
|
|
40875
41084
|
project,
|
|
40876
41085
|
userConfig: resolvedConfig.config,
|
|
@@ -40891,8 +41100,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40891
41100
|
scanElapsedMilliseconds,
|
|
40892
41101
|
scanConcurrency,
|
|
40893
41102
|
supplyChainOverlapTimedOut: supplyChainResult.timedOut,
|
|
41103
|
+
securityScanFailed,
|
|
40894
41104
|
lintCacheHitFileCount,
|
|
40895
|
-
lintCacheTotalFileCount
|
|
41105
|
+
lintCacheTotalFileCount,
|
|
41106
|
+
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
40896
41107
|
};
|
|
40897
41108
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
40898
41109
|
"inspect.directory": input.directory,
|
|
@@ -42930,6 +43141,8 @@ const METRIC = {
|
|
|
42930
43141
|
scanCheckSkipped: "scan.check_skipped",
|
|
42931
43142
|
baselineDegraded: "baseline.degraded",
|
|
42932
43143
|
ruleFired: "rule.fired",
|
|
43144
|
+
ruleDisabled: "rule.disabled",
|
|
43145
|
+
ruleSuppressed: "rule.suppressed",
|
|
42933
43146
|
lintFailed: "lint.failed",
|
|
42934
43147
|
deadCodeFailed: "deadcode.failed",
|
|
42935
43148
|
scoreUnavailable: "score.unavailable",
|
|
@@ -42942,12 +43155,17 @@ const METRIC = {
|
|
|
42942
43155
|
installWorkflow: "install.workflow",
|
|
42943
43156
|
installAgentHooks: "install.agent_hooks",
|
|
42944
43157
|
installDependency: "install.dependency",
|
|
43158
|
+
ciScaffolded: "ci.scaffolded",
|
|
43159
|
+
ciUpgraded: "ci.upgraded",
|
|
43160
|
+
ciConfigured: "ci.configured",
|
|
42945
43161
|
rulesChanged: "rules.changed",
|
|
42946
43162
|
rulesQueried: "rules.queried",
|
|
42947
43163
|
lspSessionStarted: "lsp.session.started",
|
|
42948
43164
|
lspScanCompleted: "lsp.scan.completed",
|
|
42949
43165
|
lspScanDuration: "lsp.scan.duration",
|
|
42950
|
-
lspScanDiagnostics: "lsp.scan.diagnostics"
|
|
43166
|
+
lspScanDiagnostics: "lsp.scan.diagnostics",
|
|
43167
|
+
aiTrainingWarningShown: "ai.training.warning_shown",
|
|
43168
|
+
jsonOutUsed: "json.out_used"
|
|
42951
43169
|
};
|
|
42952
43170
|
//#endregion
|
|
42953
43171
|
//#region src/cli/utils/to-category-key.ts
|
|
@@ -43264,5 +43482,5 @@ const startLanguageServer = () => {
|
|
|
43264
43482
|
};
|
|
43265
43483
|
//#endregion
|
|
43266
43484
|
export { startLanguageServer };
|
|
43267
|
-
!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]="
|
|
43268
|
-
//# debugId=
|
|
43485
|
+
!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]="6811672b-baa6-5b7c-b676-5121c592d4a0")}catch(e){}}();
|
|
43486
|
+
//# debugId=6811672b-baa6-5b7c-b676-5121c592d4a0
|