react-doctor 0.7.7-dev.ec144d9 → 0.7.7-dev.ed241c3

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.
Files changed (4) hide show
  1. package/dist/cli.js +427 -69
  2. package/dist/index.js +424 -66
  3. package/dist/lsp.js +424 -66
  4. package/package.json +4 -4
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]="1872748b-acb2-5e9a-a748-43bfd023eb85")}catch(e){}}();
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";
@@ -37182,18 +37182,45 @@ const checkReactServerComponentsAdvisory = (scanDirectory, project) => {
37182
37182
  }
37183
37183
  return diagnostics;
37184
37184
  };
37185
- const REDUCED_MOTION_GREP_PATTERN = "prefers-reduced-motion|useReducedMotion|MotionConfig|reducedMotion";
37186
- const REDUCED_MOTION_PATTERN = new RegExp(REDUCED_MOTION_GREP_PATTERN);
37187
- const REDUCED_MOTION_FILE_EXTENSIONS = new Set([
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 = [
37188
37214
  ".ts",
37189
37215
  ".tsx",
37190
37216
  ".js",
37191
- ".jsx",
37192
- ".css",
37193
- ".scss"
37194
- ]);
37195
- const REDUCED_MOTION_FILE_GLOBS = [...REDUCED_MOTION_FILE_EXTENSIONS].map((extension) => `*${extension}`);
37196
- const GIT_GREP_NO_MATCH_STATUS = 1;
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*:/;
37197
37224
  const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
37198
37225
  filePath: "package.json",
37199
37226
  plugin: "react-doctor",
@@ -37205,52 +37232,406 @@ const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
37205
37232
  column: 0,
37206
37233
  category: "Accessibility"
37207
37234
  };
37208
- const hasReducedMotionHandlingViaFilesystem = (rootDirectory) => {
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;
37209
37590
  for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
37210
- if (!REDUCED_MOTION_FILE_EXTENSIONS.has(Path.extname(name))) continue;
37591
+ const extension = Path.extname(name);
37592
+ if (!SCRIPT_FILE_EXTENSIONS.has(extension) && !STYLE_FILE_EXTENSIONS.has(extension)) continue;
37211
37593
  let content;
37212
37594
  try {
37213
37595
  content = NFS.readFileSync(absolutePath, "utf-8");
37214
37596
  } catch {
37215
37597
  continue;
37216
37598
  }
37217
- if (REDUCED_MOTION_PATTERN.test(content)) return true;
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
+ });
37218
37607
  }
37219
- return false;
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
+ };
37220
37619
  };
37221
37620
  const checkReducedMotion = (rootDirectory) => {
37222
37621
  const packageJsonPath = Path.join(rootDirectory, "package.json");
37223
37622
  if (!isFile(packageJsonPath)) return [];
37224
- let hasMotionLibrary = false;
37225
37623
  try {
37226
37624
  const packageJson = readPackageJson(packageJsonPath);
37227
37625
  const allDependencies = {
37228
37626
  ...packageJson.dependencies,
37229
37627
  ...packageJson.devDependencies
37230
37628
  };
37231
- hasMotionLibrary = Object.keys(allDependencies).some((packageName) => MOTION_LIBRARY_PACKAGES.has(packageName));
37629
+ if (!Object.keys(allDependencies).some((packageName) => MOTION_LIBRARY_PACKAGES.has(packageName))) return [];
37232
37630
  } catch {
37233
37631
  return [];
37234
37632
  }
37235
- if (!hasMotionLibrary) return [];
37236
- const result = spawnSync("git", [
37237
- "grep",
37238
- "--untracked",
37239
- "-lE",
37240
- REDUCED_MOTION_GREP_PATTERN,
37241
- "--",
37242
- ...REDUCED_MOTION_FILE_GLOBS
37243
- ], {
37244
- cwd: rootDirectory,
37245
- encoding: "utf-8",
37246
- stdio: [
37247
- "ignore",
37248
- "pipe",
37249
- "pipe"
37250
- ]
37251
- });
37252
- if (!(!result.error && (result.status === 0 || result.status === GIT_GREP_NO_MATCH_STATUS))) return hasReducedMotionHandlingViaFilesystem(rootDirectory) ? [] : [MISSING_REDUCED_MOTION_DIAGNOSTIC];
37253
- 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] : [];
37254
37635
  };
