react-doctor 0.8.0 → 0.8.1-dev.0b0b5ac
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -4
- package/dist/cli.js +1043 -170
- package/dist/index.d.ts +119 -94
- package/dist/index.js +460 -60
- package/dist/lsp.js +461 -61
- package/dist/skills/react-doctor/SKILL.md +5 -0
- package/dist/skills/react-doctor/references/explain.md +1 -0
- package/package.json +6 -6
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a9af63ed-7b82-56d3-8549-274ab7f23579")}catch(e){}}();
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as NodeChildProcess from "node:child_process";
|
|
5
5
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -36242,7 +36242,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
36242
36242
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
36243
36243
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
36244
36244
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
36245
|
-
const CORE_PACKAGE_VERSION = "0.8.
|
|
36245
|
+
const CORE_PACKAGE_VERSION = "0.8.1";
|
|
36246
36246
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
36247
36247
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
36248
36248
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -36571,10 +36571,10 @@ const FALLBACK_TSCONFIG_FILENAMES = ["tsconfig.app.json", "tsconfig.build.json"]
|
|
|
36571
36571
|
const FRAMEWORK_PACKAGES = {
|
|
36572
36572
|
next: "nextjs",
|
|
36573
36573
|
"@tanstack/react-start": "tanstack-start",
|
|
36574
|
-
vite: "vite",
|
|
36575
|
-
"react-scripts": "cra",
|
|
36576
36574
|
"@remix-run/react": "remix",
|
|
36577
36575
|
gatsby: "gatsby",
|
|
36576
|
+
vite: "vite",
|
|
36577
|
+
"react-scripts": "cra",
|
|
36578
36578
|
expo: "expo",
|
|
36579
36579
|
"react-native": "react-native"
|
|
36580
36580
|
};
|
|
@@ -36598,11 +36598,17 @@ const NEXT_CONFIG_FILENAMES = [
|
|
|
36598
36598
|
];
|
|
36599
36599
|
const BABEL_CONFIG_FILENAMES = [
|
|
36600
36600
|
".babelrc",
|
|
36601
|
+
".babelrc.js",
|
|
36601
36602
|
".babelrc.json",
|
|
36603
|
+
".babelrc.cjs",
|
|
36604
|
+
".babelrc.mjs",
|
|
36605
|
+
".babelrc.cts",
|
|
36602
36606
|
"babel.config.js",
|
|
36603
36607
|
"babel.config.json",
|
|
36604
36608
|
"babel.config.cjs",
|
|
36605
|
-
"babel.config.mjs"
|
|
36609
|
+
"babel.config.mjs",
|
|
36610
|
+
"babel.config.ts",
|
|
36611
|
+
"babel.config.cts"
|
|
36606
36612
|
];
|
|
36607
36613
|
const VITE_CONFIG_FILENAMES = [
|
|
36608
36614
|
"vite.config.js",
|
|
@@ -36614,12 +36620,28 @@ const VITE_CONFIG_FILENAMES = [
|
|
|
36614
36620
|
"vitest.config.ts",
|
|
36615
36621
|
"vitest.config.js"
|
|
36616
36622
|
];
|
|
36623
|
+
const RSBUILD_CONFIG_FILENAMES = [
|
|
36624
|
+
"rsbuild.config.ts",
|
|
36625
|
+
"rsbuild.config.js",
|
|
36626
|
+
"rsbuild.config.mts",
|
|
36627
|
+
"rsbuild.config.mjs",
|
|
36628
|
+
"rsbuild.config.cts",
|
|
36629
|
+
"rsbuild.config.cjs"
|
|
36630
|
+
];
|
|
36631
|
+
const RSPACK_CONFIG_FILENAMES = [
|
|
36632
|
+
"rspack.config.ts",
|
|
36633
|
+
"rspack.config.js",
|
|
36634
|
+
"rspack.config.mts",
|
|
36635
|
+
"rspack.config.mjs",
|
|
36636
|
+
"rspack.config.cts",
|
|
36637
|
+
"rspack.config.cjs"
|
|
36638
|
+
];
|
|
36617
36639
|
const EXPO_APP_CONFIG_FILENAMES = [
|
|
36618
36640
|
"app.json",
|
|
36619
36641
|
"app.config.js",
|
|
36620
36642
|
"app.config.ts"
|
|
36621
36643
|
];
|
|
36622
|
-
const REACT_COMPILER_PACKAGE_REFERENCE_PATTERN = /babel-plugin-react-compiler|react-compiler-runtime|["']react-compiler["']/;
|
|
36644
|
+
const REACT_COMPILER_PACKAGE_REFERENCE_PATTERN = /babel-plugin-react-compiler|react-compiler-runtime|reactCompilerPreset\s*\(|["']react-compiler["']/;
|
|
36623
36645
|
const REACT_COMPILER_ENABLED_FLAG_PATTERN = /["']?reactCompiler["']?\s*:\s*(?:true\b|\{)/;
|
|
36624
36646
|
const STATIC_EXPORT_OUTPUT_PATTERN = /(?:^|[^.\w])["']?output["']?\s*:\s*["']export["']/m;
|
|
36625
36647
|
const hasCompilerPackage = (packageJson, compilerPackages) => {
|
|
@@ -36654,6 +36676,8 @@ const detectReactCompiler = (directory, packageJson) => {
|
|
|
36654
36676
|
if (hasCompilerInConfigFiles(directory, NEXT_CONFIG_FILENAMES)) return true;
|
|
36655
36677
|
if (hasCompilerInConfigFiles(directory, BABEL_CONFIG_FILENAMES)) return true;
|
|
36656
36678
|
if (hasCompilerInConfigFiles(directory, VITE_CONFIG_FILENAMES)) return true;
|
|
36679
|
+
if (hasCompilerInConfigFiles(directory, RSBUILD_CONFIG_FILENAMES)) return true;
|
|
36680
|
+
if (hasCompilerInConfigFiles(directory, RSPACK_CONFIG_FILENAMES)) return true;
|
|
36657
36681
|
if (hasCompilerInConfigFiles(directory, EXPO_APP_CONFIG_FILENAMES)) return true;
|
|
36658
36682
|
return hasCompilerPackageInAncestors(directory, REACT_COMPILER_TRANSFORM_PACKAGES);
|
|
36659
36683
|
};
|
|
@@ -37101,19 +37125,6 @@ const getPreactVersion = (packageJson) => {
|
|
|
37101
37125
|
...packageJson.devDependencies
|
|
37102
37126
|
}.preact ?? null;
|
|
37103
37127
|
};
|
|
37104
|
-
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
37105
|
-
"@tanstack/react-query",
|
|
37106
|
-
"@tanstack/query-core",
|
|
37107
|
-
"react-query"
|
|
37108
|
-
]);
|
|
37109
|
-
const hasTanStackQuery = (packageJson) => {
|
|
37110
|
-
const allDependencies = {
|
|
37111
|
-
...packageJson.peerDependencies,
|
|
37112
|
-
...packageJson.dependencies,
|
|
37113
|
-
...packageJson.devDependencies
|
|
37114
|
-
};
|
|
37115
|
-
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
37116
|
-
};
|
|
37117
37128
|
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
37118
37129
|
if (version === null || !isCatalogReference(version)) return version;
|
|
37119
37130
|
const catalogName = extractCatalogName(version);
|
|
@@ -37316,7 +37327,60 @@ const listWorkspacePackages = (rootDirectory) => {
|
|
|
37316
37327
|
}
|
|
37317
37328
|
return packages;
|
|
37318
37329
|
};
|
|
37330
|
+
const getPreferredDependencyVersion = ({ packageJson, packageNames }) => {
|
|
37331
|
+
const allDependencies = {
|
|
37332
|
+
...packageJson.devDependencies,
|
|
37333
|
+
...packageJson.peerDependencies,
|
|
37334
|
+
...packageJson.dependencies
|
|
37335
|
+
};
|
|
37336
|
+
for (const packageName of packageNames) {
|
|
37337
|
+
const version = allDependencies[packageName];
|
|
37338
|
+
if (version !== void 0) return version;
|
|
37339
|
+
}
|
|
37340
|
+
return null;
|
|
37341
|
+
};
|
|
37342
|
+
const TANSTACK_REACT_QUERY_PACKAGES = ["@tanstack/react-query", "react-query"];
|
|
37343
|
+
const getTanStackQueryVersion = (packageJson) => {
|
|
37344
|
+
return getPreferredDependencyVersion({
|
|
37345
|
+
packageJson,
|
|
37346
|
+
packageNames: TANSTACK_REACT_QUERY_PACKAGES
|
|
37347
|
+
});
|
|
37348
|
+
};
|
|
37349
|
+
const STYLED_COMPONENTS_PACKAGES = ["styled-components"];
|
|
37350
|
+
const getStyledComponentsVersion = (packageJson) => {
|
|
37351
|
+
return getPreferredDependencyVersion({
|
|
37352
|
+
packageJson,
|
|
37353
|
+
packageNames: STYLED_COMPONENTS_PACKAGES
|
|
37354
|
+
});
|
|
37355
|
+
};
|
|
37356
|
+
const I18N_PACKAGES = [
|
|
37357
|
+
"i18next",
|
|
37358
|
+
"react-i18next",
|
|
37359
|
+
"next-i18next",
|
|
37360
|
+
"react-intl",
|
|
37361
|
+
"next-intl",
|
|
37362
|
+
"use-intl",
|
|
37363
|
+
"@formatjs/intl",
|
|
37364
|
+
"@lingui/core",
|
|
37365
|
+
"@lingui/react",
|
|
37366
|
+
"typesafe-i18n",
|
|
37367
|
+
"@tolgee/react",
|
|
37368
|
+
"react-intl-universal"
|
|
37369
|
+
];
|
|
37370
|
+
const hasI18nDependency = (packageJson) => {
|
|
37371
|
+
const allDependencies = {
|
|
37372
|
+
...packageJson.peerDependencies,
|
|
37373
|
+
...packageJson.dependencies,
|
|
37374
|
+
...packageJson.devDependencies,
|
|
37375
|
+
...packageJson.optionalDependencies
|
|
37376
|
+
};
|
|
37377
|
+
return I18N_PACKAGES.some((packageName) => allDependencies[packageName] !== void 0);
|
|
37378
|
+
};
|
|
37319
37379
|
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
37380
|
+
const MOBX_REACT_PACKAGE_NAME = "mobx-react";
|
|
37381
|
+
const MOBX_REACT_LITE_PACKAGE_NAME = "mobx-react-lite";
|
|
37382
|
+
const MOBX_STATE_TREE_PACKAGE_NAME = "mobx-state-tree";
|
|
37383
|
+
const MOBX_REACT_OBSERVER_PACKAGE_NAME = "mobx-react-observer";
|
|
37320
37384
|
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
37321
37385
|
const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
|
|
37322
37386
|
const dependencyDeclaration = getDependencyDeclaration({
|
|
@@ -37335,7 +37399,26 @@ const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
|
|
|
37335
37399
|
if (nextMajor === null) return false;
|
|
37336
37400
|
return nextMajor < currentMajor;
|
|
37337
37401
|
};
|
|
37338
|
-
const
|
|
37402
|
+
const shouldReplaceSupportedDependencyFact = (currentFact, nextFact, latestSupportedMajor) => {
|
|
37403
|
+
if (currentFact.version === null) return true;
|
|
37404
|
+
if (nextFact.version === null) return false;
|
|
37405
|
+
const currentMajor = getLowestDependencyMajor(currentFact.version);
|
|
37406
|
+
const nextMajor = getLowestDependencyMajor(nextFact.version);
|
|
37407
|
+
if (currentMajor === null) return false;
|
|
37408
|
+
if (nextMajor === null) return true;
|
|
37409
|
+
if (currentMajor > latestSupportedMajor) return false;
|
|
37410
|
+
if (nextMajor > latestSupportedMajor) return true;
|
|
37411
|
+
return nextMajor < currentMajor;
|
|
37412
|
+
};
|
|
37413
|
+
const shouldReplaceStyledComponentsVersion = (currentVersion, nextVersion) => {
|
|
37414
|
+
if (!currentVersion) return true;
|
|
37415
|
+
const currentMajor = getLowestDependencyMajor(currentVersion);
|
|
37416
|
+
const nextMajor = getLowestDependencyMajor(nextVersion);
|
|
37417
|
+
if (currentMajor === null) return nextMajor !== null && nextMajor < 6;
|
|
37418
|
+
if (nextMajor === null) return currentMajor >= 6;
|
|
37419
|
+
return nextMajor < currentMajor;
|
|
37420
|
+
};
|
|
37421
|
+
const evaluateManifestFacts = (facts, packageJson, directory, rootDirectory, rootPackageJson) => {
|
|
37339
37422
|
if (facts.expo.version === null) {
|
|
37340
37423
|
const spec = getDependencySpec(packageJson, "expo");
|
|
37341
37424
|
if (spec !== null) facts.expo = {
|
|
@@ -37357,13 +37440,74 @@ const evaluateManifestFacts = (facts, packageJson, directory) => {
|
|
|
37357
37440
|
sourceDirectory: directory
|
|
37358
37441
|
};
|
|
37359
37442
|
}
|
|
37443
|
+
const mobxSpec = getDependencySpec(packageJson, "mobx");
|
|
37444
|
+
if (mobxSpec !== null) {
|
|
37445
|
+
const nextMobxFact = {
|
|
37446
|
+
version: resolveCatalogBackedDependencyVersion({
|
|
37447
|
+
rootDirectory: directory,
|
|
37448
|
+
rootPackageJson: packageJson,
|
|
37449
|
+
packageName: "mobx",
|
|
37450
|
+
version: mobxSpec
|
|
37451
|
+
}),
|
|
37452
|
+
sourceDirectory: directory
|
|
37453
|
+
};
|
|
37454
|
+
if (shouldReplaceSupportedDependencyFact(facts.mobx, nextMobxFact, 6)) facts.mobx = nextMobxFact;
|
|
37455
|
+
}
|
|
37456
|
+
facts.hasMobxReact = facts.hasMobxReact || getDependencySpec(packageJson, MOBX_REACT_PACKAGE_NAME) !== null;
|
|
37457
|
+
facts.hasMobxReactLite = facts.hasMobxReactLite || getDependencySpec(packageJson, MOBX_REACT_LITE_PACKAGE_NAME) !== null;
|
|
37458
|
+
facts.hasMobxStateTree = facts.hasMobxStateTree || getDependencySpec(packageJson, MOBX_STATE_TREE_PACKAGE_NAME) !== null;
|
|
37459
|
+
facts.hasMobxReactObserver = facts.hasMobxReactObserver || getDependencySpec(packageJson, MOBX_REACT_OBSERVER_PACKAGE_NAME) !== null;
|
|
37460
|
+
const zustandSpec = getDependencySpec(packageJson, "zustand");
|
|
37461
|
+
if (zustandSpec !== null) {
|
|
37462
|
+
const nextZustandFact = {
|
|
37463
|
+
version: resolveCatalogBackedDependencyVersion({
|
|
37464
|
+
rootDirectory: directory,
|
|
37465
|
+
rootPackageJson: packageJson,
|
|
37466
|
+
packageName: "zustand",
|
|
37467
|
+
version: zustandSpec
|
|
37468
|
+
}),
|
|
37469
|
+
sourceDirectory: directory
|
|
37470
|
+
};
|
|
37471
|
+
if (shouldReplaceSupportedDependencyFact(facts.zustand, nextZustandFact, 5)) facts.zustand = nextZustandFact;
|
|
37472
|
+
}
|
|
37360
37473
|
if (facts.reanimatedVersion === null) {
|
|
37361
37474
|
const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
|
|
37362
37475
|
if (spec !== null) facts.reanimatedVersion = spec;
|
|
37363
37476
|
}
|
|
37477
|
+
if (facts.valtioVersion === null) {
|
|
37478
|
+
const spec = getDependencySpec(packageJson, "valtio");
|
|
37479
|
+
if (spec !== null) facts.valtioVersion = spec;
|
|
37480
|
+
}
|
|
37481
|
+
facts.tanstackQueryVersion ??= getTanStackQueryVersion(packageJson);
|
|
37482
|
+
const styledComponentsVersion = resolveCatalogBackedDependencyVersion({
|
|
37483
|
+
rootDirectory: directory,
|
|
37484
|
+
rootPackageJson: packageJson,
|
|
37485
|
+
packageName: "styled-components",
|
|
37486
|
+
version: getStyledComponentsVersion(packageJson)
|
|
37487
|
+
});
|
|
37488
|
+
if (styledComponentsVersion && shouldReplaceStyledComponentsVersion(facts.styledComponentsVersion, styledComponentsVersion)) facts.styledComponentsVersion = styledComponentsVersion;
|
|
37489
|
+
facts.hasI18nLibrary = facts.hasI18nLibrary || hasI18nDependency(packageJson);
|
|
37364
37490
|
facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
|
|
37365
37491
|
facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
|
|
37366
37492
|
facts.hasSsrDependency = facts.hasSsrDependency || isPackageJsonSsrAware(packageJson);
|
|
37493
|
+
const remotionSpec = getDependencySpec(packageJson, "remotion");
|
|
37494
|
+
if (remotionSpec !== null) {
|
|
37495
|
+
facts.hasRemotionDependency = true;
|
|
37496
|
+
const resolvedRemotionVersion = resolveCatalogBackedDependencyVersion({
|
|
37497
|
+
rootDirectory,
|
|
37498
|
+
rootPackageJson,
|
|
37499
|
+
packageName: "remotion",
|
|
37500
|
+
version: remotionSpec
|
|
37501
|
+
});
|
|
37502
|
+
const remotionMajorVersion = resolvedRemotionVersion === null ? null : getLowestDependencyMajor(resolvedRemotionVersion);
|
|
37503
|
+
if (remotionMajorVersion === null) {
|
|
37504
|
+
facts.hasUnknownRemotionVersion = true;
|
|
37505
|
+
facts.remotionVersion = null;
|
|
37506
|
+
} else if (!facts.hasUnknownRemotionVersion) {
|
|
37507
|
+
const currentRemotionMajorVersion = facts.remotionVersion === null ? null : getLowestDependencyMajor(facts.remotionVersion);
|
|
37508
|
+
if (currentRemotionMajorVersion === null || remotionMajorVersion < currentRemotionMajorVersion) facts.remotionVersion = resolvedRemotionVersion;
|
|
37509
|
+
}
|
|
37510
|
+
}
|
|
37367
37511
|
};
|
|
37368
37512
|
const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
|
|
37369
37513
|
const facts = {
|
|
@@ -37383,12 +37527,31 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
|
|
|
37383
37527
|
version: null,
|
|
37384
37528
|
sourceDirectory: null
|
|
37385
37529
|
},
|
|
37530
|
+
valtioVersion: null,
|
|
37531
|
+
mobx: {
|
|
37532
|
+
version: null,
|
|
37533
|
+
sourceDirectory: null
|
|
37534
|
+
},
|
|
37535
|
+
hasMobxReact: false,
|
|
37536
|
+
hasMobxReactLite: false,
|
|
37537
|
+
hasMobxStateTree: false,
|
|
37538
|
+
hasMobxReactObserver: false,
|
|
37539
|
+
zustand: {
|
|
37540
|
+
version: null,
|
|
37541
|
+
sourceDirectory: null
|
|
37542
|
+
},
|
|
37543
|
+
tanstackQueryVersion: null,
|
|
37544
|
+
styledComponentsVersion: null,
|
|
37545
|
+
hasI18nLibrary: false,
|
|
37386
37546
|
hasReactNativeAwarePackage: false,
|
|
37387
37547
|
hasReanimatedAwarePackage: false,
|
|
37388
37548
|
hasSsrDependency: false,
|
|
37549
|
+
hasRemotionDependency: false,
|
|
37550
|
+
hasUnknownRemotionVersion: false,
|
|
37551
|
+
remotionVersion: null,
|
|
37389
37552
|
reanimatedVersion: null
|
|
37390
37553
|
};
|
|
37391
|
-
evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
|
|
37554
|
+
evaluateManifestFacts(facts, rootPackageJson, rootDirectory, rootDirectory, rootPackageJson);
|
|
37392
37555
|
let isReactGroupSettled = !collectReactGroup;
|
|
37393
37556
|
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
37394
37557
|
for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
|
|
@@ -37397,7 +37560,7 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
|
|
|
37397
37560
|
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
37398
37561
|
visitedDirectories.add(workspaceDirectory);
|
|
37399
37562
|
const workspacePackageJson = readPackageJson$1(Path.join(workspaceDirectory, "package.json"));
|
|
37400
|
-
evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
|
|
37563
|
+
evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory, rootDirectory, rootPackageJson);
|
|
37401
37564
|
const info = extractDependencyInfo(workspacePackageJson);
|
|
37402
37565
|
if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
|
|
37403
37566
|
if (isReactGroupSettled) continue;
|
|
@@ -37618,11 +37781,27 @@ const discoverProjectWithoutPackageJson = (directory) => {
|
|
|
37618
37781
|
tailwindVersion: null,
|
|
37619
37782
|
zodVersion: null,
|
|
37620
37783
|
zodMajorVersion: null,
|
|
37784
|
+
mobxVersion: null,
|
|
37785
|
+
mobxMajorVersion: null,
|
|
37786
|
+
hasMobxReact: false,
|
|
37787
|
+
hasMobxReactLite: false,
|
|
37788
|
+
hasMobxStateTree: false,
|
|
37789
|
+
hasMobxReactObserver: false,
|
|
37790
|
+
zustandVersion: null,
|
|
37791
|
+
zustandMajorVersion: null,
|
|
37621
37792
|
framework: "unknown",
|
|
37622
37793
|
hasTypeScript: hasOwnTsConfig,
|
|
37623
37794
|
hasReactCompiler: false,
|
|
37624
37795
|
hasReactCompilerLintPlugin: false,
|
|
37625
37796
|
hasTanStackQuery: false,
|
|
37797
|
+
valtioVersion: null,
|
|
37798
|
+
valtioMajorVersion: null,
|
|
37799
|
+
hasRemotion: false,
|
|
37800
|
+
remotionVersion: null,
|
|
37801
|
+
remotionMajorVersion: null,
|
|
37802
|
+
hasI18nLibrary: false,
|
|
37803
|
+
tanstackQueryVersion: null,
|
|
37804
|
+
styledComponentsVersion: null,
|
|
37626
37805
|
hasSsrDependency: false,
|
|
37627
37806
|
preactVersion: null,
|
|
37628
37807
|
preactMajorVersion: null,
|
|
@@ -37721,6 +37900,12 @@ const discoverProject = (directory) => {
|
|
|
37721
37900
|
packageName: SHOPIFY_FLASH_LIST_PACKAGE_NAME,
|
|
37722
37901
|
version: workspaceFacts.shopifyFlashList.version
|
|
37723
37902
|
}) : null;
|
|
37903
|
+
const zustandVersion = resolveCatalogBackedDependencyVersion({
|
|
37904
|
+
rootDirectory: directory,
|
|
37905
|
+
rootPackageJson: packageJson,
|
|
37906
|
+
packageName: "zustand",
|
|
37907
|
+
version: workspaceFacts.zustand.version
|
|
37908
|
+
});
|
|
37724
37909
|
const hasReanimated = hasReactNativeWorkspace && workspaceFacts.hasReanimatedAwarePackage;
|
|
37725
37910
|
const reanimatedVersion = hasReanimated ? workspaceFacts.reanimatedVersion : null;
|
|
37726
37911
|
const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
|
|
@@ -37729,7 +37914,21 @@ const discoverProject = (directory) => {
|
|
|
37729
37914
|
packageName: "next",
|
|
37730
37915
|
version: workspaceFacts.next.version
|
|
37731
37916
|
}) : null;
|
|
37917
|
+
const valtioVersion = resolveCatalogBackedDependencyVersion({
|
|
37918
|
+
rootDirectory: directory,
|
|
37919
|
+
rootPackageJson: packageJson,
|
|
37920
|
+
packageName: "valtio",
|
|
37921
|
+
version: workspaceFacts.valtioVersion
|
|
37922
|
+
});
|
|
37923
|
+
const mobxVersion = resolveCatalogBackedDependencyVersion({
|
|
37924
|
+
rootDirectory: directory,
|
|
37925
|
+
rootPackageJson: packageJson,
|
|
37926
|
+
packageName: "mobx",
|
|
37927
|
+
version: workspaceFacts.mobx.version
|
|
37928
|
+
});
|
|
37732
37929
|
const preactVersion = getPreactVersion(packageJson);
|
|
37930
|
+
const remotionVersion = workspaceFacts.remotionVersion;
|
|
37931
|
+
const tanstackQueryVersion = getTanStackQueryVersion(packageJson) ?? workspaceFacts.tanstackQueryVersion;
|
|
37733
37932
|
const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
|
|
37734
37933
|
const projectInfo = {
|
|
37735
37934
|
rootDirectory: directory,
|
|
@@ -37739,11 +37938,27 @@ const discoverProject = (directory) => {
|
|
|
37739
37938
|
tailwindVersion,
|
|
37740
37939
|
zodVersion,
|
|
37741
37940
|
zodMajorVersion: zodVersion === null ? null : getLowestDependencyMajor(zodVersion),
|
|
37941
|
+
mobxVersion,
|
|
37942
|
+
mobxMajorVersion: mobxVersion === null ? null : getLowestDependencyMajor(mobxVersion),
|
|
37943
|
+
hasMobxReact: workspaceFacts.hasMobxReact,
|
|
37944
|
+
hasMobxReactLite: workspaceFacts.hasMobxReactLite,
|
|
37945
|
+
hasMobxStateTree: workspaceFacts.hasMobxStateTree,
|
|
37946
|
+
hasMobxReactObserver: workspaceFacts.hasMobxReactObserver,
|
|
37947
|
+
zustandVersion,
|
|
37948
|
+
zustandMajorVersion: zustandVersion === null ? null : getLowestDependencyMajor(zustandVersion),
|
|
37742
37949
|
framework,
|
|
37743
37950
|
hasTypeScript,
|
|
37744
37951
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
37745
37952
|
hasReactCompilerLintPlugin: detectReactCompilerLintPlugin(directory, packageJson),
|
|
37746
|
-
hasTanStackQuery:
|
|
37953
|
+
hasTanStackQuery: tanstackQueryVersion !== null,
|
|
37954
|
+
hasI18nLibrary: workspaceFacts.hasI18nLibrary,
|
|
37955
|
+
tanstackQueryVersion,
|
|
37956
|
+
styledComponentsVersion: workspaceFacts.styledComponentsVersion,
|
|
37957
|
+
valtioVersion,
|
|
37958
|
+
valtioMajorVersion: valtioVersion === null ? null : getLowestDependencyMajor(valtioVersion),
|
|
37959
|
+
hasRemotion: workspaceFacts.hasRemotionDependency,
|
|
37960
|
+
remotionVersion,
|
|
37961
|
+
remotionMajorVersion: remotionVersion === null ? null : getLowestDependencyMajor(remotionVersion),
|
|
37747
37962
|
hasSsrDependency: workspaceFacts.hasSsrDependency,
|
|
37748
37963
|
preactVersion,
|
|
37749
37964
|
preactMajorVersion: parseReactMajor(preactVersion),
|
|
@@ -37977,7 +38192,6 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
|
37977
38192
|
"react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
|
|
37978
38193
|
"react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
|
|
37979
38194
|
"react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
|
|
37980
|
-
"react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
|
|
37981
38195
|
"react/jsx-no-undef": "react-doctor/jsx-no-undef",
|
|
37982
38196
|
"react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
|
|
37983
38197
|
"react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
|
|
@@ -38359,6 +38573,9 @@ const SCRIPT_EXTENSION_FRAGMENT = "[cm]?[jt]sx?";
|
|
|
38359
38573
|
const STORY_FILE_SUFFIX_PATTERN = new RegExp(`\\.(?:stories|story)\\.(?:${SCRIPT_EXTENSION_FRAGMENT})$`);
|
|
38360
38574
|
const TEST_FILE_SUFFIX_PATTERN = new RegExp(`\\.(?:test|spec|fixture|fixtures)\\.(?:${SCRIPT_EXTENSION_FRAGMENT})$`);
|
|
38361
38575
|
const TEST_FILE_DIRECTORY_PATTERN = /(?:^|\/)(?:__tests__|__test__|tests|test|__mocks__|cypress|e2e|playwright)\//;
|
|
38576
|
+
const AMBIGUOUS_TEST_ROUTE_DIRECTORY_PATTERN = /(?:^|\/)(?:tests|test)\//;
|
|
38577
|
+
const NEXT_APP_ROUTE_FILE_SUFFIX_PATTERN = /\/(?:default|error|forbidden|global-error|layout|loading|not-found|page|route|template|unauthorized)\.[cm]?[jt]sx?$/;
|
|
38578
|
+
const SCRIPT_FILE_SUFFIX_PATTERN = /\.[cm]?[jt]sx?$/;
|
|
38362
38579
|
const FIXTURE_PROJECT_PATTERN = /\/(?:fixtures|__fixtures__)\//;
|
|
38363
38580
|
const SOURCE_ROOT_PATTERN = /\/(?:src|app|lib|components|pages|features|modules|packages|apps|frontend|client)\//g;
|
|
38364
38581
|
const stripAboveSourceRoot = (relativePath) => {
|
|
@@ -38369,6 +38586,14 @@ const stripAboveSourceRoot = (relativePath) => {
|
|
|
38369
38586
|
if (lastIdx >= 0) return relativePath.slice(lastIdx);
|
|
38370
38587
|
return relativePath.slice(fixtureMatch.index + fixtureMatch[0].length - 1);
|
|
38371
38588
|
};
|
|
38589
|
+
const isFrameworkRouteBeforeAmbiguousTestDirectory = (relativePath) => {
|
|
38590
|
+
const testDirectoryMatch = AMBIGUOUS_TEST_ROUTE_DIRECTORY_PATTERN.exec(relativePath);
|
|
38591
|
+
if (testDirectoryMatch === null) return false;
|
|
38592
|
+
let nearestRouteRoot;
|
|
38593
|
+
for (const pathSegment of relativePath.slice(0, testDirectoryMatch.index).split("/")) if (pathSegment === "app" || pathSegment === "pages") nearestRouteRoot = pathSegment;
|
|
38594
|
+
if (nearestRouteRoot === void 0) return false;
|
|
38595
|
+
return nearestRouteRoot === "pages" ? SCRIPT_FILE_SUFFIX_PATTERN.test(relativePath) : NEXT_APP_ROUTE_FILE_SUFFIX_PATTERN.test(relativePath);
|
|
38596
|
+
};
|
|
38372
38597
|
/**
|
|
38373
38598
|
* Classifies where a file sits relative to shipped code. A finding in a
|
|
38374
38599
|
* `.stories.tsx` or `.spec.ts` file never runs in front of users, so
|
|
@@ -38385,6 +38610,7 @@ const classifyFileContext = (relativePath) => {
|
|
|
38385
38610
|
if (STORY_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return "story";
|
|
38386
38611
|
if (TEST_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return "test";
|
|
38387
38612
|
const scoped = stripAboveSourceRoot(forwardSlashed);
|
|
38613
|
+
if (isFrameworkRouteBeforeAmbiguousTestDirectory(scoped)) return "production";
|
|
38388
38614
|
return TEST_FILE_DIRECTORY_PATTERN.test(scoped) ? "test" : "production";
|
|
38389
38615
|
};
|
|
38390
38616
|
/**
|
|
@@ -39244,6 +39470,7 @@ const VALID_RULE_SEVERITIES = [
|
|
|
39244
39470
|
"warn",
|
|
39245
39471
|
"off"
|
|
39246
39472
|
];
|
|
39473
|
+
const VALID_INCLUDED_FILE_CONTEXTS = ["test", "story"];
|
|
39247
39474
|
const KNOWN_CATEGORY_LABEL = DIAGNOSTIC_CATEGORY_BUCKETS.join(", ");
|
|
39248
39475
|
const isDiagnosticCategoryBucket = (value) => DIAGNOSTIC_CATEGORY_BUCKETS.includes(value);
|
|
39249
39476
|
const filterKnownCategories = (fieldName, categories) => categories.filter((category) => {
|
|
@@ -39278,6 +39505,7 @@ const SURFACE_CONTROL_FIELD_NAMES = [
|
|
|
39278
39505
|
"includeRules",
|
|
39279
39506
|
"excludeRules"
|
|
39280
39507
|
];
|
|
39508
|
+
const isIncludedFileContext = (value) => VALID_INCLUDED_FILE_CONTEXTS.some((fileContext) => fileContext === value);
|
|
39281
39509
|
const SEVERITY_FIELD_NAMES = ["rules", "categories"];
|
|
39282
39510
|
const isPlainObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39283
39511
|
const formatType = (value) => typeof value === "string" ? `"${value}"` : typeof value;
|
|
@@ -39312,6 +39540,15 @@ const validateSurfaceControls = (surface, rawControls) => {
|
|
|
39312
39540
|
return;
|
|
39313
39541
|
}
|
|
39314
39542
|
const validatedSurfaceControls = {};
|
|
39543
|
+
if (rawControls.includeFileContexts !== void 0) {
|
|
39544
|
+
const qualifiedName = `surfaces.${surface}.includeFileContexts`;
|
|
39545
|
+
const result = validateStringArrayField(qualifiedName, rawControls.includeFileContexts);
|
|
39546
|
+
if (result !== void 0) validatedSurfaceControls.includeFileContexts = result.filter((fileContext) => {
|
|
39547
|
+
if (isIncludedFileContext(fileContext)) return true;
|
|
39548
|
+
warnConfigIssue(`config field "${qualifiedName}" lists "${fileContext}", which is not a non-production file context (expected one of: ${VALID_INCLUDED_FILE_CONTEXTS.join(", ")}); ignoring the entry.`);
|
|
39549
|
+
return false;
|
|
39550
|
+
});
|
|
39551
|
+
}
|
|
39315
39552
|
for (const fieldName of SURFACE_CONTROL_FIELD_NAMES) {
|
|
39316
39553
|
if (rawControls[fieldName] === void 0) continue;
|
|
39317
39554
|
const qualifiedName = `surfaces.${surface}.${fieldName}`;
|
|
@@ -40276,9 +40513,25 @@ const BABEL_CONFIG_FILE_NAMES = [
|
|
|
40276
40513
|
".babelrc.json"
|
|
40277
40514
|
];
|
|
40278
40515
|
const LEGACY_PRESET_SPEC = "module:metro-react-native-babel-preset";
|
|
40516
|
+
const LEGACY_PRESET_PACKAGE = "metro-react-native-babel-preset";
|
|
40517
|
+
const REACT_NATIVE_PACKAGE = "react-native";
|
|
40518
|
+
const PRESET_RENAME_MINIMUM_REACT_NATIVE_VERSION = "0.73.0";
|
|
40519
|
+
const LEGACY_PRESET_REFERENCE = new RegExp(`['"]${LEGACY_PRESET_SPEC}['"]`);
|
|
40279
40520
|
const MODERN_PRESET_REFERENCE = new RegExp(`['"]module:@react-native/babel-preset['"]`);
|
|
40280
40521
|
const ENABLE_BABEL_RUNTIME_VERSION = /enableBabelRuntime["']?\s*:\s*['"]/;
|
|
40522
|
+
const isReactNativeVersionAtLeastPresetRename = (rootDirectory, packageJson) => {
|
|
40523
|
+
const installedReactNativeManifestPath = Path.join(rootDirectory, "node_modules", REACT_NATIVE_PACKAGE, "package.json");
|
|
40524
|
+
const reactNativeVersionSpec = (isFile(installedReactNativeManifestPath) ? readPackageJson$1(installedReactNativeManifestPath).version : void 0) ?? getDependencySpec(packageJson, REACT_NATIVE_PACKAGE);
|
|
40525
|
+
if (reactNativeVersionSpec === null || reactNativeVersionSpec === void 0) return false;
|
|
40526
|
+
const normalizedVersionRange = import_semver.validRange(reactNativeVersionSpec);
|
|
40527
|
+
if (normalizedVersionRange === null) return false;
|
|
40528
|
+
const minimumVersion = import_semver.minVersion(normalizedVersionRange);
|
|
40529
|
+
return minimumVersion !== null && import_semver.gte(minimumVersion, PRESET_RENAME_MINIMUM_REACT_NATIVE_VERSION);
|
|
40530
|
+
};
|
|
40531
|
+
const canResolveLegacyPreset = (rootDirectory, packageJson) => getDependencySpec(packageJson, LEGACY_PRESET_PACKAGE) !== null || isFile(Path.join(rootDirectory, "node_modules", LEGACY_PRESET_PACKAGE, "package.json"));
|
|
40281
40532
|
const checkReactNativeMetroBabelPreset = (rootDirectory) => {
|
|
40533
|
+
const packageJson = readPackageJson$1(Path.join(rootDirectory, "package.json"));
|
|
40534
|
+
const shouldReportLegacyPreset = isReactNativeVersionAtLeastPresetRename(rootDirectory, packageJson) && !canResolveLegacyPreset(rootDirectory, packageJson);
|
|
40282
40535
|
for (const fileName of BABEL_CONFIG_FILE_NAMES) {
|
|
40283
40536
|
const filePath = Path.join(rootDirectory, fileName);
|
|
40284
40537
|
if (!isFile(filePath)) continue;
|
|
@@ -40288,7 +40541,7 @@ const checkReactNativeMetroBabelPreset = (rootDirectory) => {
|
|
|
40288
40541
|
} catch {
|
|
40289
40542
|
continue;
|
|
40290
40543
|
}
|
|
40291
|
-
if (
|
|
40544
|
+
if (shouldReportLegacyPreset && LEGACY_PRESET_REFERENCE.test(contents)) return [buildReactNativeDiagnostic({
|
|
40292
40545
|
filePath: fileName,
|
|
40293
40546
|
rule: "rn-no-metro-babel-preset",
|
|
40294
40547
|
severity: "error",
|
|
@@ -40311,12 +40564,6 @@ const checkReactNativeProject = (rootDirectory, project) => {
|
|
|
40311
40564
|
return [...checkReactNativeMetroBabelPreset(rootDirectory), ...checkReactNativeLibraryDependencies(rootDirectory)];
|
|
40312
40565
|
};
|
|
40313
40566
|
const RULE_KEY = "no-vulnerable-react-server-components";
|
|
40314
|
-
const DEPENDENCY_SECTIONS = [
|
|
40315
|
-
"dependencies",
|
|
40316
|
-
"devDependencies",
|
|
40317
|
-
"peerDependencies",
|
|
40318
|
-
"optionalDependencies"
|
|
40319
|
-
];
|
|
40320
40567
|
const REACT_RSC_ADVISORIES_BY_MINOR = {
|
|
40321
40568
|
"19.0": {
|
|
40322
40569
|
rceFixedVersion: "19.0.1",
|
|
@@ -40363,20 +40610,13 @@ const enumerateWorkspaceDirectories = (workspaceRoot) => {
|
|
|
40363
40610
|
for (const pattern of patterns) for (const directory of resolveWorkspaceDirectories(workspaceRoot, pattern)) directories.add(directory);
|
|
40364
40611
|
return [...directories];
|
|
40365
40612
|
};
|
|
40366
|
-
const readDeclaredSpec = (packageJson, packageName) => {
|
|
40367
|
-
for (const section of DEPENDENCY_SECTIONS) {
|
|
40368
|
-
const spec = packageJson[section]?.[packageName];
|
|
40369
|
-
if (typeof spec === "string") return spec;
|
|
40370
|
-
}
|
|
40371
|
-
return null;
|
|
40372
|
-
};
|
|
40373
40613
|
const resolveVersionInDirectory = (directory, packageName, declaredSpecOverride) => {
|
|
40374
40614
|
const manifestPath = Path.join(directory, "node_modules", packageName, "package.json");
|
|
40375
40615
|
if (isFile(manifestPath)) {
|
|
40376
40616
|
const installedVersion = import_semver.valid(readPackageJson$1(manifestPath).version ?? null);
|
|
40377
40617
|
if (installedVersion !== null) return installedVersion;
|
|
40378
40618
|
}
|
|
40379
|
-
const candidateSpecs = [
|
|
40619
|
+
const candidateSpecs = [getDependencySpec(readPackageJson$1(Path.join(directory, "package.json")), packageName), declaredSpecOverride];
|
|
40380
40620
|
for (const spec of candidateSpecs) {
|
|
40381
40621
|
const pinnedVersion = spec === null ? null : import_semver.valid(spec);
|
|
40382
40622
|
if (pinnedVersion !== null) return pinnedVersion;
|
|
@@ -41044,21 +41284,50 @@ const buildCapabilities = (project) => {
|
|
|
41044
41284
|
if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
|
|
41045
41285
|
if (project.isStaticExport) capabilities.add("nextjs:static-export");
|
|
41046
41286
|
if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
|
|
41287
|
+
if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 16) capabilities.add("nextjs:16");
|
|
41047
41288
|
addMajorLadder(capabilities, "react", project.reactMajorVersion, 17, 30);
|
|
41048
41289
|
if (project.reactMajorVersion !== null && project.reactMajorVersion >= 19 && isMajorMinorAtLeast(parseReactMajorMinor(project.reactVersion), {
|
|
41049
41290
|
major: 19,
|
|
41050
41291
|
minor: 2
|
|
41051
41292
|
})) capabilities.add("react:19.2");
|
|
41052
|
-
if (project.tailwindVersion !== null)
|
|
41053
|
-
|
|
41054
|
-
|
|
41055
|
-
|
|
41056
|
-
|
|
41293
|
+
if (project.tailwindVersion !== null) {
|
|
41294
|
+
capabilities.add("tailwind");
|
|
41295
|
+
const tailwindVersion = parseTailwindMajorMinor(project.tailwindVersion);
|
|
41296
|
+
if (isMajorMinorAtLeast(tailwindVersion, {
|
|
41297
|
+
major: 3,
|
|
41298
|
+
minor: 4
|
|
41299
|
+
})) capabilities.add("tailwind:3.4");
|
|
41300
|
+
if (tailwindVersion !== null && isMajorMinorAtLeast(tailwindVersion, {
|
|
41301
|
+
major: 4,
|
|
41302
|
+
minor: 0
|
|
41303
|
+
})) capabilities.add("tailwind:4");
|
|
41304
|
+
}
|
|
41057
41305
|
if (project.zodVersion !== null) capabilities.add("zod");
|
|
41058
41306
|
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
41307
|
+
if (project.mobxVersion !== void 0 && project.mobxVersion !== null || project.hasMobxReact === true || project.hasMobxReactLite === true || project.hasMobxStateTree === true || project.hasMobxReactObserver === true) capabilities.add("mobx");
|
|
41308
|
+
if (project.hasMobxReact === true) capabilities.add("mobx-react");
|
|
41309
|
+
if (project.hasMobxReactLite === true) capabilities.add("mobx-react-lite");
|
|
41310
|
+
if (project.hasMobxReact === true || project.hasMobxReactLite === true) capabilities.add("mobx-react-binding");
|
|
41311
|
+
if (project.hasMobxStateTree === true) capabilities.add("mobx-state-tree");
|
|
41312
|
+
if (project.hasMobxReactObserver === true) capabilities.add("mobx-react-observer");
|
|
41313
|
+
if (project.mobxMajorVersion !== void 0 && project.mobxMajorVersion !== null && project.mobxMajorVersion >= 4 && project.mobxMajorVersion <= 6) addMajorLadder(capabilities, "mobx", project.mobxMajorVersion, 4, 6);
|
|
41314
|
+
if (project.zustandVersion !== void 0 && project.zustandVersion !== null) capabilities.add("zustand");
|
|
41315
|
+
if (project.zustandMajorVersion !== void 0 && project.zustandMajorVersion !== null && project.zustandMajorVersion >= 1 && project.zustandMajorVersion <= 5) addMajorLadder(capabilities, "zustand", project.zustandMajorVersion, 1, 5);
|
|
41059
41316
|
if (project.isPreES2023Target) capabilities.add("pre-es2023");
|
|
41060
41317
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
41061
|
-
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
41318
|
+
if (Boolean(project.hasTanStackQuery) || Boolean(project.tanstackQueryVersion)) capabilities.add("tanstack-query");
|
|
41319
|
+
if (project.styledComponentsVersion) {
|
|
41320
|
+
capabilities.add("styled-components");
|
|
41321
|
+
const styledComponentsMajor = getLowestDependencyMajor(project.styledComponentsVersion);
|
|
41322
|
+
if (styledComponentsMajor !== null && styledComponentsMajor >= 6) capabilities.add("styled-components:6");
|
|
41323
|
+
}
|
|
41324
|
+
if (project.hasI18nLibrary) capabilities.add("i18n");
|
|
41325
|
+
if (project.valtioVersion !== null) capabilities.add("valtio");
|
|
41326
|
+
addMajorLadder(capabilities, "valtio", project.valtioMajorVersion, 1, 10);
|
|
41327
|
+
if (project.hasRemotion) {
|
|
41328
|
+
capabilities.add("remotion");
|
|
41329
|
+
addMajorLadder(capabilities, "remotion", project.remotionMajorVersion ?? null, 4, 30);
|
|
41330
|
+
}
|
|
41062
41331
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
41063
41332
|
if (project.preactVersion !== null) capabilities.add("preact");
|
|
41064
41333
|
addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
|
|
@@ -41073,7 +41342,7 @@ const getCapabilities = (project) => {
|
|
|
41073
41342
|
capabilitiesByProject.set(project, capabilities);
|
|
41074
41343
|
return capabilities;
|
|
41075
41344
|
};
|
|
41076
|
-
const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhen) => {
|
|
41345
|
+
const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhen, includedTags) => {
|
|
41077
41346
|
if (requires) {
|
|
41078
41347
|
for (const capability of requires) if (!capabilities.has(capability)) return false;
|
|
41079
41348
|
}
|
|
@@ -41084,20 +41353,29 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhe
|
|
|
41084
41353
|
if (tags) {
|
|
41085
41354
|
for (const tag of tags) if (ignoredTags.has(tag)) return false;
|
|
41086
41355
|
}
|
|
41356
|
+
if (includedTags && includedTags.size > 0) {
|
|
41357
|
+
if (!tags?.some((tag) => includedTags.has(tag))) return false;
|
|
41358
|
+
}
|
|
41087
41359
|
return true;
|
|
41088
41360
|
};
|
|
41361
|
+
const shouldEnableRuleByDefaultStatus = (options) => options.defaultEnabled !== false || options.hasExplicitOverride === true || options.includeTagDefaults && options.hasIncludedTags;
|
|
41089
41362
|
const yieldToEventLoop = () => new Promise((resolve) => {
|
|
41090
41363
|
setImmediate(resolve);
|
|
41091
41364
|
});
|
|
41092
41365
|
const createSecurityScanSession = (rootDirectory, options) => {
|
|
41093
41366
|
const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
41094
41367
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
41368
|
+
const includedTags = options.includedTags ?? /* @__PURE__ */ new Set();
|
|
41095
41369
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
41096
41370
|
const rule = entry.rule;
|
|
41097
41371
|
const scan = rule.scan;
|
|
41098
41372
|
if (typeof scan !== "function") return [];
|
|
41099
|
-
if (
|
|
41100
|
-
|
|
41373
|
+
if (!shouldEnableRuleByDefaultStatus({
|
|
41374
|
+
defaultEnabled: rule.defaultEnabled,
|
|
41375
|
+
includeTagDefaults: options.includeTagDefaults === true,
|
|
41376
|
+
hasIncludedTags: includedTags.size > 0
|
|
41377
|
+
})) return [];
|
|
41378
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen, includedTags)) return [];
|
|
41101
41379
|
return [{
|
|
41102
41380
|
entry,
|
|
41103
41381
|
scan,
|
|
@@ -42047,6 +42325,7 @@ const buildResolvedControls = (surface, userControls) => {
|
|
|
42047
42325
|
for (const tag of includeTags) excludeTags.delete(tag);
|
|
42048
42326
|
for (const tag of toStringSet(userControls?.excludeTags)) excludeTags.add(tag);
|
|
42049
42327
|
return {
|
|
42328
|
+
includeFileContexts: toStringSet(userControls?.includeFileContexts),
|
|
42050
42329
|
includeTags,
|
|
42051
42330
|
excludeTags,
|
|
42052
42331
|
includeCategories: toStringSet(userControls?.includeCategories),
|
|
@@ -42062,7 +42341,7 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
|
|
|
42062
42341
|
if (resolved.includeRuleKeys.has(ruleKey)) return true;
|
|
42063
42342
|
if (resolved.includeCategories.has(category)) return true;
|
|
42064
42343
|
if (intersects(tags, resolved.includeTags)) return true;
|
|
42065
|
-
if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure")) return false;
|
|
42344
|
+
if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure") && !resolved.includeFileContexts.has(diagnostic.fileContext)) return false;
|
|
42066
42345
|
if (resolved.excludeRuleKeys.has(ruleKey)) return false;
|
|
42067
42346
|
if (resolved.excludeCategories.has(category)) return false;
|
|
42068
42347
|
if (intersects(tags, resolved.excludeTags)) return false;
|
|
@@ -42310,6 +42589,35 @@ const parseGithubViewerPermission = (stdout) => {
|
|
|
42310
42589
|
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
42311
42590
|
};
|
|
42312
42591
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
42592
|
+
const parseBaselineDiffPlan = (value) => {
|
|
42593
|
+
const entries = splitNullSeparated(value);
|
|
42594
|
+
const baseFiles = /* @__PURE__ */ new Set();
|
|
42595
|
+
const headFiles = /* @__PURE__ */ new Set();
|
|
42596
|
+
for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 2) {
|
|
42597
|
+
const status = entries[entryIndex];
|
|
42598
|
+
const filePath = entries[entryIndex + 1];
|
|
42599
|
+
if (status === void 0 || filePath === void 0 || status.length !== 1) return null;
|
|
42600
|
+
if (status === "A") {
|
|
42601
|
+
headFiles.add(filePath);
|
|
42602
|
+
continue;
|
|
42603
|
+
}
|
|
42604
|
+
if (status === "D") {
|
|
42605
|
+
baseFiles.add(filePath);
|
|
42606
|
+
continue;
|
|
42607
|
+
}
|
|
42608
|
+
if (status === "M" || status === "T") {
|
|
42609
|
+
baseFiles.add(filePath);
|
|
42610
|
+
headFiles.add(filePath);
|
|
42611
|
+
continue;
|
|
42612
|
+
}
|
|
42613
|
+
return null;
|
|
42614
|
+
}
|
|
42615
|
+
return {
|
|
42616
|
+
baseFiles: [...baseFiles],
|
|
42617
|
+
headFiles: [...headFiles],
|
|
42618
|
+
untrackedFiles: []
|
|
42619
|
+
};
|
|
42620
|
+
};
|
|
42313
42621
|
const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
|
|
42314
42622
|
/**
|
|
42315
42623
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
@@ -42512,6 +42820,45 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42512
42820
|
githubViewerPermission,
|
|
42513
42821
|
branchExists,
|
|
42514
42822
|
mergeBase,
|
|
42823
|
+
baselineDiffPlan: (input) => {
|
|
42824
|
+
if (!isSafeGitRevision(input.ref)) return succeed$2(null);
|
|
42825
|
+
return gen(function* () {
|
|
42826
|
+
const unmerged = yield* runGit(input.directory, [
|
|
42827
|
+
"diff",
|
|
42828
|
+
"--no-ext-diff",
|
|
42829
|
+
"-z",
|
|
42830
|
+
"--name-only",
|
|
42831
|
+
"--diff-filter=U",
|
|
42832
|
+
"--relative"
|
|
42833
|
+
]);
|
|
42834
|
+
if (unmerged.status !== 0 || unmerged.stdout.length > 0) return null;
|
|
42835
|
+
const result = yield* runGit(input.directory, [
|
|
42836
|
+
"diff",
|
|
42837
|
+
"--no-ext-diff",
|
|
42838
|
+
"--no-textconv",
|
|
42839
|
+
"--no-renames",
|
|
42840
|
+
"-z",
|
|
42841
|
+
"--name-status",
|
|
42842
|
+
"--relative",
|
|
42843
|
+
input.ref
|
|
42844
|
+
]);
|
|
42845
|
+
if (result.status !== 0) return null;
|
|
42846
|
+
const plan = parseBaselineDiffPlan(result.stdout);
|
|
42847
|
+
if (plan === null) return null;
|
|
42848
|
+
const untracked = yield* runGit(input.directory, [
|
|
42849
|
+
"ls-files",
|
|
42850
|
+
"--others",
|
|
42851
|
+
"--exclude-standard",
|
|
42852
|
+
"-z"
|
|
42853
|
+
]);
|
|
42854
|
+
if (untracked.status !== 0) return null;
|
|
42855
|
+
return {
|
|
42856
|
+
baseFiles: plan.baseFiles,
|
|
42857
|
+
headFiles: plan.headFiles,
|
|
42858
|
+
untrackedFiles: splitNullSeparated(untracked.stdout)
|
|
42859
|
+
};
|
|
42860
|
+
}).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.baselineDiffPlan"));
|
|
42861
|
+
},
|
|
42515
42862
|
diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
|
|
42516
42863
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
42517
42864
|
if (explicitBaseBranch !== void 0) {
|
|
@@ -42661,6 +43008,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42661
43008
|
githubViewerPermission: () => succeed$2(snapshot.githubViewerPermission ?? null),
|
|
42662
43009
|
branchExists: (_directory, branch) => succeed$2(snapshot.branchExists?.get(branch) ?? false),
|
|
42663
43010
|
mergeBase: ({ ref }) => succeed$2(snapshot.mergeBase?.get(ref) ?? null),
|
|
43011
|
+
baselineDiffPlan: () => succeed$2(snapshot.baselineDiffPlan ?? null),
|
|
42664
43012
|
diffSelection: () => succeed$2(snapshot.diffSelection ?? null),
|
|
42665
43013
|
stagedFilePaths: () => succeed$2(snapshot.stagedFiles ?? []),
|
|
42666
43014
|
showStagedContent: (_directory, relativePath) => succeed$2(snapshot.stagedContent?.get(relativePath) ?? null),
|
|
@@ -43029,8 +43377,9 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
43029
43377
|
}
|
|
43030
43378
|
return enabled;
|
|
43031
43379
|
};
|
|
43032
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
|
|
43033
|
-
const
|
|
43380
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), includedTags = /* @__PURE__ */ new Set(), includeTagDefaults = false, serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
|
|
43381
|
+
const hasIncludedTags = includedTags.size > 0;
|
|
43382
|
+
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" || hasIncludedTags ? null : resolveReactHooksJsPlugin(project.hasReactCompiler || project.hasReactCompilerLintPlugin === true, customRulesOnly);
|
|
43034
43383
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
43035
43384
|
const jsPlugins = [];
|
|
43036
43385
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
@@ -43045,9 +43394,14 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
43045
43394
|
if (rule.scan !== void 0) continue;
|
|
43046
43395
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
43047
43396
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
43048
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) continue;
|
|
43397
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen, includedTags)) continue;
|
|
43049
43398
|
const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
|
|
43050
|
-
if (
|
|
43399
|
+
if (!shouldEnableRuleByDefaultStatus({
|
|
43400
|
+
defaultEnabled: rule.defaultEnabled,
|
|
43401
|
+
includeTagDefaults,
|
|
43402
|
+
hasIncludedTags,
|
|
43403
|
+
hasExplicitOverride: explicitRuleOverride !== void 0
|
|
43404
|
+
})) continue;
|
|
43051
43405
|
const severity = resolveRuleSeverityOverride({
|
|
43052
43406
|
ruleKey: registryEntry.key,
|
|
43053
43407
|
category: rule.category
|
|
@@ -43056,7 +43410,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
43056
43410
|
enabledReactDoctorRules[registryEntry.key] = severity;
|
|
43057
43411
|
}
|
|
43058
43412
|
const userPluginRules = {};
|
|
43059
|
-
if (ruleSelection !== "sidecar") for (const userPlugin of userPlugins) {
|
|
43413
|
+
if (ruleSelection !== "sidecar" && !hasIncludedTags) for (const userPlugin of userPlugins) {
|
|
43060
43414
|
Object.assign(userPluginRules, buildUserPluginRules(userPlugin, severityControls));
|
|
43061
43415
|
jsPlugins.push(userPlugin.entry);
|
|
43062
43416
|
}
|
|
@@ -43273,6 +43627,19 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
43273
43627
|
}
|
|
43274
43628
|
};
|
|
43275
43629
|
};
|
|
43630
|
+
const fingerprintCacheByFilePath = /* @__PURE__ */ new Map();
|
|
43631
|
+
const fingerprintFileContents = (filePath, fingerprintLength) => {
|
|
43632
|
+
const fileStats = NFS.statSync(filePath, { bigint: true });
|
|
43633
|
+
const cachedFingerprint = fingerprintCacheByFilePath.get(filePath);
|
|
43634
|
+
if (cachedFingerprint?.modifiedTimeNanoseconds === fileStats.mtimeNs && cachedFingerprint.sizeBytes === fileStats.size) return cachedFingerprint.fingerprint;
|
|
43635
|
+
const fingerprint = crypto.createHash("sha256").update(NFS.readFileSync(filePath)).digest("hex").slice(0, fingerprintLength);
|
|
43636
|
+
fingerprintCacheByFilePath.set(filePath, {
|
|
43637
|
+
fingerprint,
|
|
43638
|
+
modifiedTimeNanoseconds: fileStats.mtimeNs,
|
|
43639
|
+
sizeBytes: fileStats.size
|
|
43640
|
+
});
|
|
43641
|
+
return fingerprint;
|
|
43642
|
+
};
|
|
43276
43643
|
const bundledRequire$3 = createRequire(import.meta.url);
|
|
43277
43644
|
const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
|
|
43278
43645
|
const resolveChildNodeVersion = (nodeBinaryPath) => {
|
|
@@ -43296,6 +43663,13 @@ const TOOLCHAIN_PACKAGE_SPECIFIERS$1 = [
|
|
|
43296
43663
|
"oxlint-plugin-react-doctor/package.json",
|
|
43297
43664
|
"eslint-plugin-react-hooks/package.json"
|
|
43298
43665
|
];
|
|
43666
|
+
const resolvePluginFingerprint = () => {
|
|
43667
|
+
try {
|
|
43668
|
+
return fingerprintFileContents(bundledRequire$3.resolve("oxlint-plugin-react-doctor"), 16);
|
|
43669
|
+
} catch {
|
|
43670
|
+
return "unresolved";
|
|
43671
|
+
}
|
|
43672
|
+
};
|
|
43299
43673
|
const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
|
|
43300
43674
|
const versions = [`node=${resolveChildNodeVersion(nodeBinaryPath)}`];
|
|
43301
43675
|
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS$1) try {
|
|
@@ -43305,6 +43679,7 @@ const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
|
|
|
43305
43679
|
} catch {
|
|
43306
43680
|
versions.push(`${specifier}=missing`);
|
|
43307
43681
|
}
|
|
43682
|
+
versions.push(`oxlint-plugin-react-doctor#fingerprint=${resolvePluginFingerprint()}`);
|
|
43308
43683
|
return versions;
|
|
43309
43684
|
};
|
|
43310
43685
|
const esmRequire = createRequire(import.meta.url);
|
|
@@ -43998,12 +44373,17 @@ const REACT_ERROR_BOUNDARY_MESSAGE = "This try/catch cannot catch errors thrown
|
|
|
43998
44373
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
43999
44374
|
const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
|
|
44000
44375
|
const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
|
|
44376
|
+
const SET_STATE_IN_EFFECT_ACTION = "Prefer deriving or initializing the value before render. If the effect must read a browser API after mount, treat this as advisory or suppress it with `// react-doctor-disable-next-line react-hooks-js/set-state-in-effect`.";
|
|
44001
44377
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
44002
44378
|
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
44003
44379
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
44004
44380
|
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
44005
44381
|
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
44006
44382
|
};
|
|
44383
|
+
const buildSetStateInEffectMessage = (reasonSummary) => {
|
|
44384
|
+
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
44385
|
+
return `This synchronous effect update causes an extra render${normalizedSummary ? `: ${normalizedSummary}` : ""}. ${SET_STATE_IN_EFFECT_ACTION}`;
|
|
44386
|
+
};
|
|
44007
44387
|
const PLUGIN_CATEGORY_MAP = {
|
|
44008
44388
|
react: "Bugs",
|
|
44009
44389
|
"react-hooks": "Bugs",
|
|
@@ -44061,6 +44441,10 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
44061
44441
|
message: REACT_ERROR_BOUNDARY_MESSAGE,
|
|
44062
44442
|
help: reasonDetail || help
|
|
44063
44443
|
};
|
|
44444
|
+
if (rule === "set-state-in-effect") return {
|
|
44445
|
+
message: buildSetStateInEffectMessage(reasonSummary.trim()),
|
|
44446
|
+
help: reasonDetail || help
|
|
44447
|
+
};
|
|
44064
44448
|
return {
|
|
44065
44449
|
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
44066
44450
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
@@ -44771,7 +45155,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
44771
45155
|
* 6. always restore disable directives + clean up the temp dir
|
|
44772
45156
|
*/
|
|
44773
45157
|
const runOxlint = async (options) => {
|
|
44774
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, onFileCoverage, perFileLintCacheEnabled = false, sidecarLintCacheEnabled = false, onCacheStats, onSidecarStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
45158
|
+
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), includedTags = /* @__PURE__ */ new Set(), includeTagDefaults = false, userConfig, configSourceDirectory = rootDirectory, onPartialFailure, onFileCoverage, perFileLintCacheEnabled = false, sidecarLintCacheEnabled = false, onCacheStats, onSidecarStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
44775
45159
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
44776
45160
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
44777
45161
|
validateRuleRegistration();
|
|
@@ -44785,13 +45169,15 @@ const runOxlint = async (options) => {
|
|
|
44785
45169
|
}
|
|
44786
45170
|
const pluginPath = resolvePluginPath();
|
|
44787
45171
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
44788
|
-
const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
45172
|
+
const userPlugins = includedTags.size > 0 ? [] : resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
44789
45173
|
const buildConfig = (overrides) => createOxlintConfig({
|
|
44790
45174
|
pluginPath,
|
|
44791
45175
|
project,
|
|
44792
45176
|
customRulesOnly,
|
|
44793
45177
|
extendsPaths: overrides.extendsPaths,
|
|
44794
45178
|
ignoredTags,
|
|
45179
|
+
includedTags,
|
|
45180
|
+
includeTagDefaults,
|
|
44795
45181
|
serverAuthFunctionNames,
|
|
44796
45182
|
severityControls,
|
|
44797
45183
|
userPlugins,
|
|
@@ -45180,6 +45566,8 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
45180
45566
|
respectInlineDisables: input.respectInlineDisables,
|
|
45181
45567
|
adoptExistingLintConfig: input.adoptExistingLintConfig,
|
|
45182
45568
|
ignoredTags: input.ignoredTags,
|
|
45569
|
+
includedTags: input.includedTags,
|
|
45570
|
+
includeTagDefaults: input.includeTagDefaults,
|
|
45183
45571
|
userConfig: input.userConfig ?? null,
|
|
45184
45572
|
configSourceDirectory: input.configSourceDirectory,
|
|
45185
45573
|
onPartialFailure: (reason) => {
|
|
@@ -46028,7 +46416,9 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
46028
46416
|
const securityScanFailedRef = yield* make$13(false);
|
|
46029
46417
|
const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
|
|
46030
46418
|
project,
|
|
46031
|
-
ignoredTags: input.ignoredTags
|
|
46419
|
+
ignoredTags: input.ignoredTags,
|
|
46420
|
+
includedTags: input.includedTags,
|
|
46421
|
+
includeTagDefaults: input.includeTagDefaults
|
|
46032
46422
|
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
|
|
46033
46423
|
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
46034
46424
|
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
@@ -46119,6 +46509,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
46119
46509
|
respectInlineDisables: input.respectInlineDisables,
|
|
46120
46510
|
adoptExistingLintConfig: input.adoptExistingLintConfig,
|
|
46121
46511
|
ignoredTags: input.ignoredTags,
|
|
46512
|
+
includedTags: input.includedTags,
|
|
46513
|
+
includeTagDefaults: input.includeTagDefaults,
|
|
46122
46514
|
userConfig: resolvedConfig.config ?? void 0,
|
|
46123
46515
|
configSourceDirectory: resolvedConfig.configSourceDirectory ?? void 0,
|
|
46124
46516
|
onFileProgress: (scannedFileCount, totalFileCount) => {
|
|
@@ -46397,12 +46789,19 @@ const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
|
|
|
46397
46789
|
*/
|
|
46398
46790
|
const materializeSourceTree = (input) => gen(function* () {
|
|
46399
46791
|
const materializedFiles = [];
|
|
46792
|
+
const unmaterializedFiles = [];
|
|
46400
46793
|
const resolvedTempDirectory = path.resolve(input.tempDirectory);
|
|
46401
46794
|
for (const relativePath of input.files) {
|
|
46402
46795
|
const content = yield* input.readContent(relativePath).pipe(orElseSucceed(() => null));
|
|
46403
|
-
if (content === null)
|
|
46796
|
+
if (content === null) {
|
|
46797
|
+
unmaterializedFiles.push(relativePath);
|
|
46798
|
+
continue;
|
|
46799
|
+
}
|
|
46404
46800
|
const candidateTargetPath = path.resolve(resolvedTempDirectory, relativePath);
|
|
46405
|
-
if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory))
|
|
46801
|
+
if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory)) {
|
|
46802
|
+
unmaterializedFiles.push(relativePath);
|
|
46803
|
+
continue;
|
|
46804
|
+
}
|
|
46406
46805
|
yield* sync(() => {
|
|
46407
46806
|
fs.mkdirSync(path.dirname(candidateTargetPath), { recursive: true });
|
|
46408
46807
|
fs.writeFileSync(candidateTargetPath, content);
|
|
@@ -46419,6 +46818,7 @@ const materializeSourceTree = (input) => gen(function* () {
|
|
|
46419
46818
|
return {
|
|
46420
46819
|
tempDirectory: input.tempDirectory,
|
|
46421
46820
|
materializedFiles,
|
|
46821
|
+
unmaterializedFiles,
|
|
46422
46822
|
cleanup: () => {
|
|
46423
46823
|
try {
|
|
46424
46824
|
fs.rmSync(input.tempDirectory, {
|
|
@@ -46688,54 +47088,80 @@ const buildSkippedChecks = (input) => {
|
|
|
46688
47088
|
skippedCheckReasons
|
|
46689
47089
|
};
|
|
46690
47090
|
};
|
|
46691
|
-
const
|
|
47091
|
+
const fingerprintText = (text) => createHash("sha256").update(text).digest("hex");
|
|
47092
|
+
const normalizeEvidence = (evidence) => evidence.replace(/\s+/g, " ").trim();
|
|
47093
|
+
const getDiagnosticMatchKeys = (diagnostic, evidence) => {
|
|
46692
47094
|
const ruleKey = `${diagnostic.plugin}/${diagnostic.rule}`;
|
|
46693
|
-
const
|
|
46694
|
-
|
|
46695
|
-
};
|
|
46696
|
-
|
|
46697
|
-
|
|
46698
|
-
|
|
46699
|
-
|
|
46700
|
-
|
|
46701
|
-
|
|
46702
|
-
|
|
46703
|
-
|
|
46704
|
-
|
|
46705
|
-
|
|
46706
|
-
|
|
46707
|
-
|
|
46708
|
-
|
|
46709
|
-
|
|
46710
|
-
|
|
46711
|
-
|
|
46712
|
-
|
|
46713
|
-
|
|
46714
|
-
|
|
46715
|
-
|
|
46716
|
-
|
|
46717
|
-
|
|
46718
|
-
|
|
46719
|
-
*
|
|
47095
|
+
const messageFingerprint = fingerprintText(`${diagnostic.title ?? ""}\0${diagnostic.message}`);
|
|
47096
|
+
const normalizedEvidence = evidence === null ? "" : normalizeEvidence(evidence);
|
|
47097
|
+
const stableEvidenceKey = normalizedEvidence.length > 0 ? `evidence\0${ruleKey}\0${messageFingerprint}\0${fingerprintText(normalizedEvidence)}` : null;
|
|
47098
|
+
return {
|
|
47099
|
+
stableEvidenceKey,
|
|
47100
|
+
sameFileStableEvidenceKey: stableEvidenceKey === null ? null : `${diagnostic.filePath}\0${stableEvidenceKey}`,
|
|
47101
|
+
sameFileFallbackKey: diagnostic.matchByOccurrence || normalizedEvidence.length === 0 ? `fallback\0${diagnostic.filePath}\0${ruleKey}\0${messageFingerprint}` : null
|
|
47102
|
+
};
|
|
47103
|
+
};
|
|
47104
|
+
const addDiagnosticIndex = (buckets, key, diagnosticIndex) => {
|
|
47105
|
+
if (key === null) return;
|
|
47106
|
+
const diagnosticIndexes = buckets.get(key) ?? [];
|
|
47107
|
+
diagnosticIndexes.push(diagnosticIndex);
|
|
47108
|
+
buckets.set(key, diagnosticIndexes);
|
|
47109
|
+
};
|
|
47110
|
+
const takeMatchingDiagnosticIndex = (buckets, key, matchedDiagnosticIndexes) => {
|
|
47111
|
+
if (key === null) return null;
|
|
47112
|
+
for (const diagnosticIndex of buckets.get(key) ?? []) if (!matchedDiagnosticIndexes.has(diagnosticIndex)) return diagnosticIndex;
|
|
47113
|
+
return null;
|
|
47114
|
+
};
|
|
47115
|
+
const readDiagnosticEvidence = (diagnostic, readEvidence, readLine) => readEvidence?.(diagnostic) ?? readLine(diagnostic.filePath, diagnostic.line);
|
|
47116
|
+
const buildMatchCandidates = (diagnostics, readEvidence, readLine) => diagnostics.map((diagnostic, diagnosticIndex) => ({
|
|
47117
|
+
diagnosticIndex,
|
|
47118
|
+
...getDiagnosticMatchKeys(diagnostic, readDiagnosticEvidence(diagnostic, readEvidence, readLine))
|
|
47119
|
+
}));
|
|
47120
|
+
/**
|
|
47121
|
+
* Diffs a head scan against a base scan using a multiset of construct-level
|
|
47122
|
+
* evidence. Stable identities combine plugin/rule, the diagnostic message,
|
|
47123
|
+
* and normalized diagnosed source, so unchanged findings can move across
|
|
47124
|
+
* files while changed constructs or messages remain new. Cardinality is
|
|
47125
|
+
* retained for identical findings. Diagnostics explicitly marked
|
|
47126
|
+
* `matchByOccurrence` may fall back to same-file plugin/rule/message matching
|
|
47127
|
+
* after same-file strict evidence matching. Cross-file evidence matching runs
|
|
47128
|
+
* last so a copy cannot consume a reformatted local occurrence. Unreadable
|
|
47129
|
+
* evidence uses the same conservative fallback rather than matching across
|
|
47130
|
+
* files without proof.
|
|
46720
47131
|
*/
|
|
46721
47132
|
const computeDiagnosticDelta = (input) => {
|
|
46722
|
-
const
|
|
46723
|
-
|
|
46724
|
-
|
|
46725
|
-
|
|
46726
|
-
|
|
46727
|
-
const
|
|
46728
|
-
|
|
46729
|
-
|
|
46730
|
-
|
|
46731
|
-
|
|
46732
|
-
|
|
46733
|
-
|
|
46734
|
-
|
|
46735
|
-
|
|
47133
|
+
const baseCandidates = buildMatchCandidates(input.baseDiagnostics, input.readBaseEvidence, input.readBaseLine);
|
|
47134
|
+
const headCandidates = buildMatchCandidates(input.headDiagnostics, input.readHeadEvidence, input.readHeadLine);
|
|
47135
|
+
const baseByStableEvidence = /* @__PURE__ */ new Map();
|
|
47136
|
+
const baseBySameFileStableEvidence = /* @__PURE__ */ new Map();
|
|
47137
|
+
const baseBySameFileFallback = /* @__PURE__ */ new Map();
|
|
47138
|
+
for (const candidate of baseCandidates) {
|
|
47139
|
+
addDiagnosticIndex(baseByStableEvidence, candidate.stableEvidenceKey, candidate.diagnosticIndex);
|
|
47140
|
+
addDiagnosticIndex(baseBySameFileStableEvidence, candidate.sameFileStableEvidenceKey, candidate.diagnosticIndex);
|
|
47141
|
+
addDiagnosticIndex(baseBySameFileFallback, candidate.sameFileFallbackKey, candidate.diagnosticIndex);
|
|
47142
|
+
}
|
|
47143
|
+
const matchedHeadDiagnosticIndexes = /* @__PURE__ */ new Set();
|
|
47144
|
+
const matchedBaseDiagnosticIndexes = /* @__PURE__ */ new Set();
|
|
47145
|
+
const matchCandidates = (baseBuckets, getKey, onMatch) => {
|
|
47146
|
+
for (const candidate of headCandidates) {
|
|
47147
|
+
if (matchedHeadDiagnosticIndexes.has(candidate.diagnosticIndex)) continue;
|
|
47148
|
+
const baseDiagnosticIndex = takeMatchingDiagnosticIndex(baseBuckets, getKey(candidate), matchedBaseDiagnosticIndexes);
|
|
47149
|
+
if (baseDiagnosticIndex === null) continue;
|
|
47150
|
+
matchedHeadDiagnosticIndexes.add(candidate.diagnosticIndex);
|
|
47151
|
+
matchedBaseDiagnosticIndexes.add(baseDiagnosticIndex);
|
|
47152
|
+
onMatch?.(candidate.diagnosticIndex, baseDiagnosticIndex);
|
|
47153
|
+
}
|
|
47154
|
+
};
|
|
47155
|
+
matchCandidates(baseBySameFileStableEvidence, (candidate) => candidate.sameFileStableEvidenceKey);
|
|
47156
|
+
matchCandidates(baseBySameFileFallback, (candidate) => candidate.sameFileFallbackKey);
|
|
47157
|
+
let crossFileMatchCount = 0;
|
|
47158
|
+
matchCandidates(baseByStableEvidence, (candidate) => candidate.stableEvidenceKey, (head, base) => {
|
|
47159
|
+
if (input.headDiagnostics[head]?.filePath !== input.baseDiagnostics[base]?.filePath) crossFileMatchCount += 1;
|
|
47160
|
+
});
|
|
46736
47161
|
return {
|
|
46737
|
-
newDiagnostics,
|
|
46738
|
-
fixedCount
|
|
47162
|
+
newDiagnostics: input.headDiagnostics.filter((_diagnostic, diagnosticIndex) => !matchedHeadDiagnosticIndexes.has(diagnosticIndex)),
|
|
47163
|
+
fixedCount: input.baseDiagnostics.length - matchedBaseDiagnosticIndexes.size,
|
|
47164
|
+
crossFileMatchCount
|
|
46739
47165
|
};
|
|
46740
47166
|
};
|
|
46741
47167
|
/**
|
|
@@ -46767,6 +47193,12 @@ const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runProm
|
|
|
46767
47193
|
};
|
|
46768
47194
|
}).pipe(provide(Git.layerNode)));
|
|
46769
47195
|
const filterSourceFiles = (filePaths) => filePaths.filter(isLintableSourceFile);
|
|
47196
|
+
const getBaselineDiffPlan = (directory, ref) => runPromise(gen(function* () {
|
|
47197
|
+
return yield* (yield* Git).baselineDiffPlan({
|
|
47198
|
+
directory,
|
|
47199
|
+
ref
|
|
47200
|
+
});
|
|
47201
|
+
}).pipe(provide(Git.layerNode)));
|
|
46770
47202
|
/**
|
|
46771
47203
|
* Programmatic façade over `Git.changedLineRanges` (the `lines` scope). Diffs
|
|
46772
47204
|
* `files` with `--unified=0` against `baseRef` (or the index when `cached`),
|
|
@@ -47223,7 +47655,7 @@ const makeNoopConsole = () => ({
|
|
|
47223
47655
|
});
|
|
47224
47656
|
//#endregion
|
|
47225
47657
|
//#region src/cli/utils/version.ts
|
|
47226
|
-
const VERSION = "0.8.
|
|
47658
|
+
const VERSION = "0.8.1-dev.0b0b5ac";
|
|
47227
47659
|
//#endregion
|
|
47228
47660
|
//#region src/cli/utils/json-mode.ts
|
|
47229
47661
|
let context = null;
|
|
@@ -47310,7 +47742,11 @@ const getRunId = () => {
|
|
|
47310
47742
|
};
|
|
47311
47743
|
//#endregion
|
|
47312
47744
|
//#region src/cli/utils/build-run-context.ts
|
|
47313
|
-
const ROOT_SUBCOMMANDS = new Set([
|
|
47745
|
+
const ROOT_SUBCOMMANDS = new Set([
|
|
47746
|
+
"design",
|
|
47747
|
+
"install",
|
|
47748
|
+
"setup"
|
|
47749
|
+
]);
|
|
47314
47750
|
const detectInvokedVia = () => {
|
|
47315
47751
|
const userAgent = process.env.npm_config_user_agent;
|
|
47316
47752
|
if (!userAgent) return "unknown";
|
|
@@ -47394,7 +47830,9 @@ const buildSentryProjectContext = (projectInfo) => ({
|
|
|
47394
47830
|
reactMajorVersion: projectInfo.reactMajorVersion,
|
|
47395
47831
|
hasTypeScript: projectInfo.hasTypeScript,
|
|
47396
47832
|
hasReactCompiler: projectInfo.hasReactCompiler,
|
|
47397
|
-
|
|
47833
|
+
tanstackQueryVersion: projectInfo.tanstackQueryVersion,
|
|
47834
|
+
mobxVersion: projectInfo.mobxVersion,
|
|
47835
|
+
styledComponentsVersion: projectInfo.styledComponentsVersion,
|
|
47398
47836
|
tailwindVersion: projectInfo.tailwindVersion,
|
|
47399
47837
|
zodVersion: projectInfo.zodVersion,
|
|
47400
47838
|
preactVersion: projectInfo.preactVersion,
|
|
@@ -47575,13 +48013,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
47575
48013
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
47576
48014
|
* standard `SENTRY_RELEASE` override.
|
|
47577
48015
|
*/
|
|
47578
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.
|
|
48016
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.1-dev.0b0b5ac`;
|
|
47579
48017
|
/**
|
|
47580
48018
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
47581
48019
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
47582
48020
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
47583
48021
|
*/
|
|
47584
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.
|
|
48022
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.1-dev.0b0b5ac") ? "development" : "production");
|
|
47585
48023
|
/**
|
|
47586
48024
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
47587
48025
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -59907,6 +60345,8 @@ const buildScanResultCacheKey = (input) => {
|
|
|
59907
60345
|
warnings: input.options.warnings,
|
|
59908
60346
|
adoptExistingLintConfig: input.options.adoptExistingLintConfig,
|
|
59909
60347
|
ignoredTags: [...input.options.ignoredTags].sort(),
|
|
60348
|
+
includedTags: [...input.options.includedTags].sort(),
|
|
60349
|
+
includeTagDefaults: input.options.includeTagDefaults,
|
|
59910
60350
|
concurrency: input.options.concurrency,
|
|
59911
60351
|
lintBatchOrdering: resolveLintBatchOrdering(),
|
|
59912
60352
|
baselineRef: input.options.baseline?.ref,
|
|
@@ -60204,6 +60644,7 @@ const buildOutcomeAttributes = (input) => {
|
|
|
60204
60644
|
new: summary.totalDiagnosticCount,
|
|
60205
60645
|
fixed: result.baselineDelta.fixedCount,
|
|
60206
60646
|
baseTotal: result.baselineDelta.baseTotalCount,
|
|
60647
|
+
crossFileMatches: result.baselineDelta.crossFileMatchCount ?? null,
|
|
60207
60648
|
degraded: false
|
|
60208
60649
|
}));
|
|
60209
60650
|
else if (input.gateExempt) attributes["baseline.degraded"] = true;
|
|
@@ -60317,28 +60758,49 @@ const diagnosticIntersectsLineRanges = (diagnostic, lineRanges) => {
|
|
|
60317
60758
|
return lineRanges.some(([rangeStartLine, rangeEndLine]) => diagnostic.line <= rangeEndLine && diagnosticEndLine >= rangeStartLine);
|
|
60318
60759
|
};
|
|
60319
60760
|
//#endregion
|
|
60761
|
+
//#region src/cli/utils/is-materializable-git-source.ts
|
|
60762
|
+
const GIT_LFS_POINTER_HEADER = "version https://git-lfs.github.com/spec/v1";
|
|
60763
|
+
const isMaterializableGitSource = (content) => !content.includes("\0") && !content.startsWith(GIT_LFS_POINTER_HEADER);
|
|
60764
|
+
//#endregion
|
|
60320
60765
|
//#region src/cli/utils/materialize-baseline-files.ts
|
|
60321
60766
|
/**
|
|
60322
|
-
* Materializes the
|
|
60323
|
-
* `
|
|
60324
|
-
*
|
|
60325
|
-
*
|
|
60326
|
-
*
|
|
60327
|
-
*
|
|
60767
|
+
* Materializes the base side of the `ref` → worktree diff into
|
|
60768
|
+
* `tempDirectory`, mirroring the project layout plus head's config files so
|
|
60769
|
+
* both sides lint under the same rules. Git rename heuristics are disabled by
|
|
60770
|
+
* the diff planner, so an old path is retained as a base deletion while its
|
|
60771
|
+
* new path is a head addition. Missing head-only additions are expected;
|
|
60772
|
+
* missing paths that the plan says must exist at base make `isComplete` false.
|
|
60328
60773
|
*/
|
|
60329
60774
|
const materializeBaselineFiles = (input) => runPromise(gen(function* () {
|
|
60330
60775
|
const git = yield* Git;
|
|
60331
|
-
|
|
60776
|
+
const diffPlan = yield* git.baselineDiffPlan({
|
|
60777
|
+
directory: input.directory,
|
|
60778
|
+
ref: input.ref
|
|
60779
|
+
});
|
|
60780
|
+
const baseFiles = input.baseFiles ?? diffPlan?.baseFiles;
|
|
60781
|
+
const headFiles = input.headFiles ?? diffPlan?.headFiles;
|
|
60782
|
+
const untrackedFiles = diffPlan?.untrackedFiles ?? [];
|
|
60783
|
+
if (baseFiles === void 0 || headFiles === void 0) return null;
|
|
60784
|
+
const files = [...new Set([...input.files, ...baseFiles])];
|
|
60785
|
+
const tree = yield* materializeSourceTree({
|
|
60332
60786
|
directory: input.directory,
|
|
60333
|
-
files
|
|
60787
|
+
files,
|
|
60334
60788
|
tempDirectory: input.tempDirectory,
|
|
60335
60789
|
readContent: (relativePath) => git.showRefContent({
|
|
60336
60790
|
directory: input.directory,
|
|
60337
60791
|
ref: input.ref,
|
|
60338
60792
|
relativePath,
|
|
60339
60793
|
options: { maxBufferBytes: GIT_SHOW_MAX_BUFFER_BYTES }
|
|
60340
|
-
})
|
|
60794
|
+
}).pipe(map$3((content) => content !== null && isMaterializableGitSource(content) ? content : null))
|
|
60341
60795
|
});
|
|
60796
|
+
const materializedFiles = new Set(tree.materializedFiles);
|
|
60797
|
+
return {
|
|
60798
|
+
...tree,
|
|
60799
|
+
baseFiles,
|
|
60800
|
+
headFiles,
|
|
60801
|
+
isComplete: baseFiles.every((filePath) => materializedFiles.has(filePath)),
|
|
60802
|
+
untrackedFiles
|
|
60803
|
+
};
|
|
60342
60804
|
}).pipe(provide(Git.layerNode)));
|
|
60343
60805
|
/**
|
|
60344
60806
|
* Resolves the commit a baseline scan should read base content from — the
|
|
@@ -60361,8 +60823,8 @@ const resolveMergeBaseRef = async (directory, ref) => {
|
|
|
60361
60823
|
//#region src/cli/utils/read-source-line.ts
|
|
60362
60824
|
/**
|
|
60363
60825
|
* Builds a cached `(filePath, line) -> source text` reader rooted at
|
|
60364
|
-
* `rootDirectory`, used
|
|
60365
|
-
*
|
|
60826
|
+
* `rootDirectory`, used as the fallback when construct-level diagnostic
|
|
60827
|
+
* evidence cannot be read. Wraps core's
|
|
60366
60828
|
* `createNodeReadFileLinesSync` with per-file memoization + 1-indexed line
|
|
60367
60829
|
* lookup; unreadable files / out-of-range lines return `null`.
|
|
60368
60830
|
*/
|
|
@@ -60377,10 +60839,342 @@ const createSourceLineReader = (rootDirectory) => {
|
|
|
60377
60839
|
};
|
|
60378
60840
|
};
|
|
60379
60841
|
//#endregion
|
|
60842
|
+
//#region src/cli/utils/read-diagnostic-evidence.ts
|
|
60843
|
+
const SOURCE_EXTENSIONS = [
|
|
60844
|
+
".ts",
|
|
60845
|
+
".tsx",
|
|
60846
|
+
".js",
|
|
60847
|
+
".jsx",
|
|
60848
|
+
".mts",
|
|
60849
|
+
".cts",
|
|
60850
|
+
".mjs",
|
|
60851
|
+
".cjs"
|
|
60852
|
+
];
|
|
60853
|
+
const COMPONENT_WRAPPER_NAMES = new Set(["memo", "forwardRef"]);
|
|
60854
|
+
const getScriptKind = (filePath) => {
|
|
60855
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
60856
|
+
if (extension === ".tsx") return ts.ScriptKind.TSX;
|
|
60857
|
+
if (extension === ".jsx") return ts.ScriptKind.JSX;
|
|
60858
|
+
if (extension === ".js" || extension === ".mjs" || extension === ".cjs") return ts.ScriptKind.JS;
|
|
60859
|
+
return ts.ScriptKind.TS;
|
|
60860
|
+
};
|
|
60861
|
+
const resolveSafeSourcePath = (rootDirectory, filePath) => {
|
|
60862
|
+
try {
|
|
60863
|
+
const resolvedRootDirectory = fs.realpathSync(rootDirectory);
|
|
60864
|
+
const absolutePath = path.resolve(resolvedRootDirectory, filePath);
|
|
60865
|
+
const relativePath = path.relative(resolvedRootDirectory, absolutePath);
|
|
60866
|
+
if (relativePath.length === 0 || relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) return null;
|
|
60867
|
+
const stats = fs.lstatSync(absolutePath);
|
|
60868
|
+
if (!stats.isFile() || stats.isSymbolicLink()) return null;
|
|
60869
|
+
const realPath = fs.realpathSync(absolutePath);
|
|
60870
|
+
const realRelativePath = path.relative(resolvedRootDirectory, realPath);
|
|
60871
|
+
return realRelativePath !== ".." && !realRelativePath.startsWith(`..${path.sep}`) && !path.isAbsolute(realRelativePath) ? realPath : null;
|
|
60872
|
+
} catch {
|
|
60873
|
+
return null;
|
|
60874
|
+
}
|
|
60875
|
+
};
|
|
60876
|
+
const readSourceRecord = (rootDirectory, filePath, state) => {
|
|
60877
|
+
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
60878
|
+
if (state.recordByFilePath.has(normalizedFilePath)) return state.recordByFilePath.get(normalizedFilePath) ?? null;
|
|
60879
|
+
const absolutePath = resolveSafeSourcePath(rootDirectory, normalizedFilePath);
|
|
60880
|
+
if (absolutePath === null) {
|
|
60881
|
+
state.recordByFilePath.set(normalizedFilePath, null);
|
|
60882
|
+
return null;
|
|
60883
|
+
}
|
|
60884
|
+
try {
|
|
60885
|
+
const sourceText = fs.readFileSync(absolutePath, "utf-8");
|
|
60886
|
+
const record = {
|
|
60887
|
+
absolutePath,
|
|
60888
|
+
filePath: normalizedFilePath,
|
|
60889
|
+
sourceFile: ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
60890
|
+
sourceText
|
|
60891
|
+
};
|
|
60892
|
+
state.recordByFilePath.set(normalizedFilePath, record);
|
|
60893
|
+
return record;
|
|
60894
|
+
} catch {
|
|
60895
|
+
state.recordByFilePath.set(normalizedFilePath, null);
|
|
60896
|
+
return null;
|
|
60897
|
+
}
|
|
60898
|
+
};
|
|
60899
|
+
const listSourceRecords = (rootDirectory, state) => {
|
|
60900
|
+
if (state.sourceRecords !== null) return state.sourceRecords;
|
|
60901
|
+
state.sourceRecords = listSourceFiles(rootDirectory).flatMap((filePath) => {
|
|
60902
|
+
const record = readSourceRecord(rootDirectory, filePath, state);
|
|
60903
|
+
return record === null ? [] : [record];
|
|
60904
|
+
});
|
|
60905
|
+
return state.sourceRecords;
|
|
60906
|
+
};
|
|
60907
|
+
const unwrapExpression = (expression) => {
|
|
60908
|
+
let currentExpression = expression;
|
|
60909
|
+
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
60910
|
+
return currentExpression;
|
|
60911
|
+
};
|
|
60912
|
+
const getCalleeName = (expression) => {
|
|
60913
|
+
const unwrappedExpression = unwrapExpression(expression);
|
|
60914
|
+
if (ts.isIdentifier(unwrappedExpression)) return unwrappedExpression.text;
|
|
60915
|
+
if (ts.isPropertyAccessExpression(unwrappedExpression)) return unwrappedExpression.name.text;
|
|
60916
|
+
return null;
|
|
60917
|
+
};
|
|
60918
|
+
const isComponentWrapperCall = (node) => {
|
|
60919
|
+
const wrapperName = getCalleeName(node.expression);
|
|
60920
|
+
return wrapperName !== null && COMPONENT_WRAPPER_NAMES.has(wrapperName);
|
|
60921
|
+
};
|
|
60922
|
+
const getFunctionName = (node) => {
|
|
60923
|
+
if (ts.isFunctionDeclaration(node) && node.name !== void 0) return node.name.text;
|
|
60924
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
60925
|
+
let componentExpression = node;
|
|
60926
|
+
while (true) {
|
|
60927
|
+
const parent = componentExpression.parent;
|
|
60928
|
+
if (ts.isParenthesizedExpression(parent) || ts.isAsExpression(parent) || ts.isTypeAssertionExpression(parent) || ts.isNonNullExpression(parent) || ts.isSatisfiesExpression(parent)) {
|
|
60929
|
+
componentExpression = parent;
|
|
60930
|
+
continue;
|
|
60931
|
+
}
|
|
60932
|
+
if (ts.isCallExpression(parent) && parent.arguments[0] === componentExpression && isComponentWrapperCall(parent)) {
|
|
60933
|
+
componentExpression = parent;
|
|
60934
|
+
continue;
|
|
60935
|
+
}
|
|
60936
|
+
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) return parent.name.text;
|
|
60937
|
+
return node.name?.text ?? null;
|
|
60938
|
+
}
|
|
60939
|
+
}
|
|
60940
|
+
return null;
|
|
60941
|
+
};
|
|
60942
|
+
const isComponentFunction = (node) => ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node);
|
|
60943
|
+
const isDefaultExportedComponent = (record, node, componentName) => {
|
|
60944
|
+
if (ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword)) return true;
|
|
60945
|
+
return record.sourceFile.statements.some((statement) => ts.isExportAssignment(statement) && ts.isIdentifier(statement.expression) && statement.expression.text === componentName);
|
|
60946
|
+
};
|
|
60947
|
+
const findEnclosingComponent = (record, diagnostic) => {
|
|
60948
|
+
if (!Number.isInteger(diagnostic.line) || diagnostic.line < 1) return null;
|
|
60949
|
+
const lineIndex = diagnostic.line - 1;
|
|
60950
|
+
const lineStarts = record.sourceFile.getLineStarts();
|
|
60951
|
+
const lineStart = lineStarts[lineIndex];
|
|
60952
|
+
if (lineStart === void 0) return null;
|
|
60953
|
+
const nextLineStart = lineStarts[lineIndex + 1] ?? record.sourceFile.end;
|
|
60954
|
+
const lineEnd = Math.max(lineStart, nextLineStart - 1);
|
|
60955
|
+
const columnIndex = Number.isInteger(diagnostic.column) ? Math.max(0, diagnostic.column - 1) : 0;
|
|
60956
|
+
const diagnosticPosition = Math.min(lineStart + columnIndex, lineEnd);
|
|
60957
|
+
let component = null;
|
|
60958
|
+
let componentWidth = Number.POSITIVE_INFINITY;
|
|
60959
|
+
const visit = (node) => {
|
|
60960
|
+
if (diagnosticPosition < node.getStart(record.sourceFile) || diagnosticPosition > node.end) return;
|
|
60961
|
+
if (isComponentFunction(node)) {
|
|
60962
|
+
const name = getFunctionName(node);
|
|
60963
|
+
const width = node.end - node.getStart(record.sourceFile);
|
|
60964
|
+
if (name !== null && /^[A-Z]/.test(name) && width < componentWidth) {
|
|
60965
|
+
component = {
|
|
60966
|
+
isDefaultExport: isDefaultExportedComponent(record, node, name),
|
|
60967
|
+
name,
|
|
60968
|
+
node,
|
|
60969
|
+
record
|
|
60970
|
+
};
|
|
60971
|
+
componentWidth = width;
|
|
60972
|
+
}
|
|
60973
|
+
}
|
|
60974
|
+
ts.forEachChild(node, visit);
|
|
60975
|
+
};
|
|
60976
|
+
visit(record.sourceFile);
|
|
60977
|
+
return component;
|
|
60978
|
+
};
|
|
60979
|
+
const moduleSpecifierTargetsComponent = (callerRecord, moduleSpecifier, componentRecord) => {
|
|
60980
|
+
if (!moduleSpecifier.startsWith(".")) return false;
|
|
60981
|
+
const unresolvedPath = path.resolve(path.dirname(callerRecord.absolutePath), moduleSpecifier);
|
|
60982
|
+
const specifiedExtension = path.extname(unresolvedPath).toLowerCase();
|
|
60983
|
+
const sourcePathWithoutExtension = SOURCE_EXTENSIONS.includes(specifiedExtension) ? unresolvedPath.slice(0, -specifiedExtension.length) : unresolvedPath;
|
|
60984
|
+
return [
|
|
60985
|
+
unresolvedPath,
|
|
60986
|
+
...SOURCE_EXTENSIONS.map((extension) => `${sourcePathWithoutExtension}${extension}`),
|
|
60987
|
+
...sourcePathWithoutExtension === unresolvedPath ? SOURCE_EXTENSIONS.map((extension) => path.join(unresolvedPath, `index${extension}`)) : []
|
|
60988
|
+
].includes(componentRecord.absolutePath);
|
|
60989
|
+
};
|
|
60990
|
+
const getComponentTagNames = (record, component) => {
|
|
60991
|
+
if (record.absolutePath === component.record.absolutePath) return new Set([component.name]);
|
|
60992
|
+
const tagNames = /* @__PURE__ */ new Set();
|
|
60993
|
+
for (const statement of record.sourceFile.statements) {
|
|
60994
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier) || !moduleSpecifierTargetsComponent(record, statement.moduleSpecifier.text, component.record)) continue;
|
|
60995
|
+
const importClause = statement.importClause;
|
|
60996
|
+
if (component.isDefaultExport && importClause?.name !== void 0) tagNames.add(importClause.name.text);
|
|
60997
|
+
const namedBindings = importClause?.namedBindings;
|
|
60998
|
+
if (namedBindings !== void 0 && ts.isNamedImports(namedBindings)) {
|
|
60999
|
+
for (const element of namedBindings.elements) if ((element.propertyName?.text ?? element.name.text) === component.name) tagNames.add(element.name.text);
|
|
61000
|
+
}
|
|
61001
|
+
}
|
|
61002
|
+
return tagNames;
|
|
61003
|
+
};
|
|
61004
|
+
const getTransparentCall = (functionNode) => {
|
|
61005
|
+
if (functionNode.body === void 0) return null;
|
|
61006
|
+
if (!ts.isBlock(functionNode.body)) {
|
|
61007
|
+
const expression = unwrapExpression(functionNode.body);
|
|
61008
|
+
return ts.isCallExpression(expression) ? expression : null;
|
|
61009
|
+
}
|
|
61010
|
+
if (functionNode.body.statements.length !== 1) return null;
|
|
61011
|
+
const statement = functionNode.body.statements[0];
|
|
61012
|
+
if (statement === void 0) return null;
|
|
61013
|
+
const expression = ts.isExpressionStatement(statement) || ts.isReturnStatement(statement) ? statement.expression : void 0;
|
|
61014
|
+
if (expression === void 0) return null;
|
|
61015
|
+
const unwrappedExpression = unwrapExpression(expression);
|
|
61016
|
+
return ts.isCallExpression(unwrappedExpression) ? unwrappedExpression : null;
|
|
61017
|
+
};
|
|
61018
|
+
const resolveTransparentTarget = (functionNode) => {
|
|
61019
|
+
const call = getTransparentCall(functionNode);
|
|
61020
|
+
if (call === null) return null;
|
|
61021
|
+
const target = unwrapExpression(call.expression);
|
|
61022
|
+
if (!ts.isIdentifier(target) || call.arguments.length !== functionNode.parameters.length) return null;
|
|
61023
|
+
for (const [parameterIndex, parameter] of functionNode.parameters.entries()) {
|
|
61024
|
+
const argument = call.arguments[parameterIndex];
|
|
61025
|
+
const unwrappedArgument = argument === void 0 ? null : unwrapExpression(argument);
|
|
61026
|
+
if (!ts.isIdentifier(parameter.name) || unwrappedArgument === null || !ts.isIdentifier(unwrappedArgument) || unwrappedArgument.text !== parameter.name.text) return null;
|
|
61027
|
+
}
|
|
61028
|
+
return target.text;
|
|
61029
|
+
};
|
|
61030
|
+
const getForwardingFunction = (declaration) => {
|
|
61031
|
+
if (ts.isFunctionDeclaration(declaration)) return declaration;
|
|
61032
|
+
if (!ts.isVariableDeclaration(declaration) || declaration.initializer === void 0) return null;
|
|
61033
|
+
const initializer = unwrapExpression(declaration.initializer);
|
|
61034
|
+
if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) return initializer;
|
|
61035
|
+
if (ts.isCallExpression(initializer) && getCalleeName(initializer.expression) === "useCallback") {
|
|
61036
|
+
const callback = initializer.arguments[0];
|
|
61037
|
+
if (callback !== void 0 && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) return callback;
|
|
61038
|
+
}
|
|
61039
|
+
return null;
|
|
61040
|
+
};
|
|
61041
|
+
const getDeclaredBindingName = (node) => {
|
|
61042
|
+
if ((ts.isVariableDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node) || ts.isFunctionDeclaration(node)) && node.name !== void 0 && ts.isIdentifier(node.name)) return node.name.text;
|
|
61043
|
+
if (ts.isImportClause(node)) return node.name?.text ?? null;
|
|
61044
|
+
if (ts.isImportSpecifier(node) || ts.isNamespaceImport(node)) return node.name.text;
|
|
61045
|
+
return null;
|
|
61046
|
+
};
|
|
61047
|
+
const findBindingScope = (node) => {
|
|
61048
|
+
let currentNode = node;
|
|
61049
|
+
while (currentNode !== void 0) {
|
|
61050
|
+
if (ts.isParameter(currentNode) && ts.isFunctionLike(currentNode.parent)) return currentNode.parent;
|
|
61051
|
+
if (ts.isBlock(currentNode) || ts.isModuleBlock(currentNode) || ts.isSourceFile(currentNode)) return currentNode;
|
|
61052
|
+
currentNode = currentNode.parent;
|
|
61053
|
+
}
|
|
61054
|
+
return node.getSourceFile();
|
|
61055
|
+
};
|
|
61056
|
+
const findBindingDeclaration = (record, binding) => {
|
|
61057
|
+
const usagePosition = binding.getStart(record.sourceFile);
|
|
61058
|
+
const candidates = [];
|
|
61059
|
+
const visit = (node) => {
|
|
61060
|
+
if (getDeclaredBindingName(node) === binding.text) {
|
|
61061
|
+
const scope = findBindingScope(node);
|
|
61062
|
+
const isVisibleScope = usagePosition >= scope.getStart(record.sourceFile) && usagePosition <= scope.end;
|
|
61063
|
+
const isVisibleDeclaration = ts.isFunctionDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node) || ts.isImportClause(node) || ts.isImportSpecifier(node) || ts.isNamespaceImport(node) || node.getStart(record.sourceFile) < usagePosition;
|
|
61064
|
+
if (isVisibleScope && isVisibleDeclaration) candidates.push({
|
|
61065
|
+
declaration: node,
|
|
61066
|
+
scope
|
|
61067
|
+
});
|
|
61068
|
+
}
|
|
61069
|
+
ts.forEachChild(node, visit);
|
|
61070
|
+
};
|
|
61071
|
+
visit(record.sourceFile);
|
|
61072
|
+
let nearestCandidate = null;
|
|
61073
|
+
let nearestScopeWidth = Number.POSITIVE_INFINITY;
|
|
61074
|
+
let isNearestScopeAmbiguous = false;
|
|
61075
|
+
for (const candidate of candidates) {
|
|
61076
|
+
const scopeWidth = candidate.scope.end - candidate.scope.getStart(record.sourceFile);
|
|
61077
|
+
if (scopeWidth < nearestScopeWidth) {
|
|
61078
|
+
nearestCandidate = candidate;
|
|
61079
|
+
nearestScopeWidth = scopeWidth;
|
|
61080
|
+
isNearestScopeAmbiguous = false;
|
|
61081
|
+
} else if (scopeWidth === nearestScopeWidth) isNearestScopeAmbiguous = true;
|
|
61082
|
+
}
|
|
61083
|
+
if (isNearestScopeAmbiguous) return null;
|
|
61084
|
+
const declaration = nearestCandidate?.declaration;
|
|
61085
|
+
return declaration !== void 0 && (ts.isVariableDeclaration(declaration) || ts.isFunctionDeclaration(declaration)) ? declaration : null;
|
|
61086
|
+
};
|
|
61087
|
+
const resolveHandlerBinding = (record, binding) => {
|
|
61088
|
+
const declaration = findBindingDeclaration(record, binding);
|
|
61089
|
+
if (declaration === null) return binding.text;
|
|
61090
|
+
const forwardingFunction = getForwardingFunction(declaration);
|
|
61091
|
+
const target = forwardingFunction === null ? null : resolveTransparentTarget(forwardingFunction);
|
|
61092
|
+
return target === null || target === binding.text ? binding.text : target;
|
|
61093
|
+
};
|
|
61094
|
+
const getJsxTagName = (node) => ts.isIdentifier(node.tagName) ? node.tagName.text : null;
|
|
61095
|
+
const getHandlerBinding = (node, propName) => {
|
|
61096
|
+
for (const property of node.attributes.properties) {
|
|
61097
|
+
if (!ts.isJsxAttribute(property) || property.name.getText() !== propName) continue;
|
|
61098
|
+
const initializer = property.initializer;
|
|
61099
|
+
if (initializer === void 0 || !ts.isJsxExpression(initializer)) return null;
|
|
61100
|
+
if (initializer.expression === void 0) return null;
|
|
61101
|
+
const expression = unwrapExpression(initializer.expression);
|
|
61102
|
+
return ts.isIdentifier(expression) ? expression : null;
|
|
61103
|
+
}
|
|
61104
|
+
return null;
|
|
61105
|
+
};
|
|
61106
|
+
const getForwardedHandlerAliases = (rootDirectory, component, propNames, state) => {
|
|
61107
|
+
const cacheKey = `${component.record.filePath}\0${component.name}\0${[...propNames].sort().join("\0")}`;
|
|
61108
|
+
const cachedAliases = state.aliasesByComponent.get(cacheKey);
|
|
61109
|
+
if (cachedAliases !== void 0) return cachedAliases;
|
|
61110
|
+
const bindingsByPropName = /* @__PURE__ */ new Map();
|
|
61111
|
+
const unresolvedPropNames = /* @__PURE__ */ new Set();
|
|
61112
|
+
let callsiteCount = 0;
|
|
61113
|
+
for (const record of listSourceRecords(rootDirectory, state)) {
|
|
61114
|
+
const tagNames = getComponentTagNames(record, component);
|
|
61115
|
+
if (tagNames.size === 0) continue;
|
|
61116
|
+
const visit = (node) => {
|
|
61117
|
+
if (record === component.record && node === component.node) return;
|
|
61118
|
+
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
61119
|
+
const tagName = getJsxTagName(node);
|
|
61120
|
+
if (tagName !== null && tagNames.has(tagName)) {
|
|
61121
|
+
callsiteCount += 1;
|
|
61122
|
+
for (const propName of propNames) {
|
|
61123
|
+
const binding = getHandlerBinding(node, propName);
|
|
61124
|
+
if (binding === null) {
|
|
61125
|
+
unresolvedPropNames.add(propName);
|
|
61126
|
+
continue;
|
|
61127
|
+
}
|
|
61128
|
+
const bindings = bindingsByPropName.get(propName) ?? /* @__PURE__ */ new Set();
|
|
61129
|
+
bindings.add(resolveHandlerBinding(record, binding));
|
|
61130
|
+
bindingsByPropName.set(propName, bindings);
|
|
61131
|
+
}
|
|
61132
|
+
}
|
|
61133
|
+
}
|
|
61134
|
+
ts.forEachChild(node, visit);
|
|
61135
|
+
};
|
|
61136
|
+
visit(record.sourceFile);
|
|
61137
|
+
}
|
|
61138
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
61139
|
+
if (callsiteCount > 0) for (const [propName, bindings] of bindingsByPropName) {
|
|
61140
|
+
if (bindings.size !== 1 || unresolvedPropNames.has(propName)) continue;
|
|
61141
|
+
const [bindingName] = bindings;
|
|
61142
|
+
if (bindingName !== void 0) aliases.set(propName, bindingName);
|
|
61143
|
+
}
|
|
61144
|
+
state.aliasesByComponent.set(cacheKey, aliases);
|
|
61145
|
+
return aliases;
|
|
61146
|
+
};
|
|
61147
|
+
const normalizeForwardedHandlers = (evidence, aliases) => {
|
|
61148
|
+
let normalizedEvidence = evidence;
|
|
61149
|
+
for (const [propName, bindingName] of aliases) normalizedEvidence = normalizedEvidence.replace(new RegExp(`\\{\\s*${propName}\\s*\\}`, "g"), `{${bindingName}}`).replace(new RegExp(`=>\\s*${propName}\\s*\\(`, "g"), `=> ${bindingName}(`);
|
|
61150
|
+
return normalizedEvidence;
|
|
61151
|
+
};
|
|
61152
|
+
const createDiagnosticEvidenceReader = (rootDirectory, options = {}) => {
|
|
61153
|
+
const state = {
|
|
61154
|
+
aliasesByComponent: /* @__PURE__ */ new Map(),
|
|
61155
|
+
recordByFilePath: /* @__PURE__ */ new Map(),
|
|
61156
|
+
sourceRecords: null
|
|
61157
|
+
};
|
|
61158
|
+
return (diagnostic) => {
|
|
61159
|
+
const record = readSourceRecord(rootDirectory, diagnostic.filePath, state);
|
|
61160
|
+
if (record === null || !Number.isInteger(diagnostic.line)) return null;
|
|
61161
|
+
const sourceLines = record.sourceText.split(/\r?\n/);
|
|
61162
|
+
const startLineIndex = Math.max(0, diagnostic.line - 1);
|
|
61163
|
+
const endLineIndex = Math.max(startLineIndex, (diagnostic.endLine ?? diagnostic.line) - 1);
|
|
61164
|
+
const evidence = sourceLines.slice(startLineIndex, endLineIndex + 1).join("\n");
|
|
61165
|
+
if (!options.resolveForwardedHandlers) return evidence;
|
|
61166
|
+
const propNames = new Set(evidence.match(/\bon[A-Z]\w*\b/g) ?? []);
|
|
61167
|
+
if (propNames.size === 0) return evidence;
|
|
61168
|
+
const component = findEnclosingComponent(record, diagnostic);
|
|
61169
|
+
if (component === null) return evidence;
|
|
61170
|
+
return normalizeForwardedHandlers(evidence, getForwardedHandlerAliases(rootDirectory, component, propNames, state));
|
|
61171
|
+
};
|
|
61172
|
+
};
|
|
61173
|
+
//#endregion
|
|
60380
61174
|
//#region src/cli/utils/build-no-score-message.ts
|
|
60381
61175
|
const ENTERPRISE_CONTACT_HINT = `Want something custom to your company? Contact us at ${ENTERPRISE_CONTACT_URL}.`;
|
|
60382
|
-
const buildNoScoreMessage = (isScoreDisabled) => {
|
|
60383
|
-
return `${isScoreDisabled ? "Score disabled by --no-score." : "Score unavailable (could not reach the score API)."} ${ENTERPRISE_CONTACT_HINT}`;
|
|
61176
|
+
const buildNoScoreMessage = (isScoreDisabled, disabledMessage) => {
|
|
61177
|
+
return `${isScoreDisabled ? disabledMessage ?? "Score disabled by --no-score." : "Score unavailable (could not reach the score API)."} ${ENTERPRISE_CONTACT_HINT}`;
|
|
60384
61178
|
};
|
|
60385
61179
|
//#endregion
|
|
60386
61180
|
//#region src/cli/utils/render-agent-guidance.ts
|
|
@@ -61505,40 +62299,47 @@ const buildChangedLineMatcher = (directory, changedLineRanges) => {
|
|
|
61505
62299
|
return diagnosticIntersectsLineRanges(diagnostic, ranges);
|
|
61506
62300
|
};
|
|
61507
62301
|
};
|
|
61508
|
-
const buildIgnoredTags = (userConfig) => {
|
|
62302
|
+
const buildIgnoredTags = (userConfig, includedTags) => {
|
|
61509
62303
|
const tags = /* @__PURE__ */ new Set();
|
|
61510
62304
|
if (userConfig?.ignore?.tags) for (const tag of userConfig.ignore.tags) tags.add(tag);
|
|
62305
|
+
for (const tag of includedTags) tags.delete(tag);
|
|
61511
62306
|
return tags;
|
|
61512
62307
|
};
|
|
61513
|
-
const mergeInspectOptions = (inputOptions, userConfig) =>
|
|
61514
|
-
|
|
61515
|
-
|
|
61516
|
-
|
|
61517
|
-
|
|
61518
|
-
|
|
61519
|
-
|
|
61520
|
-
|
|
61521
|
-
|
|
61522
|
-
|
|
61523
|
-
|
|
61524
|
-
|
|
61525
|
-
|
|
61526
|
-
|
|
61527
|
-
|
|
61528
|
-
|
|
61529
|
-
|
|
61530
|
-
|
|
61531
|
-
|
|
61532
|
-
|
|
61533
|
-
|
|
61534
|
-
|
|
61535
|
-
|
|
61536
|
-
|
|
61537
|
-
|
|
61538
|
-
|
|
61539
|
-
|
|
61540
|
-
|
|
61541
|
-
|
|
62308
|
+
const mergeInspectOptions = (inputOptions, userConfig) => {
|
|
62309
|
+
const includedTags = inputOptions.includedTags ?? /* @__PURE__ */ new Set();
|
|
62310
|
+
return {
|
|
62311
|
+
lint: inputOptions.lint ?? userConfig?.lint ?? true,
|
|
62312
|
+
deadCode: inputOptions.deadCode ?? userConfig?.deadCode ?? true,
|
|
62313
|
+
supplyChain: inputOptions.supplyChain ?? userConfig?.supplyChain?.enabled ?? true,
|
|
62314
|
+
verbose: inputOptions.verbose ?? userConfig?.verbose ?? false,
|
|
62315
|
+
outputDirectory: inputOptions.outputDirectory || null,
|
|
62316
|
+
scoreOnly: inputOptions.scoreOnly ?? false,
|
|
62317
|
+
noScore: inputOptions.noScore ?? userConfig?.noScore ?? false,
|
|
62318
|
+
isCi: inputOptions.isCi ?? false,
|
|
62319
|
+
isCiOrCodingAgentEnvironment: isCiOrCodingAgentEnvironment(),
|
|
62320
|
+
isNonInteractiveEnvironment: isNonInteractiveEnvironment(),
|
|
62321
|
+
silent: inputOptions.silent ?? false,
|
|
62322
|
+
includePaths: inputOptions.includePaths ?? [],
|
|
62323
|
+
customRulesOnly: includedTags.size > 0 ? false : userConfig?.customRulesOnly ?? false,
|
|
62324
|
+
share: userConfig?.share ?? true,
|
|
62325
|
+
respectInlineDisables: inputOptions.respectInlineDisables ?? userConfig?.respectInlineDisables ?? true,
|
|
62326
|
+
warnings: inputOptions.warnings ?? userConfig?.warnings ?? true,
|
|
62327
|
+
categoryFilters: new Set(resolveCliCategories(inputOptions.categoryFilters) ?? []),
|
|
62328
|
+
adoptExistingLintConfig: includedTags.size > 0 ? false : userConfig?.adoptExistingLintConfig ?? true,
|
|
62329
|
+
ignoredTags: buildIgnoredTags(userConfig, includedTags),
|
|
62330
|
+
includedTags,
|
|
62331
|
+
includeTagDefaults: inputOptions.includeTagDefaults ?? false,
|
|
62332
|
+
scoreDisabledMessage: inputOptions.scoreDisabledMessage,
|
|
62333
|
+
outputSurface: inputOptions.outputSurface ?? "cli",
|
|
62334
|
+
suppressRendering: inputOptions.suppressRendering ?? false,
|
|
62335
|
+
concurrentScan: inputOptions.concurrentScan ?? false,
|
|
62336
|
+
concurrency: inputOptions.concurrency,
|
|
62337
|
+
maxDurationMs: inputOptions.maxDurationMs ?? null,
|
|
62338
|
+
baseline: inputOptions.baseline ?? null,
|
|
62339
|
+
changedLineRanges: inputOptions.changedLineRanges ?? null,
|
|
62340
|
+
supplyChainManifestChanged: inputOptions.supplyChainManifestChanged ?? false
|
|
62341
|
+
};
|
|
62342
|
+
};
|
|
61542
62343
|
const deriveScope = (options) => {
|
|
61543
62344
|
if (options.baseline) return "changed";
|
|
61544
62345
|
if (options.changedLineRanges !== null) return "lines";
|
|
@@ -61620,6 +62421,8 @@ const runBaselineComparison = async (params) => {
|
|
|
61620
62421
|
directory: params.directory,
|
|
61621
62422
|
ref: params.baselineRef,
|
|
61622
62423
|
files: params.options.includePaths,
|
|
62424
|
+
baseFiles: params.baseFiles,
|
|
62425
|
+
headFiles: params.headFiles,
|
|
61623
62426
|
tempDirectory
|
|
61624
62427
|
}).catch((error) => {
|
|
61625
62428
|
rmSync(tempDirectory, {
|
|
@@ -61628,7 +62431,24 @@ const runBaselineComparison = async (params) => {
|
|
|
61628
62431
|
});
|
|
61629
62432
|
throw error;
|
|
61630
62433
|
});
|
|
62434
|
+
if (snapshot === null) {
|
|
62435
|
+
rmSync(tempDirectory, {
|
|
62436
|
+
recursive: true,
|
|
62437
|
+
force: true
|
|
62438
|
+
});
|
|
62439
|
+
return null;
|
|
62440
|
+
}
|
|
61631
62441
|
try {
|
|
62442
|
+
if (!snapshot.isComplete) return null;
|
|
62443
|
+
const analyzedHeadFiles = new Set(params.headAnalyzedFiles.map(toForwardSlashes));
|
|
62444
|
+
const baseFiles = new Set(snapshot.baseFiles.map(toForwardSlashes));
|
|
62445
|
+
const trackedHeadFiles = new Set(snapshot.headFiles.map(toForwardSlashes));
|
|
62446
|
+
const expectedHeadFiles = new Set(trackedHeadFiles);
|
|
62447
|
+
for (const filePath of params.options.includePaths) {
|
|
62448
|
+
const normalizedFilePath = toForwardSlashes(filePath);
|
|
62449
|
+
if (!baseFiles.has(normalizedFilePath)) expectedHeadFiles.add(normalizedFilePath);
|
|
62450
|
+
}
|
|
62451
|
+
if (filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
|
|
61632
62452
|
const baseLayers = buildRuntimeLayers({
|
|
61633
62453
|
directory: snapshot.tempDirectory,
|
|
61634
62454
|
hasConfigOverride: true,
|
|
@@ -61644,12 +62464,14 @@ const runBaselineComparison = async (params) => {
|
|
|
61644
62464
|
});
|
|
61645
62465
|
const baseOutput = await runPromise(restoreLegacyThrow(runInspect({
|
|
61646
62466
|
directory: snapshot.tempDirectory,
|
|
61647
|
-
includePaths:
|
|
62467
|
+
includePaths: snapshot.materializedFiles,
|
|
61648
62468
|
customRulesOnly: params.options.customRulesOnly,
|
|
61649
62469
|
respectInlineDisables: params.options.respectInlineDisables,
|
|
61650
62470
|
warnings: params.options.warnings,
|
|
61651
62471
|
adoptExistingLintConfig: params.options.adoptExistingLintConfig,
|
|
61652
62472
|
ignoredTags: params.options.ignoredTags,
|
|
62473
|
+
includedTags: params.options.includedTags,
|
|
62474
|
+
includeTagDefaults: params.options.includeTagDefaults,
|
|
61653
62475
|
nodeBinaryPath: params.resolvedNodeBinaryPath ?? void 0,
|
|
61654
62476
|
runDeadCode: false,
|
|
61655
62477
|
isCi: params.options.isCi,
|
|
@@ -61661,18 +62483,22 @@ const runBaselineComparison = async (params) => {
|
|
|
61661
62483
|
deadlineEpochMs: params.deadlineEpochMs ?? void 0
|
|
61662
62484
|
}, {}).pipe(provide(baseLayers), provideService$2(PerFileLintCacheEnabled, false), provideService$2(SidecarLintCacheEnabled, false), provideService$2(Console, silentConsole))));
|
|
61663
62485
|
if (baseOutput.didLintFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
|
|
62486
|
+
const hasUnscannedUntrackedSourceFiles = filterSourceFiles(snapshot.untrackedFiles.map(toForwardSlashes)).some((filePath) => !analyzedHeadFiles.has(filePath));
|
|
61664
62487
|
const delta = computeDiagnosticDelta({
|
|
61665
62488
|
headDiagnostics: params.headDiagnostics,
|
|
61666
62489
|
baseDiagnostics: baseOutput.diagnostics,
|
|
61667
62490
|
readHeadLine: createSourceLineReader(params.directory),
|
|
61668
|
-
readBaseLine: createSourceLineReader(snapshot.tempDirectory)
|
|
62491
|
+
readBaseLine: createSourceLineReader(snapshot.tempDirectory),
|
|
62492
|
+
readHeadEvidence: createDiagnosticEvidenceReader(params.directory, { resolveForwardedHandlers: true }),
|
|
62493
|
+
readBaseEvidence: createDiagnosticEvidenceReader(snapshot.tempDirectory)
|
|
61669
62494
|
});
|
|
61670
62495
|
return {
|
|
61671
62496
|
displayDiagnostics: delta.newDiagnostics,
|
|
61672
62497
|
baselineDelta: {
|
|
61673
62498
|
baseRef: params.baselineRef,
|
|
61674
|
-
fixedCount: delta.fixedCount,
|
|
61675
|
-
baseTotalCount: baseOutput.diagnostics.length
|
|
62499
|
+
fixedCount: hasUnscannedUntrackedSourceFiles ? 0 : delta.fixedCount,
|
|
62500
|
+
baseTotalCount: baseOutput.diagnostics.length,
|
|
62501
|
+
crossFileMatchCount: delta.crossFileMatchCount
|
|
61676
62502
|
}
|
|
61677
62503
|
};
|
|
61678
62504
|
} finally {
|
|
@@ -61739,6 +62565,8 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
61739
62565
|
warnings: options.warnings,
|
|
61740
62566
|
adoptExistingLintConfig: options.adoptExistingLintConfig,
|
|
61741
62567
|
ignoredTags: options.ignoredTags,
|
|
62568
|
+
includedTags: options.includedTags,
|
|
62569
|
+
includeTagDefaults: options.includeTagDefaults,
|
|
61742
62570
|
nodeBinaryPath: resolvedNodeBinaryPath ?? void 0,
|
|
61743
62571
|
runDeadCode: options.deadCode,
|
|
61744
62572
|
isCi: options.isCi,
|
|
@@ -61779,6 +62607,9 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
61779
62607
|
headDiagnostics: output.diagnostics,
|
|
61780
62608
|
resolvedNodeBinaryPath,
|
|
61781
62609
|
baselineRef: options.baseline.ref,
|
|
62610
|
+
baseFiles: options.baseline.baseFiles,
|
|
62611
|
+
headFiles: options.baseline.headFiles,
|
|
62612
|
+
headAnalyzedFiles: output.analyzedFiles,
|
|
61782
62613
|
deadlineEpochMs
|
|
61783
62614
|
});
|
|
61784
62615
|
if (comparison) {
|
|
@@ -61924,7 +62755,7 @@ const finalizeAndRender = (input) => gen(function* () {
|
|
|
61924
62755
|
securityScanFailed
|
|
61925
62756
|
});
|
|
61926
62757
|
const hasSkippedChecks = skippedChecks.length > 0;
|
|
61927
|
-
const noScoreMessage = buildNoScoreMessage(options.noScore);
|
|
62758
|
+
const noScoreMessage = buildNoScoreMessage(options.noScore, options.scoreDisabledMessage);
|
|
61928
62759
|
const buildResult = () => ({
|
|
61929
62760
|
diagnostics: [...diagnostics],
|
|
61930
62761
|
score,
|
|
@@ -64029,21 +64860,25 @@ const resolveParallelFlag = (parallel) => parallel === false ? 1 : void 0;
|
|
|
64029
64860
|
*/
|
|
64030
64861
|
const resolveCliInspectOptions = (flags, userConfig) => {
|
|
64031
64862
|
const wantsWarningGate = pickBlockingLevel(flags, userConfig) === "warning";
|
|
64863
|
+
const isDesignScan = flags.design === true;
|
|
64032
64864
|
return {
|
|
64033
64865
|
lint: flags.lint,
|
|
64034
|
-
deadCode: flags.deadCode,
|
|
64035
|
-
supplyChain: flags.supplyChain,
|
|
64866
|
+
deadCode: isDesignScan ? false : flags.deadCode,
|
|
64867
|
+
supplyChain: isDesignScan ? false : flags.supplyChain,
|
|
64036
64868
|
verbose: flags.verbose,
|
|
64037
64869
|
outputDirectory: flags.outputDir,
|
|
64038
64870
|
respectInlineDisables: flags.respectInlineDisables === false ? false : void 0,
|
|
64039
64871
|
warnings: wantsWarningGate ? true : flags.warnings,
|
|
64040
64872
|
scoreOnly: flags.score === true,
|
|
64041
|
-
noScore: flags.score === false || flags.telemetry === false ? true : void 0,
|
|
64873
|
+
noScore: isDesignScan || flags.score === false || flags.telemetry === false ? true : void 0,
|
|
64042
64874
|
isCi: isCiEnvironment(),
|
|
64043
64875
|
silent: Boolean(flags.json),
|
|
64044
64876
|
concurrency: resolveParallelFlag(flags.parallel),
|
|
64045
64877
|
maxDurationMs: resolveMaxDurationFlag(flags.maxDuration),
|
|
64046
|
-
categoryFilters: resolveCliCategories(flags.category)
|
|
64878
|
+
categoryFilters: resolveCliCategories(flags.category),
|
|
64879
|
+
includedTags: isDesignScan ? new Set(["design"]) : void 0,
|
|
64880
|
+
includeTagDefaults: isDesignScan ? true : void 0,
|
|
64881
|
+
scoreDisabledMessage: isDesignScan ? "Design scans do not affect the React health score." : void 0
|
|
64047
64882
|
};
|
|
64048
64883
|
};
|
|
64049
64884
|
//#endregion
|
|
@@ -64063,20 +64898,25 @@ const resolveProjectRelativeDirectory = (rootDirectory, projectDirectory) => {
|
|
|
64063
64898
|
return relativeProjectDirectory;
|
|
64064
64899
|
};
|
|
64065
64900
|
//#endregion
|
|
64066
|
-
//#region src/cli/utils/resolve-project-
|
|
64067
|
-
const
|
|
64901
|
+
//#region src/cli/utils/resolve-project-source-file-paths.ts
|
|
64902
|
+
const resolveProjectSourceFilePaths = (rootDirectory, projectDirectory, filePaths) => {
|
|
64068
64903
|
const relativeProjectDirectory = resolveProjectRelativeDirectory(rootDirectory, projectDirectory);
|
|
64069
64904
|
if (relativeProjectDirectory === null) return [];
|
|
64070
|
-
const
|
|
64071
|
-
if (relativeProjectDirectory.length === 0) return
|
|
64905
|
+
const sourceFilePaths = filterSourceFiles([...filePaths]);
|
|
64906
|
+
if (relativeProjectDirectory.length === 0) return sourceFilePaths;
|
|
64072
64907
|
const projectPrefix = `${relativeProjectDirectory}/`;
|
|
64073
|
-
return
|
|
64908
|
+
return sourceFilePaths.flatMap((filePath) => {
|
|
64074
64909
|
const normalizedFilePath = toForwardSlashes(filePath);
|
|
64075
64910
|
if (!normalizedFilePath.startsWith(projectPrefix)) return [];
|
|
64076
64911
|
const projectRelativePath = normalizedFilePath.slice(projectPrefix.length);
|
|
64077
64912
|
return projectRelativePath.length > 0 ? [projectRelativePath] : [];
|
|
64078
64913
|
});
|
|
64079
64914
|
};
|
|
64915
|
+
//#endregion
|
|
64916
|
+
//#region src/cli/utils/resolve-project-diff-include-paths.ts
|
|
64917
|
+
const resolveProjectDiffIncludePaths = (rootDirectory, projectDirectory, diffInfo) => {
|
|
64918
|
+
return resolveProjectSourceFilePaths(rootDirectory, projectDirectory, diffInfo.changedFiles);
|
|
64919
|
+
};
|
|
64080
64920
|
/**
|
|
64081
64921
|
* Re-keys repo-relative changed line ranges (from `getChangedLineRanges`) to
|
|
64082
64922
|
* the scanned project, mirroring `resolveProjectDiffIncludePaths`'s boundary
|
|
@@ -64456,7 +65296,7 @@ const maybeMigrateLegacyConfig = async (requestedDirectory, { isQuiet, isStaged
|
|
|
64456
65296
|
if (!(!isQuiet && !isStaged && process.stdout.isTTY === true && !isCiOrCodingAgentEnvironment())) return;
|
|
64457
65297
|
await runProjectMigrations(requestedDirectory);
|
|
64458
65298
|
};
|
|
64459
|
-
const inspectAction = async (directory, flags) => {
|
|
65299
|
+
const inspectAction = async (directory, flags, invocationCommand = "inspect") => {
|
|
64460
65300
|
const isScoreOnly = Boolean(flags.score);
|
|
64461
65301
|
const isJsonMode = Boolean(flags.json);
|
|
64462
65302
|
const isQuiet = isScoreOnly || isJsonMode;
|
|
@@ -64470,7 +65310,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
64470
65310
|
});
|
|
64471
65311
|
if (flags.jsonOut) recordCount(METRIC.jsonOutUsed, 1);
|
|
64472
65312
|
}
|
|
64473
|
-
recordCount(METRIC.cliInvoked, 1, { command:
|
|
65313
|
+
recordCount(METRIC.cliInvoked, 1, { command: invocationCommand });
|
|
64474
65314
|
try {
|
|
64475
65315
|
validateModeFlags(flags);
|
|
64476
65316
|
if (flags.staged) setJsonReportMode("staged");
|
|
@@ -64615,6 +65455,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
64615
65455
|
const isDiffMode = scope !== "full";
|
|
64616
65456
|
const comparisonBaseRef = isDiffMode && diffInfo && !diffInfo.isCurrentChanges ? diffInfo.baseSha ? await resolveMergeBaseRef(resolvedDirectory, diffInfo.baseSha) : diffInfo.diffBaseRef ?? await resolveMergeBaseRef(resolvedDirectory, diffInfo.baseBranch) : null;
|
|
64617
65457
|
const baselineRef = scope === "changed" ? comparisonBaseRef : null;
|
|
65458
|
+
const baselineDiffPlan = baselineRef === null ? null : await getBaselineDiffPlan(resolvedDirectory, baselineRef);
|
|
64618
65459
|
const linesBaseRef = diffInfo?.isCurrentChanges ? "HEAD" : comparisonBaseRef;
|
|
64619
65460
|
const changedLineRanges = scope === "lines" && diffInfo !== null && (diffInfo.isCurrentChanges || linesBaseRef !== null) && diffInfo !== null ? await getChangedLineRanges({
|
|
64620
65461
|
directory: resolvedDirectory,
|
|
@@ -64645,10 +65486,13 @@ const inspectAction = async (directory, flags) => {
|
|
|
64645
65486
|
const supplyChainEnabled = flags.supplyChain ?? projectConfig?.supplyChain?.enabled !== false;
|
|
64646
65487
|
let includePaths;
|
|
64647
65488
|
let supplyChainManifestChanged = false;
|
|
65489
|
+
const projectBaselineBaseFiles = baselineDiffPlan === null ? null : resolveProjectSourceFilePaths(resolvedDirectory, scanDirectory, baselineDiffPlan.baseFiles);
|
|
65490
|
+
const projectBaselineHeadFiles = baselineDiffPlan === null ? null : resolveProjectSourceFilePaths(resolvedDirectory, scanDirectory, baselineDiffPlan.headFiles);
|
|
64648
65491
|
if (isDiffMode) {
|
|
64649
65492
|
const changedSourceFiles = diffInfo === null ? [] : resolveProjectDiffIncludePaths(resolvedDirectory, scanDirectory, diffInfo);
|
|
64650
65493
|
supplyChainManifestChanged = supplyChainEnabled && diffInfo !== null && projectManifestChanged(resolvedDirectory, scanDirectory, diffInfo);
|
|
64651
|
-
|
|
65494
|
+
const hasBaselineOnlyFiles = (projectBaselineBaseFiles?.length ?? 0) > 0;
|
|
65495
|
+
if (changedSourceFiles.length === 0 && !supplyChainManifestChanged && !hasBaselineOnlyFiles) {
|
|
64652
65496
|
if (!isQuiet) {
|
|
64653
65497
|
cliLogger.dim(`No changed source files in ${scanDirectory}, skipping.`);
|
|
64654
65498
|
cliLogger.break();
|
|
@@ -64656,6 +65500,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
64656
65500
|
return null;
|
|
64657
65501
|
}
|
|
64658
65502
|
includePaths = [...changedSourceFiles];
|
|
65503
|
+
if (includePaths.length === 0 && hasBaselineOnlyFiles) includePaths.push(...projectBaselineBaseFiles ?? []);
|
|
64659
65504
|
if (supplyChainManifestChanged) includePaths.push("package.json");
|
|
64660
65505
|
}
|
|
64661
65506
|
if (!isQuiet && !isMultiProject) cliLogger.dim(" ");
|
|
@@ -64667,7 +65512,11 @@ const inspectAction = async (directory, flags) => {
|
|
|
64667
65512
|
configSourceDirectory: projectConfigSourceDirectory ?? void 0,
|
|
64668
65513
|
suppressRendering: isMultiProject,
|
|
64669
65514
|
concurrentScan: isMultiProject,
|
|
64670
|
-
baseline: baselineRef
|
|
65515
|
+
baseline: baselineRef !== null && projectBaselineBaseFiles !== null && projectBaselineHeadFiles !== null ? {
|
|
65516
|
+
ref: baselineRef,
|
|
65517
|
+
baseFiles: projectBaselineBaseFiles,
|
|
65518
|
+
headFiles: projectBaselineHeadFiles
|
|
65519
|
+
} : void 0,
|
|
64671
65520
|
changedLineRanges: scope === "lines" && changedLineRanges !== null ? resolveProjectChangedLineRanges(resolvedDirectory, scanDirectory, changedLineRanges) : void 0,
|
|
64672
65521
|
supplyChainManifestChanged
|
|
64673
65522
|
});
|
|
@@ -64773,6 +65622,13 @@ const inspectAction = async (directory, flags) => {
|
|
|
64773
65622
|
}
|
|
64774
65623
|
};
|
|
64775
65624
|
//#endregion
|
|
65625
|
+
//#region src/cli/commands/design.ts
|
|
65626
|
+
const designAction = async (directory, flags) => inspectAction(directory, {
|
|
65627
|
+
...flags,
|
|
65628
|
+
design: true,
|
|
65629
|
+
lint: flags.lint ?? true
|
|
65630
|
+
}, "design");
|
|
65631
|
+
//#endregion
|
|
64776
65632
|
//#region src/cli/commands/install.ts
|
|
64777
65633
|
const installAction = async (options, command) => {
|
|
64778
65634
|
recordCount(METRIC.cliInvoked, 1, { command: "install" });
|
|
@@ -65609,6 +66465,7 @@ const INSTALL_FLAG_SPEC = {
|
|
|
65609
66465
|
shortOptionsWithRequiredValues: new Set(["-c"])
|
|
65610
66466
|
};
|
|
65611
66467
|
const COMMAND_FLAG_SPECS = new Map([
|
|
66468
|
+
["design", ROOT_FLAG_SPEC],
|
|
65612
66469
|
["install", INSTALL_FLAG_SPEC],
|
|
65613
66470
|
["setup", INSTALL_FLAG_SPEC],
|
|
65614
66471
|
["version", {
|
|
@@ -65765,6 +66622,7 @@ ${formatExampleLines([
|
|
|
65765
66622
|
["react-doctor --scope changed --base main", "scan only new issues vs. main"],
|
|
65766
66623
|
["react-doctor --project modules/a,modules/b", "score each module separately (names or paths)"],
|
|
65767
66624
|
["react-doctor --staged", "scan staged files (pre-commit hook)"],
|
|
66625
|
+
["react-doctor design", "run the focused UI design audit"],
|
|
65768
66626
|
["react-doctor --category Security", "show only one diagnostic category"],
|
|
65769
66627
|
["react-doctor --blocking warning", "fail CI on warnings too (default: error)"],
|
|
65770
66628
|
["react-doctor --json > report.json", "write a machine-readable report"],
|
|
@@ -65798,6 +66656,20 @@ ${highlighter.dim("Managing CI:")}
|
|
|
65798
66656
|
${highlighter.dim("Learn more:")}
|
|
65799
66657
|
${highlighter.info(CANONICAL_GITHUB_URL)}
|
|
65800
66658
|
`;
|
|
66659
|
+
const renderDesignHelpEpilog = () => `
|
|
66660
|
+
${highlighter.dim("Examples:")}
|
|
66661
|
+
${formatExampleLines([
|
|
66662
|
+
["react-doctor design", "audit UI design in the current project"],
|
|
66663
|
+
["react-doctor design ./apps/web", "audit one application"],
|
|
66664
|
+
["react-doctor design --verbose", "show every design finding"],
|
|
66665
|
+
["react-doctor design --json", "write a design-only JSON report"]
|
|
66666
|
+
])}
|
|
66667
|
+
|
|
66668
|
+
${highlighter.dim("Scope:")}
|
|
66669
|
+
Runs only rules tagged ${highlighter.info("design")}, including focused rules that stay opt-in during a general health scan.
|
|
66670
|
+
Dead-code, supply-chain, external lint-config, custom-plugin, and health-score passes are skipped.
|
|
66671
|
+
Standard scan flags such as ${highlighter.info("--scope")}, ${highlighter.info("--project")}, ${highlighter.info("--verbose")}, and ${highlighter.info("--json")} still work.
|
|
66672
|
+
`;
|
|
65801
66673
|
const renderCiHelpEpilog = () => `
|
|
65802
66674
|
${highlighter.dim("Examples:")}
|
|
65803
66675
|
${formatExampleLines([
|
|
@@ -65819,6 +66691,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
65819
66691
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
65820
66692
|
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much supported JS/TS source to scan/report: full (default), files, changed (only new issues vs base), or lines (issues whose source spans touch changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").option("--include-untracked", "with --scope files/changed/lines, also scan ordinary untracked files (respects .gitignore)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
65821
66693
|
program.action(inspectAction);
|
|
66694
|
+
program.command("design [directory]").description("Run only the focused UI design diagnostics").addHelpText("after", renderDesignHelpEpilog).action((directory, _options, command) => designAction(directory ?? ".", command.optsWithGlobals()));
|
|
65822
66695
|
program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action((location, options) => whyAction(location, options));
|
|
65823
66696
|
program.command("install").alias("setup").description("Install the react-doctor skill into your coding agents and optional git hook").option("-y, --yes", "skip prompts, install for all detected agents").option("--dry-run", "show what would be installed without writing files").option("--agent-hooks", "install native non-blocking agent hooks for Claude Code and Cursor").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderInstallHelpEpilog).action(installAction);
|
|
65824
66697
|
const providerOption = ["--provider <name>", "ci provider: github-actions or gitlab-ci (auto-detected by default)"];
|
|
@@ -65867,4 +66740,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
65867
66740
|
export {};
|
|
65868
66741
|
|
|
65869
66742
|
//# sourceMappingURL=cli.js.map
|
|
65870
|
-
//# debugId=
|
|
66743
|
+
//# debugId=a9af63ed-7b82-56d3-8549-274ab7f23579
|