react-doctor 0.7.1-dev.64452aa → 0.7.1-dev.7cf4d0b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +519 -54
- package/dist/index.d.ts +7 -0
- package/dist/index.js +498 -44
- package/dist/lsp.js +498 -44
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="7b7c23b7-250e-5339-af06-e3e3b5eb5f98")}catch(e){}}();
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as NodeChildProcess from "node:child_process";
|
|
5
5
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -9,7 +9,7 @@ import * as NFS from "node:fs";
|
|
|
9
9
|
import fs, { mkdtempSync, rmSync } from "node:fs";
|
|
10
10
|
import process$1 from "node:process";
|
|
11
11
|
import * as ts from "typescript";
|
|
12
|
-
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
12
|
+
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
13
13
|
import * as OS from "node:os";
|
|
14
14
|
import os, { tmpdir } from "node:os";
|
|
15
15
|
import { parseJSON5 } from "confbox";
|
|
@@ -35968,8 +35968,10 @@ const isFile = (filePath) => {
|
|
|
35968
35968
|
};
|
|
35969
35969
|
const SOURCE_FILE_PATTERN = /\.(tsx?|jsx?|mts|mjs)$/;
|
|
35970
35970
|
const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
|
|
35971
|
+
const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
|
|
35971
35972
|
const MINIFIED_SNIFF_BYTES = 65536;
|
|
35972
35973
|
const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
35974
|
+
const SCANNED_DOT_DIRECTORIES = new Set([".dumi", ".storybook"]);
|
|
35973
35975
|
const IGNORED_DIRECTORIES = new Set([
|
|
35974
35976
|
".angular",
|
|
35975
35977
|
".astro",
|
|
@@ -36154,8 +36156,9 @@ const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
|
36154
36156
|
const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
|
|
36155
36157
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
36156
36158
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
36157
|
-
const
|
|
36158
|
-
const
|
|
36159
|
+
const isIgnoredDirectoryName = (directoryName) => IGNORED_DIRECTORIES.has(directoryName) || directoryName.startsWith(".") && !SCANNED_DOT_DIRECTORIES.has(directoryName);
|
|
36160
|
+
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => isIgnoredDirectoryName(segment));
|
|
36161
|
+
const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath) && !GENERATED_SOURCE_DIRECTORY_PATTERN.test(filePath);
|
|
36159
36162
|
const isMinifiedSource = (absolutePath) => {
|
|
36160
36163
|
let fileDescriptor;
|
|
36161
36164
|
try {
|
|
@@ -36216,7 +36219,7 @@ function* walkSourceTreeFiles(rootDirectory) {
|
|
|
36216
36219
|
for (const entry of readDirectoryEntries(currentDirectory)) {
|
|
36217
36220
|
const absolutePath = Path.join(currentDirectory, entry.name);
|
|
36218
36221
|
if (entry.isDirectory()) {
|
|
36219
|
-
if (!
|
|
36222
|
+
if (!isIgnoredDirectoryName(entry.name)) stack.push(absolutePath);
|
|
36220
36223
|
continue;
|
|
36221
36224
|
}
|
|
36222
36225
|
if (entry.isFile()) yield {
|
|
@@ -36262,6 +36265,23 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
|
36262
36265
|
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
|
|
36263
36266
|
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
36264
36267
|
const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
|
|
36268
|
+
/**
|
|
36269
|
+
* The chain of ancestor directories of `startDirectory`, nearest first, up to
|
|
36270
|
+
* and including the filesystem root. With `includeStart`, the chain begins at
|
|
36271
|
+
* `startDirectory` itself; otherwise at its parent. The one place the "walk up
|
|
36272
|
+
* the tree" traversal is expressed, so each caller adds only its own match /
|
|
36273
|
+
* stop policy instead of re-deriving the `path.dirname` loop.
|
|
36274
|
+
*/
|
|
36275
|
+
const ancestorDirectories = (startDirectory, { includeStart }) => {
|
|
36276
|
+
const directories = [];
|
|
36277
|
+
let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
|
|
36278
|
+
while (true) {
|
|
36279
|
+
directories.push(currentDirectory);
|
|
36280
|
+
const parentDirectory = Path.dirname(currentDirectory);
|
|
36281
|
+
if (parentDirectory === currentDirectory) return directories;
|
|
36282
|
+
currentDirectory = parentDirectory;
|
|
36283
|
+
}
|
|
36284
|
+
};
|
|
36265
36285
|
const cachedPackageJsons = /* @__PURE__ */ new Map();
|
|
36266
36286
|
const readPackageJsonUncached = (packageJsonPath) => {
|
|
36267
36287
|
try {
|
|
@@ -36292,11 +36312,7 @@ const isMonorepoRoot = (directory) => {
|
|
|
36292
36312
|
return Array.isArray(packageJson.workspaces) || Boolean(packageJson.workspaces?.packages);
|
|
36293
36313
|
};
|
|
36294
36314
|
const findMonorepoRoot = (startDirectory) => {
|
|
36295
|
-
|
|
36296
|
-
while (currentDirectory !== Path.dirname(currentDirectory)) {
|
|
36297
|
-
if (isMonorepoRoot(currentDirectory)) return currentDirectory;
|
|
36298
|
-
currentDirectory = Path.dirname(currentDirectory);
|
|
36299
|
-
}
|
|
36315
|
+
for (const directory of ancestorDirectories(startDirectory, { includeStart: false })) if (isMonorepoRoot(directory)) return directory;
|
|
36300
36316
|
return null;
|
|
36301
36317
|
};
|
|
36302
36318
|
/**
|
|
@@ -36942,6 +36958,74 @@ const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
|
36942
36958
|
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
36943
36959
|
};
|
|
36944
36960
|
};
|
|
36961
|
+
/**
|
|
36962
|
+
* Walk up from `startDirectory` to the nearest ancestor that owns a
|
|
36963
|
+
* `package.json`, stopping at (and including) the enclosing project boundary
|
|
36964
|
+
* — the working tree's git root or a monorepo root. Lets a scan of a package
|
|
36965
|
+
* subfolder that has no `package.json` of its own adopt the nearest enclosing
|
|
36966
|
+
* package as its project root, so it inherits that package's dependency +
|
|
36967
|
+
* framework detection instead of synthesizing an empty (React-blind) project.
|
|
36968
|
+
*
|
|
36969
|
+
* Generalizes the older monorepo-root-only lookup: the nearest ancestor is the
|
|
36970
|
+
* most specific owning package (a leaf workspace, not just the repo root), so
|
|
36971
|
+
* a subfolder of one workspace no longer borrows a sibling's capabilities.
|
|
36972
|
+
*
|
|
36973
|
+
* Returns `null` when no ancestor `package.json` exists inside the project
|
|
36974
|
+
* boundary (a loose tree of files, or a subfolder of a package-less repo).
|
|
36975
|
+
*/
|
|
36976
|
+
const findNearestAncestorPackageJson = (startDirectory) => {
|
|
36977
|
+
if (isProjectBoundary(startDirectory)) return null;
|
|
36978
|
+
for (const directory of ancestorDirectories(startDirectory, { includeStart: false })) {
|
|
36979
|
+
if (isFile(Path.join(directory, "package.json"))) return directory;
|
|
36980
|
+
if (isProjectBoundary(directory)) return null;
|
|
36981
|
+
}
|
|
36982
|
+
return null;
|
|
36983
|
+
};
|
|
36984
|
+
/**
|
|
36985
|
+
* Last-resort React detection: locate React the way Node's `node_modules`
|
|
36986
|
+
* resolution would from `directory` — check each `node_modules/react` up the
|
|
36987
|
+
* tree — but bounded to the enclosing repo so a globally installed React can't
|
|
36988
|
+
* masquerade as the project's version. Stops at (and including) the nearest
|
|
36989
|
+
* project boundary — the git root or a monorepo root — and returns the first
|
|
36990
|
+
* installed `version`.
|
|
36991
|
+
*
|
|
36992
|
+
* Makes "React is installed and importable" ⇒ "React is detected" an invariant
|
|
36993
|
+
* for packages whose only React declaration is a version-less spec
|
|
36994
|
+
* (`workspace:*`, `*`, a dist-tag) or where React lives solely in a hoisted
|
|
36995
|
+
* `node_modules` the declaration walks never reach — the profile of a component
|
|
36996
|
+
* package inside a monorepo. Matching the `node_modules/react` entry rather than
|
|
36997
|
+
* its realpath means a project whose `node_modules` is symlinked elsewhere (a
|
|
36998
|
+
* Docker volume, a shared store) still counts, while a global install outside
|
|
36999
|
+
* the repo does not.
|
|
37000
|
+
*
|
|
37001
|
+
* Returns `null` when React isn't installed within the repo or its package.json
|
|
37002
|
+
* carries no version string. A tree with no boundary marker at all bounds the
|
|
37003
|
+
* search to `directory`, so a dependency hoisted above the scanned package there
|
|
37004
|
+
* is conservatively not adopted.
|
|
37005
|
+
*/
|
|
37006
|
+
/**
|
|
37007
|
+
* The nearest enclosing project boundary (git root or monorepo root), or
|
|
37008
|
+
* `directory` itself when the scan target sits outside any repo. Bounds the
|
|
37009
|
+
* node_modules walk so a React hoisted anywhere inside the repo counts, but a
|
|
37010
|
+
* React in an ancestor above the repo — a home-directory or global
|
|
37011
|
+
* `node_modules` — can't leak in.
|
|
37012
|
+
*/
|
|
37013
|
+
const findContainmentRoot = (directory) => {
|
|
37014
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) if (isProjectBoundary(ancestorDirectory)) return ancestorDirectory;
|
|
37015
|
+
return directory;
|
|
37016
|
+
};
|
|
37017
|
+
const resolveInstalledReactVersion = (directory) => {
|
|
37018
|
+
const containmentRoot = findContainmentRoot(directory);
|
|
37019
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) {
|
|
37020
|
+
const reactPackageJsonPath = Path.join(ancestorDirectory, "node_modules", "react", "package.json");
|
|
37021
|
+
if (isFile(reactPackageJsonPath)) {
|
|
37022
|
+
const installedVersion = readPackageJson$1(reactPackageJsonPath).version;
|
|
37023
|
+
return typeof installedVersion === "string" ? installedVersion : null;
|
|
37024
|
+
}
|
|
37025
|
+
if (ancestorDirectory === containmentRoot) return null;
|
|
37026
|
+
}
|
|
37027
|
+
return null;
|
|
37028
|
+
};
|
|
36945
37029
|
const findInWorkspacePackageJsons = (rootDirectory, rootPackageJson, select) => {
|
|
36946
37030
|
const rootValue = select(rootPackageJson);
|
|
36947
37031
|
if (rootValue !== null) return rootValue;
|
|
@@ -37098,6 +37182,10 @@ const normalizeCompilerOptions = (compilerOptions) => {
|
|
|
37098
37182
|
hasExplicitLib
|
|
37099
37183
|
};
|
|
37100
37184
|
};
|
|
37185
|
+
const normalizeReferencePaths = (references) => {
|
|
37186
|
+
if (!Array.isArray(references)) return [];
|
|
37187
|
+
return references.map((reference) => isPlainObject(reference) && typeof reference.path === "string" ? reference.path : null).filter((referencePath) => referencePath !== null);
|
|
37188
|
+
};
|
|
37101
37189
|
const readTsConfig = (filePath) => {
|
|
37102
37190
|
let content;
|
|
37103
37191
|
try {
|
|
@@ -37109,6 +37197,7 @@ const readTsConfig = (filePath) => {
|
|
|
37109
37197
|
if (!isPlainObject(parsed.config)) return null;
|
|
37110
37198
|
return {
|
|
37111
37199
|
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
37200
|
+
referencePaths: normalizeReferencePaths(parsed.config.references),
|
|
37112
37201
|
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
37113
37202
|
};
|
|
37114
37203
|
};
|
|
@@ -37149,20 +37238,37 @@ const libEntryIncludesES2023Array = (entry) => {
|
|
|
37149
37238
|
};
|
|
37150
37239
|
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
37151
37240
|
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
37241
|
+
if (compilerOptions.target && targetYearIsPreES2023(compilerOptions.target)) return true;
|
|
37152
37242
|
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
37153
|
-
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
37154
37243
|
return false;
|
|
37155
37244
|
};
|
|
37156
37245
|
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
37157
|
-
const detectPreES2023FromConfig = (tsConfigPath) => {
|
|
37246
|
+
const detectPreES2023FromConfig = (tsConfigPath, visitedConfigPaths = /* @__PURE__ */ new Set()) => {
|
|
37247
|
+
if (visitedConfigPaths.has(tsConfigPath)) return false;
|
|
37158
37248
|
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
37159
37249
|
if (!compilerOptions) return false;
|
|
37160
|
-
if (!compilerOptionsDeclareTargetOrLib(compilerOptions))
|
|
37250
|
+
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) {
|
|
37251
|
+
const tsConfig = readTsConfig(tsConfigPath);
|
|
37252
|
+
if (!tsConfig) return false;
|
|
37253
|
+
const nextVisitedConfigPaths = new Set(visitedConfigPaths);
|
|
37254
|
+
nextVisitedConfigPaths.add(tsConfigPath);
|
|
37255
|
+
const configDirectory = Path.dirname(tsConfigPath);
|
|
37256
|
+
return tsConfig.referencePaths.some((referencePath) => {
|
|
37257
|
+
const resolvedReferencePath = Path.resolve(configDirectory, referencePath);
|
|
37258
|
+
const referencedConfigPath = isFile(resolvedReferencePath) ? resolvedReferencePath : Path.join(resolvedReferencePath, TSCONFIG_FILENAME);
|
|
37259
|
+
return isFile(referencedConfigPath) && detectPreES2023FromConfig(referencedConfigPath, nextVisitedConfigPaths);
|
|
37260
|
+
});
|
|
37261
|
+
}
|
|
37161
37262
|
return compilerOptionsArePreES2023(compilerOptions);
|
|
37162
37263
|
};
|
|
37264
|
+
const FALLBACK_TSCONFIG_FILENAMES = ["tsconfig.app.json", "tsconfig.build.json"];
|
|
37163
37265
|
const detectPreES2023Target = (directory) => {
|
|
37164
37266
|
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
37165
37267
|
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
37268
|
+
for (const fallbackFilename of FALLBACK_TSCONFIG_FILENAMES) {
|
|
37269
|
+
const fallbackPath = Path.join(directory, fallbackFilename);
|
|
37270
|
+
if (isFile(fallbackPath)) return detectPreES2023FromConfig(fallbackPath);
|
|
37271
|
+
}
|
|
37166
37272
|
return false;
|
|
37167
37273
|
};
|
|
37168
37274
|
const REACT_DEPENDENCY_NAMES = new Set([
|
|
@@ -37278,19 +37384,20 @@ const discoverReactSubprojects = (rootDirectory) => {
|
|
|
37278
37384
|
const cachedProjectInfos = /* @__PURE__ */ new Map();
|
|
37279
37385
|
/**
|
|
37280
37386
|
* Build a `ProjectInfo` for a directory that has no `package.json` of
|
|
37281
|
-
* its own — a
|
|
37282
|
-
* of TypeScript/JavaScript files. Dependency + framework
|
|
37283
|
-
* inherited from the enclosing
|
|
37284
|
-
*
|
|
37285
|
-
*
|
|
37286
|
-
*
|
|
37287
|
-
*
|
|
37387
|
+
* its own — a package subfolder like `repo/packages` or `app/src/features`,
|
|
37388
|
+
* or any loose tree of TypeScript/JavaScript files. Dependency + framework
|
|
37389
|
+
* detection is inherited from the nearest enclosing package (a leaf
|
|
37390
|
+
* workspace, a plain app root, or a monorepo root — whichever is closest,
|
|
37391
|
+
* bounded by the git root), so scanning a subdirectory of a React project
|
|
37392
|
+
* still gets the React capabilities; a standalone non-React directory simply
|
|
37393
|
+
* scans with the framework-agnostic rules. Throws only when the directory has
|
|
37394
|
+
* nothing to scan (no enclosing project and no source files of its own).
|
|
37288
37395
|
*/
|
|
37289
37396
|
const discoverProjectWithoutPackageJson = (directory) => {
|
|
37290
37397
|
const sourceFileCount = countSourceFiles(directory);
|
|
37291
37398
|
const hasOwnTsConfig = NFS.existsSync(Path.join(directory, "tsconfig.json"));
|
|
37292
|
-
const
|
|
37293
|
-
const enclosingProject =
|
|
37399
|
+
const enclosingProjectRoot = findNearestAncestorPackageJson(directory);
|
|
37400
|
+
const enclosingProject = enclosingProjectRoot !== null ? discoverProject(enclosingProjectRoot) : null;
|
|
37294
37401
|
if (enclosingProject !== null) return {
|
|
37295
37402
|
...enclosingProject,
|
|
37296
37403
|
rootDirectory: directory,
|
|
@@ -37320,6 +37427,7 @@ const discoverProjectWithoutPackageJson = (directory) => {
|
|
|
37320
37427
|
shopifyFlashListVersion: null,
|
|
37321
37428
|
shopifyFlashListMajorVersion: null,
|
|
37322
37429
|
hasReanimated: false,
|
|
37430
|
+
reanimatedVersion: null,
|
|
37323
37431
|
isPreES2023Target: hasOwnTsConfig && detectPreES2023Target(directory),
|
|
37324
37432
|
sourceFileCount
|
|
37325
37433
|
};
|
|
@@ -37394,6 +37502,7 @@ const discoverProject = (directory) => {
|
|
|
37394
37502
|
if (!reactVersion && reactDeclaration.version && !isCatalogReference(reactDeclaration.version)) reactVersion = reactDeclaration.version;
|
|
37395
37503
|
if (!tailwindVersion && tailwindDeclaration.version && !isCatalogReference(tailwindDeclaration.version)) tailwindVersion = tailwindDeclaration.version;
|
|
37396
37504
|
if (!zodVersion && zodDeclaration.version && !isCatalogReference(zodDeclaration.version)) zodVersion = zodDeclaration.version;
|
|
37505
|
+
if (!reactVersion || parseReactMajor(reactVersion) === null) reactVersion = resolveInstalledReactVersion(directory) ?? reactVersion;
|
|
37397
37506
|
const projectName = packageJson.name ?? Path.basename(directory);
|
|
37398
37507
|
const hasTypeScript = NFS.existsSync(Path.join(directory, "tsconfig.json"));
|
|
37399
37508
|
const sourceFileCount = countSourceFiles(directory);
|
|
@@ -37411,6 +37520,7 @@ const discoverProject = (directory) => {
|
|
|
37411
37520
|
version: findShopifyFlashListVersion(directory, packageJson)
|
|
37412
37521
|
}) : null;
|
|
37413
37522
|
const hasReanimated = hasReactNativeWorkspace && someWorkspacePackageJson(directory, packageJson, isPackageJsonReanimatedAware);
|
|
37523
|
+
const reanimatedVersion = hasReanimated ? findInWorkspacePackageJsons(directory, packageJson, (workspacePackageJson) => getDependencySpec(workspacePackageJson, REANIMATED_DEPENDENCY_NAME)) : null;
|
|
37414
37524
|
const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
|
|
37415
37525
|
rootDirectory: directory,
|
|
37416
37526
|
rootPackageJson: packageJson,
|
|
@@ -37440,6 +37550,7 @@ const discoverProject = (directory) => {
|
|
|
37440
37550
|
shopifyFlashListVersion,
|
|
37441
37551
|
shopifyFlashListMajorVersion: shopifyFlashListVersion === null ? null : getLowestDependencyMajor(shopifyFlashListVersion),
|
|
37442
37552
|
hasReanimated,
|
|
37553
|
+
reanimatedVersion,
|
|
37443
37554
|
isPreES2023Target,
|
|
37444
37555
|
sourceFileCount
|
|
37445
37556
|
};
|
|
@@ -37632,6 +37743,9 @@ const buildRuleSeverityControls = (config) => {
|
|
|
37632
37743
|
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
37633
37744
|
};
|
|
37634
37745
|
};
|
|
37746
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
37747
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
37748
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
37635
37749
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
37636
37750
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
37637
37751
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -37709,6 +37823,7 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
|
37709
37823
|
"react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
|
|
37710
37824
|
"react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
|
|
37711
37825
|
"react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
|
|
37826
|
+
"react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
|
|
37712
37827
|
"react/jsx-no-undef": "react-doctor/jsx-no-undef",
|
|
37713
37828
|
"react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
|
|
37714
37829
|
"react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
|
|
@@ -37793,9 +37908,6 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
37793
37908
|
if (!ruleSection) return [];
|
|
37794
37909
|
return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
|
|
37795
37910
|
};
|
|
37796
|
-
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
37797
|
-
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
37798
|
-
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
37799
37911
|
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}\`).`;
|
|
37800
37912
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
37801
37913
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
@@ -37849,6 +37961,53 @@ const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
37849
37961
|
if (!ruleId.startsWith("react-doctor/")) return null;
|
|
37850
37962
|
return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
|
|
37851
37963
|
};
|
|
37964
|
+
const ESLINT_REACT_HOOKS_PLUGIN_PREFIX = "react-hooks/";
|
|
37965
|
+
const buildAcceptedTokens = (ruleId) => {
|
|
37966
|
+
const shortRuleId = ruleId.slice(15);
|
|
37967
|
+
return new Set([ruleId, `${ESLINT_REACT_HOOKS_PLUGIN_PREFIX}${shortRuleId}`]);
|
|
37968
|
+
};
|
|
37969
|
+
const listNamesRule = (ruleList, acceptedTokens) => tokenizeRuleList(ruleList).some((token) => acceptedTokens.has(token));
|
|
37970
|
+
const isInlineDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
37971
|
+
const candidates = [{
|
|
37972
|
+
line: lines[diagnosticLineIndex],
|
|
37973
|
+
requiredScope: "line"
|
|
37974
|
+
}, {
|
|
37975
|
+
line: lines[diagnosticLineIndex - 1],
|
|
37976
|
+
requiredScope: "next-line"
|
|
37977
|
+
}];
|
|
37978
|
+
for (const { line, requiredScope } of candidates) {
|
|
37979
|
+
const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
|
|
37980
|
+
if (!match) continue;
|
|
37981
|
+
const [, , scope, ruleList] = match;
|
|
37982
|
+
if (scope !== requiredScope) continue;
|
|
37983
|
+
if (listNamesRule(ruleList, acceptedTokens)) return true;
|
|
37984
|
+
}
|
|
37985
|
+
return false;
|
|
37986
|
+
};
|
|
37987
|
+
const isBlockDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
37988
|
+
let isRangeOpen = false;
|
|
37989
|
+
const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
|
|
37990
|
+
for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
|
|
37991
|
+
const line = lines[lineIndex];
|
|
37992
|
+
if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
|
|
37993
|
+
const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
|
|
37994
|
+
if (disableMatch && listNamesRule(disableMatch[2], acceptedTokens)) {
|
|
37995
|
+
isRangeOpen = true;
|
|
37996
|
+
continue;
|
|
37997
|
+
}
|
|
37998
|
+
const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
|
|
37999
|
+
if (enableMatch) {
|
|
38000
|
+
const enabledRules = tokenizeRuleList(enableMatch[1]);
|
|
38001
|
+
if (enabledRules.length === 0 || enabledRules.some((rule) => acceptedTokens.has(rule))) isRangeOpen = false;
|
|
38002
|
+
}
|
|
38003
|
+
}
|
|
38004
|
+
return isRangeOpen;
|
|
38005
|
+
};
|
|
38006
|
+
const isCompilerRuleForeignDisabled = (lines, diagnosticLineIndex, ruleId) => {
|
|
38007
|
+
if (!ruleId.startsWith("react-hooks-js/")) return false;
|
|
38008
|
+
const acceptedTokens = buildAcceptedTokens(ruleId);
|
|
38009
|
+
return isInlineDisabled(lines, diagnosticLineIndex, acceptedTokens) || isBlockDisabled(lines, diagnosticLineIndex, acceptedTokens);
|
|
38010
|
+
};
|
|
37852
38011
|
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
37853
38012
|
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
37854
38013
|
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
@@ -37992,6 +38151,11 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
37992
38151
|
isSuppressed: true,
|
|
37993
38152
|
nearMissHint: null
|
|
37994
38153
|
};
|
|
38154
|
+
if (isCompilerRuleForeignDisabled(lines, diagnosticLineIndex, ruleId)) return {
|
|
38155
|
+
isSuppressed: true,
|
|
38156
|
+
nearMissHint: null,
|
|
38157
|
+
isForeignDirective: true
|
|
38158
|
+
};
|
|
37995
38159
|
const directComments = findStackedDisableCommentsAbove(lines, diagnosticLineIndex);
|
|
37996
38160
|
if (hasChainSuppressor(directComments, ruleId)) return {
|
|
37997
38161
|
isSuppressed: true,
|
|
@@ -38273,12 +38437,18 @@ const collectStringSet = (values) => {
|
|
|
38273
38437
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
38274
38438
|
};
|
|
38275
38439
|
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
38440
|
+
const NODE_CLI_PATH_SEGMENTS = new Set([
|
|
38441
|
+
"scripts",
|
|
38442
|
+
"bin",
|
|
38443
|
+
"tools"
|
|
38444
|
+
]);
|
|
38445
|
+
const hasNodeCliPathSegment = (filePath) => filePath.split(/[\\/]/).slice(0, -1).some((segment) => NODE_CLI_PATH_SEGMENTS.has(segment));
|
|
38276
38446
|
/**
|
|
38277
38447
|
* Pre-compiles every stateful filter and returns a single
|
|
38278
38448
|
* `apply(diagnostic)` closure that runs (in order):
|
|
38279
38449
|
*
|
|
38280
38450
|
* 1. auto-suppress (test-noise rules in test files; `migration-hint`
|
|
38281
|
-
* wins over `test-noise`)
|
|
38451
|
+
* wins over `test-noise`; `no-console` on Node CLI scripts)
|
|
38282
38452
|
* 2. severity overrides (top-level `rules` / `categories`, with
|
|
38283
38453
|
* `"off"` dropping)
|
|
38284
38454
|
* 3. warning suppression (only when `showWarnings` is false: drops every
|
|
@@ -38376,6 +38546,12 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
38376
38546
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
38377
38547
|
return false;
|
|
38378
38548
|
};
|
|
38549
|
+
const isNoConsoleOnNodeCliScript = (diagnostic) => {
|
|
38550
|
+
if (diagnostic.rule !== "no-console") return false;
|
|
38551
|
+
if (hasNodeCliPathSegment(diagnostic.filePath)) return true;
|
|
38552
|
+
const lines = getFileLines(diagnostic.filePath);
|
|
38553
|
+
return lines !== null && Boolean(lines[0]?.startsWith("#!"));
|
|
38554
|
+
};
|
|
38379
38555
|
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
38380
38556
|
if (!hasRuntimeGlobals) return false;
|
|
38381
38557
|
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
@@ -38385,6 +38561,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
38385
38561
|
return {
|
|
38386
38562
|
apply: (diagnostic) => {
|
|
38387
38563
|
if (shouldAutoSuppress(diagnostic)) return null;
|
|
38564
|
+
if (isNoConsoleOnNodeCliScript(diagnostic)) return null;
|
|
38388
38565
|
let current = diagnostic;
|
|
38389
38566
|
let explicitSeverityOverride;
|
|
38390
38567
|
let explicitRuleOverride;
|
|
@@ -38414,7 +38591,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
38414
38591
|
if (lines) {
|
|
38415
38592
|
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
38416
38593
|
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
38417
|
-
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
38594
|
+
if (evaluation.isSuppressed) return suppress(current, evaluation.isForeignDirective ? "foreign-inline" : "inline");
|
|
38418
38595
|
if (evaluation.nearMissHint) current = {
|
|
38419
38596
|
...current,
|
|
38420
38597
|
suppressionHint: evaluation.nearMissHint
|
|
@@ -42590,9 +42767,17 @@ const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
|
42590
42767
|
return `Move secrets to server-only code. In ${formatFrameworkName(project.framework)}, only \`${publicEnvPrefix}\` env vars are exposed to the browser, and they must not contain secrets`;
|
|
42591
42768
|
};
|
|
42592
42769
|
const REANIMATED_SHARED_VALUE_HINT = "If this is a Reanimated shared value, prefer its React Compiler-compatible `.get()` / `.set()` accessors over `.value` — https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#react-compiler-support";
|
|
42770
|
+
const ACCESSORS_MIN_VERSION = "3.15.0";
|
|
42771
|
+
const supportsAccessors = (reanimatedVersion) => {
|
|
42772
|
+
if (reanimatedVersion === null) return true;
|
|
42773
|
+
const lowerBound = import_semver.validRange(reanimatedVersion) !== null ? import_semver.minVersion(reanimatedVersion) : import_semver.coerce(reanimatedVersion);
|
|
42774
|
+
if (lowerBound === null) return true;
|
|
42775
|
+
return import_semver.gte(lowerBound, ACCESSORS_MIN_VERSION);
|
|
42776
|
+
};
|
|
42593
42777
|
const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
42594
42778
|
if (rule !== "immutability") return help;
|
|
42595
42779
|
if (!project.hasReanimated) return help;
|
|
42780
|
+
if (!supportsAccessors(project.reanimatedVersion)) return help;
|
|
42596
42781
|
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
42597
42782
|
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
42598
42783
|
};
|
|
@@ -42754,13 +42939,13 @@ const REACT_USE_BINDING_RESOLUTION = {
|
|
|
42754
42939
|
isReactUseBinding: true,
|
|
42755
42940
|
isReactNamespaceBinding: false
|
|
42756
42941
|
};
|
|
42757
|
-
const getScriptKind = (filename) => {
|
|
42942
|
+
const getScriptKind$2 = (filename) => {
|
|
42758
42943
|
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
42759
42944
|
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
42760
42945
|
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
42761
42946
|
return ts.ScriptKind.JS;
|
|
42762
42947
|
};
|
|
42763
|
-
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
42948
|
+
const getUtf16Offset$1 = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
42764
42949
|
const unwrapExpression = (expression) => {
|
|
42765
42950
|
let currentExpression = expression;
|
|
42766
42951
|
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
@@ -43009,8 +43194,8 @@ const findUseCallIdentifier = (sourceFile, useOffset) => {
|
|
|
43009
43194
|
return matchedIdentifier;
|
|
43010
43195
|
};
|
|
43011
43196
|
const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
|
|
43012
|
-
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(filename));
|
|
43013
|
-
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset(sourceText, utf8Offset));
|
|
43197
|
+
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$2(filename));
|
|
43198
|
+
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset$1(sourceText, utf8Offset));
|
|
43014
43199
|
if (!useIdentifier) return null;
|
|
43015
43200
|
return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
|
|
43016
43201
|
};
|
|
@@ -43031,6 +43216,201 @@ const shouldSuppressLocalUseHookDiagnostic = (diagnostic, rootDirectory) => {
|
|
|
43031
43216
|
const bindingResolution = resolveUseCallBinding(sourceText, absolutePath, primaryLabel.span.offset);
|
|
43032
43217
|
return bindingResolution !== null && !bindingResolution.isReactUseBinding;
|
|
43033
43218
|
};
|
|
43219
|
+
const SUPPRESSED_COMPILER_CODES = new Set(["react-hooks-js(immutability)", "react-hooks-js(refs)"]);
|
|
43220
|
+
const WORKLET_DIRECTIVE = "worklet";
|
|
43221
|
+
const WORKLET_ACCEPTING_CALLEES = new Set([
|
|
43222
|
+
"useAnimatedStyle",
|
|
43223
|
+
"useAnimatedProps",
|
|
43224
|
+
"useDerivedValue",
|
|
43225
|
+
"useAnimatedReaction",
|
|
43226
|
+
"useAnimatedScrollHandler",
|
|
43227
|
+
"useAnimatedGestureHandler",
|
|
43228
|
+
"useFrameCallback",
|
|
43229
|
+
"runOnUI",
|
|
43230
|
+
"executeOnUIRuntimeSync"
|
|
43231
|
+
]);
|
|
43232
|
+
const GESTURE_ROOT_IDENTIFIER = "Gesture";
|
|
43233
|
+
const getScriptKind$1 = (filename) => {
|
|
43234
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
43235
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
43236
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
43237
|
+
return ts.ScriptKind.JS;
|
|
43238
|
+
};
|
|
43239
|
+
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
43240
|
+
const hasWorkletDirective = (node) => {
|
|
43241
|
+
if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node)) return false;
|
|
43242
|
+
const body = node.body;
|
|
43243
|
+
if (!body || !ts.isBlock(body)) return false;
|
|
43244
|
+
for (const statement of body.statements) {
|
|
43245
|
+
if (!ts.isExpressionStatement(statement)) return false;
|
|
43246
|
+
if (!ts.isStringLiteral(statement.expression)) return false;
|
|
43247
|
+
if (statement.expression.text === WORKLET_DIRECTIVE) return true;
|
|
43248
|
+
}
|
|
43249
|
+
return false;
|
|
43250
|
+
};
|
|
43251
|
+
const rootIdentifierOfCallChain = (expression) => {
|
|
43252
|
+
let currentExpression = expression;
|
|
43253
|
+
while (true) {
|
|
43254
|
+
if (ts.isIdentifier(currentExpression)) return currentExpression.text;
|
|
43255
|
+
if (ts.isPropertyAccessExpression(currentExpression)) {
|
|
43256
|
+
currentExpression = currentExpression.expression;
|
|
43257
|
+
continue;
|
|
43258
|
+
}
|
|
43259
|
+
if (ts.isCallExpression(currentExpression)) {
|
|
43260
|
+
currentExpression = currentExpression.expression;
|
|
43261
|
+
continue;
|
|
43262
|
+
}
|
|
43263
|
+
return null;
|
|
43264
|
+
}
|
|
43265
|
+
};
|
|
43266
|
+
const isWorkletAcceptingCall = (callExpression) => {
|
|
43267
|
+
const callee = callExpression.expression;
|
|
43268
|
+
if (ts.isIdentifier(callee)) return WORKLET_ACCEPTING_CALLEES.has(callee.text);
|
|
43269
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
43270
|
+
if (WORKLET_ACCEPTING_CALLEES.has(callee.name.text)) return true;
|
|
43271
|
+
return rootIdentifierOfCallChain(callee) === GESTURE_ROOT_IDENTIFIER;
|
|
43272
|
+
}
|
|
43273
|
+
return false;
|
|
43274
|
+
};
|
|
43275
|
+
const isFunctionWorklet = (functionNode) => {
|
|
43276
|
+
if (hasWorkletDirective(functionNode)) return true;
|
|
43277
|
+
const parent = functionNode.parent;
|
|
43278
|
+
return ts.isCallExpression(parent) && parent.arguments.some((argument) => argument === functionNode) && isWorkletAcceptingCall(parent);
|
|
43279
|
+
};
|
|
43280
|
+
const findNodeAtOffset = (sourceFile, targetOffset) => {
|
|
43281
|
+
let matchedNode = null;
|
|
43282
|
+
const visit = (node) => {
|
|
43283
|
+
if (node.getStart(sourceFile) > targetOffset || node.getEnd() <= targetOffset) return;
|
|
43284
|
+
matchedNode = node;
|
|
43285
|
+
ts.forEachChild(node, visit);
|
|
43286
|
+
};
|
|
43287
|
+
visit(sourceFile);
|
|
43288
|
+
return matchedNode;
|
|
43289
|
+
};
|
|
43290
|
+
const isOffsetInsideWorklet = (sourceFile, targetOffset) => {
|
|
43291
|
+
let currentNode = findNodeAtOffset(sourceFile, targetOffset);
|
|
43292
|
+
while (currentNode) {
|
|
43293
|
+
if (ts.isFunctionLike(currentNode) && isFunctionWorklet(currentNode)) return true;
|
|
43294
|
+
currentNode = currentNode.parent;
|
|
43295
|
+
}
|
|
43296
|
+
return false;
|
|
43297
|
+
};
|
|
43298
|
+
const shouldSuppressCompilerFindingInWorklet = (diagnostic, project, rootDirectory) => {
|
|
43299
|
+
if (!SUPPRESSED_COMPILER_CODES.has(diagnostic.code)) return false;
|
|
43300
|
+
if (!project.hasReanimated) return false;
|
|
43301
|
+
const primaryLabel = diagnostic.labels[0];
|
|
43302
|
+
if (!primaryLabel) return false;
|
|
43303
|
+
const absolutePath = Path.isAbsolute(diagnostic.filename) ? diagnostic.filename : Path.join(rootDirectory, diagnostic.filename);
|
|
43304
|
+
let sourceText;
|
|
43305
|
+
try {
|
|
43306
|
+
sourceText = NFS.readFileSync(absolutePath, "utf-8");
|
|
43307
|
+
} catch {
|
|
43308
|
+
return false;
|
|
43309
|
+
}
|
|
43310
|
+
return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$1(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
|
|
43311
|
+
};
|
|
43312
|
+
const MANUAL_MEMOIZATION_PLUGIN = "react-doctor";
|
|
43313
|
+
const MANUAL_MEMOIZATION_RULE = "react-compiler-no-manual-memoization";
|
|
43314
|
+
const REACT_COMPILER_PLUGIN = "react-hooks-js";
|
|
43315
|
+
const getScriptKind = (filename) => {
|
|
43316
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
43317
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
43318
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
43319
|
+
return ts.ScriptKind.JS;
|
|
43320
|
+
};
|
|
43321
|
+
const isManualMemoizationDiagnostic = (diagnostic) => diagnostic.plugin === MANUAL_MEMOIZATION_PLUGIN && diagnostic.rule === MANUAL_MEMOIZATION_RULE;
|
|
43322
|
+
const nodeLineRange = (sourceFile, node) => ({
|
|
43323
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
43324
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1
|
|
43325
|
+
});
|
|
43326
|
+
const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
|
|
43327
|
+
let foundRange = null;
|
|
43328
|
+
const visit = (node) => {
|
|
43329
|
+
if (foundRange) return;
|
|
43330
|
+
if (ts.isFunctionLike(node)) {
|
|
43331
|
+
const range = nodeLineRange(sourceFile, node);
|
|
43332
|
+
if (range.startLine <= targetLine && targetLine <= range.endLine) foundRange = range;
|
|
43333
|
+
return;
|
|
43334
|
+
}
|
|
43335
|
+
ts.forEachChild(node, visit);
|
|
43336
|
+
};
|
|
43337
|
+
visit(sourceFile);
|
|
43338
|
+
return foundRange;
|
|
43339
|
+
};
|
|
43340
|
+
const NEWLINE_BYTE = 10;
|
|
43341
|
+
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
43342
|
+
let lineNumber = 1;
|
|
43343
|
+
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
43344
|
+
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
|
|
43345
|
+
return lineNumber;
|
|
43346
|
+
};
|
|
43347
|
+
/**
|
|
43348
|
+
* Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
|
|
43349
|
+
* whose enclosing function also carries a `react-hooks-js` (React
|
|
43350
|
+
* Compiler) diagnostic. Every react-hooks-js finding marks a function the
|
|
43351
|
+
* compiler could NOT compile (a bail-out / skip / preserve-manual-
|
|
43352
|
+
* memoization failure), so "React Compiler already caches every value
|
|
43353
|
+
* here" is false for that function and the manual memoization is
|
|
43354
|
+
* load-bearing, not dead weight.
|
|
43355
|
+
*
|
|
43356
|
+
* Matching is per enclosing function, not per file: a bailed-out
|
|
43357
|
+
* component must not silence redundant-memo findings in a sibling
|
|
43358
|
+
* component the compiler did optimize.
|
|
43359
|
+
*
|
|
43360
|
+
* Module-scope `memo(() => …)` wrappers have no enclosing function; for
|
|
43361
|
+
* those the memo call's own span is the containment range, so a bail-out
|
|
43362
|
+
* inside the inline-wrapped component still suppresses. Every failure
|
|
43363
|
+
* path (unreadable file, missing span) fails open and keeps the
|
|
43364
|
+
* diagnostic.
|
|
43365
|
+
*/
|
|
43366
|
+
const suppressMemoizationInBailedOutFunctions = (diagnostics, rootDirectory) => {
|
|
43367
|
+
const compilerDiagnosticLinesByFile = /* @__PURE__ */ new Map();
|
|
43368
|
+
let hasManualMemoizationDiagnostic = false;
|
|
43369
|
+
for (const diagnostic of diagnostics) {
|
|
43370
|
+
if (isManualMemoizationDiagnostic(diagnostic)) {
|
|
43371
|
+
hasManualMemoizationDiagnostic = true;
|
|
43372
|
+
continue;
|
|
43373
|
+
}
|
|
43374
|
+
if (diagnostic.plugin !== REACT_COMPILER_PLUGIN || diagnostic.line <= 0) continue;
|
|
43375
|
+
const lines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
43376
|
+
if (lines) lines.push(diagnostic.line);
|
|
43377
|
+
else compilerDiagnosticLinesByFile.set(diagnostic.filePath, [diagnostic.line]);
|
|
43378
|
+
}
|
|
43379
|
+
if (!hasManualMemoizationDiagnostic || compilerDiagnosticLinesByFile.size === 0) return diagnostics;
|
|
43380
|
+
const parsedFileCache = /* @__PURE__ */ new Map();
|
|
43381
|
+
const readAndParse = (filePath) => {
|
|
43382
|
+
const cached = parsedFileCache.get(filePath);
|
|
43383
|
+
if (cached !== void 0) return cached;
|
|
43384
|
+
const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory || ".", filePath);
|
|
43385
|
+
let parsed;
|
|
43386
|
+
try {
|
|
43387
|
+
const buffer = NFS.readFileSync(absolutePath);
|
|
43388
|
+
parsed = {
|
|
43389
|
+
sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
43390
|
+
buffer
|
|
43391
|
+
};
|
|
43392
|
+
} catch {
|
|
43393
|
+
parsed = null;
|
|
43394
|
+
}
|
|
43395
|
+
parsedFileCache.set(filePath, parsed);
|
|
43396
|
+
return parsed;
|
|
43397
|
+
};
|
|
43398
|
+
const isInsideBailedOutFunction = (diagnostic) => {
|
|
43399
|
+
const compilerLines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
43400
|
+
if (!compilerLines || diagnostic.line <= 0) return false;
|
|
43401
|
+
const parsed = readAndParse(diagnostic.filePath);
|
|
43402
|
+
if (!parsed) return false;
|
|
43403
|
+
let containmentRange = findOutermostEnclosingFunctionRange(parsed.sourceFile, diagnostic.line);
|
|
43404
|
+
if (!containmentRange && diagnostic.offset !== void 0 && diagnostic.length !== void 0) containmentRange = {
|
|
43405
|
+
startLine: diagnostic.line,
|
|
43406
|
+
endLine: lineOfUtf8Offset(parsed.buffer, diagnostic.offset + diagnostic.length)
|
|
43407
|
+
};
|
|
43408
|
+
if (!containmentRange) return false;
|
|
43409
|
+
const { startLine, endLine } = containmentRange;
|
|
43410
|
+
return compilerLines.some((compilerLine) => startLine <= compilerLine && compilerLine <= endLine);
|
|
43411
|
+
};
|
|
43412
|
+
return diagnostics.filter((diagnostic) => !isManualMemoizationDiagnostic(diagnostic) || !isInsideBailedOutFunction(diagnostic));
|
|
43413
|
+
};
|
|
43034
43414
|
const FILEPATH_WITH_LOCATION_PATTERN = /\S+\.\w+:\d+:\d+[\s\S]*$/;
|
|
43035
43415
|
const LEADING_SEVERITY_LABEL_PATTERN = /^(?:Error|Warning):\s*/;
|
|
43036
43416
|
const TRAILING_PERIOD_PATTERN = /\.$/;
|
|
@@ -43170,7 +43550,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
43170
43550
|
minifiedFileCache.set(absolutePath, minified);
|
|
43171
43551
|
return minified;
|
|
43172
43552
|
};
|
|
43173
|
-
return parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory)).map((diagnostic) => {
|
|
43553
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
43174
43554
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
43175
43555
|
const primaryLabel = diagnostic.labels[0];
|
|
43176
43556
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
@@ -43197,7 +43577,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
43197
43577
|
...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
|
|
43198
43578
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
43199
43579
|
};
|
|
43200
|
-
});
|
|
43580
|
+
}), rootDirectory);
|
|
43201
43581
|
};
|
|
43202
43582
|
const buildOxlintChildEnv = (sourceEnv) => {
|
|
43203
43583
|
const childEnv = {};
|
|
@@ -43708,6 +44088,7 @@ const runOxlint = async (options) => {
|
|
|
43708
44088
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
43709
44089
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
43710
44090
|
validateRuleRegistration();
|
|
44091
|
+
resetManifestCaches();
|
|
43711
44092
|
if (includePaths !== void 0 && includePaths.length === 0) return [];
|
|
43712
44093
|
const pluginPath = resolvePluginPath();
|
|
43713
44094
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
@@ -44370,9 +44751,10 @@ const CONTEXT_AXES = [
|
|
|
44370
44751
|
}
|
|
44371
44752
|
];
|
|
44372
44753
|
const SCORE_AXES = [...GATED_AXES, ...CONTEXT_AXES];
|
|
44373
|
-
const
|
|
44374
|
-
|
|
44375
|
-
|
|
44754
|
+
const gatedAxesForSection = (section) => section === "devDependencies" ? GATED_AXES.filter((axis) => axis.key !== "vulnerability") : GATED_AXES;
|
|
44755
|
+
const worstGatedAxis = (score, gatedAxes) => {
|
|
44756
|
+
let worst = gatedAxes[0];
|
|
44757
|
+
for (const axis of gatedAxes) if (score[axis.key] < score[worst.key]) worst = axis;
|
|
44376
44758
|
return worst;
|
|
44377
44759
|
};
|
|
44378
44760
|
const clampScore = (value) => {
|
|
@@ -44396,6 +44778,69 @@ const resolveConcreteVersion = (spec) => {
|
|
|
44396
44778
|
if (range === null || range === "*") return null;
|
|
44397
44779
|
return import_semver.minVersion(trimmed)?.version ?? null;
|
|
44398
44780
|
};
|
|
44781
|
+
const readTextFileOrNull = (filePath) => {
|
|
44782
|
+
try {
|
|
44783
|
+
return NFS.readFileSync(filePath, "utf-8");
|
|
44784
|
+
} catch {
|
|
44785
|
+
return null;
|
|
44786
|
+
}
|
|
44787
|
+
};
|
|
44788
|
+
const buildNpmLockLookup = (lockText) => {
|
|
44789
|
+
let parsed;
|
|
44790
|
+
try {
|
|
44791
|
+
parsed = JSON.parse(lockText);
|
|
44792
|
+
} catch {
|
|
44793
|
+
return null;
|
|
44794
|
+
}
|
|
44795
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
44796
|
+
const lock = parsed;
|
|
44797
|
+
return (name) => {
|
|
44798
|
+
const entry = lock.packages?.[`node_modules/${name}`] ?? lock.dependencies?.[name];
|
|
44799
|
+
return typeof entry?.version === "string" ? entry.version : null;
|
|
44800
|
+
};
|
|
44801
|
+
};
|
|
44802
|
+
const buildYarnLockLookup = (lockText) => {
|
|
44803
|
+
const lines = lockText.split(/\r?\n/);
|
|
44804
|
+
return (name, spec) => {
|
|
44805
|
+
const descriptor = `${name}@${spec}`;
|
|
44806
|
+
const berryDescriptor = `${name}@npm:${spec}`;
|
|
44807
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
44808
|
+
const line = lines[lineIndex];
|
|
44809
|
+
if (line.startsWith(" ") || line.startsWith("#") || !line.includes(":")) continue;
|
|
44810
|
+
const descriptors = line.replace(/:\s*$/, "").split(",").map((part) => part.trim().replace(/^"|"$/g, ""));
|
|
44811
|
+
if (!descriptors.includes(descriptor) && !descriptors.includes(berryDescriptor)) continue;
|
|
44812
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && (lines[bodyIndex].startsWith(" ") || lines[bodyIndex] === ""); bodyIndex += 1) {
|
|
44813
|
+
const versionMatch = /^\s+version:?\s+"?([^"\s]+)"?/.exec(lines[bodyIndex]);
|
|
44814
|
+
if (versionMatch) return versionMatch[1];
|
|
44815
|
+
}
|
|
44816
|
+
return null;
|
|
44817
|
+
}
|
|
44818
|
+
return null;
|
|
44819
|
+
};
|
|
44820
|
+
};
|
|
44821
|
+
const buildPnpmLockLookup = (lockText) => {
|
|
44822
|
+
return (name) => {
|
|
44823
|
+
const entryPattern = new RegExp(`^ {4,8}['"]?${name.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}['"]?:\\s*$`);
|
|
44824
|
+
const lines = lockText.split(/\r?\n/);
|
|
44825
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
44826
|
+
if (!entryPattern.test(lines[lineIndex])) continue;
|
|
44827
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && /^ {5,}\S/.test(lines[bodyIndex]); bodyIndex += 1) {
|
|
44828
|
+
const versionMatch = /^\s+version:\s+['"]?([^'"\s(]+)/.exec(lines[bodyIndex]);
|
|
44829
|
+
if (versionMatch) return versionMatch[1];
|
|
44830
|
+
}
|
|
44831
|
+
}
|
|
44832
|
+
return null;
|
|
44833
|
+
};
|
|
44834
|
+
};
|
|
44835
|
+
const buildLockedVersionLookup = (rootDirectory) => {
|
|
44836
|
+
const npmLockText = readTextFileOrNull(Path.join(rootDirectory, "package-lock.json"));
|
|
44837
|
+
if (npmLockText !== null) return buildNpmLockLookup(npmLockText);
|
|
44838
|
+
const yarnLockText = readTextFileOrNull(Path.join(rootDirectory, "yarn.lock"));
|
|
44839
|
+
if (yarnLockText !== null) return buildYarnLockLookup(yarnLockText);
|
|
44840
|
+
const pnpmLockText = readTextFileOrNull(Path.join(rootDirectory, "pnpm-lock.yaml"));
|
|
44841
|
+
if (pnpmLockText !== null) return buildPnpmLockLookup(pnpmLockText);
|
|
44842
|
+
return null;
|
|
44843
|
+
};
|
|
44399
44844
|
const locateDependencyKey = (packageJsonText, section, name) => {
|
|
44400
44845
|
const needle = `"${name}"`;
|
|
44401
44846
|
const sectionHeader = new RegExp(`"${section}"\\s*:\\s*\\{`);
|
|
@@ -44422,7 +44867,7 @@ const locateDependencyKey = (packageJsonText, section, name) => {
|
|
|
44422
44867
|
}
|
|
44423
44868
|
return null;
|
|
44424
44869
|
};
|
|
44425
|
-
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies) => {
|
|
44870
|
+
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies, lockedVersionLookup) => {
|
|
44426
44871
|
const sectionByName = /* @__PURE__ */ new Map();
|
|
44427
44872
|
for (const name of Object.keys(packageJson.dependencies ?? {})) sectionByName.set(name, "dependencies");
|
|
44428
44873
|
if (includeDevDependencies) {
|
|
@@ -44432,13 +44877,18 @@ const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDepe
|
|
|
44432
44877
|
for (const [name, section] of sectionByName) {
|
|
44433
44878
|
if (SUPPLY_CHAIN_IGNORED_PACKAGES.has(name)) continue;
|
|
44434
44879
|
const spec = (packageJson[section] ?? {})[name] ?? "";
|
|
44435
|
-
const
|
|
44436
|
-
if (
|
|
44880
|
+
const floorVersion = resolveConcreteVersion(spec);
|
|
44881
|
+
if (floorVersion === null) continue;
|
|
44882
|
+
const lockedVersion = lockedVersionLookup?.(name, spec) ?? null;
|
|
44883
|
+
const lockSatisfiesSpec = lockedVersion !== null && import_semver.valid(lockedVersion) !== null && import_semver.satisfies(lockedVersion, spec);
|
|
44884
|
+
const isExactPin = import_semver.valid(spec.trim()) !== null;
|
|
44437
44885
|
const location = locateDependencyKey(packageJsonText, section, name);
|
|
44438
44886
|
dependencies.push({
|
|
44439
44887
|
name,
|
|
44440
|
-
version,
|
|
44888
|
+
version: lockSatisfiesSpec ? lockedVersion : floorVersion,
|
|
44441
44889
|
spec,
|
|
44890
|
+
section,
|
|
44891
|
+
resolution: isExactPin ? "pin" : lockSatisfiesSpec ? "locked" : "floor",
|
|
44442
44892
|
line: location?.line ?? 0,
|
|
44443
44893
|
column: location?.column ?? 0
|
|
44444
44894
|
});
|
|
@@ -44589,7 +45039,11 @@ const formatAlertReason = (topAlerts, totalCount) => {
|
|
|
44589
45039
|
}
|
|
44590
45040
|
return `Socket flagged ${totalCount} alerts (${topAlerts.map((alert) => friendlyAlertType(alert.type)).join(", ")}${totalCount > topAlerts.length ? ` (+${totalCount - topAlerts.length} more)` : ""}); most severe: ${displaySeverity(topAlerts[0].severity)}.`;
|
|
44591
45041
|
};
|
|
44592
|
-
const formatDependencyIdentity = (dependency) =>
|
|
45042
|
+
const formatDependencyIdentity = (dependency) => {
|
|
45043
|
+
if (dependency.resolution === "pin") return `${dependency.name}@${dependency.version}`;
|
|
45044
|
+
if (dependency.resolution === "locked") return `${dependency.name}@${dependency.version} (lockfile resolution of "${dependency.spec}")`;
|
|
45045
|
+
return `${dependency.name}@${dependency.version} (lowest version "${dependency.spec}" allows)`;
|
|
45046
|
+
};
|
|
44593
45047
|
const buildSupplyChainHelp = (dependency, failingAxis, topAlerts, packagePageUrl, options) => {
|
|
44594
45048
|
const hasCriticalAlert = topAlerts.some((alert) => alert.severity.toLowerCase() === "critical");
|
|
44595
45049
|
const entry = `\`"${dependency.name}": "${dependency.spec}"\``;
|
|
@@ -44632,7 +45086,7 @@ const buildLowScoreDiagnostic = (dependency, artifact, failingAxis, options) =>
|
|
|
44632
45086
|
const checkSupplyChain = (input) => gen(function* () {
|
|
44633
45087
|
const options = resolveOptions(input.userConfig);
|
|
44634
45088
|
const packageJsonPath = Path.join(input.rootDirectory, "package.json");
|
|
44635
|
-
const dependencies = collectDependenciesToScore(readPackageJson$1(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
45089
|
+
const dependencies = collectDependenciesToScore(readPackageJson$1(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies, buildLockedVersionLookup(input.rootDirectory));
|
|
44636
45090
|
if (dependencies.length === 0) return [];
|
|
44637
45091
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
44638
45092
|
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
@@ -44641,7 +45095,7 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
44641
45095
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
44642
45096
|
const artifact = artifacts[index];
|
|
44643
45097
|
if (!artifact) continue;
|
|
44644
|
-
const worstAxis = worstGatedAxis(artifact.score);
|
|
45098
|
+
const worstAxis = worstGatedAxis(artifact.score, gatedAxesForSection(dependencies[index].section));
|
|
44645
45099
|
if (toHundred(artifact.score[worstAxis.key]) >= options.minScore) continue;
|
|
44646
45100
|
diagnostics.push(buildLowScoreDiagnostic(dependencies[index], artifact, worstAxis, options));
|
|
44647
45101
|
}
|
|
@@ -45924,7 +46378,7 @@ const makeNoopConsole = () => ({
|
|
|
45924
46378
|
});
|
|
45925
46379
|
//#endregion
|
|
45926
46380
|
//#region src/cli/utils/version.ts
|
|
45927
|
-
const VERSION = "0.7.1-dev.
|
|
46381
|
+
const VERSION = "0.7.1-dev.7cf4d0b";
|
|
45928
46382
|
//#endregion
|
|
45929
46383
|
//#region src/cli/utils/json-mode.ts
|
|
45930
46384
|
let context = null;
|
|
@@ -46293,13 +46747,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
46293
46747
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
46294
46748
|
* standard `SENTRY_RELEASE` override.
|
|
46295
46749
|
*/
|
|
46296
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.1-dev.
|
|
46750
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.1-dev.7cf4d0b`;
|
|
46297
46751
|
/**
|
|
46298
46752
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
46299
46753
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
46300
46754
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
46301
46755
|
*/
|
|
46302
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.1-dev.
|
|
46756
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.1-dev.7cf4d0b") ? "development" : "production");
|
|
46303
46757
|
/**
|
|
46304
46758
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
46305
46759
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -58143,7 +58597,7 @@ const buildRuntimeLayers = (input) => {
|
|
|
58143
58597
|
const linterLayer = input.shouldSkipLint ? Linter.layerOf([]) : Linter.layerOxlint;
|
|
58144
58598
|
const deadCodeLayer = input.shouldRunDeadCode ? DeadCode.layerNode : DeadCode.layerOf([]);
|
|
58145
58599
|
const scoreLayer = input.shouldComputeScore ? Score.layerHttp : Score.layerOf(null);
|
|
58146
|
-
const supplyChainLayer = input.
|
|
58600
|
+
const supplyChainLayer = input.shouldRunSupplyChain ? SupplyChain.layerNode : SupplyChain.layerOf([]);
|
|
58147
58601
|
const projectLayer = input.projectInfoOverride === void 0 ? Project.layerNode : Project.layerOf(input.projectInfoOverride);
|
|
58148
58602
|
const progressLayer = input.shouldShowProgressSpinners ? Progress.layerOra(buildSpinnerProgressHandle) : Progress.layerNoop;
|
|
58149
58603
|
const baseLayers = mergeAll$1(projectLayer, input.hasConfigOverride ? Config.layerOf({
|
|
@@ -58606,6 +59060,7 @@ const buildScanResultCacheKey = (input) => {
|
|
|
58606
59060
|
engineOptions: {
|
|
58607
59061
|
lint: input.options.lint,
|
|
58608
59062
|
deadCode: input.options.deadCode,
|
|
59063
|
+
supplyChain: input.options.supplyChain,
|
|
58609
59064
|
includePaths: [...input.options.includePaths].sort(),
|
|
58610
59065
|
customRulesOnly: input.options.customRulesOnly,
|
|
58611
59066
|
respectInlineDisables: input.options.respectInlineDisables,
|
|
@@ -58815,13 +59270,15 @@ const buildOutcomeAttributes = (input) => {
|
|
|
58815
59270
|
const countBySource = {
|
|
58816
59271
|
config: 0,
|
|
58817
59272
|
override: 0,
|
|
58818
|
-
inline: 0
|
|
59273
|
+
inline: 0,
|
|
59274
|
+
"foreign-inline": 0
|
|
58819
59275
|
};
|
|
58820
59276
|
for (const suppression of input.suppressedRuleCounts) countBySource[suppression.source] += suppression.count;
|
|
58821
|
-
suppressionRollup.suppressed = countBySource.config + countBySource.override + countBySource.inline;
|
|
59277
|
+
suppressionRollup.suppressed = countBySource.config + countBySource.override + countBySource.inline + countBySource["foreign-inline"];
|
|
58822
59278
|
suppressionRollup.suppressedConfig = countBySource.config;
|
|
58823
59279
|
suppressionRollup.suppressedOverride = countBySource.override;
|
|
58824
59280
|
suppressionRollup.suppressedInline = countBySource.inline;
|
|
59281
|
+
suppressionRollup.suppressedForeignInline = countBySource["foreign-inline"];
|
|
58825
59282
|
}
|
|
58826
59283
|
const attributes = {
|
|
58827
59284
|
...withNamespace("outcome", {
|
|
@@ -58913,6 +59370,7 @@ const buildScanAttributes = (input) => {
|
|
|
58913
59370
|
maxDurationMs: input.maxDurationMs,
|
|
58914
59371
|
lint: input.lint,
|
|
58915
59372
|
deadCode: input.deadCode,
|
|
59373
|
+
supplyChain: input.supplyChain,
|
|
58916
59374
|
scoreOnly: input.scoreOnly,
|
|
58917
59375
|
noScore: input.noScore,
|
|
58918
59376
|
respectInlineDisables: input.respectInlineDisables,
|
|
@@ -60187,6 +60645,7 @@ const buildIgnoredTags = (userConfig) => {
|
|
|
60187
60645
|
const mergeInspectOptions = (inputOptions, userConfig) => ({
|
|
60188
60646
|
lint: inputOptions.lint ?? userConfig?.lint ?? true,
|
|
60189
60647
|
deadCode: inputOptions.deadCode ?? userConfig?.deadCode ?? true,
|
|
60648
|
+
supplyChain: inputOptions.supplyChain ?? userConfig?.supplyChain?.enabled ?? true,
|
|
60190
60649
|
verbose: inputOptions.verbose ?? userConfig?.verbose ?? false,
|
|
60191
60650
|
outputDirectory: inputOptions.outputDirectory || null,
|
|
60192
60651
|
scoreOnly: inputOptions.scoreOnly ?? false,
|
|
@@ -60226,6 +60685,7 @@ const buildRunEventConfig = (options, userConfig, hasCustomConfig, resolvedWorke
|
|
|
60226
60685
|
maxDurationMs: options.maxDurationMs,
|
|
60227
60686
|
lint: options.lint,
|
|
60228
60687
|
deadCode: options.deadCode,
|
|
60688
|
+
supplyChain: options.supplyChain,
|
|
60229
60689
|
scoreOnly: options.scoreOnly,
|
|
60230
60690
|
noScore: options.noScore,
|
|
60231
60691
|
respectInlineDisables: options.respectInlineDisables,
|
|
@@ -60309,6 +60769,7 @@ const runBaselineComparison = async (params) => {
|
|
|
60309
60769
|
projectInfoOverride: params.headProjectInfo,
|
|
60310
60770
|
shouldSkipLint: !params.options.lint || !params.resolvedNodeBinaryPath,
|
|
60311
60771
|
shouldRunDeadCode: false,
|
|
60772
|
+
shouldRunSupplyChain: params.options.supplyChain,
|
|
60312
60773
|
shouldComputeScore: false,
|
|
60313
60774
|
shouldShowProgressSpinners: false,
|
|
60314
60775
|
oxlintConcurrency: params.options.concurrency
|
|
@@ -60397,6 +60858,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
60397
60858
|
configSourceDirectory,
|
|
60398
60859
|
shouldSkipLint: !options.lint || lintBindingMissing,
|
|
60399
60860
|
shouldRunDeadCode: options.deadCode,
|
|
60861
|
+
shouldRunSupplyChain: options.supplyChain,
|
|
60400
60862
|
shouldComputeScore: !options.noScore,
|
|
60401
60863
|
shouldShowProgressSpinners,
|
|
60402
60864
|
oxlintConcurrency: options.concurrency
|
|
@@ -62682,8 +63144,8 @@ const resolveParallelFlag = (parallel) => parallel === false ? 1 : void 0;
|
|
|
62682
63144
|
* accepts. Flag-derived fields only (`scoreOnly`, `noScore`, `silent`,
|
|
62683
63145
|
* `isCi`): every `userConfig` fallback — including `noScore` — lives in
|
|
62684
63146
|
* `inspect()`'s merge layer, which sees each project's module-merged
|
|
62685
|
-
* config. The plain boolean knobs (`lint`, `deadCode`, `
|
|
62686
|
-
* through unchanged for the same reason. This resolver reads
|
|
63147
|
+
* config. The plain boolean knobs (`lint`, `deadCode`, `supplyChain`,
|
|
63148
|
+
* `verbose`) pass through unchanged for the same reason. This resolver reads
|
|
62687
63149
|
* `userConfig` only to decide the `--blocking warning` gate.
|
|
62688
63150
|
*/
|
|
62689
63151
|
const resolveCliInspectOptions = (flags, userConfig) => {
|
|
@@ -62691,6 +63153,7 @@ const resolveCliInspectOptions = (flags, userConfig) => {
|
|
|
62691
63153
|
return {
|
|
62692
63154
|
lint: flags.lint,
|
|
62693
63155
|
deadCode: flags.deadCode,
|
|
63156
|
+
supplyChain: flags.supplyChain,
|
|
62694
63157
|
verbose: flags.verbose,
|
|
62695
63158
|
outputDirectory: flags.outputDir,
|
|
62696
63159
|
respectInlineDisables: flags.respectInlineDisables === false ? false : void 0,
|
|
@@ -63241,7 +63704,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
63241
63704
|
const scanDirectory = projectScanTarget.resolvedDirectory;
|
|
63242
63705
|
const projectConfig = projectDirectory === resolvedDirectory ? userConfig : mergeReactDoctorConfigs(userConfig, projectScanTarget.userConfig ?? void 0);
|
|
63243
63706
|
const projectConfigSourceDirectory = projectScanTarget.userConfig?.plugins === void 0 ? scanTarget.configSourceDirectory : projectScanTarget.configSourceDirectory;
|
|
63244
|
-
const supplyChainEnabled = projectConfig?.supplyChain?.enabled !== false;
|
|
63707
|
+
const supplyChainEnabled = flags.supplyChain ?? projectConfig?.supplyChain?.enabled !== false;
|
|
63245
63708
|
let includePaths;
|
|
63246
63709
|
let supplyChainManifestChanged = false;
|
|
63247
63710
|
if (isDiffMode) {
|
|
@@ -64161,10 +64624,12 @@ const ROOT_FLAG_SPEC = {
|
|
|
64161
64624
|
"--no-parallel",
|
|
64162
64625
|
"--no-respect-inline-disables",
|
|
64163
64626
|
"--no-score",
|
|
64627
|
+
"--no-supply-chain",
|
|
64164
64628
|
"--no-telemetry",
|
|
64165
64629
|
"--no-warnings",
|
|
64166
64630
|
"--score",
|
|
64167
64631
|
"--staged",
|
|
64632
|
+
"--supply-chain",
|
|
64168
64633
|
"--verbose",
|
|
64169
64634
|
"--version",
|
|
64170
64635
|
"--warnings",
|
|
@@ -64413,7 +64878,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
64413
64878
|
${highlighter.info(CI_URL)}
|
|
64414
64879
|
`;
|
|
64415
64880
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
64416
|
-
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
64881
|
+
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
64417
64882
|
program.action(inspectAction);
|
|
64418
64883
|
program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action((location, options) => whyAction(location, options));
|
|
64419
64884
|
program.command("install").alias("setup").description("Install the react-doctor skill into your coding agents and optional git hook").option("-y, --yes", "skip prompts, install for all detected agents").option("--dry-run", "show what would be installed without writing files").option("--agent-hooks", "install native non-blocking agent hooks for Claude Code and Cursor").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderInstallHelpEpilog).action(installAction);
|
|
@@ -64463,4 +64928,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
64463
64928
|
export {};
|
|
64464
64929
|
|
|
64465
64930
|
//# sourceMappingURL=cli.js.map
|
|
64466
|
-
//# debugId=
|
|
64931
|
+
//# debugId=7b7c23b7-250e-5339-af06-e3e3b5eb5f98
|