react-doctor 0.7.1-dev.d353dad → 0.7.2-dev.aa519e5
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 +409 -36
- package/dist/index.d.ts +7 -0
- package/dist/index.js +402 -31
- package/dist/lsp.js +403 -32
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="10e3e1b0-f291-5306-b5fd-79b158b00159")}catch(e){}}();
|
|
3
3
|
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import * as NFS from "node:fs";
|
|
@@ -9,7 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import * as NodeChildProcess from "node:child_process";
|
|
10
10
|
import { execFileSync, spawn, spawnSync } from "node:child_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 from "node:os";
|
|
15
15
|
import { parseJSON5 } from "confbox";
|
|
@@ -32800,8 +32800,10 @@ const isFile = (filePath) => {
|
|
|
32800
32800
|
};
|
|
32801
32801
|
const SOURCE_FILE_PATTERN = /\.(tsx?|jsx?|mts|mjs)$/;
|
|
32802
32802
|
const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
|
|
32803
|
+
const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
|
|
32803
32804
|
const MINIFIED_SNIFF_BYTES = 65536;
|
|
32804
32805
|
const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
32806
|
+
const SCANNED_DOT_DIRECTORIES = new Set([".dumi", ".storybook"]);
|
|
32805
32807
|
const IGNORED_DIRECTORIES = new Set([
|
|
32806
32808
|
".angular",
|
|
32807
32809
|
".astro",
|
|
@@ -32943,7 +32945,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
32943
32945
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32944
32946
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32945
32947
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32946
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
32948
|
+
const CORE_PACKAGE_VERSION = "0.7.2";
|
|
32947
32949
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32948
32950
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
32949
32951
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -32953,8 +32955,9 @@ const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
|
32953
32955
|
const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
|
|
32954
32956
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
32955
32957
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
32956
|
-
const
|
|
32957
|
-
const
|
|
32958
|
+
const isIgnoredDirectoryName = (directoryName) => IGNORED_DIRECTORIES.has(directoryName) || directoryName.startsWith(".") && !SCANNED_DOT_DIRECTORIES.has(directoryName);
|
|
32959
|
+
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => isIgnoredDirectoryName(segment));
|
|
32960
|
+
const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath) && !GENERATED_SOURCE_DIRECTORY_PATTERN.test(filePath);
|
|
32958
32961
|
const isMinifiedSource = (absolutePath) => {
|
|
32959
32962
|
let fileDescriptor;
|
|
32960
32963
|
try {
|
|
@@ -33018,7 +33021,7 @@ function* walkSourceTreeFiles(rootDirectory) {
|
|
|
33018
33021
|
for (const entry of readDirectoryEntries(currentDirectory)) {
|
|
33019
33022
|
const absolutePath = Path.join(currentDirectory, entry.name);
|
|
33020
33023
|
if (entry.isDirectory()) {
|
|
33021
|
-
if (!
|
|
33024
|
+
if (!isIgnoredDirectoryName(entry.name)) stack.push(absolutePath);
|
|
33022
33025
|
continue;
|
|
33023
33026
|
}
|
|
33024
33027
|
if (entry.isFile()) yield {
|
|
@@ -33984,6 +33987,10 @@ const normalizeCompilerOptions = (compilerOptions) => {
|
|
|
33984
33987
|
hasExplicitLib
|
|
33985
33988
|
};
|
|
33986
33989
|
};
|
|
33990
|
+
const normalizeReferencePaths = (references) => {
|
|
33991
|
+
if (!Array.isArray(references)) return [];
|
|
33992
|
+
return references.map((reference) => isPlainObject(reference) && typeof reference.path === "string" ? reference.path : null).filter((referencePath) => referencePath !== null);
|
|
33993
|
+
};
|
|
33987
33994
|
const readTsConfig = (filePath) => {
|
|
33988
33995
|
let content;
|
|
33989
33996
|
try {
|
|
@@ -33995,6 +34002,7 @@ const readTsConfig = (filePath) => {
|
|
|
33995
34002
|
if (!isPlainObject(parsed.config)) return null;
|
|
33996
34003
|
return {
|
|
33997
34004
|
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
34005
|
+
referencePaths: normalizeReferencePaths(parsed.config.references),
|
|
33998
34006
|
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
33999
34007
|
};
|
|
34000
34008
|
};
|
|
@@ -34035,20 +34043,37 @@ const libEntryIncludesES2023Array = (entry) => {
|
|
|
34035
34043
|
};
|
|
34036
34044
|
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
34037
34045
|
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
34046
|
+
if (compilerOptions.target && targetYearIsPreES2023(compilerOptions.target)) return true;
|
|
34038
34047
|
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
34039
|
-
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
34040
34048
|
return false;
|
|
34041
34049
|
};
|
|
34042
34050
|
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
34043
|
-
const detectPreES2023FromConfig = (tsConfigPath) => {
|
|
34051
|
+
const detectPreES2023FromConfig = (tsConfigPath, visitedConfigPaths = /* @__PURE__ */ new Set()) => {
|
|
34052
|
+
if (visitedConfigPaths.has(tsConfigPath)) return false;
|
|
34044
34053
|
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
34045
34054
|
if (!compilerOptions) return false;
|
|
34046
|
-
if (!compilerOptionsDeclareTargetOrLib(compilerOptions))
|
|
34055
|
+
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) {
|
|
34056
|
+
const tsConfig = readTsConfig(tsConfigPath);
|
|
34057
|
+
if (!tsConfig) return false;
|
|
34058
|
+
const nextVisitedConfigPaths = new Set(visitedConfigPaths);
|
|
34059
|
+
nextVisitedConfigPaths.add(tsConfigPath);
|
|
34060
|
+
const configDirectory = Path.dirname(tsConfigPath);
|
|
34061
|
+
return tsConfig.referencePaths.some((referencePath) => {
|
|
34062
|
+
const resolvedReferencePath = Path.resolve(configDirectory, referencePath);
|
|
34063
|
+
const referencedConfigPath = isFile(resolvedReferencePath) ? resolvedReferencePath : Path.join(resolvedReferencePath, TSCONFIG_FILENAME);
|
|
34064
|
+
return isFile(referencedConfigPath) && detectPreES2023FromConfig(referencedConfigPath, nextVisitedConfigPaths);
|
|
34065
|
+
});
|
|
34066
|
+
}
|
|
34047
34067
|
return compilerOptionsArePreES2023(compilerOptions);
|
|
34048
34068
|
};
|
|
34069
|
+
const FALLBACK_TSCONFIG_FILENAMES = ["tsconfig.app.json", "tsconfig.build.json"];
|
|
34049
34070
|
const detectPreES2023Target = (directory) => {
|
|
34050
34071
|
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
34051
34072
|
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
34073
|
+
for (const fallbackFilename of FALLBACK_TSCONFIG_FILENAMES) {
|
|
34074
|
+
const fallbackPath = Path.join(directory, fallbackFilename);
|
|
34075
|
+
if (isFile(fallbackPath)) return detectPreES2023FromConfig(fallbackPath);
|
|
34076
|
+
}
|
|
34052
34077
|
return false;
|
|
34053
34078
|
};
|
|
34054
34079
|
const REACT_DEPENDENCY_NAMES = new Set([
|
|
@@ -34210,6 +34235,7 @@ const discoverProjectWithoutPackageJson = (directory) => {
|
|
|
34210
34235
|
shopifyFlashListVersion: null,
|
|
34211
34236
|
shopifyFlashListMajorVersion: null,
|
|
34212
34237
|
hasReanimated: false,
|
|
34238
|
+
reanimatedVersion: null,
|
|
34213
34239
|
isPreES2023Target: hasOwnTsConfig && detectPreES2023Target(directory),
|
|
34214
34240
|
sourceFileCount
|
|
34215
34241
|
};
|
|
@@ -34302,6 +34328,7 @@ const discoverProject = (directory) => {
|
|
|
34302
34328
|
version: findShopifyFlashListVersion(directory, packageJson)
|
|
34303
34329
|
}) : null;
|
|
34304
34330
|
const hasReanimated = hasReactNativeWorkspace && someWorkspacePackageJson(directory, packageJson, isPackageJsonReanimatedAware);
|
|
34331
|
+
const reanimatedVersion = hasReanimated ? findInWorkspacePackageJsons(directory, packageJson, (workspacePackageJson) => getDependencySpec(workspacePackageJson, REANIMATED_DEPENDENCY_NAME)) : null;
|
|
34305
34332
|
const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
|
|
34306
34333
|
rootDirectory: directory,
|
|
34307
34334
|
rootPackageJson: packageJson,
|
|
@@ -34331,6 +34358,7 @@ const discoverProject = (directory) => {
|
|
|
34331
34358
|
shopifyFlashListVersion,
|
|
34332
34359
|
shopifyFlashListMajorVersion: shopifyFlashListVersion === null ? null : getLowestDependencyMajor(shopifyFlashListVersion),
|
|
34333
34360
|
hasReanimated,
|
|
34361
|
+
reanimatedVersion,
|
|
34334
34362
|
isPreES2023Target,
|
|
34335
34363
|
sourceFileCount
|
|
34336
34364
|
};
|
|
@@ -34523,6 +34551,9 @@ const buildRuleSeverityControls = (config) => {
|
|
|
34523
34551
|
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
34524
34552
|
};
|
|
34525
34553
|
};
|
|
34554
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34555
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34556
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34526
34557
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
34527
34558
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
34528
34559
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -34600,6 +34631,7 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
|
34600
34631
|
"react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
|
|
34601
34632
|
"react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
|
|
34602
34633
|
"react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
|
|
34634
|
+
"react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
|
|
34603
34635
|
"react/jsx-no-undef": "react-doctor/jsx-no-undef",
|
|
34604
34636
|
"react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
|
|
34605
34637
|
"react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
|
|
@@ -34673,9 +34705,6 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
34673
34705
|
if (!ruleSection) return [];
|
|
34674
34706
|
return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
|
|
34675
34707
|
};
|
|
34676
|
-
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34677
|
-
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34678
|
-
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34679
34708
|
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}\`).`;
|
|
34680
34709
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34681
34710
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
@@ -34729,6 +34758,53 @@ const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34729
34758
|
if (!ruleId.startsWith("react-doctor/")) return null;
|
|
34730
34759
|
return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
|
|
34731
34760
|
};
|
|
34761
|
+
const ESLINT_REACT_HOOKS_PLUGIN_PREFIX = "react-hooks/";
|
|
34762
|
+
const buildAcceptedTokens = (ruleId) => {
|
|
34763
|
+
const shortRuleId = ruleId.slice(15);
|
|
34764
|
+
return new Set([ruleId, `${ESLINT_REACT_HOOKS_PLUGIN_PREFIX}${shortRuleId}`]);
|
|
34765
|
+
};
|
|
34766
|
+
const listNamesRule = (ruleList, acceptedTokens) => tokenizeRuleList(ruleList).some((token) => acceptedTokens.has(token));
|
|
34767
|
+
const isInlineDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
34768
|
+
const candidates = [{
|
|
34769
|
+
line: lines[diagnosticLineIndex],
|
|
34770
|
+
requiredScope: "line"
|
|
34771
|
+
}, {
|
|
34772
|
+
line: lines[diagnosticLineIndex - 1],
|
|
34773
|
+
requiredScope: "next-line"
|
|
34774
|
+
}];
|
|
34775
|
+
for (const { line, requiredScope } of candidates) {
|
|
34776
|
+
const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
|
|
34777
|
+
if (!match) continue;
|
|
34778
|
+
const [, , scope, ruleList] = match;
|
|
34779
|
+
if (scope !== requiredScope) continue;
|
|
34780
|
+
if (listNamesRule(ruleList, acceptedTokens)) return true;
|
|
34781
|
+
}
|
|
34782
|
+
return false;
|
|
34783
|
+
};
|
|
34784
|
+
const isBlockDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
34785
|
+
let isRangeOpen = false;
|
|
34786
|
+
const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
|
|
34787
|
+
for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
|
|
34788
|
+
const line = lines[lineIndex];
|
|
34789
|
+
if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
|
|
34790
|
+
const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
|
|
34791
|
+
if (disableMatch && listNamesRule(disableMatch[2], acceptedTokens)) {
|
|
34792
|
+
isRangeOpen = true;
|
|
34793
|
+
continue;
|
|
34794
|
+
}
|
|
34795
|
+
const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
|
|
34796
|
+
if (enableMatch) {
|
|
34797
|
+
const enabledRules = tokenizeRuleList(enableMatch[1]);
|
|
34798
|
+
if (enabledRules.length === 0 || enabledRules.some((rule) => acceptedTokens.has(rule))) isRangeOpen = false;
|
|
34799
|
+
}
|
|
34800
|
+
}
|
|
34801
|
+
return isRangeOpen;
|
|
34802
|
+
};
|
|
34803
|
+
const isCompilerRuleForeignDisabled = (lines, diagnosticLineIndex, ruleId) => {
|
|
34804
|
+
if (!ruleId.startsWith("react-hooks-js/")) return false;
|
|
34805
|
+
const acceptedTokens = buildAcceptedTokens(ruleId);
|
|
34806
|
+
return isInlineDisabled(lines, diagnosticLineIndex, acceptedTokens) || isBlockDisabled(lines, diagnosticLineIndex, acceptedTokens);
|
|
34807
|
+
};
|
|
34732
34808
|
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
34733
34809
|
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
34734
34810
|
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
@@ -34872,6 +34948,11 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34872
34948
|
isSuppressed: true,
|
|
34873
34949
|
nearMissHint: null
|
|
34874
34950
|
};
|
|
34951
|
+
if (isCompilerRuleForeignDisabled(lines, diagnosticLineIndex, ruleId)) return {
|
|
34952
|
+
isSuppressed: true,
|
|
34953
|
+
nearMissHint: null,
|
|
34954
|
+
isForeignDirective: true
|
|
34955
|
+
};
|
|
34875
34956
|
const directComments = findStackedDisableCommentsAbove(lines, diagnosticLineIndex);
|
|
34876
34957
|
if (hasChainSuppressor(directComments, ruleId)) return {
|
|
34877
34958
|
isSuppressed: true,
|
|
@@ -35157,12 +35238,18 @@ const collectStringSet = (values) => {
|
|
|
35157
35238
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35158
35239
|
};
|
|
35159
35240
|
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35241
|
+
const NODE_CLI_PATH_SEGMENTS = new Set([
|
|
35242
|
+
"scripts",
|
|
35243
|
+
"bin",
|
|
35244
|
+
"tools"
|
|
35245
|
+
]);
|
|
35246
|
+
const hasNodeCliPathSegment = (filePath) => filePath.split(/[\\/]/).slice(0, -1).some((segment) => NODE_CLI_PATH_SEGMENTS.has(segment));
|
|
35160
35247
|
/**
|
|
35161
35248
|
* Pre-compiles every stateful filter and returns a single
|
|
35162
35249
|
* `apply(diagnostic)` closure that runs (in order):
|
|
35163
35250
|
*
|
|
35164
35251
|
* 1. auto-suppress (test-noise rules in test files; `migration-hint`
|
|
35165
|
-
* wins over `test-noise`)
|
|
35252
|
+
* wins over `test-noise`; `no-console` on Node CLI scripts)
|
|
35166
35253
|
* 2. severity overrides (top-level `rules` / `categories`, with
|
|
35167
35254
|
* `"off"` dropping)
|
|
35168
35255
|
* 3. warning suppression (only when `showWarnings` is false: drops every
|
|
@@ -35260,6 +35347,12 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35260
35347
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35261
35348
|
return false;
|
|
35262
35349
|
};
|
|
35350
|
+
const isNoConsoleOnNodeCliScript = (diagnostic) => {
|
|
35351
|
+
if (diagnostic.rule !== "no-console") return false;
|
|
35352
|
+
if (hasNodeCliPathSegment(diagnostic.filePath)) return true;
|
|
35353
|
+
const lines = getFileLines(diagnostic.filePath);
|
|
35354
|
+
return lines !== null && Boolean(lines[0]?.startsWith("#!"));
|
|
35355
|
+
};
|
|
35263
35356
|
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
35264
35357
|
if (!hasRuntimeGlobals) return false;
|
|
35265
35358
|
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
@@ -35269,6 +35362,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35269
35362
|
return {
|
|
35270
35363
|
apply: (diagnostic) => {
|
|
35271
35364
|
if (shouldAutoSuppress(diagnostic)) return null;
|
|
35365
|
+
if (isNoConsoleOnNodeCliScript(diagnostic)) return null;
|
|
35272
35366
|
let current = diagnostic;
|
|
35273
35367
|
let explicitSeverityOverride;
|
|
35274
35368
|
let explicitRuleOverride;
|
|
@@ -35298,7 +35392,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35298
35392
|
if (lines) {
|
|
35299
35393
|
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35300
35394
|
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35301
|
-
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
35395
|
+
if (evaluation.isSuppressed) return suppress(current, evaluation.isForeignDirective ? "foreign-inline" : "inline");
|
|
35302
35396
|
if (evaluation.nearMissHint) current = {
|
|
35303
35397
|
...current,
|
|
35304
35398
|
suppressionHint: evaluation.nearMissHint
|
|
@@ -39430,9 +39524,17 @@ const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
|
39430
39524
|
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`;
|
|
39431
39525
|
};
|
|
39432
39526
|
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";
|
|
39527
|
+
const ACCESSORS_MIN_VERSION = "3.15.0";
|
|
39528
|
+
const supportsAccessors = (reanimatedVersion) => {
|
|
39529
|
+
if (reanimatedVersion === null) return true;
|
|
39530
|
+
const lowerBound = import_semver.validRange(reanimatedVersion) !== null ? import_semver.minVersion(reanimatedVersion) : import_semver.coerce(reanimatedVersion);
|
|
39531
|
+
if (lowerBound === null) return true;
|
|
39532
|
+
return import_semver.gte(lowerBound, ACCESSORS_MIN_VERSION);
|
|
39533
|
+
};
|
|
39433
39534
|
const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
39434
39535
|
if (rule !== "immutability") return help;
|
|
39435
39536
|
if (!project.hasReanimated) return help;
|
|
39537
|
+
if (!supportsAccessors(project.reanimatedVersion)) return help;
|
|
39436
39538
|
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
39437
39539
|
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
39438
39540
|
};
|
|
@@ -39594,13 +39696,13 @@ const REACT_USE_BINDING_RESOLUTION = {
|
|
|
39594
39696
|
isReactUseBinding: true,
|
|
39595
39697
|
isReactNamespaceBinding: false
|
|
39596
39698
|
};
|
|
39597
|
-
const getScriptKind = (filename) => {
|
|
39699
|
+
const getScriptKind$2 = (filename) => {
|
|
39598
39700
|
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
39599
39701
|
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
39600
39702
|
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
39601
39703
|
return ts.ScriptKind.JS;
|
|
39602
39704
|
};
|
|
39603
|
-
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39705
|
+
const getUtf16Offset$1 = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39604
39706
|
const unwrapExpression = (expression) => {
|
|
39605
39707
|
let currentExpression = expression;
|
|
39606
39708
|
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
@@ -39849,8 +39951,8 @@ const findUseCallIdentifier = (sourceFile, useOffset) => {
|
|
|
39849
39951
|
return matchedIdentifier;
|
|
39850
39952
|
};
|
|
39851
39953
|
const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
|
|
39852
|
-
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(filename));
|
|
39853
|
-
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset(sourceText, utf8Offset));
|
|
39954
|
+
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$2(filename));
|
|
39955
|
+
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset$1(sourceText, utf8Offset));
|
|
39854
39956
|
if (!useIdentifier) return null;
|
|
39855
39957
|
return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
|
|
39856
39958
|
};
|
|
@@ -39871,6 +39973,201 @@ const shouldSuppressLocalUseHookDiagnostic = (diagnostic, rootDirectory) => {
|
|
|
39871
39973
|
const bindingResolution = resolveUseCallBinding(sourceText, absolutePath, primaryLabel.span.offset);
|
|
39872
39974
|
return bindingResolution !== null && !bindingResolution.isReactUseBinding;
|
|
39873
39975
|
};
|
|
39976
|
+
const SUPPRESSED_COMPILER_CODES = new Set(["react-hooks-js(immutability)", "react-hooks-js(refs)"]);
|
|
39977
|
+
const WORKLET_DIRECTIVE = "worklet";
|
|
39978
|
+
const WORKLET_ACCEPTING_CALLEES = new Set([
|
|
39979
|
+
"useAnimatedStyle",
|
|
39980
|
+
"useAnimatedProps",
|
|
39981
|
+
"useDerivedValue",
|
|
39982
|
+
"useAnimatedReaction",
|
|
39983
|
+
"useAnimatedScrollHandler",
|
|
39984
|
+
"useAnimatedGestureHandler",
|
|
39985
|
+
"useFrameCallback",
|
|
39986
|
+
"runOnUI",
|
|
39987
|
+
"executeOnUIRuntimeSync"
|
|
39988
|
+
]);
|
|
39989
|
+
const GESTURE_ROOT_IDENTIFIER = "Gesture";
|
|
39990
|
+
const getScriptKind$1 = (filename) => {
|
|
39991
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
39992
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
39993
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
39994
|
+
return ts.ScriptKind.JS;
|
|
39995
|
+
};
|
|
39996
|
+
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39997
|
+
const hasWorkletDirective = (node) => {
|
|
39998
|
+
if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node)) return false;
|
|
39999
|
+
const body = node.body;
|
|
40000
|
+
if (!body || !ts.isBlock(body)) return false;
|
|
40001
|
+
for (const statement of body.statements) {
|
|
40002
|
+
if (!ts.isExpressionStatement(statement)) return false;
|
|
40003
|
+
if (!ts.isStringLiteral(statement.expression)) return false;
|
|
40004
|
+
if (statement.expression.text === WORKLET_DIRECTIVE) return true;
|
|
40005
|
+
}
|
|
40006
|
+
return false;
|
|
40007
|
+
};
|
|
40008
|
+
const rootIdentifierOfCallChain = (expression) => {
|
|
40009
|
+
let currentExpression = expression;
|
|
40010
|
+
while (true) {
|
|
40011
|
+
if (ts.isIdentifier(currentExpression)) return currentExpression.text;
|
|
40012
|
+
if (ts.isPropertyAccessExpression(currentExpression)) {
|
|
40013
|
+
currentExpression = currentExpression.expression;
|
|
40014
|
+
continue;
|
|
40015
|
+
}
|
|
40016
|
+
if (ts.isCallExpression(currentExpression)) {
|
|
40017
|
+
currentExpression = currentExpression.expression;
|
|
40018
|
+
continue;
|
|
40019
|
+
}
|
|
40020
|
+
return null;
|
|
40021
|
+
}
|
|
40022
|
+
};
|
|
40023
|
+
const isWorkletAcceptingCall = (callExpression) => {
|
|
40024
|
+
const callee = callExpression.expression;
|
|
40025
|
+
if (ts.isIdentifier(callee)) return WORKLET_ACCEPTING_CALLEES.has(callee.text);
|
|
40026
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
40027
|
+
if (WORKLET_ACCEPTING_CALLEES.has(callee.name.text)) return true;
|
|
40028
|
+
return rootIdentifierOfCallChain(callee) === GESTURE_ROOT_IDENTIFIER;
|
|
40029
|
+
}
|
|
40030
|
+
return false;
|
|
40031
|
+
};
|
|
40032
|
+
const isFunctionWorklet = (functionNode) => {
|
|
40033
|
+
if (hasWorkletDirective(functionNode)) return true;
|
|
40034
|
+
const parent = functionNode.parent;
|
|
40035
|
+
return ts.isCallExpression(parent) && parent.arguments.some((argument) => argument === functionNode) && isWorkletAcceptingCall(parent);
|
|
40036
|
+
};
|
|
40037
|
+
const findNodeAtOffset = (sourceFile, targetOffset) => {
|
|
40038
|
+
let matchedNode = null;
|
|
40039
|
+
const visit = (node) => {
|
|
40040
|
+
if (node.getStart(sourceFile) > targetOffset || node.getEnd() <= targetOffset) return;
|
|
40041
|
+
matchedNode = node;
|
|
40042
|
+
ts.forEachChild(node, visit);
|
|
40043
|
+
};
|
|
40044
|
+
visit(sourceFile);
|
|
40045
|
+
return matchedNode;
|
|
40046
|
+
};
|
|
40047
|
+
const isOffsetInsideWorklet = (sourceFile, targetOffset) => {
|
|
40048
|
+
let currentNode = findNodeAtOffset(sourceFile, targetOffset);
|
|
40049
|
+
while (currentNode) {
|
|
40050
|
+
if (ts.isFunctionLike(currentNode) && isFunctionWorklet(currentNode)) return true;
|
|
40051
|
+
currentNode = currentNode.parent;
|
|
40052
|
+
}
|
|
40053
|
+
return false;
|
|
40054
|
+
};
|
|
40055
|
+
const shouldSuppressCompilerFindingInWorklet = (diagnostic, project, rootDirectory) => {
|
|
40056
|
+
if (!SUPPRESSED_COMPILER_CODES.has(diagnostic.code)) return false;
|
|
40057
|
+
if (!project.hasReanimated) return false;
|
|
40058
|
+
const primaryLabel = diagnostic.labels[0];
|
|
40059
|
+
if (!primaryLabel) return false;
|
|
40060
|
+
const absolutePath = Path.isAbsolute(diagnostic.filename) ? diagnostic.filename : Path.join(rootDirectory, diagnostic.filename);
|
|
40061
|
+
let sourceText;
|
|
40062
|
+
try {
|
|
40063
|
+
sourceText = NFS.readFileSync(absolutePath, "utf-8");
|
|
40064
|
+
} catch {
|
|
40065
|
+
return false;
|
|
40066
|
+
}
|
|
40067
|
+
return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$1(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
|
|
40068
|
+
};
|
|
40069
|
+
const MANUAL_MEMOIZATION_PLUGIN = "react-doctor";
|
|
40070
|
+
const MANUAL_MEMOIZATION_RULE = "react-compiler-no-manual-memoization";
|
|
40071
|
+
const REACT_COMPILER_PLUGIN = "react-hooks-js";
|
|
40072
|
+
const getScriptKind = (filename) => {
|
|
40073
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
40074
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
40075
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
40076
|
+
return ts.ScriptKind.JS;
|
|
40077
|
+
};
|
|
40078
|
+
const isManualMemoizationDiagnostic = (diagnostic) => diagnostic.plugin === MANUAL_MEMOIZATION_PLUGIN && diagnostic.rule === MANUAL_MEMOIZATION_RULE;
|
|
40079
|
+
const nodeLineRange = (sourceFile, node) => ({
|
|
40080
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
40081
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1
|
|
40082
|
+
});
|
|
40083
|
+
const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
|
|
40084
|
+
let foundRange = null;
|
|
40085
|
+
const visit = (node) => {
|
|
40086
|
+
if (foundRange) return;
|
|
40087
|
+
if (ts.isFunctionLike(node)) {
|
|
40088
|
+
const range = nodeLineRange(sourceFile, node);
|
|
40089
|
+
if (range.startLine <= targetLine && targetLine <= range.endLine) foundRange = range;
|
|
40090
|
+
return;
|
|
40091
|
+
}
|
|
40092
|
+
ts.forEachChild(node, visit);
|
|
40093
|
+
};
|
|
40094
|
+
visit(sourceFile);
|
|
40095
|
+
return foundRange;
|
|
40096
|
+
};
|
|
40097
|
+
const NEWLINE_BYTE = 10;
|
|
40098
|
+
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
40099
|
+
let lineNumber = 1;
|
|
40100
|
+
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
40101
|
+
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
|
|
40102
|
+
return lineNumber;
|
|
40103
|
+
};
|
|
40104
|
+
/**
|
|
40105
|
+
* Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
|
|
40106
|
+
* whose enclosing function also carries a `react-hooks-js` (React
|
|
40107
|
+
* Compiler) diagnostic. Every react-hooks-js finding marks a function the
|
|
40108
|
+
* compiler could NOT compile (a bail-out / skip / preserve-manual-
|
|
40109
|
+
* memoization failure), so "React Compiler already caches every value
|
|
40110
|
+
* here" is false for that function and the manual memoization is
|
|
40111
|
+
* load-bearing, not dead weight.
|
|
40112
|
+
*
|
|
40113
|
+
* Matching is per enclosing function, not per file: a bailed-out
|
|
40114
|
+
* component must not silence redundant-memo findings in a sibling
|
|
40115
|
+
* component the compiler did optimize.
|
|
40116
|
+
*
|
|
40117
|
+
* Module-scope `memo(() => …)` wrappers have no enclosing function; for
|
|
40118
|
+
* those the memo call's own span is the containment range, so a bail-out
|
|
40119
|
+
* inside the inline-wrapped component still suppresses. Every failure
|
|
40120
|
+
* path (unreadable file, missing span) fails open and keeps the
|
|
40121
|
+
* diagnostic.
|
|
40122
|
+
*/
|
|
40123
|
+
const suppressMemoizationInBailedOutFunctions = (diagnostics, rootDirectory) => {
|
|
40124
|
+
const compilerDiagnosticLinesByFile = /* @__PURE__ */ new Map();
|
|
40125
|
+
let hasManualMemoizationDiagnostic = false;
|
|
40126
|
+
for (const diagnostic of diagnostics) {
|
|
40127
|
+
if (isManualMemoizationDiagnostic(diagnostic)) {
|
|
40128
|
+
hasManualMemoizationDiagnostic = true;
|
|
40129
|
+
continue;
|
|
40130
|
+
}
|
|
40131
|
+
if (diagnostic.plugin !== REACT_COMPILER_PLUGIN || diagnostic.line <= 0) continue;
|
|
40132
|
+
const lines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
40133
|
+
if (lines) lines.push(diagnostic.line);
|
|
40134
|
+
else compilerDiagnosticLinesByFile.set(diagnostic.filePath, [diagnostic.line]);
|
|
40135
|
+
}
|
|
40136
|
+
if (!hasManualMemoizationDiagnostic || compilerDiagnosticLinesByFile.size === 0) return diagnostics;
|
|
40137
|
+
const parsedFileCache = /* @__PURE__ */ new Map();
|
|
40138
|
+
const readAndParse = (filePath) => {
|
|
40139
|
+
const cached = parsedFileCache.get(filePath);
|
|
40140
|
+
if (cached !== void 0) return cached;
|
|
40141
|
+
const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory || ".", filePath);
|
|
40142
|
+
let parsed;
|
|
40143
|
+
try {
|
|
40144
|
+
const buffer = NFS.readFileSync(absolutePath);
|
|
40145
|
+
parsed = {
|
|
40146
|
+
sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
40147
|
+
buffer
|
|
40148
|
+
};
|
|
40149
|
+
} catch {
|
|
40150
|
+
parsed = null;
|
|
40151
|
+
}
|
|
40152
|
+
parsedFileCache.set(filePath, parsed);
|
|
40153
|
+
return parsed;
|
|
40154
|
+
};
|
|
40155
|
+
const isInsideBailedOutFunction = (diagnostic) => {
|
|
40156
|
+
const compilerLines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
40157
|
+
if (!compilerLines || diagnostic.line <= 0) return false;
|
|
40158
|
+
const parsed = readAndParse(diagnostic.filePath);
|
|
40159
|
+
if (!parsed) return false;
|
|
40160
|
+
let containmentRange = findOutermostEnclosingFunctionRange(parsed.sourceFile, diagnostic.line);
|
|
40161
|
+
if (!containmentRange && diagnostic.offset !== void 0 && diagnostic.length !== void 0) containmentRange = {
|
|
40162
|
+
startLine: diagnostic.line,
|
|
40163
|
+
endLine: lineOfUtf8Offset(parsed.buffer, diagnostic.offset + diagnostic.length)
|
|
40164
|
+
};
|
|
40165
|
+
if (!containmentRange) return false;
|
|
40166
|
+
const { startLine, endLine } = containmentRange;
|
|
40167
|
+
return compilerLines.some((compilerLine) => startLine <= compilerLine && compilerLine <= endLine);
|
|
40168
|
+
};
|
|
40169
|
+
return diagnostics.filter((diagnostic) => !isManualMemoizationDiagnostic(diagnostic) || !isInsideBailedOutFunction(diagnostic));
|
|
40170
|
+
};
|
|
39874
40171
|
const FILEPATH_WITH_LOCATION_PATTERN = /\S+\.\w+:\d+:\d+[\s\S]*$/;
|
|
39875
40172
|
const LEADING_SEVERITY_LABEL_PATTERN = /^(?:Error|Warning):\s*/;
|
|
39876
40173
|
const TRAILING_PERIOD_PATTERN = /\.$/;
|
|
@@ -40010,7 +40307,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
40010
40307
|
minifiedFileCache.set(absolutePath, minified);
|
|
40011
40308
|
return minified;
|
|
40012
40309
|
};
|
|
40013
|
-
return parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory)).map((diagnostic) => {
|
|
40310
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
40014
40311
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
40015
40312
|
const primaryLabel = diagnostic.labels[0];
|
|
40016
40313
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
@@ -40037,7 +40334,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
40037
40334
|
...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
|
|
40038
40335
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
40039
40336
|
};
|
|
40040
|
-
});
|
|
40337
|
+
}), rootDirectory);
|
|
40041
40338
|
};
|
|
40042
40339
|
const buildOxlintChildEnv = (sourceEnv) => {
|
|
40043
40340
|
const childEnv = {};
|
|
@@ -40548,6 +40845,7 @@ const runOxlint = async (options) => {
|
|
|
40548
40845
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
40549
40846
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
40550
40847
|
validateRuleRegistration();
|
|
40848
|
+
resetManifestCaches();
|
|
40551
40849
|
if (includePaths !== void 0 && includePaths.length === 0) return [];
|
|
40552
40850
|
const pluginPath = resolvePluginPath();
|
|
40553
40851
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
@@ -41210,9 +41508,10 @@ const CONTEXT_AXES = [
|
|
|
41210
41508
|
}
|
|
41211
41509
|
];
|
|
41212
41510
|
const SCORE_AXES = [...GATED_AXES, ...CONTEXT_AXES];
|
|
41213
|
-
const
|
|
41214
|
-
|
|
41215
|
-
|
|
41511
|
+
const gatedAxesForSection = (section) => section === "devDependencies" ? GATED_AXES.filter((axis) => axis.key !== "vulnerability") : GATED_AXES;
|
|
41512
|
+
const worstGatedAxis = (score, gatedAxes) => {
|
|
41513
|
+
let worst = gatedAxes[0];
|
|
41514
|
+
for (const axis of gatedAxes) if (score[axis.key] < score[worst.key]) worst = axis;
|
|
41216
41515
|
return worst;
|
|
41217
41516
|
};
|
|
41218
41517
|
const clampScore = (value) => {
|
|
@@ -41236,6 +41535,69 @@ const resolveConcreteVersion = (spec) => {
|
|
|
41236
41535
|
if (range === null || range === "*") return null;
|
|
41237
41536
|
return import_semver.minVersion(trimmed)?.version ?? null;
|
|
41238
41537
|
};
|
|
41538
|
+
const readTextFileOrNull = (filePath) => {
|
|
41539
|
+
try {
|
|
41540
|
+
return NFS.readFileSync(filePath, "utf-8");
|
|
41541
|
+
} catch {
|
|
41542
|
+
return null;
|
|
41543
|
+
}
|
|
41544
|
+
};
|
|
41545
|
+
const buildNpmLockLookup = (lockText) => {
|
|
41546
|
+
let parsed;
|
|
41547
|
+
try {
|
|
41548
|
+
parsed = JSON.parse(lockText);
|
|
41549
|
+
} catch {
|
|
41550
|
+
return null;
|
|
41551
|
+
}
|
|
41552
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
41553
|
+
const lock = parsed;
|
|
41554
|
+
return (name) => {
|
|
41555
|
+
const entry = lock.packages?.[`node_modules/${name}`] ?? lock.dependencies?.[name];
|
|
41556
|
+
return typeof entry?.version === "string" ? entry.version : null;
|
|
41557
|
+
};
|
|
41558
|
+
};
|
|
41559
|
+
const buildYarnLockLookup = (lockText) => {
|
|
41560
|
+
const lines = lockText.split(/\r?\n/);
|
|
41561
|
+
return (name, spec) => {
|
|
41562
|
+
const descriptor = `${name}@${spec}`;
|
|
41563
|
+
const berryDescriptor = `${name}@npm:${spec}`;
|
|
41564
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
41565
|
+
const line = lines[lineIndex];
|
|
41566
|
+
if (line.startsWith(" ") || line.startsWith("#") || !line.includes(":")) continue;
|
|
41567
|
+
const descriptors = line.replace(/:\s*$/, "").split(",").map((part) => part.trim().replace(/^"|"$/g, ""));
|
|
41568
|
+
if (!descriptors.includes(descriptor) && !descriptors.includes(berryDescriptor)) continue;
|
|
41569
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && (lines[bodyIndex].startsWith(" ") || lines[bodyIndex] === ""); bodyIndex += 1) {
|
|
41570
|
+
const versionMatch = /^\s+version:?\s+"?([^"\s]+)"?/.exec(lines[bodyIndex]);
|
|
41571
|
+
if (versionMatch) return versionMatch[1];
|
|
41572
|
+
}
|
|
41573
|
+
return null;
|
|
41574
|
+
}
|
|
41575
|
+
return null;
|
|
41576
|
+
};
|
|
41577
|
+
};
|
|
41578
|
+
const buildPnpmLockLookup = (lockText) => {
|
|
41579
|
+
return (name) => {
|
|
41580
|
+
const entryPattern = new RegExp(`^ {4,8}['"]?${name.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}['"]?:\\s*$`);
|
|
41581
|
+
const lines = lockText.split(/\r?\n/);
|
|
41582
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
41583
|
+
if (!entryPattern.test(lines[lineIndex])) continue;
|
|
41584
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && /^ {5,}\S/.test(lines[bodyIndex]); bodyIndex += 1) {
|
|
41585
|
+
const versionMatch = /^\s+version:\s+['"]?([^'"\s(]+)/.exec(lines[bodyIndex]);
|
|
41586
|
+
if (versionMatch) return versionMatch[1];
|
|
41587
|
+
}
|
|
41588
|
+
}
|
|
41589
|
+
return null;
|
|
41590
|
+
};
|
|
41591
|
+
};
|
|
41592
|
+
const buildLockedVersionLookup = (rootDirectory) => {
|
|
41593
|
+
const npmLockText = readTextFileOrNull(Path.join(rootDirectory, "package-lock.json"));
|
|
41594
|
+
if (npmLockText !== null) return buildNpmLockLookup(npmLockText);
|
|
41595
|
+
const yarnLockText = readTextFileOrNull(Path.join(rootDirectory, "yarn.lock"));
|
|
41596
|
+
if (yarnLockText !== null) return buildYarnLockLookup(yarnLockText);
|
|
41597
|
+
const pnpmLockText = readTextFileOrNull(Path.join(rootDirectory, "pnpm-lock.yaml"));
|
|
41598
|
+
if (pnpmLockText !== null) return buildPnpmLockLookup(pnpmLockText);
|
|
41599
|
+
return null;
|
|
41600
|
+
};
|
|
41239
41601
|
const locateDependencyKey = (packageJsonText, section, name) => {
|
|
41240
41602
|
const needle = `"${name}"`;
|
|
41241
41603
|
const sectionHeader = new RegExp(`"${section}"\\s*:\\s*\\{`);
|
|
@@ -41262,7 +41624,7 @@ const locateDependencyKey = (packageJsonText, section, name) => {
|
|
|
41262
41624
|
}
|
|
41263
41625
|
return null;
|
|
41264
41626
|
};
|
|
41265
|
-
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies) => {
|
|
41627
|
+
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies, lockedVersionLookup) => {
|
|
41266
41628
|
const sectionByName = /* @__PURE__ */ new Map();
|
|
41267
41629
|
for (const name of Object.keys(packageJson.dependencies ?? {})) sectionByName.set(name, "dependencies");
|
|
41268
41630
|
if (includeDevDependencies) {
|
|
@@ -41272,13 +41634,18 @@ const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDepe
|
|
|
41272
41634
|
for (const [name, section] of sectionByName) {
|
|
41273
41635
|
if (SUPPLY_CHAIN_IGNORED_PACKAGES.has(name)) continue;
|
|
41274
41636
|
const spec = (packageJson[section] ?? {})[name] ?? "";
|
|
41275
|
-
const
|
|
41276
|
-
if (
|
|
41637
|
+
const floorVersion = resolveConcreteVersion(spec);
|
|
41638
|
+
if (floorVersion === null) continue;
|
|
41639
|
+
const lockedVersion = lockedVersionLookup?.(name, spec) ?? null;
|
|
41640
|
+
const lockSatisfiesSpec = lockedVersion !== null && import_semver.valid(lockedVersion) !== null && import_semver.satisfies(lockedVersion, spec);
|
|
41641
|
+
const isExactPin = import_semver.valid(spec.trim()) !== null;
|
|
41277
41642
|
const location = locateDependencyKey(packageJsonText, section, name);
|
|
41278
41643
|
dependencies.push({
|
|
41279
41644
|
name,
|
|
41280
|
-
version,
|
|
41645
|
+
version: lockSatisfiesSpec ? lockedVersion : floorVersion,
|
|
41281
41646
|
spec,
|
|
41647
|
+
section,
|
|
41648
|
+
resolution: isExactPin ? "pin" : lockSatisfiesSpec ? "locked" : "floor",
|
|
41282
41649
|
line: location?.line ?? 0,
|
|
41283
41650
|
column: location?.column ?? 0
|
|
41284
41651
|
});
|
|
@@ -41429,7 +41796,11 @@ const formatAlertReason = (topAlerts, totalCount) => {
|
|
|
41429
41796
|
}
|
|
41430
41797
|
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)}.`;
|
|
41431
41798
|
};
|
|
41432
|
-
const formatDependencyIdentity = (dependency) =>
|
|
41799
|
+
const formatDependencyIdentity = (dependency) => {
|
|
41800
|
+
if (dependency.resolution === "pin") return `${dependency.name}@${dependency.version}`;
|
|
41801
|
+
if (dependency.resolution === "locked") return `${dependency.name}@${dependency.version} (lockfile resolution of "${dependency.spec}")`;
|
|
41802
|
+
return `${dependency.name}@${dependency.version} (lowest version "${dependency.spec}" allows)`;
|
|
41803
|
+
};
|
|
41433
41804
|
const buildSupplyChainHelp = (dependency, failingAxis, topAlerts, packagePageUrl, options) => {
|
|
41434
41805
|
const hasCriticalAlert = topAlerts.some((alert) => alert.severity.toLowerCase() === "critical");
|
|
41435
41806
|
const entry = `\`"${dependency.name}": "${dependency.spec}"\``;
|
|
@@ -41472,7 +41843,7 @@ const buildLowScoreDiagnostic = (dependency, artifact, failingAxis, options) =>
|
|
|
41472
41843
|
const checkSupplyChain = (input) => gen(function* () {
|
|
41473
41844
|
const options = resolveOptions(input.userConfig);
|
|
41474
41845
|
const packageJsonPath = Path.join(input.rootDirectory, "package.json");
|
|
41475
|
-
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
41846
|
+
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies, buildLockedVersionLookup(input.rootDirectory));
|
|
41476
41847
|
if (dependencies.length === 0) return [];
|
|
41477
41848
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
41478
41849
|
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
@@ -41481,7 +41852,7 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
41481
41852
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
41482
41853
|
const artifact = artifacts[index];
|
|
41483
41854
|
if (!artifact) continue;
|
|
41484
|
-
const worstAxis = worstGatedAxis(artifact.score);
|
|
41855
|
+
const worstAxis = worstGatedAxis(artifact.score, gatedAxesForSection(dependencies[index].section));
|
|
41485
41856
|
if (toHundred(artifact.score[worstAxis.key]) >= options.minScore) continue;
|
|
41486
41857
|
diagnostics.push(buildLowScoreDiagnostic(dependencies[index], artifact, worstAxis, options));
|
|
41487
41858
|
}
|
|
@@ -42524,4 +42895,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
42524
42895
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
42525
42896
|
|
|
42526
42897
|
//# sourceMappingURL=index.js.map
|
|
42527
|
-
//# debugId=
|
|
42898
|
+
//# debugId=10e3e1b0-f291-5306-b5fd-79b158b00159
|