react-doctor 0.7.6 → 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/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4096a0e2-9712-5e2f-b602-5ff55d3994a3")}catch(e){}}();
|
|
3
3
|
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, FRAMEWORK_TOKENS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
@@ -32930,7 +32930,6 @@ const IGNORED_DIRECTORIES = new Set([
|
|
|
32930
32930
|
"out",
|
|
32931
32931
|
"storybook-static"
|
|
32932
32932
|
]);
|
|
32933
|
-
const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
32934
32933
|
const MILLISECONDS_PER_SECOND = 1e3;
|
|
32935
32934
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
32936
32935
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
@@ -33040,7 +33039,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
33040
33039
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
33041
33040
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
33042
33041
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
33043
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
33042
|
+
const CORE_PACKAGE_VERSION = "0.7.7";
|
|
33044
33043
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
33045
33044
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
33046
33045
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -33143,13 +33142,15 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
|
33143
33142
|
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
|
|
33144
33143
|
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
33145
33144
|
const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
|
|
33145
|
+
const UTF8_BOM_CHARACTER = "";
|
|
33146
|
+
const stripUtf8Bom = (content) => content.startsWith(UTF8_BOM_CHARACTER) ? content.slice(1) : content;
|
|
33146
33147
|
const cachedPackageJsons = /* @__PURE__ */ new Map();
|
|
33147
33148
|
const clearPackageJsonCache = () => {
|
|
33148
33149
|
cachedPackageJsons.clear();
|
|
33149
33150
|
};
|
|
33150
33151
|
const readPackageJsonUncached = (packageJsonPath) => {
|
|
33151
33152
|
try {
|
|
33152
|
-
return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
|
|
33153
|
+
return JSON.parse(stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8")));
|
|
33153
33154
|
} catch (error) {
|
|
33154
33155
|
if (error instanceof SyntaxError) return {};
|
|
33155
33156
|
if (isErrnoException(error)) {
|
|
@@ -36365,6 +36366,49 @@ const assignFixGroups = (diagnostics) => {
|
|
|
36365
36366
|
};
|
|
36366
36367
|
});
|
|
36367
36368
|
};
|
|
36369
|
+
const DERIVED_STATE_RULE_PRIORITY = new Map([
|
|
36370
|
+
["no-initialize-state", 0],
|
|
36371
|
+
["no-adjust-state-on-prop-change", 1],
|
|
36372
|
+
["no-derived-state", 2],
|
|
36373
|
+
["no-derived-state-effect", 3]
|
|
36374
|
+
]);
|
|
36375
|
+
const buildDiagnosticSiteKey = (diagnostic) => `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}`;
|
|
36376
|
+
const isReactDoctorRulesOfHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-doctor" && diagnostic.rule === "rules-of-hooks";
|
|
36377
|
+
const isReactCompilerHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-hooks-js" && diagnostic.rule === "hooks";
|
|
36378
|
+
const doDiagnosticSpansOverlap = (first, second) => {
|
|
36379
|
+
if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
|
|
36380
|
+
if (first.offset !== void 0 && second.offset !== void 0) {
|
|
36381
|
+
const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
|
|
36382
|
+
const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
|
|
36383
|
+
return first.offset < secondEnd && second.offset < firstEnd;
|
|
36384
|
+
}
|
|
36385
|
+
const firstEndLine = first.endLine ?? first.line;
|
|
36386
|
+
const secondEndLine = second.endLine ?? second.line;
|
|
36387
|
+
return first.line <= secondEndLine && second.line <= firstEndLine;
|
|
36388
|
+
};
|
|
36389
|
+
const keepHighestSeverity = (survivingDiagnostic, collapsedDiagnostic) => collapsedDiagnostic.severity === "error" && survivingDiagnostic.severity !== "error" ? {
|
|
36390
|
+
...survivingDiagnostic,
|
|
36391
|
+
severity: "error"
|
|
36392
|
+
} : survivingDiagnostic;
|
|
36393
|
+
const dedupeRelatedDiagnostics = (diagnostics) => {
|
|
36394
|
+
const reactDoctorHookSites = new Set(diagnostics.filter(isReactDoctorRulesOfHooksDiagnostic).map(buildDiagnosticSiteKey));
|
|
36395
|
+
const uniqueDiagnostics = [];
|
|
36396
|
+
for (const diagnostic of diagnostics) {
|
|
36397
|
+
if (isReactCompilerHooksDiagnostic(diagnostic) && reactDoctorHookSites.has(buildDiagnosticSiteKey(diagnostic))) continue;
|
|
36398
|
+
const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
|
|
36399
|
+
if (priority !== void 0) {
|
|
36400
|
+
const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
|
|
36401
|
+
if (overlappingDiagnosticIndex >= 0) {
|
|
36402
|
+
const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
|
|
36403
|
+
const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
|
|
36404
|
+
if (existingDiagnostic && existingPriority !== void 0) uniqueDiagnostics[overlappingDiagnosticIndex] = priority < existingPriority ? keepHighestSeverity(diagnostic, existingDiagnostic) : keepHighestSeverity(existingDiagnostic, diagnostic);
|
|
36405
|
+
continue;
|
|
36406
|
+
}
|
|
36407
|
+
}
|
|
36408
|
+
uniqueDiagnostics.push(diagnostic);
|
|
36409
|
+
}
|
|
36410
|
+
return uniqueDiagnostics;
|
|
36411
|
+
};
|
|
36368
36412
|
const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
36369
36413
|
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));
|
|
36370
36414
|
const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
|
|
@@ -36790,7 +36834,6 @@ const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
|
36790
36834
|
const PNPM_LOCKFILE = "pnpm-lock.yaml";
|
|
36791
36835
|
const PACKAGE_JSON_FILE = "package.json";
|
|
36792
36836
|
const PNPM_HARDENING_RULE_KEY = "require-pnpm-hardening";
|
|
36793
|
-
const UTF8_BOM_CHAR = "";
|
|
36794
36837
|
const HARDENING_SETTING_KEYS = new Set([
|
|
36795
36838
|
"minimumReleaseAge",
|
|
36796
36839
|
"blockExoticSubdeps",
|
|
@@ -36815,12 +36858,11 @@ const stripInlineComment = (rawValue) => {
|
|
|
36815
36858
|
return rawValue;
|
|
36816
36859
|
};
|
|
36817
36860
|
const unquote = (rawValue) => rawValue.replace(/^["']|["']$/g, "");
|
|
36818
|
-
const stripBom = (rawContent) => rawContent.startsWith(UTF8_BOM_CHAR) ? rawContent.slice(1) : rawContent;
|
|
36819
36861
|
const parseHardeningSettings = (content) => {
|
|
36820
36862
|
let minimumReleaseAge = null;
|
|
36821
36863
|
let blockExoticSubdeps = null;
|
|
36822
36864
|
let trustPolicy = null;
|
|
36823
|
-
const lines =
|
|
36865
|
+
const lines = stripUtf8Bom(content).split(/\r?\n/);
|
|
36824
36866
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
36825
36867
|
const lineText = lines[lineIndex];
|
|
36826
36868
|
if (lineText === void 0) continue;
|
|
@@ -36854,7 +36896,7 @@ const isPnpmManagedProject = (rootDirectory) => {
|
|
|
36854
36896
|
const packageJsonPath = Path.join(rootDirectory, PACKAGE_JSON_FILE);
|
|
36855
36897
|
if (!isFile(packageJsonPath)) return false;
|
|
36856
36898
|
try {
|
|
36857
|
-
const packageJsonRaw = NFS.readFileSync(packageJsonPath, "utf-8");
|
|
36899
|
+
const packageJsonRaw = stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8"));
|
|
36858
36900
|
const packageJson = JSON.parse(packageJsonRaw);
|
|
36859
36901
|
if (packageJson !== null && typeof packageJson === "object" && "packageManager" in packageJson && typeof packageJson.packageManager === "string" && packageJson.packageManager.startsWith("pnpm@")) return true;
|
|
36860
36902
|
} catch {
|
|
@@ -37140,18 +37182,45 @@ const checkReactServerComponentsAdvisory = (scanDirectory, project) => {
|
|
|
37140
37182
|
}
|
|
37141
37183
|
return diagnostics;
|
|
37142
37184
|
};
|
|
37143
|
-
const
|
|
37144
|
-
|
|
37145
|
-
|
|
37185
|
+
const getTypescriptScriptKind = (fileName) => {
|
|
37186
|
+
if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
37187
|
+
if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
37188
|
+
if (fileName.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
37189
|
+
return ts.ScriptKind.JS;
|
|
37190
|
+
};
|
|
37191
|
+
const unwrapTypescriptExpression = (expression) => {
|
|
37192
|
+
let currentExpression = expression;
|
|
37193
|
+
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
37194
|
+
return currentExpression;
|
|
37195
|
+
};
|
|
37196
|
+
const EMPTY_MOTION_EXPRESSION_EVIDENCE = {
|
|
37197
|
+
isAnimationFunction: false,
|
|
37198
|
+
isMotionComponent: false,
|
|
37199
|
+
isMotionComponentFactory: false,
|
|
37200
|
+
isMotionComponentNamespace: false,
|
|
37201
|
+
isMotionConfig: false,
|
|
37202
|
+
isMotionNamespace: false,
|
|
37203
|
+
isReducedMotionHook: false
|
|
37204
|
+
};
|
|
37205
|
+
const MOTION_COMPONENT_FACTORY_EXPORT_NAMES = new Set(["m", "motion"]);
|
|
37206
|
+
const MOTION_COMPONENT_NAMESPACE_EXPORT_NAMES = new Set(["Reorder"]);
|
|
37207
|
+
const MOTION_COMPONENT_NAMESPACE_MEMBER_NAMES = new Set(["Item"]);
|
|
37208
|
+
const MOTION_ANIMATION_FUNCTION_EXPORT_NAMES = new Set(["animate"]);
|
|
37209
|
+
const REDUCED_MOTION_HOOK_EXPORT_NAME = "useReducedMotion";
|
|
37210
|
+
const MOTION_CONFIG_EXPORT_NAME = "MotionConfig";
|
|
37211
|
+
const REDUCED_MOTION_PROP_NAME = "reducedMotion";
|
|
37212
|
+
const REDUCED_MOTION_CONFIG_VALUES = new Set(["always", "user"]);
|
|
37213
|
+
const SCRIPT_MODULE_EXTENSIONS = [
|
|
37146
37214
|
".ts",
|
|
37147
37215
|
".tsx",
|
|
37148
37216
|
".js",
|
|
37149
|
-
".jsx"
|
|
37150
|
-
|
|
37151
|
-
|
|
37152
|
-
]);
|
|
37153
|
-
const
|
|
37154
|
-
const
|
|
37217
|
+
".jsx"
|
|
37218
|
+
];
|
|
37219
|
+
const SCRIPT_FILE_EXTENSIONS = new Set(SCRIPT_MODULE_EXTENSIONS);
|
|
37220
|
+
const STYLE_FILE_EXTENSIONS = new Set([".css", ".scss"]);
|
|
37221
|
+
const MOTION_SOURCE_PREFILTER = /framer-motion|["']motion(?:\/[A-Za-z0-9_./-]+)?["']|MotionConfig|useReducedMotion/;
|
|
37222
|
+
const REDUCED_MOTION_MEDIA_QUERY_PATTERN = /@media\s+([^{}]*\(\s*prefers-reduced-motion\s*:\s*reduce\s*\)[^{}]*)\{/gi;
|
|
37223
|
+
const CSS_DECLARATION_PATTERN = /(?:^|[;{])\s*[-_A-Za-z][-_A-Za-z0-9]*\s*:/;
|
|
37155
37224
|
const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
|
|
37156
37225
|
filePath: "package.json",
|
|
37157
37226
|
plugin: "react-doctor",
|
|
@@ -37163,52 +37232,406 @@ const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
|
|
|
37163
37232
|
column: 0,
|
|
37164
37233
|
category: "Accessibility"
|
|
37165
37234
|
};
|
|
37166
|
-
const
|
|
37235
|
+
const isMotionModuleSource = (moduleSource) => {
|
|
37236
|
+
for (const packageName of MOTION_LIBRARY_PACKAGES) if (moduleSource === packageName || moduleSource.startsWith(`${packageName}/`)) return true;
|
|
37237
|
+
return false;
|
|
37238
|
+
};
|
|
37239
|
+
const classifyMotionExport = (exportName) => ({
|
|
37240
|
+
isAnimationFunction: MOTION_ANIMATION_FUNCTION_EXPORT_NAMES.has(exportName),
|
|
37241
|
+
isMotionComponent: false,
|
|
37242
|
+
isMotionComponentFactory: MOTION_COMPONENT_FACTORY_EXPORT_NAMES.has(exportName),
|
|
37243
|
+
isMotionComponentNamespace: MOTION_COMPONENT_NAMESPACE_EXPORT_NAMES.has(exportName),
|
|
37244
|
+
isMotionConfig: exportName === MOTION_CONFIG_EXPORT_NAME,
|
|
37245
|
+
isMotionNamespace: false,
|
|
37246
|
+
isReducedMotionHook: exportName === REDUCED_MOTION_HOOK_EXPORT_NAME
|
|
37247
|
+
});
|
|
37248
|
+
const getImportModuleSource = (node) => {
|
|
37249
|
+
let currentNode = node;
|
|
37250
|
+
while (currentNode) {
|
|
37251
|
+
if (ts.isImportDeclaration(currentNode) && ts.isStringLiteral(currentNode.moduleSpecifier)) return currentNode.moduleSpecifier.text;
|
|
37252
|
+
currentNode = currentNode.parent;
|
|
37253
|
+
}
|
|
37254
|
+
return null;
|
|
37255
|
+
};
|
|
37256
|
+
const getImportedBindingEvidence = (declaration, typeChecker, program, visitedSymbols) => {
|
|
37257
|
+
const moduleSource = getImportModuleSource(declaration);
|
|
37258
|
+
if (!moduleSource) return null;
|
|
37259
|
+
if (isMotionModuleSource(moduleSource) && ts.isNamespaceImport(declaration)) return {
|
|
37260
|
+
...EMPTY_MOTION_EXPRESSION_EVIDENCE,
|
|
37261
|
+
isMotionNamespace: true
|
|
37262
|
+
};
|
|
37263
|
+
if (isMotionModuleSource(moduleSource) && ts.isImportSpecifier(declaration)) {
|
|
37264
|
+
if (declaration.isTypeOnly) return null;
|
|
37265
|
+
return classifyMotionExport(declaration.propertyName?.text ?? declaration.name.text);
|
|
37266
|
+
}
|
|
37267
|
+
if (isMotionModuleSource(moduleSource) && ts.isImportClause(declaration)) {
|
|
37268
|
+
if (declaration.isTypeOnly) return null;
|
|
37269
|
+
return EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
37270
|
+
}
|
|
37271
|
+
if (ts.isImportSpecifier(declaration) && !declaration.isTypeOnly || ts.isImportClause(declaration) && !declaration.isTypeOnly && declaration.name) {
|
|
37272
|
+
const importedName = ts.isImportSpecifier(declaration) ? declaration.propertyName?.text ?? declaration.name.text : "default";
|
|
37273
|
+
const moduleSourceFile = getLocalModuleSourceFile(moduleSource, declaration.getSourceFile().fileName, program);
|
|
37274
|
+
if (!moduleSourceFile) return null;
|
|
37275
|
+
return resolveModuleExportEvidence(moduleSourceFile, importedName, typeChecker, program, visitedSymbols);
|
|
37276
|
+
}
|
|
37277
|
+
return null;
|
|
37278
|
+
};
|
|
37279
|
+
const getLocalModuleSourceFile = (moduleSource, importingFileName, program) => {
|
|
37280
|
+
if (!moduleSource.startsWith(".")) return null;
|
|
37281
|
+
const moduleBasePath = Path.resolve(Path.dirname(importingFileName), moduleSource);
|
|
37282
|
+
const moduleBaseExtension = Path.extname(moduleBasePath);
|
|
37283
|
+
const typescriptModuleBasePath = moduleBaseExtension === ".js" || moduleBaseExtension === ".jsx" ? moduleBasePath.slice(0, -moduleBaseExtension.length) : null;
|
|
37284
|
+
const candidates = [
|
|
37285
|
+
moduleBasePath,
|
|
37286
|
+
...typescriptModuleBasePath ? [
|
|
37287
|
+
`${typescriptModuleBasePath}.ts`,
|
|
37288
|
+
`${typescriptModuleBasePath}.tsx`,
|
|
37289
|
+
Path.join(typescriptModuleBasePath, "index.ts"),
|
|
37290
|
+
Path.join(typescriptModuleBasePath, "index.tsx")
|
|
37291
|
+
] : [],
|
|
37292
|
+
...SCRIPT_MODULE_EXTENSIONS.map((extension) => `${moduleBasePath}${extension}`),
|
|
37293
|
+
...SCRIPT_MODULE_EXTENSIONS.map((extension) => Path.join(moduleBasePath, `index${extension}`))
|
|
37294
|
+
];
|
|
37295
|
+
for (const candidate of candidates) {
|
|
37296
|
+
const sourceFile = program.getSourceFile(candidate);
|
|
37297
|
+
if (sourceFile) return sourceFile;
|
|
37298
|
+
}
|
|
37299
|
+
return null;
|
|
37300
|
+
};
|
|
37301
|
+
const resolveModuleExportEvidence = (sourceFile, exportName, typeChecker, program, visitedSymbols) => {
|
|
37302
|
+
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
|
|
37303
|
+
if (moduleSymbol && visitedSymbols.has(moduleSymbol)) return EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
37304
|
+
const nextVisitedSymbols = new Set(visitedSymbols);
|
|
37305
|
+
if (moduleSymbol) nextVisitedSymbols.add(moduleSymbol);
|
|
37306
|
+
for (const statement of sourceFile.statements) {
|
|
37307
|
+
if (exportName === "default" && ts.isExportAssignment(statement) && !statement.isExportEquals) {
|
|
37308
|
+
const evidence = resolveMotionExpressionEvidence(statement.expression, typeChecker, program, nextVisitedSymbols);
|
|
37309
|
+
if (hasMotionExpressionEvidence(evidence)) return evidence;
|
|
37310
|
+
continue;
|
|
37311
|
+
}
|
|
37312
|
+
if (!ts.isExportDeclaration(statement) || !statement.moduleSpecifier) continue;
|
|
37313
|
+
if (statement.isTypeOnly) continue;
|
|
37314
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
37315
|
+
const moduleSource = statement.moduleSpecifier.text;
|
|
37316
|
+
if (!statement.exportClause) {
|
|
37317
|
+
if (isMotionModuleSource(moduleSource)) {
|
|
37318
|
+
const evidence = classifyMotionExport(exportName);
|
|
37319
|
+
if (hasMotionExpressionEvidence(evidence)) return evidence;
|
|
37320
|
+
continue;
|
|
37321
|
+
}
|
|
37322
|
+
const moduleSourceFile = getLocalModuleSourceFile(moduleSource, sourceFile.fileName, program);
|
|
37323
|
+
if (moduleSourceFile) {
|
|
37324
|
+
const evidence = resolveModuleExportEvidence(moduleSourceFile, exportName, typeChecker, program, nextVisitedSymbols);
|
|
37325
|
+
if (hasMotionExpressionEvidence(evidence)) return evidence;
|
|
37326
|
+
}
|
|
37327
|
+
continue;
|
|
37328
|
+
}
|
|
37329
|
+
if (!ts.isNamedExports(statement.exportClause)) continue;
|
|
37330
|
+
for (const exportSpecifier of statement.exportClause.elements) {
|
|
37331
|
+
if (exportSpecifier.name.text !== exportName || exportSpecifier.isTypeOnly) continue;
|
|
37332
|
+
const sourceExportName = exportSpecifier.propertyName?.text ?? exportSpecifier.name.text;
|
|
37333
|
+
if (isMotionModuleSource(moduleSource)) return classifyMotionExport(sourceExportName);
|
|
37334
|
+
const moduleSourceFile = getLocalModuleSourceFile(moduleSource, sourceFile.fileName, program);
|
|
37335
|
+
if (!moduleSourceFile) continue;
|
|
37336
|
+
return resolveModuleExportEvidence(moduleSourceFile, sourceExportName, typeChecker, program, nextVisitedSymbols);
|
|
37337
|
+
}
|
|
37338
|
+
}
|
|
37339
|
+
if (!moduleSymbol) return EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
37340
|
+
const exportedSymbol = typeChecker.getExportsOfModule(moduleSymbol).find((candidateSymbol) => candidateSymbol.name === exportName);
|
|
37341
|
+
if (!exportedSymbol || nextVisitedSymbols.has(exportedSymbol)) return EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
37342
|
+
nextVisitedSymbols.add(exportedSymbol);
|
|
37343
|
+
let resolvedEvidence = EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
37344
|
+
for (const declaration of exportedSymbol.declarations ?? []) {
|
|
37345
|
+
if (ts.isExportSpecifier(declaration)) {
|
|
37346
|
+
const targetSymbol = typeChecker.getExportSpecifierLocalTargetSymbol(declaration);
|
|
37347
|
+
for (const targetDeclaration of targetSymbol?.declarations ?? []) {
|
|
37348
|
+
const importedEvidence = getImportedBindingEvidence(targetDeclaration, typeChecker, program, nextVisitedSymbols);
|
|
37349
|
+
if (importedEvidence) resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, importedEvidence);
|
|
37350
|
+
if (ts.isVariableDeclaration(targetDeclaration) && targetDeclaration.initializer) resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, resolveMotionExpressionEvidence(targetDeclaration.initializer, typeChecker, program, nextVisitedSymbols));
|
|
37351
|
+
}
|
|
37352
|
+
}
|
|
37353
|
+
if (ts.isVariableDeclaration(declaration) && declaration.initializer) resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, resolveMotionExpressionEvidence(declaration.initializer, typeChecker, program, nextVisitedSymbols));
|
|
37354
|
+
}
|
|
37355
|
+
return resolvedEvidence;
|
|
37356
|
+
};
|
|
37357
|
+
const mergeMotionExpressionEvidence = (leftEvidence, rightEvidence) => ({
|
|
37358
|
+
isAnimationFunction: leftEvidence.isAnimationFunction || rightEvidence.isAnimationFunction,
|
|
37359
|
+
isMotionComponent: leftEvidence.isMotionComponent || rightEvidence.isMotionComponent,
|
|
37360
|
+
isMotionComponentFactory: leftEvidence.isMotionComponentFactory || rightEvidence.isMotionComponentFactory,
|
|
37361
|
+
isMotionComponentNamespace: leftEvidence.isMotionComponentNamespace || rightEvidence.isMotionComponentNamespace,
|
|
37362
|
+
isMotionConfig: leftEvidence.isMotionConfig || rightEvidence.isMotionConfig,
|
|
37363
|
+
isMotionNamespace: leftEvidence.isMotionNamespace || rightEvidence.isMotionNamespace,
|
|
37364
|
+
isReducedMotionHook: leftEvidence.isReducedMotionHook || rightEvidence.isReducedMotionHook
|
|
37365
|
+
});
|
|
37366
|
+
const hasMotionExpressionEvidence = (evidence) => evidence.isAnimationFunction || evidence.isMotionComponent || evidence.isMotionComponentFactory || evidence.isMotionComponentNamespace || evidence.isMotionConfig || evidence.isMotionNamespace || evidence.isReducedMotionHook;
|
|
37367
|
+
const classifyMotionMember = (receiverEvidence, memberName) => {
|
|
37368
|
+
if (receiverEvidence.isMotionNamespace) return classifyMotionExport(memberName);
|
|
37369
|
+
if (receiverEvidence.isMotionComponentFactory) return {
|
|
37370
|
+
...EMPTY_MOTION_EXPRESSION_EVIDENCE,
|
|
37371
|
+
isMotionComponent: true
|
|
37372
|
+
};
|
|
37373
|
+
if (receiverEvidence.isMotionComponentNamespace && MOTION_COMPONENT_NAMESPACE_MEMBER_NAMES.has(memberName)) return {
|
|
37374
|
+
...EMPTY_MOTION_EXPRESSION_EVIDENCE,
|
|
37375
|
+
isMotionComponent: true
|
|
37376
|
+
};
|
|
37377
|
+
return EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
37378
|
+
};
|
|
37379
|
+
const resolveMotionExpressionEvidence = (expression, typeChecker, program, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
37380
|
+
const unwrappedExpression = unwrapTypescriptExpression(expression);
|
|
37381
|
+
if (ts.isIdentifier(unwrappedExpression)) {
|
|
37382
|
+
const symbol = typeChecker.getSymbolAtLocation(unwrappedExpression);
|
|
37383
|
+
if (!symbol || visitedSymbols.has(symbol)) return EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
37384
|
+
const nextVisitedSymbols = new Set(visitedSymbols);
|
|
37385
|
+
nextVisitedSymbols.add(symbol);
|
|
37386
|
+
let resolvedEvidence = EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
37387
|
+
for (const declaration of symbol.declarations ?? []) {
|
|
37388
|
+
const importedEvidence = getImportedBindingEvidence(declaration, typeChecker, program, nextVisitedSymbols);
|
|
37389
|
+
if (importedEvidence) {
|
|
37390
|
+
resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, importedEvidence);
|
|
37391
|
+
continue;
|
|
37392
|
+
}
|
|
37393
|
+
if (ts.isVariableDeclaration(declaration) && declaration.initializer) resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, resolveMotionExpressionEvidence(declaration.initializer, typeChecker, program, nextVisitedSymbols));
|
|
37394
|
+
if (ts.isBindingElement(declaration) && ts.isObjectBindingPattern(declaration.parent) && ts.isVariableDeclaration(declaration.parent.parent) && declaration.parent.parent.initializer) {
|
|
37395
|
+
const propertyName = declaration.propertyName ?? declaration.name;
|
|
37396
|
+
if (ts.isIdentifier(propertyName) || ts.isStringLiteral(propertyName)) {
|
|
37397
|
+
const receiverEvidence = resolveMotionExpressionEvidence(declaration.parent.parent.initializer, typeChecker, program, nextVisitedSymbols);
|
|
37398
|
+
resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, classifyMotionMember(receiverEvidence, propertyName.text));
|
|
37399
|
+
}
|
|
37400
|
+
}
|
|
37401
|
+
}
|
|
37402
|
+
return resolvedEvidence;
|
|
37403
|
+
}
|
|
37404
|
+
if (ts.isPropertyAccessExpression(unwrappedExpression)) return classifyMotionMember(resolveMotionExpressionEvidence(unwrappedExpression.expression, typeChecker, program, visitedSymbols), unwrappedExpression.name.text);
|
|
37405
|
+
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);
|
|
37406
|
+
if (ts.isCallExpression(unwrappedExpression)) {
|
|
37407
|
+
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 {
|
|
37408
|
+
...EMPTY_MOTION_EXPRESSION_EVIDENCE,
|
|
37409
|
+
isMotionNamespace: true
|
|
37410
|
+
};
|
|
37411
|
+
if (ts.isPropertyAccessExpression(unwrappedExpression.expression) && unwrappedExpression.expression.name.text === "bind") {
|
|
37412
|
+
const boundReceiverEvidence = resolveMotionExpressionEvidence(unwrappedExpression.expression.expression, typeChecker, program, visitedSymbols);
|
|
37413
|
+
if (boundReceiverEvidence.isAnimationFunction) return boundReceiverEvidence;
|
|
37414
|
+
}
|
|
37415
|
+
const calleeEvidence = resolveMotionExpressionEvidence(unwrappedExpression.expression, typeChecker, program, visitedSymbols);
|
|
37416
|
+
if (calleeEvidence.isMotionComponentFactory) return {
|
|
37417
|
+
...EMPTY_MOTION_EXPRESSION_EVIDENCE,
|
|
37418
|
+
isMotionComponent: true
|
|
37419
|
+
};
|
|
37420
|
+
return calleeEvidence;
|
|
37421
|
+
}
|
|
37422
|
+
return EMPTY_MOTION_EXPRESSION_EVIDENCE;
|
|
37423
|
+
};
|
|
37424
|
+
const getStaticStringExpressionValue = (expression, typeChecker, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
37425
|
+
const unwrappedExpression = unwrapTypescriptExpression(expression);
|
|
37426
|
+
if (ts.isStringLiteral(unwrappedExpression) || ts.isNoSubstitutionTemplateLiteral(unwrappedExpression)) return unwrappedExpression.text;
|
|
37427
|
+
if (!ts.isIdentifier(unwrappedExpression)) return null;
|
|
37428
|
+
const symbol = typeChecker.getSymbolAtLocation(unwrappedExpression);
|
|
37429
|
+
if (!symbol || visitedSymbols.has(symbol)) return null;
|
|
37430
|
+
const nextVisitedSymbols = new Set(visitedSymbols);
|
|
37431
|
+
nextVisitedSymbols.add(symbol);
|
|
37432
|
+
for (const declaration of symbol.declarations ?? []) {
|
|
37433
|
+
if (!ts.isVariableDeclaration(declaration) || !declaration.initializer) continue;
|
|
37434
|
+
if (!ts.isVariableDeclarationList(declaration.parent)) continue;
|
|
37435
|
+
if (!(declaration.parent.flags & ts.NodeFlags.Const)) continue;
|
|
37436
|
+
const value = getStaticStringExpressionValue(declaration.initializer, typeChecker, nextVisitedSymbols);
|
|
37437
|
+
if (value !== null) return value;
|
|
37438
|
+
}
|
|
37439
|
+
return null;
|
|
37440
|
+
};
|
|
37441
|
+
const getStaticJsxAttributeValue = (attribute, typeChecker) => {
|
|
37442
|
+
if (!attribute.initializer) return null;
|
|
37443
|
+
if (ts.isStringLiteral(attribute.initializer)) return attribute.initializer.text;
|
|
37444
|
+
if (!ts.isJsxExpression(attribute.initializer) || !attribute.initializer.expression) return null;
|
|
37445
|
+
return getStaticStringExpressionValue(attribute.initializer.expression, typeChecker);
|
|
37446
|
+
};
|
|
37447
|
+
const isExpressionValueConsumed = (expression) => {
|
|
37448
|
+
let currentExpression = expression;
|
|
37449
|
+
while (ts.isParenthesizedExpression(currentExpression.parent) || ts.isAsExpression(currentExpression.parent) || ts.isSatisfiesExpression(currentExpression.parent) || ts.isNonNullExpression(currentExpression.parent) || ts.isTypeAssertionExpression(currentExpression.parent)) currentExpression = currentExpression.parent;
|
|
37450
|
+
return !ts.isExpressionStatement(currentExpression.parent) && !ts.isVoidExpression(currentExpression.parent);
|
|
37451
|
+
};
|
|
37452
|
+
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) ?? ""));
|
|
37453
|
+
const collectScriptMotionEvidence = (sourceFile, typeChecker, program) => {
|
|
37454
|
+
const evidence = {
|
|
37455
|
+
hasMotionUse: false,
|
|
37456
|
+
hasReducedMotionHandling: false
|
|
37457
|
+
};
|
|
37458
|
+
const visitNode = (node) => {
|
|
37459
|
+
if (ts.isCallExpression(node)) {
|
|
37460
|
+
const calleeEvidence = resolveMotionExpressionEvidence(node.expression, typeChecker, program);
|
|
37461
|
+
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;
|
|
37462
|
+
if (calleeEvidence.isAnimationFunction || isAnimationFunctionCallViaCallOrApply) evidence.hasMotionUse = true;
|
|
37463
|
+
if (calleeEvidence.isReducedMotionHook && isExpressionValueConsumed(node)) evidence.hasReducedMotionHandling = true;
|
|
37464
|
+
}
|
|
37465
|
+
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
37466
|
+
const tagEvidence = ts.isJsxNamespacedName(node.tagName) ? EMPTY_MOTION_EXPRESSION_EVIDENCE : resolveMotionExpressionEvidence(node.tagName, typeChecker, program);
|
|
37467
|
+
if (tagEvidence.isMotionComponent || tagEvidence.isMotionComponentFactory) evidence.hasMotionUse = true;
|
|
37468
|
+
if (tagEvidence.isMotionConfig && jsxElementHasReducedMotionConfiguration(node.attributes, typeChecker)) evidence.hasReducedMotionHandling = true;
|
|
37469
|
+
}
|
|
37470
|
+
ts.forEachChild(node, visitNode);
|
|
37471
|
+
};
|
|
37472
|
+
visitNode(sourceFile);
|
|
37473
|
+
return evidence;
|
|
37474
|
+
};
|
|
37475
|
+
const analyzeReducedMotionSources = (sources) => {
|
|
37476
|
+
if (!sources.some(({ sourceText }) => MOTION_SOURCE_PREFILTER.test(sourceText))) return {
|
|
37477
|
+
hasMotionUse: false,
|
|
37478
|
+
hasReducedMotionHandling: false
|
|
37479
|
+
};
|
|
37480
|
+
const compilerOptions = {
|
|
37481
|
+
allowJs: true,
|
|
37482
|
+
checkJs: false,
|
|
37483
|
+
jsx: ts.JsxEmit.Preserve,
|
|
37484
|
+
module: ts.ModuleKind.ESNext,
|
|
37485
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
37486
|
+
noEmit: true,
|
|
37487
|
+
noLib: true,
|
|
37488
|
+
noResolve: true,
|
|
37489
|
+
skipLibCheck: true,
|
|
37490
|
+
target: ts.ScriptTarget.Latest
|
|
37491
|
+
};
|
|
37492
|
+
const sourceTextByFileName = new Map(sources.map(({ fileName: sourceFileName, sourceText: content }) => [Path.resolve(sourceFileName), content]));
|
|
37493
|
+
const compilerHost = ts.createCompilerHost(compilerOptions);
|
|
37494
|
+
const getDefaultSourceFile = compilerHost.getSourceFile.bind(compilerHost);
|
|
37495
|
+
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);
|
|
37496
|
+
const program = ts.createProgram([...sourceTextByFileName.keys()], compilerOptions, compilerHost);
|
|
37497
|
+
const typeChecker = program.getTypeChecker();
|
|
37498
|
+
const evidence = {
|
|
37499
|
+
hasMotionUse: false,
|
|
37500
|
+
hasReducedMotionHandling: false
|
|
37501
|
+
};
|
|
37502
|
+
for (const sourceFileName of sourceTextByFileName.keys()) {
|
|
37503
|
+
const sourceFile = program.getSourceFile(sourceFileName);
|
|
37504
|
+
if (!sourceFile) continue;
|
|
37505
|
+
const fileEvidence = collectScriptMotionEvidence(sourceFile, typeChecker, program);
|
|
37506
|
+
evidence.hasMotionUse ||= fileEvidence.hasMotionUse;
|
|
37507
|
+
evidence.hasReducedMotionHandling ||= fileEvidence.hasReducedMotionHandling;
|
|
37508
|
+
}
|
|
37509
|
+
return evidence;
|
|
37510
|
+
};
|
|
37511
|
+
const removeCssCommentsAndStrings = (content) => {
|
|
37512
|
+
let sanitizedContent = "";
|
|
37513
|
+
let quote = null;
|
|
37514
|
+
let isInsideComment = false;
|
|
37515
|
+
let isInsideLineComment = false;
|
|
37516
|
+
for (let characterIndex = 0; characterIndex < content.length; characterIndex += 1) {
|
|
37517
|
+
const character = content[characterIndex];
|
|
37518
|
+
const nextCharacter = content[characterIndex + 1];
|
|
37519
|
+
if (isInsideLineComment) {
|
|
37520
|
+
sanitizedContent += character === "\n" ? "\n" : " ";
|
|
37521
|
+
if (character === "\n") isInsideLineComment = false;
|
|
37522
|
+
continue;
|
|
37523
|
+
}
|
|
37524
|
+
if (isInsideComment) {
|
|
37525
|
+
if (character === "*" && nextCharacter === "/") {
|
|
37526
|
+
sanitizedContent += " ";
|
|
37527
|
+
characterIndex += 1;
|
|
37528
|
+
isInsideComment = false;
|
|
37529
|
+
} else sanitizedContent += character === "\n" ? "\n" : " ";
|
|
37530
|
+
continue;
|
|
37531
|
+
}
|
|
37532
|
+
if (quote) {
|
|
37533
|
+
if (character === "\\") {
|
|
37534
|
+
sanitizedContent += " ";
|
|
37535
|
+
characterIndex += 1;
|
|
37536
|
+
continue;
|
|
37537
|
+
}
|
|
37538
|
+
if (character === quote) quote = null;
|
|
37539
|
+
sanitizedContent += character === "\n" ? "\n" : " ";
|
|
37540
|
+
continue;
|
|
37541
|
+
}
|
|
37542
|
+
if (character === "/" && nextCharacter === "*") {
|
|
37543
|
+
sanitizedContent += " ";
|
|
37544
|
+
characterIndex += 1;
|
|
37545
|
+
isInsideComment = true;
|
|
37546
|
+
continue;
|
|
37547
|
+
}
|
|
37548
|
+
if (character === "/" && nextCharacter === "/") {
|
|
37549
|
+
sanitizedContent += " ";
|
|
37550
|
+
characterIndex += 1;
|
|
37551
|
+
isInsideLineComment = true;
|
|
37552
|
+
continue;
|
|
37553
|
+
}
|
|
37554
|
+
if (character === "\"" || character === "'") {
|
|
37555
|
+
sanitizedContent += " ";
|
|
37556
|
+
quote = character;
|
|
37557
|
+
continue;
|
|
37558
|
+
}
|
|
37559
|
+
sanitizedContent += character;
|
|
37560
|
+
}
|
|
37561
|
+
return sanitizedContent;
|
|
37562
|
+
};
|
|
37563
|
+
const findMatchingClosingBrace = (content, openingBraceIndex) => {
|
|
37564
|
+
let depth = 0;
|
|
37565
|
+
for (let characterIndex = openingBraceIndex; characterIndex < content.length; characterIndex += 1) {
|
|
37566
|
+
if (content[characterIndex] === "{") depth += 1;
|
|
37567
|
+
if (content[characterIndex] !== "}") continue;
|
|
37568
|
+
depth -= 1;
|
|
37569
|
+
if (depth === 0) return characterIndex;
|
|
37570
|
+
}
|
|
37571
|
+
return -1;
|
|
37572
|
+
};
|
|
37573
|
+
const hasReducedMotionMediaQuery = (content) => {
|
|
37574
|
+
const sanitizedContent = removeCssCommentsAndStrings(content);
|
|
37575
|
+
REDUCED_MOTION_MEDIA_QUERY_PATTERN.lastIndex = 0;
|
|
37576
|
+
for (const match of sanitizedContent.matchAll(REDUCED_MOTION_MEDIA_QUERY_PATTERN)) {
|
|
37577
|
+
const mediaPrelude = match[1];
|
|
37578
|
+
if (/\bnot\b/i.test(mediaPrelude)) continue;
|
|
37579
|
+
const openingBraceIndex = (match.index ?? 0) + match[0].lastIndexOf("{");
|
|
37580
|
+
const closingBraceIndex = findMatchingClosingBrace(sanitizedContent, openingBraceIndex);
|
|
37581
|
+
if (closingBraceIndex === -1) continue;
|
|
37582
|
+
const body = sanitizedContent.slice(openingBraceIndex + 1, closingBraceIndex);
|
|
37583
|
+
if (CSS_DECLARATION_PATTERN.test(body)) return true;
|
|
37584
|
+
}
|
|
37585
|
+
return false;
|
|
37586
|
+
};
|
|
37587
|
+
const collectProjectMotionEvidence = (rootDirectory) => {
|
|
37588
|
+
const scriptSources = [];
|
|
37589
|
+
let hasReducedMotionHandling = false;
|
|
37167
37590
|
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
|
|
37168
|
-
|
|
37591
|
+
const extension = Path.extname(name);
|
|
37592
|
+
if (!SCRIPT_FILE_EXTENSIONS.has(extension) && !STYLE_FILE_EXTENSIONS.has(extension)) continue;
|
|
37169
37593
|
let content;
|
|
37170
37594
|
try {
|
|
37171
37595
|
content = NFS.readFileSync(absolutePath, "utf-8");
|
|
37172
37596
|
} catch {
|
|
37173
37597
|
continue;
|
|
37174
37598
|
}
|
|
37175
|
-
if (
|
|
37599
|
+
if (STYLE_FILE_EXTENSIONS.has(extension)) {
|
|
37600
|
+
if (hasReducedMotionMediaQuery(content)) hasReducedMotionHandling = true;
|
|
37601
|
+
continue;
|
|
37602
|
+
}
|
|
37603
|
+
scriptSources.push({
|
|
37604
|
+
fileName: absolutePath,
|
|
37605
|
+
sourceText: content
|
|
37606
|
+
});
|
|
37176
37607
|
}
|
|
37177
|
-
return
|
|
37608
|
+
if (scriptSources.length === 0) return {
|
|
37609
|
+
hasMotionUse: false,
|
|
37610
|
+
hasReducedMotionHandling
|
|
37611
|
+
};
|
|
37612
|
+
const scriptEvidence = analyzeReducedMotionSources(scriptSources);
|
|
37613
|
+
const hasMotionUse = scriptEvidence.hasMotionUse;
|
|
37614
|
+
hasReducedMotionHandling ||= scriptEvidence.hasReducedMotionHandling;
|
|
37615
|
+
return {
|
|
37616
|
+
hasMotionUse,
|
|
37617
|
+
hasReducedMotionHandling
|
|
37618
|
+
};
|
|
37178
37619
|
};
|
|
37179
37620
|
const checkReducedMotion = (rootDirectory) => {
|
|
37180
37621
|
const packageJsonPath = Path.join(rootDirectory, "package.json");
|
|
37181
37622
|
if (!isFile(packageJsonPath)) return [];
|
|
37182
|
-
let hasMotionLibrary = false;
|
|
37183
37623
|
try {
|
|
37184
37624
|
const packageJson = readPackageJson(packageJsonPath);
|
|
37185
37625
|
const allDependencies = {
|
|
37186
37626
|
...packageJson.dependencies,
|
|
37187
37627
|
...packageJson.devDependencies
|
|
37188
37628
|
};
|
|
37189
|
-
|
|
37629
|
+
if (!Object.keys(allDependencies).some((packageName) => MOTION_LIBRARY_PACKAGES.has(packageName))) return [];
|
|
37190
37630
|
} catch {
|
|
37191
37631
|
return [];
|
|
37192
37632
|
}
|
|
37193
|
-
|
|
37194
|
-
|
|
37195
|
-
"grep",
|
|
37196
|
-
"--untracked",
|
|
37197
|
-
"-lE",
|
|
37198
|
-
REDUCED_MOTION_GREP_PATTERN,
|
|
37199
|
-
"--",
|
|
37200
|
-
...REDUCED_MOTION_FILE_GLOBS
|
|
37201
|
-
], {
|
|
37202
|
-
cwd: rootDirectory,
|
|
37203
|
-
encoding: "utf-8",
|
|
37204
|
-
stdio: [
|
|
37205
|
-
"ignore",
|
|
37206
|
-
"pipe",
|
|
37207
|
-
"pipe"
|
|
37208
|
-
]
|
|
37209
|
-
});
|
|
37210
|
-
if (!(!result.error && (result.status === 0 || result.status === GIT_GREP_NO_MATCH_STATUS))) return hasReducedMotionHandlingViaFilesystem(rootDirectory) ? [] : [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
37211
|
-
return result.status === 0 && result.stdout.split("\n").some((filePath) => filePath.length > 0 && !hasIgnoredPathSegment(filePath)) ? [] : [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
37633
|
+
const evidence = collectProjectMotionEvidence(rootDirectory);
|
|
37634
|
+
return evidence.hasMotionUse && !evidence.hasReducedMotionHandling ? [MISSING_REDUCED_MOTION_DIAGNOSTIC] : [];
|
|
37212
37635
|
};
|
|
37213
37636
|
const buildSecurityScanDiagnostic = (finding, entry, relativePath) => ({
|
|
37214
37637
|
filePath: relativePath,
|
|
@@ -37516,9 +37939,7 @@ const highlighter = {
|
|
|
37516
37939
|
gray: import_picocolors.default.gray,
|
|
37517
37940
|
bold: import_picocolors.default.bold
|
|
37518
37941
|
};
|
|
37519
|
-
const
|
|
37520
|
-
const shouldIncludeExplicitLintPath = (filePath, project) => JSX_FILE_PATTERN.test(filePath) || project?.framework === "nextjs" && NEXT_ENTRY_FILE_PATTERN.test(filePath);
|
|
37521
|
-
const computeExplicitLintIncludePaths = (includePaths, project) => includePaths.length > 0 ? includePaths.filter((filePath) => shouldIncludeExplicitLintPath(filePath, project)) : void 0;
|
|
37942
|
+
const computeExplicitLintIncludePaths = (includePaths) => includePaths.length > 0 ? includePaths.filter(isLintableSourceFile) : void 0;
|
|
37522
37943
|
const LINGUIST_ATTRIBUTE_PATTERN = /^linguist-(?:vendored|generated)(?:=([a-zA-Z0-9]+))?$/i;
|
|
37523
37944
|
const FALSY_VALUES = new Set([
|
|
37524
37945
|
"false",
|
|
@@ -38360,11 +38781,11 @@ const resolveDeadCodeTimeout = (input) => {
|
|
|
38360
38781
|
phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
|
|
38361
38782
|
};
|
|
38362
38783
|
};
|
|
38363
|
-
const resolveLintIncludePaths = (rootDirectory, userConfig
|
|
38784
|
+
const resolveLintIncludePaths = (rootDirectory, userConfig) => {
|
|
38364
38785
|
if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
|
|
38365
38786
|
const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
|
|
38366
38787
|
return listSourceFiles(rootDirectory).filter((filePath) => {
|
|
38367
|
-
if (!
|
|
38788
|
+
if (!isLintableSourceFile(filePath)) return false;
|
|
38368
38789
|
return !isFileIgnoredByPatterns(filePath, rootDirectory, ignoredPatterns);
|
|
38369
38790
|
});
|
|
38370
38791
|
};
|
|
@@ -38582,6 +39003,7 @@ const parseGithubViewerPermission = (stdout) => {
|
|
|
38582
39003
|
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
38583
39004
|
};
|
|
38584
39005
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
39006
|
+
const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
|
|
38585
39007
|
/**
|
|
38586
39008
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
38587
39009
|
* behind a `Context.Service`. The production layer (`layerNode`)
|
|
@@ -38652,6 +39074,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38652
39074
|
args,
|
|
38653
39075
|
directory
|
|
38654
39076
|
});
|
|
39077
|
+
const listUntrackedFilePaths = (directory, includePaths = []) => runGit(directory, [
|
|
39078
|
+
"ls-files",
|
|
39079
|
+
"-z",
|
|
39080
|
+
"--others",
|
|
39081
|
+
"--exclude-standard",
|
|
39082
|
+
...includePaths.length > 0 ? ["--", ...includePaths] : []
|
|
39083
|
+
]).pipe(map$3((result) => result.status === 0 ? splitNullSeparated(result.stdout) : null));
|
|
39084
|
+
const mergeUntracked = (directory, trackedFilePaths, includeUntracked) => includeUntracked ? listUntrackedFilePaths(directory).pipe(map$3((untracked) => untracked === null ? trackedFilePaths : [...new Set([...trackedFilePaths, ...untracked])])) : succeed$2(trackedFilePaths);
|
|
38655
39085
|
const currentBranch = (directory) => runGit(directory, [
|
|
38656
39086
|
"rev-parse",
|
|
38657
39087
|
"--abbrev-ref",
|
|
@@ -38774,7 +39204,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38774
39204
|
githubViewerPermission,
|
|
38775
39205
|
branchExists,
|
|
38776
39206
|
mergeBase,
|
|
38777
|
-
diffSelection: ({ directory, explicitBaseBranch }) => gen(function* () {
|
|
39207
|
+
diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
|
|
38778
39208
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
38779
39209
|
if (explicitBaseBranch !== void 0) {
|
|
38780
39210
|
const range = parseGitDiffRange(explicitBaseBranch);
|
|
@@ -38803,7 +39233,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38803
39233
|
"HEAD"
|
|
38804
39234
|
]);
|
|
38805
39235
|
if (uncommitted.status !== 0) return null;
|
|
38806
|
-
const files = splitNullSeparated(uncommitted.stdout);
|
|
39236
|
+
const files = yield* mergeUntracked(directory, splitNullSeparated(uncommitted.stdout), includeUntracked);
|
|
38807
39237
|
if (files.length === 0) return null;
|
|
38808
39238
|
return {
|
|
38809
39239
|
currentBranch: resolvedCurrentBranch,
|
|
@@ -38833,7 +39263,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38833
39263
|
currentBranch: resolvedCurrentBranch,
|
|
38834
39264
|
baseBranch,
|
|
38835
39265
|
diffBaseRef: mergeBaseRef,
|
|
38836
|
-
changedFiles: splitNullSeparated(diff.stdout),
|
|
39266
|
+
changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
|
|
38837
39267
|
isCurrentChanges: false
|
|
38838
39268
|
};
|
|
38839
39269
|
}).pipe(withSpan("Git.diffSelection")),
|
|
@@ -38879,7 +39309,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38879
39309
|
stdout: result.stdout
|
|
38880
39310
|
};
|
|
38881
39311
|
}).pipe(withSpan("Git.grep")),
|
|
38882
|
-
changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
|
|
39312
|
+
changedLineRanges: ({ directory, baseRef, cached, files, includeUntracked = false }) => gen(function* () {
|
|
38883
39313
|
if (files.length === 0) return [];
|
|
38884
39314
|
if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
|
|
38885
39315
|
const result = yield* runGit(directory, [
|
|
@@ -38893,7 +39323,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38893
39323
|
...files
|
|
38894
39324
|
]);
|
|
38895
39325
|
if (result.status !== 0) return null;
|
|
38896
|
-
|
|
39326
|
+
const changedLineRanges = parseChangedLineRanges(result.stdout);
|
|
39327
|
+
if (cached || !includeUntracked) return changedLineRanges;
|
|
39328
|
+
const untrackedFilePaths = yield* listUntrackedFilePaths(directory, files);
|
|
39329
|
+
if (untrackedFilePaths === null) return changedLineRanges;
|
|
39330
|
+
return [...changedLineRanges, ...untrackedFilePaths.map((file) => ({
|
|
39331
|
+
file,
|
|
39332
|
+
ranges: [[1, UNTRACKED_FILE_LAST_LINE]]
|
|
39333
|
+
}))];
|
|
38897
39334
|
}).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
|
|
38898
39335
|
});
|
|
38899
39336
|
})).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
|
|
@@ -39478,13 +39915,13 @@ const encodeSidecarFileEntry = (entry) => ({
|
|
|
39478
39915
|
diagnostics: entry.diagnostics
|
|
39479
39916
|
});
|
|
39480
39917
|
const emptyCache = () => ({
|
|
39481
|
-
version:
|
|
39918
|
+
version: 2,
|
|
39482
39919
|
rulesets: {}
|
|
39483
39920
|
});
|
|
39484
39921
|
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
39485
39922
|
const entries = /* @__PURE__ */ new Map();
|
|
39486
39923
|
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39487
|
-
if (persisted.version !==
|
|
39924
|
+
if (persisted.version !== 2 || !isRecord(persisted.rulesets)) return entries;
|
|
39488
39925
|
const bucket = persisted.rulesets[rulesetHash];
|
|
39489
39926
|
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
39490
39927
|
for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
|
|
@@ -39504,7 +39941,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
39504
39941
|
},
|
|
39505
39942
|
persist: () => {
|
|
39506
39943
|
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39507
|
-
const rulesets = onDisk.version ===
|
|
39944
|
+
const rulesets = onDisk.version === 2 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
39508
39945
|
const existingBucket = rulesets[rulesetHash];
|
|
39509
39946
|
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
39510
39947
|
const ourFiles = {};
|
|
@@ -39518,7 +39955,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
39518
39955
|
files: Object.fromEntries(cappedEntries)
|
|
39519
39956
|
};
|
|
39520
39957
|
atomicWriteJson(cacheFilePath, {
|
|
39521
|
-
version:
|
|
39958
|
+
version: 2,
|
|
39522
39959
|
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
39523
39960
|
});
|
|
39524
39961
|
}
|
|
@@ -39569,23 +40006,6 @@ const resolveTsConfigRelativePath = (rootDirectory) => {
|
|
|
39569
40006
|
for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
|
|
39570
40007
|
return null;
|
|
39571
40008
|
};
|
|
39572
|
-
const DERIVED_STATE_RULE_PRIORITY = new Map([
|
|
39573
|
-
["no-initialize-state", 0],
|
|
39574
|
-
["no-adjust-state-on-prop-change", 1],
|
|
39575
|
-
["no-derived-state", 2],
|
|
39576
|
-
["no-derived-state-effect", 3]
|
|
39577
|
-
]);
|
|
39578
|
-
const doDiagnosticSpansOverlap = (first, second) => {
|
|
39579
|
-
if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
|
|
39580
|
-
if (first.offset !== void 0 && second.offset !== void 0) {
|
|
39581
|
-
const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
|
|
39582
|
-
const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
|
|
39583
|
-
return first.offset < secondEnd && second.offset < firstEnd;
|
|
39584
|
-
}
|
|
39585
|
-
const firstEndLine = first.endLine ?? first.line;
|
|
39586
|
-
const secondEndLine = second.endLine ?? second.line;
|
|
39587
|
-
return first.line <= secondEndLine && second.line <= firstEndLine;
|
|
39588
|
-
};
|
|
39589
40009
|
const dedupeDiagnostics = (diagnostics) => {
|
|
39590
40010
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
39591
40011
|
const uniqueDiagnostics = [];
|
|
@@ -39593,16 +40013,6 @@ const dedupeDiagnostics = (diagnostics) => {
|
|
|
39593
40013
|
const key = `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}\u0000${diagnostic.plugin}\u0000${diagnostic.rule}\u0000${diagnostic.severity}\u0000${diagnostic.message}`;
|
|
39594
40014
|
if (seenKeys.has(key)) continue;
|
|
39595
40015
|
seenKeys.add(key);
|
|
39596
|
-
const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
|
|
39597
|
-
if (priority !== void 0) {
|
|
39598
|
-
const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && candidate.severity === diagnostic.severity && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
|
|
39599
|
-
if (overlappingDiagnosticIndex >= 0) {
|
|
39600
|
-
const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
|
|
39601
|
-
const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
|
|
39602
|
-
if (existingPriority !== void 0 && priority < existingPriority) uniqueDiagnostics[overlappingDiagnosticIndex] = diagnostic;
|
|
39603
|
-
continue;
|
|
39604
|
-
}
|
|
39605
|
-
}
|
|
39606
40016
|
uniqueDiagnostics.push(diagnostic);
|
|
39607
40017
|
}
|
|
39608
40018
|
return uniqueDiagnostics;
|
|
@@ -39645,6 +40055,12 @@ const mapWithConcurrency = async (items, concurrency, task) => {
|
|
|
39645
40055
|
if (errors.length > 0) throw errors[0];
|
|
39646
40056
|
return results;
|
|
39647
40057
|
};
|
|
40058
|
+
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
40059
|
+
let lineNumber = 1;
|
|
40060
|
+
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
40061
|
+
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === 10) lineNumber++;
|
|
40062
|
+
return lineNumber;
|
|
40063
|
+
};
|
|
39648
40064
|
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";
|
|
39649
40065
|
const ACCESSORS_MIN_VERSION = "3.15.0";
|
|
39650
40066
|
const supportsAccessors = (reanimatedVersion) => {
|
|
@@ -39818,23 +40234,12 @@ const REACT_USE_BINDING_RESOLUTION = {
|
|
|
39818
40234
|
isReactUseBinding: true,
|
|
39819
40235
|
isReactNamespaceBinding: false
|
|
39820
40236
|
};
|
|
39821
|
-
const getScriptKind$2 = (filename) => {
|
|
39822
|
-
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
39823
|
-
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
39824
|
-
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
39825
|
-
return ts.ScriptKind.JS;
|
|
39826
|
-
};
|
|
39827
40237
|
const getUtf16Offset$1 = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39828
|
-
const unwrapExpression = (expression) => {
|
|
39829
|
-
let currentExpression = expression;
|
|
39830
|
-
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
39831
|
-
return currentExpression;
|
|
39832
|
-
};
|
|
39833
40238
|
const getStaticPropertyName = (node) => {
|
|
39834
40239
|
if (!node) return null;
|
|
39835
40240
|
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
|
|
39836
40241
|
if (ts.isComputedPropertyName(node)) {
|
|
39837
|
-
const expression =
|
|
40242
|
+
const expression = unwrapTypescriptExpression(node.expression);
|
|
39838
40243
|
if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) return expression.text;
|
|
39839
40244
|
}
|
|
39840
40245
|
return null;
|
|
@@ -39863,7 +40268,7 @@ const isReactUseObjectBindingElement = (bindingElement) => {
|
|
|
39863
40268
|
return getStaticPropertyName(bindingElement.propertyName) === USE_IDENTIFIER;
|
|
39864
40269
|
};
|
|
39865
40270
|
const isReactRequireCall = (expression) => {
|
|
39866
|
-
const unwrappedExpression =
|
|
40271
|
+
const unwrappedExpression = unwrapTypescriptExpression(expression);
|
|
39867
40272
|
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;
|
|
39868
40273
|
};
|
|
39869
40274
|
const getModuleSource = (node) => {
|
|
@@ -39965,7 +40370,7 @@ const isIdentifierShadowedByLocalBinding = (identifier, sourceFile) => {
|
|
|
39965
40370
|
return false;
|
|
39966
40371
|
};
|
|
39967
40372
|
const isReactNamespaceExpression = (expression, reactImportBindings, sourceFile, visitedDeclarations) => {
|
|
39968
|
-
const unwrappedExpression =
|
|
40373
|
+
const unwrappedExpression = unwrapTypescriptExpression(expression);
|
|
39969
40374
|
if (isReactRequireCall(unwrappedExpression)) return true;
|
|
39970
40375
|
if (!ts.isIdentifier(unwrappedExpression)) return false;
|
|
39971
40376
|
if (reactImportBindings.namespaceNames.has(unwrappedExpression.text) && !isIdentifierShadowedByLocalBinding(unwrappedExpression, sourceFile)) return true;
|
|
@@ -39973,7 +40378,7 @@ const isReactNamespaceExpression = (expression, reactImportBindings, sourceFile,
|
|
|
39973
40378
|
};
|
|
39974
40379
|
const isReactUseExpression = (expression, reactImportBindings, sourceFile, visitedDeclarations) => {
|
|
39975
40380
|
if (!expression) return false;
|
|
39976
|
-
const unwrappedExpression =
|
|
40381
|
+
const unwrappedExpression = unwrapTypescriptExpression(expression);
|
|
39977
40382
|
if (ts.isIdentifier(unwrappedExpression)) {
|
|
39978
40383
|
if (reactImportBindings.useImportNames.has(unwrappedExpression.text) && !isIdentifierShadowedByLocalBinding(unwrappedExpression, sourceFile)) return true;
|
|
39979
40384
|
if (unwrappedExpression.text === USE_IDENTIFIER) return false;
|
|
@@ -40073,7 +40478,7 @@ const findUseCallIdentifier = (sourceFile, useOffset) => {
|
|
|
40073
40478
|
return matchedIdentifier;
|
|
40074
40479
|
};
|
|
40075
40480
|
const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
|
|
40076
|
-
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true,
|
|
40481
|
+
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getTypescriptScriptKind(filename));
|
|
40077
40482
|
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset$1(sourceText, utf8Offset));
|
|
40078
40483
|
if (!useIdentifier) return null;
|
|
40079
40484
|
return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
|
|
@@ -40109,12 +40514,6 @@ const WORKLET_ACCEPTING_CALLEES = new Set([
|
|
|
40109
40514
|
"executeOnUIRuntimeSync"
|
|
40110
40515
|
]);
|
|
40111
40516
|
const GESTURE_ROOT_IDENTIFIER = "Gesture";
|
|
40112
|
-
const getScriptKind$1 = (filename) => {
|
|
40113
|
-
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
40114
|
-
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
40115
|
-
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
40116
|
-
return ts.ScriptKind.JS;
|
|
40117
|
-
};
|
|
40118
40517
|
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
40119
40518
|
const hasWorkletDirective = (node) => {
|
|
40120
40519
|
if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node)) return false;
|
|
@@ -40186,17 +40585,11 @@ const shouldSuppressCompilerFindingInWorklet = (diagnostic, project, rootDirecto
|
|
|
40186
40585
|
} catch {
|
|
40187
40586
|
return false;
|
|
40188
40587
|
}
|
|
40189
|
-
return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true,
|
|
40588
|
+
return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getTypescriptScriptKind(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
|
|
40190
40589
|
};
|
|
40191
40590
|
const MANUAL_MEMOIZATION_PLUGIN = "react-doctor";
|
|
40192
40591
|
const MANUAL_MEMOIZATION_RULE = "react-compiler-no-manual-memoization";
|
|
40193
40592
|
const REACT_COMPILER_PLUGIN = "react-hooks-js";
|
|
40194
|
-
const getScriptKind = (filename) => {
|
|
40195
|
-
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
40196
|
-
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
40197
|
-
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
40198
|
-
return ts.ScriptKind.JS;
|
|
40199
|
-
};
|
|
40200
40593
|
const isManualMemoizationDiagnostic = (diagnostic) => diagnostic.plugin === MANUAL_MEMOIZATION_PLUGIN && diagnostic.rule === MANUAL_MEMOIZATION_RULE;
|
|
40201
40594
|
const nodeLineRange = (sourceFile, node) => ({
|
|
40202
40595
|
startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
@@ -40216,13 +40609,6 @@ const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
|
|
|
40216
40609
|
visit(sourceFile);
|
|
40217
40610
|
return foundRange;
|
|
40218
40611
|
};
|
|
40219
|
-
const NEWLINE_BYTE = 10;
|
|
40220
|
-
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
40221
|
-
let lineNumber = 1;
|
|
40222
|
-
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
40223
|
-
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
|
|
40224
|
-
return lineNumber;
|
|
40225
|
-
};
|
|
40226
40612
|
/**
|
|
40227
40613
|
* Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
|
|
40228
40614
|
* whose enclosing function also carries a `react-hooks-js` (React
|
|
@@ -40265,7 +40651,7 @@ const suppressMemoizationInBailedOutFunctions = (diagnostics, rootDirectory) =>
|
|
|
40265
40651
|
try {
|
|
40266
40652
|
const buffer = NFS.readFileSync(absolutePath);
|
|
40267
40653
|
parsed = {
|
|
40268
|
-
sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true,
|
|
40654
|
+
sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getTypescriptScriptKind(absolutePath)),
|
|
40269
40655
|
buffer
|
|
40270
40656
|
};
|
|
40271
40657
|
} catch {
|
|
@@ -40403,11 +40789,11 @@ const buildRelatedLocations = (labels, filePath) => {
|
|
|
40403
40789
|
}
|
|
40404
40790
|
return related;
|
|
40405
40791
|
};
|
|
40406
|
-
const
|
|
40407
|
-
|
|
40408
|
-
|
|
40409
|
-
|
|
40410
|
-
|
|
40792
|
+
const isOxlintSpan = (value) => isRecord(value) && typeof value.offset === "number" && typeof value.length === "number" && typeof value.line === "number" && typeof value.column === "number";
|
|
40793
|
+
const isOxlintLabel = (value) => isRecord(value) && isOxlintSpan(value.span);
|
|
40794
|
+
const isMappableOxlintDiagnostic = (value) => isRecord(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);
|
|
40795
|
+
const isEngineFailureDiagnostic = (value) => !isRecord(value) || typeof value.filename !== "string" || value.filename.length === 0;
|
|
40796
|
+
const isOxlintOutput = (value) => isRecord(value) && Array.isArray(value.diagnostics);
|
|
40411
40797
|
/**
|
|
40412
40798
|
* Parses one oxlint subprocess's stdout into a flat `Diagnostic[]`.
|
|
40413
40799
|
* Tolerates the leading-notice-line shape oxlint sometimes prints
|
|
@@ -40426,23 +40812,59 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
40426
40812
|
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
40427
40813
|
}
|
|
40428
40814
|
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
40815
|
+
const engineFailureDiagnostic = parsed.diagnostics.find(isEngineFailureDiagnostic);
|
|
40816
|
+
if (engineFailureDiagnostic !== void 0) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: JSON.stringify(engineFailureDiagnostic).slice(0, 600) }) });
|
|
40429
40817
|
const minifiedFileCache = /* @__PURE__ */ new Map();
|
|
40818
|
+
const sourceBufferCache = /* @__PURE__ */ new Map();
|
|
40819
|
+
const sourceLinesCache = /* @__PURE__ */ new Map();
|
|
40820
|
+
const resolveAbsolutePath = (filename) => Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
|
|
40821
|
+
const readSourceBuffer = (filename) => {
|
|
40822
|
+
const absolutePath = resolveAbsolutePath(filename);
|
|
40823
|
+
const cached = sourceBufferCache.get(absolutePath);
|
|
40824
|
+
if (cached !== void 0) return cached;
|
|
40825
|
+
let sourceBuffer;
|
|
40826
|
+
try {
|
|
40827
|
+
sourceBuffer = NFS.readFileSync(absolutePath);
|
|
40828
|
+
} catch {
|
|
40829
|
+
sourceBuffer = null;
|
|
40830
|
+
}
|
|
40831
|
+
sourceBufferCache.set(absolutePath, sourceBuffer);
|
|
40832
|
+
return sourceBuffer;
|
|
40833
|
+
};
|
|
40834
|
+
const readSourceLines = (filename) => {
|
|
40835
|
+
const absolutePath = resolveAbsolutePath(filename);
|
|
40836
|
+
const cached = sourceLinesCache.get(absolutePath);
|
|
40837
|
+
if (cached !== void 0) return cached;
|
|
40838
|
+
const sourceBuffer = readSourceBuffer(filename);
|
|
40839
|
+
const sourceLines = sourceBuffer ? sourceBuffer.toString("utf8").split("\n") : null;
|
|
40840
|
+
sourceLinesCache.set(absolutePath, sourceLines);
|
|
40841
|
+
return sourceLines;
|
|
40842
|
+
};
|
|
40430
40843
|
const isMinifiedDiagnosticFile = (filename) => {
|
|
40431
|
-
const absolutePath =
|
|
40844
|
+
const absolutePath = resolveAbsolutePath(filename);
|
|
40432
40845
|
const cached = minifiedFileCache.get(absolutePath);
|
|
40433
40846
|
if (cached !== void 0) return cached;
|
|
40434
40847
|
const minified = isMinifiedSource(absolutePath);
|
|
40435
40848
|
minifiedFileCache.set(absolutePath, minified);
|
|
40436
40849
|
return minified;
|
|
40437
40850
|
};
|
|
40438
|
-
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic
|
|
40851
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
40439
40852
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
40440
40853
|
const primaryLabel = diagnostic.labels[0];
|
|
40441
40854
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
40442
40855
|
const normalizedFilePath = diagnostic.filename.replaceAll("\\", "/");
|
|
40443
40856
|
const primarySpan = primaryLabel?.span;
|
|
40857
|
+
const sourceBuffer = primarySpan ? readSourceBuffer(diagnostic.filename) : null;
|
|
40444
40858
|
const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath);
|
|
40445
40859
|
const category = resolveDiagnosticCategory(plugin, rule);
|
|
40860
|
+
const matchByOccurrence = resolveMatchByOccurrence(rule, category);
|
|
40861
|
+
const sourceLines = primarySpan ? readSourceLines(diagnostic.filename) : null;
|
|
40862
|
+
const primaryLineIndex = primarySpan ? primarySpan.line - 1 : -1;
|
|
40863
|
+
const primaryLine = sourceLines?.[primaryLineIndex];
|
|
40864
|
+
const primaryColumnIndex = primarySpan ? primarySpan.column - 1 : -1;
|
|
40865
|
+
const isJsxTagLabel = primaryLine !== void 0 && primaryColumnIndex >= 0 && (primaryLine[primaryColumnIndex] === "<" || primaryLine[primaryColumnIndex - 1] === "<");
|
|
40866
|
+
const jsxOpenerEndLineIndex = sourceLines && isJsxTagLabel ? findJsxOpenerSpan(sourceLines, primaryLineIndex) : null;
|
|
40867
|
+
const primarySpanEndLine = jsxOpenerEndLineIndex !== null ? jsxOpenerEndLineIndex + 1 : primarySpan && sourceBuffer ? lineOfUtf8Offset(sourceBuffer, primarySpan.offset + Math.max(primarySpan.length - 1, 0)) : void 0;
|
|
40446
40868
|
return {
|
|
40447
40869
|
filePath: normalizedFilePath,
|
|
40448
40870
|
plugin,
|
|
@@ -40456,10 +40878,11 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
40456
40878
|
column: primarySpan?.column ?? 0,
|
|
40457
40879
|
...primarySpan ? {
|
|
40458
40880
|
offset: primarySpan.offset,
|
|
40459
|
-
length: primarySpan.length
|
|
40881
|
+
length: primarySpan.length,
|
|
40882
|
+
...primarySpanEndLine !== void 0 ? { endLine: primarySpanEndLine } : {}
|
|
40460
40883
|
} : {},
|
|
40461
40884
|
category,
|
|
40462
|
-
...
|
|
40885
|
+
...matchByOccurrence ? { matchByOccurrence: true } : {},
|
|
40463
40886
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
40464
40887
|
};
|
|
40465
40888
|
}), rootDirectory);
|
|
@@ -42246,7 +42669,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
42246
42669
|
directory: scanDirectory,
|
|
42247
42670
|
repo
|
|
42248
42671
|
}).pipe(orElseSucceed(() => null)) : succeed$2(null));
|
|
42249
|
-
const lintIncludePaths = (input.
|
|
42672
|
+
const lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config);
|
|
42250
42673
|
const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
|
|
42251
42674
|
const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
|
|
42252
42675
|
const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
|
|
@@ -42260,7 +42683,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
42260
42683
|
respectInlineDisables: input.respectInlineDisables,
|
|
42261
42684
|
showWarnings
|
|
42262
42685
|
});
|
|
42263
|
-
const
|
|
42686
|
+
const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)));
|
|
42687
|
+
const applyPerElementPipeline = (rawStream) => filterPerElementPipeline(rawStream).pipe(tap((diagnostic) => reporterService.emit(diagnostic)));
|
|
42264
42688
|
const envCollected = yield* runCollect(applyPerElementPipeline(fromIterable$1(isDiffMode ? [] : [
|
|
42265
42689
|
...checkReducedMotion(scanDirectory),
|
|
42266
42690
|
...checkPnpmHardening(scanDirectory),
|
|
@@ -42389,7 +42813,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
42389
42813
|
});
|
|
42390
42814
|
return empty$4;
|
|
42391
42815
|
}))));
|
|
42392
|
-
const lintCollected = yield* runCollect(
|
|
42816
|
+
const lintCollected = dedupeRelatedDiagnostics(yield* runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
|
|
42393
42817
|
onNone: () => set(lintFailure, {
|
|
42394
42818
|
didFail: true,
|
|
42395
42819
|
reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
|
|
@@ -42397,7 +42821,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
42397
42821
|
reasonKind: null
|
|
42398
42822
|
}).pipe(as([])),
|
|
42399
42823
|
onSome: succeed$2
|
|
42400
|
-
})));
|
|
42824
|
+
}))));
|
|
42825
|
+
yield* forEach$1(lintCollected, reporterService.emit, { discard: true });
|
|
42401
42826
|
const lintFailureState = yield* get$2(lintFailure);
|
|
42402
42827
|
yield* afterLint(lintFailureState.didFail);
|
|
42403
42828
|
if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
|
|
@@ -42917,10 +43342,11 @@ const buildSkippedChecks = (input) => {
|
|
|
42917
43342
|
* render diff-base mistakes (`GitBaseBranchInvalid` /
|
|
42918
43343
|
* `GitBaseBranchMissing`) as clean user errors instead of crashes.
|
|
42919
43344
|
*/
|
|
42920
|
-
const getDiffInfo = (directory, explicitBaseBranch) => runPromise(gen(function* () {
|
|
43345
|
+
const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runPromise(gen(function* () {
|
|
42921
43346
|
const selection = yield* (yield* Git).diffSelection({
|
|
42922
43347
|
directory,
|
|
42923
|
-
explicitBaseBranch
|
|
43348
|
+
explicitBaseBranch,
|
|
43349
|
+
includeUntracked
|
|
42924
43350
|
});
|
|
42925
43351
|
if (selection === null) return null;
|
|
42926
43352
|
return {
|
|
@@ -43210,4 +43636,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
43210
43636
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
43211
43637
|
|
|
43212
43638
|
//# sourceMappingURL=index.js.map
|
|
43213
|
-
//# debugId=
|
|
43639
|
+
//# debugId=4096a0e2-9712-5e2f-b602-5ff55d3994a3
|