react-doctor 0.7.1 → 0.7.2
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 +543 -69
- package/dist/index.d.ts +7 -0
- package/dist/index.js +500 -46
- package/dist/lsp.js +501 -47
- package/package.json +6 -6
package/dist/lsp.js
CHANGED
|
@@ -8,7 +8,7 @@ import path from "node:path";
|
|
|
8
8
|
import * as NodeChildProcess from "node:child_process";
|
|
9
9
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
10
10
|
import * as ts from "typescript";
|
|
11
|
-
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";
|
|
11
|
+
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";
|
|
12
12
|
import { parseJSON5 } from "confbox";
|
|
13
13
|
import * as NodeUrl from "node:url";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
@@ -32836,8 +32836,10 @@ const isFile = (filePath) => {
|
|
|
32836
32836
|
};
|
|
32837
32837
|
const SOURCE_FILE_PATTERN = /\.(tsx?|jsx?|mts|mjs)$/;
|
|
32838
32838
|
const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
|
|
32839
|
+
const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
|
|
32839
32840
|
const MINIFIED_SNIFF_BYTES = 65536;
|
|
32840
32841
|
const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
32842
|
+
const SCANNED_DOT_DIRECTORIES = new Set([".dumi", ".storybook"]);
|
|
32841
32843
|
const IGNORED_DIRECTORIES = new Set([
|
|
32842
32844
|
".angular",
|
|
32843
32845
|
".astro",
|
|
@@ -33002,7 +33004,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
33002
33004
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
33003
33005
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
33004
33006
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
33005
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
33007
|
+
const CORE_PACKAGE_VERSION = "0.7.2";
|
|
33006
33008
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
33007
33009
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
33008
33010
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -33012,8 +33014,9 @@ const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
|
33012
33014
|
const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
|
|
33013
33015
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
33014
33016
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
33015
|
-
const
|
|
33016
|
-
const
|
|
33017
|
+
const isIgnoredDirectoryName = (directoryName) => IGNORED_DIRECTORIES.has(directoryName) || directoryName.startsWith(".") && !SCANNED_DOT_DIRECTORIES.has(directoryName);
|
|
33018
|
+
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => isIgnoredDirectoryName(segment));
|
|
33019
|
+
const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath) && !GENERATED_SOURCE_DIRECTORY_PATTERN.test(filePath);
|
|
33017
33020
|
const isMinifiedSource = (absolutePath) => {
|
|
33018
33021
|
let fileDescriptor;
|
|
33019
33022
|
try {
|
|
@@ -33077,7 +33080,7 @@ function* walkSourceTreeFiles(rootDirectory) {
|
|
|
33077
33080
|
for (const entry of readDirectoryEntries(currentDirectory)) {
|
|
33078
33081
|
const absolutePath = Path.join(currentDirectory, entry.name);
|
|
33079
33082
|
if (entry.isDirectory()) {
|
|
33080
|
-
if (!
|
|
33083
|
+
if (!isIgnoredDirectoryName(entry.name)) stack.push(absolutePath);
|
|
33081
33084
|
continue;
|
|
33082
33085
|
}
|
|
33083
33086
|
if (entry.isFile()) yield {
|
|
@@ -33123,6 +33126,23 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
|
33123
33126
|
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
|
|
33124
33127
|
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
33125
33128
|
const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
|
|
33129
|
+
/**
|
|
33130
|
+
* The chain of ancestor directories of `startDirectory`, nearest first, up to
|
|
33131
|
+
* and including the filesystem root. With `includeStart`, the chain begins at
|
|
33132
|
+
* `startDirectory` itself; otherwise at its parent. The one place the "walk up
|
|
33133
|
+
* the tree" traversal is expressed, so each caller adds only its own match /
|
|
33134
|
+
* stop policy instead of re-deriving the `path.dirname` loop.
|
|
33135
|
+
*/
|
|
33136
|
+
const ancestorDirectories = (startDirectory, { includeStart }) => {
|
|
33137
|
+
const directories = [];
|
|
33138
|
+
let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
|
|
33139
|
+
while (true) {
|
|
33140
|
+
directories.push(currentDirectory);
|
|
33141
|
+
const parentDirectory = Path.dirname(currentDirectory);
|
|
33142
|
+
if (parentDirectory === currentDirectory) return directories;
|
|
33143
|
+
currentDirectory = parentDirectory;
|
|
33144
|
+
}
|
|
33145
|
+
};
|
|
33126
33146
|
const cachedPackageJsons = /* @__PURE__ */ new Map();
|
|
33127
33147
|
const clearPackageJsonCache = () => {
|
|
33128
33148
|
cachedPackageJsons.clear();
|
|
@@ -33156,11 +33176,7 @@ const isMonorepoRoot = (directory) => {
|
|
|
33156
33176
|
return Array.isArray(packageJson.workspaces) || Boolean(packageJson.workspaces?.packages);
|
|
33157
33177
|
};
|
|
33158
33178
|
const findMonorepoRoot = (startDirectory) => {
|
|
33159
|
-
|
|
33160
|
-
while (currentDirectory !== Path.dirname(currentDirectory)) {
|
|
33161
|
-
if (isMonorepoRoot(currentDirectory)) return currentDirectory;
|
|
33162
|
-
currentDirectory = Path.dirname(currentDirectory);
|
|
33163
|
-
}
|
|
33179
|
+
for (const directory of ancestorDirectories(startDirectory, { includeStart: false })) if (isMonorepoRoot(directory)) return directory;
|
|
33164
33180
|
return null;
|
|
33165
33181
|
};
|
|
33166
33182
|
/**
|
|
@@ -33806,6 +33822,74 @@ const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
|
33806
33822
|
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
33807
33823
|
};
|
|
33808
33824
|
};
|
|
33825
|
+
/**
|
|
33826
|
+
* Walk up from `startDirectory` to the nearest ancestor that owns a
|
|
33827
|
+
* `package.json`, stopping at (and including) the enclosing project boundary
|
|
33828
|
+
* — the working tree's git root or a monorepo root. Lets a scan of a package
|
|
33829
|
+
* subfolder that has no `package.json` of its own adopt the nearest enclosing
|
|
33830
|
+
* package as its project root, so it inherits that package's dependency +
|
|
33831
|
+
* framework detection instead of synthesizing an empty (React-blind) project.
|
|
33832
|
+
*
|
|
33833
|
+
* Generalizes the older monorepo-root-only lookup: the nearest ancestor is the
|
|
33834
|
+
* most specific owning package (a leaf workspace, not just the repo root), so
|
|
33835
|
+
* a subfolder of one workspace no longer borrows a sibling's capabilities.
|
|
33836
|
+
*
|
|
33837
|
+
* Returns `null` when no ancestor `package.json` exists inside the project
|
|
33838
|
+
* boundary (a loose tree of files, or a subfolder of a package-less repo).
|
|
33839
|
+
*/
|
|
33840
|
+
const findNearestAncestorPackageJson = (startDirectory) => {
|
|
33841
|
+
if (isProjectBoundary(startDirectory)) return null;
|
|
33842
|
+
for (const directory of ancestorDirectories(startDirectory, { includeStart: false })) {
|
|
33843
|
+
if (isFile(Path.join(directory, "package.json"))) return directory;
|
|
33844
|
+
if (isProjectBoundary(directory)) return null;
|
|
33845
|
+
}
|
|
33846
|
+
return null;
|
|
33847
|
+
};
|
|
33848
|
+
/**
|
|
33849
|
+
* Last-resort React detection: locate React the way Node's `node_modules`
|
|
33850
|
+
* resolution would from `directory` — check each `node_modules/react` up the
|
|
33851
|
+
* tree — but bounded to the enclosing repo so a globally installed React can't
|
|
33852
|
+
* masquerade as the project's version. Stops at (and including) the nearest
|
|
33853
|
+
* project boundary — the git root or a monorepo root — and returns the first
|
|
33854
|
+
* installed `version`.
|
|
33855
|
+
*
|
|
33856
|
+
* Makes "React is installed and importable" ⇒ "React is detected" an invariant
|
|
33857
|
+
* for packages whose only React declaration is a version-less spec
|
|
33858
|
+
* (`workspace:*`, `*`, a dist-tag) or where React lives solely in a hoisted
|
|
33859
|
+
* `node_modules` the declaration walks never reach — the profile of a component
|
|
33860
|
+
* package inside a monorepo. Matching the `node_modules/react` entry rather than
|
|
33861
|
+
* its realpath means a project whose `node_modules` is symlinked elsewhere (a
|
|
33862
|
+
* Docker volume, a shared store) still counts, while a global install outside
|
|
33863
|
+
* the repo does not.
|
|
33864
|
+
*
|
|
33865
|
+
* Returns `null` when React isn't installed within the repo or its package.json
|
|
33866
|
+
* carries no version string. A tree with no boundary marker at all bounds the
|
|
33867
|
+
* search to `directory`, so a dependency hoisted above the scanned package there
|
|
33868
|
+
* is conservatively not adopted.
|
|
33869
|
+
*/
|
|
33870
|
+
/**
|
|
33871
|
+
* The nearest enclosing project boundary (git root or monorepo root), or
|
|
33872
|
+
* `directory` itself when the scan target sits outside any repo. Bounds the
|
|
33873
|
+
* node_modules walk so a React hoisted anywhere inside the repo counts, but a
|
|
33874
|
+
* React in an ancestor above the repo — a home-directory or global
|
|
33875
|
+
* `node_modules` — can't leak in.
|
|
33876
|
+
*/
|
|
33877
|
+
const findContainmentRoot = (directory) => {
|
|
33878
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) if (isProjectBoundary(ancestorDirectory)) return ancestorDirectory;
|
|
33879
|
+
return directory;
|
|
33880
|
+
};
|
|
33881
|
+
const resolveInstalledReactVersion = (directory) => {
|
|
33882
|
+
const containmentRoot = findContainmentRoot(directory);
|
|
33883
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) {
|
|
33884
|
+
const reactPackageJsonPath = Path.join(ancestorDirectory, "node_modules", "react", "package.json");
|
|
33885
|
+
if (isFile(reactPackageJsonPath)) {
|
|
33886
|
+
const installedVersion = readPackageJson(reactPackageJsonPath).version;
|
|
33887
|
+
return typeof installedVersion === "string" ? installedVersion : null;
|
|
33888
|
+
}
|
|
33889
|
+
if (ancestorDirectory === containmentRoot) return null;
|
|
33890
|
+
}
|
|
33891
|
+
return null;
|
|
33892
|
+
};
|
|
33809
33893
|
const findInWorkspacePackageJsons = (rootDirectory, rootPackageJson, select) => {
|
|
33810
33894
|
const rootValue = select(rootPackageJson);
|
|
33811
33895
|
if (rootValue !== null) return rootValue;
|
|
@@ -33962,6 +34046,10 @@ const normalizeCompilerOptions = (compilerOptions) => {
|
|
|
33962
34046
|
hasExplicitLib
|
|
33963
34047
|
};
|
|
33964
34048
|
};
|
|
34049
|
+
const normalizeReferencePaths = (references) => {
|
|
34050
|
+
if (!Array.isArray(references)) return [];
|
|
34051
|
+
return references.map((reference) => isPlainObject(reference) && typeof reference.path === "string" ? reference.path : null).filter((referencePath) => referencePath !== null);
|
|
34052
|
+
};
|
|
33965
34053
|
const readTsConfig = (filePath) => {
|
|
33966
34054
|
let content;
|
|
33967
34055
|
try {
|
|
@@ -33973,6 +34061,7 @@ const readTsConfig = (filePath) => {
|
|
|
33973
34061
|
if (!isPlainObject(parsed.config)) return null;
|
|
33974
34062
|
return {
|
|
33975
34063
|
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
34064
|
+
referencePaths: normalizeReferencePaths(parsed.config.references),
|
|
33976
34065
|
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
33977
34066
|
};
|
|
33978
34067
|
};
|
|
@@ -34013,20 +34102,37 @@ const libEntryIncludesES2023Array = (entry) => {
|
|
|
34013
34102
|
};
|
|
34014
34103
|
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
34015
34104
|
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
34105
|
+
if (compilerOptions.target && targetYearIsPreES2023(compilerOptions.target)) return true;
|
|
34016
34106
|
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
34017
|
-
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
34018
34107
|
return false;
|
|
34019
34108
|
};
|
|
34020
34109
|
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
34021
|
-
const detectPreES2023FromConfig = (tsConfigPath) => {
|
|
34110
|
+
const detectPreES2023FromConfig = (tsConfigPath, visitedConfigPaths = /* @__PURE__ */ new Set()) => {
|
|
34111
|
+
if (visitedConfigPaths.has(tsConfigPath)) return false;
|
|
34022
34112
|
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
34023
34113
|
if (!compilerOptions) return false;
|
|
34024
|
-
if (!compilerOptionsDeclareTargetOrLib(compilerOptions))
|
|
34114
|
+
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) {
|
|
34115
|
+
const tsConfig = readTsConfig(tsConfigPath);
|
|
34116
|
+
if (!tsConfig) return false;
|
|
34117
|
+
const nextVisitedConfigPaths = new Set(visitedConfigPaths);
|
|
34118
|
+
nextVisitedConfigPaths.add(tsConfigPath);
|
|
34119
|
+
const configDirectory = Path.dirname(tsConfigPath);
|
|
34120
|
+
return tsConfig.referencePaths.some((referencePath) => {
|
|
34121
|
+
const resolvedReferencePath = Path.resolve(configDirectory, referencePath);
|
|
34122
|
+
const referencedConfigPath = isFile(resolvedReferencePath) ? resolvedReferencePath : Path.join(resolvedReferencePath, TSCONFIG_FILENAME);
|
|
34123
|
+
return isFile(referencedConfigPath) && detectPreES2023FromConfig(referencedConfigPath, nextVisitedConfigPaths);
|
|
34124
|
+
});
|
|
34125
|
+
}
|
|
34025
34126
|
return compilerOptionsArePreES2023(compilerOptions);
|
|
34026
34127
|
};
|
|
34128
|
+
const FALLBACK_TSCONFIG_FILENAMES = ["tsconfig.app.json", "tsconfig.build.json"];
|
|
34027
34129
|
const detectPreES2023Target = (directory) => {
|
|
34028
34130
|
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
34029
34131
|
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
34132
|
+
for (const fallbackFilename of FALLBACK_TSCONFIG_FILENAMES) {
|
|
34133
|
+
const fallbackPath = Path.join(directory, fallbackFilename);
|
|
34134
|
+
if (isFile(fallbackPath)) return detectPreES2023FromConfig(fallbackPath);
|
|
34135
|
+
}
|
|
34030
34136
|
return false;
|
|
34031
34137
|
};
|
|
34032
34138
|
const REACT_DEPENDENCY_NAMES = new Set([
|
|
@@ -34145,19 +34251,20 @@ const clearProjectCache = () => {
|
|
|
34145
34251
|
};
|
|
34146
34252
|
/**
|
|
34147
34253
|
* Build a `ProjectInfo` for a directory that has no `package.json` of
|
|
34148
|
-
* its own — a
|
|
34149
|
-
* of TypeScript/JavaScript files. Dependency + framework
|
|
34150
|
-
* inherited from the enclosing
|
|
34151
|
-
*
|
|
34152
|
-
*
|
|
34153
|
-
*
|
|
34154
|
-
*
|
|
34254
|
+
* its own — a package subfolder like `repo/packages` or `app/src/features`,
|
|
34255
|
+
* or any loose tree of TypeScript/JavaScript files. Dependency + framework
|
|
34256
|
+
* detection is inherited from the nearest enclosing package (a leaf
|
|
34257
|
+
* workspace, a plain app root, or a monorepo root — whichever is closest,
|
|
34258
|
+
* bounded by the git root), so scanning a subdirectory of a React project
|
|
34259
|
+
* still gets the React capabilities; a standalone non-React directory simply
|
|
34260
|
+
* scans with the framework-agnostic rules. Throws only when the directory has
|
|
34261
|
+
* nothing to scan (no enclosing project and no source files of its own).
|
|
34155
34262
|
*/
|
|
34156
34263
|
const discoverProjectWithoutPackageJson = (directory) => {
|
|
34157
34264
|
const sourceFileCount = countSourceFiles(directory);
|
|
34158
34265
|
const hasOwnTsConfig = NFS.existsSync(Path.join(directory, "tsconfig.json"));
|
|
34159
|
-
const
|
|
34160
|
-
const enclosingProject =
|
|
34266
|
+
const enclosingProjectRoot = findNearestAncestorPackageJson(directory);
|
|
34267
|
+
const enclosingProject = enclosingProjectRoot !== null ? discoverProject(enclosingProjectRoot) : null;
|
|
34161
34268
|
if (enclosingProject !== null) return {
|
|
34162
34269
|
...enclosingProject,
|
|
34163
34270
|
rootDirectory: directory,
|
|
@@ -34187,6 +34294,7 @@ const discoverProjectWithoutPackageJson = (directory) => {
|
|
|
34187
34294
|
shopifyFlashListVersion: null,
|
|
34188
34295
|
shopifyFlashListMajorVersion: null,
|
|
34189
34296
|
hasReanimated: false,
|
|
34297
|
+
reanimatedVersion: null,
|
|
34190
34298
|
isPreES2023Target: hasOwnTsConfig && detectPreES2023Target(directory),
|
|
34191
34299
|
sourceFileCount
|
|
34192
34300
|
};
|
|
@@ -34261,6 +34369,7 @@ const discoverProject = (directory) => {
|
|
|
34261
34369
|
if (!reactVersion && reactDeclaration.version && !isCatalogReference(reactDeclaration.version)) reactVersion = reactDeclaration.version;
|
|
34262
34370
|
if (!tailwindVersion && tailwindDeclaration.version && !isCatalogReference(tailwindDeclaration.version)) tailwindVersion = tailwindDeclaration.version;
|
|
34263
34371
|
if (!zodVersion && zodDeclaration.version && !isCatalogReference(zodDeclaration.version)) zodVersion = zodDeclaration.version;
|
|
34372
|
+
if (!reactVersion || parseReactMajor(reactVersion) === null) reactVersion = resolveInstalledReactVersion(directory) ?? reactVersion;
|
|
34264
34373
|
const projectName = packageJson.name ?? Path.basename(directory);
|
|
34265
34374
|
const hasTypeScript = NFS.existsSync(Path.join(directory, "tsconfig.json"));
|
|
34266
34375
|
const sourceFileCount = countSourceFiles(directory);
|
|
@@ -34278,6 +34387,7 @@ const discoverProject = (directory) => {
|
|
|
34278
34387
|
version: findShopifyFlashListVersion(directory, packageJson)
|
|
34279
34388
|
}) : null;
|
|
34280
34389
|
const hasReanimated = hasReactNativeWorkspace && someWorkspacePackageJson(directory, packageJson, isPackageJsonReanimatedAware);
|
|
34390
|
+
const reanimatedVersion = hasReanimated ? findInWorkspacePackageJsons(directory, packageJson, (workspacePackageJson) => getDependencySpec(workspacePackageJson, REANIMATED_DEPENDENCY_NAME)) : null;
|
|
34281
34391
|
const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
|
|
34282
34392
|
rootDirectory: directory,
|
|
34283
34393
|
rootPackageJson: packageJson,
|
|
@@ -34307,6 +34417,7 @@ const discoverProject = (directory) => {
|
|
|
34307
34417
|
shopifyFlashListVersion,
|
|
34308
34418
|
shopifyFlashListMajorVersion: shopifyFlashListVersion === null ? null : getLowestDependencyMajor(shopifyFlashListVersion),
|
|
34309
34419
|
hasReanimated,
|
|
34420
|
+
reanimatedVersion,
|
|
34310
34421
|
isPreES2023Target,
|
|
34311
34422
|
sourceFileCount
|
|
34312
34423
|
};
|
|
@@ -34499,6 +34610,9 @@ const buildRuleSeverityControls = (config) => {
|
|
|
34499
34610
|
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
34500
34611
|
};
|
|
34501
34612
|
};
|
|
34613
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34614
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34615
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34502
34616
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
34503
34617
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
34504
34618
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -34576,6 +34690,7 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
|
34576
34690
|
"react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
|
|
34577
34691
|
"react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
|
|
34578
34692
|
"react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
|
|
34693
|
+
"react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
|
|
34579
34694
|
"react/jsx-no-undef": "react-doctor/jsx-no-undef",
|
|
34580
34695
|
"react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
|
|
34581
34696
|
"react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
|
|
@@ -34649,9 +34764,6 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
34649
34764
|
if (!ruleSection) return [];
|
|
34650
34765
|
return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
|
|
34651
34766
|
};
|
|
34652
|
-
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34653
|
-
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34654
|
-
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34655
34767
|
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}\`).`;
|
|
34656
34768
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34657
34769
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
@@ -34705,6 +34817,53 @@ const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34705
34817
|
if (!ruleId.startsWith("react-doctor/")) return null;
|
|
34706
34818
|
return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
|
|
34707
34819
|
};
|
|
34820
|
+
const ESLINT_REACT_HOOKS_PLUGIN_PREFIX = "react-hooks/";
|
|
34821
|
+
const buildAcceptedTokens = (ruleId) => {
|
|
34822
|
+
const shortRuleId = ruleId.slice(15);
|
|
34823
|
+
return new Set([ruleId, `${ESLINT_REACT_HOOKS_PLUGIN_PREFIX}${shortRuleId}`]);
|
|
34824
|
+
};
|
|
34825
|
+
const listNamesRule = (ruleList, acceptedTokens) => tokenizeRuleList(ruleList).some((token) => acceptedTokens.has(token));
|
|
34826
|
+
const isInlineDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
34827
|
+
const candidates = [{
|
|
34828
|
+
line: lines[diagnosticLineIndex],
|
|
34829
|
+
requiredScope: "line"
|
|
34830
|
+
}, {
|
|
34831
|
+
line: lines[diagnosticLineIndex - 1],
|
|
34832
|
+
requiredScope: "next-line"
|
|
34833
|
+
}];
|
|
34834
|
+
for (const { line, requiredScope } of candidates) {
|
|
34835
|
+
const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
|
|
34836
|
+
if (!match) continue;
|
|
34837
|
+
const [, , scope, ruleList] = match;
|
|
34838
|
+
if (scope !== requiredScope) continue;
|
|
34839
|
+
if (listNamesRule(ruleList, acceptedTokens)) return true;
|
|
34840
|
+
}
|
|
34841
|
+
return false;
|
|
34842
|
+
};
|
|
34843
|
+
const isBlockDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
34844
|
+
let isRangeOpen = false;
|
|
34845
|
+
const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
|
|
34846
|
+
for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
|
|
34847
|
+
const line = lines[lineIndex];
|
|
34848
|
+
if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
|
|
34849
|
+
const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
|
|
34850
|
+
if (disableMatch && listNamesRule(disableMatch[2], acceptedTokens)) {
|
|
34851
|
+
isRangeOpen = true;
|
|
34852
|
+
continue;
|
|
34853
|
+
}
|
|
34854
|
+
const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
|
|
34855
|
+
if (enableMatch) {
|
|
34856
|
+
const enabledRules = tokenizeRuleList(enableMatch[1]);
|
|
34857
|
+
if (enabledRules.length === 0 || enabledRules.some((rule) => acceptedTokens.has(rule))) isRangeOpen = false;
|
|
34858
|
+
}
|
|
34859
|
+
}
|
|
34860
|
+
return isRangeOpen;
|
|
34861
|
+
};
|
|
34862
|
+
const isCompilerRuleForeignDisabled = (lines, diagnosticLineIndex, ruleId) => {
|
|
34863
|
+
if (!ruleId.startsWith("react-hooks-js/")) return false;
|
|
34864
|
+
const acceptedTokens = buildAcceptedTokens(ruleId);
|
|
34865
|
+
return isInlineDisabled(lines, diagnosticLineIndex, acceptedTokens) || isBlockDisabled(lines, diagnosticLineIndex, acceptedTokens);
|
|
34866
|
+
};
|
|
34708
34867
|
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
34709
34868
|
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
34710
34869
|
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
@@ -34848,6 +35007,11 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34848
35007
|
isSuppressed: true,
|
|
34849
35008
|
nearMissHint: null
|
|
34850
35009
|
};
|
|
35010
|
+
if (isCompilerRuleForeignDisabled(lines, diagnosticLineIndex, ruleId)) return {
|
|
35011
|
+
isSuppressed: true,
|
|
35012
|
+
nearMissHint: null,
|
|
35013
|
+
isForeignDirective: true
|
|
35014
|
+
};
|
|
34851
35015
|
const directComments = findStackedDisableCommentsAbove(lines, diagnosticLineIndex);
|
|
34852
35016
|
if (hasChainSuppressor(directComments, ruleId)) return {
|
|
34853
35017
|
isSuppressed: true,
|
|
@@ -35129,12 +35293,18 @@ const collectStringSet = (values) => {
|
|
|
35129
35293
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35130
35294
|
};
|
|
35131
35295
|
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35296
|
+
const NODE_CLI_PATH_SEGMENTS = new Set([
|
|
35297
|
+
"scripts",
|
|
35298
|
+
"bin",
|
|
35299
|
+
"tools"
|
|
35300
|
+
]);
|
|
35301
|
+
const hasNodeCliPathSegment = (filePath) => filePath.split(/[\\/]/).slice(0, -1).some((segment) => NODE_CLI_PATH_SEGMENTS.has(segment));
|
|
35132
35302
|
/**
|
|
35133
35303
|
* Pre-compiles every stateful filter and returns a single
|
|
35134
35304
|
* `apply(diagnostic)` closure that runs (in order):
|
|
35135
35305
|
*
|
|
35136
35306
|
* 1. auto-suppress (test-noise rules in test files; `migration-hint`
|
|
35137
|
-
* wins over `test-noise`)
|
|
35307
|
+
* wins over `test-noise`; `no-console` on Node CLI scripts)
|
|
35138
35308
|
* 2. severity overrides (top-level `rules` / `categories`, with
|
|
35139
35309
|
* `"off"` dropping)
|
|
35140
35310
|
* 3. warning suppression (only when `showWarnings` is false: drops every
|
|
@@ -35232,6 +35402,12 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35232
35402
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35233
35403
|
return false;
|
|
35234
35404
|
};
|
|
35405
|
+
const isNoConsoleOnNodeCliScript = (diagnostic) => {
|
|
35406
|
+
if (diagnostic.rule !== "no-console") return false;
|
|
35407
|
+
if (hasNodeCliPathSegment(diagnostic.filePath)) return true;
|
|
35408
|
+
const lines = getFileLines(diagnostic.filePath);
|
|
35409
|
+
return lines !== null && Boolean(lines[0]?.startsWith("#!"));
|
|
35410
|
+
};
|
|
35235
35411
|
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
35236
35412
|
if (!hasRuntimeGlobals) return false;
|
|
35237
35413
|
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
@@ -35241,6 +35417,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35241
35417
|
return {
|
|
35242
35418
|
apply: (diagnostic) => {
|
|
35243
35419
|
if (shouldAutoSuppress(diagnostic)) return null;
|
|
35420
|
+
if (isNoConsoleOnNodeCliScript(diagnostic)) return null;
|
|
35244
35421
|
let current = diagnostic;
|
|
35245
35422
|
let explicitSeverityOverride;
|
|
35246
35423
|
let explicitRuleOverride;
|
|
@@ -35270,7 +35447,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35270
35447
|
if (lines) {
|
|
35271
35448
|
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35272
35449
|
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35273
|
-
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
35450
|
+
if (evaluation.isSuppressed) return suppress(current, evaluation.isForeignDirective ? "foreign-inline" : "inline");
|
|
35274
35451
|
if (evaluation.nearMissHint) current = {
|
|
35275
35452
|
...current,
|
|
35276
35453
|
suppressionHint: evaluation.nearMissHint
|
|
@@ -38549,7 +38726,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38549
38726
|
})),
|
|
38550
38727
|
showStagedContent: (directory, relativePath, options) => runCommand({
|
|
38551
38728
|
command: "git",
|
|
38552
|
-
args: ["show",
|
|
38729
|
+
args: ["show", `:./${relativePath}`],
|
|
38553
38730
|
directory,
|
|
38554
38731
|
maxStdoutBytes: options?.maxBufferBytes
|
|
38555
38732
|
}).pipe(map$3((result) => result.status === 0 ? result.stdout : null)),
|
|
@@ -39332,9 +39509,17 @@ const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
|
39332
39509
|
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`;
|
|
39333
39510
|
};
|
|
39334
39511
|
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";
|
|
39512
|
+
const ACCESSORS_MIN_VERSION = "3.15.0";
|
|
39513
|
+
const supportsAccessors = (reanimatedVersion) => {
|
|
39514
|
+
if (reanimatedVersion === null) return true;
|
|
39515
|
+
const lowerBound = import_semver.validRange(reanimatedVersion) !== null ? import_semver.minVersion(reanimatedVersion) : import_semver.coerce(reanimatedVersion);
|
|
39516
|
+
if (lowerBound === null) return true;
|
|
39517
|
+
return import_semver.gte(lowerBound, ACCESSORS_MIN_VERSION);
|
|
39518
|
+
};
|
|
39335
39519
|
const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
39336
39520
|
if (rule !== "immutability") return help;
|
|
39337
39521
|
if (!project.hasReanimated) return help;
|
|
39522
|
+
if (!supportsAccessors(project.reanimatedVersion)) return help;
|
|
39338
39523
|
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
39339
39524
|
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
39340
39525
|
};
|
|
@@ -39496,13 +39681,13 @@ const REACT_USE_BINDING_RESOLUTION = {
|
|
|
39496
39681
|
isReactUseBinding: true,
|
|
39497
39682
|
isReactNamespaceBinding: false
|
|
39498
39683
|
};
|
|
39499
|
-
const getScriptKind = (filename) => {
|
|
39684
|
+
const getScriptKind$2 = (filename) => {
|
|
39500
39685
|
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
39501
39686
|
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
39502
39687
|
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
39503
39688
|
return ts.ScriptKind.JS;
|
|
39504
39689
|
};
|
|
39505
|
-
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39690
|
+
const getUtf16Offset$1 = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39506
39691
|
const unwrapExpression = (expression) => {
|
|
39507
39692
|
let currentExpression = expression;
|
|
39508
39693
|
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
@@ -39751,8 +39936,8 @@ const findUseCallIdentifier = (sourceFile, useOffset) => {
|
|
|
39751
39936
|
return matchedIdentifier;
|
|
39752
39937
|
};
|
|
39753
39938
|
const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
|
|
39754
|
-
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(filename));
|
|
39755
|
-
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset(sourceText, utf8Offset));
|
|
39939
|
+
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$2(filename));
|
|
39940
|
+
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset$1(sourceText, utf8Offset));
|
|
39756
39941
|
if (!useIdentifier) return null;
|
|
39757
39942
|
return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
|
|
39758
39943
|
};
|
|
@@ -39773,6 +39958,201 @@ const shouldSuppressLocalUseHookDiagnostic = (diagnostic, rootDirectory) => {
|
|
|
39773
39958
|
const bindingResolution = resolveUseCallBinding(sourceText, absolutePath, primaryLabel.span.offset);
|
|
39774
39959
|
return bindingResolution !== null && !bindingResolution.isReactUseBinding;
|
|
39775
39960
|
};
|
|
39961
|
+
const SUPPRESSED_COMPILER_CODES = new Set(["react-hooks-js(immutability)", "react-hooks-js(refs)"]);
|
|
39962
|
+
const WORKLET_DIRECTIVE = "worklet";
|
|
39963
|
+
const WORKLET_ACCEPTING_CALLEES = new Set([
|
|
39964
|
+
"useAnimatedStyle",
|
|
39965
|
+
"useAnimatedProps",
|
|
39966
|
+
"useDerivedValue",
|
|
39967
|
+
"useAnimatedReaction",
|
|
39968
|
+
"useAnimatedScrollHandler",
|
|
39969
|
+
"useAnimatedGestureHandler",
|
|
39970
|
+
"useFrameCallback",
|
|
39971
|
+
"runOnUI",
|
|
39972
|
+
"executeOnUIRuntimeSync"
|
|
39973
|
+
]);
|
|
39974
|
+
const GESTURE_ROOT_IDENTIFIER = "Gesture";
|
|
39975
|
+
const getScriptKind$1 = (filename) => {
|
|
39976
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
39977
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
39978
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
39979
|
+
return ts.ScriptKind.JS;
|
|
39980
|
+
};
|
|
39981
|
+
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39982
|
+
const hasWorkletDirective = (node) => {
|
|
39983
|
+
if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node)) return false;
|
|
39984
|
+
const body = node.body;
|
|
39985
|
+
if (!body || !ts.isBlock(body)) return false;
|
|
39986
|
+
for (const statement of body.statements) {
|
|
39987
|
+
if (!ts.isExpressionStatement(statement)) return false;
|
|
39988
|
+
if (!ts.isStringLiteral(statement.expression)) return false;
|
|
39989
|
+
if (statement.expression.text === WORKLET_DIRECTIVE) return true;
|
|
39990
|
+
}
|
|
39991
|
+
return false;
|
|
39992
|
+
};
|
|
39993
|
+
const rootIdentifierOfCallChain = (expression) => {
|
|
39994
|
+
let currentExpression = expression;
|
|
39995
|
+
while (true) {
|
|
39996
|
+
if (ts.isIdentifier(currentExpression)) return currentExpression.text;
|
|
39997
|
+
if (ts.isPropertyAccessExpression(currentExpression)) {
|
|
39998
|
+
currentExpression = currentExpression.expression;
|
|
39999
|
+
continue;
|
|
40000
|
+
}
|
|
40001
|
+
if (ts.isCallExpression(currentExpression)) {
|
|
40002
|
+
currentExpression = currentExpression.expression;
|
|
40003
|
+
continue;
|
|
40004
|
+
}
|
|
40005
|
+
return null;
|
|
40006
|
+
}
|
|
40007
|
+
};
|
|
40008
|
+
const isWorkletAcceptingCall = (callExpression) => {
|
|
40009
|
+
const callee = callExpression.expression;
|
|
40010
|
+
if (ts.isIdentifier(callee)) return WORKLET_ACCEPTING_CALLEES.has(callee.text);
|
|
40011
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
40012
|
+
if (WORKLET_ACCEPTING_CALLEES.has(callee.name.text)) return true;
|
|
40013
|
+
return rootIdentifierOfCallChain(callee) === GESTURE_ROOT_IDENTIFIER;
|
|
40014
|
+
}
|
|
40015
|
+
return false;
|
|
40016
|
+
};
|
|
40017
|
+
const isFunctionWorklet = (functionNode) => {
|
|
40018
|
+
if (hasWorkletDirective(functionNode)) return true;
|
|
40019
|
+
const parent = functionNode.parent;
|
|
40020
|
+
return ts.isCallExpression(parent) && parent.arguments.some((argument) => argument === functionNode) && isWorkletAcceptingCall(parent);
|
|
40021
|
+
};
|
|
40022
|
+
const findNodeAtOffset = (sourceFile, targetOffset) => {
|
|
40023
|
+
let matchedNode = null;
|
|
40024
|
+
const visit = (node) => {
|
|
40025
|
+
if (node.getStart(sourceFile) > targetOffset || node.getEnd() <= targetOffset) return;
|
|
40026
|
+
matchedNode = node;
|
|
40027
|
+
ts.forEachChild(node, visit);
|
|
40028
|
+
};
|
|
40029
|
+
visit(sourceFile);
|
|
40030
|
+
return matchedNode;
|
|
40031
|
+
};
|
|
40032
|
+
const isOffsetInsideWorklet = (sourceFile, targetOffset) => {
|
|
40033
|
+
let currentNode = findNodeAtOffset(sourceFile, targetOffset);
|
|
40034
|
+
while (currentNode) {
|
|
40035
|
+
if (ts.isFunctionLike(currentNode) && isFunctionWorklet(currentNode)) return true;
|
|
40036
|
+
currentNode = currentNode.parent;
|
|
40037
|
+
}
|
|
40038
|
+
return false;
|
|
40039
|
+
};
|
|
40040
|
+
const shouldSuppressCompilerFindingInWorklet = (diagnostic, project, rootDirectory) => {
|
|
40041
|
+
if (!SUPPRESSED_COMPILER_CODES.has(diagnostic.code)) return false;
|
|
40042
|
+
if (!project.hasReanimated) return false;
|
|
40043
|
+
const primaryLabel = diagnostic.labels[0];
|
|
40044
|
+
if (!primaryLabel) return false;
|
|
40045
|
+
const absolutePath = Path.isAbsolute(diagnostic.filename) ? diagnostic.filename : Path.join(rootDirectory, diagnostic.filename);
|
|
40046
|
+
let sourceText;
|
|
40047
|
+
try {
|
|
40048
|
+
sourceText = NFS.readFileSync(absolutePath, "utf-8");
|
|
40049
|
+
} catch {
|
|
40050
|
+
return false;
|
|
40051
|
+
}
|
|
40052
|
+
return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$1(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
|
|
40053
|
+
};
|
|
40054
|
+
const MANUAL_MEMOIZATION_PLUGIN = "react-doctor";
|
|
40055
|
+
const MANUAL_MEMOIZATION_RULE = "react-compiler-no-manual-memoization";
|
|
40056
|
+
const REACT_COMPILER_PLUGIN = "react-hooks-js";
|
|
40057
|
+
const getScriptKind = (filename) => {
|
|
40058
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
40059
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
40060
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
40061
|
+
return ts.ScriptKind.JS;
|
|
40062
|
+
};
|
|
40063
|
+
const isManualMemoizationDiagnostic = (diagnostic) => diagnostic.plugin === MANUAL_MEMOIZATION_PLUGIN && diagnostic.rule === MANUAL_MEMOIZATION_RULE;
|
|
40064
|
+
const nodeLineRange = (sourceFile, node) => ({
|
|
40065
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
40066
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1
|
|
40067
|
+
});
|
|
40068
|
+
const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
|
|
40069
|
+
let foundRange = null;
|
|
40070
|
+
const visit = (node) => {
|
|
40071
|
+
if (foundRange) return;
|
|
40072
|
+
if (ts.isFunctionLike(node)) {
|
|
40073
|
+
const range = nodeLineRange(sourceFile, node);
|
|
40074
|
+
if (range.startLine <= targetLine && targetLine <= range.endLine) foundRange = range;
|
|
40075
|
+
return;
|
|
40076
|
+
}
|
|
40077
|
+
ts.forEachChild(node, visit);
|
|
40078
|
+
};
|
|
40079
|
+
visit(sourceFile);
|
|
40080
|
+
return foundRange;
|
|
40081
|
+
};
|
|
40082
|
+
const NEWLINE_BYTE = 10;
|
|
40083
|
+
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
40084
|
+
let lineNumber = 1;
|
|
40085
|
+
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
40086
|
+
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
|
|
40087
|
+
return lineNumber;
|
|
40088
|
+
};
|
|
40089
|
+
/**
|
|
40090
|
+
* Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
|
|
40091
|
+
* whose enclosing function also carries a `react-hooks-js` (React
|
|
40092
|
+
* Compiler) diagnostic. Every react-hooks-js finding marks a function the
|
|
40093
|
+
* compiler could NOT compile (a bail-out / skip / preserve-manual-
|
|
40094
|
+
* memoization failure), so "React Compiler already caches every value
|
|
40095
|
+
* here" is false for that function and the manual memoization is
|
|
40096
|
+
* load-bearing, not dead weight.
|
|
40097
|
+
*
|
|
40098
|
+
* Matching is per enclosing function, not per file: a bailed-out
|
|
40099
|
+
* component must not silence redundant-memo findings in a sibling
|
|
40100
|
+
* component the compiler did optimize.
|
|
40101
|
+
*
|
|
40102
|
+
* Module-scope `memo(() => …)` wrappers have no enclosing function; for
|
|
40103
|
+
* those the memo call's own span is the containment range, so a bail-out
|
|
40104
|
+
* inside the inline-wrapped component still suppresses. Every failure
|
|
40105
|
+
* path (unreadable file, missing span) fails open and keeps the
|
|
40106
|
+
* diagnostic.
|
|
40107
|
+
*/
|
|
40108
|
+
const suppressMemoizationInBailedOutFunctions = (diagnostics, rootDirectory) => {
|
|
40109
|
+
const compilerDiagnosticLinesByFile = /* @__PURE__ */ new Map();
|
|
40110
|
+
let hasManualMemoizationDiagnostic = false;
|
|
40111
|
+
for (const diagnostic of diagnostics) {
|
|
40112
|
+
if (isManualMemoizationDiagnostic(diagnostic)) {
|
|
40113
|
+
hasManualMemoizationDiagnostic = true;
|
|
40114
|
+
continue;
|
|
40115
|
+
}
|
|
40116
|
+
if (diagnostic.plugin !== REACT_COMPILER_PLUGIN || diagnostic.line <= 0) continue;
|
|
40117
|
+
const lines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
40118
|
+
if (lines) lines.push(diagnostic.line);
|
|
40119
|
+
else compilerDiagnosticLinesByFile.set(diagnostic.filePath, [diagnostic.line]);
|
|
40120
|
+
}
|
|
40121
|
+
if (!hasManualMemoizationDiagnostic || compilerDiagnosticLinesByFile.size === 0) return diagnostics;
|
|
40122
|
+
const parsedFileCache = /* @__PURE__ */ new Map();
|
|
40123
|
+
const readAndParse = (filePath) => {
|
|
40124
|
+
const cached = parsedFileCache.get(filePath);
|
|
40125
|
+
if (cached !== void 0) return cached;
|
|
40126
|
+
const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory || ".", filePath);
|
|
40127
|
+
let parsed;
|
|
40128
|
+
try {
|
|
40129
|
+
const buffer = NFS.readFileSync(absolutePath);
|
|
40130
|
+
parsed = {
|
|
40131
|
+
sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
40132
|
+
buffer
|
|
40133
|
+
};
|
|
40134
|
+
} catch {
|
|
40135
|
+
parsed = null;
|
|
40136
|
+
}
|
|
40137
|
+
parsedFileCache.set(filePath, parsed);
|
|
40138
|
+
return parsed;
|
|
40139
|
+
};
|
|
40140
|
+
const isInsideBailedOutFunction = (diagnostic) => {
|
|
40141
|
+
const compilerLines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
40142
|
+
if (!compilerLines || diagnostic.line <= 0) return false;
|
|
40143
|
+
const parsed = readAndParse(diagnostic.filePath);
|
|
40144
|
+
if (!parsed) return false;
|
|
40145
|
+
let containmentRange = findOutermostEnclosingFunctionRange(parsed.sourceFile, diagnostic.line);
|
|
40146
|
+
if (!containmentRange && diagnostic.offset !== void 0 && diagnostic.length !== void 0) containmentRange = {
|
|
40147
|
+
startLine: diagnostic.line,
|
|
40148
|
+
endLine: lineOfUtf8Offset(parsed.buffer, diagnostic.offset + diagnostic.length)
|
|
40149
|
+
};
|
|
40150
|
+
if (!containmentRange) return false;
|
|
40151
|
+
const { startLine, endLine } = containmentRange;
|
|
40152
|
+
return compilerLines.some((compilerLine) => startLine <= compilerLine && compilerLine <= endLine);
|
|
40153
|
+
};
|
|
40154
|
+
return diagnostics.filter((diagnostic) => !isManualMemoizationDiagnostic(diagnostic) || !isInsideBailedOutFunction(diagnostic));
|
|
40155
|
+
};
|
|
39776
40156
|
const FILEPATH_WITH_LOCATION_PATTERN = /\S+\.\w+:\d+:\d+[\s\S]*$/;
|
|
39777
40157
|
const LEADING_SEVERITY_LABEL_PATTERN = /^(?:Error|Warning):\s*/;
|
|
39778
40158
|
const TRAILING_PERIOD_PATTERN = /\.$/;
|
|
@@ -39912,7 +40292,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
39912
40292
|
minifiedFileCache.set(absolutePath, minified);
|
|
39913
40293
|
return minified;
|
|
39914
40294
|
};
|
|
39915
|
-
return parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory)).map((diagnostic) => {
|
|
40295
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
39916
40296
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
39917
40297
|
const primaryLabel = diagnostic.labels[0];
|
|
39918
40298
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
@@ -39939,7 +40319,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
39939
40319
|
...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
|
|
39940
40320
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
39941
40321
|
};
|
|
39942
|
-
});
|
|
40322
|
+
}), rootDirectory);
|
|
39943
40323
|
};
|
|
39944
40324
|
const buildOxlintChildEnv = (sourceEnv) => {
|
|
39945
40325
|
const childEnv = {};
|
|
@@ -40450,6 +40830,7 @@ const runOxlint = async (options) => {
|
|
|
40450
40830
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
40451
40831
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
40452
40832
|
validateRuleRegistration();
|
|
40833
|
+
resetManifestCaches();
|
|
40453
40834
|
if (includePaths !== void 0 && includePaths.length === 0) return [];
|
|
40454
40835
|
const pluginPath = resolvePluginPath();
|
|
40455
40836
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
@@ -41112,9 +41493,10 @@ const CONTEXT_AXES = [
|
|
|
41112
41493
|
}
|
|
41113
41494
|
];
|
|
41114
41495
|
const SCORE_AXES = [...GATED_AXES, ...CONTEXT_AXES];
|
|
41115
|
-
const
|
|
41116
|
-
|
|
41117
|
-
|
|
41496
|
+
const gatedAxesForSection = (section) => section === "devDependencies" ? GATED_AXES.filter((axis) => axis.key !== "vulnerability") : GATED_AXES;
|
|
41497
|
+
const worstGatedAxis = (score, gatedAxes) => {
|
|
41498
|
+
let worst = gatedAxes[0];
|
|
41499
|
+
for (const axis of gatedAxes) if (score[axis.key] < score[worst.key]) worst = axis;
|
|
41118
41500
|
return worst;
|
|
41119
41501
|
};
|
|
41120
41502
|
const clampScore = (value) => {
|
|
@@ -41138,6 +41520,69 @@ const resolveConcreteVersion = (spec) => {
|
|
|
41138
41520
|
if (range === null || range === "*") return null;
|
|
41139
41521
|
return import_semver.minVersion(trimmed)?.version ?? null;
|
|
41140
41522
|
};
|
|
41523
|
+
const readTextFileOrNull = (filePath) => {
|
|
41524
|
+
try {
|
|
41525
|
+
return NFS.readFileSync(filePath, "utf-8");
|
|
41526
|
+
} catch {
|
|
41527
|
+
return null;
|
|
41528
|
+
}
|
|
41529
|
+
};
|
|
41530
|
+
const buildNpmLockLookup = (lockText) => {
|
|
41531
|
+
let parsed;
|
|
41532
|
+
try {
|
|
41533
|
+
parsed = JSON.parse(lockText);
|
|
41534
|
+
} catch {
|
|
41535
|
+
return null;
|
|
41536
|
+
}
|
|
41537
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
41538
|
+
const lock = parsed;
|
|
41539
|
+
return (name) => {
|
|
41540
|
+
const entry = lock.packages?.[`node_modules/${name}`] ?? lock.dependencies?.[name];
|
|
41541
|
+
return typeof entry?.version === "string" ? entry.version : null;
|
|
41542
|
+
};
|
|
41543
|
+
};
|
|
41544
|
+
const buildYarnLockLookup = (lockText) => {
|
|
41545
|
+
const lines = lockText.split(/\r?\n/);
|
|
41546
|
+
return (name, spec) => {
|
|
41547
|
+
const descriptor = `${name}@${spec}`;
|
|
41548
|
+
const berryDescriptor = `${name}@npm:${spec}`;
|
|
41549
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
41550
|
+
const line = lines[lineIndex];
|
|
41551
|
+
if (line.startsWith(" ") || line.startsWith("#") || !line.includes(":")) continue;
|
|
41552
|
+
const descriptors = line.replace(/:\s*$/, "").split(",").map((part) => part.trim().replace(/^"|"$/g, ""));
|
|
41553
|
+
if (!descriptors.includes(descriptor) && !descriptors.includes(berryDescriptor)) continue;
|
|
41554
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && (lines[bodyIndex].startsWith(" ") || lines[bodyIndex] === ""); bodyIndex += 1) {
|
|
41555
|
+
const versionMatch = /^\s+version:?\s+"?([^"\s]+)"?/.exec(lines[bodyIndex]);
|
|
41556
|
+
if (versionMatch) return versionMatch[1];
|
|
41557
|
+
}
|
|
41558
|
+
return null;
|
|
41559
|
+
}
|
|
41560
|
+
return null;
|
|
41561
|
+
};
|
|
41562
|
+
};
|
|
41563
|
+
const buildPnpmLockLookup = (lockText) => {
|
|
41564
|
+
return (name) => {
|
|
41565
|
+
const entryPattern = new RegExp(`^ {4,8}['"]?${name.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}['"]?:\\s*$`);
|
|
41566
|
+
const lines = lockText.split(/\r?\n/);
|
|
41567
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
41568
|
+
if (!entryPattern.test(lines[lineIndex])) continue;
|
|
41569
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && /^ {5,}\S/.test(lines[bodyIndex]); bodyIndex += 1) {
|
|
41570
|
+
const versionMatch = /^\s+version:\s+['"]?([^'"\s(]+)/.exec(lines[bodyIndex]);
|
|
41571
|
+
if (versionMatch) return versionMatch[1];
|
|
41572
|
+
}
|
|
41573
|
+
}
|
|
41574
|
+
return null;
|
|
41575
|
+
};
|
|
41576
|
+
};
|
|
41577
|
+
const buildLockedVersionLookup = (rootDirectory) => {
|
|
41578
|
+
const npmLockText = readTextFileOrNull(Path.join(rootDirectory, "package-lock.json"));
|
|
41579
|
+
if (npmLockText !== null) return buildNpmLockLookup(npmLockText);
|
|
41580
|
+
const yarnLockText = readTextFileOrNull(Path.join(rootDirectory, "yarn.lock"));
|
|
41581
|
+
if (yarnLockText !== null) return buildYarnLockLookup(yarnLockText);
|
|
41582
|
+
const pnpmLockText = readTextFileOrNull(Path.join(rootDirectory, "pnpm-lock.yaml"));
|
|
41583
|
+
if (pnpmLockText !== null) return buildPnpmLockLookup(pnpmLockText);
|
|
41584
|
+
return null;
|
|
41585
|
+
};
|
|
41141
41586
|
const locateDependencyKey = (packageJsonText, section, name) => {
|
|
41142
41587
|
const needle = `"${name}"`;
|
|
41143
41588
|
const sectionHeader = new RegExp(`"${section}"\\s*:\\s*\\{`);
|
|
@@ -41164,7 +41609,7 @@ const locateDependencyKey = (packageJsonText, section, name) => {
|
|
|
41164
41609
|
}
|
|
41165
41610
|
return null;
|
|
41166
41611
|
};
|
|
41167
|
-
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies) => {
|
|
41612
|
+
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies, lockedVersionLookup) => {
|
|
41168
41613
|
const sectionByName = /* @__PURE__ */ new Map();
|
|
41169
41614
|
for (const name of Object.keys(packageJson.dependencies ?? {})) sectionByName.set(name, "dependencies");
|
|
41170
41615
|
if (includeDevDependencies) {
|
|
@@ -41174,13 +41619,18 @@ const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDepe
|
|
|
41174
41619
|
for (const [name, section] of sectionByName) {
|
|
41175
41620
|
if (SUPPLY_CHAIN_IGNORED_PACKAGES.has(name)) continue;
|
|
41176
41621
|
const spec = (packageJson[section] ?? {})[name] ?? "";
|
|
41177
|
-
const
|
|
41178
|
-
if (
|
|
41622
|
+
const floorVersion = resolveConcreteVersion(spec);
|
|
41623
|
+
if (floorVersion === null) continue;
|
|
41624
|
+
const lockedVersion = lockedVersionLookup?.(name, spec) ?? null;
|
|
41625
|
+
const lockSatisfiesSpec = lockedVersion !== null && import_semver.valid(lockedVersion) !== null && import_semver.satisfies(lockedVersion, spec);
|
|
41626
|
+
const isExactPin = import_semver.valid(spec.trim()) !== null;
|
|
41179
41627
|
const location = locateDependencyKey(packageJsonText, section, name);
|
|
41180
41628
|
dependencies.push({
|
|
41181
41629
|
name,
|
|
41182
|
-
version,
|
|
41630
|
+
version: lockSatisfiesSpec ? lockedVersion : floorVersion,
|
|
41183
41631
|
spec,
|
|
41632
|
+
section,
|
|
41633
|
+
resolution: isExactPin ? "pin" : lockSatisfiesSpec ? "locked" : "floor",
|
|
41184
41634
|
line: location?.line ?? 0,
|
|
41185
41635
|
column: location?.column ?? 0
|
|
41186
41636
|
});
|
|
@@ -41331,7 +41781,11 @@ const formatAlertReason = (topAlerts, totalCount) => {
|
|
|
41331
41781
|
}
|
|
41332
41782
|
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)}.`;
|
|
41333
41783
|
};
|
|
41334
|
-
const formatDependencyIdentity = (dependency) =>
|
|
41784
|
+
const formatDependencyIdentity = (dependency) => {
|
|
41785
|
+
if (dependency.resolution === "pin") return `${dependency.name}@${dependency.version}`;
|
|
41786
|
+
if (dependency.resolution === "locked") return `${dependency.name}@${dependency.version} (lockfile resolution of "${dependency.spec}")`;
|
|
41787
|
+
return `${dependency.name}@${dependency.version} (lowest version "${dependency.spec}" allows)`;
|
|
41788
|
+
};
|
|
41335
41789
|
const buildSupplyChainHelp = (dependency, failingAxis, topAlerts, packagePageUrl, options) => {
|
|
41336
41790
|
const hasCriticalAlert = topAlerts.some((alert) => alert.severity.toLowerCase() === "critical");
|
|
41337
41791
|
const entry = `\`"${dependency.name}": "${dependency.spec}"\``;
|
|
@@ -41374,7 +41828,7 @@ const buildLowScoreDiagnostic = (dependency, artifact, failingAxis, options) =>
|
|
|
41374
41828
|
const checkSupplyChain = (input) => gen(function* () {
|
|
41375
41829
|
const options = resolveOptions(input.userConfig);
|
|
41376
41830
|
const packageJsonPath = Path.join(input.rootDirectory, "package.json");
|
|
41377
|
-
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
41831
|
+
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies, buildLockedVersionLookup(input.rootDirectory));
|
|
41378
41832
|
if (dependencies.length === 0) return [];
|
|
41379
41833
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
41380
41834
|
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
@@ -41383,7 +41837,7 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
41383
41837
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
41384
41838
|
const artifact = artifacts[index];
|
|
41385
41839
|
if (!artifact) continue;
|
|
41386
|
-
const worstAxis = worstGatedAxis(artifact.score);
|
|
41840
|
+
const worstAxis = worstGatedAxis(artifact.score, gatedAxesForSection(dependencies[index].section));
|
|
41387
41841
|
if (toHundred(artifact.score[worstAxis.key]) >= options.minScore) continue;
|
|
41388
41842
|
diagnostics.push(buildLowScoreDiagnostic(dependencies[index], artifact, worstAxis, options));
|
|
41389
41843
|
}
|
|
@@ -42109,7 +42563,7 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
42109
42563
|
/** Display name used in client-facing messages and progress titles. */
|
|
42110
42564
|
const SERVER_DISPLAY_NAME = "React Doctor";
|
|
42111
42565
|
/** Server version reported in `serverInfo`; injected at build, `dev` from source. */
|
|
42112
|
-
const SERVER_VERSION = "0.7.
|
|
42566
|
+
const SERVER_VERSION = "0.7.2";
|
|
42113
42567
|
/** `Diagnostic.source` shown next to every published diagnostic. */
|
|
42114
42568
|
const DIAGNOSTIC_SOURCE = "react-doctor";
|
|
42115
42569
|
/**
|
|
@@ -44160,5 +44614,5 @@ const startLanguageServer = () => {
|
|
|
44160
44614
|
};
|
|
44161
44615
|
//#endregion
|
|
44162
44616
|
export { startLanguageServer };
|
|
44163
|
-
!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]="
|
|
44164
|
-
//# debugId=
|
|
44617
|
+
!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]="30e226f8-fb2c-5784-b48c-95e8d218d035")}catch(e){}}();
|
|
44618
|
+
//# debugId=30e226f8-fb2c-5784-b48c-95e8d218d035
|