react-doctor 0.7.7-dev.bdd1321 → 0.7.7-dev.ce4179b

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/lsp.js CHANGED
@@ -37160,18 +37160,45 @@ const checkReactServerComponentsAdvisory = (scanDirectory, project) => {
37160
37160
  }
37161
37161
  return diagnostics;
37162
37162
  };
37163
- const REDUCED_MOTION_GREP_PATTERN = "prefers-reduced-motion|useReducedMotion|MotionConfig|reducedMotion";
37164
- const REDUCED_MOTION_PATTERN = new RegExp(REDUCED_MOTION_GREP_PATTERN);
37165
- const REDUCED_MOTION_FILE_EXTENSIONS = new Set([
37163
+ const getTypescriptScriptKind = (fileName) => {
37164
+ if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
37165
+ if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
37166
+ if (fileName.endsWith(".ts")) return ts.ScriptKind.TS;
37167
+ return ts.ScriptKind.JS;
37168
+ };
37169
+ const unwrapTypescriptExpression = (expression) => {
37170
+ let currentExpression = expression;
37171
+ while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
37172
+ return currentExpression;
37173
+ };
37174
+ const EMPTY_MOTION_EXPRESSION_EVIDENCE = {
37175
+ isAnimationFunction: false,
37176
+ isMotionComponent: false,
37177
+ isMotionComponentFactory: false,
37178
+ isMotionComponentNamespace: false,
37179
+ isMotionConfig: false,
37180
+ isMotionNamespace: false,
37181
+ isReducedMotionHook: false
37182
+ };
37183
+ const MOTION_COMPONENT_FACTORY_EXPORT_NAMES = new Set(["m", "motion"]);
37184
+ const MOTION_COMPONENT_NAMESPACE_EXPORT_NAMES = new Set(["Reorder"]);
37185
+ const MOTION_COMPONENT_NAMESPACE_MEMBER_NAMES = new Set(["Item"]);
37186
+ const MOTION_ANIMATION_FUNCTION_EXPORT_NAMES = new Set(["animate"]);
37187
+ const REDUCED_MOTION_HOOK_EXPORT_NAME = "useReducedMotion";
37188
+ const MOTION_CONFIG_EXPORT_NAME = "MotionConfig";
37189
+ const REDUCED_MOTION_PROP_NAME = "reducedMotion";
37190
+ const REDUCED_MOTION_CONFIG_VALUES = new Set(["always", "user"]);
37191
+ const SCRIPT_MODULE_EXTENSIONS = [
37166
37192
  ".ts",
37167
37193
  ".tsx",
37168
37194
  ".js",
37169
- ".jsx",
37170
- ".css",
37171
- ".scss"
37172
- ]);
37173
- const REDUCED_MOTION_FILE_GLOBS = [...REDUCED_MOTION_FILE_EXTENSIONS].map((extension) => `*${extension}`);
37174
- const GIT_GREP_NO_MATCH_STATUS = 1;
37195
+ ".jsx"
37196
+ ];
37197
+ const SCRIPT_FILE_EXTENSIONS = new Set(SCRIPT_MODULE_EXTENSIONS);
37198
+ const STYLE_FILE_EXTENSIONS = new Set([".css", ".scss"]);
37199
+ const MOTION_SOURCE_PREFILTER = /framer-motion|["']motion(?:\/[A-Za-z0-9_./-]+)?["']|MotionConfig|useReducedMotion/;
37200
+ const REDUCED_MOTION_MEDIA_QUERY_PATTERN = /@media\s+([^{}]*\(\s*prefers-reduced-motion\s*:\s*reduce\s*\)[^{}]*)\{/gi;
37201
+ const CSS_DECLARATION_PATTERN = /(?:^|[;{])\s*[-_A-Za-z][-_A-Za-z0-9]*\s*:/;
37175
37202
  const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
37176
37203
  filePath: "package.json",
37177
37204
  plugin: "react-doctor",
@@ -37183,52 +37210,406 @@ const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
37183
37210
  column: 0,
37184
37211
  category: "Accessibility"
37185
37212
  };
37186
- const hasReducedMotionHandlingViaFilesystem = (rootDirectory) => {
37213
+ const isMotionModuleSource = (moduleSource) => {
37214
+ for (const packageName of MOTION_LIBRARY_PACKAGES) if (moduleSource === packageName || moduleSource.startsWith(`${packageName}/`)) return true;
37215
+ return false;
37216
+ };
37217
+ const classifyMotionExport = (exportName) => ({
37218
+ isAnimationFunction: MOTION_ANIMATION_FUNCTION_EXPORT_NAMES.has(exportName),
37219
+ isMotionComponent: false,
37220
+ isMotionComponentFactory: MOTION_COMPONENT_FACTORY_EXPORT_NAMES.has(exportName),
37221
+ isMotionComponentNamespace: MOTION_COMPONENT_NAMESPACE_EXPORT_NAMES.has(exportName),
37222
+ isMotionConfig: exportName === MOTION_CONFIG_EXPORT_NAME,
37223
+ isMotionNamespace: false,
37224
+ isReducedMotionHook: exportName === REDUCED_MOTION_HOOK_EXPORT_NAME
37225
+ });
37226
+ const getImportModuleSource = (node) => {
37227
+ let currentNode = node;
37228
+ while (currentNode) {
37229
+ if (ts.isImportDeclaration(currentNode) && ts.isStringLiteral(currentNode.moduleSpecifier)) return currentNode.moduleSpecifier.text;
37230
+ currentNode = currentNode.parent;
37231
+ }
37232
+ return null;
37233
+ };
37234
+ const getImportedBindingEvidence = (declaration, typeChecker, program, visitedSymbols) => {
37235
+ const moduleSource = getImportModuleSource(declaration);
37236
+ if (!moduleSource) return null;
37237
+ if (isMotionModuleSource(moduleSource) && ts.isNamespaceImport(declaration)) return {
37238
+ ...EMPTY_MOTION_EXPRESSION_EVIDENCE,
37239
+ isMotionNamespace: true
37240
+ };
37241
+ if (isMotionModuleSource(moduleSource) && ts.isImportSpecifier(declaration)) {
37242
+ if (declaration.isTypeOnly) return null;
37243
+ return classifyMotionExport(declaration.propertyName?.text ?? declaration.name.text);
37244
+ }
37245
+ if (isMotionModuleSource(moduleSource) && ts.isImportClause(declaration)) {
37246
+ if (declaration.isTypeOnly) return null;
37247
+ return EMPTY_MOTION_EXPRESSION_EVIDENCE;
37248
+ }
37249
+ if (ts.isImportSpecifier(declaration) && !declaration.isTypeOnly || ts.isImportClause(declaration) && !declaration.isTypeOnly && declaration.name) {
37250
+ const importedName = ts.isImportSpecifier(declaration) ? declaration.propertyName?.text ?? declaration.name.text : "default";
37251
+ const moduleSourceFile = getLocalModuleSourceFile(moduleSource, declaration.getSourceFile().fileName, program);
37252
+ if (!moduleSourceFile) return null;
37253
+ return resolveModuleExportEvidence(moduleSourceFile, importedName, typeChecker, program, visitedSymbols);
37254
+ }
37255
+ return null;
37256
+ };
37257
+ const getLocalModuleSourceFile = (moduleSource, importingFileName, program) => {
37258
+ if (!moduleSource.startsWith(".")) return null;
37259
+ const moduleBasePath = Path.resolve(Path.dirname(importingFileName), moduleSource);
37260
+ const moduleBaseExtension = Path.extname(moduleBasePath);
37261
+ const typescriptModuleBasePath = moduleBaseExtension === ".js" || moduleBaseExtension === ".jsx" ? moduleBasePath.slice(0, -moduleBaseExtension.length) : null;
37262
+ const candidates = [
37263
+ moduleBasePath,
37264
+ ...typescriptModuleBasePath ? [
37265
+ `${typescriptModuleBasePath}.ts`,
37266
+ `${typescriptModuleBasePath}.tsx`,
37267
+ Path.join(typescriptModuleBasePath, "index.ts"),
37268
+ Path.join(typescriptModuleBasePath, "index.tsx")
37269
+ ] : [],
37270
+ ...SCRIPT_MODULE_EXTENSIONS.map((extension) => `${moduleBasePath}${extension}`),
37271
+ ...SCRIPT_MODULE_EXTENSIONS.map((extension) => Path.join(moduleBasePath, `index${extension}`))
37272
+ ];
37273
+ for (const candidate of candidates) {
37274
+ const sourceFile = program.getSourceFile(candidate);
37275
+ if (sourceFile) return sourceFile;
37276
+ }
37277
+ return null;
37278
+ };
37279
+ const resolveModuleExportEvidence = (sourceFile, exportName, typeChecker, program, visitedSymbols) => {
37280
+ const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
37281
+ if (moduleSymbol && visitedSymbols.has(moduleSymbol)) return EMPTY_MOTION_EXPRESSION_EVIDENCE;
37282
+ const nextVisitedSymbols = new Set(visitedSymbols);
37283
+ if (moduleSymbol) nextVisitedSymbols.add(moduleSymbol);
37284
+ for (const statement of sourceFile.statements) {
37285
+ if (exportName === "default" && ts.isExportAssignment(statement) && !statement.isExportEquals) {
37286
+ const evidence = resolveMotionExpressionEvidence(statement.expression, typeChecker, program, nextVisitedSymbols);
37287
+ if (hasMotionExpressionEvidence(evidence)) return evidence;
37288
+ continue;
37289
+ }
37290
+ if (!ts.isExportDeclaration(statement) || !statement.moduleSpecifier) continue;
37291
+ if (statement.isTypeOnly) continue;
37292
+ if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
37293
+ const moduleSource = statement.moduleSpecifier.text;
37294
+ if (!statement.exportClause) {
37295
+ if (isMotionModuleSource(moduleSource)) {
37296
+ const evidence = classifyMotionExport(exportName);
37297
+ if (hasMotionExpressionEvidence(evidence)) return evidence;
37298
+ continue;
37299
+ }
37300
+ const moduleSourceFile = getLocalModuleSourceFile(moduleSource, sourceFile.fileName, program);
37301
+ if (moduleSourceFile) {
37302
+ const evidence = resolveModuleExportEvidence(moduleSourceFile, exportName, typeChecker, program, nextVisitedSymbols);
37303
+ if (hasMotionExpressionEvidence(evidence)) return evidence;
37304
+ }
37305
+ continue;
37306
+ }
37307
+ if (!ts.isNamedExports(statement.exportClause)) continue;
37308
+ for (const exportSpecifier of statement.exportClause.elements) {
37309
+ if (exportSpecifier.name.text !== exportName || exportSpecifier.isTypeOnly) continue;
37310
+ const sourceExportName = exportSpecifier.propertyName?.text ?? exportSpecifier.name.text;
37311
+ if (isMotionModuleSource(moduleSource)) return classifyMotionExport(sourceExportName);
37312
+ const moduleSourceFile = getLocalModuleSourceFile(moduleSource, sourceFile.fileName, program);
37313
+ if (!moduleSourceFile) continue;
37314
+ return resolveModuleExportEvidence(moduleSourceFile, sourceExportName, typeChecker, program, nextVisitedSymbols);
37315
+ }
37316
+ }
37317
+ if (!moduleSymbol) return EMPTY_MOTION_EXPRESSION_EVIDENCE;
37318
+ const exportedSymbol = typeChecker.getExportsOfModule(moduleSymbol).find((candidateSymbol) => candidateSymbol.name === exportName);
37319
+ if (!exportedSymbol || nextVisitedSymbols.has(exportedSymbol)) return EMPTY_MOTION_EXPRESSION_EVIDENCE;
37320
+ nextVisitedSymbols.add(exportedSymbol);
37321
+ let resolvedEvidence = EMPTY_MOTION_EXPRESSION_EVIDENCE;
37322
+ for (const declaration of exportedSymbol.declarations ?? []) {
37323
+ if (ts.isExportSpecifier(declaration)) {
37324
+ const targetSymbol = typeChecker.getExportSpecifierLocalTargetSymbol(declaration);
37325
+ for (const targetDeclaration of targetSymbol?.declarations ?? []) {
37326
+ const importedEvidence = getImportedBindingEvidence(targetDeclaration, typeChecker, program, nextVisitedSymbols);
37327
+ if (importedEvidence) resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, importedEvidence);
37328
+ if (ts.isVariableDeclaration(targetDeclaration) && targetDeclaration.initializer) resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, resolveMotionExpressionEvidence(targetDeclaration.initializer, typeChecker, program, nextVisitedSymbols));
37329
+ }
37330
+ }
37331
+ if (ts.isVariableDeclaration(declaration) && declaration.initializer) resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, resolveMotionExpressionEvidence(declaration.initializer, typeChecker, program, nextVisitedSymbols));
37332
+ }
37333
+ return resolvedEvidence;
37334
+ };
37335
+ const mergeMotionExpressionEvidence = (leftEvidence, rightEvidence) => ({
37336
+ isAnimationFunction: leftEvidence.isAnimationFunction || rightEvidence.isAnimationFunction,
37337
+ isMotionComponent: leftEvidence.isMotionComponent || rightEvidence.isMotionComponent,
37338
+ isMotionComponentFactory: leftEvidence.isMotionComponentFactory || rightEvidence.isMotionComponentFactory,
37339
+ isMotionComponentNamespace: leftEvidence.isMotionComponentNamespace || rightEvidence.isMotionComponentNamespace,
37340
+ isMotionConfig: leftEvidence.isMotionConfig || rightEvidence.isMotionConfig,
37341
+ isMotionNamespace: leftEvidence.isMotionNamespace || rightEvidence.isMotionNamespace,
37342
+ isReducedMotionHook: leftEvidence.isReducedMotionHook || rightEvidence.isReducedMotionHook
37343
+ });
37344
+ const hasMotionExpressionEvidence = (evidence) => evidence.isAnimationFunction || evidence.isMotionComponent || evidence.isMotionComponentFactory || evidence.isMotionComponentNamespace || evidence.isMotionConfig || evidence.isMotionNamespace || evidence.isReducedMotionHook;
37345
+ const classifyMotionMember = (receiverEvidence, memberName) => {
37346
+ if (receiverEvidence.isMotionNamespace) return classifyMotionExport(memberName);
37347
+ if (receiverEvidence.isMotionComponentFactory) return {
37348
+ ...EMPTY_MOTION_EXPRESSION_EVIDENCE,
37349
+ isMotionComponent: true
37350
+ };
37351
+ if (receiverEvidence.isMotionComponentNamespace && MOTION_COMPONENT_NAMESPACE_MEMBER_NAMES.has(memberName)) return {
37352
+ ...EMPTY_MOTION_EXPRESSION_EVIDENCE,
37353
+ isMotionComponent: true
37354
+ };
37355
+ return EMPTY_MOTION_EXPRESSION_EVIDENCE;
37356
+ };
37357
+ const resolveMotionExpressionEvidence = (expression, typeChecker, program, visitedSymbols = /* @__PURE__ */ new Set()) => {
37358
+ const unwrappedExpression = unwrapTypescriptExpression(expression);
37359
+ if (ts.isIdentifier(unwrappedExpression)) {
37360
+ const symbol = typeChecker.getSymbolAtLocation(unwrappedExpression);
37361
+ if (!symbol || visitedSymbols.has(symbol)) return EMPTY_MOTION_EXPRESSION_EVIDENCE;
37362
+ const nextVisitedSymbols = new Set(visitedSymbols);
37363
+ nextVisitedSymbols.add(symbol);
37364
+ let resolvedEvidence = EMPTY_MOTION_EXPRESSION_EVIDENCE;
37365
+ for (const declaration of symbol.declarations ?? []) {
37366
+ const importedEvidence = getImportedBindingEvidence(declaration, typeChecker, program, nextVisitedSymbols);
37367
+ if (importedEvidence) {
37368
+ resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, importedEvidence);
37369
+ continue;
37370
+ }
37371
+ if (ts.isVariableDeclaration(declaration) && declaration.initializer) resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, resolveMotionExpressionEvidence(declaration.initializer, typeChecker, program, nextVisitedSymbols));
37372
+ if (ts.isBindingElement(declaration) && ts.isObjectBindingPattern(declaration.parent) && ts.isVariableDeclaration(declaration.parent.parent) && declaration.parent.parent.initializer) {
37373
+ const propertyName = declaration.propertyName ?? declaration.name;
37374
+ if (ts.isIdentifier(propertyName) || ts.isStringLiteral(propertyName)) {
37375
+ const receiverEvidence = resolveMotionExpressionEvidence(declaration.parent.parent.initializer, typeChecker, program, nextVisitedSymbols);
37376
+ resolvedEvidence = mergeMotionExpressionEvidence(resolvedEvidence, classifyMotionMember(receiverEvidence, propertyName.text));
37377
+ }
37378
+ }
37379
+ }
37380
+ return resolvedEvidence;
37381
+ }
37382
+ if (ts.isPropertyAccessExpression(unwrappedExpression)) return classifyMotionMember(resolveMotionExpressionEvidence(unwrappedExpression.expression, typeChecker, program, visitedSymbols), unwrappedExpression.name.text);
37383
+ 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);
37384
+ if (ts.isCallExpression(unwrappedExpression)) {
37385
+ 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 {
37386
+ ...EMPTY_MOTION_EXPRESSION_EVIDENCE,
37387
+ isMotionNamespace: true
37388
+ };
37389
+ if (ts.isPropertyAccessExpression(unwrappedExpression.expression) && unwrappedExpression.expression.name.text === "bind") {
37390
+ const boundReceiverEvidence = resolveMotionExpressionEvidence(unwrappedExpression.expression.expression, typeChecker, program, visitedSymbols);
37391
+ if (boundReceiverEvidence.isAnimationFunction) return boundReceiverEvidence;
37392
+ }
37393
+ const calleeEvidence = resolveMotionExpressionEvidence(unwrappedExpression.expression, typeChecker, program, visitedSymbols);
37394
+ if (calleeEvidence.isMotionComponentFactory) return {
37395
+ ...EMPTY_MOTION_EXPRESSION_EVIDENCE,
37396
+ isMotionComponent: true
37397
+ };
37398
+ return calleeEvidence;
37399
+ }
37400
+ return EMPTY_MOTION_EXPRESSION_EVIDENCE;
37401
+ };
37402
+ const getStaticStringExpressionValue = (expression, typeChecker, visitedSymbols = /* @__PURE__ */ new Set()) => {
37403
+ const unwrappedExpression = unwrapTypescriptExpression(expression);
37404
+ if (ts.isStringLiteral(unwrappedExpression) || ts.isNoSubstitutionTemplateLiteral(unwrappedExpression)) return unwrappedExpression.text;
37405
+ if (!ts.isIdentifier(unwrappedExpression)) return null;
37406
+ const symbol = typeChecker.getSymbolAtLocation(unwrappedExpression);
37407
+ if (!symbol || visitedSymbols.has(symbol)) return null;
37408
+ const nextVisitedSymbols = new Set(visitedSymbols);
37409
+ nextVisitedSymbols.add(symbol);
37410
+ for (const declaration of symbol.declarations ?? []) {
37411
+ if (!ts.isVariableDeclaration(declaration) || !declaration.initializer) continue;
37412
+ if (!ts.isVariableDeclarationList(declaration.parent)) continue;
37413
+ if (!(declaration.parent.flags & ts.NodeFlags.Const)) continue;
37414
+ const value = getStaticStringExpressionValue(declaration.initializer, typeChecker, nextVisitedSymbols);
37415
+ if (value !== null) return value;
37416
+ }
37417
+ return null;
37418
+ };
37419
+ const getStaticJsxAttributeValue = (attribute, typeChecker) => {
37420
+ if (!attribute.initializer) return null;
37421
+ if (ts.isStringLiteral(attribute.initializer)) return attribute.initializer.text;
37422
+ if (!ts.isJsxExpression(attribute.initializer) || !attribute.initializer.expression) return null;
37423
+ return getStaticStringExpressionValue(attribute.initializer.expression, typeChecker);
37424
+ };
37425
+ const isExpressionValueConsumed = (expression) => {
37426
+ let currentExpression = expression;
37427
+ while (ts.isParenthesizedExpression(currentExpression.parent) || ts.isAsExpression(currentExpression.parent) || ts.isSatisfiesExpression(currentExpression.parent) || ts.isNonNullExpression(currentExpression.parent) || ts.isTypeAssertionExpression(currentExpression.parent)) currentExpression = currentExpression.parent;
37428
+ return !ts.isExpressionStatement(currentExpression.parent) && !ts.isVoidExpression(currentExpression.parent);
37429
+ };
37430
+ 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) ?? ""));
37431
+ const collectScriptMotionEvidence = (sourceFile, typeChecker, program) => {
37432
+ const evidence = {
37433
+ hasMotionUse: false,
37434
+ hasReducedMotionHandling: false
37435
+ };
37436
+ const visitNode = (node) => {
37437
+ if (ts.isCallExpression(node)) {
37438
+ const calleeEvidence = resolveMotionExpressionEvidence(node.expression, typeChecker, program);
37439
+ 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;
37440
+ if (calleeEvidence.isAnimationFunction || isAnimationFunctionCallViaCallOrApply) evidence.hasMotionUse = true;
37441
+ if (calleeEvidence.isReducedMotionHook && isExpressionValueConsumed(node)) evidence.hasReducedMotionHandling = true;
37442
+ }
37443
+ if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
37444
+ const tagEvidence = ts.isJsxNamespacedName(node.tagName) ? EMPTY_MOTION_EXPRESSION_EVIDENCE : resolveMotionExpressionEvidence(node.tagName, typeChecker, program);
37445
+ if (tagEvidence.isMotionComponent || tagEvidence.isMotionComponentFactory) evidence.hasMotionUse = true;
37446
+ if (tagEvidence.isMotionConfig && jsxElementHasReducedMotionConfiguration(node.attributes, typeChecker)) evidence.hasReducedMotionHandling = true;
37447
+ }
37448
+ ts.forEachChild(node, visitNode);
37449
+ };
37450
+ visitNode(sourceFile);
37451
+ return evidence;
37452
+ };
37453
+ const analyzeReducedMotionSources = (sources) => {
37454
+ if (!sources.some(({ sourceText }) => MOTION_SOURCE_PREFILTER.test(sourceText))) return {
37455
+ hasMotionUse: false,
37456
+ hasReducedMotionHandling: false
37457
+ };
37458
+ const compilerOptions = {
37459
+ allowJs: true,
37460
+ checkJs: false,
37461
+ jsx: ts.JsxEmit.Preserve,
37462
+ module: ts.ModuleKind.ESNext,
37463
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
37464
+ noEmit: true,
37465
+ noLib: true,
37466
+ noResolve: true,
37467
+ skipLibCheck: true,
37468
+ target: ts.ScriptTarget.Latest
37469
+ };
37470
+ const sourceTextByFileName = new Map(sources.map(({ fileName: sourceFileName, sourceText: content }) => [Path.resolve(sourceFileName), content]));
37471
+ const compilerHost = ts.createCompilerHost(compilerOptions);
37472
+ const getDefaultSourceFile = compilerHost.getSourceFile.bind(compilerHost);
37473
+ 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);
37474
+ const program = ts.createProgram([...sourceTextByFileName.keys()], compilerOptions, compilerHost);
37475
+ const typeChecker = program.getTypeChecker();
37476
+ const evidence = {
37477
+ hasMotionUse: false,
37478
+ hasReducedMotionHandling: false
37479
+ };
37480
+ for (const sourceFileName of sourceTextByFileName.keys()) {
37481
+ const sourceFile = program.getSourceFile(sourceFileName);
37482
+ if (!sourceFile) continue;
37483
+ const fileEvidence = collectScriptMotionEvidence(sourceFile, typeChecker, program);
37484
+ evidence.hasMotionUse ||= fileEvidence.hasMotionUse;
37485
+ evidence.hasReducedMotionHandling ||= fileEvidence.hasReducedMotionHandling;
37486
+ }
37487
+ return evidence;
37488
+ };
37489
+ const removeCssCommentsAndStrings = (content) => {
37490
+ let sanitizedContent = "";
37491
+ let quote = null;
37492
+ let isInsideComment = false;
37493
+ let isInsideLineComment = false;
37494
+ for (let characterIndex = 0; characterIndex < content.length; characterIndex += 1) {
37495
+ const character = content[characterIndex];
37496
+ const nextCharacter = content[characterIndex + 1];
37497
+ if (isInsideLineComment) {
37498
+ sanitizedContent += character === "\n" ? "\n" : " ";
37499
+ if (character === "\n") isInsideLineComment = false;
37500
+ continue;
37501
+ }
37502
+ if (isInsideComment) {
37503
+ if (character === "*" && nextCharacter === "/") {
37504
+ sanitizedContent += " ";
37505
+ characterIndex += 1;
37506
+ isInsideComment = false;
37507
+ } else sanitizedContent += character === "\n" ? "\n" : " ";
37508
+ continue;
37509
+ }
37510
+ if (quote) {
37511
+ if (character === "\\") {
37512
+ sanitizedContent += " ";
37513
+ characterIndex += 1;
37514
+ continue;
37515
+ }
37516
+ if (character === quote) quote = null;
37517
+ sanitizedContent += character === "\n" ? "\n" : " ";
37518
+ continue;
37519
+ }
37520
+ if (character === "/" && nextCharacter === "*") {
37521
+ sanitizedContent += " ";
37522
+ characterIndex += 1;
37523
+ isInsideComment = true;
37524
+ continue;
37525
+ }
37526
+ if (character === "/" && nextCharacter === "/") {
37527
+ sanitizedContent += " ";
37528
+ characterIndex += 1;
37529
+ isInsideLineComment = true;
37530
+ continue;
37531
+ }
37532
+ if (character === "\"" || character === "'") {
37533
+ sanitizedContent += " ";
37534
+ quote = character;
37535
+ continue;
37536
+ }
37537
+ sanitizedContent += character;
37538
+ }
37539
+ return sanitizedContent;
37540
+ };
37541
+ const findMatchingClosingBrace = (content, openingBraceIndex) => {
37542
+ let depth = 0;
37543
+ for (let characterIndex = openingBraceIndex; characterIndex < content.length; characterIndex += 1) {
37544
+ if (content[characterIndex] === "{") depth += 1;
37545
+ if (content[characterIndex] !== "}") continue;
37546
+ depth -= 1;
37547
+ if (depth === 0) return characterIndex;
37548
+ }
37549
+ return -1;
37550
+ };
37551
+ const hasReducedMotionMediaQuery = (content) => {
37552
+ const sanitizedContent = removeCssCommentsAndStrings(content);
37553
+ REDUCED_MOTION_MEDIA_QUERY_PATTERN.lastIndex = 0;
37554
+ for (const match of sanitizedContent.matchAll(REDUCED_MOTION_MEDIA_QUERY_PATTERN)) {
37555
+ const mediaPrelude = match[1];
37556
+ if (/\bnot\b/i.test(mediaPrelude)) continue;
37557
+ const openingBraceIndex = (match.index ?? 0) + match[0].lastIndexOf("{");
37558
+ const closingBraceIndex = findMatchingClosingBrace(sanitizedContent, openingBraceIndex);
37559
+ if (closingBraceIndex === -1) continue;
37560
+ const body = sanitizedContent.slice(openingBraceIndex + 1, closingBraceIndex);
37561
+ if (CSS_DECLARATION_PATTERN.test(body)) return true;
37562
+ }
37563
+ return false;
37564
+ };
37565
+ const collectProjectMotionEvidence = (rootDirectory) => {
37566
+ const scriptSources = [];
37567
+ let hasReducedMotionHandling = false;
37187
37568
  for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
37188
- if (!REDUCED_MOTION_FILE_EXTENSIONS.has(Path.extname(name))) continue;
37569
+ const extension = Path.extname(name);
37570
+ if (!SCRIPT_FILE_EXTENSIONS.has(extension) && !STYLE_FILE_EXTENSIONS.has(extension)) continue;
37189
37571
  let content;
37190
37572
  try {
37191
37573
  content = NFS.readFileSync(absolutePath, "utf-8");
37192
37574
  } catch {
37193
37575
  continue;
37194
37576
  }
37195
- if (REDUCED_MOTION_PATTERN.test(content)) return true;
37577
+ if (STYLE_FILE_EXTENSIONS.has(extension)) {
37578
+ if (hasReducedMotionMediaQuery(content)) hasReducedMotionHandling = true;
37579
+ continue;
37580
+ }
37581
+ scriptSources.push({
37582
+ fileName: absolutePath,
37583
+ sourceText: content
37584
+ });
37196
37585
  }
