react-doctor 0.5.8-dev.fff9466 → 0.6.0-dev.113637e
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/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";
|
|
@@ -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,138 +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_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
|
|
33815
|
-
const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
|
|
33816
|
-
const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
|
|
33817
|
-
const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
|
|
33818
|
-
const RECOMMENDED_PNPM_MINIMUM_RELEASE_AGE_MINUTES = 10080;
|
|
33819
|
-
const REACT_SERVER_DOM_PACKAGES = [
|
|
33820
|
-
"react-server-dom-webpack",
|
|
33821
|
-
"react-server-dom-parcel",
|
|
33822
|
-
"react-server-dom-turbopack"
|
|
33823
|
-
];
|
|
33824
|
-
const REACT_BLOG_RSC_ADVISORY_URL = "https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components";
|
|
33825
|
-
const VERCEL_NEXTJS_SECURITY_RELEASE_URL = "https://vercel.com/changelog/next-js-may-2026-security-release";
|
|
33826
|
-
const DIAGNOSTIC_CATEGORY_BUCKETS = [
|
|
33827
|
-
"Security",
|
|
33828
|
-
"Bugs",
|
|
33829
|
-
"Performance",
|
|
33830
|
-
"Accessibility",
|
|
33831
|
-
"Maintainability"
|
|
33832
|
-
];
|
|
33833
|
-
const APP_ONLY_RULE_KEYS = new Set([
|
|
33834
|
-
"react-hooks-js/static-components",
|
|
33835
|
-
"react-doctor/no-render-prop-children",
|
|
33836
|
-
"react-doctor/prefer-explicit-variants"
|
|
33837
|
-
]);
|
|
33838
|
-
const COMPILER_CLEANUP_BUCKET = "compiler-cleanup";
|
|
33839
|
-
const COMPILER_CLEANUP_RULE_KEYS = new Set(["react-doctor/react-compiler-no-manual-memoization"]);
|
|
33840
|
-
const ROOT_CAUSE_GROUPABLE_RULE_KEYS = new Set([
|
|
33841
|
-
"react-doctor/no-derived-state",
|
|
33842
|
-
"react-doctor/no-derived-state-effect",
|
|
33843
|
-
"react-doctor/no-derived-useState",
|
|
33844
|
-
"react-doctor/no-adjust-state-on-prop-change",
|
|
33845
|
-
"react-doctor/no-reset-all-state-on-prop-change"
|
|
33846
|
-
]);
|
|
33847
|
-
const MAX_GLOB_PATTERN_LENGTH_CHARS = 1024;
|
|
33848
|
-
const CONFIG_CACHE_TTL_MS = 300 * 1e3;
|
|
33849
|
-
const SOCKET_FREE_PURL_API_BASE = "https://firewall-api.socket.dev/purl";
|
|
33850
|
-
const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
33851
|
-
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
33852
|
-
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
33853
|
-
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
33854
|
-
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
33855
|
-
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
33856
|
-
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
33857
|
-
const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
33858
|
-
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
33859
|
-
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
33860
33914
|
const TSCONFIG_FILENAME = "tsconfig.json";
|
|
33861
33915
|
const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
|
|
33862
33916
|
const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
|
|
@@ -34578,7 +34632,7 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
34578
34632
|
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34579
34633
|
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34580
34634
|
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34581
|
-
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}\`).`;
|
|
34582
34636
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34583
34637
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34584
34638
|
const candidates = [{
|
|
@@ -35054,6 +35108,7 @@ const collectStringSet = (values) => {
|
|
|
35054
35108
|
if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
|
|
35055
35109
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35056
35110
|
};
|
|
35111
|
+
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35057
35112
|
/**
|
|
35058
35113
|
* Pre-compiles every stateful filter and returns a single
|
|
35059
35114
|
* `apply(diagnostic)` closure that runs (in order):
|
|
@@ -35087,11 +35142,28 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35087
35142
|
const compiledOverrides = compileIgnoreOverrides(userConfig);
|
|
35088
35143
|
const textComponentNames = collectStringSet(userConfig?.textComponents);
|
|
35089
35144
|
const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
|
|
35145
|
+
const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
|
|
35090
35146
|
const hasTextComponents = textComponentNames.size > 0;
|
|
35091
35147
|
const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
|
|
35148
|
+
const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
|
|
35092
35149
|
const fileLinesCache = /* @__PURE__ */ new Map();
|
|
35093
35150
|
const fileContextCache = /* @__PURE__ */ new Map();
|
|
35094
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
|
+
};
|
|
35095
35167
|
const isLibraryFile = (filePath) => {
|
|
35096
35168
|
let cached = libraryFileCache.get(filePath);
|
|
35097
35169
|
if (cached === void 0) {
|
|
@@ -35140,50 +35212,60 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35140
35212
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35141
35213
|
return false;
|
|
35142
35214
|
};
|
|
35143
|
-
|
|
35144
|
-
if (
|
|
35145
|
-
|
|
35146
|
-
|
|
35147
|
-
|
|
35148
|
-
|
|
35149
|
-
|
|
35150
|
-
|
|
35151
|
-
|
|
35152
|
-
|
|
35153
|
-
|
|
35154
|
-
|
|
35155
|
-
if (
|
|
35156
|
-
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
|
|
35160
|
-
|
|
35161
|
-
|
|
35162
|
-
|
|
35163
|
-
|
|
35164
|
-
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35165
|
-
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
|
|
35166
|
-
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35167
|
-
}
|
|
35168
|
-
if (respectInlineDisables && current.line > 0) {
|
|
35169
|
-
const lines = getFileLines(current.filePath);
|
|
35170
|
-
if (lines) {
|
|
35171
|
-
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35172
|
-
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35173
|
-
if (evaluation.isSuppressed) return null;
|
|
35174
|
-
if (evaluation.nearMissHint) current = {
|
|
35175
|
-
...current,
|
|
35176
|
-
suppressionHint: evaluation.nearMissHint
|
|
35177
|
-
};
|
|
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);
|
|
35178
35236
|
}
|
|
35179
|
-
|
|
35180
|
-
|
|
35181
|
-
|
|
35182
|
-
|
|
35183
|
-
|
|
35184
|
-
|
|
35185
|
-
|
|
35186
|
-
|
|
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
|
+
};
|
|
35187
35269
|
};
|
|
35188
35270
|
const OxlintUnavailableKind = Literals(["binary-not-found", "native-binding-missing"]);
|
|
35189
35271
|
var OxlintUnavailable = class extends TaggedErrorClass()("OxlintUnavailable", {
|
|
@@ -35465,13 +35547,7 @@ var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { de
|
|
|
35465
35547
|
* without recompiling react-doctor. Tests override via
|
|
35466
35548
|
* `Layer.succeed(SupplyChainOverlapTimeoutMs, ...)`.
|
|
35467
35549
|
*/
|
|
35468
|
-
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => {
|
|
35469
|
-
const raw = process.env["REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS"];
|
|
35470
|
-
if (raw === void 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35471
|
-
const parsed = Number(raw);
|
|
35472
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35473
|
-
return parsed;
|
|
35474
|
-
} }) {};
|
|
35550
|
+
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS", SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS) }) {};
|
|
35475
35551
|
/**
|
|
35476
35552
|
* Hard cap on combined stdout+stderr bytes per oxlint batch. The
|
|
35477
35553
|
* subprocess gets SIGKILL'd if it produces more; the recovery path
|
|
@@ -35619,6 +35695,7 @@ const STRING_ARRAY_FIELD_NAMES = [
|
|
|
35619
35695
|
"projects",
|
|
35620
35696
|
"textComponents",
|
|
35621
35697
|
"rawTextWrapperComponents",
|
|
35698
|
+
"runtimeGlobals",
|
|
35622
35699
|
"serverAuthFunctionNames"
|
|
35623
35700
|
];
|
|
35624
35701
|
const SURFACE_CONTROL_FIELD_NAMES = [
|
|
@@ -36707,14 +36784,17 @@ const checkReactServerComponentsAdvisory = (scanDirectory, project) => {
|
|
|
36707
36784
|
return diagnostics;
|
|
36708
36785
|
};
|
|
36709
36786
|
const REDUCED_MOTION_GREP_PATTERN = "prefers-reduced-motion|useReducedMotion|MotionConfig|reducedMotion";
|
|
36710
|
-
const
|
|
36711
|
-
|
|
36712
|
-
"
|
|
36713
|
-
"
|
|
36714
|
-
"
|
|
36715
|
-
"
|
|
36716
|
-
"
|
|
36717
|
-
|
|
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;
|
|
36718
36798
|
const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
|
|
36719
36799
|
filePath: "package.json",
|
|
36720
36800
|
plugin: "react-doctor",
|
|
@@ -36726,6 +36806,19 @@ const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
|
|
|
36726
36806
|
column: 0,
|
|
36727
36807
|
category: "Accessibility"
|
|
36728
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
|
+
};
|
|
36729
36822
|
const checkReducedMotion = (rootDirectory) => {
|
|
36730
36823
|
const packageJsonPath = Path.join(rootDirectory, "package.json");
|
|
36731
36824
|
if (!isFile(packageJsonPath)) return [];
|
|
@@ -36744,22 +36837,21 @@ const checkReducedMotion = (rootDirectory) => {
|
|
|
36744
36837
|
const result = spawnSync("git", [
|
|
36745
36838
|
"grep",
|
|
36746
36839
|
"--untracked",
|
|
36747
|
-
"-
|
|
36748
|
-
"-E",
|
|
36840
|
+
"-lE",
|
|
36749
36841
|
REDUCED_MOTION_GREP_PATTERN,
|
|
36750
36842
|
"--",
|
|
36751
36843
|
...REDUCED_MOTION_FILE_GLOBS
|
|
36752
36844
|
], {
|
|
36753
36845
|
cwd: rootDirectory,
|
|
36846
|
+
encoding: "utf-8",
|
|
36754
36847
|
stdio: [
|
|
36755
36848
|
"ignore",
|
|
36756
36849
|
"pipe",
|
|
36757
36850
|
"pipe"
|
|
36758
36851
|
]
|
|
36759
36852
|
});
|
|
36760
|
-
if (result.error) return [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
36761
|
-
|
|
36762
|
-
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];
|
|
36763
36855
|
};
|
|
36764
36856
|
const buildSecurityScanDiagnostic = (finding, entry, relativePath) => ({
|
|
36765
36857
|
filePath: relativePath,
|
|
@@ -37655,6 +37747,7 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
|
|
|
37655
37747
|
return true;
|
|
37656
37748
|
};
|
|
37657
37749
|
const filterDiagnosticsForSurface = (diagnostics, surface, config) => diagnostics.filter((diagnostic) => isDiagnosticOnSurface(diagnostic, surface, config));
|
|
37750
|
+
const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs - Date.now(), 0);
|
|
37658
37751
|
/**
|
|
37659
37752
|
* Budget for the dead-code phase, scaled to the work. deslop's graph build is
|
|
37660
37753
|
* CPU-bound and roughly linear in file count, so a fixed 120s cap is too tight
|
|
@@ -37673,53 +37766,6 @@ const resolveDeadCodeTimeout = (input) => {
|
|
|
37673
37766
|
phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
|
|
37674
37767
|
};
|
|
37675
37768
|
};
|
|
37676
|
-
const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
|
|
37677
|
-
const entries = [];
|
|
37678
|
-
for (const relativePath of relativePaths) {
|
|
37679
|
-
const absolutePath = Path.resolve(rootDirectory, relativePath);
|
|
37680
|
-
const sizeBytes = statSourceFileSize(absolutePath);
|
|
37681
|
-
if (isLargeMinifiedFile(absolutePath, sizeBytes)) continue;
|
|
37682
|
-
entries.push({
|
|
37683
|
-
path: relativePath,
|
|
37684
|
-
sizeBytes: sizeBytes ?? 0
|
|
37685
|
-
});
|
|
37686
|
-
}
|
|
37687
|
-
return entries;
|
|
37688
|
-
};
|
|
37689
|
-
const listSourceFilesViaGit = (rootDirectory) => {
|
|
37690
|
-
const result = spawnSync("git", [
|
|
37691
|
-
"ls-files",
|
|
37692
|
-
"-z",
|
|
37693
|
-
"--cached",
|
|
37694
|
-
"--others",
|
|
37695
|
-
"--exclude-standard"
|
|
37696
|
-
], {
|
|
37697
|
-
cwd: rootDirectory,
|
|
37698
|
-
encoding: "utf-8",
|
|
37699
|
-
maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES
|
|
37700
|
-
});
|
|
37701
|
-
if (result.error || result.status !== 0) return null;
|
|
37702
|
-
return result.stdout.split("\0").filter((filePath) => filePath.length > 0 && isLintableSourceFile(filePath));
|
|
37703
|
-
};
|
|
37704
|
-
const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
37705
|
-
const filePaths = [];
|
|
37706
|
-
const stack = [rootDirectory];
|
|
37707
|
-
while (stack.length > 0) {
|
|
37708
|
-
const currentDirectory = stack.pop();
|
|
37709
|
-
const entries = readDirectoryEntries(currentDirectory);
|
|
37710
|
-
for (const entry of entries) {
|
|
37711
|
-
const absolutePath = Path.join(currentDirectory, entry.name);
|
|
37712
|
-
if (entry.isDirectory()) {
|
|
37713
|
-
if (!entry.name.startsWith(".") && !IGNORED_DIRECTORIES.has(entry.name)) stack.push(absolutePath);
|
|
37714
|
-
continue;
|
|
37715
|
-
}
|
|
37716
|
-
if (entry.isFile() && isLintableSourceFile(entry.name)) filePaths.push(Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/"));
|
|
37717
|
-
}
|
|
37718
|
-
}
|
|
37719
|
-
return filePaths;
|
|
37720
|
-
};
|
|
37721
|
-
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory));
|
|
37722
|
-
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
37723
37769
|
const resolveLintIncludePaths = (rootDirectory, userConfig, project) => {
|
|
37724
37770
|
if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
|
|
37725
37771
|
const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
|
|
@@ -37764,7 +37810,6 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
|
37764
37810
|
return yield* tryPromise({
|
|
37765
37811
|
try: (signal) => checkDeadCode({
|
|
37766
37812
|
rootDirectory: input.rootDirectory,
|
|
37767
|
-
userConfig: input.userConfig,
|
|
37768
37813
|
parseConcurrency: input.parseConcurrency,
|
|
37769
37814
|
workerTimeoutMs: input.workerTimeoutMs,
|
|
37770
37815
|
abortSignal: signal
|
|
@@ -37887,6 +37932,11 @@ const isSafeGitRevision = (candidate) => {
|
|
|
37887
37932
|
if (candidate.includes("..") || candidate.includes("@{")) return false;
|
|
37888
37933
|
return SAFE_GIT_REVISION_PATTERN.test(candidate);
|
|
37889
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
|
+
};
|
|
37890
37940
|
/**
|
|
37891
37941
|
* Splits a git revision range into its endpoints: three-dot `A...B`
|
|
37892
37942
|
* (symmetric, merge-base) or two-dot `A..B` (direct). Returns `null`
|
|
@@ -37971,7 +38021,8 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37971
38021
|
return scoped(gen(function* () {
|
|
37972
38022
|
if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
|
|
37973
38023
|
const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
|
|
37974
|
-
|
|
38024
|
+
const spawnArgsLengthCap = resolveSpawnArgsLengthCap();
|
|
38025
|
+
if (argvLengthChars > spawnArgsLengthCap) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${spawnArgsLengthCap})`);
|
|
37975
38026
|
const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
|
|
37976
38027
|
cwd: input.directory,
|
|
37977
38028
|
env: input.env,
|
|
@@ -38140,6 +38191,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38140
38191
|
if (resolvedCurrentBranch === null && explicitBaseBranch === void 0) return null;
|
|
38141
38192
|
const baseBranch = explicitBaseBranch ?? (yield* defaultBranch(directory));
|
|
38142
38193
|
if (baseBranch === null) return null;
|
|
38194
|
+
if (!isSafeGitRevision(baseBranch)) return null;
|
|
38143
38195
|
if (explicitBaseBranch !== void 0) {
|
|
38144
38196
|
if (!(yield* branchExists(directory, explicitBaseBranch))) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchMissing({ branch: explicitBaseBranch }) }));
|
|
38145
38197
|
}
|
|
@@ -38410,12 +38462,12 @@ const findFilesWithDisableDirectivesViaGit = async (rootDirectory, includePaths)
|
|
|
38410
38462
|
return null;
|
|
38411
38463
|
}
|
|
38412
38464
|
if (grepResult === null) return null;
|
|
38413
|
-
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));
|
|
38414
38466
|
};
|
|
38415
38467
|
const findFilesWithDisableDirectivesViaFilesystem = (rootDirectory, includePaths) => {
|
|
38416
38468
|
const matches = [];
|
|
38417
38469
|
const checkFile = (relativePath) => {
|
|
38418
|
-
if (!isLintableSourceFile(relativePath)) return;
|
|
38470
|
+
if (!isLintableSourceFile(relativePath) || hasIgnoredPathSegment(relativePath)) return;
|
|
38419
38471
|
const absolutePath = Path.join(rootDirectory, relativePath);
|
|
38420
38472
|
let content;
|
|
38421
38473
|
try {
|
|
@@ -38429,22 +38481,7 @@ const findFilesWithDisableDirectivesViaFilesystem = (rootDirectory, includePaths
|
|
|
38429
38481
|
for (const candidate of includePaths) checkFile(candidate);
|
|
38430
38482
|
return matches;
|
|
38431
38483
|
}
|
|
38432
|
-
const
|
|
38433
|
-
while (stack.length > 0) {
|
|
38434
|
-
const current = stack.pop();
|
|
38435
|
-
if (current === void 0) continue;
|
|
38436
|
-
const entries = readDirectoryEntries(current);
|
|
38437
|
-
for (const entry of entries) {
|
|
38438
|
-
if (entry.isDirectory()) {
|
|
38439
|
-
if (entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
38440
|
-
stack.push(Path.join(current, entry.name));
|
|
38441
|
-
continue;
|
|
38442
|
-
}
|
|
38443
|
-
if (!entry.isFile()) continue;
|
|
38444
|
-
const absolute = Path.join(current, entry.name);
|
|
38445
|
-
checkFile(Path.relative(rootDirectory, absolute));
|
|
38446
|
-
}
|
|
38447
|
-
}
|
|
38484
|
+
for (const { absolutePath } of walkSourceTreeFiles(rootDirectory)) checkFile(Path.relative(rootDirectory, absolutePath));
|
|
38448
38485
|
return matches;
|
|
38449
38486
|
};
|
|
38450
38487
|
const findFilesWithDisableDirectives = async (rootDirectory, includePaths) => await findFilesWithDisableDirectivesViaGit(rootDirectory, includePaths) ?? findFilesWithDisableDirectivesViaFilesystem(rootDirectory, includePaths);
|
|
@@ -38490,7 +38527,7 @@ const normalizeConfigForHash = (config) => {
|
|
|
38490
38527
|
if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
|
|
38491
38528
|
return clone;
|
|
38492
38529
|
};
|
|
38493
|
-
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");
|
|
38494
38531
|
/**
|
|
38495
38532
|
* Loads a plugin module via the local require resolver and extracts
|
|
38496
38533
|
* `(name, ruleNames)` from either `module.exports.meta + rules` or
|
|
@@ -38661,12 +38698,12 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38661
38698
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38662
38699
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
38663
38700
|
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledBy)) continue;
|
|
38664
|
-
const
|
|
38701
|
+
const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
|
|
38702
|
+
if (rule.defaultEnabled === false && explicitRuleOverride === void 0) continue;
|
|
38703
|
+
const severity = resolveRuleSeverityOverride({
|
|
38665
38704
|
ruleKey: registryEntry.key,
|
|
38666
38705
|
category: rule.category
|
|
38667
|
-
}, severityControls);
|
|
38668
|
-
if (rule.defaultEnabled === false && explicitSeverity === void 0) continue;
|
|
38669
|
-
const severity = explicitSeverity ?? resolveCompilerCleanupBucketSeverity(registryEntry.key, severityControls) ?? rule.severity;
|
|
38706
|
+
}, severityControls) ?? resolveCompilerCleanupBucketSeverity(registryEntry.key, severityControls) ?? rule.severity;
|
|
38670
38707
|
if (severity === "off") continue;
|
|
38671
38708
|
enabledReactDoctorRules[registryEntry.key] = severity;
|
|
38672
38709
|
}
|
|
@@ -38768,24 +38805,38 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38768
38805
|
updatedAtMs: Date.now(),
|
|
38769
38806
|
files: Object.fromEntries(cappedEntries)
|
|
38770
38807
|
};
|
|
38771
|
-
const keptHashes = Object.entries(rulesets).sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8).map(([hash]) => hash);
|
|
38772
|
-
const prunedRulesets = {};
|
|
38773
|
-
for (const hash of keptHashes) prunedRulesets[hash] = rulesets[hash];
|
|
38774
38808
|
atomicWriteJson(cacheFilePath, {
|
|
38775
38809
|
version: 1,
|
|
38776
|
-
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))
|
|
38777
38811
|
});
|
|
38778
38812
|
}
|
|
38779
38813
|
};
|
|
38780
38814
|
};
|
|
38781
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
|
+
};
|
|
38782
38833
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
38783
38834
|
"oxlint/package.json",
|
|
38784
38835
|
"oxlint-plugin-react-doctor/package.json",
|
|
38785
38836
|
"eslint-plugin-react-hooks/package.json"
|
|
38786
38837
|
];
|
|
38787
|
-
const resolveOxlintToolchainVersions = () => {
|
|
38788
|
-
const versions = [`node=${
|
|
38838
|
+
const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
|
|
38839
|
+
const versions = [`node=${resolveChildNodeVersion(nodeBinaryPath)}`];
|
|
38789
38840
|
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
38790
38841
|
const packageJson = bundledRequire(specifier);
|
|
38791
38842
|
const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
@@ -39319,11 +39370,12 @@ const REACT_COMPILER_TITLE = "React Compiler can't optimize this";
|
|
|
39319
39370
|
const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
|
|
39320
39371
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
39321
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`.";
|
|
39322
39374
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
39323
|
-
const buildReactCompilerMessage = (reasonSummary) => {
|
|
39375
|
+
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
39324
39376
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
39325
|
-
if (!normalizedSummary) return
|
|
39326
|
-
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${
|
|
39377
|
+
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
39378
|
+
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
39327
39379
|
};
|
|
39328
39380
|
const PLUGIN_CATEGORY_MAP = {
|
|
39329
39381
|
react: "Bugs",
|
|
@@ -39372,7 +39424,7 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
39372
39424
|
const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
|
|
39373
39425
|
const reasonDetail = reasonDetailLines.join("\n").trim();
|
|
39374
39426
|
return {
|
|
39375
|
-
message: buildReactCompilerMessage(reasonSummary.trim()),
|
|
39427
|
+
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
39376
39428
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
39377
39429
|
};
|
|
39378
39430
|
}
|
|
@@ -39633,28 +39685,46 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
39633
39685
|
* loop with a slimmer config in that case.
|
|
39634
39686
|
*/
|
|
39635
39687
|
const spawnLintBatches = async (input) => {
|
|
39636
|
-
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;
|
|
39637
39689
|
const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
|
|
39638
39690
|
const totalFileCount = fileBatches.reduce((sum, batch) => sum + batch.length, 0);
|
|
39639
39691
|
const runBatchPass = async (concurrency) => {
|
|
39640
39692
|
const allDiagnostics = [];
|
|
39641
39693
|
const droppedFiles = [];
|
|
39694
|
+
const deadlineSkippedFiles = [];
|
|
39695
|
+
const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0;
|
|
39642
39696
|
let firstDropReason = null;
|
|
39643
|
-
const
|
|
39644
|
-
|
|
39697
|
+
const spawnLintBatch = async (batch, depth, batchState) => {
|
|
39698
|
+
if (isPastDeadline()) {
|
|
39699
|
+
deadlineSkippedFiles.push(...batch);
|
|
39700
|
+
batchState.deadlineSkippedFileCount += batch.length;
|
|
39701
|
+
return [];
|
|
39702
|
+
}
|
|
39645
39703
|
const batchArgs = [...baseArgs, ...batch];
|
|
39646
39704
|
try {
|
|
39647
39705
|
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
|
|
39648
39706
|
} catch (error) {
|
|
39649
39707
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
39650
|
-
|
|
39651
|
-
|
|
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) {
|
|
39652
39717
|
droppedFiles.push(...batch);
|
|
39653
|
-
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
|
+
}
|
|
39654
39724
|
return [];
|
|
39655
39725
|
}
|
|
39656
39726
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
39657
|
-
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)];
|
|
39658
39728
|
}
|
|
39659
39729
|
};
|
|
39660
39730
|
let startedFileCount = 0;
|
|
@@ -39670,9 +39740,17 @@ const spawnLintBatches = async (input) => {
|
|
|
39670
39740
|
progressTimer?.unref?.();
|
|
39671
39741
|
try {
|
|
39672
39742
|
const batchResults = await mapWithConcurrency(fileBatches, concurrency, async (batch) => {
|
|
39743
|
+
if (isPastDeadline()) {
|
|
39744
|
+
deadlineSkippedFiles.push(...batch);
|
|
39745
|
+
return [];
|
|
39746
|
+
}
|
|
39673
39747
|
startedFileCount += batch.length;
|
|
39674
|
-
const
|
|
39675
|
-
|
|
39748
|
+
const batchState = {
|
|
39749
|
+
deadlineMs: null,
|
|
39750
|
+
deadlineSkippedFileCount: 0
|
|
39751
|
+
};
|
|
39752
|
+
const batchDiagnostics = await spawnLintBatch(batch, 0, batchState);
|
|
39753
|
+
scannedFileCount += batch.length - batchState.deadlineSkippedFileCount;
|
|
39676
39754
|
if (onFileProgress) {
|
|
39677
39755
|
displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
|
|
39678
39756
|
onFileProgress(displayedFileCount, totalFileCount);
|
|
@@ -39683,12 +39761,13 @@ const spawnLintBatches = async (input) => {
|
|
|
39683
39761
|
} finally {
|
|
39684
39762
|
if (progressTimer !== null) clearInterval(progressTimer);
|
|
39685
39763
|
}
|
|
39686
|
-
|
|
39687
|
-
|
|
39688
|
-
|
|
39689
|
-
|
|
39690
|
-
|
|
39691
|
-
}
|
|
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})`);
|
|
39692
39771
|
return allDiagnostics;
|
|
39693
39772
|
};
|
|
39694
39773
|
let diagnostics;
|
|
@@ -39745,7 +39824,14 @@ const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
|
39745
39824
|
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
39746
39825
|
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
39747
39826
|
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
39748
|
-
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
|
+
}
|
|
39749
39835
|
};
|
|
39750
39836
|
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39751
39837
|
/**
|
|
@@ -39885,7 +39971,8 @@ const runOxlint = async (options) => {
|
|
|
39885
39971
|
spawnTimeoutMs,
|
|
39886
39972
|
outputMaxBytes,
|
|
39887
39973
|
concurrency: options.concurrency,
|
|
39888
|
-
signal: options.signal
|
|
39974
|
+
signal: options.signal,
|
|
39975
|
+
deadlineEpochMs: options.deadlineEpochMs
|
|
39889
39976
|
});
|
|
39890
39977
|
writeOxlintConfig(passConfigPath, buildConfigForPass({}));
|
|
39891
39978
|
try {
|
|
@@ -39913,7 +40000,7 @@ const runOxlint = async (options) => {
|
|
|
39913
40000
|
extendsPaths: [],
|
|
39914
40001
|
ruleSelection: "cacheable"
|
|
39915
40002
|
}),
|
|
39916
|
-
toolchainVersions: resolveOxlintToolchainVersions(),
|
|
40003
|
+
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
39917
40004
|
ignorePatterns: combinedPatterns,
|
|
39918
40005
|
tsconfigContent
|
|
39919
40006
|
});
|
|
@@ -39927,7 +40014,7 @@ const runOxlint = async (options) => {
|
|
|
39927
40014
|
missFiles.push(candidateFile);
|
|
39928
40015
|
continue;
|
|
39929
40016
|
}
|
|
39930
|
-
const cacheKey = `${candidateFile.replaceAll("\\", "/")}
|
|
40017
|
+
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
39931
40018
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
39932
40019
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
39933
40020
|
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
@@ -39984,7 +40071,8 @@ const runOxlint = async (options) => {
|
|
|
39984
40071
|
spawnTimeoutMs,
|
|
39985
40072
|
outputMaxBytes,
|
|
39986
40073
|
concurrency: options.concurrency,
|
|
39987
|
-
signal: options.signal
|
|
40074
|
+
signal: options.signal,
|
|
40075
|
+
deadlineEpochMs: options.deadlineEpochMs
|
|
39988
40076
|
});
|
|
39989
40077
|
writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
|
|
39990
40078
|
try {
|
|
@@ -40088,7 +40176,8 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40088
40176
|
outputMaxBytes,
|
|
40089
40177
|
concurrency,
|
|
40090
40178
|
signal,
|
|
40091
|
-
lintBatchOrdering
|
|
40179
|
+
lintBatchOrdering,
|
|
40180
|
+
deadlineEpochMs: input.deadlineEpochMs
|
|
40092
40181
|
}),
|
|
40093
40182
|
catch: ensureReactDoctorError
|
|
40094
40183
|
});
|
|
@@ -40243,7 +40332,7 @@ const ScoreApiResponseSchema = Struct({
|
|
|
40243
40332
|
})))
|
|
40244
40333
|
});
|
|
40245
40334
|
const parseScoreResult = (value) => getOrNull(decodeUnknownOption(ScoreApiResponseSchema)(value));
|
|
40246
|
-
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);
|
|
40247
40336
|
const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
40248
40337
|
const describeFailure = (error) => {
|
|
40249
40338
|
if (isAbortError(error)) return `timed out after ${FETCH_TIMEOUT_MS / 1e3}s`;
|
|
@@ -40547,7 +40636,10 @@ const ALERT_SEVERITY_RANK = {
|
|
|
40547
40636
|
medium: 2,
|
|
40548
40637
|
low: 1
|
|
40549
40638
|
};
|
|
40550
|
-
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
|
+
};
|
|
40551
40643
|
const displaySeverity = (severity) => {
|
|
40552
40644
|
const normalized = sanitizeTerminalText(severity.toLowerCase());
|
|
40553
40645
|
return normalized === "middle" ? "medium" : normalized;
|
|
@@ -40564,7 +40656,7 @@ const ALERT_TYPE_LABELS = {
|
|
|
40564
40656
|
typosquat: "possible typosquat"
|
|
40565
40657
|
};
|
|
40566
40658
|
const humanizeAlertType = (type) => type.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase().trim();
|
|
40567
|
-
const friendlyAlertType = (type) => ALERT_TYPE_LABELS[type]
|
|
40659
|
+
const friendlyAlertType = (type) => Object.hasOwn(ALERT_TYPE_LABELS, type) ? ALERT_TYPE_LABELS[type] : sanitizeTerminalText(humanizeAlertType(type));
|
|
40568
40660
|
const summarizeAlertNote = (note) => {
|
|
40569
40661
|
const collapsed = sanitizeTerminalText(note.replace(/\s+/g, " ").trim());
|
|
40570
40662
|
const firstSentence = collapsed.split(/(?<=\.)\s/)[0] || collapsed;
|
|
@@ -40659,7 +40751,10 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
40659
40751
|
* outage.
|
|
40660
40752
|
*/
|
|
40661
40753
|
var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
|
|
40662
|
-
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"))) }));
|
|
40663
40758
|
static layerOf = (diagnostics) => succeed$3(SupplyChain, SupplyChain.of({ run: () => fromIterable$1(diagnostics) }));
|
|
40664
40759
|
};
|
|
40665
40760
|
const getObjectProperty = (value, propertyName) => {
|
|
@@ -40807,10 +40902,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40807
40902
|
...checkExpoProject(scanDirectory, project),
|
|
40808
40903
|
...checkReactNativeProject(scanDirectory, project)
|
|
40809
40904
|
])));
|
|
40810
|
-
const
|
|
40905
|
+
const securityScanFailedRef = yield* make$13(false);
|
|
40906
|
+
const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
|
|
40811
40907
|
project,
|
|
40812
40908
|
ignoredTags: input.ignoredTags
|
|
40813
|
-
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)))))).pipe(withSpan("SecurityScan.run")));
|
|
40909
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
|
|
40814
40910
|
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
40815
40911
|
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
40816
40912
|
const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
|
|
@@ -40841,6 +40937,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40841
40937
|
const deadCodePhaseTimeoutMs = yield* DeadCodePhaseTimeoutMs;
|
|
40842
40938
|
const resolveDeadCodePhaseTimeoutMs = (scaledPhaseTimeoutMs) => deadCodePhaseTimeoutMs === 15e4 ? scaledPhaseTimeoutMs : deadCodePhaseTimeoutMs;
|
|
40843
40939
|
const workerCountSuffix = scanConcurrency > 1 ? ` ${highlighter.dim(`[~${scanConcurrency} workers]`)}` : "";
|
|
40940
|
+
const capToDeadline = (phaseTimeoutMs) => input.deadlineEpochMs === void 0 ? phaseTimeoutMs : Math.min(phaseTimeoutMs, remainingDeadlineBudgetMs(input.deadlineEpochMs));
|
|
40844
40941
|
const shouldRunDeadCode = input.runDeadCode && !isDiffMode && (showWarnings || deadCodeMaySurfaceWhenWarningsHidden(resolvedConfig.config));
|
|
40845
40942
|
const deadCodeOverlapMode = yield* DeadCodeOverlap;
|
|
40846
40943
|
const shouldOverlapDeadCode = shouldRunDeadCode && deadCodeOverlapMode === "on";
|
|
@@ -40848,7 +40945,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40848
40945
|
const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
|
|
40849
40946
|
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
40850
40947
|
rootDirectory: scanDirectory,
|
|
40851
|
-
userConfig: resolvedConfig.config,
|
|
40852
40948
|
parseConcurrency: deadCodeParseConcurrency,
|
|
40853
40949
|
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
40854
40950
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
@@ -40871,7 +40967,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40871
40967
|
});
|
|
40872
40968
|
const deadCodeFiber = shouldOverlapDeadCode ? yield* forkChild(buildCollectDeadCode({
|
|
40873
40969
|
workerTimeoutMs: overlapDeadCodeTimeout.workerTimeoutMs,
|
|
40874
|
-
phaseTimeoutMs: resolveDeadCodePhaseTimeoutMs(overlapDeadCodeTimeout.phaseTimeoutMs)
|
|
40970
|
+
phaseTimeoutMs: capToDeadline(resolveDeadCodePhaseTimeoutMs(overlapDeadCodeTimeout.phaseTimeoutMs))
|
|
40875
40971
|
})) : null;
|
|
40876
40972
|
const scanProgress = yield* progressService.start("Scanning...");
|
|
40877
40973
|
const scanStartTime = Date.now();
|
|
@@ -40896,7 +40992,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40896
40992
|
onCacheStats: (cacheHitFileCount, totalConsideredFileCount) => {
|
|
40897
40993
|
lintCacheHitFileCount = cacheHitFileCount;
|
|
40898
40994
|
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
40899
|
-
}
|
|
40995
|
+
},
|
|
40996
|
+
deadlineEpochMs: input.deadlineEpochMs
|
|
40900
40997
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
40901
40998
|
yield* set(lintFailure, {
|
|
40902
40999
|
didFail: true,
|
|
@@ -40923,7 +41020,13 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40923
41020
|
let deadCodeCollected = [];
|
|
40924
41021
|
if (lintFailureState.didFail) {
|
|
40925
41022
|
if (deadCodeFiber !== null) yield* interrupt(deadCodeFiber);
|
|
40926
|
-
} 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 {
|
|
40927
41030
|
yield* scanProgress.update(`Scanned ${scannedFilesLabel}, analyzing dead code...`);
|
|
40928
41031
|
const sequentialDeadCodeTimeout = resolveDeadCodeTimeout({
|
|
40929
41032
|
sourceFileCount: totalFileCount,
|
|
@@ -40932,7 +41035,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40932
41035
|
});
|
|
40933
41036
|
deadCodeCollected = deadCodeFiber !== null ? yield* join(deadCodeFiber) : yield* buildCollectDeadCode({
|
|
40934
41037
|
workerTimeoutMs: sequentialDeadCodeTimeout.workerTimeoutMs,
|
|
40935
|
-
phaseTimeoutMs: resolveDeadCodePhaseTimeoutMs(sequentialDeadCodeTimeout.phaseTimeoutMs)
|
|
41038
|
+
phaseTimeoutMs: capToDeadline(resolveDeadCodePhaseTimeoutMs(sequentialDeadCodeTimeout.phaseTimeoutMs))
|
|
40936
41039
|
});
|
|
40937
41040
|
}
|
|
40938
41041
|
const deadCodeFailureState = lintFailureState.didFail ? {
|
|
@@ -40970,12 +41073,13 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40970
41073
|
};
|
|
40971
41074
|
const scoreSurface = input.scoreSurface ?? "score";
|
|
40972
41075
|
const scoreDiagnostics = filterDiagnosticsForSurface([...finalDiagnostics], scoreSurface, resolvedConfig.config);
|
|
40973
|
-
const score = lintFailureState.didFail ? null : yield* scoreService.compute({
|
|
41076
|
+
const score = lintFailureState.didFail || deadCodeFailureState.didFail ? null : yield* scoreService.compute({
|
|
40974
41077
|
diagnostics: scoreDiagnostics,
|
|
40975
41078
|
isCi: input.isCi,
|
|
40976
41079
|
metadata: scoreMetadata
|
|
40977
41080
|
});
|
|
40978
41081
|
const lintPartialFailures = yield* get$2(partialFailuresRef);
|
|
41082
|
+
const securityScanFailed = yield* get$2(securityScanFailedRef);
|
|
40979
41083
|
return {
|
|
40980
41084
|
project,
|
|
40981
41085
|
userConfig: resolvedConfig.config,
|
|
@@ -40996,8 +41100,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40996
41100
|
scanElapsedMilliseconds,
|
|
40997
41101
|
scanConcurrency,
|
|
40998
41102
|
supplyChainOverlapTimedOut: supplyChainResult.timedOut,
|
|
41103
|
+
securityScanFailed,
|
|
40999
41104
|
lintCacheHitFileCount,
|
|
41000
|
-
lintCacheTotalFileCount
|
|
41105
|
+
lintCacheTotalFileCount,
|
|
41106
|
+
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
41001
41107
|
};
|
|
41002
41108
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
41003
41109
|
"inspect.directory": input.directory,
|
|
@@ -41326,7 +41432,7 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
41326
41432
|
/** Display name used in client-facing messages and progress titles. */
|
|
41327
41433
|
const SERVER_DISPLAY_NAME = "React Doctor";
|
|
41328
41434
|
/** Server version reported in `serverInfo`; injected at build, `dev` from source. */
|
|
41329
|
-
const SERVER_VERSION = "0.
|
|
41435
|
+
const SERVER_VERSION = "0.6.0";
|
|
41330
41436
|
/** `Diagnostic.source` shown next to every published diagnostic. */
|
|
41331
41437
|
const DIAGNOSTIC_SOURCE = "react-doctor";
|
|
41332
41438
|
/**
|
|
@@ -43035,6 +43141,8 @@ const METRIC = {
|
|
|
43035
43141
|
scanCheckSkipped: "scan.check_skipped",
|
|
43036
43142
|
baselineDegraded: "baseline.degraded",
|
|
43037
43143
|
ruleFired: "rule.fired",
|
|
43144
|
+
ruleDisabled: "rule.disabled",
|
|
43145
|
+
ruleSuppressed: "rule.suppressed",
|
|
43038
43146
|
lintFailed: "lint.failed",
|
|
43039
43147
|
deadCodeFailed: "deadcode.failed",
|
|
43040
43148
|
scoreUnavailable: "score.unavailable",
|
|
@@ -43047,6 +43155,9 @@ const METRIC = {
|
|
|
43047
43155
|
installWorkflow: "install.workflow",
|
|
43048
43156
|
installAgentHooks: "install.agent_hooks",
|
|
43049
43157
|
installDependency: "install.dependency",
|
|
43158
|
+
ciScaffolded: "ci.scaffolded",
|
|
43159
|
+
ciUpgraded: "ci.upgraded",
|
|
43160
|
+
ciConfigured: "ci.configured",
|
|
43050
43161
|
rulesChanged: "rules.changed",
|
|
43051
43162
|
rulesQueried: "rules.queried",
|
|
43052
43163
|
lspSessionStarted: "lsp.session.started",
|
|
@@ -43371,5 +43482,5 @@ const startLanguageServer = () => {
|
|
|
43371
43482
|
};
|
|
43372
43483
|
//#endregion
|
|
43373
43484
|
export { startLanguageServer };
|
|
43374
|
-
!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]="
|
|
43375
|
-
//# 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]="d75c00e3-8628-559b-ac80-d7c571aca1d1")}catch(e){}}();
|
|
43486
|
+
//# debugId=d75c00e3-8628-559b-ac80-d7c571aca1d1
|