react-doctor 0.7.6-dev.fbd85e3 → 0.7.7-dev.04c7e36
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 +702 -160
- package/dist/index.d.ts +5 -4
- package/dist/index.js +562 -136
- package/dist/lsp.js +580 -161
- package/dist/skills/improve-react/AUDIT.md +140 -0
- package/dist/skills/improve-react/PLAN-TEMPLATE.md +82 -0
- package/dist/skills/improve-react/SKILL.md +121 -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]="3de13f8e-2b7e-5141-a62e-0cce9208b352")}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";
|
|
@@ -36241,7 +36241,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
36241
36241
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
36242
36242
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
36243
36243
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
36244
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
36244
|
+
const CORE_PACKAGE_VERSION = "0.7.7";
|
|
36245
36245
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
36246
36246
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
36247
36247
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -36341,10 +36341,12 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
|
36341
36341
|
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
|
|
36342
36342
|
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
36343
36343
|
const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
|
|
36344
|
+
const UTF8_BOM_CHARACTER = "";
|
|
36345
|
+
const stripUtf8Bom = (content) => content.startsWith(UTF8_BOM_CHARACTER) ? content.slice(1) : content;
|
|
36344
36346
|
const cachedPackageJsons = /* @__PURE__ */ new Map();
|
|
36345
36347
|
const readPackageJsonUncached = (packageJsonPath) => {
|
|
36346
36348
|
try {
|
|
36347
|
-
return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
|
|
36349
|
+
return JSON.parse(stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8")));
|
|
36348
36350
|
} catch (error) {
|
|
36349
36351
|
if (error instanceof SyntaxError) return {};
|
|
36350
36352
|
if (isErrnoException(error)) {
|
|
@@ -39592,6 +39594,49 @@ const assignFixGroups = (diagnostics) => {
|
|
|
39592
39594
|
};
|
|
39593
39595
|
});
|
|
39594
39596
|
};
|
|
39597
|
+
const DERIVED_STATE_RULE_PRIORITY = new Map([
|
|
39598
|
+
["no-initialize-state", 0],
|
|
39599
|
+
["no-adjust-state-on-prop-change", 1],
|
|
39600
|
+
["no-derived-state", 2],
|
|
39601
|
+
["no-derived-state-effect", 3]
|
|
39602
|
+
]);
|
|
39603
|
+
const buildDiagnosticSiteKey = (diagnostic) => `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}`;
|
|
39604
|
+
const isReactDoctorRulesOfHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-doctor" && diagnostic.rule === "rules-of-hooks";
|
|
39605
|
+
const isReactCompilerHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-hooks-js" && diagnostic.rule === "hooks";
|
|
39606
|
+
const doDiagnosticSpansOverlap = (first, second) => {
|
|
39607
|
+
if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
|
|
39608
|
+
if (first.offset !== void 0 && second.offset !== void 0) {
|
|
39609
|
+
const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
|
|
39610
|
+
const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
|
|
39611
|
+
return first.offset < secondEnd && second.offset < firstEnd;
|
|
39612
|
+
}
|
|
39613
|
+
const firstEndLine = first.endLine ?? first.line;
|
|
39614
|
+
const secondEndLine = second.endLine ?? second.line;
|
|
39615
|
+
return first.line <= secondEndLine && second.line <= firstEndLine;
|
|
39616
|
+
};
|
|
39617
|
+
const keepHighestSeverity = (survivingDiagnostic, collapsedDiagnostic) => collapsedDiagnostic.severity === "error" && survivingDiagnostic.severity !== "error" ? {
|
|
39618
|
+
...survivingDiagnostic,
|
|
39619
|
+
severity: "error"
|
|
39620
|
+
} : survivingDiagnostic;
|
|
39621
|
+
const dedupeRelatedDiagnostics = (diagnostics) => {
|
|
39622
|
+
const reactDoctorHookSites = new Set(diagnostics.filter(isReactDoctorRulesOfHooksDiagnostic).map(buildDiagnosticSiteKey));
|
|
39623
|
+
const uniqueDiagnostics = [];
|
|
39624
|
+
for (const diagnostic of diagnostics) {
|
|
39625
|
+
if (isReactCompilerHooksDiagnostic(diagnostic) && reactDoctorHookSites.has(buildDiagnosticSiteKey(diagnostic))) continue;
|
|
39626
|
+
const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
|
|
39627
|
+
if (priority !== void 0) {
|
|
39628
|
+
const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
|
|
39629
|
+
if (overlappingDiagnosticIndex >= 0) {
|
|
39630
|
+
const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
|
|
39631
|
+
const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
|
|
39632
|
+
if (existingDiagnostic && existingPriority !== void 0) uniqueDiagnostics[overlappingDiagnosticIndex] = priority < existingPriority ? keepHighestSeverity(diagnostic, existingDiagnostic) : keepHighestSeverity(existingDiagnostic, diagnostic);
|
|
39633
|
+
continue;
|
|
39634
|
+
}
|
|
39635
|
+
}
|
|
39636
|
+
uniqueDiagnostics.push(diagnostic);
|
|
39637
|
+
}
|
|
39638
|
+
return uniqueDiagnostics;
|
|
39639
|
+
};
|
|
39595
39640
|
const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
39596
39641
|
const sortDiagnosticsStable = (diagnostics) => [...diagnostics].sort((left, right) => compareStrings(left.filePath, right.filePath) || left.line - right.line || left.column - right.column || compareStrings(left.plugin, right.plugin) || compareStrings(left.rule, right.rule) || compareStrings(left.severity, right.severity) || compareStrings(left.message, right.message));
|
|
39597
39642
|
const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
|
|
@@ -40017,7 +40062,6 @@ const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
|
40017
40062
|
const PNPM_LOCKFILE = "pnpm-lock.yaml";
|
|
40018
40063
|
const PACKAGE_JSON_FILE = "package.json";
|
|
40019
40064
|
const PNPM_HARDENING_RULE_KEY = "require-pnpm-hardening";
|
|
40020
|
-
const UTF8_BOM_CHAR = "";
|
|
40021
40065
|
const HARDENING_SETTING_KEYS = new Set([
|
|
40022
40066
|
"minimumReleaseAge",
|
|
40023
40067
|
"blockExoticSubdeps",
|
|
@@ -40042,12 +40086,11 @@ const stripInlineComment = (rawValue) => {
|
|
|
40042
40086
|
return rawValue;
|
|
40043
40087
|
};
|
|
40044
40088
|
const unquote = (rawValue) => rawValue.replace(/^["']|["']$/g, "");
|
|
40045
|
-
const stripBom = (rawContent) => rawContent.startsWith(UTF8_BOM_CHAR) ? rawContent.slice(1) : rawContent;
|
|
40046
40089
|
const parseHardeningSettings = (content) => {
|
|
40047
40090
|
let minimumReleaseAge = null;
|
|
40048
40091
|
let blockExoticSubdeps = null;
|
|
40049
40092
|
let trustPolicy = null;
|
|
40050
|
-
const lines =
|
|
40093
|
+
const lines = stripUtf8Bom(content).split(/\r?\n/);
|
|
40051
40094
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
40052
40095
|
const lineText = lines[lineIndex];
|
|
40053
40096
|
if (lineText === void 0) continue;
|
|
@@ -40081,7 +40124,7 @@ const isPnpmManagedProject = (rootDirectory) => {
|
|
|
40081
40124
|
const packageJsonPath = Path.join(rootDirectory, PACKAGE_JSON_FILE);
|
|
40082
40125
|
if (!isFile(packageJsonPath)) return false;
|
|
40083
40126
|
try {
|
|
40084
|
-
const packageJsonRaw = NFS.readFileSync(packageJsonPath, "utf-8");
|
|
40127
|
+
const packageJsonRaw = stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8"));
|
|
40085
40128
|
const packageJson = JSON.parse(packageJsonRaw);
|
|
40086
40129
|
if (packageJson !== null && typeof packageJson === "object" && "packageManager" in packageJson && typeof packageJson.packageManager === "string" && packageJson.packageManager.startsWith("pnpm@")) return true;
|
|
40087
40130
|
} catch {
|
|
@@ -40367,18 +40410,45 @@ const checkReactServerComponentsAdvisory = (scanDirectory, project) => {
|
|
|
40367
40410
|
}
|
|
40368
40411
|
return diagnostics;
|
|
40369
40412
|
};
|
|
40370
|
-
const
|
|
40371
|
-
|
|
40372
|
-
|
|
40413
|
+
const getTypescriptScriptKind = (fileName) => {
|
|
40414
|
+
if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
40415
|
+
if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
40416
|
+
if (fileName.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
40417
|
+
return ts.ScriptKind.JS;
|
|
40418
|
+
};
|
|
40419
|
+
const unwrapTypescriptExpression = (expression) => {
|
|
40420
|
+
let currentExpression = expression;
|
|
40421
|
+
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
40422
|
+
return currentExpression;
|
|
40423
|
+
};
|
|
40424
|
+
const EMPTY_MOTION_EXPRESSION_EVIDENCE = {
|
|
40425
|
+
isAnimationFunction: false,
|
|
40426
|
+
isMotionComponent: false,
|
|
40427
|
+
isMotionComponentFactory: false,
|
|
40428
|
+
isMotionComponentNamespace: false,
|
|
40429
|
+
isMotionConfig: false,
|
|
40430
|
+
isMotionNamespace: false,
|
|
40431
|
+
isReducedMotionHook: false
|
|
40432
|
+
};
|
|
40433
|
+
const MOTION_COMPONENT_FACTORY_EXPORT_NAMES = new Set(["m", "motion"]);
|
|
40434
|
+
const MOTION_COMPONENT_NAMESPACE_EXPORT_NAMES = new Set(["Reorder"]);
|
|
40435
|
+
const MOTION_COMPONENT_NAMESPACE_MEMBER_NAMES = new Set(["Item"]);
|
|
40436
|
+
const MOTION_ANIMATION_FUNCTION_EXPORT_NAMES = new Set(["animate"]);
|
|
40437
|
+
const REDUCED_MOTION_HOOK_EXPORT_NAME = "useReducedMotion";
|
|
40438
|
+
const MOTION_CONFIG_EXPORT_NAME = "MotionConfig";
|
|
40439
|
+
const REDUCED_MOTION_PROP_NAME = "reducedMotion";
|
|
40440
|
+
const REDUCED_MOTION_CONFIG_VALUES = new Set(["always", "user"]);
|
|
40441
|
+
const SCRIPT_MODULE_EXTENSIONS = [
|
|
40373
40442
|
".ts",
|
|
40374
40443
|
".tsx",
|
|
40375
40444
|
".js",
|
|
40376
|
-
".jsx"
|
|
40377
|
-
|
|
40378
|
-
|
|
40379
|
-
]);
|
|
40380
|
-
const
|
|
40381
|
-
const
|
|
40445
|
+
".jsx"
|
|
40446
|
+
];
|
|
40447
|
+
const SCRIPT_FILE_EXTENSIONS = new Set(SCRIPT_MODULE_EXTENSIONS);
|
|
40448
|
+
const STYLE_FILE_EXTENSIONS = new Set([".css", ".scss"]);
|
|
40449
|
+
const MOTION_SOURCE_PREFILTER = /framer-motion|["']motion(?:\/[A-Za-z0-9_./-]+)?["']|MotionConfig|useReducedMotion/;
|
|
40450
|
+
const REDUCED_MOTION_MEDIA_QUERY_PATTERN = /@media\s+([^{}]*\(\s*prefers-reduced-motion\s*:\s*reduce\s*\)[^{}]*)\{/gi;
|
|
40451
|
+
const CSS_DECLARATION_PATTERN = /(?:^|[;{])\s*[-_A-Za-z][-_A-Za-z0-9]*\s*:/;
|
|
40382
40452
|
const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
|
|
40383
40453
|
filePath: "package.json",
|
|
40384
40454
|
plugin: "react-doctor",
|
|
@@ -40390,52 +40460,406 @@ const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
|
|
|
40390
40460
|
column: 0,
|
|
40391
40461
|
category: "Accessibility"
|
|
40392
40462
|
};
|
|
40393
|
-
const
|
|
40463
|
+
const isMotionModuleSource = (moduleSource) => {
|
|
40464
|
+
for (const packageName of MOTION_LIBRARY_PACKAGES) if (moduleSource === packageName || moduleSource.startsWith(`${packageName}/`)) return true;
|
|
40465
|
+
return false;
|
|
40466
|
+
};
|
|
40467
|
+
const classifyMotionExport = (exportName) => ({
|
|
40468
|
+
isAnimationFunction: MOTION_ANIMATION_FUNCTION_EXPORT_NAMES.has(exportName),
|
|
40469
|
+
isMotionComponent: false,
|
|
40470
|
+
isMotionComponentFactory: MOTION_COMPONENT_FACTORY_EXPORT_NAMES.has(exportName),
|
|
40471
|
+
isMotionComponentNamespace: MOTION_COMPONENT_NAMESPACE_EXPORT_NAMES.has(exportName),
|
|
40472
|
+
isMotionConfig: exportName === MOTION_CONFIG_EXPORT_NAME,
|
|
40473
|
+
isMotionNamespace: false,
|
|
40474
|
+
isReducedMotionHook: exportName === REDUCED_MOTION_HOOK_EXPORT_NAME
|
|
40475
|
+
});
|
|
40476
|
+
const getImportModuleSource = (node) => {
|
|
40477
|
+
let currentNode = node;
|
|
40478
|
+
while (currentNode) {
|
|
40479
|
+
if (ts.isImportDeclaration(currentNode) && ts.isStringLiteral(currentNode.moduleSpecifier)) return currentNode.moduleSpecifier.text;
|
|
40480
|
+
currentNode = currentNode.parent;
|
|
40481
|
+
}
|
|
40482
|
+
return null;
|
|
40483
|
+
};
|
|
40484
|
+
const getImportedBindingEvidence = (declaration, typeChecker, program, visitedSymbols) => {
|
|
40485
|
+
const moduleSource = getImportModuleSource(declaration);
|
|
40486
|
+
if (!moduleSource) return null;
|
|
40487
|
+
if (isMotionModuleSource(moduleSource) && ts.isNamespaceImport(declaration)) return {
|
|
40488
|
+
...EMPTY_MOTION_EXPRESSION_EVIDENCE,
|
|
40489
|
+
isMotionNamespace: true
|
|
40490
|
+
};
|
|
40491
|
+
if (isMotionModuleSource(moduleSource) && ts.isImportSpecifier(declaration)) {
|
|
40492
|
+
if (declaration.isTypeOnly) return null;
|
|
40493
|
+
return classifyMotionExport(declaration.propertyName?.text ?? declaration.name.text);
|
|
40494
|
+
}
|
|
40495
|
+
if (isMotionModuleSource(moduleSource) && ts.isImportClause(declaration)) {
|
|
40496
|
+
if (declaration.isTypeOnly) return null;
|
|
40497
|
+
return EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
40498
|
+
}
|
|
40499
|
+
if (ts.isImportSpecifier(declaration) && !declaration.isTypeOnly || ts.isImportClause(declaration) && !declaration.isTypeOnly && declaration.name) {
|
|
40500
|
+
const importedName = ts.isImportSpecifier(declaration) ? declaration.propertyName?.text ?? declaration.name.text : "default";
|
|
40501
|
+
const moduleSourceFile = getLocalModuleSourceFile(moduleSource, declaration.getSourceFile().fileName, program);
|
|
40502
|
+
if (!moduleSourceFile) return null;
|
|
40503
|
+
return resolveModuleExportEvidence(moduleSourceFile, importedName, typeChecker, program, visitedSymbols);
|
|
40504
|
+
}
|
|
40505
|
+
return null;
|
|
40506
|
+
};
|
|
40507
|
+
const getLocalModuleSourceFile = (moduleSource, importingFileName, program) => {
|
|
40508
|
+
if (!moduleSource.startsWith(".")) return null;
|
|
40509
|
+
const moduleBasePath = Path.resolve(Path.dirname(importingFileName), moduleSource);
|
|
40510
|
+
const moduleBaseExtension = Path.extname(moduleBasePath);
|
|
40511
|
+
const typescriptModuleBasePath = moduleBaseExtension === ".js" || moduleBaseExtension === ".jsx" ? moduleBasePath.slice(0, -moduleBaseExtension.length) : null;
|
|
40512
|
+
const candidates = [
|
|
40513
|
+
moduleBasePath,
|
|
40514
|
+
...typescriptModuleBasePath ? [
|
|
40515
|
+
`${typescriptModuleBasePath}.ts`,
|
|
40516
|
+
`${typescriptModuleBasePath}.tsx`,
|
|
40517
|
+
Path.join(typescriptModuleBasePath, "index.ts"),
|
|
40518
|
+
Path.join(typescriptModuleBasePath, "index.tsx")
|
|
40519
|
+
] : [],
|
|
40520
|
+
...SCRIPT_MODULE_EXTENSIONS.map((extension) => `${moduleBasePath}${extension}`),
|
|
40521
|
+
...SCRIPT_MODULE_EXTENSIONS.map((extension) => Path.join(moduleBasePath, `index${extension}`))
|
|
40522
|
+
];
|
|
40523
|
+
for (const candidate of candidates) {
|
|
40524
|
+
const sourceFile = program.getSourceFile(candidate);
|
|
40525
|
+
if (sourceFile) return sourceFile;
|
|
40526
|
+
}
|
|
40527
|
+
return null;
|
|
40528
|
+
};
|
|
40529
|
+
const resolveModuleExportEvidence = (sourceFile, exportName, typeChecker, program, visitedSymbols) => {
|
|
40530
|
+
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
|
|
40531
|
+
if (moduleSymbol && visitedSymbols.has(moduleSymbol)) return EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
40532
|
+
const nextVisitedSymbols = new Set(visitedSymbols);
|
|
40533
|
+
if (moduleSymbol) nextVisitedSymbols.add(moduleSymbol);
|
|
40534
|
+
for (const statement of sourceFile.statements) {
|
|
40535
|
+
if (exportName === "default" && ts.isExportAssignment(statement) && !statement.isExportEquals) {
|
|
40536
|
+
const evidence = resolveMotionExpressionEvidence(statement.expression, typeChecker, program, nextVisitedSymbols);
|
|
40537
|
+
if (hasMotionExpressionEvidence(evidence)) return evidence;
|
|
40538
|
+
continue;
|
|
40539
|
+
}
|
|
40540
|
+
if (!ts.isExportDeclaration(statement) || !statement.moduleSpecifier) continue;
|
|
40541
|
+
if (statement.isTypeOnly) continue;
|
|
40542
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
40543
|
+
const moduleSource = statement.moduleSpecifier.text;
|
|
40544
|
+
if (!statement.exportClause) {
|
|
40545
|
+
if (isMotionModuleSource(moduleSource)) {
|
|
40546
|
+
const evidence = classifyMotionExport(exportName);
|
|
40547
|
+
if (hasMotionExpressionEvidence(evidence)) return evidence;
|
|
40548
|
+
continue;
|
|
40549
|
+
}
|
|
40550
|
+
const moduleSourceFile = getLocalModuleSourceFile(moduleSource, sourceFile.fileName, program);
|
|
40551
|
+
if (moduleSourceFile) {
|
|
40552
|
+
const evidence = resolveModuleExportEvidence(moduleSourceFile, exportName, typeChecker, program, nextVisitedSymbols);
|
|
40553
|
+
if (hasMotionExpressionEvidence(evidence)) return evidence;
|
|
40554
|
+
}
|
|
40555
|
+
continue;
|
|
40556
|
+
}
|
|
40557
|
+
if (!ts.isNamedExports(statement.exportClause)) continue;
|
|
40558
|
+
for (const exportSpecifier of statement.exportClause.elements) {
|
|
40559
|
+
if (exportSpecifier.name.text !== exportName || exportSpecifier.isTypeOnly) continue;
|
|
40560
|
+
const sourceExportName = exportSpecifier.propertyName?.text ?? exportSpecifier.name.text;
|
|
40561
|
+
if (isMotionModuleSource(moduleSource)) return classifyMotionExport(sourceExportName);
|
|
40562
|
+
const moduleSourceFile = getLocalModuleSourceFile(moduleSource, sourceFile.fileName, program);
|
|
40563
|
+
if (!moduleSourceFile) continue;
|
|
40564
|
+
return resolveModuleExportEvidence(moduleSourceFile, sourceExportName, typeChecker, program, nextVisitedSymbols);
|
|
40565
|
+
}
|
|
40566
|
+
}
|
|
40567
|
+
if (!moduleSymbol) return EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
40568
|
+
const exportedSymbol = typeChecker.getExportsOfModule(moduleSymbol).find((candidateSymbol) => candidateSymbol.name === exportName);
|
|
40569
|
+
if (!exportedSymbol || nextVisitedSymbols.has(exportedSymbol)) return EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
40570
|
+
nextVisitedSymbols.add(exportedSymbol);
|
|
40571
|
+
let resolvedEvidence = EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
40572
|
+
for (const declaration of exportedSymbol.declarations ?? []) {
|
|
40573
|
+
if (ts.isExportSpecifier(declaration)) {
|
|
40574
|
+
const targetSymbol = typeChecker.getExportSpecifierLocalTargetSymbol(declaration);
|
|
40575
|
+
for (const targetDeclaration of targetSymbol?.declarations ?? []) {
|
|
40576
|
+
const importedEvidence = getImportedBindingEvidence(targetDeclaration, typeChecker, program, nextVisitedSymbols);
|
|
40577
|
+
if (importedEvidence) resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, importedEvidence);
|
|
40578
|
+
if (ts.isVariableDeclaration(targetDeclaration) && targetDeclaration.initializer) resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, resolveMotionExpressionEvidence(targetDeclaration.initializer, typeChecker, program, nextVisitedSymbols));
|
|
40579
|
+
}
|
|
40580
|
+
}
|
|
40581
|
+
if (ts.isVariableDeclaration(declaration) && declaration.initializer) resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, resolveMotionExpressionEvidence(declaration.initializer, typeChecker, program, nextVisitedSymbols));
|
|
40582
|
+
}
|
|
40583
|
+
return resolvedEvidence;
|
|
40584
|
+
};
|
|
40585
|
+
const mergeMotionExpressionEvidence = (leftEvidence, rightEvidence) => ({
|
|
40586
|
+
isAnimationFunction: leftEvidence.isAnimationFunction || rightEvidence.isAnimationFunction,
|
|
40587
|
+
isMotionComponent: leftEvidence.isMotionComponent || rightEvidence.isMotionComponent,
|
|
40588
|
+
isMotionComponentFactory: leftEvidence.isMotionComponentFactory || rightEvidence.isMotionComponentFactory,
|
|
40589
|
+
isMotionComponentNamespace: leftEvidence.isMotionComponentNamespace || rightEvidence.isMotionComponentNamespace,
|
|
40590
|
+
isMotionConfig: leftEvidence.isMotionConfig || rightEvidence.isMotionConfig,
|
|
40591
|
+
isMotionNamespace: leftEvidence.isMotionNamespace || rightEvidence.isMotionNamespace,
|
|
40592
|
+
isReducedMotionHook: leftEvidence.isReducedMotionHook || rightEvidence.isReducedMotionHook
|
|
40593
|
+
});
|
|
40594
|
+
const hasMotionExpressionEvidence = (evidence) => evidence.isAnimationFunction || evidence.isMotionComponent || evidence.isMotionComponentFactory || evidence.isMotionComponentNamespace || evidence.isMotionConfig || evidence.isMotionNamespace || evidence.isReducedMotionHook;
|
|
40595
|
+
const classifyMotionMember = (receiverEvidence, memberName) => {
|
|
40596
|
+
if (receiverEvidence.isMotionNamespace) return classifyMotionExport(memberName);
|
|
40597
|
+
if (receiverEvidence.isMotionComponentFactory) return {
|
|
40598
|
+
...EMPTY_MOTION_EXPRESSION_EVIDENCE,
|
|
40599
|
+
isMotionComponent: true
|
|
40600
|
+
};
|
|
40601
|
+
if (receiverEvidence.isMotionComponentNamespace && MOTION_COMPONENT_NAMESPACE_MEMBER_NAMES.has(memberName)) return {
|
|
40602
|
+
...EMPTY_MOTION_EXPRESSION_EVIDENCE,
|
|
40603
|
+
isMotionComponent: true
|
|
40604
|
+
};
|
|
40605
|
+
return EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
40606
|
+
};
|
|
40607
|
+
const resolveMotionExpressionEvidence = (expression, typeChecker, program, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
40608
|
+
const unwrappedExpression = unwrapTypescriptExpression(expression);
|
|
40609
|
+
if (ts.isIdentifier(unwrappedExpression)) {
|
|
40610
|
+
const symbol = typeChecker.getSymbolAtLocation(unwrappedExpression);
|
|
40611
|
+
if (!symbol || visitedSymbols.has(symbol)) return EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
40612
|
+
const nextVisitedSymbols = new Set(visitedSymbols);
|
|
40613
|
+
nextVisitedSymbols.add(symbol);
|
|
40614
|
+
let resolvedEvidence = EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
40615
|
+
for (const declaration of symbol.declarations ?? []) {
|
|
40616
|
+
const importedEvidence = getImportedBindingEvidence(declaration, typeChecker, program, nextVisitedSymbols);
|
|
40617
|
+
if (importedEvidence) {
|
|
40618
|
+
resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, importedEvidence);
|
|
40619
|
+
continue;
|
|
40620
|
+
}
|
|
40621
|
+
if (ts.isVariableDeclaration(declaration) && declaration.initializer) resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, resolveMotionExpressionEvidence(declaration.initializer, typeChecker, program, nextVisitedSymbols));
|
|
40622
|
+
if (ts.isBindingElement(declaration) && ts.isObjectBindingPattern(declaration.parent) && ts.isVariableDeclaration(declaration.parent.parent) && declaration.parent.parent.initializer) {
|
|
40623
|
+
const propertyName = declaration.propertyName ?? declaration.name;
|
|
40624
|
+
if (ts.isIdentifier(propertyName) || ts.isStringLiteral(propertyName)) {
|
|
40625
|
+
const receiverEvidence = resolveMotionExpressionEvidence(declaration.parent.parent.initializer, typeChecker, program, nextVisitedSymbols);
|
|
40626
|
+
resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, classifyMotionMember(receiverEvidence, propertyName.text));
|
|
40627
|
+
}
|
|
40628
|
+
}
|
|
40629
|
+
}
|
|
40630
|
+
return resolvedEvidence;
|
|
40631
|
+
}
|
|
40632
|
+
if (ts.isPropertyAccessExpression(unwrappedExpression)) return classifyMotionMember(resolveMotionExpressionEvidence(unwrappedExpression.expression, typeChecker, program, visitedSymbols), unwrappedExpression.name.text);
|
|
40633
|
+
if (ts.isElementAccessExpression(unwrappedExpression) && unwrappedExpression.argumentExpression && (ts.isStringLiteral(unwrappedExpression.argumentExpression) || ts.isNoSubstitutionTemplateLiteral(unwrappedExpression.argumentExpression))) return classifyMotionMember(resolveMotionExpressionEvidence(unwrappedExpression.expression, typeChecker, program, visitedSymbols), unwrappedExpression.argumentExpression.text);
|
|
40634
|
+
if (ts.isCallExpression(unwrappedExpression)) {
|
|
40635
|
+
if (ts.isIdentifier(unwrappedExpression.expression) && unwrappedExpression.expression.text === "require" && !typeChecker.getSymbolAtLocation(unwrappedExpression.expression) && unwrappedExpression.arguments.length === 1 && ts.isStringLiteral(unwrappedExpression.arguments[0]) && isMotionModuleSource(unwrappedExpression.arguments[0].text)) return {
|
|
40636
|
+
...EMPTY_MOTION_EXPRESSION_EVIDENCE,
|
|
40637
|
+
isMotionNamespace: true
|
|
40638
|
+
};
|
|
40639
|
+
if (ts.isPropertyAccessExpression(unwrappedExpression.expression) && unwrappedExpression.expression.name.text === "bind") {
|
|
40640
|
+
const boundReceiverEvidence = resolveMotionExpressionEvidence(unwrappedExpression.expression.expression, typeChecker, program, visitedSymbols);
|
|
40641
|
+
if (boundReceiverEvidence.isAnimationFunction) return boundReceiverEvidence;
|
|
40642
|
+
}
|
|
40643
|
+
const calleeEvidence = resolveMotionExpressionEvidence(unwrappedExpression.expression, typeChecker, program, visitedSymbols);
|
|
40644
|
+
if (calleeEvidence.isMotionComponentFactory) return {
|
|
40645
|
+
...EMPTY_MOTION_EXPRESSION_EVIDENCE,
|
|
40646
|
+
isMotionComponent: true
|
|
40647
|
+
};
|
|
40648
|
+
return calleeEvidence;
|
|
40649
|
+
}
|
|
40650
|
+
return EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
40651
|
+
};
|
|
40652
|
+
const getStaticStringExpressionValue = (expression, typeChecker, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
40653
|
+
const unwrappedExpression = unwrapTypescriptExpression(expression);
|
|
40654
|
+
if (ts.isStringLiteral(unwrappedExpression) || ts.isNoSubstitutionTemplateLiteral(unwrappedExpression)) return unwrappedExpression.text;
|
|
40655
|
+
if (!ts.isIdentifier(unwrappedExpression)) return null;
|
|
40656
|
+
const symbol = typeChecker.getSymbolAtLocation(unwrappedExpression);
|
|
40657
|
+
if (!symbol || visitedSymbols.has(symbol)) return null;
|
|
40658
|
+
const nextVisitedSymbols = new Set(visitedSymbols);
|
|
40659
|
+
nextVisitedSymbols.add(symbol);
|
|
40660
|
+
for (const declaration of symbol.declarations ?? []) {
|
|
40661
|
+
if (!ts.isVariableDeclaration(declaration) || !declaration.initializer) continue;
|
|
40662
|
+
if (!ts.isVariableDeclarationList(declaration.parent)) continue;
|
|
40663
|
+
if (!(declaration.parent.flags & ts.NodeFlags.Const)) continue;
|
|
40664
|
+
const value = getStaticStringExpressionValue(declaration.initializer, typeChecker, nextVisitedSymbols);
|
|
40665
|
+
if (value !== null) return value;
|
|
40666
|
+
}
|
|
40667
|
+
return null;
|
|
40668
|
+
};
|
|
40669
|
+
const getStaticJsxAttributeValue = (attribute, typeChecker) => {
|
|
40670
|
+
if (!attribute.initializer) return null;
|
|
40671
|
+
if (ts.isStringLiteral(attribute.initializer)) return attribute.initializer.text;
|
|
40672
|
+
if (!ts.isJsxExpression(attribute.initializer) || !attribute.initializer.expression) return null;
|
|
40673
|
+
return getStaticStringExpressionValue(attribute.initializer.expression, typeChecker);
|
|
40674
|
+
};
|
|
40675
|
+
const isExpressionValueConsumed = (expression) => {
|
|
40676
|
+
let currentExpression = expression;
|
|
40677
|
+
while (ts.isParenthesizedExpression(currentExpression.parent) || ts.isAsExpression(currentExpression.parent) || ts.isSatisfiesExpression(currentExpression.parent) || ts.isNonNullExpression(currentExpression.parent) || ts.isTypeAssertionExpression(currentExpression.parent)) currentExpression = currentExpression.parent;
|
|
40678
|
+
return !ts.isExpressionStatement(currentExpression.parent) && !ts.isVoidExpression(currentExpression.parent);
|
|
40679
|
+
};
|
|
40680
|
+
const jsxElementHasReducedMotionConfiguration = (attributes, typeChecker) => attributes.properties.some((attribute) => ts.isJsxAttribute(attribute) && ts.isIdentifier(attribute.name) && attribute.name.text === REDUCED_MOTION_PROP_NAME && REDUCED_MOTION_CONFIG_VALUES.has(getStaticJsxAttributeValue(attribute, typeChecker) ?? ""));
|
|
40681
|
+
const collectScriptMotionEvidence = (sourceFile, typeChecker, program) => {
|
|
40682
|
+
const evidence = {
|
|
40683
|
+
hasMotionUse: false,
|
|
40684
|
+
hasReducedMotionHandling: false
|
|
40685
|
+
};
|
|
40686
|
+
const visitNode = (node) => {
|
|
40687
|
+
if (ts.isCallExpression(node)) {
|
|
40688
|
+
const calleeEvidence = resolveMotionExpressionEvidence(node.expression, typeChecker, program);
|
|
40689
|
+
const isAnimationFunctionCallViaCallOrApply = (ts.isPropertyAccessExpression(node.expression) && (node.expression.name.text === "call" || node.expression.name.text === "apply") || ts.isElementAccessExpression(node.expression) && node.expression.argumentExpression && (ts.isStringLiteral(node.expression.argumentExpression) || ts.isNoSubstitutionTemplateLiteral(node.expression.argumentExpression)) && (node.expression.argumentExpression.text === "call" || node.expression.argumentExpression.text === "apply")) && resolveMotionExpressionEvidence(node.expression.expression, typeChecker, program).isAnimationFunction;
|
|
40690
|
+
if (calleeEvidence.isAnimationFunction || isAnimationFunctionCallViaCallOrApply) evidence.hasMotionUse = true;
|
|
40691
|
+
if (calleeEvidence.isReducedMotionHook && isExpressionValueConsumed(node)) evidence.hasReducedMotionHandling = true;
|
|
40692
|
+
}
|
|
40693
|
+
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
40694
|
+
const tagEvidence = ts.isJsxNamespacedName(node.tagName) ? EMPTY_MOTION_EXPRESSION_EVIDENCE : resolveMotionExpressionEvidence(node.tagName, typeChecker, program);
|
|
40695
|
+
if (tagEvidence.isMotionComponent || tagEvidence.isMotionComponentFactory) evidence.hasMotionUse = true;
|
|
40696
|
+
if (tagEvidence.isMotionConfig && jsxElementHasReducedMotionConfiguration(node.attributes, typeChecker)) evidence.hasReducedMotionHandling = true;
|
|
40697
|
+
}
|
|
40698
|
+
ts.forEachChild(node, visitNode);
|
|
40699
|
+
};
|
|
40700
|
+
visitNode(sourceFile);
|
|
40701
|
+
return evidence;
|
|
40702
|
+
};
|
|
40703
|
+
const analyzeReducedMotionSources = (sources) => {
|
|
40704
|
+
if (!sources.some(({ sourceText }) => MOTION_SOURCE_PREFILTER.test(sourceText))) return {
|
|
40705
|
+
hasMotionUse: false,
|
|
40706
|
+
hasReducedMotionHandling: false
|
|
40707
|
+
};
|
|
40708
|
+
const compilerOptions = {
|
|
40709
|
+
allowJs: true,
|
|
40710
|
+
checkJs: false,
|
|
40711
|
+
jsx: ts.JsxEmit.Preserve,
|
|
40712
|
+
module: ts.ModuleKind.ESNext,
|
|
40713
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
40714
|
+
noEmit: true,
|
|
40715
|
+
noLib: true,
|
|
40716
|
+
noResolve: true,
|
|
40717
|
+
skipLibCheck: true,
|
|
40718
|
+
target: ts.ScriptTarget.Latest
|
|
40719
|
+
};
|
|
40720
|
+
const sourceTextByFileName = new Map(sources.map(({ fileName: sourceFileName, sourceText: content }) => [Path.resolve(sourceFileName), content]));
|
|
40721
|
+
const compilerHost = ts.createCompilerHost(compilerOptions);
|
|
40722
|
+
const getDefaultSourceFile = compilerHost.getSourceFile.bind(compilerHost);
|
|
40723
|
+
compilerHost.getSourceFile = (requestedFileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => sourceTextByFileName.has(Path.resolve(requestedFileName)) ? ts.createSourceFile(Path.resolve(requestedFileName), sourceTextByFileName.get(Path.resolve(requestedFileName)) ?? "", languageVersionOrOptions, true, getTypescriptScriptKind(requestedFileName)) : getDefaultSourceFile(requestedFileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
|
|
40724
|
+
const program = ts.createProgram([...sourceTextByFileName.keys()], compilerOptions, compilerHost);
|
|
40725
|
+
const typeChecker = program.getTypeChecker();
|
|
40726
|
+
const evidence = {
|
|
40727
|
+
hasMotionUse: false,
|
|
40728
|
+
hasReducedMotionHandling: false
|
|
40729
|
+
};
|
|
40730
|
+
for (const sourceFileName of sourceTextByFileName.keys()) {
|
|
40731
|
+
const sourceFile = program.getSourceFile(sourceFileName);
|
|
40732
|
+
if (!sourceFile) continue;
|
|
40733
|
+
const fileEvidence = collectScriptMotionEvidence(sourceFile, typeChecker, program);
|
|
40734
|
+
evidence.hasMotionUse ||= fileEvidence.hasMotionUse;
|
|
40735
|
+
evidence.hasReducedMotionHandling ||= fileEvidence.hasReducedMotionHandling;
|
|
40736
|
+
}
|
|
40737
|
+
return evidence;
|
|
40738
|
+
};
|
|
40739
|
+
const removeCssCommentsAndStrings = (content) => {
|
|
40740
|
+
let sanitizedContent = "";
|
|
40741
|
+
let quote = null;
|
|
40742
|
+
let isInsideComment = false;
|
|
40743
|
+
let isInsideLineComment = false;
|
|
40744
|
+
for (let characterIndex = 0; characterIndex < content.length; characterIndex += 1) {
|
|
40745
|
+
const character = content[characterIndex];
|
|
40746
|
+
const nextCharacter = content[characterIndex + 1];
|
|
40747
|
+
if (isInsideLineComment) {
|
|
40748
|
+
sanitizedContent += character === "\n" ? "\n" : " ";
|
|
40749
|
+
if (character === "\n") isInsideLineComment = false;
|
|
40750
|
+
continue;
|
|
40751
|
+
}
|
|
40752
|
+
if (isInsideComment) {
|
|
40753
|
+
if (character === "*" && nextCharacter === "/") {
|
|
40754
|
+
sanitizedContent += " ";
|
|
40755
|
+
characterIndex += 1;
|
|
40756
|
+
isInsideComment = false;
|
|
40757
|
+
} else sanitizedContent += character === "\n" ? "\n" : " ";
|
|
40758
|
+
continue;
|
|
40759
|
+
}
|
|
40760
|
+
if (quote) {
|
|
40761
|
+
if (character === "\\") {
|
|
40762
|
+
sanitizedContent += " ";
|
|
40763
|
+
characterIndex += 1;
|
|
40764
|
+
continue;
|
|
40765
|
+
}
|
|
40766
|
+
if (character === quote) quote = null;
|
|
40767
|
+
sanitizedContent += character === "\n" ? "\n" : " ";
|
|
40768
|
+
continue;
|
|
40769
|
+
}
|
|
40770
|
+
if (character === "/" && nextCharacter === "*") {
|
|
40771
|
+
sanitizedContent += " ";
|
|
40772
|
+
characterIndex += 1;
|
|
40773
|
+
isInsideComment = true;
|
|
40774
|
+
continue;
|
|
40775
|
+
}
|
|
40776
|
+
if (character === "/" && nextCharacter === "/") {
|
|
40777
|
+
sanitizedContent += " ";
|
|
40778
|
+
characterIndex += 1;
|
|
40779
|
+
isInsideLineComment = true;
|
|
40780
|
+
continue;
|
|
40781
|
+
}
|
|
40782
|
+
if (character === "\"" || character === "'") {
|
|
40783
|
+
sanitizedContent += " ";
|
|
40784
|
+
quote = character;
|
|
40785
|
+
continue;
|
|
40786
|
+
}
|
|
40787
|
+
sanitizedContent += character;
|
|
40788
|
+
}
|
|
40789
|
+
return sanitizedContent;
|
|
40790
|
+
};
|
|
40791
|
+
const findMatchingClosingBrace = (content, openingBraceIndex) => {
|
|
40792
|
+
let depth = 0;
|
|
40793
|
+
for (let characterIndex = openingBraceIndex; characterIndex < content.length; characterIndex += 1) {
|
|
40794
|
+
if (content[characterIndex] === "{") depth += 1;
|
|
40795
|
+
if (content[characterIndex] !== "}") continue;
|
|
40796
|
+
depth -= 1;
|
|
40797
|
+
if (depth === 0) return characterIndex;
|
|
40798
|
+
}
|
|
40799
|
+
return -1;
|
|
40800
|
+
};
|
|
40801
|
+
const hasReducedMotionMediaQuery = (content) => {
|
|
40802
|
+
const sanitizedContent = removeCssCommentsAndStrings(content);
|
|
40803
|
+
REDUCED_MOTION_MEDIA_QUERY_PATTERN.lastIndex = 0;
|
|
40804
|
+
for (const match of sanitizedContent.matchAll(REDUCED_MOTION_MEDIA_QUERY_PATTERN)) {
|
|
40805
|
+
const mediaPrelude = match[1];
|
|
40806
|
+
if (/\bnot\b/i.test(mediaPrelude)) continue;
|
|
40807
|
+
const openingBraceIndex = (match.index ?? 0) + match[0].lastIndexOf("{");
|
|
40808
|
+
const closingBraceIndex = findMatchingClosingBrace(sanitizedContent, openingBraceIndex);
|
|
40809
|
+
if (closingBraceIndex === -1) continue;
|
|
40810
|
+
const body = sanitizedContent.slice(openingBraceIndex + 1, closingBraceIndex);
|
|
40811
|
+
if (CSS_DECLARATION_PATTERN.test(body)) return true;
|
|
40812
|
+
}
|
|
40813
|
+
return false;
|
|
40814
|
+
};
|
|
40815
|
+
const collectProjectMotionEvidence = (rootDirectory) => {
|
|
40816
|
+
const scriptSources = [];
|
|
40817
|
+
let hasReducedMotionHandling = false;
|
|
40394
40818
|
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
|
|
40395
|
-
|
|
40819
|
+
const extension = Path.extname(name);
|
|
40820
|
+
if (!SCRIPT_FILE_EXTENSIONS.has(extension) && !STYLE_FILE_EXTENSIONS.has(extension)) continue;
|
|
40396
40821
|
let content;
|
|
40397
40822
|
try {
|
|
40398
40823
|
content = NFS.readFileSync(absolutePath, "utf-8");
|
|
40399
40824
|
} catch {
|
|
40400
40825
|
continue;
|
|
40401
40826
|
}
|
|
40402
|
-
if (
|
|
40827
|
+
if (STYLE_FILE_EXTENSIONS.has(extension)) {
|
|
40828
|
+
if (hasReducedMotionMediaQuery(content)) hasReducedMotionHandling = true;
|
|
40829
|
+
continue;
|
|
40830
|
+
}
|
|
40831
|
+
scriptSources.push({
|
|
40832
|
+
fileName: absolutePath,
|
|
40833
|
+
sourceText: content
|
|
40834
|
+
});
|
|
40403
40835
|
}
|
|
40404
|
-
return
|
|
40836
|
+
if (scriptSources.length === 0) return {
|
|
40837
|
+
hasMotionUse: false,
|
|
40838
|
+
hasReducedMotionHandling
|
|
40839
|
+
};
|
|
40840
|
+
const scriptEvidence = analyzeReducedMotionSources(scriptSources);
|
|
40841
|
+
const hasMotionUse = scriptEvidence.hasMotionUse;
|
|
40842
|
+
hasReducedMotionHandling ||= scriptEvidence.hasReducedMotionHandling;
|
|
40843
|
+
return {
|
|
40844
|
+
hasMotionUse,
|
|
40845
|
+
hasReducedMotionHandling
|
|
40846
|
+
};
|
|
40405
40847
|
};
|
|
40406
40848
|
const checkReducedMotion = (rootDirectory) => {
|
|
40407
40849
|
const packageJsonPath = Path.join(rootDirectory, "package.json");
|
|
40408
40850
|
if (!isFile(packageJsonPath)) return [];
|
|
40409
|
-
let hasMotionLibrary = false;
|
|
40410
40851
|
try {
|
|
40411
40852
|
const packageJson = readPackageJson$1(packageJsonPath);
|
|
40412
40853
|
const allDependencies = {
|
|
40413
40854
|
...packageJson.dependencies,
|
|
40414
40855
|
...packageJson.devDependencies
|
|
40415
40856
|
};
|
|
40416
|
-
|
|
40857
|
+
if (!Object.keys(allDependencies).some((packageName) => MOTION_LIBRARY_PACKAGES.has(packageName))) return [];
|
|
40417
40858
|
} catch {
|
|
40418
40859
|
return [];
|
|
40419
40860
|
}
|
|
40420
|
-
|
|
40421
|
-
|
|
40422
|
-
"grep",
|
|
40423
|
-
"--untracked",
|
|
40424
|
-
"-lE",
|
|
40425
|
-
REDUCED_MOTION_GREP_PATTERN,
|
|
40426
|
-
"--",
|
|
40427
|
-
...REDUCED_MOTION_FILE_GLOBS
|
|
40428
|
-
], {
|
|
40429
|
-
cwd: rootDirectory,
|
|
40430
|
-
encoding: "utf-8",
|
|
40431
|
-
stdio: [
|
|
40432
|
-
"ignore",
|
|
40433
|
-
"pipe",
|
|
40434
|
-
"pipe"
|
|
40435
|
-
]
|
|
40436
|
-
});
|
|
40437
|
-
if (!(!result.error && (result.status === 0 || result.status === GIT_GREP_NO_MATCH_STATUS))) return hasReducedMotionHandlingViaFilesystem(rootDirectory) ? [] : [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
40438
|
-
return result.status === 0 && result.stdout.split("\n").some((filePath) => filePath.length > 0 && !hasIgnoredPathSegment(filePath)) ? [] : [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
40861
|
+
const evidence = collectProjectMotionEvidence(rootDirectory);
|
|
40862
|
+
return evidence.hasMotionUse && !evidence.hasReducedMotionHandling ? [MISSING_REDUCED_MOTION_DIAGNOSTIC] : [];
|
|
40439
40863
|
};
|
|
40440
40864
|
const buildSecurityScanDiagnostic = (finding, entry, relativePath) => ({
|
|
40441
40865
|
filePath: relativePath,
|
|
@@ -40762,9 +41186,7 @@ const setColorEnabled = (enabled) => {
|
|
|
40762
41186
|
highlighter.gray = colors.gray;
|
|
40763
41187
|
highlighter.bold = colors.bold;
|
|
40764
41188
|
};
|
|
40765
|
-
const
|
|
40766
|
-
const shouldIncludeExplicitLintPath = (filePath, project) => JSX_FILE_PATTERN.test(filePath) || project?.framework === "nextjs" && NEXT_ENTRY_FILE_PATTERN.test(filePath);
|
|
40767
|
-
const computeExplicitLintIncludePaths = (includePaths, project) => includePaths.length > 0 ? includePaths.filter((filePath) => shouldIncludeExplicitLintPath(filePath, project)) : void 0;
|
|
41189
|
+
const computeExplicitLintIncludePaths = (includePaths) => includePaths.length > 0 ? includePaths.filter(isLintableSourceFile) : void 0;
|
|
40768
41190
|
const LINGUIST_ATTRIBUTE_PATTERN = /^linguist-(?:vendored|generated)(?:=([a-zA-Z0-9]+))?$/i;
|
|
40769
41191
|
const FALSY_VALUES = new Set([
|
|
40770
41192
|
"false",
|
|
@@ -41603,11 +42025,11 @@ const resolveDeadCodeTimeout = (input) => {
|
|
|
41603
42025
|
phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
|
|
41604
42026
|
};
|
|
41605
42027
|
};
|
|
41606
|
-
const resolveLintIncludePaths = (rootDirectory, userConfig
|
|
42028
|
+
const resolveLintIncludePaths = (rootDirectory, userConfig) => {
|
|
41607
42029
|
if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
|
|
41608
42030
|
const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
|
|
41609
42031
|
return listSourceFiles(rootDirectory).filter((filePath) => {
|
|
41610
|
-
if (!
|
|
42032
|
+
if (!isLintableSourceFile(filePath)) return false;
|
|
41611
42033
|
return !isFileIgnoredByPatterns(filePath, rootDirectory, ignoredPatterns);
|
|
41612
42034
|
});
|
|
41613
42035
|
};
|
|
@@ -41825,6 +42247,7 @@ const parseGithubViewerPermission = (stdout) => {
|
|
|
41825
42247
|
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
41826
42248
|
};
|
|
41827
42249
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
42250
|
+
const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
|
|
41828
42251
|
/**
|
|
41829
42252
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
41830
42253
|
* behind a `Context.Service`. The production layer (`layerNode`)
|
|
@@ -41895,6 +42318,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
41895
42318
|
args,
|
|
41896
42319
|
directory
|
|
41897
42320
|
});
|
|
42321
|
+
const listUntrackedFilePaths = (directory, includePaths = []) => runGit(directory, [
|
|
42322
|
+
"ls-files",
|
|
42323
|
+
"-z",
|
|
42324
|
+
"--others",
|
|
42325
|
+
"--exclude-standard",
|
|
42326
|
+
...includePaths.length > 0 ? ["--", ...includePaths] : []
|
|
42327
|
+
]).pipe(map$3((result) => result.status === 0 ? splitNullSeparated(result.stdout) : null));
|
|
42328
|
+
const mergeUntracked = (directory, trackedFilePaths, includeUntracked) => includeUntracked ? listUntrackedFilePaths(directory).pipe(map$3((untracked) => untracked === null ? trackedFilePaths : [...new Set([...trackedFilePaths, ...untracked])])) : succeed$2(trackedFilePaths);
|
|
41898
42329
|
const currentBranch = (directory) => runGit(directory, [
|
|
41899
42330
|
"rev-parse",
|
|
41900
42331
|
"--abbrev-ref",
|
|
@@ -42017,7 +42448,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42017
42448
|
githubViewerPermission,
|
|
42018
42449
|
branchExists,
|
|
42019
42450
|
mergeBase,
|
|
42020
|
-
diffSelection: ({ directory, explicitBaseBranch }) => gen(function* () {
|
|
42451
|
+
diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
|
|
42021
42452
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
42022
42453
|
if (explicitBaseBranch !== void 0) {
|
|
42023
42454
|
const range = parseGitDiffRange(explicitBaseBranch);
|
|
@@ -42046,7 +42477,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42046
42477
|
"HEAD"
|
|
42047
42478
|
]);
|
|
42048
42479
|
if (uncommitted.status !== 0) return null;
|
|
42049
|
-
const files = splitNullSeparated(uncommitted.stdout);
|
|
42480
|
+
const files = yield* mergeUntracked(directory, splitNullSeparated(uncommitted.stdout), includeUntracked);
|
|
42050
42481
|
if (files.length === 0) return null;
|
|
42051
42482
|
return {
|
|
42052
42483
|
currentBranch: resolvedCurrentBranch,
|
|
@@ -42076,7 +42507,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42076
42507
|
currentBranch: resolvedCurrentBranch,
|
|
42077
42508
|
baseBranch,
|
|
42078
42509
|
diffBaseRef: mergeBaseRef,
|
|
42079
|
-
changedFiles: splitNullSeparated(diff.stdout),
|
|
42510
|
+
changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
|
|
42080
42511
|
isCurrentChanges: false
|
|
42081
42512
|
};
|
|
42082
42513
|
}).pipe(withSpan("Git.diffSelection")),
|
|
@@ -42122,7 +42553,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42122
42553
|
stdout: result.stdout
|
|
42123
42554
|
};
|
|
42124
42555
|
}).pipe(withSpan("Git.grep")),
|
|
42125
|
-
changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
|
|
42556
|
+
changedLineRanges: ({ directory, baseRef, cached, files, includeUntracked = false }) => gen(function* () {
|
|
42126
42557
|
if (files.length === 0) return [];
|
|
42127
42558
|
if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
|
|
42128
42559
|
const result = yield* runGit(directory, [
|
|
@@ -42136,7 +42567,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42136
42567
|
...files
|
|
42137
42568
|
]);
|
|
42138
42569
|
if (result.status !== 0) return null;
|
|
42139
|
-
|
|
42570
|
+
const changedLineRanges = parseChangedLineRanges(result.stdout);
|
|
42571
|
+
if (cached || !includeUntracked) return changedLineRanges;
|
|
42572
|
+
const untrackedFilePaths = yield* listUntrackedFilePaths(directory, files);
|
|
42573
|
+
if (untrackedFilePaths === null) return changedLineRanges;
|
|
42574
|
+
return [...changedLineRanges, ...untrackedFilePaths.map((file) => ({
|
|
42575
|
+
file,
|
|
42576
|
+
ranges: [[1, UNTRACKED_FILE_LAST_LINE]]
|
|
42577
|
+
}))];
|
|
42140
42578
|
}).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
|
|
42141
42579
|
});
|
|
42142
42580
|
})).pipe(provide$2(layer$3.pipe(provide$2(mergeAll$1(layer$2, layer$1)))));
|
|
@@ -42721,13 +43159,13 @@ const encodeSidecarFileEntry = (entry) => ({
|
|
|
42721
43159
|
diagnostics: entry.diagnostics
|
|
42722
43160
|
});
|
|
42723
43161
|
const emptyCache = () => ({
|
|
42724
|
-
version:
|
|
43162
|
+
version: 2,
|
|
42725
43163
|
rulesets: {}
|
|
42726
43164
|
});
|
|
42727
43165
|
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
42728
43166
|
const entries = /* @__PURE__ */ new Map();
|
|
42729
43167
|
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
42730
|
-
if (persisted.version !==
|
|
43168
|
+
if (persisted.version !== 2 || !isRecord$1(persisted.rulesets)) return entries;
|
|
42731
43169
|
const bucket = persisted.rulesets[rulesetHash];
|
|
42732
43170
|
if (!isRecord$1(bucket) || !isRecord$1(bucket.files)) return entries;
|
|
42733
43171
|
for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
|
|
@@ -42747,7 +43185,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
42747
43185
|
},
|
|
42748
43186
|
persist: () => {
|
|
42749
43187
|
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
42750
|
-
const rulesets = onDisk.version ===
|
|
43188
|
+
const rulesets = onDisk.version === 2 && isRecord$1(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
42751
43189
|
const existingBucket = rulesets[rulesetHash];
|
|
42752
43190
|
const existingFiles = isRecord$1(existingBucket) && isRecord$1(existingBucket.files) ? existingBucket.files : {};
|
|
42753
43191
|
const ourFiles = {};
|
|
@@ -42761,7 +43199,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
42761
43199
|
files: Object.fromEntries(cappedEntries)
|
|
42762
43200
|
};
|
|
42763
43201
|
atomicWriteJson(cacheFilePath, {
|
|
42764
|
-
version:
|
|
43202
|
+
version: 2,
|
|
42765
43203
|
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord$1(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
42766
43204
|
});
|
|
42767
43205
|
}
|
|
@@ -42812,23 +43250,6 @@ const resolveTsConfigRelativePath = (rootDirectory) => {
|
|
|
42812
43250
|
for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
|
|
42813
43251
|
return null;
|
|
42814
43252
|
};
|
|
42815
|
-
const DERIVED_STATE_RULE_PRIORITY = new Map([
|
|
42816
|
-
["no-initialize-state", 0],
|
|
42817
|
-
["no-adjust-state-on-prop-change", 1],
|
|
42818
|
-
["no-derived-state", 2],
|
|
42819
|
-
["no-derived-state-effect", 3]
|
|
42820
|
-
]);
|
|
42821
|
-
const doDiagnosticSpansOverlap = (first, second) => {
|
|
42822
|
-
if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
|
|
42823
|
-
if (first.offset !== void 0 && second.offset !== void 0) {
|
|
42824
|
-
const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
|
|
42825
|
-
const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
|
|
42826
|
-
return first.offset < secondEnd && second.offset < firstEnd;
|
|
42827
|
-
}
|
|
42828
|
-
const firstEndLine = first.endLine ?? first.line;
|
|
42829
|
-
const secondEndLine = second.endLine ?? second.line;
|
|
42830
|
-
return first.line <= secondEndLine && second.line <= firstEndLine;
|
|
42831
|
-
};
|
|
42832
43253
|
const dedupeDiagnostics = (diagnostics) => {
|
|
42833
43254
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
42834
43255
|
const uniqueDiagnostics = [];
|
|
@@ -42836,16 +43257,6 @@ const dedupeDiagnostics = (diagnostics) => {
|
|
|
42836
43257
|
const key = `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}\u0000${diagnostic.plugin}\u0000${diagnostic.rule}\u0000${diagnostic.severity}\u0000${diagnostic.message}`;
|
|
42837
43258
|
if (seenKeys.has(key)) continue;
|
|
42838
43259
|
seenKeys.add(key);
|
|
42839
|
-
const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
|
|
42840
|
-
if (priority !== void 0) {
|
|
42841
|
-
const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && candidate.severity === diagnostic.severity && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
|
|
42842
|
-
if (overlappingDiagnosticIndex >= 0) {
|
|
42843
|
-
const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
|
|
42844
|
-
const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
|
|
42845
|
-
if (existingPriority !== void 0 && priority < existingPriority) uniqueDiagnostics[overlappingDiagnosticIndex] = diagnostic;
|
|
42846
|
-
continue;
|
|
42847
|
-
}
|
|
42848
|
-
}
|
|
42849
43260
|
uniqueDiagnostics.push(diagnostic);
|
|
42850
43261
|
}
|
|
42851
43262
|
return uniqueDiagnostics;
|
|
@@ -42888,6 +43299,12 @@ const mapWithConcurrency = async (items, concurrency, task) => {
|
|
|
42888
43299
|
if (errors.length > 0) throw errors[0];
|
|
42889
43300
|
return results;
|
|
42890
43301
|
};
|
|
43302
|
+
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
43303
|
+
let lineNumber = 1;
|
|
43304
|
+
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
43305
|
+
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === 10) lineNumber++;
|
|
43306
|
+
return lineNumber;
|
|
43307
|
+
};
|
|
42891
43308
|
const REANIMATED_SHARED_VALUE_HINT = "If this is a Reanimated shared value, prefer its React Compiler-compatible `.get()` / `.set()` accessors over `.value` — https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#react-compiler-support";
|
|
42892
43309
|
const ACCESSORS_MIN_VERSION = "3.15.0";
|
|
42893
43310
|
const supportsAccessors = (reanimatedVersion) => {
|
|
@@ -43061,23 +43478,12 @@ const REACT_USE_BINDING_RESOLUTION = {
|
|
|
43061
43478
|
isReactUseBinding: true,
|
|
43062
43479
|
isReactNamespaceBinding: false
|
|
43063
43480
|
};
|
|
43064
|
-
const getScriptKind$2 = (filename) => {
|
|
43065
|
-
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
43066
|
-
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
43067
|
-
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
43068
|
-
return ts.ScriptKind.JS;
|
|
43069
|
-
};
|
|
43070
43481
|
const getUtf16Offset$1 = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
43071
|
-
const unwrapExpression = (expression) => {
|
|
43072
|
-
let currentExpression = expression;
|
|
43073
|
-
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
43074
|
-
return currentExpression;
|
|
43075
|
-
};
|
|
43076
43482
|
const getStaticPropertyName = (node) => {
|
|
43077
43483
|
if (!node) return null;
|
|
43078
43484
|
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
|
|
43079
43485
|
if (ts.isComputedPropertyName(node)) {
|
|
43080
|
-
const expression =
|
|
43486
|
+
const expression = unwrapTypescriptExpression(node.expression);
|
|
43081
43487
|
if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) return expression.text;
|
|
43082
43488
|
}
|
|
43083
43489
|
return null;
|
|
@@ -43106,7 +43512,7 @@ const isReactUseObjectBindingElement = (bindingElement) => {
|
|
|
43106
43512
|
return getStaticPropertyName(bindingElement.propertyName) === USE_IDENTIFIER;
|
|
43107
43513
|
};
|
|
43108
43514
|
const isReactRequireCall = (expression) => {
|
|
43109
|
-
const unwrappedExpression =
|
|
43515
|
+
const unwrappedExpression = unwrapTypescriptExpression(expression);
|
|
43110
43516
|
return ts.isCallExpression(unwrappedExpression) && ts.isIdentifier(unwrappedExpression.expression) && unwrappedExpression.expression.text === REQUIRE_IDENTIFIER && unwrappedExpression.arguments.length === 1 && ts.isStringLiteral(unwrappedExpression.arguments[0]) && unwrappedExpression.arguments[0].text === REACT_MODULE_SOURCE;
|
|
43111
43517
|
};
|
|
43112
43518
|
const getModuleSource = (node) => {
|
|
@@ -43208,7 +43614,7 @@ const isIdentifierShadowedByLocalBinding = (identifier, sourceFile) => {
|
|
|
43208
43614
|
return false;
|
|
43209
43615
|
};
|
|
43210
43616
|
const isReactNamespaceExpression = (expression, reactImportBindings, sourceFile, visitedDeclarations) => {
|
|
43211
|
-
const unwrappedExpression =
|
|
43617
|
+
const unwrappedExpression = unwrapTypescriptExpression(expression);
|
|
43212
43618
|
if (isReactRequireCall(unwrappedExpression)) return true;
|
|
43213
43619
|
if (!ts.isIdentifier(unwrappedExpression)) return false;
|
|
43214
43620
|
if (reactImportBindings.namespaceNames.has(unwrappedExpression.text) && !isIdentifierShadowedByLocalBinding(unwrappedExpression, sourceFile)) return true;
|
|
@@ -43216,7 +43622,7 @@ const isReactNamespaceExpression = (expression, reactImportBindings, sourceFile,
|
|
|
43216
43622
|
};
|
|
43217
43623
|
const isReactUseExpression = (expression, reactImportBindings, sourceFile, visitedDeclarations) => {
|
|
43218
43624
|
if (!expression) return false;
|
|
43219
|
-
const unwrappedExpression =
|
|
43625
|
+
const unwrappedExpression = unwrapTypescriptExpression(expression);
|
|
43220
43626
|
if (ts.isIdentifier(unwrappedExpression)) {
|
|
43221
43627
|
if (reactImportBindings.useImportNames.has(unwrappedExpression.text) && !isIdentifierShadowedByLocalBinding(unwrappedExpression, sourceFile)) return true;
|
|
43222
43628
|
if (unwrappedExpression.text === USE_IDENTIFIER) return false;
|
|
@@ -43316,7 +43722,7 @@ const findUseCallIdentifier = (sourceFile, useOffset) => {
|
|
|
43316
43722
|
return matchedIdentifier;
|
|
43317
43723
|
};
|
|
43318
43724
|
const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
|
|
43319
|
-
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true,
|
|
43725
|
+
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getTypescriptScriptKind(filename));
|
|
43320
43726
|
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset$1(sourceText, utf8Offset));
|
|
43321
43727
|
if (!useIdentifier) return null;
|
|
43322
43728
|
return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
|
|
@@ -43352,12 +43758,6 @@ const WORKLET_ACCEPTING_CALLEES = new Set([
|
|
|
43352
43758
|
"executeOnUIRuntimeSync"
|
|
43353
43759
|
]);
|
|
43354
43760
|
const GESTURE_ROOT_IDENTIFIER = "Gesture";
|
|
43355
|
-
const getScriptKind$1 = (filename) => {
|
|
43356
|
-
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
43357
|
-
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
43358
|
-
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
43359
|
-
return ts.ScriptKind.JS;
|
|
43360
|
-
};
|
|
43361
43761
|
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
43362
43762
|
const hasWorkletDirective = (node) => {
|
|
43363
43763
|
if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node)) return false;
|
|
@@ -43429,17 +43829,11 @@ const shouldSuppressCompilerFindingInWorklet = (diagnostic, project, rootDirecto
|
|
|
43429
43829
|
} catch {
|
|
43430
43830
|
return false;
|
|
43431
43831
|
}
|
|
43432
|
-
return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true,
|
|
43832
|
+
return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getTypescriptScriptKind(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
|
|
43433
43833
|
};
|
|
43434
43834
|
const MANUAL_MEMOIZATION_PLUGIN = "react-doctor";
|
|
43435
43835
|
const MANUAL_MEMOIZATION_RULE = "react-compiler-no-manual-memoization";
|
|
43436
43836
|
const REACT_COMPILER_PLUGIN = "react-hooks-js";
|
|
43437
|
-
const getScriptKind = (filename) => {
|
|
43438
|
-
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
43439
|
-
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
43440
|
-
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
43441
|
-
return ts.ScriptKind.JS;
|
|
43442
|
-
};
|
|
43443
43837
|
const isManualMemoizationDiagnostic = (diagnostic) => diagnostic.plugin === MANUAL_MEMOIZATION_PLUGIN && diagnostic.rule === MANUAL_MEMOIZATION_RULE;
|
|
43444
43838
|
const nodeLineRange = (sourceFile, node) => ({
|
|
43445
43839
|
startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
@@ -43459,13 +43853,6 @@ const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
|
|
|
43459
43853
|
visit(sourceFile);
|
|
43460
43854
|
return foundRange;
|
|
43461
43855
|
};
|
|
43462
|
-
const NEWLINE_BYTE = 10;
|
|
43463
|
-
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
43464
|
-
let lineNumber = 1;
|
|
43465
|
-
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
43466
|
-
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
|
|
43467
|
-
return lineNumber;
|
|
43468
|
-
};
|
|
43469
43856
|
/**
|
|
43470
43857
|
* Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
|
|
43471
43858
|
* whose enclosing function also carries a `react-hooks-js` (React
|
|
@@ -43508,7 +43895,7 @@ const suppressMemoizationInBailedOutFunctions = (diagnostics, rootDirectory) =>
|
|
|
43508
43895
|
try {
|
|
43509
43896
|
const buffer = NFS.readFileSync(absolutePath);
|
|
43510
43897
|
parsed = {
|
|
43511
|
-
sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true,
|
|
43898
|
+
sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getTypescriptScriptKind(absolutePath)),
|
|
43512
43899
|
buffer
|
|
43513
43900
|
};
|
|
43514
43901
|
} catch {
|
|
@@ -43646,11 +44033,11 @@ const buildRelatedLocations = (labels, filePath) => {
|
|
|
43646
44033
|
}
|
|
43647
44034
|
return related;
|
|
43648
44035
|
};
|
|
43649
|
-
const
|
|
43650
|
-
|
|
43651
|
-
|
|
43652
|
-
|
|
43653
|
-
|
|
44036
|
+
const isOxlintSpan = (value) => isRecord$1(value) && typeof value.offset === "number" && typeof value.length === "number" && typeof value.line === "number" && typeof value.column === "number";
|
|
44037
|
+
const isOxlintLabel = (value) => isRecord$1(value) && isOxlintSpan(value.span);
|
|
44038
|
+
const isMappableOxlintDiagnostic = (value) => isRecord$1(value) && typeof value.code === "string" && value.code.length > 0 && typeof value.filename === "string" && value.filename.length > 0 && (value.severity === "warning" || value.severity === "error") && Array.isArray(value.labels) && value.labels.every(isOxlintLabel);
|
|
44039
|
+
const isEngineFailureDiagnostic = (value) => !isRecord$1(value) || typeof value.filename !== "string" || value.filename.length === 0;
|
|
44040
|
+
const isOxlintOutput = (value) => isRecord$1(value) && Array.isArray(value.diagnostics);
|
|
43654
44041
|
/**
|
|
43655
44042
|
* Parses one oxlint subprocess's stdout into a flat `Diagnostic[]`.
|
|
43656
44043
|
* Tolerates the leading-notice-line shape oxlint sometimes prints
|
|
@@ -43669,23 +44056,59 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
43669
44056
|
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
43670
44057
|
}
|
|
43671
44058
|
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
44059
|
+
const engineFailureDiagnostic = parsed.diagnostics.find(isEngineFailureDiagnostic);
|
|
44060
|
+
if (engineFailureDiagnostic !== void 0) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: JSON.stringify(engineFailureDiagnostic).slice(0, 600) }) });
|
|
43672
44061
|
const minifiedFileCache = /* @__PURE__ */ new Map();
|
|
44062
|
+
const sourceBufferCache = /* @__PURE__ */ new Map();
|
|
44063
|
+
const sourceLinesCache = /* @__PURE__ */ new Map();
|
|
44064
|
+
const resolveAbsolutePath = (filename) => Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
|
|
44065
|
+
const readSourceBuffer = (filename) => {
|
|
44066
|
+
const absolutePath = resolveAbsolutePath(filename);
|
|
44067
|
+
const cached = sourceBufferCache.get(absolutePath);
|
|
44068
|
+
if (cached !== void 0) return cached;
|
|
44069
|
+
let sourceBuffer;
|
|
44070
|
+
try {
|
|
44071
|
+
sourceBuffer = NFS.readFileSync(absolutePath);
|
|
44072
|
+
} catch {
|
|
44073
|
+
sourceBuffer = null;
|
|
44074
|
+
}
|
|
44075
|
+
sourceBufferCache.set(absolutePath, sourceBuffer);
|
|
44076
|
+
return sourceBuffer;
|
|
44077
|
+
};
|
|
44078
|
+
const readSourceLines = (filename) => {
|
|
44079
|
+
const absolutePath = resolveAbsolutePath(filename);
|
|
44080
|
+
const cached = sourceLinesCache.get(absolutePath);
|
|
44081
|
+
if (cached !== void 0) return cached;
|
|
44082
|
+
const sourceBuffer = readSourceBuffer(filename);
|
|
44083
|
+
const sourceLines = sourceBuffer ? sourceBuffer.toString("utf8").split("\n") : null;
|
|
44084
|
+
sourceLinesCache.set(absolutePath, sourceLines);
|
|
44085
|
+
return sourceLines;
|
|
44086
|
+
};
|
|
43673
44087
|
const isMinifiedDiagnosticFile = (filename) => {
|
|
43674
|
-
const absolutePath =
|
|
44088
|
+
const absolutePath = resolveAbsolutePath(filename);
|
|
43675
44089
|
const cached = minifiedFileCache.get(absolutePath);
|
|
43676
44090
|
if (cached !== void 0) return cached;
|
|
43677
44091
|
const minified = isMinifiedSource(absolutePath);
|
|
43678
44092
|
minifiedFileCache.set(absolutePath, minified);
|
|
43679
44093
|
return minified;
|
|
43680
44094
|
};
|
|
43681
|
-
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic
|
|
44095
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
43682
44096
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
43683
44097
|
const primaryLabel = diagnostic.labels[0];
|
|
43684
44098
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
43685
44099
|
const normalizedFilePath = diagnostic.filename.replaceAll("\\", "/");
|
|
43686
44100
|
const primarySpan = primaryLabel?.span;
|
|
44101
|
+
const sourceBuffer = primarySpan ? readSourceBuffer(diagnostic.filename) : null;
|
|
43687
44102
|
const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath);
|
|
43688
44103
|
const category = resolveDiagnosticCategory(plugin, rule);
|
|
44104
|
+
const matchByOccurrence = resolveMatchByOccurrence(rule, category);
|
|
44105
|
+
const sourceLines = primarySpan ? readSourceLines(diagnostic.filename) : null;
|
|
44106
|
+
const primaryLineIndex = primarySpan ? primarySpan.line - 1 : -1;
|
|
44107
|
+
const primaryLine = sourceLines?.[primaryLineIndex];
|
|
44108
|
+
const primaryColumnIndex = primarySpan ? primarySpan.column - 1 : -1;
|
|
44109
|
+
const isJsxTagLabel = primaryLine !== void 0 && primaryColumnIndex >= 0 && (primaryLine[primaryColumnIndex] === "<" || primaryLine[primaryColumnIndex - 1] === "<");
|
|
44110
|
+
const jsxOpenerEndLineIndex = sourceLines && isJsxTagLabel ? findJsxOpenerSpan(sourceLines, primaryLineIndex) : null;
|
|
44111
|
+
const primarySpanEndLine = jsxOpenerEndLineIndex !== null ? jsxOpenerEndLineIndex + 1 : primarySpan && sourceBuffer ? lineOfUtf8Offset(sourceBuffer, primarySpan.offset + Math.max(primarySpan.length - 1, 0)) : void 0;
|
|
43689
44112
|
return {
|
|
43690
44113
|
filePath: normalizedFilePath,
|
|
43691
44114
|
plugin,
|
|
@@ -43699,10 +44122,11 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
43699
44122
|
column: primarySpan?.column ?? 0,
|
|
43700
44123
|
...primarySpan ? {
|
|
43701
44124
|
offset: primarySpan.offset,
|
|
43702
|
-
length: primarySpan.length
|
|
44125
|
+
length: primarySpan.length,
|
|
44126
|
+
...primarySpanEndLine !== void 0 ? { endLine: primarySpanEndLine } : {}
|
|
43703
44127
|
} : {},
|
|
43704
44128
|
category,
|
|
43705
|
-
...
|
|
44129
|
+
...matchByOccurrence ? { matchByOccurrence: true } : {},
|
|
43706
44130
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
43707
44131
|
};
|
|
43708
44132
|
}), rootDirectory);
|
|
@@ -45489,7 +45913,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
45489
45913
|
directory: scanDirectory,
|
|
45490
45914
|
repo
|
|
45491
45915
|
}).pipe(orElseSucceed(() => null)) : succeed$2(null));
|
|
45492
|
-
const lintIncludePaths = (input.
|
|
45916
|
+
const lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config);
|
|
45493
45917
|
const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
|
|
45494
45918
|
const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
|
|
45495
45919
|
const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
|
|
@@ -45503,7 +45927,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
45503
45927
|
respectInlineDisables: input.respectInlineDisables,
|
|
45504
45928
|
showWarnings
|
|
45505
45929
|
});
|
|
45506
|
-
const
|
|
45930
|
+
const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)));
|
|
45931
|
+
const applyPerElementPipeline = (rawStream) => filterPerElementPipeline(rawStream).pipe(tap((diagnostic) => reporterService.emit(diagnostic)));
|
|
45507
45932
|
const envCollected = yield* runCollect(applyPerElementPipeline(fromIterable$1(isDiffMode ? [] : [
|
|
45508
45933
|
...checkReducedMotion(scanDirectory),
|
|
45509
45934
|
...checkPnpmHardening(scanDirectory),
|
|
@@ -45632,7 +46057,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
45632
46057
|
});
|
|
45633
46058
|
return empty$4;
|
|
45634
46059
|
}))));
|
|
45635
|
-
const lintCollected = yield* runCollect(
|
|
46060
|
+
const lintCollected = dedupeRelatedDiagnostics(yield* runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
|
|
45636
46061
|
onNone: () => set(lintFailure, {
|
|
45637
46062
|
didFail: true,
|
|
45638
46063
|
reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
|
|
@@ -45640,7 +46065,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
45640
46065
|
reasonKind: null
|
|
45641
46066
|
}).pipe(as([])),
|
|
45642
46067
|
onSome: succeed$2
|
|
45643
|
-
})));
|
|
46068
|
+
}))));
|
|
46069
|
+
yield* forEach$1(lintCollected, reporterService.emit, { discard: true });
|
|
45644
46070
|
const lintFailureState = yield* get$2(lintFailure);
|
|
45645
46071
|
yield* afterLint(lintFailureState.didFail);
|
|
45646
46072
|
if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
|
|
@@ -46234,10 +46660,11 @@ const computeDiagnosticDelta = (input) => {
|
|
|
46234
46660
|
* render diff-base mistakes (`GitBaseBranchInvalid` /
|
|
46235
46661
|
* `GitBaseBranchMissing`) as clean user errors instead of crashes.
|
|
46236
46662
|
*/
|
|
46237
|
-
const getDiffInfo = (directory, explicitBaseBranch) => runPromise(gen(function* () {
|
|
46663
|
+
const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runPromise(gen(function* () {
|
|
46238
46664
|
const selection = yield* (yield* Git).diffSelection({
|
|
46239
46665
|
directory,
|
|
46240
|
-
explicitBaseBranch
|
|
46666
|
+
explicitBaseBranch,
|
|
46667
|
+
includeUntracked
|
|
46241
46668
|
});
|
|
46242
46669
|
if (selection === null) return null;
|
|
46243
46670
|
return {
|
|
@@ -46573,6 +47000,43 @@ const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.
|
|
|
46573
47000
|
//#region src/cli/utils/constants.ts
|
|
46574
47001
|
const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
|
|
46575
47002
|
const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
|
|
47003
|
+
const STAGED_SNAPSHOT_ADDITIONAL_CONFIG_FILENAMES = [
|
|
47004
|
+
".babelrc",
|
|
47005
|
+
".babelrc.json",
|
|
47006
|
+
".eslintignore",
|
|
47007
|
+
".eslintrc.json",
|
|
47008
|
+
".oxlintignore",
|
|
47009
|
+
"app.config.cjs",
|
|
47010
|
+
"app.config.js",
|
|
47011
|
+
"app.config.json",
|
|
47012
|
+
"app.config.mjs",
|
|
47013
|
+
"app.config.ts",
|
|
47014
|
+
"app.json",
|
|
47015
|
+
"babel.config.cjs",
|
|
47016
|
+
"babel.config.js",
|
|
47017
|
+
"babel.config.json",
|
|
47018
|
+
"babel.config.mjs",
|
|
47019
|
+
"eslint.config.cjs",
|
|
47020
|
+
"eslint.config.cts",
|
|
47021
|
+
"eslint.config.js",
|
|
47022
|
+
"eslint.config.mjs",
|
|
47023
|
+
"eslint.config.mts",
|
|
47024
|
+
"eslint.config.ts",
|
|
47025
|
+
"next.config.cjs",
|
|
47026
|
+
"next.config.js",
|
|
47027
|
+
"next.config.mjs",
|
|
47028
|
+
"next.config.ts",
|
|
47029
|
+
"oxlint.config.json",
|
|
47030
|
+
"react-doctor.config.json",
|
|
47031
|
+
"vite.config.cjs",
|
|
47032
|
+
"vite.config.cts",
|
|
47033
|
+
"vite.config.js",
|
|
47034
|
+
"vite.config.mjs",
|
|
47035
|
+
"vite.config.mts",
|
|
47036
|
+
"vite.config.ts",
|
|
47037
|
+
"vitest.config.js",
|
|
47038
|
+
"vitest.config.ts"
|
|
47039
|
+
];
|
|
46576
47040
|
const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
|
|
46577
47041
|
const SCAN_RESULT_CACHE_FILENAME = "scan-cache.json";
|
|
46578
47042
|
const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
@@ -46587,6 +47051,7 @@ const METRIC = {
|
|
|
46587
47051
|
cliInvoked: "cli.invoked",
|
|
46588
47052
|
cliError: "cli.error",
|
|
46589
47053
|
cliEnvironmentError: "cli.env_error",
|
|
47054
|
+
stagedSnapshotDivergence: "staged.snapshot_divergence",
|
|
46590
47055
|
projectDetected: "project.detected",
|
|
46591
47056
|
projectPathSelected: "project.path_selected",
|
|
46592
47057
|
projectConfigSelected: "project.config_selected",
|
|
@@ -46667,7 +47132,7 @@ const makeNoopConsole = () => ({
|
|
|
46667
47132
|
});
|
|
46668
47133
|
//#endregion
|
|
46669
47134
|
//#region src/cli/utils/version.ts
|
|
46670
|
-
const VERSION = "0.7.
|
|
47135
|
+
const VERSION = "0.7.7-dev.04c7e36";
|
|
46671
47136
|
//#endregion
|
|
46672
47137
|
//#region src/cli/utils/json-mode.ts
|
|
46673
47138
|
let context = null;
|
|
@@ -47036,13 +47501,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
47036
47501
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
47037
47502
|
* standard `SENTRY_RELEASE` override.
|
|
47038
47503
|
*/
|
|
47039
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.
|
|
47504
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.7-dev.04c7e36`;
|
|
47040
47505
|
/**
|
|
47041
47506
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
47042
47507
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
47043
47508
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
47044
47509
|
*/
|
|
47045
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.
|
|
47510
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.7-dev.04c7e36") ? "development" : "production");
|
|
47046
47511
|
/**
|
|
47047
47512
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
47048
47513
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -47234,7 +47699,7 @@ const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
|
|
|
47234
47699
|
"fi"
|
|
47235
47700
|
].join(" ");
|
|
47236
47701
|
const PACKAGE_JSON_FILE_NAME = "package.json";
|
|
47237
|
-
const
|
|
47702
|
+
const runGitRaw = (projectRoot, args) => {
|
|
47238
47703
|
try {
|
|
47239
47704
|
return execFileSync("git", [...args], {
|
|
47240
47705
|
cwd: projectRoot,
|
|
@@ -47245,11 +47710,12 @@ const runGit = (projectRoot, args) => {
|
|
|
47245
47710
|
"ignore"
|
|
47246
47711
|
],
|
|
47247
47712
|
maxBuffer: RUN_GIT_MAX_BUFFER_BYTES
|
|
47248
|
-
})
|
|
47713
|
+
});
|
|
47249
47714
|
} catch {
|
|
47250
47715
|
return null;
|
|
47251
47716
|
}
|
|
47252
47717
|
};
|
|
47718
|
+
const runGit = (projectRoot, args) => runGitRaw(projectRoot, args)?.trim() ?? null;
|
|
47253
47719
|
const resolveGitPath = (baseDirectory, value) => Path.isAbsolute(value) ? value : Path.resolve(baseDirectory, value);
|
|
47254
47720
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
47255
47721
|
const getPackageJsonPath = (projectRoot) => Path.join(projectRoot, PACKAGE_JSON_FILE_NAME);
|
|
@@ -50807,7 +51273,7 @@ const BLOCKING_SUMMARY = {
|
|
|
50807
51273
|
const SCOPE_SUMMARY = {
|
|
50808
51274
|
full: "Scan the whole project on every run",
|
|
50809
51275
|
files: "Report every issue in changed files",
|
|
50810
|
-
lines: "Report issues
|
|
51276
|
+
lines: "Report issues whose source spans touch changed lines",
|
|
50811
51277
|
changed: "Report only the issues a change introduces"
|
|
50812
51278
|
};
|
|
50813
51279
|
const gatesEqual = (left, right) => left.blocking === right.blocking && left.scope === right.scope && left.comment === right.comment && left.reviewComments === right.reviewComments && left.commitStatus === right.commitStatus;
|
|
@@ -59120,6 +59586,17 @@ const buildRuleBlastRadii = (diagnostics) => buildSortedRuleGroups(diagnostics).
|
|
|
59120
59586
|
})).toSorted((left, right) => right.fileCount - left.fileCount);
|
|
59121
59587
|
const findMigrationScaleBuckets = (diagnostics) => buildRuleBlastRadii(diagnostics).filter((bucket) => bucket.fileCount >= 40);
|
|
59122
59588
|
//#endregion
|
|
59589
|
+
//#region src/cli/utils/has-lint-hard-failure.ts
|
|
59590
|
+
const hasLintHardFailure = (result) => result.skippedChecks.includes("lint");
|
|
59591
|
+
//#endregion
|
|
59592
|
+
//#region src/cli/utils/is-inspect-result-complete.ts
|
|
59593
|
+
const isInspectResultComplete = (result) => isScanComplete({
|
|
59594
|
+
analyzedFileCount: result.analyzedFiles?.length,
|
|
59595
|
+
scannedFileCount: result.scannedFileCount,
|
|
59596
|
+
skippedCheckCount: result.skippedChecks.length,
|
|
59597
|
+
skippedCheckReasonCount: Object.keys(result.skippedCheckReasons ?? {}).length
|
|
59598
|
+
});
|
|
59599
|
+
//#endregion
|
|
59123
59600
|
//#region src/cli/utils/scan-result-cache.ts
|
|
59124
59601
|
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
59125
59602
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
@@ -59202,7 +59679,7 @@ const buildDirtyPathContentFingerprint = (absolutePath, statusCode) => {
|
|
|
59202
59679
|
return null;
|
|
59203
59680
|
}
|
|
59204
59681
|
if (!stats.isFile()) return null;
|
|
59205
|
-
if (stats.size > 10485760) return
|
|
59682
|
+
if (stats.size > 10485760) return null;
|
|
59206
59683
|
return hashFileContents(absolutePath);
|
|
59207
59684
|
};
|
|
59208
59685
|
const buildWorktreeFingerprint = (projectDirectory) => {
|
|
@@ -59525,15 +60002,12 @@ const buildOutcomeAttributes = (input) => {
|
|
|
59525
60002
|
const summary = summarizeDiagnostics(result.diagnostics);
|
|
59526
60003
|
const blockingLevel = resolveTelemetryBlocking(input.userConfig);
|
|
59527
60004
|
const gateDiagnostics = filterDiagnosticsForSurface(result.diagnostics, "ciFailure", input.userConfig);
|
|
60005
|
+
const complete = isInspectResultComplete(result);
|
|
60006
|
+
const didLintHardFail = hasLintHardFailure(result);
|
|
60007
|
+
const failsOnHardFailure = didLintHardFail && blockingLevel !== "none";
|
|
59528
60008
|
const wouldBlock = !input.scoreOnly && !input.gateExempt && shouldBlockCi(gateDiagnostics, blockingLevel);
|
|
59529
|
-
const complete = isScanComplete({
|
|
59530
|
-
analyzedFileCount: result.analyzedFiles?.length,
|
|
59531
|
-
scannedFileCount: result.scannedFileCount,
|
|
59532
|
-
skippedCheckCount: result.skippedChecks.length,
|
|
59533
|
-
skippedCheckReasonCount: Object.keys(result.skippedCheckReasons ?? {}).length
|
|
59534
|
-
});
|
|
59535
60009
|
const isClean = result.diagnostics.length === 0 && complete;
|
|
59536
|
-
const outcome = wouldBlock ? "blocked" : isClean ? "clean" : "ok";
|
|
60010
|
+
const outcome = didLintHardFail ? "error" : wouldBlock ? "blocked" : isClean ? "clean" : "ok";
|
|
59537
60011
|
const firings = summarizeRuleFirings(result.diagnostics);
|
|
59538
60012
|
const countByRule = /* @__PURE__ */ new Map();
|
|
59539
60013
|
const countByCategory = /* @__PURE__ */ new Map();
|
|
@@ -59578,7 +60052,7 @@ const buildOutcomeAttributes = (input) => {
|
|
|
59578
60052
|
const attributes = {
|
|
59579
60053
|
...withNamespace("outcome", {
|
|
59580
60054
|
status: outcome,
|
|
59581
|
-
exitCode: wouldBlock ? 1 : 0,
|
|
60055
|
+
exitCode: failsOnHardFailure || wouldBlock ? 1 : 0,
|
|
59582
60056
|
wouldBlock,
|
|
59583
60057
|
blocking: blockingLevel,
|
|
59584
60058
|
clean: isClean,
|
|
@@ -59676,7 +60150,9 @@ const buildScanAttributes = (input) => {
|
|
|
59676
60150
|
hasCustomConfig: input.hasCustomConfig,
|
|
59677
60151
|
rulesConfigured: ruleKeys.length,
|
|
59678
60152
|
rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
|
|
59679
|
-
fileCount: input.result?.scannedFileCount ?? null
|
|
60153
|
+
fileCount: input.result?.scannedFileCount ?? null,
|
|
60154
|
+
nonJsxFileCount: input.result?.analyzedFiles?.filter((filePath) => !JSX_FILE_PATTERN.test(filePath)).length ?? null,
|
|
60155
|
+
multilineDiagnosticCount: input.result?.diagnostics.filter((diagnostic) => (diagnostic.endLine ?? diagnostic.line) > diagnostic.line).length ?? null
|
|
59680
60156
|
});
|
|
59681
60157
|
};
|
|
59682
60158
|
/**
|
|
@@ -59745,6 +60221,12 @@ const countDeadlineSkippedFiles = (lintPartialFailures) => sumFileCountsMatching
|
|
|
59745
60221
|
const DROPPED_FILES_MESSAGE_PATTERN = /^(\d+) file\(s\) failed to lint and were skipped/;
|
|
59746
60222
|
const countDroppedLintFiles = (lintPartialFailures) => sumFileCountsMatching(lintPartialFailures, DROPPED_FILES_MESSAGE_PATTERN);
|
|
59747
60223
|
//#endregion
|
|
60224
|
+
//#region src/cli/utils/diagnostic-intersects-line-ranges.ts
|
|
60225
|
+
const diagnosticIntersectsLineRanges = (diagnostic, lineRanges) => {
|
|
60226
|
+
const diagnosticEndLine = Math.max(diagnostic.line, diagnostic.endLine ?? diagnostic.line);
|
|
60227
|
+
return lineRanges.some(([rangeStartLine, rangeEndLine]) => diagnostic.line <= rangeEndLine && diagnosticEndLine >= rangeStartLine);
|
|
60228
|
+
};
|
|
60229
|
+
//#endregion
|
|
59748
60230
|
//#region src/cli/utils/materialize-baseline-files.ts
|
|
59749
60231
|
/**
|
|
59750
60232
|
* Materializes the `ref` version of `files` into `tempDirectory` (via
|
|
@@ -60930,7 +61412,7 @@ const buildChangedLineMatcher = (directory, changedLineRanges) => {
|
|
|
60930
61412
|
const relativePath = toForwardSlashes(path.isAbsolute(diagnostic.filePath) ? path.relative(directory, diagnostic.filePath) : diagnostic.filePath);
|
|
60931
61413
|
const ranges = rangesByFile.get(relativePath);
|
|
60932
61414
|
if (ranges === void 0) return false;
|
|
60933
|
-
return
|
|
61415
|
+
return diagnosticIntersectsLineRanges(diagnostic, ranges);
|
|
60934
61416
|
};
|
|
60935
61417
|
};
|
|
60936
61418
|
const buildIgnoredTags = (userConfig) => {
|
|
@@ -63752,10 +64234,15 @@ const warnIfAiTrainingEnvironment = () => {
|
|
|
63752
64234
|
//#region src/cli/utils/validate-mode-flags.ts
|
|
63753
64235
|
const usedDiffAlias = (flags) => flags.diff !== void 0 && flags.diff !== false && flags.diff !== "false" && flags.diff !== "";
|
|
63754
64236
|
const usedScope = (flags) => typeof flags.scope === "string" && flags.scope.length > 0;
|
|
64237
|
+
const validateIncludeUntrackedScope = (includeUntracked, scope) => {
|
|
64238
|
+
if (!includeUntracked || scope !== void 0 && scope !== "full") return;
|
|
64239
|
+
throw new CliInputError("--include-untracked requires the files, changed, or lines scope (via --scope, --diff, or config).");
|
|
64240
|
+
};
|
|
63755
64241
|
const validateModeFlags = (flags) => {
|
|
63756
64242
|
if (usedScope(flags) && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --scope and --diff; --diff is the deprecated alias.");
|
|
63757
64243
|
if (flags.staged && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --staged and --diff; pick one mode.");
|
|
63758
64244
|
if (flags.staged && (flags.scope === "full" || flags.scope === "changed")) throw new CliInputError(`Cannot combine --staged with --scope ${flags.scope}; use --scope files or --scope lines, or drop --scope.`);
|
|
64245
|
+
if (flags.includeUntracked && flags.staged) throw new CliInputError("Cannot combine --include-untracked with --staged; the git index never holds untracked files.");
|
|
63759
64246
|
if (flags.score && flags.json) throw new CliInputError("Cannot combine --score and --json; pick one output mode.");
|
|
63760
64247
|
if (flags.score && flags.telemetry === false) throw new CliInputError("Cannot combine --score with --no-telemetry; --score prints the score that --no-telemetry disables.");
|
|
63761
64248
|
if (flags.debug && (flags.score === false || flags.telemetry === false)) {
|
|
@@ -63764,6 +64251,39 @@ const validateModeFlags = (flags) => {
|
|
|
63764
64251
|
}
|
|
63765
64252
|
};
|
|
63766
64253
|
//#endregion
|
|
64254
|
+
//#region src/cli/utils/parse-staged-snapshot-divergences.ts
|
|
64255
|
+
const SNAPSHOT_CONFIG_FILENAMES = new Set([...STAGED_FILES_PROJECT_CONFIG_FILENAMES, ...STAGED_SNAPSHOT_ADDITIONAL_CONFIG_FILENAMES]);
|
|
64256
|
+
const parseStagedSnapshotDivergences = (statusOutput) => {
|
|
64257
|
+
const divergentConfigFiles = /* @__PURE__ */ new Set();
|
|
64258
|
+
const statusEntries = statusOutput.split("\0").filter((entry) => entry.length > 0);
|
|
64259
|
+
for (let entryIndex = 0; entryIndex < statusEntries.length; entryIndex += 1) {
|
|
64260
|
+
const entry = statusEntries[entryIndex];
|
|
64261
|
+
const [indexStatus, worktreeStatus] = entry;
|
|
64262
|
+
const filePath = entry.slice(3);
|
|
64263
|
+
const hasRenameOrCopySource = indexStatus === "R" || indexStatus === "C" || worktreeStatus === "R" || worktreeStatus === "C";
|
|
64264
|
+
const sourcePath = hasRenameOrCopySource ? statusEntries[entryIndex + 1] : void 0;
|
|
64265
|
+
const worktreePaths = worktreeStatus === "R" || worktreeStatus === "C" ? [filePath, sourcePath] : [filePath];
|
|
64266
|
+
if (worktreeStatus !== " ") {
|
|
64267
|
+
for (const worktreePath of worktreePaths) if (worktreePath && SNAPSHOT_CONFIG_FILENAMES.has(Path.basename(worktreePath))) divergentConfigFiles.add(worktreePath);
|
|
64268
|
+
}
|
|
64269
|
+
if (hasRenameOrCopySource) entryIndex += 1;
|
|
64270
|
+
}
|
|
64271
|
+
return [...divergentConfigFiles].sort();
|
|
64272
|
+
};
|
|
64273
|
+
//#endregion
|
|
64274
|
+
//#region src/cli/utils/find-staged-snapshot-divergences.ts
|
|
64275
|
+
const findStagedSnapshotDivergences = (directory) => {
|
|
64276
|
+
const statusOutput = runGitRaw(directory, [
|
|
64277
|
+
"status",
|
|
64278
|
+
"--porcelain=v1",
|
|
64279
|
+
"-z",
|
|
64280
|
+
"--untracked-files=all",
|
|
64281
|
+
"--ignored=matching"
|
|
64282
|
+
]);
|
|
64283
|
+
if (statusOutput === null) return null;
|
|
64284
|
+
return parseStagedSnapshotDivergences(statusOutput);
|
|
64285
|
+
};
|
|
64286
|
+
//#endregion
|
|
63767
64287
|
//#region src/cli/commands/inspect.ts
|
|
63768
64288
|
const filterCompletedScansByCategories = (completedScans, categoryFilters) => {
|
|
63769
64289
|
if (categoryFilters.size === 0) return [...completedScans];
|
|
@@ -63778,9 +64298,14 @@ const filterCompletedScansByCategories = (completedScans, categoryFilters) => {
|
|
|
63778
64298
|
/**
|
|
63779
64299
|
* Post-scan finalization shared by the staged-arm and project-loop
|
|
63780
64300
|
* paths of `inspectAction`: emit the JSON report (when in JSON mode)
|
|
63781
|
-
* and set `process.exitCode = 1` when
|
|
63782
|
-
*
|
|
63783
|
-
*
|
|
64301
|
+
* and set `process.exitCode = 1` when any scan's lint pass hard-failed
|
|
64302
|
+
* (an engine/plugin/binding failure destroys the findings, so success
|
|
64303
|
+
* would be a false clean) or a diagnostic at or above the `--blocking`
|
|
64304
|
+
* threshold (default `"error"`) reaches the `ciFailure` surface.
|
|
64305
|
+
* `--blocking none` keeps the scan advisory (always exits 0), and
|
|
64306
|
+
* fail-open degradations — `--no-lint`, `--max-duration` truncation,
|
|
64307
|
+
* supply-chain/security skips — stay advisory too, surfaced through
|
|
64308
|
+
* `complete: false` in the JSON report.
|
|
63784
64309
|
*/
|
|
63785
64310
|
const finalizeScans = (input) => {
|
|
63786
64311
|
const baselineDeltas = input.completedScans.flatMap((scan) => scan.result.baselineDelta ? [scan.result.baselineDelta] : []);
|
|
@@ -63810,8 +64335,13 @@ const finalizeScans = (input) => {
|
|
|
63810
64335
|
baselineDegraded
|
|
63811
64336
|
}));
|
|
63812
64337
|
}
|
|
64338
|
+
const blockingLevel = resolveBlockingLevel(input.flags, input.userConfig);
|
|
64339
|
+
if (input.completedScans.some(({ result }) => hasLintHardFailure(result)) && blockingLevel !== "none") {
|
|
64340
|
+
process.exitCode = 1;
|
|
64341
|
+
return;
|
|
64342
|
+
}
|
|
63813
64343
|
if (input.isScoreOnly || baselineDegraded) return;
|
|
63814
|
-
if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"),
|
|
64344
|
+
if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"), blockingLevel)) process.exitCode = 1;
|
|
63815
64345
|
};
|
|
63816
64346
|
const buildChangedFilesDiffInfo = (changedFiles) => ({
|
|
63817
64347
|
currentBranch: process.env.GITHUB_HEAD_REF?.trim() || null,
|
|
@@ -63849,6 +64379,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
63849
64379
|
recordCount(METRIC.cliInvoked, 1, { command: "inspect" });
|
|
63850
64380
|
try {
|
|
63851
64381
|
validateModeFlags(flags);
|
|
64382
|
+
if (flags.staged) setJsonReportMode("staged");
|
|
63852
64383
|
await maybeMigrateLegacyConfig(requestedDirectory, {
|
|
63853
64384
|
isQuiet,
|
|
63854
64385
|
isStaged: Boolean(flags.staged)
|
|
@@ -63864,6 +64395,14 @@ const inspectAction = async (directory, flags) => {
|
|
|
63864
64395
|
cliLogger.dim(`Redirected to ${highlighter.info(toRelativePath(resolvedDirectory, requestedDirectory))} via react-doctor config "rootDir".`);
|
|
63865
64396
|
cliLogger.break();
|
|
63866
64397
|
}
|
|
64398
|
+
if (flags.staged) {
|
|
64399
|
+
const divergentConfigFiles = findStagedSnapshotDivergences(resolvedDirectory);
|
|
64400
|
+
if (divergentConfigFiles === null) throw new CliInputError("Could not verify that staged configuration matches the worktree. Run the command from a Git worktree with Git available.");
|
|
64401
|
+
if (divergentConfigFiles.length > 0) {
|
|
64402
|
+
recordCount(METRIC.stagedSnapshotDivergence, 1, { divergentInputCount: divergentConfigFiles.length });
|
|
64403
|
+
throw new CliInputError(`Cannot scan staged files while configuration differs between the index and worktree: ${divergentConfigFiles.join(", ")}. Stage or restore those files, then rerun react-doctor --staged.`);
|
|
64404
|
+
}
|
|
64405
|
+
}
|
|
63867
64406
|
const explainArgument = flags.explain;
|
|
63868
64407
|
if (explainArgument !== void 0) {
|
|
63869
64408
|
await runExplain(explainArgument, {
|
|
@@ -63884,7 +64423,6 @@ const inspectAction = async (directory, flags) => {
|
|
|
63884
64423
|
json: flags.json
|
|
63885
64424
|
});
|
|
63886
64425
|
if (flags.staged) {
|
|
63887
|
-
setJsonReportMode("staged");
|
|
63888
64426
|
const stagedFiles = await getStagedSourceFiles(resolvedDirectory);
|
|
63889
64427
|
if (stagedFiles.length === 0) {
|
|
63890
64428
|
if (isJsonMode) writeJsonReport(buildJsonReport({
|
|
@@ -63965,13 +64503,15 @@ const inspectAction = async (directory, flags) => {
|
|
|
63965
64503
|
const projectDirectories = await selectProjects(resolvedDirectory, flags.project, skipPrompts, userConfig?.projects);
|
|
63966
64504
|
const changedFilesDiffInfo = flags.changedFilesFrom ? buildChangedFilesDiffInfo(readChangedFilesFrom(Path.resolve(flags.changedFilesFrom))) : null;
|
|
63967
64505
|
const requestedScope = resolveScope(flags, userConfig);
|
|
64506
|
+
const includeUntracked = flags.includeUntracked ?? false;
|
|
63968
64507
|
const scopeRequest = requestedScope.scope === void 0 && changedFilesDiffInfo !== null ? {
|
|
63969
64508
|
...requestedScope,
|
|
63970
64509
|
scope: "changed"
|
|
63971
64510
|
} : requestedScope;
|
|
64511
|
+
validateIncludeUntrackedScope(includeUntracked, scopeRequest.scope);
|
|
63972
64512
|
const wantsDiffMode = scopeRequest.scope !== void 0 && scopeRequest.scope !== "full";
|
|
63973
64513
|
const shouldDetectDiff = changedFilesDiffInfo === null && (wantsDiffMode || scopeRequest.scope === void 0 && !skipPrompts && !isQuiet);
|
|
63974
|
-
const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base) : null);
|
|
64514
|
+
const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base, includeUntracked) : null);
|
|
63975
64515
|
const scope = await finalizeScope({
|
|
63976
64516
|
requested: scopeRequest,
|
|
63977
64517
|
diffInfo,
|
|
@@ -63985,7 +64525,8 @@ const inspectAction = async (directory, flags) => {
|
|
|
63985
64525
|
const changedLineRanges = scope === "lines" && diffInfo !== null && (diffInfo.isCurrentChanges || linesBaseRef !== null) && diffInfo !== null ? await getChangedLineRanges({
|
|
63986
64526
|
directory: resolvedDirectory,
|
|
63987
64527
|
baseRef: linesBaseRef ?? void 0,
|
|
63988
|
-
files: [...diffInfo.changedFiles]
|
|
64528
|
+
files: [...diffInfo.changedFiles],
|
|
64529
|
+
includeUntracked
|
|
63989
64530
|
}) : null;
|
|
63990
64531
|
if (scope === "lines" && changedLineRanges === null && !isQuiet) {
|
|
63991
64532
|
cliLogger.warn("Could not determine changed lines (no base ref or git diff failed); reporting all issues in changed files.");
|
|
@@ -64918,6 +65459,7 @@ const ROOT_FLAG_SPEC = {
|
|
|
64918
65459
|
"--dead-code",
|
|
64919
65460
|
"--debug",
|
|
64920
65461
|
"--help",
|
|
65462
|
+
"--include-untracked",
|
|
64921
65463
|
"--json",
|
|
64922
65464
|
"--json-compact",
|
|
64923
65465
|
"--lint",
|
|
@@ -65181,7 +65723,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
65181
65723
|
${highlighter.info(CI_URL)}
|
|
65182
65724
|
`;
|
|
65183
65725
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
65184
|
-
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (
|
|
65726
|
+
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);
|
|
65185
65727
|
program.action(inspectAction);
|
|
65186
65728
|
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));
|
|
65187
65729
|
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);
|
|
@@ -65231,4 +65773,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
65231
65773
|
export {};
|
|
65232
65774
|
|
|
65233
65775
|
//# sourceMappingURL=cli.js.map
|
|
65234
|
-
//# debugId=
|
|
65776
|
+
//# debugId=3de13f8e-2b7e-5141-a62e-0cce9208b352
|