37255
37636
  const buildSecurityScanDiagnostic = (finding, entry, relativePath) => ({
37256
37637
  filePath: relativePath,
@@ -39853,23 +40234,12 @@ const REACT_USE_BINDING_RESOLUTION = {
39853
40234
  isReactUseBinding: true,
39854
40235
  isReactNamespaceBinding: false
39855
40236
  };
39856
- const getScriptKind$2 = (filename) => {
39857
- if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
39858
- if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
39859
- if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
39860
- return ts.ScriptKind.JS;
39861
- };
39862
40237
  const getUtf16Offset$1 = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
39863
- const unwrapExpression = (expression) => {
39864
- let currentExpression = expression;
39865
- while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
39866
- return currentExpression;
39867
- };
39868
40238
  const getStaticPropertyName = (node) => {
39869
40239
  if (!node) return null;
39870
40240
  if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
39871
40241
  if (ts.isComputedPropertyName(node)) {
39872
- const expression = unwrapExpression(node.expression);
40242
+ const expression = unwrapTypescriptExpression(node.expression);
39873
40243
  if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) return expression.text;
39874
40244
  }
39875
40245
  return null;
@@ -39898,7 +40268,7 @@ const isReactUseObjectBindingElement = (bindingElement) => {
39898
40268
  return getStaticPropertyName(bindingElement.propertyName) === USE_IDENTIFIER;
39899
40269
  };
39900
40270
  const isReactRequireCall = (expression) => {
39901
- const unwrappedExpression = unwrapExpression(expression);
40271
+ const unwrappedExpression = unwrapTypescriptExpression(expression);
39902
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;
39903
40273
  };
39904
40274
  const getModuleSource = (node) => {
@@ -40000,7 +40370,7 @@ const isIdentifierShadowedByLocalBinding = (identifier, sourceFile) => {
40000
40370
  return false;
40001
40371
  };
40002
40372
  const isReactNamespaceExpression = (expression, reactImportBindings, sourceFile, visitedDeclarations) => {
40003
- const unwrappedExpression = unwrapExpression(expression);
40373
+ const unwrappedExpression = unwrapTypescriptExpression(expression);
40004
40374
  if (isReactRequireCall(unwrappedExpression)) return true;
40005
40375
  if (!ts.isIdentifier(unwrappedExpression)) return false;
40006
40376
  if (reactImportBindings.namespaceNames.has(unwrappedExpression.text) && !isIdentifierShadowedByLocalBinding(unwrappedExpression, sourceFile)) return true;
@@ -40008,7 +40378,7 @@ const isReactNamespaceExpression = (expression, reactImportBindings, sourceFile,
40008
40378
  };
40009
40379
  const isReactUseExpression = (expression, reactImportBindings, sourceFile, visitedDeclarations) => {
40010
40380
  if (!expression) return false;
40011
- const unwrappedExpression = unwrapExpression(expression);
40381
+ const unwrappedExpression = unwrapTypescriptExpression(expression);
40012
40382
  if (ts.isIdentifier(unwrappedExpression)) {
40013
40383
  if (reactImportBindings.useImportNames.has(unwrappedExpression.text) && !isIdentifierShadowedByLocalBinding(unwrappedExpression, sourceFile)) return true;
40014
40384
  if (unwrappedExpression.text === USE_IDENTIFIER) return false;
@@ -40108,7 +40478,7 @@ const findUseCallIdentifier = (sourceFile, useOffset) => {
40108
40478
  return matchedIdentifier;
40109
40479
  };
40110
40480
  const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
40111
- const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$2(filename));
40481
+ const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getTypescriptScriptKind(filename));
40112
40482
  const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset$1(sourceText, utf8Offset));
40113
40483
  if (!useIdentifier) return null;
40114
40484
  return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
@@ -40144,12 +40514,6 @@ const WORKLET_ACCEPTING_CALLEES = new Set([
40144
40514
  "executeOnUIRuntimeSync"
40145
40515
  ]);
40146
40516
  const GESTURE_ROOT_IDENTIFIER = "Gesture";
40147
- const getScriptKind$1 = (filename) => {
40148
- if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
40149
- if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
40150
- if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
40151
- return ts.ScriptKind.JS;
40152
- };
40153
40517
  const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
40154
40518
  const hasWorkletDirective = (node) => {
40155
40519
  if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node)) return false;
@@ -40221,17 +40585,11 @@ const shouldSuppressCompilerFindingInWorklet = (diagnostic, project, rootDirecto
40221
40585
  } catch {
40222
40586
  return false;
40223
40587
  }
40224
- return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$1(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
40588
+ return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getTypescriptScriptKind(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
40225
40589
  };
40226
40590
  const MANUAL_MEMOIZATION_PLUGIN = "react-doctor";
40227
40591
  const MANUAL_MEMOIZATION_RULE = "react-compiler-no-manual-memoization";
40228
40592
  const REACT_COMPILER_PLUGIN = "react-hooks-js";
40229
- const getScriptKind = (filename) => {
40230
- if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
40231
- if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
40232
- if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
40233
- return ts.ScriptKind.JS;
40234
- };
40235
40593
  const isManualMemoizationDiagnostic = (diagnostic) => diagnostic.plugin === MANUAL_MEMOIZATION_PLUGIN && diagnostic.rule === MANUAL_MEMOIZATION_RULE;
40236
40594
  const nodeLineRange = (sourceFile, node) => ({
40237
40595
  startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
@@ -40293,7 +40651,7 @@ const suppressMemoizationInBailedOutFunctions = (diagnostics, rootDirectory) =>
40293
40651
  try {
40294
40652
  const buffer = NFS.readFileSync(absolutePath);
40295
40653
  parsed = {
40296
- sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
40654
+ sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getTypescriptScriptKind(absolutePath)),
40297
40655
  buffer
40298
40656
  };
40299
40657
  } catch {
@@ -43278,4 +43636,4 @@ const toJsonReport = (result, options) => buildJsonReport({
43278
43636
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
43279
43637
 
43280
43638
  //# sourceMappingURL=index.js.map
43281
- //# debugId=1872748b-acb2-5e9a-a748-43bfd023eb85
43639
+ //# debugId=4096a0e2-9712-5e2f-b602-5ff55d3994a3