37197
- return false;
37586
+ if (scriptSources.length === 0) return {
37587
+ hasMotionUse: false,
37588
+ hasReducedMotionHandling
37589
+ };
37590
+ const scriptEvidence = analyzeReducedMotionSources(scriptSources);
37591
+ const hasMotionUse = scriptEvidence.hasMotionUse;
37592
+ hasReducedMotionHandling ||= scriptEvidence.hasReducedMotionHandling;
37593
+ return {
37594
+ hasMotionUse,
37595
+ hasReducedMotionHandling
37596
+ };
37198
37597
  };
37199
37598
  const checkReducedMotion = (rootDirectory) => {
37200
37599
  const packageJsonPath = Path.join(rootDirectory, "package.json");
37201
37600
  if (!isFile(packageJsonPath)) return [];
37202
- let hasMotionLibrary = false;
37203
37601
  try {
37204
37602
  const packageJson = readPackageJson(packageJsonPath);
37205
37603
  const allDependencies = {
37206
37604
  ...packageJson.dependencies,
37207
37605
  ...packageJson.devDependencies
37208
37606
  };
37209
- hasMotionLibrary = Object.keys(allDependencies).some((packageName) => MOTION_LIBRARY_PACKAGES.has(packageName));
37607
+ if (!Object.keys(allDependencies).some((packageName) => MOTION_LIBRARY_PACKAGES.has(packageName))) return [];
37210
37608
  } catch {
37211
37609
  return [];
37212
37610
  }
37213
- if (!hasMotionLibrary) return [];
37214
- const result = spawnSync("git", [
37215
- "grep",
37216
- "--untracked",
37217
- "-lE",
37218
- REDUCED_MOTION_GREP_PATTERN,
37219
- "--",
37220
- ...REDUCED_MOTION_FILE_GLOBS
37221
- ], {
37222
- cwd: rootDirectory,
37223
- encoding: "utf-8",
37224
- stdio: [
37225
- "ignore",
37226
- "pipe",
37227
- "pipe"
37228
- ]
37229
- });
37230
- if (!(!result.error && (result.status === 0 || result.status === GIT_GREP_NO_MATCH_STATUS))) return hasReducedMotionHandlingViaFilesystem(rootDirectory) ? [] : [MISSING_REDUCED_MOTION_DIAGNOSTIC];
37231
- return result.status === 0 && result.stdout.split("\n").some((filePath) => filePath.length > 0 && !hasIgnoredPathSegment(filePath)) ? [] : [MISSING_REDUCED_MOTION_DIAGNOSTIC];
37611
+ const evidence = collectProjectMotionEvidence(rootDirectory);
37612
+ return evidence.hasMotionUse && !evidence.hasReducedMotionHandling ? [MISSING_REDUCED_MOTION_DIAGNOSTIC] : [];
37232
37613
  };
