@agent-native/core-corpus 0.218.0 → 0.219.0

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 (32) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/hubspot/SKILL.md +5 -3
  3. package/corpus/templates/community/qr/package.json +1 -1
  4. package/corpus/templates/content/CHANGELOG.md +5 -0
  5. package/corpus/templates/content/app/components/editor/CommentStack.tsx +301 -0
  6. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +651 -149
  7. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +19 -14
  8. package/corpus/templates/content/app/components/editor/block-field-comment-threads.ts +15 -0
  9. package/corpus/templates/content/app/components/editor/block-field-comments.tsx +5 -9
  10. package/corpus/templates/content/app/components/editor/comment-stacks.ts +143 -0
  11. package/corpus/templates/content/app/i18n-data.ts +8 -0
  12. package/corpus/templates/content/changelog/2026-10-22-blocks-with-several-comment-threads-fold-into-one-stack-in-t.md +6 -0
  13. package/corpus/templates/content/changelog/2026-10-22-opening-a-page-no-longer-saves-an-edit-again-after-another-o.md +6 -0
  14. package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +8 -1
  15. package/corpus/templates/design/.generated/bridge/interact-links.generated.ts +8 -8
  16. package/corpus/templates/design/CHANGELOG.md +5 -0
  17. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +18 -1
  18. package/corpus/templates/design/app/components/design/bridge/interact-links.bridge.ts +8 -8
  19. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +15 -13
  20. package/corpus/templates/design/app/i18n/en-US.ts +1 -1
  21. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +15 -6
  22. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-screen-rendering.tsx +29 -5
  23. package/corpus/templates/design/app/pages/design-editor/prototype-navigation-transition.ts +423 -0
  24. package/corpus/templates/design/app/pages/design-editor/view/single-screen-canvas.tsx +35 -9
  25. package/corpus/templates/design/changelog/2026-10-20-the-design-full-app-toggle-on-the-home-page-no-longer-hides-.md +6 -0
  26. package/corpus/templates/design/changelog/2026-10-22-prototype-links-can-play-code-authored-screen-transitions.md +6 -0
  27. package/corpus/templates/design/shared/motion-authored-css-import.ts +938 -7
  28. package/corpus/templates/design/shared/motion-html-source.ts +1689 -135
  29. package/corpus/templates/design/shared/motion-prototype-transition.ts +329 -0
  30. package/corpus/templates/design/shared/motion-react-source.ts +2154 -126
  31. package/corpus/templates/design/shared/motion-text-runtime-v1.fixture.txt +221 -0
  32. package/package.json +1 -1
