react-doctor 0.8.1-dev.7818922 → 0.8.1-dev.7eca0ee
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +749 -92
- package/dist/index.d.ts +33 -15
- package/dist/index.js +254 -23
- package/dist/lsp.js +254 -23
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="81bdc95e-12f2-565f-a05c-7b8a60a98f81")}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";
|
|
@@ -37317,6 +37317,10 @@ const listWorkspacePackages = (rootDirectory) => {
|
|
|
37317
37317
|
return packages;
|
|
37318
37318
|
};
|
|
37319
37319
|
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
37320
|
+
const MOBX_REACT_PACKAGE_NAME = "mobx-react";
|
|
37321
|
+
const MOBX_REACT_LITE_PACKAGE_NAME = "mobx-react-lite";
|
|
37322
|
+
const MOBX_STATE_TREE_PACKAGE_NAME = "mobx-state-tree";
|
|
37323
|
+
const MOBX_REACT_OBSERVER_PACKAGE_NAME = "mobx-react-observer";
|
|
37320
37324
|
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
37321
37325
|
const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
|
|
37322
37326
|
const dependencyDeclaration = getDependencyDeclaration({
|
|
@@ -37335,7 +37339,18 @@ const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
|
|
|
37335
37339
|
if (nextMajor === null) return false;
|
|
37336
37340
|
return nextMajor < currentMajor;
|
|
37337
37341
|
};
|
|
37338
|
-
const
|
|
37342
|
+
const shouldReplaceSupportedDependencyFact = (currentFact, nextFact, latestSupportedMajor) => {
|
|
37343
|
+
if (currentFact.version === null) return true;
|
|
37344
|
+
if (nextFact.version === null) return false;
|
|
37345
|
+
const currentMajor = getLowestDependencyMajor(currentFact.version);
|
|
37346
|
+
const nextMajor = getLowestDependencyMajor(nextFact.version);
|
|
37347
|
+
if (currentMajor === null) return false;
|
|
37348
|
+
if (nextMajor === null) return true;
|
|
37349
|
+
if (currentMajor > latestSupportedMajor) return false;
|
|
37350
|
+
if (nextMajor > latestSupportedMajor) return true;
|
|
37351
|
+
return nextMajor < currentMajor;
|
|
37352
|
+
};
|
|
37353
|
+
const evaluateManifestFacts = (facts, packageJson, directory, rootDirectory, rootPackageJson) => {
|
|
37339
37354
|
if (facts.expo.version === null) {
|
|
37340
37355
|
const spec = getDependencySpec(packageJson, "expo");
|
|
37341
37356
|
if (spec !== null) facts.expo = {
|
|
@@ -37357,13 +37372,65 @@ const evaluateManifestFacts = (facts, packageJson, directory) => {
|
|
|
37357
37372
|
sourceDirectory: directory
|
|
37358
37373
|
};
|
|
37359
37374
|
}
|
|
37375
|
+
const mobxSpec = getDependencySpec(packageJson, "mobx");
|
|
37376
|
+
if (mobxSpec !== null) {
|
|
37377
|
+
const nextMobxFact = {
|
|
37378
|
+
version: resolveCatalogBackedDependencyVersion({
|
|
37379
|
+
rootDirectory: directory,
|
|
37380
|
+
rootPackageJson: packageJson,
|
|
37381
|
+
packageName: "mobx",
|
|
37382
|
+
version: mobxSpec
|
|
37383
|
+
}),
|
|
37384
|
+
sourceDirectory: directory
|
|
37385
|
+
};
|
|
37386
|
+
if (shouldReplaceSupportedDependencyFact(facts.mobx, nextMobxFact, 6)) facts.mobx = nextMobxFact;
|
|
37387
|
+
}
|
|
37388
|
+
facts.hasMobxReact = facts.hasMobxReact || getDependencySpec(packageJson, MOBX_REACT_PACKAGE_NAME) !== null;
|
|
37389
|
+
facts.hasMobxReactLite = facts.hasMobxReactLite || getDependencySpec(packageJson, MOBX_REACT_LITE_PACKAGE_NAME) !== null;
|
|
37390
|
+
facts.hasMobxStateTree = facts.hasMobxStateTree || getDependencySpec(packageJson, MOBX_STATE_TREE_PACKAGE_NAME) !== null;
|
|
37391
|
+
facts.hasMobxReactObserver = facts.hasMobxReactObserver || getDependencySpec(packageJson, MOBX_REACT_OBSERVER_PACKAGE_NAME) !== null;
|
|
37392
|
+
const zustandSpec = getDependencySpec(packageJson, "zustand");
|
|
37393
|
+
if (zustandSpec !== null) {
|
|
37394
|
+
const nextZustandFact = {
|
|
37395
|
+
version: resolveCatalogBackedDependencyVersion({
|
|
37396
|
+
rootDirectory: directory,
|
|
37397
|
+
rootPackageJson: packageJson,
|
|
37398
|
+
packageName: "zustand",
|
|
37399
|
+
version: zustandSpec
|
|
37400
|
+
}),
|
|
37401
|
+
sourceDirectory: directory
|
|
37402
|
+
};
|
|
37403
|
+
if (shouldReplaceSupportedDependencyFact(facts.zustand, nextZustandFact, 5)) facts.zustand = nextZustandFact;
|
|
37404
|
+
}
|
|
37360
37405
|
if (facts.reanimatedVersion === null) {
|
|
37361
37406
|
const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
|
|
37362
37407
|
if (spec !== null) facts.reanimatedVersion = spec;
|
|
37363
37408
|
}
|
|
37409
|
+
if (facts.valtioVersion === null) {
|
|
37410
|
+
const spec = getDependencySpec(packageJson, "valtio");
|
|
37411
|
+
if (spec !== null) facts.valtioVersion = spec;
|
|
37412
|
+
}
|
|
37364
37413
|
facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
|
|
37365
37414
|
facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
|
|
37366
37415
|
facts.hasSsrDependency = facts.hasSsrDependency || isPackageJsonSsrAware(packageJson);
|
|
37416
|
+
const remotionSpec = getDependencySpec(packageJson, "remotion");
|
|
37417
|
+
if (remotionSpec !== null) {
|
|
37418
|
+
facts.hasRemotionDependency = true;
|
|
37419
|
+
const resolvedRemotionVersion = resolveCatalogBackedDependencyVersion({
|
|
37420
|
+
rootDirectory,
|
|
37421
|
+
rootPackageJson,
|
|
37422
|
+
packageName: "remotion",
|
|
37423
|
+
version: remotionSpec
|
|
37424
|
+
});
|
|
37425
|
+
const remotionMajorVersion = resolvedRemotionVersion === null ? null : getLowestDependencyMajor(resolvedRemotionVersion);
|
|
37426
|
+
if (remotionMajorVersion === null) {
|
|
37427
|
+
facts.hasUnknownRemotionVersion = true;
|
|
37428
|
+
facts.remotionVersion = null;
|
|
37429
|
+
} else if (!facts.hasUnknownRemotionVersion) {
|
|
37430
|
+
const currentRemotionMajorVersion = facts.remotionVersion === null ? null : getLowestDependencyMajor(facts.remotionVersion);
|
|
37431
|
+
if (currentRemotionMajorVersion === null || remotionMajorVersion < currentRemotionMajorVersion) facts.remotionVersion = resolvedRemotionVersion;
|
|
37432
|
+
}
|
|
37433
|
+
}
|
|
37367
37434
|
};
|
|
37368
37435
|
const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
|
|
37369
37436
|
const facts = {
|
|
@@ -37383,12 +37450,28 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
|
|
|
37383
37450
|
version: null,
|
|
37384
37451
|
sourceDirectory: null
|
|
37385
37452
|
},
|
|
37453
|
+
valtioVersion: null,
|
|
37454
|
+
mobx: {
|
|
37455
|
+
version: null,
|
|
37456
|
+
sourceDirectory: null
|
|
37457
|
+
},
|
|
37458
|
+
hasMobxReact: false,
|
|
37459
|
+
hasMobxReactLite: false,
|
|
37460
|
+
hasMobxStateTree: false,
|
|
37461
|
+
hasMobxReactObserver: false,
|
|
37462
|
+
zustand: {
|
|
37463
|
+
version: null,
|
|
37464
|
+
sourceDirectory: null
|
|
37465
|
+
},
|
|
37386
37466
|
hasReactNativeAwarePackage: false,
|
|
37387
37467
|
hasReanimatedAwarePackage: false,
|
|
37388
37468
|
hasSsrDependency: false,
|
|
37469
|
+
hasRemotionDependency: false,
|
|
37470
|
+
hasUnknownRemotionVersion: false,
|
|
37471
|
+
remotionVersion: null,
|
|
37389
37472
|
reanimatedVersion: null
|
|
37390
37473
|
};
|
|
37391
|
-
evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
|
|
37474
|
+
evaluateManifestFacts(facts, rootPackageJson, rootDirectory, rootDirectory, rootPackageJson);
|
|
37392
37475
|
let isReactGroupSettled = !collectReactGroup;
|
|
37393
37476
|
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
37394
37477
|
for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
|
|
@@ -37397,7 +37480,7 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
|
|
|
37397
37480
|
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
37398
37481
|
visitedDirectories.add(workspaceDirectory);
|
|
37399
37482
|
const workspacePackageJson = readPackageJson$1(Path.join(workspaceDirectory, "package.json"));
|
|
37400
|
-
evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
|
|
37483
|
+
evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory, rootDirectory, rootPackageJson);
|
|
37401
37484
|
const info = extractDependencyInfo(workspacePackageJson);
|
|
37402
37485
|
if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
|
|
37403
37486
|
if (isReactGroupSettled) continue;
|
|
@@ -37618,11 +37701,24 @@ const discoverProjectWithoutPackageJson = (directory) => {
|
|
|
37618
37701
|
tailwindVersion: null,
|
|
37619
37702
|
zodVersion: null,
|
|
37620
37703
|
zodMajorVersion: null,
|
|
37704
|
+
mobxVersion: null,
|
|
37705
|
+
mobxMajorVersion: null,
|
|
37706
|
+
hasMobxReact: false,
|
|
37707
|
+
hasMobxReactLite: false,
|
|
37708
|
+
hasMobxStateTree: false,
|
|
37709
|
+
hasMobxReactObserver: false,
|
|
37710
|
+
zustandVersion: null,
|
|
37711
|
+
zustandMajorVersion: null,
|
|
37621
37712
|
framework: "unknown",
|
|
37622
37713
|
hasTypeScript: hasOwnTsConfig,
|
|
37623
37714
|
hasReactCompiler: false,
|
|
37624
37715
|
hasReactCompilerLintPlugin: false,
|
|
37625
37716
|
hasTanStackQuery: false,
|
|
37717
|
+
valtioVersion: null,
|
|
37718
|
+
valtioMajorVersion: null,
|
|
37719
|
+
hasRemotion: false,
|
|
37720
|
+
remotionVersion: null,
|
|
37721
|
+
remotionMajorVersion: null,
|
|
37626
37722
|
hasSsrDependency: false,
|
|
37627
37723
|
preactVersion: null,
|
|
37628
37724
|
preactMajorVersion: null,
|
|
@@ -37721,6 +37817,12 @@ const discoverProject = (directory) => {
|
|
|
37721
37817
|
packageName: SHOPIFY_FLASH_LIST_PACKAGE_NAME,
|
|
37722
37818
|
version: workspaceFacts.shopifyFlashList.version
|
|
37723
37819
|
}) : null;
|
|
37820
|
+
const zustandVersion = resolveCatalogBackedDependencyVersion({
|
|
37821
|
+
rootDirectory: directory,
|
|
37822
|
+
rootPackageJson: packageJson,
|
|
37823
|
+
packageName: "zustand",
|
|
37824
|
+
version: workspaceFacts.zustand.version
|
|
37825
|
+
});
|
|
37724
37826
|
const hasReanimated = hasReactNativeWorkspace && workspaceFacts.hasReanimatedAwarePackage;
|
|
37725
37827
|
const reanimatedVersion = hasReanimated ? workspaceFacts.reanimatedVersion : null;
|
|
37726
37828
|
const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
|
|
@@ -37729,7 +37831,20 @@ const discoverProject = (directory) => {
|
|
|
37729
37831
|
packageName: "next",
|
|
37730
37832
|
version: workspaceFacts.next.version
|
|
37731
37833
|
}) : null;
|
|
37834
|
+
const valtioVersion = resolveCatalogBackedDependencyVersion({
|
|
37835
|
+
rootDirectory: directory,
|
|
37836
|
+
rootPackageJson: packageJson,
|
|
37837
|
+
packageName: "valtio",
|
|
37838
|
+
version: workspaceFacts.valtioVersion
|
|
37839
|
+
});
|
|
37840
|
+
const mobxVersion = resolveCatalogBackedDependencyVersion({
|
|
37841
|
+
rootDirectory: directory,
|
|
37842
|
+
rootPackageJson: packageJson,
|
|
37843
|
+
packageName: "mobx",
|
|
37844
|
+
version: workspaceFacts.mobx.version
|
|
37845
|
+
});
|
|
37732
37846
|
const preactVersion = getPreactVersion(packageJson);
|
|
37847
|
+
const remotionVersion = workspaceFacts.remotionVersion;
|
|
37733
37848
|
const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
|
|
37734
37849
|
const projectInfo = {
|
|
37735
37850
|
rootDirectory: directory,
|
|
@@ -37739,11 +37854,24 @@ const discoverProject = (directory) => {
|
|
|
37739
37854
|
tailwindVersion,
|
|
37740
37855
|
zodVersion,
|
|
37741
37856
|
zodMajorVersion: zodVersion === null ? null : getLowestDependencyMajor(zodVersion),
|
|
37857
|
+
mobxVersion,
|
|
37858
|
+
mobxMajorVersion: mobxVersion === null ? null : getLowestDependencyMajor(mobxVersion),
|
|
37859
|
+
hasMobxReact: workspaceFacts.hasMobxReact,
|
|
37860
|
+
hasMobxReactLite: workspaceFacts.hasMobxReactLite,
|
|
37861
|
+
hasMobxStateTree: workspaceFacts.hasMobxStateTree,
|
|
37862
|
+
hasMobxReactObserver: workspaceFacts.hasMobxReactObserver,
|
|
37863
|
+
zustandVersion,
|
|
37864
|
+
zustandMajorVersion: zustandVersion === null ? null : getLowestDependencyMajor(zustandVersion),
|
|
37742
37865
|
framework,
|
|
37743
37866
|
hasTypeScript,
|
|
37744
37867
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
37745
37868
|
hasReactCompilerLintPlugin: detectReactCompilerLintPlugin(directory, packageJson),
|
|
37746
37869
|
hasTanStackQuery: hasTanStackQuery(packageJson),
|
|
37870
|
+
valtioVersion,
|
|
37871
|
+
valtioMajorVersion: valtioVersion === null ? null : getLowestDependencyMajor(valtioVersion),
|
|
37872
|
+
hasRemotion: workspaceFacts.hasRemotionDependency,
|
|
37873
|
+
remotionVersion,
|
|
37874
|
+
remotionMajorVersion: remotionVersion === null ? null : getLowestDependencyMajor(remotionVersion),
|
|
37747
37875
|
hasSsrDependency: workspaceFacts.hasSsrDependency,
|
|
37748
37876
|
preactVersion,
|
|
37749
37877
|
preactMajorVersion: parseReactMajor(preactVersion),
|
|
@@ -37977,7 +38105,6 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
|
37977
38105
|
"react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
|
|
37978
38106
|
"react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
|
|
37979
38107
|
"react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
|
|
37980
|
-
"react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
|
|
37981
38108
|
"react/jsx-no-undef": "react-doctor/jsx-no-undef",
|
|
37982
38109
|
"react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
|
|
37983
38110
|
"react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
|
|
@@ -40299,9 +40426,25 @@ const BABEL_CONFIG_FILE_NAMES = [
|
|
|
40299
40426
|
".babelrc.json"
|
|
40300
40427
|
];
|
|
40301
40428
|
const LEGACY_PRESET_SPEC = "module:metro-react-native-babel-preset";
|
|
40429
|
+
const LEGACY_PRESET_PACKAGE = "metro-react-native-babel-preset";
|
|
40430
|
+
const REACT_NATIVE_PACKAGE = "react-native";
|
|
40431
|
+
const PRESET_RENAME_MINIMUM_REACT_NATIVE_VERSION = "0.73.0";
|
|
40432
|
+
const LEGACY_PRESET_REFERENCE = new RegExp(`['"]${LEGACY_PRESET_SPEC}['"]`);
|
|
40302
40433
|
const MODERN_PRESET_REFERENCE = new RegExp(`['"]module:@react-native/babel-preset['"]`);
|
|
40303
40434
|
const ENABLE_BABEL_RUNTIME_VERSION = /enableBabelRuntime["']?\s*:\s*['"]/;
|
|
40435
|
+
const isReactNativeVersionAtLeastPresetRename = (rootDirectory, packageJson) => {
|
|
40436
|
+
const installedReactNativeManifestPath = Path.join(rootDirectory, "node_modules", REACT_NATIVE_PACKAGE, "package.json");
|
|
40437
|
+
const reactNativeVersionSpec = (isFile(installedReactNativeManifestPath) ? readPackageJson$1(installedReactNativeManifestPath).version : void 0) ?? getDependencySpec(packageJson, REACT_NATIVE_PACKAGE);
|
|
40438
|
+
if (reactNativeVersionSpec === null || reactNativeVersionSpec === void 0) return false;
|
|
40439
|
+
const normalizedVersionRange = import_semver.validRange(reactNativeVersionSpec);
|
|
40440
|
+
if (normalizedVersionRange === null) return false;
|
|
40441
|
+
const minimumVersion = import_semver.minVersion(normalizedVersionRange);
|
|
40442
|
+
return minimumVersion !== null && import_semver.gte(minimumVersion, PRESET_RENAME_MINIMUM_REACT_NATIVE_VERSION);
|
|
40443
|
+
};
|
|
40444
|
+
const canResolveLegacyPreset = (rootDirectory, packageJson) => getDependencySpec(packageJson, LEGACY_PRESET_PACKAGE) !== null || isFile(Path.join(rootDirectory, "node_modules", LEGACY_PRESET_PACKAGE, "package.json"));
|
|
40304
40445
|
const checkReactNativeMetroBabelPreset = (rootDirectory) => {
|
|
40446
|
+
const packageJson = readPackageJson$1(Path.join(rootDirectory, "package.json"));
|
|
40447
|
+
const shouldReportLegacyPreset = isReactNativeVersionAtLeastPresetRename(rootDirectory, packageJson) && !canResolveLegacyPreset(rootDirectory, packageJson);
|
|
40305
40448
|
for (const fileName of BABEL_CONFIG_FILE_NAMES) {
|
|
40306
40449
|
const filePath = Path.join(rootDirectory, fileName);
|
|
40307
40450
|
if (!isFile(filePath)) continue;
|
|
@@ -40311,7 +40454,7 @@ const checkReactNativeMetroBabelPreset = (rootDirectory) => {
|
|
|
40311
40454
|
} catch {
|
|
40312
40455
|
continue;
|
|
40313
40456
|
}
|
|
40314
|
-
if (
|
|
40457
|
+
if (shouldReportLegacyPreset && LEGACY_PRESET_REFERENCE.test(contents)) return [buildReactNativeDiagnostic({
|
|
40315
40458
|
filePath: fileName,
|
|
40316
40459
|
rule: "rn-no-metro-babel-preset",
|
|
40317
40460
|
severity: "error",
|
|
@@ -40334,12 +40477,6 @@ const checkReactNativeProject = (rootDirectory, project) => {
|
|
|
40334
40477
|
return [...checkReactNativeMetroBabelPreset(rootDirectory), ...checkReactNativeLibraryDependencies(rootDirectory)];
|
|
40335
40478
|
};
|
|
40336
40479
|
const RULE_KEY = "no-vulnerable-react-server-components";
|
|
40337
|
-
const DEPENDENCY_SECTIONS = [
|
|
40338
|
-
"dependencies",
|
|
40339
|
-
"devDependencies",
|
|
40340
|
-
"peerDependencies",
|
|
40341
|
-
"optionalDependencies"
|
|
40342
|
-
];
|
|
40343
40480
|
const REACT_RSC_ADVISORIES_BY_MINOR = {
|
|
40344
40481
|
"19.0": {
|
|
40345
40482
|
rceFixedVersion: "19.0.1",
|
|
@@ -40386,20 +40523,13 @@ const enumerateWorkspaceDirectories = (workspaceRoot) => {
|
|
|
40386
40523
|
for (const pattern of patterns) for (const directory of resolveWorkspaceDirectories(workspaceRoot, pattern)) directories.add(directory);
|
|
40387
40524
|
return [...directories];
|
|
40388
40525
|
};
|
|
40389
|
-
const readDeclaredSpec = (packageJson, packageName) => {
|
|
40390
|
-
for (const section of DEPENDENCY_SECTIONS) {
|
|
40391
|
-
const spec = packageJson[section]?.[packageName];
|
|
40392
|
-
if (typeof spec === "string") return spec;
|
|
40393
|
-
}
|
|
40394
|
-
return null;
|
|
40395
|
-
};
|
|
40396
40526
|
const resolveVersionInDirectory = (directory, packageName, declaredSpecOverride) => {
|
|
40397
40527
|
const manifestPath = Path.join(directory, "node_modules", packageName, "package.json");
|
|
40398
40528
|
if (isFile(manifestPath)) {
|
|
40399
40529
|
const installedVersion = import_semver.valid(readPackageJson$1(manifestPath).version ?? null);
|
|
40400
40530
|
if (installedVersion !== null) return installedVersion;
|
|
40401
40531
|
}
|
|
40402
|
-
const candidateSpecs = [
|
|
40532
|
+
const candidateSpecs = [getDependencySpec(readPackageJson$1(Path.join(directory, "package.json")), packageName), declaredSpecOverride];
|
|
40403
40533
|
for (const spec of candidateSpecs) {
|
|
40404
40534
|
const pinnedVersion = spec === null ? null : import_semver.valid(spec);
|
|
40405
40535
|
if (pinnedVersion !== null) return pinnedVersion;
|
|
@@ -41079,9 +41209,24 @@ const buildCapabilities = (project) => {
|
|
|
41079
41209
|
})) capabilities.add("tailwind:3.4");
|
|
41080
41210
|
if (project.zodVersion !== null) capabilities.add("zod");
|
|
41081
41211
|
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
41212
|
+
if (project.mobxVersion !== void 0 && project.mobxVersion !== null || project.hasMobxReact === true || project.hasMobxReactLite === true || project.hasMobxStateTree === true || project.hasMobxReactObserver === true) capabilities.add("mobx");
|
|
41213
|
+
if (project.hasMobxReact === true) capabilities.add("mobx-react");
|
|
41214
|
+
if (project.hasMobxReactLite === true) capabilities.add("mobx-react-lite");
|
|
41215
|
+
if (project.hasMobxReact === true || project.hasMobxReactLite === true) capabilities.add("mobx-react-binding");
|
|
41216
|
+
if (project.hasMobxStateTree === true) capabilities.add("mobx-state-tree");
|
|
41217
|
+
if (project.hasMobxReactObserver === true) capabilities.add("mobx-react-observer");
|
|
41218
|
+
if (project.mobxMajorVersion !== void 0 && project.mobxMajorVersion !== null && project.mobxMajorVersion >= 4 && project.mobxMajorVersion <= 6) addMajorLadder(capabilities, "mobx", project.mobxMajorVersion, 4, 6);
|
|
41219
|
+
if (project.zustandVersion !== void 0 && project.zustandVersion !== null) capabilities.add("zustand");
|
|
41220
|
+
if (project.zustandMajorVersion !== void 0 && project.zustandMajorVersion !== null && project.zustandMajorVersion >= 1 && project.zustandMajorVersion <= 5) addMajorLadder(capabilities, "zustand", project.zustandMajorVersion, 1, 5);
|
|
41082
41221
|
if (project.isPreES2023Target) capabilities.add("pre-es2023");
|
|
41083
41222
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
41084
41223
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
41224
|
+
if (project.valtioVersion !== null) capabilities.add("valtio");
|
|
41225
|
+
addMajorLadder(capabilities, "valtio", project.valtioMajorVersion, 1, 10);
|
|
41226
|
+
if (project.hasRemotion) {
|
|
41227
|
+
capabilities.add("remotion");
|
|
41228
|
+
addMajorLadder(capabilities, "remotion", project.remotionMajorVersion ?? null, 4, 30);
|
|
41229
|
+
}
|
|
41085
41230
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
41086
41231
|
if (project.preactVersion !== null) capabilities.add("preact");
|
|
41087
41232
|
addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
|
|
@@ -42334,6 +42479,35 @@ const parseGithubViewerPermission = (stdout) => {
|
|
|
42334
42479
|
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
42335
42480
|
};
|
|
42336
42481
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
42482
|
+
const parseBaselineDiffPlan = (value) => {
|
|
42483
|
+
const entries = splitNullSeparated(value);
|
|
42484
|
+
const baseFiles = /* @__PURE__ */ new Set();
|
|
42485
|
+
const headFiles = /* @__PURE__ */ new Set();
|
|
42486
|
+
for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 2) {
|
|
42487
|
+
const status = entries[entryIndex];
|
|
42488
|
+
const filePath = entries[entryIndex + 1];
|
|
42489
|
+
if (status === void 0 || filePath === void 0 || status.length !== 1) return null;
|
|
42490
|
+
if (status === "A") {
|
|
42491
|
+
headFiles.add(filePath);
|
|
42492
|
+
continue;
|
|
42493
|
+
}
|
|
42494
|
+
if (status === "D") {
|
|
42495
|
+
baseFiles.add(filePath);
|
|
42496
|
+
continue;
|
|
42497
|
+
}
|
|
42498
|
+
if (status === "M" || status === "T") {
|
|
42499
|
+
baseFiles.add(filePath);
|
|
42500
|
+
headFiles.add(filePath);
|
|
42501
|
+
continue;
|
|
42502
|
+
}
|
|
42503
|
+
return null;
|
|
42504
|
+
}
|
|
42505
|
+
return {
|
|
42506
|
+
baseFiles: [...baseFiles],
|
|
42507
|
+
headFiles: [...headFiles],
|
|
42508
|
+
untrackedFiles: []
|
|
42509
|
+
};
|
|
42510
|
+
};
|
|
42337
42511
|
const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
|
|
42338
42512
|
/**
|
|
42339
42513
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
@@ -42536,6 +42710,45 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42536
42710
|
githubViewerPermission,
|
|
42537
42711
|
branchExists,
|
|
42538
42712
|
mergeBase,
|
|
42713
|
+
baselineDiffPlan: (input) => {
|
|
42714
|
+
if (!isSafeGitRevision(input.ref)) return succeed$2(null);
|
|
42715
|
+
return gen(function* () {
|
|
42716
|
+
const unmerged = yield* runGit(input.directory, [
|
|
42717
|
+
"diff",
|
|
42718
|
+
"--no-ext-diff",
|
|
42719
|
+
"-z",
|
|
42720
|
+
"--name-only",
|
|
42721
|
+
"--diff-filter=U",
|
|
42722
|
+
"--relative"
|
|
42723
|
+
]);
|
|
42724
|
+
if (unmerged.status !== 0 || unmerged.stdout.length > 0) return null;
|
|
42725
|
+
const result = yield* runGit(input.directory, [
|
|
42726
|
+
"diff",
|
|
42727
|
+
"--no-ext-diff",
|
|
42728
|
+
"--no-textconv",
|
|
42729
|
+
"--no-renames",
|
|
42730
|
+
"-z",
|
|
42731
|
+
"--name-status",
|
|
42732
|
+
"--relative",
|
|
42733
|
+
input.ref
|
|
42734
|
+
]);
|
|
42735
|
+
if (result.status !== 0) return null;
|
|
42736
|
+
const plan = parseBaselineDiffPlan(result.stdout);
|
|
42737
|
+
if (plan === null) return null;
|
|
42738
|
+
const untracked = yield* runGit(input.directory, [
|
|
42739
|
+
"ls-files",
|
|
42740
|
+
"--others",
|
|
42741
|
+
"--exclude-standard",
|
|
42742
|
+
"-z"
|
|
42743
|
+
]);
|
|
42744
|
+
if (untracked.status !== 0) return null;
|
|
42745
|
+
return {
|
|
42746
|
+
baseFiles: plan.baseFiles,
|
|
42747
|
+
headFiles: plan.headFiles,
|
|
42748
|
+
untrackedFiles: splitNullSeparated(untracked.stdout)
|
|
42749
|
+
};
|
|
42750
|
+
}).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.baselineDiffPlan"));
|
|
42751
|
+
},
|
|
42539
42752
|
diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
|
|
42540
42753
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
42541
42754
|
if (explicitBaseBranch !== void 0) {
|
|
@@ -42685,6 +42898,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42685
42898
|
githubViewerPermission: () => succeed$2(snapshot.githubViewerPermission ?? null),
|
|
42686
42899
|
branchExists: (_directory, branch) => succeed$2(snapshot.branchExists?.get(branch) ?? false),
|
|
42687
42900
|
mergeBase: ({ ref }) => succeed$2(snapshot.mergeBase?.get(ref) ?? null),
|
|
42901
|
+
baselineDiffPlan: () => succeed$2(snapshot.baselineDiffPlan ?? null),
|
|
42688
42902
|
diffSelection: () => succeed$2(snapshot.diffSelection ?? null),
|
|
42689
42903
|
stagedFilePaths: () => succeed$2(snapshot.stagedFiles ?? []),
|
|
42690
42904
|
showStagedContent: (_directory, relativePath) => succeed$2(snapshot.stagedContent?.get(relativePath) ?? null),
|
|
@@ -44022,12 +44236,17 @@ const REACT_ERROR_BOUNDARY_MESSAGE = "This try/catch cannot catch errors thrown
|
|
|
44022
44236
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
44023
44237
|
const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
|
|
44024
44238
|
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`.";
|
|
44239
|
+
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`.";
|
|
44025
44240
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
44026
44241
|
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
44027
44242
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
44028
44243
|
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
44029
44244
|
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
44030
44245
|
};
|
|
44246
|
+
const buildSetStateInEffectMessage = (reasonSummary) => {
|
|
44247
|
+
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
44248
|
+
return `This synchronous effect update causes an extra render${normalizedSummary ? `: ${normalizedSummary}` : ""}. ${SET_STATE_IN_EFFECT_ACTION}`;
|
|
44249
|
+
};
|
|
44031
44250
|
const PLUGIN_CATEGORY_MAP = {
|
|
44032
44251
|
react: "Bugs",
|
|
44033
44252
|
"react-hooks": "Bugs",
|
|
@@ -44085,6 +44304,10 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
44085
44304
|
message: REACT_ERROR_BOUNDARY_MESSAGE,
|
|
44086
44305
|
help: reasonDetail || help
|
|
44087
44306
|
};
|
|
44307
|
+
if (rule === "set-state-in-effect") return {
|
|
44308
|
+
message: buildSetStateInEffectMessage(reasonSummary.trim()),
|
|
44309
|
+
help: reasonDetail || help
|
|
44310
|
+
};
|
|
44088
44311
|
return {
|
|
44089
44312
|
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
44090
44313
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
@@ -46421,12 +46644,19 @@ const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
|
|
|
46421
46644
|
*/
|
|
46422
46645
|
const materializeSourceTree = (input) => gen(function* () {
|
|
46423
46646
|
const materializedFiles = [];
|
|
46647
|
+
const unmaterializedFiles = [];
|
|
46424
46648
|
const resolvedTempDirectory = path.resolve(input.tempDirectory);
|
|
46425
46649
|
for (const relativePath of input.files) {
|
|
46426
46650
|
const content = yield* input.readContent(relativePath).pipe(orElseSucceed(() => null));
|
|
46427
|
-
if (content === null)
|
|
46651
|
+
if (content === null) {
|
|
46652
|
+
unmaterializedFiles.push(relativePath);
|
|
46653
|
+
continue;
|
|
46654
|
+
}
|
|
46428
46655
|
const candidateTargetPath = path.resolve(resolvedTempDirectory, relativePath);
|
|
46429
|
-
if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory))
|
|
46656
|
+
if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory)) {
|
|
46657
|
+
unmaterializedFiles.push(relativePath);
|
|
46658
|
+
continue;
|
|
46659
|
+
}
|
|
46430
46660
|
yield* sync(() => {
|
|
46431
46661
|
fs.mkdirSync(path.dirname(candidateTargetPath), { recursive: true });
|
|
46432
46662
|
fs.writeFileSync(candidateTargetPath, content);
|
|
@@ -46443,6 +46673,7 @@ const materializeSourceTree = (input) => gen(function* () {
|
|
|
46443
46673
|
return {
|
|
46444
46674
|
tempDirectory: input.tempDirectory,
|
|
46445
46675
|
materializedFiles,
|
|
46676
|
+
unmaterializedFiles,
|
|
46446
46677
|
cleanup: () => {
|
|
46447
46678
|
try {
|
|
46448
46679
|
fs.rmSync(input.tempDirectory, {
|
|
@@ -46712,54 +46943,80 @@ const buildSkippedChecks = (input) => {
|
|
|
46712
46943
|
skippedCheckReasons
|
|
46713
46944
|
};
|
|
46714
46945
|
};
|
|
46715
|
-
const
|
|
46946
|
+
const fingerprintText = (text) => createHash("sha256").update(text).digest("hex");
|
|
46947
|
+
const normalizeEvidence = (evidence) => evidence.replace(/\s+/g, " ").trim();
|
|
46948
|
+
const getDiagnosticMatchKeys = (diagnostic, evidence) => {
|
|
46716
46949
|
const ruleKey = `${diagnostic.plugin}/${diagnostic.rule}`;
|
|
46717
|
-
const
|
|
46718
|
-
|
|
46719
|
-
};
|
|
46720
|
-
|
|
46721
|
-
|
|
46722
|
-
|
|
46723
|
-
|
|
46724
|
-
|
|
46725
|
-
|
|
46726
|
-
|
|
46727
|
-
|
|
46728
|
-
|
|
46729
|
-
|
|
46730
|
-
|
|
46731
|
-
|
|
46732
|
-
|
|
46733
|
-
|
|
46734
|
-
|
|
46735
|
-
|
|
46736
|
-
|
|
46737
|
-
|
|
46738
|
-
|
|
46739
|
-
|
|
46740
|
-
|
|
46741
|
-
|
|
46742
|
-
|
|
46743
|
-
*
|
|
46950
|
+
const messageFingerprint = fingerprintText(`${diagnostic.title ?? ""}\0${diagnostic.message}`);
|
|
46951
|
+
const normalizedEvidence = evidence === null ? "" : normalizeEvidence(evidence);
|
|
46952
|
+
const stableEvidenceKey = normalizedEvidence.length > 0 ? `evidence\0${ruleKey}\0${messageFingerprint}\0${fingerprintText(normalizedEvidence)}` : null;
|
|
46953
|
+
return {
|
|
46954
|
+
stableEvidenceKey,
|
|
46955
|
+
sameFileStableEvidenceKey: stableEvidenceKey === null ? null : `${diagnostic.filePath}\0${stableEvidenceKey}`,
|
|
46956
|
+
sameFileFallbackKey: diagnostic.matchByOccurrence || normalizedEvidence.length === 0 ? `fallback\0${diagnostic.filePath}\0${ruleKey}\0${messageFingerprint}` : null
|
|
46957
|
+
};
|
|
46958
|
+
};
|
|
46959
|
+
const addDiagnosticIndex = (buckets, key, diagnosticIndex) => {
|
|
46960
|
+
if (key === null) return;
|
|
46961
|
+
const diagnosticIndexes = buckets.get(key) ?? [];
|
|
46962
|
+
diagnosticIndexes.push(diagnosticIndex);
|
|
46963
|
+
buckets.set(key, diagnosticIndexes);
|
|
46964
|
+
};
|
|
46965
|
+
const takeMatchingDiagnosticIndex = (buckets, key, matchedDiagnosticIndexes) => {
|
|
46966
|
+
if (key === null) return null;
|
|
46967
|
+
for (const diagnosticIndex of buckets.get(key) ?? []) if (!matchedDiagnosticIndexes.has(diagnosticIndex)) return diagnosticIndex;
|
|
46968
|
+
return null;
|
|
46969
|
+
};
|
|
46970
|
+
const readDiagnosticEvidence = (diagnostic, readEvidence, readLine) => readEvidence?.(diagnostic) ?? readLine(diagnostic.filePath, diagnostic.line);
|
|
46971
|
+
const buildMatchCandidates = (diagnostics, readEvidence, readLine) => diagnostics.map((diagnostic, diagnosticIndex) => ({
|
|
46972
|
+
diagnosticIndex,
|
|
46973
|
+
...getDiagnosticMatchKeys(diagnostic, readDiagnosticEvidence(diagnostic, readEvidence, readLine))
|
|
46974
|
+
}));
|
|
46975
|
+
/**
|
|
46976
|
+
* Diffs a head scan against a base scan using a multiset of construct-level
|
|
46977
|
+
* evidence. Stable identities combine plugin/rule, the diagnostic message,
|
|
46978
|
+
* and normalized diagnosed source, so unchanged findings can move across
|
|
46979
|
+
* files while changed constructs or messages remain new. Cardinality is
|
|
46980
|
+
* retained for identical findings. Diagnostics explicitly marked
|
|
46981
|
+
* `matchByOccurrence` may fall back to same-file plugin/rule/message matching
|
|
46982
|
+
* after same-file strict evidence matching. Cross-file evidence matching runs
|
|
46983
|
+
* last so a copy cannot consume a reformatted local occurrence. Unreadable
|
|
46984
|
+
* evidence uses the same conservative fallback rather than matching across
|
|
46985
|
+
* files without proof.
|
|
46744
46986
|
*/
|
|
46745
46987
|
const computeDiagnosticDelta = (input) => {
|
|
46746
|
-
const
|
|
46747
|
-
|
|
46748
|
-
|
|
46749
|
-
|
|
46750
|
-
|
|
46751
|
-
const
|
|
46752
|
-
|
|
46753
|
-
|
|
46754
|
-
|
|
46755
|
-
|
|
46756
|
-
|
|
46757
|
-
|
|
46758
|
-
|
|
46759
|
-
|
|
46988
|
+
const baseCandidates = buildMatchCandidates(input.baseDiagnostics, input.readBaseEvidence, input.readBaseLine);
|
|
46989
|
+
const headCandidates = buildMatchCandidates(input.headDiagnostics, input.readHeadEvidence, input.readHeadLine);
|
|
46990
|
+
const baseByStableEvidence = /* @__PURE__ */ new Map();
|
|
46991
|
+
const baseBySameFileStableEvidence = /* @__PURE__ */ new Map();
|
|
46992
|
+
const baseBySameFileFallback = /* @__PURE__ */ new Map();
|
|
46993
|
+
for (const candidate of baseCandidates) {
|
|
46994
|
+
addDiagnosticIndex(baseByStableEvidence, candidate.stableEvidenceKey, candidate.diagnosticIndex);
|
|
46995
|
+
addDiagnosticIndex(baseBySameFileStableEvidence, candidate.sameFileStableEvidenceKey, candidate.diagnosticIndex);
|
|
46996
|
+
addDiagnosticIndex(baseBySameFileFallback, candidate.sameFileFallbackKey, candidate.diagnosticIndex);
|
|
46997
|
+
}
|
|
46998
|
+
const matchedHeadDiagnosticIndexes = /* @__PURE__ */ new Set();
|
|
46999
|
+
const matchedBaseDiagnosticIndexes = /* @__PURE__ */ new Set();
|
|
47000
|
+
const matchCandidates = (baseBuckets, getKey, onMatch) => {
|
|
47001
|
+
for (const candidate of headCandidates) {
|
|
47002
|
+
if (matchedHeadDiagnosticIndexes.has(candidate.diagnosticIndex)) continue;
|
|
47003
|
+
const baseDiagnosticIndex = takeMatchingDiagnosticIndex(baseBuckets, getKey(candidate), matchedBaseDiagnosticIndexes);
|
|
47004
|
+
if (baseDiagnosticIndex === null) continue;
|
|
47005
|
+
matchedHeadDiagnosticIndexes.add(candidate.diagnosticIndex);
|
|
47006
|
+
matchedBaseDiagnosticIndexes.add(baseDiagnosticIndex);
|
|
47007
|
+
onMatch?.(candidate.diagnosticIndex, baseDiagnosticIndex);
|
|
47008
|
+
}
|
|
47009
|
+
};
|
|
47010
|
+
matchCandidates(baseBySameFileStableEvidence, (candidate) => candidate.sameFileStableEvidenceKey);
|
|
47011
|
+
matchCandidates(baseBySameFileFallback, (candidate) => candidate.sameFileFallbackKey);
|
|
47012
|
+
let crossFileMatchCount = 0;
|
|
47013
|
+
matchCandidates(baseByStableEvidence, (candidate) => candidate.stableEvidenceKey, (head, base) => {
|
|
47014
|
+
if (input.headDiagnostics[head]?.filePath !== input.baseDiagnostics[base]?.filePath) crossFileMatchCount += 1;
|
|
47015
|
+
});
|
|
46760
47016
|
return {
|
|
46761
|
-
newDiagnostics,
|
|
46762
|
-
fixedCount
|
|
47017
|
+
newDiagnostics: input.headDiagnostics.filter((_diagnostic, diagnosticIndex) => !matchedHeadDiagnosticIndexes.has(diagnosticIndex)),
|
|
47018
|
+
fixedCount: input.baseDiagnostics.length - matchedBaseDiagnosticIndexes.size,
|
|
47019
|
+
crossFileMatchCount
|
|
46763
47020
|
};
|
|
46764
47021
|
};
|
|
46765
47022
|
/**
|
|
@@ -46791,6 +47048,12 @@ const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runProm
|
|
|
46791
47048
|
};
|
|
46792
47049
|
}).pipe(provide(Git.layerNode)));
|
|
46793
47050
|
const filterSourceFiles = (filePaths) => filePaths.filter(isLintableSourceFile);
|
|
47051
|
+
const getBaselineDiffPlan = (directory, ref) => runPromise(gen(function* () {
|
|
47052
|
+
return yield* (yield* Git).baselineDiffPlan({
|
|
47053
|
+
directory,
|
|
47054
|
+
ref
|
|
47055
|
+
});
|
|
47056
|
+
}).pipe(provide(Git.layerNode)));
|
|
46794
47057
|
/**
|
|
46795
47058
|
* Programmatic façade over `Git.changedLineRanges` (the `lines` scope). Diffs
|
|
46796
47059
|
* `files` with `--unified=0` against `baseRef` (or the index when `cached`),
|
|
@@ -47247,7 +47510,7 @@ const makeNoopConsole = () => ({
|
|
|
47247
47510
|
});
|
|
47248
47511
|
//#endregion
|
|
47249
47512
|
//#region src/cli/utils/version.ts
|
|
47250
|
-
const VERSION = "0.8.1-dev.
|
|
47513
|
+
const VERSION = "0.8.1-dev.7eca0ee";
|
|
47251
47514
|
//#endregion
|
|
47252
47515
|
//#region src/cli/utils/json-mode.ts
|
|
47253
47516
|
let context = null;
|
|
@@ -47599,13 +47862,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
47599
47862
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
47600
47863
|
* standard `SENTRY_RELEASE` override.
|
|
47601
47864
|
*/
|
|
47602
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.1-dev.
|
|
47865
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.1-dev.7eca0ee`;
|
|
47603
47866
|
/**
|
|
47604
47867
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
47605
47868
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
47606
47869
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
47607
47870
|
*/
|
|
47608
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.1-dev.
|
|
47871
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.1-dev.7eca0ee") ? "development" : "production");
|
|
47609
47872
|
/**
|
|
47610
47873
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
47611
47874
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -60228,6 +60491,7 @@ const buildOutcomeAttributes = (input) => {
|
|
|
60228
60491
|
new: summary.totalDiagnosticCount,
|
|
60229
60492
|
fixed: result.baselineDelta.fixedCount,
|
|
60230
60493
|
baseTotal: result.baselineDelta.baseTotalCount,
|
|
60494
|
+
crossFileMatches: result.baselineDelta.crossFileMatchCount ?? null,
|
|
60231
60495
|
degraded: false
|
|
60232
60496
|
}));
|
|
60233
60497
|
else if (input.gateExempt) attributes["baseline.degraded"] = true;
|
|
@@ -60341,28 +60605,49 @@ const diagnosticIntersectsLineRanges = (diagnostic, lineRanges) => {
|
|
|
60341
60605
|
return lineRanges.some(([rangeStartLine, rangeEndLine]) => diagnostic.line <= rangeEndLine && diagnosticEndLine >= rangeStartLine);
|
|
60342
60606
|
};
|
|
60343
60607
|
//#endregion
|
|
60608
|
+
//#region src/cli/utils/is-materializable-git-source.ts
|
|
60609
|
+
const GIT_LFS_POINTER_HEADER = "version https://git-lfs.github.com/spec/v1";
|
|
60610
|
+
const isMaterializableGitSource = (content) => !content.includes("\0") && !content.startsWith(GIT_LFS_POINTER_HEADER);
|
|
60611
|
+
//#endregion
|
|
60344
60612
|
//#region src/cli/utils/materialize-baseline-files.ts
|
|
60345
60613
|
/**
|
|
60346
|
-
* Materializes the
|
|
60347
|
-
* `
|
|
60348
|
-
*
|
|
60349
|
-
*
|
|
60350
|
-
*
|
|
60351
|
-
*
|
|
60614
|
+
* Materializes the base side of the `ref` → worktree diff into
|
|
60615
|
+
* `tempDirectory`, mirroring the project layout plus head's config files so
|
|
60616
|
+
* both sides lint under the same rules. Git rename heuristics are disabled by
|
|
60617
|
+
* the diff planner, so an old path is retained as a base deletion while its
|
|
60618
|
+
* new path is a head addition. Missing head-only additions are expected;
|
|
60619
|
+
* missing paths that the plan says must exist at base make `isComplete` false.
|
|
60352
60620
|
*/
|
|
60353
60621
|
const materializeBaselineFiles = (input) => runPromise(gen(function* () {
|
|
60354
60622
|
const git = yield* Git;
|
|
60355
|
-
|
|
60623
|
+
const diffPlan = yield* git.baselineDiffPlan({
|
|
60624
|
+
directory: input.directory,
|
|
60625
|
+
ref: input.ref
|
|
60626
|
+
});
|
|
60627
|
+
const baseFiles = input.baseFiles ?? diffPlan?.baseFiles;
|
|
60628
|
+
const headFiles = input.headFiles ?? diffPlan?.headFiles;
|
|
60629
|
+
const untrackedFiles = diffPlan?.untrackedFiles ?? [];
|
|
60630
|
+
if (baseFiles === void 0 || headFiles === void 0) return null;
|
|
60631
|
+
const files = [...new Set([...input.files, ...baseFiles])];
|
|
60632
|
+
const tree = yield* materializeSourceTree({
|
|
60356
60633
|
directory: input.directory,
|
|
60357
|
-
files
|
|
60634
|
+
files,
|
|
60358
60635
|
tempDirectory: input.tempDirectory,
|
|
60359
60636
|
readContent: (relativePath) => git.showRefContent({
|
|
60360
60637
|
directory: input.directory,
|
|
60361
60638
|
ref: input.ref,
|
|
60362
60639
|
relativePath,
|
|
60363
60640
|
options: { maxBufferBytes: GIT_SHOW_MAX_BUFFER_BYTES }
|
|
60364
|
-
})
|
|
60641
|
+
}).pipe(map$3((content) => content !== null && isMaterializableGitSource(content) ? content : null))
|
|
60365
60642
|
});
|
|
60643
|
+
const materializedFiles = new Set(tree.materializedFiles);
|
|
60644
|
+
return {
|
|
60645
|
+
...tree,
|
|
60646
|
+
baseFiles,
|
|
60647
|
+
headFiles,
|
|
60648
|
+
isComplete: baseFiles.every((filePath) => materializedFiles.has(filePath)),
|
|
60649
|
+
untrackedFiles
|
|
60650
|
+
};
|
|
60366
60651
|
}).pipe(provide(Git.layerNode)));
|
|
60367
60652
|
/**
|
|
60368
60653
|
* Resolves the commit a baseline scan should read base content from — the
|
|
@@ -60385,8 +60670,8 @@ const resolveMergeBaseRef = async (directory, ref) => {
|
|
|
60385
60670
|
//#region src/cli/utils/read-source-line.ts
|
|
60386
60671
|
/**
|
|
60387
60672
|
* Builds a cached `(filePath, line) -> source text` reader rooted at
|
|
60388
|
-
* `rootDirectory`, used
|
|
60389
|
-
*
|
|
60673
|
+
* `rootDirectory`, used as the fallback when construct-level diagnostic
|
|
60674
|
+
* evidence cannot be read. Wraps core's
|
|
60390
60675
|
* `createNodeReadFileLinesSync` with per-file memoization + 1-indexed line
|
|
60391
60676
|
* lookup; unreadable files / out-of-range lines return `null`.
|
|
60392
60677
|
*/
|
|
@@ -60401,6 +60686,338 @@ const createSourceLineReader = (rootDirectory) => {
|
|
|
60401
60686
|
};
|
|
60402
60687
|
};
|
|
60403
60688
|
//#endregion
|
|
60689
|
+
//#region src/cli/utils/read-diagnostic-evidence.ts
|
|
60690
|
+
const SOURCE_EXTENSIONS = [
|
|
60691
|
+
".ts",
|
|
60692
|
+
".tsx",
|
|
60693
|
+
".js",
|
|
60694
|
+
".jsx",
|
|
60695
|
+
".mts",
|
|
60696
|
+
".cts",
|
|
60697
|
+
".mjs",
|
|
60698
|
+
".cjs"
|
|
60699
|
+
];
|
|
60700
|
+
const COMPONENT_WRAPPER_NAMES = new Set(["memo", "forwardRef"]);
|
|
60701
|
+
const getScriptKind = (filePath) => {
|
|
60702
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
60703
|
+
if (extension === ".tsx") return ts.ScriptKind.TSX;
|
|
60704
|
+
if (extension === ".jsx") return ts.ScriptKind.JSX;
|
|
60705
|
+
if (extension === ".js" || extension === ".mjs" || extension === ".cjs") return ts.ScriptKind.JS;
|
|
60706
|
+
return ts.ScriptKind.TS;
|
|
60707
|
+
};
|
|
60708
|
+
const resolveSafeSourcePath = (rootDirectory, filePath) => {
|
|
60709
|
+
try {
|
|
60710
|
+
const resolvedRootDirectory = fs.realpathSync(rootDirectory);
|
|
60711
|
+
const absolutePath = path.resolve(resolvedRootDirectory, filePath);
|
|
60712
|
+
const relativePath = path.relative(resolvedRootDirectory, absolutePath);
|
|
60713
|
+
if (relativePath.length === 0 || relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) return null;
|
|
60714
|
+
const stats = fs.lstatSync(absolutePath);
|
|
60715
|
+
if (!stats.isFile() || stats.isSymbolicLink()) return null;
|
|
60716
|
+
const realPath = fs.realpathSync(absolutePath);
|
|
60717
|
+
const realRelativePath = path.relative(resolvedRootDirectory, realPath);
|
|
60718
|
+
return realRelativePath !== ".." && !realRelativePath.startsWith(`..${path.sep}`) && !path.isAbsolute(realRelativePath) ? realPath : null;
|
|
60719
|
+
} catch {
|
|
60720
|
+
return null;
|
|
60721
|
+
}
|
|
60722
|
+
};
|
|
60723
|
+
const readSourceRecord = (rootDirectory, filePath, state) => {
|
|
60724
|
+
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
60725
|
+
if (state.recordByFilePath.has(normalizedFilePath)) return state.recordByFilePath.get(normalizedFilePath) ?? null;
|
|
60726
|
+
const absolutePath = resolveSafeSourcePath(rootDirectory, normalizedFilePath);
|
|
60727
|
+
if (absolutePath === null) {
|
|
60728
|
+
state.recordByFilePath.set(normalizedFilePath, null);
|
|
60729
|
+
return null;
|
|
60730
|
+
}
|
|
60731
|
+
try {
|
|
60732
|
+
const sourceText = fs.readFileSync(absolutePath, "utf-8");
|
|
60733
|
+
const record = {
|
|
60734
|
+
absolutePath,
|
|
60735
|
+
filePath: normalizedFilePath,
|
|
60736
|
+
sourceFile: ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
60737
|
+
sourceText
|
|
60738
|
+
};
|
|
60739
|
+
state.recordByFilePath.set(normalizedFilePath, record);
|
|
60740
|
+
return record;
|
|
60741
|
+
} catch {
|
|
60742
|
+
state.recordByFilePath.set(normalizedFilePath, null);
|
|
60743
|
+
return null;
|
|
60744
|
+
}
|
|
60745
|
+
};
|
|
60746
|
+
const listSourceRecords = (rootDirectory, state) => {
|
|
60747
|
+
if (state.sourceRecords !== null) return state.sourceRecords;
|
|
60748
|
+
state.sourceRecords = listSourceFiles(rootDirectory).flatMap((filePath) => {
|
|
60749
|
+
const record = readSourceRecord(rootDirectory, filePath, state);
|
|
60750
|
+
return record === null ? [] : [record];
|
|
60751
|
+
});
|
|
60752
|
+
return state.sourceRecords;
|
|
60753
|
+
};
|
|
60754
|
+
const unwrapExpression = (expression) => {
|
|
60755
|
+
let currentExpression = expression;
|
|
60756
|
+
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
60757
|
+
return currentExpression;
|
|
60758
|
+
};
|
|
60759
|
+
const getCalleeName = (expression) => {
|
|
60760
|
+
const unwrappedExpression = unwrapExpression(expression);
|
|
60761
|
+
if (ts.isIdentifier(unwrappedExpression)) return unwrappedExpression.text;
|
|
60762
|
+
if (ts.isPropertyAccessExpression(unwrappedExpression)) return unwrappedExpression.name.text;
|
|
60763
|
+
return null;
|
|
60764
|
+
};
|
|
60765
|
+
const isComponentWrapperCall = (node) => {
|
|
60766
|
+
const wrapperName = getCalleeName(node.expression);
|
|
60767
|
+
return wrapperName !== null && COMPONENT_WRAPPER_NAMES.has(wrapperName);
|
|
60768
|
+
};
|
|
60769
|
+
const getFunctionName = (node) => {
|
|
60770
|
+
if (ts.isFunctionDeclaration(node) && node.name !== void 0) return node.name.text;
|
|
60771
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
60772
|
+
let componentExpression = node;
|
|
60773
|
+
while (true) {
|
|
60774
|
+
const parent = componentExpression.parent;
|
|
60775
|
+
if (ts.isParenthesizedExpression(parent) || ts.isAsExpression(parent) || ts.isTypeAssertionExpression(parent) || ts.isNonNullExpression(parent) || ts.isSatisfiesExpression(parent)) {
|
|
60776
|
+
componentExpression = parent;
|
|
60777
|
+
continue;
|
|
60778
|
+
}
|
|
60779
|
+
if (ts.isCallExpression(parent) && parent.arguments[0] === componentExpression && isComponentWrapperCall(parent)) {
|
|
60780
|
+
componentExpression = parent;
|
|
60781
|
+
continue;
|
|
60782
|
+
}
|
|
60783
|
+
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) return parent.name.text;
|
|
60784
|
+
return node.name?.text ?? null;
|
|
60785
|
+
}
|
|
60786
|
+
}
|
|
60787
|
+
return null;
|
|
60788
|
+
};
|
|
60789
|
+
const isComponentFunction = (node) => ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node);
|
|
60790
|
+
const isDefaultExportedComponent = (record, node, componentName) => {
|
|
60791
|
+
if (ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword)) return true;
|
|
60792
|
+
return record.sourceFile.statements.some((statement) => ts.isExportAssignment(statement) && ts.isIdentifier(statement.expression) && statement.expression.text === componentName);
|
|
60793
|
+
};
|
|
60794
|
+
const findEnclosingComponent = (record, diagnostic) => {
|
|
60795
|
+
if (!Number.isInteger(diagnostic.line) || diagnostic.line < 1) return null;
|
|
60796
|
+
const lineIndex = diagnostic.line - 1;
|
|
60797
|
+
const lineStarts = record.sourceFile.getLineStarts();
|
|
60798
|
+
const lineStart = lineStarts[lineIndex];
|
|
60799
|
+
if (lineStart === void 0) return null;
|
|
60800
|
+
const nextLineStart = lineStarts[lineIndex + 1] ?? record.sourceFile.end;
|
|
60801
|
+
const lineEnd = Math.max(lineStart, nextLineStart - 1);
|
|
60802
|
+
const columnIndex = Number.isInteger(diagnostic.column) ? Math.max(0, diagnostic.column - 1) : 0;
|
|
60803
|
+
const diagnosticPosition = Math.min(lineStart + columnIndex, lineEnd);
|
|
60804
|
+
let component = null;
|
|
60805
|
+
let componentWidth = Number.POSITIVE_INFINITY;
|
|
60806
|
+
const visit = (node) => {
|
|
60807
|
+
if (diagnosticPosition < node.getStart(record.sourceFile) || diagnosticPosition > node.end) return;
|
|
60808
|
+
if (isComponentFunction(node)) {
|
|
60809
|
+
const name = getFunctionName(node);
|
|
60810
|
+
const width = node.end - node.getStart(record.sourceFile);
|
|
60811
|
+
if (name !== null && /^[A-Z]/.test(name) && width < componentWidth) {
|
|
60812
|
+
component = {
|
|
60813
|
+
isDefaultExport: isDefaultExportedComponent(record, node, name),
|
|
60814
|
+
name,
|
|
60815
|
+
node,
|
|
60816
|
+
record
|
|
60817
|
+
};
|
|
60818
|
+
componentWidth = width;
|
|
60819
|
+
}
|
|
60820
|
+
}
|
|
60821
|
+
ts.forEachChild(node, visit);
|
|
60822
|
+
};
|
|
60823
|
+
visit(record.sourceFile);
|
|
60824
|
+
return component;
|
|
60825
|
+
};
|
|
60826
|
+
const moduleSpecifierTargetsComponent = (callerRecord, moduleSpecifier, componentRecord) => {
|
|
60827
|
+
if (!moduleSpecifier.startsWith(".")) return false;
|
|
60828
|
+
const unresolvedPath = path.resolve(path.dirname(callerRecord.absolutePath), moduleSpecifier);
|
|
60829
|
+
const specifiedExtension = path.extname(unresolvedPath).toLowerCase();
|
|
60830
|
+
const sourcePathWithoutExtension = SOURCE_EXTENSIONS.includes(specifiedExtension) ? unresolvedPath.slice(0, -specifiedExtension.length) : unresolvedPath;
|
|
60831
|
+
return [
|
|
60832
|
+
unresolvedPath,
|
|
60833
|
+
...SOURCE_EXTENSIONS.map((extension) => `${sourcePathWithoutExtension}${extension}`),
|
|
60834
|
+
...sourcePathWithoutExtension === unresolvedPath ? SOURCE_EXTENSIONS.map((extension) => path.join(unresolvedPath, `index${extension}`)) : []
|
|
60835
|
+
].includes(componentRecord.absolutePath);
|
|
60836
|
+
};
|
|
60837
|
+
const getComponentTagNames = (record, component) => {
|
|
60838
|
+
if (record.absolutePath === component.record.absolutePath) return new Set([component.name]);
|
|
60839
|
+
const tagNames = /* @__PURE__ */ new Set();
|
|
60840
|
+
for (const statement of record.sourceFile.statements) {
|
|
60841
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier) || !moduleSpecifierTargetsComponent(record, statement.moduleSpecifier.text, component.record)) continue;
|
|
60842
|
+
const importClause = statement.importClause;
|
|
60843
|
+
if (component.isDefaultExport && importClause?.name !== void 0) tagNames.add(importClause.name.text);
|
|
60844
|
+
const namedBindings = importClause?.namedBindings;
|
|
60845
|
+
if (namedBindings !== void 0 && ts.isNamedImports(namedBindings)) {
|
|
60846
|
+
for (const element of namedBindings.elements) if ((element.propertyName?.text ?? element.name.text) === component.name) tagNames.add(element.name.text);
|
|
60847
|
+
}
|
|
60848
|
+
}
|
|
60849
|
+
return tagNames;
|
|
60850
|
+
};
|
|
60851
|
+
const getTransparentCall = (functionNode) => {
|
|
60852
|
+
if (functionNode.body === void 0) return null;
|
|
60853
|
+
if (!ts.isBlock(functionNode.body)) {
|
|
60854
|
+
const expression = unwrapExpression(functionNode.body);
|
|
60855
|
+
return ts.isCallExpression(expression) ? expression : null;
|
|
60856
|
+
}
|
|
60857
|
+
if (functionNode.body.statements.length !== 1) return null;
|
|
60858
|
+
const statement = functionNode.body.statements[0];
|
|
60859
|
+
if (statement === void 0) return null;
|
|
60860
|
+
const expression = ts.isExpressionStatement(statement) || ts.isReturnStatement(statement) ? statement.expression : void 0;
|
|
60861
|
+
if (expression === void 0) return null;
|
|
60862
|
+
const unwrappedExpression = unwrapExpression(expression);
|
|
60863
|
+
return ts.isCallExpression(unwrappedExpression) ? unwrappedExpression : null;
|
|
60864
|
+
};
|
|
60865
|
+
const resolveTransparentTarget = (functionNode) => {
|
|
60866
|
+
const call = getTransparentCall(functionNode);
|
|
60867
|
+
if (call === null) return null;
|
|
60868
|
+
const target = unwrapExpression(call.expression);
|
|
60869
|
+
if (!ts.isIdentifier(target) || call.arguments.length !== functionNode.parameters.length) return null;
|
|
60870
|
+
for (const [parameterIndex, parameter] of functionNode.parameters.entries()) {
|
|
60871
|
+
const argument = call.arguments[parameterIndex];
|
|
60872
|
+
const unwrappedArgument = argument === void 0 ? null : unwrapExpression(argument);
|
|
60873
|
+
if (!ts.isIdentifier(parameter.name) || unwrappedArgument === null || !ts.isIdentifier(unwrappedArgument) || unwrappedArgument.text !== parameter.name.text) return null;
|
|
60874
|
+
}
|
|
60875
|
+
return target.text;
|
|
60876
|
+
};
|
|
60877
|
+
const getForwardingFunction = (declaration) => {
|
|
60878
|
+
if (ts.isFunctionDeclaration(declaration)) return declaration;
|
|
60879
|
+
if (!ts.isVariableDeclaration(declaration) || declaration.initializer === void 0) return null;
|
|
60880
|
+
const initializer = unwrapExpression(declaration.initializer);
|
|
60881
|
+
if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) return initializer;
|
|
60882
|
+
if (ts.isCallExpression(initializer) && getCalleeName(initializer.expression) === "useCallback") {
|
|
60883
|
+
const callback = initializer.arguments[0];
|
|
60884
|
+
if (callback !== void 0 && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) return callback;
|
|
60885
|
+
}
|
|
60886
|
+
return null;
|
|
60887
|
+
};
|
|
60888
|
+
const getDeclaredBindingName = (node) => {
|
|
60889
|
+
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;
|
|
60890
|
+
if (ts.isImportClause(node)) return node.name?.text ?? null;
|
|
60891
|
+
if (ts.isImportSpecifier(node) || ts.isNamespaceImport(node)) return node.name.text;
|
|
60892
|
+
return null;
|
|
60893
|
+
};
|
|
60894
|
+
const findBindingScope = (node) => {
|
|
60895
|
+
let currentNode = node;
|
|
60896
|
+
while (currentNode !== void 0) {
|
|
60897
|
+
if (ts.isParameter(currentNode) && ts.isFunctionLike(currentNode.parent)) return currentNode.parent;
|
|
60898
|
+
if (ts.isBlock(currentNode) || ts.isModuleBlock(currentNode) || ts.isSourceFile(currentNode)) return currentNode;
|
|
60899
|
+
currentNode = currentNode.parent;
|
|
60900
|
+
}
|
|
60901
|
+
return node.getSourceFile();
|
|
60902
|
+
};
|
|
60903
|
+
const findBindingDeclaration = (record, binding) => {
|
|
60904
|
+
const usagePosition = binding.getStart(record.sourceFile);
|
|
60905
|
+
const candidates = [];
|
|
60906
|
+
const visit = (node) => {
|
|
60907
|
+
if (getDeclaredBindingName(node) === binding.text) {
|
|
60908
|
+
const scope = findBindingScope(node);
|
|
60909
|
+
const isVisibleScope = usagePosition >= scope.getStart(record.sourceFile) && usagePosition <= scope.end;
|
|
60910
|
+
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;
|
|
60911
|
+
if (isVisibleScope && isVisibleDeclaration) candidates.push({
|
|
60912
|
+
declaration: node,
|
|
60913
|
+
scope
|
|
60914
|
+
});
|
|
60915
|
+
}
|
|
60916
|
+
ts.forEachChild(node, visit);
|
|
60917
|
+
};
|
|
60918
|
+
visit(record.sourceFile);
|
|
60919
|
+
let nearestCandidate = null;
|
|
60920
|
+
let nearestScopeWidth = Number.POSITIVE_INFINITY;
|
|
60921
|
+
let isNearestScopeAmbiguous = false;
|
|
60922
|
+
for (const candidate of candidates) {
|
|
60923
|
+
const scopeWidth = candidate.scope.end - candidate.scope.getStart(record.sourceFile);
|
|
60924
|
+
if (scopeWidth < nearestScopeWidth) {
|
|
60925
|
+
nearestCandidate = candidate;
|
|
60926
|
+
nearestScopeWidth = scopeWidth;
|
|
60927
|
+
isNearestScopeAmbiguous = false;
|
|
60928
|
+
} else if (scopeWidth === nearestScopeWidth) isNearestScopeAmbiguous = true;
|
|
60929
|
+
}
|
|
60930
|
+
if (isNearestScopeAmbiguous) return null;
|
|
60931
|
+
const declaration = nearestCandidate?.declaration;
|
|
60932
|
+
return declaration !== void 0 && (ts.isVariableDeclaration(declaration) || ts.isFunctionDeclaration(declaration)) ? declaration : null;
|
|
60933
|
+
};
|
|
60934
|
+
const resolveHandlerBinding = (record, binding) => {
|
|
60935
|
+
const declaration = findBindingDeclaration(record, binding);
|
|
60936
|
+
if (declaration === null) return binding.text;
|
|
60937
|
+
const forwardingFunction = getForwardingFunction(declaration);
|
|
60938
|
+
const target = forwardingFunction === null ? null : resolveTransparentTarget(forwardingFunction);
|
|
60939
|
+
return target === null || target === binding.text ? binding.text : target;
|
|
60940
|
+
};
|
|
60941
|
+
const getJsxTagName = (node) => ts.isIdentifier(node.tagName) ? node.tagName.text : null;
|
|
60942
|
+
const getHandlerBinding = (node, propName) => {
|
|
60943
|
+
for (const property of node.attributes.properties) {
|
|
60944
|
+
if (!ts.isJsxAttribute(property) || property.name.getText() !== propName) continue;
|
|
60945
|
+
const initializer = property.initializer;
|
|
60946
|
+
if (initializer === void 0 || !ts.isJsxExpression(initializer)) return null;
|
|
60947
|
+
if (initializer.expression === void 0) return null;
|
|
60948
|
+
const expression = unwrapExpression(initializer.expression);
|
|
60949
|
+
return ts.isIdentifier(expression) ? expression : null;
|
|
60950
|
+
}
|
|
60951
|
+
return null;
|
|
60952
|
+
};
|
|
60953
|
+
const getForwardedHandlerAliases = (rootDirectory, component, propNames, state) => {
|
|
60954
|
+
const cacheKey = `${component.record.filePath}\0${component.name}\0${[...propNames].sort().join("\0")}`;
|
|
60955
|
+
const cachedAliases = state.aliasesByComponent.get(cacheKey);
|
|
60956
|
+
if (cachedAliases !== void 0) return cachedAliases;
|
|
60957
|
+
const bindingsByPropName = /* @__PURE__ */ new Map();
|
|
60958
|
+
const unresolvedPropNames = /* @__PURE__ */ new Set();
|
|
60959
|
+
let callsiteCount = 0;
|
|
60960
|
+
for (const record of listSourceRecords(rootDirectory, state)) {
|
|
60961
|
+
const tagNames = getComponentTagNames(record, component);
|
|
60962
|
+
if (tagNames.size === 0) continue;
|
|
60963
|
+
const visit = (node) => {
|
|
60964
|
+
if (record === component.record && node === component.node) return;
|
|
60965
|
+
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
60966
|
+
const tagName = getJsxTagName(node);
|
|
60967
|
+
if (tagName !== null && tagNames.has(tagName)) {
|
|
60968
|
+
callsiteCount += 1;
|
|
60969
|
+
for (const propName of propNames) {
|
|
60970
|
+
const binding = getHandlerBinding(node, propName);
|
|
60971
|
+
if (binding === null) {
|
|
60972
|
+
unresolvedPropNames.add(propName);
|
|
60973
|
+
continue;
|
|
60974
|
+
}
|
|
60975
|
+
const bindings = bindingsByPropName.get(propName) ?? /* @__PURE__ */ new Set();
|
|
60976
|
+
bindings.add(resolveHandlerBinding(record, binding));
|
|
60977
|
+
bindingsByPropName.set(propName, bindings);
|
|
60978
|
+
}
|
|
60979
|
+
}
|
|
60980
|
+
}
|
|
60981
|
+
ts.forEachChild(node, visit);
|
|
60982
|
+
};
|
|
60983
|
+
visit(record.sourceFile);
|
|
60984
|
+
}
|
|
60985
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
60986
|
+
if (callsiteCount > 0) for (const [propName, bindings] of bindingsByPropName) {
|
|
60987
|
+
if (bindings.size !== 1 || unresolvedPropNames.has(propName)) continue;
|
|
60988
|
+
const [bindingName] = bindings;
|
|
60989
|
+
if (bindingName !== void 0) aliases.set(propName, bindingName);
|
|
60990
|
+
}
|
|
60991
|
+
state.aliasesByComponent.set(cacheKey, aliases);
|
|
60992
|
+
return aliases;
|
|
60993
|
+
};
|
|
60994
|
+
const normalizeForwardedHandlers = (evidence, aliases) => {
|
|
60995
|
+
let normalizedEvidence = evidence;
|
|
60996
|
+
for (const [propName, bindingName] of aliases) normalizedEvidence = normalizedEvidence.replace(new RegExp(`\\{\\s*${propName}\\s*\\}`, "g"), `{${bindingName}}`).replace(new RegExp(`=>\\s*${propName}\\s*\\(`, "g"), `=> ${bindingName}(`);
|
|
60997
|
+
return normalizedEvidence;
|
|
60998
|
+
};
|
|
60999
|
+
const createDiagnosticEvidenceReader = (rootDirectory, options = {}) => {
|
|
61000
|
+
const state = {
|
|
61001
|
+
aliasesByComponent: /* @__PURE__ */ new Map(),
|
|
61002
|
+
recordByFilePath: /* @__PURE__ */ new Map(),
|
|
61003
|
+
sourceRecords: null
|
|
61004
|
+
};
|
|
61005
|
+
return (diagnostic) => {
|
|
61006
|
+
const record = readSourceRecord(rootDirectory, diagnostic.filePath, state);
|
|
61007
|
+
if (record === null || !Number.isInteger(diagnostic.line)) return null;
|
|
61008
|
+
const sourceLines = record.sourceText.split(/\r?\n/);
|
|
61009
|
+
const startLineIndex = Math.max(0, diagnostic.line - 1);
|
|
61010
|
+
const endLineIndex = Math.max(startLineIndex, (diagnostic.endLine ?? diagnostic.line) - 1);
|
|
61011
|
+
const evidence = sourceLines.slice(startLineIndex, endLineIndex + 1).join("\n");
|
|
61012
|
+
if (!options.resolveForwardedHandlers) return evidence;
|
|
61013
|
+
const propNames = new Set(evidence.match(/\bon[A-Z]\w*\b/g) ?? []);
|
|
61014
|
+
if (propNames.size === 0) return evidence;
|
|
61015
|
+
const component = findEnclosingComponent(record, diagnostic);
|
|
61016
|
+
if (component === null) return evidence;
|
|
61017
|
+
return normalizeForwardedHandlers(evidence, getForwardedHandlerAliases(rootDirectory, component, propNames, state));
|
|
61018
|
+
};
|
|
61019
|
+
};
|
|
61020
|
+
//#endregion
|
|
60404
61021
|
//#region src/cli/utils/build-no-score-message.ts
|
|
60405
61022
|
const ENTERPRISE_CONTACT_HINT = `Want something custom to your company? Contact us at ${ENTERPRISE_CONTACT_URL}.`;
|
|
60406
61023
|
const buildNoScoreMessage = (isScoreDisabled) => {
|
|
@@ -61644,6 +62261,8 @@ const runBaselineComparison = async (params) => {
|
|
|
61644
62261
|
directory: params.directory,
|
|
61645
62262
|
ref: params.baselineRef,
|
|
61646
62263
|
files: params.options.includePaths,
|
|
62264
|
+
baseFiles: params.baseFiles,
|
|
62265
|
+
headFiles: params.headFiles,
|
|
61647
62266
|
tempDirectory
|
|
61648
62267
|
}).catch((error) => {
|
|
61649
62268
|
rmSync(tempDirectory, {
|
|
@@ -61652,7 +62271,24 @@ const runBaselineComparison = async (params) => {
|
|
|
61652
62271
|
});
|
|
61653
62272
|
throw error;
|
|
61654
62273
|
});
|
|
62274
|
+
if (snapshot === null) {
|
|
62275
|
+
rmSync(tempDirectory, {
|
|
62276
|
+
recursive: true,
|
|
62277
|
+
force: true
|
|
62278
|
+
});
|
|
62279
|
+
return null;
|
|
62280
|
+
}
|
|
61655
62281
|
try {
|
|
62282
|
+
if (!snapshot.isComplete) return null;
|
|
62283
|
+
const analyzedHeadFiles = new Set(params.headAnalyzedFiles.map(toForwardSlashes));
|
|
62284
|
+
const baseFiles = new Set(snapshot.baseFiles.map(toForwardSlashes));
|
|
62285
|
+
const trackedHeadFiles = new Set(snapshot.headFiles.map(toForwardSlashes));
|
|
62286
|
+
const expectedHeadFiles = new Set(trackedHeadFiles);
|
|
62287
|
+
for (const filePath of params.options.includePaths) {
|
|
62288
|
+
const normalizedFilePath = toForwardSlashes(filePath);
|
|
62289
|
+
if (!baseFiles.has(normalizedFilePath)) expectedHeadFiles.add(normalizedFilePath);
|
|
62290
|
+
}
|
|
62291
|
+
if (filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
|
|
61656
62292
|
const baseLayers = buildRuntimeLayers({
|
|
61657
62293
|
directory: snapshot.tempDirectory,
|
|
61658
62294
|
hasConfigOverride: true,
|
|
@@ -61668,7 +62304,7 @@ const runBaselineComparison = async (params) => {
|
|
|
61668
62304
|
});
|
|
61669
62305
|
const baseOutput = await runPromise(restoreLegacyThrow(runInspect({
|
|
61670
62306
|
directory: snapshot.tempDirectory,
|
|
61671
|
-
includePaths:
|
|
62307
|
+
includePaths: snapshot.materializedFiles,
|
|
61672
62308
|
customRulesOnly: params.options.customRulesOnly,
|
|
61673
62309
|
respectInlineDisables: params.options.respectInlineDisables,
|
|
61674
62310
|
warnings: params.options.warnings,
|
|
@@ -61685,18 +62321,22 @@ const runBaselineComparison = async (params) => {
|
|
|
61685
62321
|
deadlineEpochMs: params.deadlineEpochMs ?? void 0
|
|
61686
62322
|
}, {}).pipe(provide(baseLayers), provideService$2(PerFileLintCacheEnabled, false), provideService$2(SidecarLintCacheEnabled, false), provideService$2(Console, silentConsole))));
|
|
61687
62323
|
if (baseOutput.didLintFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
|
|
62324
|
+
const hasUnscannedUntrackedSourceFiles = filterSourceFiles(snapshot.untrackedFiles.map(toForwardSlashes)).some((filePath) => !analyzedHeadFiles.has(filePath));
|
|
61688
62325
|
const delta = computeDiagnosticDelta({
|
|
61689
62326
|
headDiagnostics: params.headDiagnostics,
|
|
61690
62327
|
baseDiagnostics: baseOutput.diagnostics,
|
|
61691
62328
|
readHeadLine: createSourceLineReader(params.directory),
|
|
61692
|
-
readBaseLine: createSourceLineReader(snapshot.tempDirectory)
|
|
62329
|
+
readBaseLine: createSourceLineReader(snapshot.tempDirectory),
|
|
62330
|
+
readHeadEvidence: createDiagnosticEvidenceReader(params.directory, { resolveForwardedHandlers: true }),
|
|
62331
|
+
readBaseEvidence: createDiagnosticEvidenceReader(snapshot.tempDirectory)
|
|
61693
62332
|
});
|
|
61694
62333
|
return {
|
|
61695
62334
|
displayDiagnostics: delta.newDiagnostics,
|
|
61696
62335
|
baselineDelta: {
|
|
61697
62336
|
baseRef: params.baselineRef,
|
|
61698
|
-
fixedCount: delta.fixedCount,
|
|
61699
|
-
baseTotalCount: baseOutput.diagnostics.length
|
|
62337
|
+
fixedCount: hasUnscannedUntrackedSourceFiles ? 0 : delta.fixedCount,
|
|
62338
|
+
baseTotalCount: baseOutput.diagnostics.length,
|
|
62339
|
+
crossFileMatchCount: delta.crossFileMatchCount
|
|
61700
62340
|
}
|
|
61701
62341
|
};
|
|
61702
62342
|
} finally {
|
|
@@ -61803,6 +62443,9 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
61803
62443
|
headDiagnostics: output.diagnostics,
|
|
61804
62444
|
resolvedNodeBinaryPath,
|
|
61805
62445
|
baselineRef: options.baseline.ref,
|
|
62446
|
+
baseFiles: options.baseline.baseFiles,
|
|
62447
|
+
headFiles: options.baseline.headFiles,
|
|
62448
|
+
headAnalyzedFiles: output.analyzedFiles,
|
|
61806
62449
|
deadlineEpochMs
|
|
61807
62450
|
});
|
|
61808
62451
|
if (comparison) {
|
|
@@ -64087,20 +64730,25 @@ const resolveProjectRelativeDirectory = (rootDirectory, projectDirectory) => {
|
|
|
64087
64730
|
return relativeProjectDirectory;
|
|
64088
64731
|
};
|
|
64089
64732
|
//#endregion
|
|
64090
|
-
//#region src/cli/utils/resolve-project-
|
|
64091
|
-
const
|
|
64733
|
+
//#region src/cli/utils/resolve-project-source-file-paths.ts
|
|
64734
|
+
const resolveProjectSourceFilePaths = (rootDirectory, projectDirectory, filePaths) => {
|
|
64092
64735
|
const relativeProjectDirectory = resolveProjectRelativeDirectory(rootDirectory, projectDirectory);
|
|
64093
64736
|
if (relativeProjectDirectory === null) return [];
|
|
64094
|
-
const
|
|
64095
|
-
if (relativeProjectDirectory.length === 0) return
|
|
64737
|
+
const sourceFilePaths = filterSourceFiles([...filePaths]);
|
|
64738
|
+
if (relativeProjectDirectory.length === 0) return sourceFilePaths;
|
|
64096
64739
|
const projectPrefix = `${relativeProjectDirectory}/`;
|
|
64097
|
-
return
|
|
64740
|
+
return sourceFilePaths.flatMap((filePath) => {
|
|
64098
64741
|
const normalizedFilePath = toForwardSlashes(filePath);
|
|
64099
64742
|
if (!normalizedFilePath.startsWith(projectPrefix)) return [];
|
|
64100
64743
|
const projectRelativePath = normalizedFilePath.slice(projectPrefix.length);
|
|
64101
64744
|
return projectRelativePath.length > 0 ? [projectRelativePath] : [];
|
|
64102
64745
|
});
|
|
64103
64746
|
};
|
|
64747
|
+
//#endregion
|
|
64748
|
+
//#region src/cli/utils/resolve-project-diff-include-paths.ts
|
|
64749
|
+
const resolveProjectDiffIncludePaths = (rootDirectory, projectDirectory, diffInfo) => {
|
|
64750
|
+
return resolveProjectSourceFilePaths(rootDirectory, projectDirectory, diffInfo.changedFiles);
|
|
64751
|
+
};
|
|
64104
64752
|
/**
|
|
64105
64753
|
* Re-keys repo-relative changed line ranges (from `getChangedLineRanges`) to
|
|
64106
64754
|
* the scanned project, mirroring `resolveProjectDiffIncludePaths`'s boundary
|
|
@@ -64639,6 +65287,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
64639
65287
|
const isDiffMode = scope !== "full";
|
|
64640
65288
|
const comparisonBaseRef = isDiffMode && diffInfo && !diffInfo.isCurrentChanges ? diffInfo.baseSha ? await resolveMergeBaseRef(resolvedDirectory, diffInfo.baseSha) : diffInfo.diffBaseRef ?? await resolveMergeBaseRef(resolvedDirectory, diffInfo.baseBranch) : null;
|
|
64641
65289
|
const baselineRef = scope === "changed" ? comparisonBaseRef : null;
|
|
65290
|
+
const baselineDiffPlan = baselineRef === null ? null : await getBaselineDiffPlan(resolvedDirectory, baselineRef);
|
|
64642
65291
|
const linesBaseRef = diffInfo?.isCurrentChanges ? "HEAD" : comparisonBaseRef;
|
|
64643
65292
|
const changedLineRanges = scope === "lines" && diffInfo !== null && (diffInfo.isCurrentChanges || linesBaseRef !== null) && diffInfo !== null ? await getChangedLineRanges({
|
|
64644
65293
|
directory: resolvedDirectory,
|
|
@@ -64669,10 +65318,13 @@ const inspectAction = async (directory, flags) => {
|
|
|
64669
65318
|
const supplyChainEnabled = flags.supplyChain ?? projectConfig?.supplyChain?.enabled !== false;
|
|
64670
65319
|
let includePaths;
|
|
64671
65320
|
let supplyChainManifestChanged = false;
|
|
65321
|
+
const projectBaselineBaseFiles = baselineDiffPlan === null ? null : resolveProjectSourceFilePaths(resolvedDirectory, scanDirectory, baselineDiffPlan.baseFiles);
|
|
65322
|
+
const projectBaselineHeadFiles = baselineDiffPlan === null ? null : resolveProjectSourceFilePaths(resolvedDirectory, scanDirectory, baselineDiffPlan.headFiles);
|
|
64672
65323
|
if (isDiffMode) {
|
|
64673
65324
|
const changedSourceFiles = diffInfo === null ? [] : resolveProjectDiffIncludePaths(resolvedDirectory, scanDirectory, diffInfo);
|
|
64674
65325
|
supplyChainManifestChanged = supplyChainEnabled && diffInfo !== null && projectManifestChanged(resolvedDirectory, scanDirectory, diffInfo);
|
|
64675
|
-
|
|
65326
|
+
const hasBaselineOnlyFiles = (projectBaselineBaseFiles?.length ?? 0) > 0;
|
|
65327
|
+
if (changedSourceFiles.length === 0 && !supplyChainManifestChanged && !hasBaselineOnlyFiles) {
|
|
64676
65328
|
if (!isQuiet) {
|
|
64677
65329
|
cliLogger.dim(`No changed source files in ${scanDirectory}, skipping.`);
|
|
64678
65330
|
cliLogger.break();
|
|
@@ -64680,6 +65332,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
64680
65332
|
return null;
|
|
64681
65333
|
}
|
|
64682
65334
|
includePaths = [...changedSourceFiles];
|
|
65335
|
+
if (includePaths.length === 0 && hasBaselineOnlyFiles) includePaths.push(...projectBaselineBaseFiles ?? []);
|
|
64683
65336
|
if (supplyChainManifestChanged) includePaths.push("package.json");
|
|
64684
65337
|
}
|
|
64685
65338
|
if (!isQuiet && !isMultiProject) cliLogger.dim(" ");
|
|
@@ -64691,7 +65344,11 @@ const inspectAction = async (directory, flags) => {
|
|
|
64691
65344
|
configSourceDirectory: projectConfigSourceDirectory ?? void 0,
|
|
64692
65345
|
suppressRendering: isMultiProject,
|
|
64693
65346
|
concurrentScan: isMultiProject,
|
|
64694
|
-
baseline: baselineRef
|
|
65347
|
+
baseline: baselineRef !== null && projectBaselineBaseFiles !== null && projectBaselineHeadFiles !== null ? {
|
|
65348
|
+
ref: baselineRef,
|
|
65349
|
+
baseFiles: projectBaselineBaseFiles,
|
|
65350
|
+
headFiles: projectBaselineHeadFiles
|
|
65351
|
+
} : void 0,
|
|
64695
65352
|
changedLineRanges: scope === "lines" && changedLineRanges !== null ? resolveProjectChangedLineRanges(resolvedDirectory, scanDirectory, changedLineRanges) : void 0,
|
|
64696
65353
|
supplyChainManifestChanged
|
|
64697
65354
|
});
|
|
@@ -65891,4 +66548,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
65891
66548
|
export {};
|
|
65892
66549
|
|
|
65893
66550
|
//# sourceMappingURL=cli.js.map
|
|
65894
|
-
//# debugId=
|
|
66551
|
+
//# debugId=81bdc95e-12f2-565f-a05c-7b8a60a98f81
|