37233
37614
  const buildSecurityScanDiagnostic = (finding, entry, relativePath) => ({
37234
37615
  filePath: relativePath,
@@ -39831,23 +40212,12 @@ const REACT_USE_BINDING_RESOLUTION = {
39831
40212
  isReactUseBinding: true,
39832
40213
  isReactNamespaceBinding: false
39833
40214
  };
39834
- const getScriptKind$2 = (filename) => {
39835
- if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
39836
- if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
39837
- if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
39838
- return ts.ScriptKind.JS;
39839
- };
39840
40215
  const getUtf16Offset$1 = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
39841
- const unwrapExpression = (expression) => {
39842
- let currentExpression = expression;
39843
- while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
39844
- return currentExpression;
39845
- };
39846
40216
  const getStaticPropertyName = (node) => {
39847
40217
  if (!node) return null;
39848
40218
  if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
39849
40219
  if (ts.isComputedPropertyName(node)) {
39850
- const expression = unwrapExpression(node.expression);
40220
+ const expression = unwrapTypescriptExpression(node.expression);
39851
40221
  if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) return expression.text;
39852
40222
  }
39853
40223
  return null;
@@ -39876,7 +40246,7 @@ const isReactUseObjectBindingElement = (bindingElement) => {
39876
40246
  return getStaticPropertyName(bindingElement.propertyName) === USE_IDENTIFIER;
39877
40247
  };
39878
40248
  const isReactRequireCall = (expression) => {
39879
- const unwrappedExpression = unwrapExpression(expression);
40249
+ const unwrappedExpression = unwrapTypescriptExpression(expression);
39880
40250
  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;
39881
40251
  };
39882
40252
  const getModuleSource = (node) => {
@@ -39978,7 +40348,7 @@ const isIdentifierShadowedByLocalBinding = (identifier, sourceFile) => {
39978
40348
  return false;
39979
40349
  };
39980
40350
  const isReactNamespaceExpression = (expression, reactImportBindings, sourceFile, visitedDeclarations) => {
39981
- const unwrappedExpression = unwrapExpression(expression);
40351
+ const unwrappedExpression = unwrapTypescriptExpression(expression);
39982
40352
  if (isReactRequireCall(unwrappedExpression)) return true;
39983
40353
  if (!ts.isIdentifier(unwrappedExpression)) return false;
39984
40354
  if (reactImportBindings.namespaceNames.has(unwrappedExpression.text) && !isIdentifierShadowedByLocalBinding(unwrappedExpression, sourceFile)) return true;
@@ -39986,7 +40356,7 @@ const isReactNamespaceExpression = (expression, reactImportBindings, sourceFile,
39986
40356
  };
39987
40357
  const isReactUseExpression = (expression, reactImportBindings, sourceFile, visitedDeclarations) => {
39988
40358
  if (!expression) return false;
39989
- const unwrappedExpression = unwrapExpression(expression);
40359
+ const unwrappedExpression = unwrapTypescriptExpression(expression);
39990
40360
  if (ts.isIdentifier(unwrappedExpression)) {
39991
40361
  if (reactImportBindings.useImportNames.has(unwrappedExpression.text) && !isIdentifierShadowedByLocalBinding(unwrappedExpression, sourceFile)) return true;
39992
40362
  if (unwrappedExpression.text === USE_IDENTIFIER) return false;
@@ -40086,7 +40456,7 @@ const findUseCallIdentifier = (sourceFile, useOffset) => {
40086
40456
  return matchedIdentifier;
40087
40457
  };
40088
40458
  const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
40089
- const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$2(filename));
40459
+ const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getTypescriptScriptKind(filename));
40090
40460
  const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset$1(sourceText, utf8Offset));
40091
40461
  if (!useIdentifier) return null;
40092
40462
  return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
@@ -40122,12 +40492,6 @@ const WORKLET_ACCEPTING_CALLEES = new Set([
40122
40492
  "executeOnUIRuntimeSync"
40123
40493
  ]);
40124
40494
  const GESTURE_ROOT_IDENTIFIER = "Gesture";
40125
- const getScriptKind$1 = (filename) => {
40126
- if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
40127
- if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
40128
- if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
40129
- return ts.ScriptKind.JS;
40130
- };
40131
40495
  const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
40132
40496
  const hasWorkletDirective = (node) => {
40133
40497
  if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node)) return false;
@@ -40199,17 +40563,11 @@ const shouldSuppressCompilerFindingInWorklet = (diagnostic, project, rootDirecto
40199
40563
  } catch {
40200
40564
  return false;
40201
40565
  }
40202
- return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$1(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
40566
+ return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getTypescriptScriptKind(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
40203
40567
  };
40204
40568
  const MANUAL_MEMOIZATION_PLUGIN = "react-doctor";
40205
40569
  const MANUAL_MEMOIZATION_RULE = "react-compiler-no-manual-memoization";
40206
40570
  const REACT_COMPILER_PLUGIN = "react-hooks-js";
40207
- const getScriptKind = (filename) => {
40208
- if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
40209
- if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
40210
- if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
40211
- return ts.ScriptKind.JS;
40212
- };
40213
40571
  const isManualMemoizationDiagnostic = (diagnostic) => diagnostic.plugin === MANUAL_MEMOIZATION_PLUGIN && diagnostic.rule === MANUAL_MEMOIZATION_RULE;
40214
40572
  const nodeLineRange = (sourceFile, node) => ({
40215
40573
  startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
@@ -40271,7 +40629,7 @@ const suppressMemoizationInBailedOutFunctions = (diagnostics, rootDirectory) =>
40271
40629
  try {
40272
40630
  const buffer = NFS.readFileSync(absolutePath);
40273
40631
  parsed = {
40274
- sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
40632
+ sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getTypescriptScriptKind(absolutePath)),
40275
40633
  buffer
40276
40634
  };
40277
40635
  } catch {
@@ -44915,5 +45273,5 @@ const startLanguageServer = () => {
44915
45273
  };
44916
45274
  //#endregion
44917
45275
  export { startLanguageServer };
44918
- !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]="64a3133a-c62e-5131-9413-879f051371ef")}catch(e){}}();
44919
- //# debugId=64a3133a-c62e-5131-9413-879f051371ef
45276
+ !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]="9428cea1-cc36-547a-941f-20487b0140fc")}catch(e){}}();
45277
+ //# debugId=9428cea1-cc36-547a-941f-20487b0140fc