@@ -441,6 +441,14 @@ export function applyMotionReactSourceEdit(
441
441
  export function readMotionReactSource(
442
442
  source: string,
443
443
  modulePath?: string,
444
+ ): MotionReactSourceReadResult {
445
+ return readMotionReactSourceInternal(source, modulePath);
446
+ }
447
+
448
+ function readMotionReactSourceInternal(
449
+ source: string,
450
+ modulePath: string | undefined,
451
+ options: { allowUnsupportedRuntimeCallbacks?: boolean } = {},
444
452
  ): MotionReactSourceReadResult {
445
453
  try {
446
454
  const sourceFile = parseTsx(source);
@@ -502,6 +510,11 @@ export function readMotionReactSource(
502
510
  modulePath,
503
511
  managedStyles,
504
512
  managedTargetNodeIds,
513
+ {
514
+ allowManagedMotionWithUnresolvedStylesheets: true,
515
+ allowUnsupportedRuntimeCallbacks:
516
+ options.allowUnsupportedRuntimeCallbacks,
517
+ },
505
518
  );
506
519
  if (authoredMotion.document) {
507
520
  try {
@@ -599,6 +612,12 @@ export function inspectMotionReactSourceWriteCapability(args: {
599
612
  try {
600
613
  if (read.status === "invalid") throw read.error;
601
614
  const sourceFile = parseTsx(args.source);
615
+ if (targetNodeIds.length > 0 && hasUnresolvedReactStylesheet(sourceFile)) {
616
+ throw new MotionReactSourceError(
617
+ "unsupported-authored-motion",
618
+ "Resolve external stylesheets before editing or adding Motion.",
619
+ );
620
+ }
602
621
  if (targetNodeIds.length === 0) {
603
622
  const canEditMetadata =
604
623
  read.status === "valid" &&
@@ -629,6 +648,11 @@ export function inspectMotionReactSourceWriteCapability(args: {
629
648
  }
630
649
  } else {
631
650
  const targets = findTargets(sourceFile, targetNodeIds, args.modulePath);
651
+ assertNoUnsupportedReactStylePropAnimations(
652
+ sourceFile,
653
+ targetNodeIds,
654
+ args.modulePath,
655
+ );
632
656
  const targetRoots = new Set(
633
657
  targets.map((target) => target.componentRoot),
634
658
  );
@@ -679,7 +703,9 @@ export function removeMotionReactSource(
679
703
  source: string,
680
704
  modulePath?: string,
681
705
  ): MotionReactSourceRemoval {
682
- const read = readMotionReactSource(source, modulePath);
706
+ const read = readMotionReactSourceInternal(source, modulePath, {
707
+ allowUnsupportedRuntimeCallbacks: true,
708
+ });
683
709
  if (read.status === "invalid") throw read.error;
684
710
  if (read.status === "absent") return { source, changed: false };
685
711
 
@@ -689,6 +715,10 @@ export function removeMotionReactSource(
689
715
  modulePath,
690
716
  findManagedStyles(originalSourceFile),
691
717
  read.status === "valid" ? read.targetNodeIds : [],
718
+ {
719
+ allowManagedMotionWithUnresolvedStylesheets: true,
720
+ allowUnsupportedRuntimeCallbacks: true,
721
+ },
692
722
  );
693
723
  const withoutAuthored = removeAuthoredCssSourceRanges(
694
724
  source,
@@ -756,7 +786,22 @@ function readAuthoredReactMotion(
756
786
  modulePath: string | undefined,
757
787
  managedStyles: readonly ManagedStyle[],
758
788
  managedTargetNodeIds: readonly string[] = [],
789
+ options: {
790
+ allowManagedMotionWithUnresolvedStylesheets?: boolean;
791
+ allowUnsupportedRuntimeCallbacks?: boolean;
792
+ } = {},
759
793
  ): AuthoredReactMotion {
794
+ if (
795
+ !options.allowManagedMotionWithUnresolvedStylesheets &&
796
+ managedTargetNodeIds.length > 0 &&
797
+ hasUnresolvedReactStylesheet(sourceFile)
798
+ ) {
799
+ throw new MotionReactSourceError(
800
+ "unsupported-authored-motion",
801
+ "Resolve external stylesheets before editing or adding Motion.",
802
+ );
803
+ }
804
+
760
805
  const managedElements = new Set(managedStyles.map((style) => style.element));
761
806
  const authoredStyles: TypeScript.JsxElement[] = [];
762
807
  const visit = (node: TypeScript.Node): void => {
@@ -773,6 +818,10 @@ function readAuthoredReactMotion(
773
818
  visit(sourceFile);
774
819
 
775
820
  const hosts = findReactAuthoredHosts(sourceFile);
821
+ const transitionCssBlocks = authoredStyles.flatMap((style) => {
822
+ const content = readStaticReactStyleContent(sourceFile, style);
823
+ return content ? [content.css] : [];
824
+ });
776
825
  const tracks: NonNullable<MotionCodeDocument["tracks"]> = [];
777
826
  const rangesToRemove: MotionAuthoredCssSourceRange[] = [];
778
827
  const importedTargetNodeIds = new Set<string>();
@@ -795,27 +844,31 @@ function readAuthoredReactMotion(
795
844
  continue;
796
845
  }
797
846
 
798
- const result = importAuthoredCssAnimations(content.css, (selector) => {
799
- let styleRoot: TypeScript.JsxElement | TypeScript.JsxFragment;
800
- try {
801
- styleRoot = findComponentRoot(sourceFile, style);
802
- } catch (error) {
803
- return {
804
- status: "unsupported",
805
- reason:
806
- error instanceof Error
807
- ? error.message
808
- : "React inline style animations must belong to one static component root.",
809
- };
810
- }
811
- return resolveAuthoredReactTarget(
812
- selector,
813
- hosts,
814
- modulePath,
815
- sourceFile,
816
- styleRoot,
817
- );
818
- });
847
+ const result = importAuthoredCssAnimations(
848
+ content.css,
849
+ (selector) => {
850
+ let styleRoot: TypeScript.JsxElement | TypeScript.JsxFragment;
851
+ try {
852
+ styleRoot = findComponentRoot(sourceFile, style);
853
+ } catch (error) {
854
+ return {
855
+ status: "unsupported",
856
+ reason:
857
+ error instanceof Error
858
+ ? error.message
859
+ : "React inline style animations must belong to one static component root.",
860
+ };
861
+ }
862
+ return resolveAuthoredReactTarget(
863
+ selector,
864
+ hosts,
865
+ modulePath,
866
+ sourceFile,
867
+ styleRoot,
868
+ );
869
+ },
870
+ { transitionCssBlocks },
871
+ );
819
872
  if (result.status === "absent") {
820
873
  for (const name of result.definedKeyframeNames) {
821
874
  keyframeNameCounts.set(name, (keyframeNameCounts.get(name) ?? 0) + 1);
@@ -828,10 +881,10 @@ function readAuthoredReactMotion(
828
881
  result.reason,
829
882
  );
830
883
  }
831
- if (hasImportedStylesheet(sourceFile)) {
884
+ if (hasUnresolvedReactStylesheet(sourceFile)) {
832
885
  throw new MotionReactSourceError(
833
886
  "unsupported-authored-motion",
834
- "Inline CSS animations cannot be imported while CSS module dependencies remain unresolved.",
887
+ "Inline CSS animations cannot be imported while stylesheet dependencies remain unresolved.",
835
888
  );
836
889
  }
837
890
  for (const name of result.definedKeyframeNames) {
@@ -861,11 +914,13 @@ function readAuthoredReactMotion(
861
914
  );
862
915
  }
863
916
 
864
- assertNoUnsupportedReactStylePropAnimations(
865
- sourceFile,
866
- [...managedTargetNodeIds, ...importedTargetNodeIds],
867
- modulePath,
868
- );
917
+ if (!options.allowUnsupportedRuntimeCallbacks) {
918
+ assertNoUnsupportedReactStylePropAnimations(
919
+ sourceFile,
920
+ [...managedTargetNodeIds, ...importedTargetNodeIds],
921
+ modulePath,
922
+ );
923
+ }
869
924
  if (tracks.length === 0) return { document: null, rangesToRemove: [] };
870
925
  for (const name of usedKeyframeNames) {
871
926
  if ((keyframeNameCounts.get(name) ?? 0) > 1) {
@@ -917,13 +972,117 @@ function hasImportedStylesheet(sourceFile: TypeScript.SourceFile): boolean {
917
972
  ts.isIdentifier(node.expression) && node.expression.text === "require";
918
973
  const isDynamicImport =
919
974
  node.expression.kind === ts.SyntaxKind.ImportKeyword;
975
+ const isImportMetaGlob =
976
+ ts.isPropertyAccessExpression(node.expression) &&
977
+ ts.isMetaProperty(node.expression.expression) &&
978
+ node.expression.expression.keywordToken ===
979
+ ts.SyntaxKind.ImportKeyword &&
980
+ node.expression.expression.name.text === "meta" &&
981
+ ["glob", "globEager"].includes(node.expression.name.text);
920
982
  if (
921
983
  (isRequire || isDynamicImport) &&
922
- isCssModuleSpecifier(node.arguments[0]!)
984
+ isPotentialCssModuleSpecifier(node.arguments[0]!)
923
985
  ) {
924
986
  found = true;
925
987
  return;
926
988
  }
989
+ if (isImportMetaGlob && isPotentialCssGlobPattern(node.arguments[0]!)) {
990
+ found = true;
991
+ return;
992
+ }
993
+ }
994
+ if (!found) ts.forEachChild(node, visit);
995
+ };
996
+ visit(sourceFile);
997
+ return found;
998
+ }
999
+
1000
+ function hasUnresolvedReactStylesheet(
1001
+ sourceFile: TypeScript.SourceFile,
1002
+ ): boolean {
1003
+ return (
1004
+ hasImportedStylesheet(sourceFile) || hasLinkedReactStylesheet(sourceFile)
1005
+ );
1006
+ }
1007
+
1008
+ function isPotentialCssGlobPattern(node: TypeScript.Expression): boolean {
1009
+ if (ts.isArrayLiteralExpression(node)) {
1010
+ return node.elements.some(isPotentialCssGlobPattern);
1011
+ }
1012
+ if (!ts.isStringLiteral(node) && !ts.isNoSubstitutionTemplateLiteral(node)) {
1013
+ return true;
1014
+ }
1015
+
1016
+ const pattern = node.text;
1017
+ if (/\.(?:css|scss|sass|less|styl)(?:$|[?#*{}])/i.test(pattern)) {
1018
+ return true;
1019
+ }
1020
+ const extensionList = pattern.match(/\.\{([^}]+)\}(?:[?#].*)?$/);
1021
+ if (extensionList) {
1022
+ return extensionList[1]!
1023
+ .split(",")
1024
+ .some((extension) => /^(?:css|scss|sass|less|styl)$/i.test(extension));
1025
+ }
1026
+ return !/\.[a-z0-9]+(?:[?#].*)?$/i.test(pattern);
1027
+ }
1028
+
1029
+ function hasLinkedReactStylesheet(sourceFile: TypeScript.SourceFile): boolean {
1030
+ let found = false;
1031
+ const visit = (node: TypeScript.Node): void => {
1032
+ if (found) return;
1033
+ const opening = ts.isJsxElement(node)
1034
+ ? node.openingElement
1035
+ : ts.isJsxSelfClosingElement(node)
1036
+ ? node
1037
+ : null;
1038
+ if (
1039
+ opening &&
1040
+ ts.isIdentifier(opening.tagName) &&
1041
+ opening.tagName.text === "link"
1042
+ ) {
1043
+ const properties = opening.attributes.properties;
1044
+ const attributes = new Map<string, TypeScript.JsxAttribute>();
1045
+ let hasSpread = false;
1046
+ for (const property of properties) {
1047
+ if (ts.isJsxSpreadAttribute(property)) {
1048
+ hasSpread = true;
1049
+ } else {
1050
+ attributes.set(
1051
+ property.name.getText(sourceFile).toLowerCase(),
1052
+ property,
1053
+ );
1054
+ }
1055
+ }
1056
+ const relAttribute = attributes.get("rel");
1057
+ const hrefAttribute = attributes.get("href");
1058
+ const rel = relAttribute
1059
+ ? readStaticJsxAttributeValue(relAttribute)
1060
+ : null;
1061
+ const relationTokens =
1062
+ rel?.status === "static"
1063
+ ? rel.value.split(/[\t\n\f\r ]+/).map((value) => value.toLowerCase())
1064
+ : [];
1065
+ const href = hrefAttribute
1066
+ ? readStaticJsxAttributeValue(hrefAttribute)
1067
+ : null;
1068
+ const asAttribute = attributes.get("as");
1069
+ const as = asAttribute ? readStaticJsxAttributeValue(asAttribute) : null;
1070
+ const hasHref =
1071
+ href?.status === "dynamic" ||
1072
+ (href?.status === "static" && href.value.trim().length > 0);
1073
+ const relMayBeStylesheet =
1074
+ rel?.status === "dynamic" && hrefAttribute !== undefined;
1075
+ const preloadMayBecomeStylesheet =
1076
+ relationTokens.includes("preload") &&
1077
+ (as?.status === "dynamic" ||
1078
+ (as?.status === "static" && as.value.toLowerCase() === "style")) &&
1079
+ attributes.has("onload");
1080
+ found =
1081
+ hasSpread ||
1082
+ (hasHref &&
1083
+ (relationTokens.includes("stylesheet") ||
1084
+ relMayBeStylesheet ||
1085
+ preloadMayBecomeStylesheet));
927
1086
  }
928
1087
  if (!found) ts.forEachChild(node, visit);
929
1088
  };
@@ -931,6 +1090,13 @@ function hasImportedStylesheet(sourceFile: TypeScript.SourceFile): boolean {
931
1090
  return found;
932
1091
  }
933
1092
 
1093
+ function isPotentialCssModuleSpecifier(node: TypeScript.Expression): boolean {
1094
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
1095
+ return isCssModuleSpecifier(node);
1096
+ }
1097
+ return true;
1098
+ }
1099
+
934
1100
  function isCssModuleSpecifier(node: TypeScript.Expression): boolean {
935
1101
  if (!ts.isStringLiteral(node) && !ts.isNoSubstitutionTemplateLiteral(node)) {
936
1102
  return false;
@@ -943,34 +1109,199 @@ function assertNoUnsupportedReactStylePropAnimations(
943
1109
  targetNodeIds: readonly string[],
944
1110
  modulePath: string | undefined,
945
1111
  ): void {
946
- const targetElements = new Set(
1112
+ const targets =
947
1113
  targetNodeIds.length > 0
948
- ? findStaticTargets(
949
- sourceFile,
950
- [...new Set(targetNodeIds)],
951
- modulePath,
952
- ).map((target) => target.element)
953
- : [],
1114
+ ? findStaticTargets(sourceFile, [...new Set(targetNodeIds)], modulePath)
1115
+ : [];
1116
+ const targetElements = new Set(targets.map((target) => target.element));
1117
+ const capturedMotionRefNames = new Set<string>();
1118
+ for (const target of targets) {
1119
+ const attributes = ts.isJsxElement(target.element)
1120
+ ? target.element.openingElement.attributes
1121
+ : target.element.attributes;
1122
+ for (const property of attributes.properties) {
1123
+ if (
1124
+ !ts.isJsxAttribute(property) ||
1125
+ property.name.getText(sourceFile) !== "ref" ||
1126
+ !property.initializer ||
1127
+ !ts.isJsxExpression(property.initializer) ||
1128
+ !property.initializer.expression
1129
+ ) {
1130
+ continue;
1131
+ }
1132
+ for (const name of collectReactCapturedRefNames(
1133
+ property.initializer.expression,
1134
+ sourceFile,
1135
+ new Set(),
1136
+ )) {
1137
+ capturedMotionRefNames.add(name);
1138
+ }
1139
+ }
1140
+ }
1141
+ const targetRoots = new Set(
1142
+ targets.map((target) => findComponentRoot(sourceFile, target.element)),
954
1143
  );
1144
+ const isWithinTargetRoot = (node: TypeScript.Node): boolean => {
1145
+ for (
1146
+ let current: TypeScript.Node | undefined = node;
1147
+ current;
1148
+ current = current.parent
1149
+ ) {
1150
+ if (
1151
+ targetRoots.has(
1152
+ current as TypeScript.JsxElement | TypeScript.JsxFragment,
1153
+ )
1154
+ ) {
1155
+ return true;
1156
+ }
1157
+ }
1158
+ return false;
1159
+ };
1160
+ const componentFunctions = new Set<
1161
+ | TypeScript.FunctionDeclaration
1162
+ | TypeScript.FunctionExpression
1163
+ | TypeScript.ArrowFunction
1164
+ | TypeScript.MethodDeclaration
1165
+ >();
1166
+ for (const root of targetRoots) {
1167
+ for (
1168
+ let current: TypeScript.Node | undefined = root.parent;
1169
+ current;
1170
+ current = current.parent
1171
+ ) {
1172
+ if (isFunctionBoundary(current)) {
1173
+ if (
1174
+ ts.isFunctionDeclaration(current) ||
1175
+ ts.isFunctionExpression(current) ||
1176
+ ts.isArrowFunction(current) ||
1177
+ ts.isMethodDeclaration(current)
1178
+ ) {
1179
+ componentFunctions.add(current);
1180
+ }
1181
+ break;
1182
+ }
1183
+ }
1184
+ }
1185
+ for (const component of componentFunctions) {
1186
+ if (!component.body) continue;
1187
+ const inspectLifecycleEffects = (node: TypeScript.Node): void => {
1188
+ if (node !== component.body && isFunctionBoundary(node)) return;
1189
+ if (
1190
+ ts.isCallExpression(node) &&
1191
+ isReactLifecycleEffectCall(node, sourceFile)
1192
+ ) {
1193
+ const callback = node.arguments[0];
1194
+ const inspection = callback
1195
+ ? inspectReactStyleMutationCallback(
1196
+ callback,
1197
+ sourceFile,
1198
+ new Set(),
1199
+ false,
1200
+ capturedMotionRefNames,
1201
+ true,
1202
+ )
1203
+ : "unknown";
1204
+ if (inspection !== "safe") {
1205
+ throw new MotionReactSourceError(
1206
+ "unsupported-authored-motion",
1207
+ "React effect callbacks in a Motion component can access or mutate target styles and cannot be proven safe.",
1208
+ );
1209
+ }
1210
+ return;
1211
+ }
1212
+ ts.forEachChild(node, inspectLifecycleEffects);
1213
+ };
1214
+ inspectLifecycleEffects(component.body);
1215
+ }
1216
+ for (const root of targetRoots) {
1217
+ const visitRoot = (node: TypeScript.Node): void => {
1218
+ if (
1219
+ ts.isJsxAttribute(node) &&
1220
+ node.name.getText(sourceFile) === "dangerouslySetInnerHTML"
1221
+ ) {
1222
+ throw new MotionReactSourceError(
1223
+ "unsupported-authored-motion",
1224
+ "React dangerouslySetInnerHTML in a Motion component can hide CSS from source inspection.",
1225
+ );
1226
+ }
1227
+ ts.forEachChild(node, visitRoot);
1228
+ };
1229
+ visitRoot(root);
1230
+ }
955
1231
  const visit = (node: TypeScript.Node): void => {
956
- if (ts.isJsxAttribute(node) && node.name.getText(sourceFile) === "style") {
1232
+ if (ts.isJsxSpreadAttribute(node)) {
1233
+ const opening = node.parent.parent;
1234
+ const host = ts.isJsxOpeningElement(opening) ? opening.parent : opening;
1235
+ const isRelevantTargetProp =
1236
+ targetElements.has(host) || isWithinTargetRoot(host);
1237
+ if (isRelevantTargetProp) {
1238
+ const inspection = inspectReactJsxSpreadExpression(
1239
+ node.expression,
1240
+ sourceFile,
1241
+ new Set(),
1242
+ capturedMotionRefNames,
1243
+ );
1244
+ if (inspection !== "safe") {
1245
+ throw new MotionReactSourceError(
1246
+ "unsupported-authored-motion",
1247
+ "React JSX spread props in a Motion component can supply style declarations or callbacks that mutate target styles and cannot be proven safe.",
1248
+ );
1249
+ }
1250
+ }
1251
+ ts.forEachChild(node, visit);
1252
+ return;
1253
+ }
1254
+
1255
+ if (ts.isJsxAttribute(node)) {
1256
+ const attributeName = node.name.getText(sourceFile);
957
1257
  const initializer = node.initializer;
958
- if (!initializer || !ts.isJsxExpression(initializer)) return;
959
- if (!initializer.expression) return;
960
- const inspection = inspectReactStyleExpression(
961
- initializer.expression,
962
- sourceFile,
963
- new Set(),
964
- );
965
1258
  const attributes = node.parent;
966
1259
  const opening = attributes.parent;
967
1260
  const host = ts.isJsxOpeningElement(opening) ? opening.parent : opening;
968
- if (targetElements.has(host) && inspection !== "safe") {
1261
+ const isRelevantTargetProp =
1262
+ targetElements.has(host) ||
1263
+ (attributeName !== "style" && isWithinTargetRoot(host));
1264
+ const expression =
1265
+ initializer && ts.isJsxExpression(initializer) && initializer.expression
1266
+ ? initializer.expression
1267
+ : null;
1268
+
1269
+ if (!["style", "css", "sx"].includes(attributeName)) {
1270
+ if (
1271
+ isRelevantTargetProp &&
1272
+ (attributeName === "ref" || /^on[A-Za-z]/.test(attributeName))
1273
+ ) {
1274
+ const inspection = expression
1275
+ ? inspectReactStyleMutationCallback(
1276
+ expression,
1277
+ sourceFile,
1278
+ new Set(),
1279
+ attributeName === "ref",
1280
+ capturedMotionRefNames,
1281
+ )
1282
+ : "unknown";
1283
+ if (inspection !== "safe") {
1284
+ throw new MotionReactSourceError(
1285
+ "unsupported-authored-motion",
1286
+ `React ${attributeName} callbacks on a Motion component can access or mutate target styles and cannot be proven safe.`,
1287
+ );
1288
+ }
1289
+ }
1290
+ ts.forEachChild(node, visit);
1291
+ return;
1292
+ }
1293
+ if (!isRelevantTargetProp) return;
1294
+ const inspection = expression
1295
+ ? attributeName === "style"
1296
+ ? inspectReactStyleExpression(expression, sourceFile, new Set())
1297
+ : inspectReactCssExpression(expression, sourceFile, new Set(), true)
1298
+ : "unknown";
1299
+ if (inspection !== "safe") {
969
1300
  throw new MotionReactSourceError(
970
1301
  "unsupported-authored-motion",
971
1302
  inspection === "motion"
972
- ? "React inline style-prop animations and transitions cannot be imported losslessly; use a supported static JSX <style> animation rule."
973
- : "A React dynamic style prop on a Motion target cannot be proven free of animation or transition declarations.",
1303
+ ? `React ${attributeName} prop animations and transitions cannot be imported losslessly; use a supported static JSX <style> animation rule.`
1304
+ : `A React dynamic ${attributeName} prop on a Motion target cannot be proven free of animation or transition declarations.`,
974
1305
  );
975
1306
  }
976
1307
  }
@@ -980,12 +1311,60 @@ function assertNoUnsupportedReactStylePropAnimations(
980
1311
  }
981
1312
 
982
1313
  type ReactStyleInspection = "safe" | "motion" | "unknown";
1314
+ const REACT_EVENT_TARGET_PROPERTIES = new Set([
1315
+ "currentTarget",
1316
+ "target",
1317
+ "srcElement",
1318
+ ]);
1319
+ const REACT_EVENT_TARGET_SCALAR_PROPERTIES = new Set([
1320
+ "alt",
1321
+ "checked",
1322
+ "disabled",
1323
+ "height",
1324
+ "htmlFor",
1325
+ "id",
1326
+ "innerText",
1327
+ "max",
1328
+ "maxLength",
1329
+ "min",
1330
+ "minLength",
1331
+ "multiple",
1332
+ "name",
1333
+ "placeholder",
1334
+ "readOnly",
1335
+ "required",
1336
+ "role",
1337
+ "scrollLeft",
1338
+ "scrollTop",
1339
+ "selected",
1340
+ "selectedIndex",
1341
+ "selectionEnd",
1342
+ "selectionStart",
1343
+ "tabIndex",
1344
+ "textContent",
1345
+ "title",
1346
+ "type",
1347
+ "value",
1348
+ "width",
1349
+ ]);
1350
+ const REACT_EVENT_TARGET_SCALAR_METHODS = new Set(["trim"]);
1351
+ const REACT_REF_READ_METHODS = new Set([
1352
+ "closest",
1353
+ "contains",
1354
+ "getAttribute",
1355
+ "getBoundingClientRect",
1356
+ "getClientRects",
1357
+ "hasAttribute",
1358
+ "matches",
1359
+ "querySelector",
1360
+ "querySelectorAll",
1361
+ ]);
983
1362
 
984
- function inspectReactStyleExpression(
1363
+ function collectReactCapturedRefNames(
985
1364
  expression: TypeScript.Expression,
986
1365
  sourceFile: TypeScript.SourceFile,
987
1366
  resolving: Set<string>,
988
- ): ReactStyleInspection {
1367
+ ): Set<string> {
989
1368
  while (
990
1369
  ts.isParenthesizedExpression(expression) ||
991
1370
  ts.isAsExpression(expression) ||
@@ -995,95 +1374,1654 @@ function inspectReactStyleExpression(
995
1374
  expression = expression.expression;
996
1375
  }
997
1376
 
998
- if (expression.kind === ts.SyntaxKind.NullKeyword) return "safe";
999
- if (ts.isObjectLiteralExpression(expression)) {
1000
- for (const property of expression.properties) {
1001
- if (ts.isSpreadAssignment(property)) return "unknown";
1002
- const name = ts.isPropertyAssignment(property)
1003
- ? property.name
1004
- : ts.isShorthandPropertyAssignment(property)
1005
- ? property.name
1006
- : null;
1007
- const text = name
1008
- ? ts.isIdentifier(name) || ts.isStringLiteral(name)
1009
- ? name.text
1010
- : null
1011
- : null;
1012
- if (text && isReactMotionStylePropName(text)) return "motion";
1013
- if (!text) return "unknown";
1377
+ if (!ts.isArrowFunction(expression) && !ts.isFunctionExpression(expression)) {
1378
+ if (!ts.isIdentifier(expression)) return new Set();
1379
+
1380
+ const resolved = resolveReactStyleExpression(expression, sourceFile);
1381
+ if (resolved) {
1382
+ const declarationKey = `${resolved.start}:${resolved.end}`;
1383
+ if (resolving.has(declarationKey)) return new Set();
1384
+ resolving.add(declarationKey);
1385
+ const names = collectReactCapturedRefNames(
1386
+ resolved.expression,
1387
+ sourceFile,
1388
+ resolving,
1389
+ );
1390
+ resolving.delete(declarationKey);
1391
+ return names;
1014
1392
  }
1015
- return "safe";
1393
+
1394
+ const functions: TypeScript.FunctionDeclaration[] = [];
1395
+ const findFunction = (node: TypeScript.Node): void => {
1396
+ if (
1397
+ ts.isFunctionDeclaration(node) &&
1398
+ node.name?.text === expression.text
1399
+ ) {
1400
+ functions.push(node);
1401
+ }
1402
+ ts.forEachChild(node, findFunction);
1403
+ };
1404
+ findFunction(sourceFile);
1405
+ const callback = functions.length === 1 ? functions[0] : null;
1406
+ if (!callback?.body) return new Set();
1407
+ return collectReactCapturedRefNamesFromCallback(
1408
+ callback.parameters,
1409
+ callback.body,
1410
+ );
1016
1411
  }
1017
1412
 
1018
- const resolved = resolveReactStyleExpression(expression, sourceFile);
1019
- if (!resolved) return "unknown";
1020
- const declarationKey = `${resolved.start}:${resolved.end}`;
1021
- if (resolving.has(declarationKey)) return "unknown";
1022
- resolving.add(declarationKey);
1023
- const result = inspectReactStyleExpression(
1024
- resolved.expression,
1025
- sourceFile,
1026
- resolving,
1413
+ return collectReactCapturedRefNamesFromCallback(
1414
+ expression.parameters,
1415
+ expression.body,
1027
1416
  );
1028
- resolving.delete(declarationKey);
1029
- return result;
1030
1417
  }
1031
1418
 
1032
- function resolveReactStyleExpression(
1033
- expression: TypeScript.Expression,
1034
- sourceFile: TypeScript.SourceFile,
1035
- ): { expression: TypeScript.Expression; start: number; end: number } | null {
1036
- const name = ts.isIdentifier(expression)
1037
- ? expression.text
1038
- : ts.isPropertyAccessExpression(expression) &&
1039
- ts.isIdentifier(expression.expression)
1040
- ? expression.expression.text
1041
- : null;
1042
- if (!name) return null;
1043
-
1044
- if (hasReactStyleParameterShadow(expression, sourceFile, name)) return null;
1419
+ function collectReactCapturedRefNamesFromCallback(
1420
+ parameters: TypeScript.NodeArray<TypeScript.ParameterDeclaration>,
1421
+ body: TypeScript.ConciseBody,
1422
+ ): Set<string> {
1423
+ const capturedNames = new Set<string>();
1424
+ const parameterNames = getReactCallbackParameterNames(parameters);
1425
+ if (parameterNames.size === 0) return capturedNames;
1045
1426
 
1046
- const matches: TypeScript.VariableDeclaration[] = [];
1047
1427
  const visit = (node: TypeScript.Node): void => {
1048
1428
  if (
1049
- ts.isVariableDeclaration(node) &&
1050
- ts.isIdentifier(node.name) &&
1051
- node.name.text === name
1429
+ ts.isBinaryExpression(node) &&
1430
+ isReactAssignmentOperator(node.operatorToken.getText()) &&
1431
+ ts.isIdentifier(node.left) &&
1432
+ !parameterNames.has(node.left.text) &&
1433
+ !isReactNameBoundWithinCallback(node, node.left.text, parameters, body) &&
1434
+ isReactCallbackParameterDerivedExpression(
1435
+ node.right,
1436
+ parameterNames,
1437
+ body,
1438
+ new Set(),
1439
+ )
1052
1440
  ) {
1053
- matches.push(node);
1441
+ capturedNames.add(node.left.text);
1054
1442
  }
1055
1443
  ts.forEachChild(node, visit);
1056
1444
  };
1057
- visit(sourceFile);
1058
- if (matches.length !== 1) return null;
1059
- const declaration = matches[0]!;
1060
- const scope = reactVariableDeclarationScope(declaration);
1061
- if (!scope || !isReactNodeWithinScope(scope, expression)) return null;
1062
- const initializer = declaration.initializer;
1063
- if (
1064
- ts.isPropertyAccessExpression(expression) &&
1065
- initializer &&
1066
- ts.isObjectLiteralExpression(initializer)
1067
- ) {
1068
- const propertyName = expression.name.text;
1069
- const properties = initializer.properties.filter(
1070
- (property): property is TypeScript.PropertyAssignment =>
1071
- ts.isPropertyAssignment(property) &&
1072
- ((ts.isIdentifier(property.name) &&
1073
- property.name.text === propertyName) ||
1074
- (ts.isStringLiteral(property.name) &&
1075
- property.name.text === propertyName)),
1445
+ visit(body);
1446
+ return capturedNames;
1447
+ }
1448
+
1449
+ function isReactNameBoundWithinCallback(
1450
+ assignment: TypeScript.BinaryExpression,
1451
+ name: string,
1452
+ callbackParameters: TypeScript.NodeArray<TypeScript.ParameterDeclaration>,
1453
+ callbackBody: TypeScript.ConciseBody,
1454
+ ): boolean {
1455
+ const bindingContainsName = (
1456
+ bindingName: TypeScript.BindingName,
1457
+ ): boolean => {
1458
+ if (ts.isIdentifier(bindingName)) return bindingName.text === name;
1459
+ return bindingName.elements.some(
1460
+ (element) =>
1461
+ !ts.isOmittedExpression(element) && bindingContainsName(element.name),
1076
1462
  );
1077
- if (properties.length === 1) {
1078
- return {
1079
- expression: properties[0]!.initializer,
1080
- start: properties[0]!.getStart(sourceFile),
1081
- end: properties[0]!.end,
1082
- };
1083
- }
1084
- }
1085
- return initializer
1086
- ? {
1463
+ };
1464
+ const hasFunctionScopedBinding = (
1465
+ functionNode: TypeScript.FunctionLikeDeclaration,
1466
+ ): boolean => {
1467
+ if (
1468
+ functionNode.parameters.some((parameter) =>
1469
+ bindingContainsName(parameter.name),
1470
+ )
1471
+ ) {
1472
+ return true;
1473
+ }
1474
+ if (
1475
+ (ts.isFunctionExpression(functionNode) ||
1476
+ ts.isFunctionDeclaration(functionNode)) &&
1477
+ functionNode.name?.text === name
1478
+ ) {
1479
+ return true;
1480
+ }
1481
+
1482
+ let found = false;
1483
+ const visit = (node: TypeScript.Node): void => {
1484
+ if (found || (node !== functionNode.body && isFunctionBoundary(node))) {
1485
+ return;
1486
+ }
1487
+ if (
1488
+ ts.isVariableDeclaration(node) &&
1489
+ ts.isIdentifier(node.name) &&
1490
+ node.name.text === name &&
1491
+ node.parent &&
1492
+ ts.isVariableDeclarationList(node.parent) &&
1493
+ (node.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) === 0
1494
+ ) {
1495
+ found = true;
1496
+ return;
1497
+ }
1498
+ ts.forEachChild(node, visit);
1499
+ };
1500
+ if (functionNode.body) visit(functionNode.body);
1501
+ return found;
1502
+ };
1503
+ const hasBlockScopedBinding = (block: TypeScript.Block): boolean => {
1504
+ let found = false;
1505
+ const visit = (node: TypeScript.Node): void => {
1506
+ if (
1507
+ found ||
1508
+ (node !== block && (ts.isBlock(node) || isFunctionBoundary(node)))
1509
+ ) {
1510
+ return;
1511
+ }
1512
+ if (
1513
+ ts.isVariableDeclaration(node) &&
1514
+ ts.isIdentifier(node.name) &&
1515
+ node.name.text === name &&
1516
+ node.parent &&
1517
+ ts.isVariableDeclarationList(node.parent) &&
1518
+ (node.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0
1519
+ ) {
1520
+ found = true;
1521
+ return;
1522
+ }
1523
+ if (
1524
+ (ts.isFunctionDeclaration(node) ||
1525
+ ts.isClassDeclaration(node) ||
1526
+ ts.isEnumDeclaration(node)) &&
1527
+ node.name?.text === name
1528
+ ) {
1529
+ found = true;
1530
+ return;
1531
+ }
1532
+ ts.forEachChild(node, visit);
1533
+ };
1534
+ visit(block);
1535
+ return found;
1536
+ };
1537
+
1538
+ if (
1539
+ callbackParameters.some((parameter) => bindingContainsName(parameter.name))
1540
+ ) {
1541
+ return true;
1542
+ }
1543
+ for (
1544
+ let current: TypeScript.Node | undefined = assignment.parent;
1545
+ current;
1546
+ current = current.parent
1547
+ ) {
1548
+ if (ts.isBlock(current) && hasBlockScopedBinding(current)) return true;
1549
+ if (
1550
+ isFunctionBoundary(current) &&
1551
+ hasFunctionScopedBinding(current as TypeScript.FunctionLikeDeclaration)
1552
+ ) {
1553
+ return true;
1554
+ }
1555
+ if (current === callbackBody) {
1556
+ const callbackFunction = current.parent;
1557
+ if (
1558
+ callbackFunction &&
1559
+ isFunctionBoundary(callbackFunction) &&
1560
+ hasFunctionScopedBinding(
1561
+ callbackFunction as TypeScript.FunctionLikeDeclaration,
1562
+ )
1563
+ ) {
1564
+ return true;
1565
+ }
1566
+ break;
1567
+ }
1568
+ }
1569
+ return false;
1570
+ }
1571
+
1572
+ function inspectReactStyleMutationCallback(
1573
+ expression: TypeScript.Expression,
1574
+ sourceFile: TypeScript.SourceFile,
1575
+ resolving: Set<string>,
1576
+ isRefCallback: boolean,
1577
+ capturedMotionRefNames: ReadonlySet<string> = new Set(),
1578
+ inspectLifecycleCalls = false,
1579
+ ): ReactStyleInspection {
1580
+ while (
1581
+ ts.isParenthesizedExpression(expression) ||
1582
+ ts.isAsExpression(expression) ||
1583
+ ts.isTypeAssertionExpression(expression) ||
1584
+ ts.isSatisfiesExpression(expression)
1585
+ ) {
1586
+ expression = expression.expression;
1587
+ }
1588
+
1589
+ if (!ts.isArrowFunction(expression) && !ts.isFunctionExpression(expression)) {
1590
+ if (ts.isIdentifier(expression)) {
1591
+ const resolved = resolveReactStyleExpression(expression, sourceFile);
1592
+ if (resolved) {
1593
+ const declarationKey = `${resolved.start}:${resolved.end}`;
1594
+ if (resolving.has(declarationKey)) return "unknown";
1595
+ resolving.add(declarationKey);
1596
+ const result = inspectReactStyleMutationCallback(
1597
+ resolved.expression,
1598
+ sourceFile,
1599
+ resolving,
1600
+ isRefCallback,
1601
+ capturedMotionRefNames,
1602
+ inspectLifecycleCalls,
1603
+ );
1604
+ resolving.delete(declarationKey);
1605
+ return result;
1606
+ }
1607
+
1608
+ const handlerName = expression.text;
1609
+ const functions: TypeScript.FunctionDeclaration[] = [];
1610
+ const findFunction = (node: TypeScript.Node): void => {
1611
+ if (ts.isFunctionDeclaration(node) && node.name?.text === handlerName) {
1612
+ functions.push(node);
1613
+ }
1614
+ ts.forEachChild(node, findFunction);
1615
+ };
1616
+ findFunction(sourceFile);
1617
+ if (functions.length === 1 && functions[0]!.body) {
1618
+ if (
1619
+ isRefCallback &&
1620
+ functions[0]!.parameters.some(
1621
+ (parameter) => !ts.isIdentifier(parameter.name),
1622
+ )
1623
+ ) {
1624
+ return "unknown";
1625
+ }
1626
+ const callback = functions[0]!;
1627
+ const declarationKey = `${callback.getStart(sourceFile)}:${callback.end}`;
1628
+ if (resolving.has(declarationKey)) return "unknown";
1629
+ resolving.add(declarationKey);
1630
+ const callbackParameterNames = getReactCallbackParameterNames(
1631
+ callback.parameters,
1632
+ );
1633
+ const eventTargetBindings = isRefCallback
1634
+ ? { targetNames: new Set<string>(), scalarNames: new Set<string>() }
1635
+ : getReactCallbackEventTargetBindings(
1636
+ callback.parameters,
1637
+ callback.body!,
1638
+ callbackParameterNames,
1639
+ );
1640
+ if (!isRefCallback) {
1641
+ for (const name of capturedMotionRefNames) {
1642
+ eventTargetBindings.targetNames.add(name);
1643
+ }
1644
+ }
1645
+ const result = inspectReactStyleMutationBody(
1646
+ callback.body!,
1647
+ callbackParameterNames,
1648
+ isRefCallback,
1649
+ sourceFile,
1650
+ eventTargetBindings.targetNames,
1651
+ eventTargetBindings.scalarNames,
1652
+ resolving,
1653
+ inspectLifecycleCalls,
1654
+ );
1655
+ resolving.delete(declarationKey);
1656
+ return result;
1657
+ }
1658
+ }
1659
+ return "unknown";
1660
+ }
1661
+
1662
+ if (
1663
+ isRefCallback &&
1664
+ expression.parameters.some((parameter) => !ts.isIdentifier(parameter.name))
1665
+ ) {
1666
+ return "unknown";
1667
+ }
1668
+
1669
+ const callbackParameterNames = getReactCallbackParameterNames(
1670
+ expression.parameters,
1671
+ );
1672
+ const eventTargetBindings = isRefCallback
1673
+ ? { targetNames: new Set<string>(), scalarNames: new Set<string>() }
1674
+ : getReactCallbackEventTargetBindings(
1675
+ expression.parameters,
1676
+ expression.body,
1677
+ callbackParameterNames,
1678
+ );
1679
+ if (!isRefCallback) {
1680
+ for (const name of capturedMotionRefNames) {
1681
+ eventTargetBindings.targetNames.add(name);
1682
+ }
1683
+ }
1684
+ return inspectReactStyleMutationBody(
1685
+ expression.body,
1686
+ callbackParameterNames,
1687
+ isRefCallback,
1688
+ sourceFile,
1689
+ eventTargetBindings.targetNames,
1690
+ eventTargetBindings.scalarNames,
1691
+ resolving,
1692
+ inspectLifecycleCalls,
1693
+ );
1694
+ }
1695
+
1696
+ function inspectReactStyleMutationBody(
1697
+ body: TypeScript.ConciseBody,
1698
+ callbackParameterNames: Set<string>,
1699
+ isRefCallback: boolean,
1700
+ sourceFile: TypeScript.SourceFile,
1701
+ eventTargetNames: Set<string>,
1702
+ eventTargetScalarNames: Set<string>,
1703
+ resolving: Set<string>,
1704
+ inspectLifecycleCalls: boolean,
1705
+ ): ReactStyleInspection {
1706
+ let inspection: ReactStyleInspection = "safe";
1707
+ const visit = (node: TypeScript.Node): void => {
1708
+ if (inspection !== "safe") return;
1709
+
1710
+ if (isReactGlobalDocumentReference(node, sourceFile)) {
1711
+ inspection = "unknown";
1712
+ return;
1713
+ }
1714
+
1715
+ if (inspectLifecycleCalls && ts.isCallExpression(node)) {
1716
+ const callInspection = inspectReactLifecycleHelperCall(
1717
+ node,
1718
+ sourceFile,
1719
+ resolving,
1720
+ eventTargetNames,
1721
+ );
1722
+ if (callInspection !== "safe") {
1723
+ inspection = callInspection;
1724
+ return;
1725
+ }
1726
+ }
1727
+
1728
+ if (
1729
+ isRefCallback &&
1730
+ ts.isBinaryExpression(node) &&
1731
+ isReactAssignmentOperator(node.operatorToken.getText()) &&
1732
+ containsReactCallbackParameterReference(
1733
+ node.right,
1734
+ callbackParameterNames,
1735
+ ) &&
1736
+ (!ts.isIdentifier(node.left) ||
1737
+ !isReactCallbackParameterDerivedExpression(
1738
+ node.right,
1739
+ callbackParameterNames,
1740
+ body,
1741
+ new Set(),
1742
+ ))
1743
+ ) {
1744
+ inspection = "unknown";
1745
+ return;
1746
+ }
1747
+
1748
+ if (
1749
+ isRefCallback &&
1750
+ ts.isVariableDeclaration(node) &&
1751
+ !ts.isIdentifier(node.name) &&
1752
+ node.initializer &&
1753
+ isReactCallbackParameterDerivedExpression(
1754
+ node.initializer,
1755
+ callbackParameterNames,
1756
+ body,
1757
+ new Set(),
1758
+ )
1759
+ ) {
1760
+ inspection = "unknown";
1761
+ return;
1762
+ }
1763
+
1764
+ if (
1765
+ isRefCallback &&
1766
+ ((ts.isCallExpression(node) &&
1767
+ isReactRefTargetMethodCall(node, callbackParameterNames, body)) ||
1768
+ isReactRefTargetMemberAccess(node, callbackParameterNames, body) ||
1769
+ (ts.isBinaryExpression(node) &&
1770
+ isReactRefTargetMemberAssignment(node, callbackParameterNames, body)))
1771
+ ) {
1772
+ inspection = "unknown";
1773
+ return;
1774
+ }
1775
+
1776
+ if (
1777
+ !isRefCallback &&
1778
+ isReactEventTargetMutation(
1779
+ node,
1780
+ callbackParameterNames,
1781
+ body,
1782
+ eventTargetNames,
1783
+ )
1784
+ ) {
1785
+ inspection = "unknown";
1786
+ return;
1787
+ }
1788
+
1789
+ if (
1790
+ !isRefCallback &&
1791
+ ts.isIdentifier(node) &&
1792
+ eventTargetNames.has(node.text) &&
1793
+ !isReactStaticPropertyName(node) &&
1794
+ !isReactBindingNameIdentifier(node)
1795
+ ) {
1796
+ const access = getReactMemberAccessFollowing(node);
1797
+ if (
1798
+ !access ||
1799
+ !isReactSafeEventTargetScalarValue(
1800
+ access,
1801
+ callbackParameterNames,
1802
+ body,
1803
+ eventTargetNames,
1804
+ eventTargetScalarNames,
1805
+ )
1806
+ ) {
1807
+ inspection = "unknown";
1808
+ return;
1809
+ }
1810
+ }
1811
+
1812
+ if (
1813
+ ts.isPropertyAccessExpression(node) &&
1814
+ ["style", ...REACT_EVENT_TARGET_PROPERTIES, "current"].includes(
1815
+ node.name.text,
1816
+ )
1817
+ ) {
1818
+ inspection =
1819
+ node.name.text === "style"
1820
+ ? "motion"
1821
+ : !isRefCallback &&
1822
+ isReactSafeEventTargetScalarRead(
1823
+ node,
1824
+ callbackParameterNames,
1825
+ body,
1826
+ eventTargetNames,
1827
+ )
1828
+ ? "safe"
1829
+ : "unknown";
1830
+ return;
1831
+ }
1832
+ if (
1833
+ ts.isElementAccessExpression(node) &&
1834
+ node.argumentExpression &&
1835
+ ts.isStringLiteralLike(node.argumentExpression) &&
1836
+ ["style", ...REACT_EVENT_TARGET_PROPERTIES, "current"].includes(
1837
+ node.argumentExpression.text,
1838
+ )
1839
+ ) {
1840
+ inspection =
1841
+ node.argumentExpression.text === "style"
1842
+ ? "motion"
1843
+ : !isRefCallback &&
1844
+ isReactSafeEventTargetScalarRead(
1845
+ node,
1846
+ callbackParameterNames,
1847
+ body,
1848
+ eventTargetNames,
1849
+ )
1850
+ ? "safe"
1851
+ : "unknown";
1852
+ return;
1853
+ }
1854
+
1855
+ if (
1856
+ ts.isCallExpression(node) &&
1857
+ node.arguments.some(
1858
+ (argument) =>
1859
+ containsReactCallbackParameterReference(
1860
+ argument,
1861
+ callbackParameterNames,
1862
+ ) &&
1863
+ !isReactSafeEventTargetScalarValue(
1864
+ argument,
1865
+ callbackParameterNames,
1866
+ body,
1867
+ eventTargetNames,
1868
+ eventTargetScalarNames,
1869
+ ),
1870
+ )
1871
+ ) {
1872
+ inspection = "unknown";
1873
+ return;
1874
+ }
1875
+
1876
+ ts.forEachChild(node, visit);
1877
+ };
1878
+ visit(body);
1879
+ return inspection;
1880
+ }
1881
+
1882
+ function inspectReactLifecycleHelperCall(
1883
+ call: TypeScript.CallExpression,
1884
+ sourceFile: TypeScript.SourceFile,
1885
+ resolving: Set<string>,
1886
+ capturedMotionRefNames: ReadonlySet<string>,
1887
+ ): ReactStyleInspection {
1888
+ if (call.arguments.length > 0) return "unknown";
1889
+ const callee = unwrapReactMotionExpression(call.expression);
1890
+ if (!ts.isIdentifier(callee)) return "unknown";
1891
+ return inspectReactStyleMutationCallback(
1892
+ callee,
1893
+ sourceFile,
1894
+ resolving,
1895
+ false,
1896
+ capturedMotionRefNames,
1897
+ true,
1898
+ );
1899
+ }
1900
+
1901
+ function isReactGlobalDocumentReference(
1902
+ node: TypeScript.Node,
1903
+ sourceFile: TypeScript.SourceFile,
1904
+ resolving = new Set<string>(),
1905
+ ): boolean {
1906
+ if (ts.isIdentifier(node) && node.text === "document") {
1907
+ return !isReactStaticPropertyName(node);
1908
+ }
1909
+
1910
+ const propertyAccess = ts.isPropertyAccessExpression(node) ? node : null;
1911
+ const elementAccess = ts.isElementAccessExpression(node) ? node : null;
1912
+ const receiver =
1913
+ propertyAccess?.expression ?? elementAccess?.expression ?? null;
1914
+ const propertyName = propertyAccess
1915
+ ? propertyAccess.name.text
1916
+ : elementAccess?.argumentExpression &&
1917
+ ts.isStringLiteralLike(elementAccess.argumentExpression)
1918
+ ? elementAccess.argumentExpression.text
1919
+ : null;
1920
+ if (
1921
+ propertyName === "document" &&
1922
+ receiver !== null &&
1923
+ isReactGlobalObjectReference(receiver, sourceFile, resolving)
1924
+ ) {
1925
+ return true;
1926
+ }
1927
+
1928
+ if (!ts.isIdentifier(node) || isReactStaticPropertyName(node)) return false;
1929
+ const resolved = resolveReactStyleExpression(node, sourceFile);
1930
+ if (!resolved) return false;
1931
+ const declarationKey = `${resolved.start}:${resolved.end}`;
1932
+ if (resolving.has(declarationKey)) return true;
1933
+ resolving.add(declarationKey);
1934
+
1935
+ let found = false;
1936
+ const visit = (current: TypeScript.Node): void => {
1937
+ if (found) return;
1938
+ if (isReactGlobalDocumentReference(current, sourceFile, resolving)) {
1939
+ found = true;
1940
+ return;
1941
+ }
1942
+ ts.forEachChild(current, visit);
1943
+ };
1944
+ visit(resolved.expression);
1945
+ resolving.delete(declarationKey);
1946
+ return found;
1947
+ }
1948
+
1949
+ function isReactGlobalObjectReference(
1950
+ expression: TypeScript.Expression,
1951
+ sourceFile: TypeScript.SourceFile,
1952
+ resolving: Set<string>,
1953
+ ): boolean {
1954
+ while (
1955
+ ts.isParenthesizedExpression(expression) ||
1956
+ ts.isAsExpression(expression) ||
1957
+ ts.isTypeAssertionExpression(expression) ||
1958
+ ts.isSatisfiesExpression(expression)
1959
+ ) {
1960
+ expression = expression.expression;
1961
+ }
1962
+ if (!ts.isIdentifier(expression) || isReactStaticPropertyName(expression)) {
1963
+ return false;
1964
+ }
1965
+ if (
1966
+ ["globalThis", "self", "window"].includes(expression.text) &&
1967
+ !hasReactStyleParameterShadow(expression, sourceFile, expression.text) &&
1968
+ !resolveReactStyleExpression(expression, sourceFile)
1969
+ ) {
1970
+ return true;
1971
+ }
1972
+
1973
+ const resolved = resolveReactStyleExpression(expression, sourceFile);
1974
+ if (!resolved) return false;
1975
+ const declarationKey = `${resolved.start}:${resolved.end}`;
1976
+ if (resolving.has(declarationKey)) return false;
1977
+ resolving.add(declarationKey);
1978
+ const isGlobal = isReactGlobalObjectReference(
1979
+ resolved.expression,
1980
+ sourceFile,
1981
+ resolving,
1982
+ );
1983
+ resolving.delete(declarationKey);
1984
+ return isGlobal;
1985
+ }
1986
+
1987
+ function isReactLifecycleEffectCall(
1988
+ call: TypeScript.CallExpression,
1989
+ sourceFile: TypeScript.SourceFile,
1990
+ ): boolean {
1991
+ return isReactLifecycleEffectExpression(call.expression, sourceFile);
1992
+ }
1993
+
1994
+ function isReactLifecycleEffectExpression(
1995
+ expression: TypeScript.Expression,
1996
+ sourceFile: TypeScript.SourceFile,
1997
+ resolving = new Set<string>(),
1998
+ ): boolean {
1999
+ while (
2000
+ ts.isParenthesizedExpression(expression) ||
2001
+ ts.isAsExpression(expression) ||
2002
+ ts.isTypeAssertionExpression(expression) ||
2003
+ ts.isSatisfiesExpression(expression)
2004
+ ) {
2005
+ expression = expression.expression;
2006
+ }
2007
+ if (ts.isPropertyAccessExpression(expression)) {
2008
+ return ["useEffect", "useLayoutEffect", "useInsertionEffect"].includes(
2009
+ expression.name.text,
2010
+ );
2011
+ }
2012
+ if (!ts.isIdentifier(expression)) return false;
2013
+ if (
2014
+ ["useEffect", "useLayoutEffect", "useInsertionEffect"].includes(
2015
+ expression.text,
2016
+ )
2017
+ ) {
2018
+ return true;
2019
+ }
2020
+
2021
+ const importedReactHook = sourceFile.statements.some((statement) => {
2022
+ if (
2023
+ !ts.isImportDeclaration(statement) ||
2024
+ !ts.isStringLiteral(statement.moduleSpecifier) ||
2025
+ statement.moduleSpecifier.text !== "react"
2026
+ ) {
2027
+ return false;
2028
+ }
2029
+ const namedBindings = statement.importClause?.namedBindings;
2030
+ return (
2031
+ !!namedBindings &&
2032
+ ts.isNamedImports(namedBindings) &&
2033
+ namedBindings.elements.some(
2034
+ (element) =>
2035
+ element.name.text === expression.text &&
2036
+ ["useEffect", "useLayoutEffect", "useInsertionEffect"].includes(
2037
+ element.propertyName?.text ?? element.name.text,
2038
+ ),
2039
+ )
2040
+ );
2041
+ });
2042
+ if (importedReactHook) return true;
2043
+
2044
+ const resolved = resolveReactStyleExpression(expression, sourceFile);
2045
+ if (!resolved) return false;
2046
+ const declarationKey = `${resolved.start}:${resolved.end}`;
2047
+ if (resolving.has(declarationKey)) return false;
2048
+ resolving.add(declarationKey);
2049
+ const isEffect = isReactLifecycleEffectExpression(
2050
+ resolved.expression,
2051
+ sourceFile,
2052
+ resolving,
2053
+ );
2054
+ resolving.delete(declarationKey);
2055
+ return isEffect;
2056
+ }
2057
+
2058
+ function inspectReactJsxSpreadExpression(
2059
+ expression: TypeScript.Expression,
2060
+ sourceFile: TypeScript.SourceFile,
2061
+ resolving: Set<string>,
2062
+ capturedMotionRefNames: ReadonlySet<string> = new Set(),
2063
+ ): ReactStyleInspection {
2064
+ while (
2065
+ ts.isParenthesizedExpression(expression) ||
2066
+ ts.isAsExpression(expression) ||
2067
+ ts.isTypeAssertionExpression(expression) ||
2068
+ ts.isSatisfiesExpression(expression)
2069
+ ) {
2070
+ expression = expression.expression;
2071
+ }
2072
+
2073
+ if (!ts.isObjectLiteralExpression(expression)) {
2074
+ const resolved = resolveReactStyleExpression(expression, sourceFile);
2075
+ if (!resolved) return "unknown";
2076
+ const declarationKey = `${resolved.start}:${resolved.end}`;
2077
+ if (resolving.has(declarationKey)) return "unknown";
2078
+ resolving.add(declarationKey);
2079
+ const result = inspectReactJsxSpreadExpression(
2080
+ resolved.expression,
2081
+ sourceFile,
2082
+ resolving,
2083
+ capturedMotionRefNames,
2084
+ );
2085
+ resolving.delete(declarationKey);
2086
+ return result;
2087
+ }
2088
+
2089
+ for (const property of expression.properties) {
2090
+ if (ts.isSpreadAssignment(property)) {
2091
+ const inspection = inspectReactJsxSpreadExpression(
2092
+ property.expression,
2093
+ sourceFile,
2094
+ resolving,
2095
+ capturedMotionRefNames,
2096
+ );
2097
+ if (inspection !== "safe") return inspection;
2098
+ continue;
2099
+ }
2100
+
2101
+ const name =
2102
+ ts.isPropertyAssignment(property) ||
2103
+ ts.isShorthandPropertyAssignment(property)
2104
+ ? property.name
2105
+ : null;
2106
+ if (!name) return "unknown";
2107
+ const propertyName =
2108
+ ts.isIdentifier(name) || ts.isStringLiteral(name)
2109
+ ? name.text
2110
+ : ts.isComputedPropertyName(name) && ts.isStringLiteral(name.expression)
2111
+ ? name.expression.text
2112
+ : null;
2113
+ if (!propertyName) return "unknown";
2114
+
2115
+ const value = ts.isPropertyAssignment(property)
2116
+ ? property.initializer
2117
+ : ts.isShorthandPropertyAssignment(property)
2118
+ ? property.name
2119
+ : null;
2120
+ if (!value) return "unknown";
2121
+
2122
+ let inspection: ReactStyleInspection;
2123
+ if (propertyName === "style") {
2124
+ inspection = inspectReactStyleExpression(value, sourceFile, new Set());
2125
+ } else if (propertyName === "css" || propertyName === "sx") {
2126
+ inspection = inspectReactCssExpression(
2127
+ value,
2128
+ sourceFile,
2129
+ new Set(),
2130
+ true,
2131
+ );
2132
+ } else if (propertyName === "ref" || /^on[A-Z]/.test(propertyName)) {
2133
+ inspection = inspectReactStyleMutationCallback(
2134
+ value,
2135
+ sourceFile,
2136
+ new Set(),
2137
+ propertyName === "ref",
2138
+ capturedMotionRefNames,
2139
+ );
2140
+ } else if (
2141
+ ["class", "className", "children", "dangerouslySetInnerHTML"].includes(
2142
+ propertyName,
2143
+ )
2144
+ ) {
2145
+ inspection = "unknown";
2146
+ } else {
2147
+ inspection = isStaticReactJsxSpreadValue(value) ? "safe" : "unknown";
2148
+ }
2149
+ if (inspection !== "safe") return inspection;
2150
+ }
2151
+
2152
+ return "safe";
2153
+ }
2154
+
2155
+ function isStaticReactJsxSpreadValue(
2156
+ expression: TypeScript.Expression,
2157
+ ): boolean {
2158
+ while (
2159
+ ts.isParenthesizedExpression(expression) ||
2160
+ ts.isAsExpression(expression) ||
2161
+ ts.isTypeAssertionExpression(expression) ||
2162
+ ts.isSatisfiesExpression(expression)
2163
+ ) {
2164
+ expression = expression.expression;
2165
+ }
2166
+
2167
+ return (
2168
+ ts.isStringLiteralLike(expression) ||
2169
+ ts.isNumericLiteral(expression) ||
2170
+ expression.kind === ts.SyntaxKind.TrueKeyword ||
2171
+ expression.kind === ts.SyntaxKind.FalseKeyword ||
2172
+ expression.kind === ts.SyntaxKind.NullKeyword ||
2173
+ (ts.isIdentifier(expression) && expression.text === "undefined")
2174
+ );
2175
+ }
2176
+
2177
+ function isReactRefTargetMethodCall(
2178
+ call: TypeScript.CallExpression,
2179
+ callbackParameterNames: Set<string>,
2180
+ body: TypeScript.ConciseBody,
2181
+ ): boolean {
2182
+ const callee = call.expression;
2183
+ const propertyAccess = ts.isPropertyAccessExpression(callee) ? callee : null;
2184
+ const elementAccess = ts.isElementAccessExpression(callee) ? callee : null;
2185
+ const receiver =
2186
+ propertyAccess?.expression ?? elementAccess?.expression ?? null;
2187
+ if (
2188
+ !receiver ||
2189
+ !isReactCallbackParameterDerivedExpression(
2190
+ receiver,
2191
+ callbackParameterNames,
2192
+ body,
2193
+ new Set(),
2194
+ )
2195
+ ) {
2196
+ return false;
2197
+ }
2198
+
2199
+ const argumentExpression = elementAccess?.argumentExpression;
2200
+ const methodName = propertyAccess
2201
+ ? propertyAccess.name.text
2202
+ : argumentExpression && ts.isStringLiteralLike(argumentExpression)
2203
+ ? argumentExpression.text
2204
+ : null;
2205
+ return !methodName || !REACT_REF_READ_METHODS.has(methodName);
2206
+ }
2207
+
2208
+ function isReactRefTargetMemberAccess(
2209
+ node: TypeScript.Node,
2210
+ callbackParameterNames: Set<string>,
2211
+ body: TypeScript.ConciseBody,
2212
+ ): boolean {
2213
+ const propertyAccess = ts.isPropertyAccessExpression(node) ? node : null;
2214
+ const elementAccess = ts.isElementAccessExpression(node) ? node : null;
2215
+ const receiver =
2216
+ propertyAccess?.expression ?? elementAccess?.expression ?? null;
2217
+ if (
2218
+ !receiver ||
2219
+ !isReactCallbackParameterDerivedExpression(
2220
+ receiver,
2221
+ callbackParameterNames,
2222
+ body,
2223
+ new Set(),
2224
+ )
2225
+ ) {
2226
+ return false;
2227
+ }
2228
+
2229
+ const argumentExpression = elementAccess?.argumentExpression;
2230
+ const propertyName = propertyAccess
2231
+ ? propertyAccess.name.text
2232
+ : argumentExpression && ts.isStringLiteralLike(argumentExpression)
2233
+ ? argumentExpression.text
2234
+ : null;
2235
+ return !propertyName || !REACT_REF_READ_METHODS.has(propertyName);
2236
+ }
2237
+
2238
+ function isReactRefTargetMemberAssignment(
2239
+ assignment: TypeScript.BinaryExpression,
2240
+ callbackParameterNames: Set<string>,
2241
+ body: TypeScript.ConciseBody,
2242
+ ): boolean {
2243
+ if (!isReactAssignmentOperator(assignment.operatorToken.getText())) {
2244
+ return false;
2245
+ }
2246
+
2247
+ const left = assignment.left;
2248
+ const receiver = ts.isPropertyAccessExpression(left)
2249
+ ? left.expression
2250
+ : ts.isElementAccessExpression(left)
2251
+ ? left.expression
2252
+ : null;
2253
+ return (
2254
+ receiver !== null &&
2255
+ isReactCallbackParameterDerivedExpression(
2256
+ receiver,
2257
+ callbackParameterNames,
2258
+ body,
2259
+ new Set(),
2260
+ )
2261
+ );
2262
+ }
2263
+
2264
+ function isReactSafeEventTargetScalarRead(
2265
+ node:
2266
+ | TypeScript.PropertyAccessExpression
2267
+ | TypeScript.ElementAccessExpression,
2268
+ callbackParameterNames: Set<string>,
2269
+ body: TypeScript.ConciseBody,
2270
+ eventTargetNames: Set<string>,
2271
+ ): boolean {
2272
+ const parent = getReactMemberAccessFollowing(node);
2273
+ const propertyName = parent
2274
+ ? ts.isPropertyAccessExpression(parent)
2275
+ ? parent.name.text
2276
+ : parent.argumentExpression &&
2277
+ ts.isStringLiteralLike(parent.argumentExpression)
2278
+ ? parent.argumentExpression.text
2279
+ : null
2280
+ : null;
2281
+ return (
2282
+ propertyName !== null &&
2283
+ REACT_EVENT_TARGET_SCALAR_PROPERTIES.has(propertyName) &&
2284
+ isReactEventTargetBaseAccess(
2285
+ node,
2286
+ callbackParameterNames,
2287
+ body,
2288
+ eventTargetNames,
2289
+ )
2290
+ );
2291
+ }
2292
+
2293
+ function isReactSafeEventTargetScalarValue(
2294
+ expression: TypeScript.Expression,
2295
+ callbackParameterNames: Set<string>,
2296
+ body: TypeScript.ConciseBody,
2297
+ eventTargetNames: Set<string>,
2298
+ eventTargetScalarNames: Set<string>,
2299
+ ): boolean {
2300
+ while (
2301
+ ts.isParenthesizedExpression(expression) ||
2302
+ ts.isAsExpression(expression) ||
2303
+ ts.isTypeAssertionExpression(expression) ||
2304
+ ts.isSatisfiesExpression(expression) ||
2305
+ ts.isNonNullExpression(expression)
2306
+ ) {
2307
+ expression = expression.expression;
2308
+ }
2309
+ if (
2310
+ ts.isIdentifier(expression) &&
2311
+ eventTargetScalarNames.has(expression.text)
2312
+ ) {
2313
+ return true;
2314
+ }
2315
+ if (ts.isPropertyAccessExpression(expression)) {
2316
+ return (
2317
+ REACT_EVENT_TARGET_SCALAR_PROPERTIES.has(expression.name.text) &&
2318
+ isReactEventTargetBaseAccess(
2319
+ expression.expression,
2320
+ callbackParameterNames,
2321
+ body,
2322
+ eventTargetNames,
2323
+ )
2324
+ );
2325
+ }
2326
+ if (ts.isElementAccessExpression(expression)) {
2327
+ const propertyName = expression.argumentExpression;
2328
+ return (
2329
+ !!propertyName &&
2330
+ ts.isStringLiteralLike(propertyName) &&
2331
+ REACT_EVENT_TARGET_SCALAR_PROPERTIES.has(propertyName.text) &&
2332
+ isReactEventTargetBaseAccess(
2333
+ expression.expression,
2334
+ callbackParameterNames,
2335
+ body,
2336
+ eventTargetNames,
2337
+ )
2338
+ );
2339
+ }
2340
+ if (ts.isCallExpression(expression) && expression.arguments.length === 0) {
2341
+ const callee = unwrapReactMotionExpression(expression.expression);
2342
+ return (
2343
+ ts.isPropertyAccessExpression(callee) &&
2344
+ REACT_EVENT_TARGET_SCALAR_METHODS.has(callee.name.text) &&
2345
+ isReactSafeEventTargetScalarValue(
2346
+ callee.expression,
2347
+ callbackParameterNames,
2348
+ body,
2349
+ eventTargetNames,
2350
+ eventTargetScalarNames,
2351
+ )
2352
+ );
2353
+ }
2354
+ return false;
2355
+ }
2356
+
2357
+ function isReactEventTargetBaseAccess(
2358
+ expression: TypeScript.Expression,
2359
+ callbackParameterNames: Set<string>,
2360
+ body: TypeScript.ConciseBody,
2361
+ eventTargetNames: Set<string>,
2362
+ ): boolean {
2363
+ expression = unwrapReactMotionExpression(expression);
2364
+ if (ts.isIdentifier(expression) && eventTargetNames.has(expression.text)) {
2365
+ return true;
2366
+ }
2367
+ if (ts.isPropertyAccessExpression(expression)) {
2368
+ return (
2369
+ REACT_EVENT_TARGET_PROPERTIES.has(expression.name.text) &&
2370
+ isReactCallbackParameterDerivedExpression(
2371
+ expression.expression,
2372
+ callbackParameterNames,
2373
+ body,
2374
+ new Set(),
2375
+ )
2376
+ );
2377
+ }
2378
+ if (ts.isElementAccessExpression(expression)) {
2379
+ const propertyName = expression.argumentExpression;
2380
+ return (
2381
+ !!propertyName &&
2382
+ ts.isStringLiteralLike(propertyName) &&
2383
+ REACT_EVENT_TARGET_PROPERTIES.has(propertyName.text) &&
2384
+ isReactCallbackParameterDerivedExpression(
2385
+ expression.expression,
2386
+ callbackParameterNames,
2387
+ body,
2388
+ new Set(),
2389
+ )
2390
+ );
2391
+ }
2392
+ return false;
2393
+ }
2394
+
2395
+ function getReactMemberAccessFollowing(
2396
+ node: TypeScript.Node,
2397
+ ):
2398
+ | TypeScript.PropertyAccessExpression
2399
+ | TypeScript.ElementAccessExpression
2400
+ | null {
2401
+ let current: TypeScript.Node = node;
2402
+ let parent = current.parent;
2403
+ while (
2404
+ (ts.isParenthesizedExpression(parent) ||
2405
+ ts.isAsExpression(parent) ||
2406
+ ts.isTypeAssertionExpression(parent) ||
2407
+ ts.isSatisfiesExpression(parent) ||
2408
+ ts.isNonNullExpression(parent)) &&
2409
+ parent.expression === current
2410
+ ) {
2411
+ current = parent;
2412
+ parent = current.parent;
2413
+ }
2414
+ return (ts.isPropertyAccessExpression(parent) ||
2415
+ ts.isElementAccessExpression(parent)) &&
2416
+ parent.expression === current
2417
+ ? parent
2418
+ : null;
2419
+ }
2420
+
2421
+ function unwrapReactMotionExpression(
2422
+ expression: TypeScript.Expression,
2423
+ ): TypeScript.Expression {
2424
+ while (
2425
+ ts.isParenthesizedExpression(expression) ||
2426
+ ts.isAsExpression(expression) ||
2427
+ ts.isTypeAssertionExpression(expression) ||
2428
+ ts.isSatisfiesExpression(expression) ||
2429
+ ts.isNonNullExpression(expression)
2430
+ ) {
2431
+ expression = expression.expression;
2432
+ }
2433
+ return expression;
2434
+ }
2435
+
2436
+ function isReactEventTargetMutation(
2437
+ node: TypeScript.Node,
2438
+ callbackParameterNames: Set<string>,
2439
+ body: TypeScript.ConciseBody,
2440
+ eventTargetNames: Set<string>,
2441
+ ): boolean {
2442
+ if (ts.isBinaryExpression(node)) {
2443
+ return (
2444
+ isReactAssignmentOperator(node.operatorToken.getText()) &&
2445
+ containsReactEventTargetBaseAccess(
2446
+ node.left,
2447
+ callbackParameterNames,
2448
+ body,
2449
+ eventTargetNames,
2450
+ )
2451
+ );
2452
+ }
2453
+ if (ts.isPrefixUnaryExpression(node)) {
2454
+ return (
2455
+ [
2456
+ ts.SyntaxKind.DeleteKeyword,
2457
+ ts.SyntaxKind.PlusPlusToken,
2458
+ ts.SyntaxKind.MinusMinusToken,
2459
+ ].includes(node.operator) &&
2460
+ containsReactEventTargetBaseAccess(
2461
+ node.operand,
2462
+ callbackParameterNames,
2463
+ body,
2464
+ eventTargetNames,
2465
+ )
2466
+ );
2467
+ }
2468
+ if (ts.isPostfixUnaryExpression(node)) {
2469
+ return (
2470
+ [ts.SyntaxKind.PlusPlusToken, ts.SyntaxKind.MinusMinusToken].includes(
2471
+ node.operator,
2472
+ ) &&
2473
+ containsReactEventTargetBaseAccess(
2474
+ node.operand,
2475
+ callbackParameterNames,
2476
+ body,
2477
+ eventTargetNames,
2478
+ )
2479
+ );
2480
+ }
2481
+ return false;
2482
+ }
2483
+
2484
+ function containsReactEventTargetBaseAccess(
2485
+ expression: TypeScript.Expression,
2486
+ callbackParameterNames: Set<string>,
2487
+ body: TypeScript.ConciseBody,
2488
+ eventTargetNames: Set<string>,
2489
+ ): boolean {
2490
+ expression = unwrapReactMotionExpression(expression);
2491
+ if (
2492
+ isReactEventTargetBaseAccess(
2493
+ expression,
2494
+ callbackParameterNames,
2495
+ body,
2496
+ eventTargetNames,
2497
+ )
2498
+ ) {
2499
+ return true;
2500
+ }
2501
+ if (
2502
+ ts.isPropertyAccessExpression(expression) ||
2503
+ ts.isElementAccessExpression(expression)
2504
+ ) {
2505
+ return containsReactEventTargetBaseAccess(
2506
+ expression.expression,
2507
+ callbackParameterNames,
2508
+ body,
2509
+ eventTargetNames,
2510
+ );
2511
+ }
2512
+ return false;
2513
+ }
2514
+
2515
+ function isReactAssignmentOperator(operator: string): boolean {
2516
+ return [
2517
+ "=",
2518
+ "+=",
2519
+ "-=",
2520
+ "*=",
2521
+ "**=",
2522
+ "/=",
2523
+ "%=",
2524
+ "<<=",
2525
+ ">>=",
2526
+ ">>>=",
2527
+ "&=",
2528
+ "|=",
2529
+ "^=",
2530
+ "&&=",
2531
+ "||=",
2532
+ "??=",
2533
+ ].includes(operator);
2534
+ }
2535
+
2536
+ function isReactCallbackParameterDerivedExpression(
2537
+ expression: TypeScript.Expression,
2538
+ callbackParameterNames: Set<string>,
2539
+ body: TypeScript.ConciseBody,
2540
+ resolving: Set<string>,
2541
+ ): boolean {
2542
+ while (
2543
+ ts.isParenthesizedExpression(expression) ||
2544
+ ts.isAsExpression(expression) ||
2545
+ ts.isTypeAssertionExpression(expression) ||
2546
+ ts.isSatisfiesExpression(expression) ||
2547
+ ts.isNonNullExpression(expression)
2548
+ ) {
2549
+ expression = expression.expression;
2550
+ }
2551
+
2552
+ if (ts.isIdentifier(expression)) {
2553
+ if (callbackParameterNames.has(expression.text)) return true;
2554
+ if (resolving.has(expression.text)) return false;
2555
+
2556
+ const assignments: TypeScript.Expression[] = [];
2557
+ const visit = (node: TypeScript.Node): void => {
2558
+ if (
2559
+ ts.isVariableDeclaration(node) &&
2560
+ ts.isIdentifier(node.name) &&
2561
+ node.name.text === expression.text &&
2562
+ node.initializer
2563
+ ) {
2564
+ assignments.push(node.initializer);
2565
+ } else if (
2566
+ ts.isBinaryExpression(node) &&
2567
+ ts.isIdentifier(node.left) &&
2568
+ node.left.text === expression.text &&
2569
+ isReactAssignmentOperator(node.operatorToken.getText())
2570
+ ) {
2571
+ assignments.push(node.right);
2572
+ }
2573
+ ts.forEachChild(node, visit);
2574
+ };
2575
+ visit(body);
2576
+ if (assignments.length === 0) return false;
2577
+
2578
+ resolving.add(expression.text);
2579
+ const derived = assignments.some((assignment) =>
2580
+ isReactCallbackParameterDerivedExpression(
2581
+ assignment,
2582
+ callbackParameterNames,
2583
+ body,
2584
+ resolving,
2585
+ ),
2586
+ );
2587
+ resolving.delete(expression.text);
2588
+ return derived;
2589
+ }
2590
+
2591
+ if (ts.isPropertyAccessExpression(expression)) {
2592
+ return isReactCallbackParameterDerivedExpression(
2593
+ expression.expression,
2594
+ callbackParameterNames,
2595
+ body,
2596
+ resolving,
2597
+ );
2598
+ }
2599
+ if (ts.isElementAccessExpression(expression)) {
2600
+ return isReactCallbackParameterDerivedExpression(
2601
+ expression.expression,
2602
+ callbackParameterNames,
2603
+ body,
2604
+ resolving,
2605
+ );
2606
+ }
2607
+ if (ts.isCallExpression(expression)) {
2608
+ const callee = expression.expression;
2609
+ const receiver = ts.isPropertyAccessExpression(callee)
2610
+ ? callee.expression
2611
+ : ts.isElementAccessExpression(callee)
2612
+ ? callee.expression
2613
+ : null;
2614
+ return (
2615
+ receiver !== null &&
2616
+ isReactCallbackParameterDerivedExpression(
2617
+ receiver,
2618
+ callbackParameterNames,
2619
+ body,
2620
+ resolving,
2621
+ )
2622
+ );
2623
+ }
2624
+ return false;
2625
+ }
2626
+
2627
+ function containsReactCallbackParameterReference(
2628
+ node: TypeScript.Node,
2629
+ callbackParameterNames: Set<string>,
2630
+ ): boolean {
2631
+ let found = false;
2632
+ const visit = (current: TypeScript.Node): void => {
2633
+ if (found) return;
2634
+ if (
2635
+ ts.isIdentifier(current) &&
2636
+ callbackParameterNames.has(current.text) &&
2637
+ !isReactStaticPropertyName(current)
2638
+ ) {
2639
+ found = true;
2640
+ return;
2641
+ }
2642
+ ts.forEachChild(current, visit);
2643
+ };
2644
+ visit(node);
2645
+ return found;
2646
+ }
2647
+
2648
+ function isReactStaticPropertyName(node: TypeScript.Identifier): boolean {
2649
+ const parent = node.parent;
2650
+ return (
2651
+ (ts.isPropertyAccessExpression(parent) && parent.name === node) ||
2652
+ ((ts.isPropertyAssignment(parent) ||
2653
+ ts.isMethodDeclaration(parent) ||
2654
+ ts.isMethodSignature(parent) ||
2655
+ ts.isPropertyDeclaration(parent) ||
2656
+ ts.isPropertySignature(parent)) &&
2657
+ parent.name === node &&
2658
+ !ts.isComputedPropertyName(parent.name))
2659
+ );
2660
+ }
2661
+
2662
+ function isReactBindingNameIdentifier(node: TypeScript.Identifier): boolean {
2663
+ const parent = node.parent;
2664
+ return (
2665
+ (ts.isBindingElement(parent) && parent.name === node) ||
2666
+ (ts.isParameter(parent) && parent.name === node) ||
2667
+ (ts.isVariableDeclaration(parent) && parent.name === node)
2668
+ );
2669
+ }
2670
+
2671
+ function getReactCallbackParameterNames(
2672
+ parameters: TypeScript.NodeArray<TypeScript.ParameterDeclaration>,
2673
+ ): Set<string> {
2674
+ const names = new Set<string>();
2675
+ const addBindingNames = (bindingName: TypeScript.BindingName): void => {
2676
+ if (ts.isIdentifier(bindingName)) {
2677
+ names.add(bindingName.text);
2678
+ return;
2679
+ }
2680
+ if (ts.isObjectBindingPattern(bindingName)) {
2681
+ for (const element of bindingName.elements) {
2682
+ addBindingNames(element.name);
2683
+ }
2684
+ return;
2685
+ }
2686
+ for (const element of bindingName.elements) {
2687
+ if (!ts.isOmittedExpression(element)) addBindingNames(element.name);
2688
+ }
2689
+ };
2690
+
2691
+ for (const parameter of parameters) addBindingNames(parameter.name);
2692
+ return names;
2693
+ }
2694
+
2695
+ interface ReactCallbackEventTargetBindings {
2696
+ targetNames: Set<string>;
2697
+ scalarNames: Set<string>;
2698
+ }
2699
+
2700
+ function getReactCallbackEventTargetBindings(
2701
+ parameters: TypeScript.NodeArray<TypeScript.ParameterDeclaration>,
2702
+ body: TypeScript.ConciseBody,
2703
+ callbackParameterNames: Set<string>,
2704
+ ): ReactCallbackEventTargetBindings {
2705
+ const targetNames = new Set<string>();
2706
+ const scalarNames = new Set<string>();
2707
+ const eventObjectNames = new Set(
2708
+ parameters.flatMap((parameter) =>
2709
+ ts.isIdentifier(parameter.name) ? [parameter.name.text] : [],
2710
+ ),
2711
+ );
2712
+ const localDeclarations: TypeScript.VariableDeclaration[] = [];
2713
+ const addBindingNames = (
2714
+ bindingName: TypeScript.BindingName,
2715
+ into: Set<string>,
2716
+ ): boolean => {
2717
+ if (ts.isIdentifier(bindingName)) {
2718
+ const previousSize = into.size;
2719
+ into.add(bindingName.text);
2720
+ return into.size !== previousSize;
2721
+ }
2722
+ let changed = false;
2723
+ if (ts.isObjectBindingPattern(bindingName)) {
2724
+ for (const element of bindingName.elements) {
2725
+ changed = addBindingNames(element.name, into) || changed;
2726
+ }
2727
+ return changed;
2728
+ }
2729
+ for (const element of bindingName.elements) {
2730
+ if (!ts.isOmittedExpression(element)) {
2731
+ changed = addBindingNames(element.name, into) || changed;
2732
+ }
2733
+ }
2734
+ return changed;
2735
+ };
2736
+
2737
+ const addTargetObjectBinding = (
2738
+ bindingName: TypeScript.BindingName,
2739
+ ): boolean => {
2740
+ if (!ts.isObjectBindingPattern(bindingName)) {
2741
+ return addBindingNames(bindingName, targetNames);
2742
+ }
2743
+ let changed = false;
2744
+ for (const element of bindingName.elements) {
2745
+ const propertyName = element.propertyName
2746
+ ? getReactStaticBindingPropertyName(element.propertyName)
2747
+ : ts.isIdentifier(element.name)
2748
+ ? element.name.text
2749
+ : null;
2750
+ if (
2751
+ !element.dotDotDotToken &&
2752
+ propertyName &&
2753
+ REACT_EVENT_TARGET_SCALAR_PROPERTIES.has(propertyName) &&
2754
+ ts.isIdentifier(element.name)
2755
+ ) {
2756
+ const previousSize = scalarNames.size;
2757
+ scalarNames.add(element.name.text);
2758
+ changed = scalarNames.size !== previousSize || changed;
2759
+ } else {
2760
+ changed = addBindingNames(element.name, targetNames) || changed;
2761
+ }
2762
+ }
2763
+ return changed;
2764
+ };
2765
+
2766
+ const addEventObjectBinding = (
2767
+ bindingName: TypeScript.BindingName,
2768
+ ): boolean => {
2769
+ if (!ts.isObjectBindingPattern(bindingName)) {
2770
+ return addBindingNames(bindingName, targetNames);
2771
+ }
2772
+ let changed = false;
2773
+ for (const element of bindingName.elements) {
2774
+ const propertyName = element.propertyName
2775
+ ? getReactStaticBindingPropertyName(element.propertyName)
2776
+ : ts.isIdentifier(element.name)
2777
+ ? element.name.text
2778
+ : null;
2779
+ if (
2780
+ !element.dotDotDotToken &&
2781
+ propertyName &&
2782
+ REACT_EVENT_TARGET_PROPERTIES.has(propertyName)
2783
+ ) {
2784
+ if (ts.isIdentifier(element.name)) {
2785
+ const previousSize = targetNames.size;
2786
+ targetNames.add(element.name.text);
2787
+ changed = targetNames.size !== previousSize || changed;
2788
+ } else {
2789
+ changed = addTargetObjectBinding(element.name) || changed;
2790
+ }
2791
+ } else if (element.dotDotDotToken) {
2792
+ changed = addBindingNames(element.name, targetNames) || changed;
2793
+ }
2794
+ }
2795
+ return changed;
2796
+ };
2797
+
2798
+ for (const parameter of parameters) {
2799
+ if (!ts.isObjectBindingPattern(parameter.name)) continue;
2800
+ addEventObjectBinding(parameter.name);
2801
+ }
2802
+
2803
+ const collectDeclarations = (node: TypeScript.Node): void => {
2804
+ if (ts.isVariableDeclaration(node)) localDeclarations.push(node);
2805
+ ts.forEachChild(node, collectDeclarations);
2806
+ };
2807
+ collectDeclarations(body);
2808
+
2809
+ let changed = true;
2810
+ while (changed) {
2811
+ changed = false;
2812
+ for (const declaration of localDeclarations) {
2813
+ if (!declaration.initializer) continue;
2814
+ const initializer = unwrapReactMotionExpression(declaration.initializer);
2815
+ if (
2816
+ ts.isIdentifier(declaration.name) &&
2817
+ ts.isIdentifier(initializer) &&
2818
+ eventObjectNames.has(initializer.text)
2819
+ ) {
2820
+ const previousSize = eventObjectNames.size;
2821
+ eventObjectNames.add(declaration.name.text);
2822
+ changed = eventObjectNames.size !== previousSize || changed;
2823
+ continue;
2824
+ }
2825
+ if (
2826
+ ts.isObjectBindingPattern(declaration.name) &&
2827
+ ts.isIdentifier(initializer) &&
2828
+ eventObjectNames.has(initializer.text)
2829
+ ) {
2830
+ changed = addEventObjectBinding(declaration.name) || changed;
2831
+ continue;
2832
+ }
2833
+ if (
2834
+ ts.isObjectBindingPattern(declaration.name) &&
2835
+ isReactEventTargetBaseAccess(
2836
+ initializer,
2837
+ callbackParameterNames,
2838
+ body,
2839
+ targetNames,
2840
+ )
2841
+ ) {
2842
+ changed = addTargetObjectBinding(declaration.name) || changed;
2843
+ }
2844
+ }
2845
+ }
2846
+ return { targetNames, scalarNames };
2847
+ }
2848
+
2849
+ function getReactStaticBindingPropertyName(
2850
+ name: TypeScript.PropertyName,
2851
+ ): string | null {
2852
+ if (ts.isIdentifier(name) || ts.isStringLiteralLike(name)) return name.text;
2853
+ return ts.isComputedPropertyName(name) &&
2854
+ ts.isStringLiteralLike(name.expression)
2855
+ ? name.expression.text
2856
+ : null;
2857
+ }
2858
+
2859
+ function inspectReactStyleExpression(
2860
+ expression: TypeScript.Expression,
2861
+ sourceFile: TypeScript.SourceFile,
2862
+ resolving: Set<string>,
2863
+ ): ReactStyleInspection {
2864
+ while (
2865
+ ts.isParenthesizedExpression(expression) ||
2866
+ ts.isAsExpression(expression) ||
2867
+ ts.isTypeAssertionExpression(expression) ||
2868
+ ts.isSatisfiesExpression(expression)
2869
+ ) {
2870
+ expression = expression.expression;
2871
+ }
2872
+
2873
+ if (expression.kind === ts.SyntaxKind.NullKeyword) return "safe";
2874
+ if (ts.isObjectLiteralExpression(expression)) {
2875
+ for (const property of expression.properties) {
2876
+ if (ts.isSpreadAssignment(property)) return "unknown";
2877
+ const name = ts.isPropertyAssignment(property)
2878
+ ? property.name
2879
+ : ts.isShorthandPropertyAssignment(property)
2880
+ ? property.name
2881
+ : null;
2882
+ const text = name
2883
+ ? ts.isIdentifier(name) || ts.isStringLiteral(name)
2884
+ ? name.text
2885
+ : null
2886
+ : null;
2887
+ if (text && isReactMotionStylePropName(text)) return "motion";
2888
+ if (!text) return "unknown";
2889
+ }
2890
+ return "safe";
2891
+ }
2892
+
2893
+ const resolved = resolveReactStyleExpression(expression, sourceFile);
2894
+ if (!resolved) return "unknown";
2895
+ const declarationKey = `${resolved.start}:${resolved.end}`;
2896
+ if (resolving.has(declarationKey)) return "unknown";
2897
+ resolving.add(declarationKey);
2898
+ const result = inspectReactStyleExpression(
2899
+ resolved.expression,
2900
+ sourceFile,
2901
+ resolving,
2902
+ );
2903
+ resolving.delete(declarationKey);
2904
+ return result;
2905
+ }
2906
+
2907
+ function inspectReactCssExpression(
2908
+ expression: TypeScript.Expression,
2909
+ sourceFile: TypeScript.SourceFile,
2910
+ resolving: Set<string>,
2911
+ inspectRootCssString = false,
2912
+ ): ReactStyleInspection {
2913
+ while (
2914
+ ts.isParenthesizedExpression(expression) ||
2915
+ ts.isAsExpression(expression) ||
2916
+ ts.isTypeAssertionExpression(expression) ||
2917
+ ts.isSatisfiesExpression(expression)
2918
+ ) {
2919
+ expression = expression.expression;
2920
+ }
2921
+
2922
+ if (inspectRootCssString && isReactCssStringLiteral(expression)) {
2923
+ return inspectReactCssString(expression.text);
2924
+ }
2925
+ if (isStaticReactCssValueExpression(expression)) return "safe";
2926
+ if (ts.isObjectLiteralExpression(expression)) {
2927
+ for (const property of expression.properties) {
2928
+ if (ts.isSpreadAssignment(property)) return "unknown";
2929
+ if (
2930
+ !ts.isPropertyAssignment(property) &&
2931
+ !ts.isShorthandPropertyAssignment(property)
2932
+ ) {
2933
+ return "unknown";
2934
+ }
2935
+ const name = property.name;
2936
+ const text =
2937
+ ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : null;
2938
+ if (!text) return "unknown";
2939
+ if (isReactMotionStylePropName(text)) return "motion";
2940
+ const valueExpression = ts.isPropertyAssignment(property)
2941
+ ? property.initializer
2942
+ : property.name;
2943
+ if (!isStaticReactCssValueExpression(valueExpression)) {
2944
+ const nested = inspectReactCssExpression(
2945
+ valueExpression,
2946
+ sourceFile,
2947
+ resolving,
2948
+ );
2949
+ if (nested !== "safe") return nested;
2950
+ }
2951
+ }
2952
+ return "safe";
2953
+ }
2954
+
2955
+ const resolved = resolveReactStyleExpression(expression, sourceFile);
2956
+ if (!resolved) return "unknown";
2957
+ const declarationKey = `${resolved.start}:${resolved.end}`;
2958
+ if (resolving.has(declarationKey)) return "unknown";
2959
+ resolving.add(declarationKey);
2960
+ const result = inspectReactCssExpression(
2961
+ resolved.expression,
2962
+ sourceFile,
2963
+ resolving,
2964
+ inspectRootCssString,
2965
+ );
2966
+ resolving.delete(declarationKey);
2967
+ return result;
2968
+ }
2969
+
2970
+ function resolveReactStyleExpression(
2971
+ expression: TypeScript.Expression,
2972
+ sourceFile: TypeScript.SourceFile,
2973
+ ): { expression: TypeScript.Expression; start: number; end: number } | null {
2974
+ const name = ts.isIdentifier(expression)
2975
+ ? expression.text
2976
+ : ts.isPropertyAccessExpression(expression) &&
2977
+ ts.isIdentifier(expression.expression)
2978
+ ? expression.expression.text
2979
+ : null;
2980
+ if (!name) return null;
2981
+
2982
+ if (hasReactStyleParameterShadow(expression, sourceFile, name)) return null;
2983
+
2984
+ const matches: TypeScript.VariableDeclaration[] = [];
2985
+ const visit = (node: TypeScript.Node): void => {
2986
+ if (
2987
+ ts.isVariableDeclaration(node) &&
2988
+ ts.isIdentifier(node.name) &&
2989
+ node.name.text === name
2990
+ ) {
2991
+ matches.push(node);
2992
+ }
2993
+ ts.forEachChild(node, visit);
2994
+ };
2995
+ visit(sourceFile);
2996
+ if (matches.length !== 1) return null;
2997
+ const declaration = matches[0]!;
2998
+ const scope = reactVariableDeclarationScope(declaration);
2999
+ if (!scope || !isReactNodeWithinScope(scope, expression)) return null;
3000
+ const initializer = declaration.initializer;
3001
+ if (
3002
+ ts.isPropertyAccessExpression(expression) &&
3003
+ initializer &&
3004
+ ts.isObjectLiteralExpression(initializer)
3005
+ ) {
3006
+ const propertyName = expression.name.text;
3007
+ const properties = initializer.properties.filter(
3008
+ (property): property is TypeScript.PropertyAssignment =>
3009
+ ts.isPropertyAssignment(property) &&
3010
+ ((ts.isIdentifier(property.name) &&
3011
+ property.name.text === propertyName) ||
3012
+ (ts.isStringLiteral(property.name) &&
3013
+ property.name.text === propertyName)),
3014
+ );
3015
+ if (properties.length === 1) {
3016
+ return {
3017
+ expression: properties[0]!.initializer,
3018
+ start: properties[0]!.getStart(sourceFile),
3019
+ end: properties[0]!.end,
3020
+ };
3021
+ }
3022
+ }
3023
+ return initializer
3024
+ ? {
1087
3025
  expression: initializer,
1088
3026
  start: initializer.getStart(sourceFile),
1089
3027
  end: initializer.end,
@@ -1172,12 +3110,102 @@ function isReactMotionStylePropName(name: string): boolean {
1172
3110
  /^(?:webkit|moz|ms|o)?animation(?:name|duration|delay|timingfunction|fillmode|iterationcount|direction|playstate|composition|timeline|range(?:start|end)?)?$/.test(
1173
3111
  normalized,
1174
3112
  ) ||
1175
- /^(?:webkit|moz|ms|o)?transition(?:property|duration|delay|timingfunction)?$/.test(
3113
+ /^(?:webkit|moz|ms|o)?transition(?:property|duration|delay|timingfunction|behavior)?$/.test(
1176
3114
  normalized,
1177
3115
  )
1178
3116
  );
1179
3117
  }
1180
3118
 
3119
+ function isReactCssStringLiteral(
3120
+ expression: TypeScript.Expression,
3121
+ ): expression is
3122
+ | TypeScript.StringLiteral
3123
+ | TypeScript.NoSubstitutionTemplateLiteral {
3124
+ return (
3125
+ ts.isStringLiteral(expression) ||
3126
+ ts.isNoSubstitutionTemplateLiteral(expression)
3127
+ );
3128
+ }
3129
+
3130
+ function inspectReactCssString(css: string): ReactStyleInspection {
3131
+ const inspected = maskReactCssCommentsAndStrings(css);
3132
+ if (inspected.includes("\\")) return "unknown";
3133
+ const declarations = inspected.matchAll(/(?:^|[;{}\s])\s*([\w-]+)\s*:/g);
3134
+ let foundDeclaration = false;
3135
+ for (const declaration of declarations) {
3136
+ foundDeclaration = true;
3137
+ if (isReactMotionStylePropName(declaration[1]!)) return "motion";
3138
+ }
3139
+ if (foundDeclaration || inspected.trim().length === 0) return "safe";
3140
+ return "unknown";
3141
+ }
3142
+
3143
+ function maskReactCssCommentsAndStrings(css: string): string {
3144
+ let result = "";
3145
+ let quote: "'" | '"' | null = null;
3146
+ for (let index = 0; index < css.length; index++) {
3147
+ const character = css[index]!;
3148
+ if (quote) {
3149
+ if (character === "\\") {
3150
+ result += " ";
3151
+ const escaped = css[index + 1];
3152
+ if (escaped !== undefined) {
3153
+ result += escaped === "\n" ? "\n" : " ";
3154
+ index++;
3155
+ }
3156
+ } else {
3157
+ if (character === quote) quote = null;
3158
+ result += character === "\n" ? "\n" : " ";
3159
+ }
3160
+ continue;
3161
+ }
3162
+ if (character === "/" && css[index + 1] === "*") {
3163
+ result += " ";
3164
+ index++;
3165
+ while (
3166
+ index + 1 < css.length &&
3167
+ !(css[index] === "*" && css[index + 1] === "/")
3168
+ ) {
3169
+ index++;
3170
+ result += css[index] === "\n" ? "\n" : " ";
3171
+ }
3172
+ if (index + 1 < css.length) {
3173
+ result += " ";
3174
+ index++;
3175
+ }
3176
+ continue;
3177
+ }
3178
+ if (character === "'" || character === '"') {
3179
+ quote = character;
3180
+ result += " ";
3181
+ continue;
3182
+ }
3183
+ result += character;
3184
+ }
3185
+ return result;
3186
+ }
3187
+
3188
+ function isStaticReactCssValueExpression(
3189
+ expression: TypeScript.Expression,
3190
+ ): boolean {
3191
+ while (
3192
+ ts.isParenthesizedExpression(expression) ||
3193
+ ts.isAsExpression(expression) ||
3194
+ ts.isTypeAssertionExpression(expression) ||
3195
+ ts.isSatisfiesExpression(expression)
3196
+ ) {
3197
+ expression = expression.expression;
3198
+ }
3199
+ return (
3200
+ ts.isStringLiteral(expression) ||
3201
+ ts.isNoSubstitutionTemplateLiteral(expression) ||
3202
+ ts.isNumericLiteral(expression) ||
3203
+ expression.kind === ts.SyntaxKind.TrueKeyword ||
3204
+ expression.kind === ts.SyntaxKind.FalseKeyword ||
3205
+ expression.kind === ts.SyntaxKind.NullKeyword
3206
+ );
3207
+ }
3208
+
1181
3209
  function readStaticReactStyleContent(
1182
3210
  sourceFile: TypeScript.SourceFile,
1183
3211
  style: TypeScript.JsxElement,