@planningcenter/tapestry-migration-cli 3.2.2-rc.11 → 3.2.2-rc.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/tapestry-migration-cli",
3
- "version": "3.2.2-rc.11",
3
+ "version": "3.2.2-rc.12",
4
4
  "description": "CLI tool for Tapestry migrations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "devDependencies": {
34
34
  "@emotion/react": "^11.14.0",
35
- "@planningcenter/tapestry": "^3.2.2-rc.11",
35
+ "@planningcenter/tapestry": "^3.2.2-rc.12",
36
36
  "@planningcenter/tapestry-react": "^4.11.5",
37
37
  "@types/jscodeshift": "^17.3.0",
38
38
  "@types/node": "^20.0.0",
@@ -52,5 +52,5 @@
52
52
  "publishConfig": {
53
53
  "access": "public"
54
54
  },
55
- "gitHead": "d64a6e07db10af8f16bc160ea3857d30524d68e3"
55
+ "gitHead": "de4d6b4f5e4de190f2df030e208a7f6ab637bf59"
56
56
  }
@@ -0,0 +1,294 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import { stylePropTransformFactory } from "./stylePropTransformFactory"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(
9
+ transform: ReturnType<typeof stylePropTransformFactory>,
10
+ source: string
11
+ ): string {
12
+ const fileInfo = { path: "test.tsx", source }
13
+ const result = transform(
14
+ fileInfo,
15
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
16
+ {}
17
+ ) as string | null
18
+ return result || source
19
+ }
20
+
21
+ describe("stylePropTransformFactory", () => {
22
+ describe("grid alias remapping", () => {
23
+ const transform = stylePropTransformFactory({
24
+ stylesToRemove: [],
25
+ targetComponent: "Box",
26
+ targetPackage: "@planningcenter/tapestry-react",
27
+ })
28
+
29
+ it("remaps column/row string props to gridColumn/gridRow via splitStyles", () => {
30
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box column="1 / 3" row="2 / 4" />`
31
+ const result = applyTransform(transform, input)
32
+ expect(result).toMatchInlineSnapshot(`
33
+ "import { Box } from "@planningcenter/tapestry-react";<Box
34
+ style={{
35
+ gridColumn: "1 / 3",
36
+ gridRow: "2 / 4"
37
+ }} />"
38
+ `)
39
+ })
40
+
41
+ it("remaps column/row expression props to gridColumn/gridRow", () => {
42
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box column={1} row={2} />`
43
+ const result = applyTransform(transform, input)
44
+ expect(result).toMatchInlineSnapshot(`
45
+ "import { Box } from "@planningcenter/tapestry-react";<Box
46
+ style={{
47
+ gridColumn: 1,
48
+ gridRow: 2
49
+ }} />"
50
+ `)
51
+ })
52
+
53
+ it("remaps column/row identifier expression props to gridColumn/gridRow", () => {
54
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box column={col} row={r} />`
55
+ const result = applyTransform(transform, input)
56
+ expect(result).toMatchInlineSnapshot(`
57
+ "import { Box } from "@planningcenter/tapestry-react";<Box
58
+ style={{
59
+ gridColumn: col,
60
+ gridRow: r
61
+ }} />"
62
+ `)
63
+ })
64
+
65
+ it("remaps columnStart/columnEnd/rowStart/rowEnd expression props", () => {
66
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box columnStart={1} columnEnd={3} rowStart={2} rowEnd={4} />`
67
+ const result = applyTransform(transform, input)
68
+ expect(result).toMatchInlineSnapshot(`
69
+ "import { Box } from "@planningcenter/tapestry-react";<Box
70
+ style={{
71
+ gridColumnStart: 1,
72
+ gridColumnEnd: 3,
73
+ gridRowStart: 2,
74
+ gridRowEnd: 4
75
+ }} />"
76
+ `)
77
+ })
78
+
79
+ it("remaps direction expression prop to flexDirection", () => {
80
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box direction={dir} />`
81
+ const result = applyTransform(transform, input)
82
+ expect(result).toMatchInlineSnapshot(`
83
+ "import { Box } from "@planningcenter/tapestry-react";<Box style={{
84
+ flexDirection: dir
85
+ }} />"
86
+ `)
87
+ })
88
+
89
+ // Conditional expressions can't be evaluated at codemod time, so they
90
+ // bypass splitStyles and land in directProps as raw source. Without the
91
+ // alias map, the resulting `style` object would contain non-CSS keys
92
+ // like `column` / `row` instead of `gridColumn` / `gridRow`.
93
+ it("remaps column/row ternary expression props to gridColumn/gridRow", () => {
94
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box column={filter ? '1' : '2'} row={filter ? '2' : undefined} />`
95
+ const result = applyTransform(transform, input)
96
+ expect(result).toMatchInlineSnapshot(`
97
+ "import { Box } from "@planningcenter/tapestry-react";<Box
98
+ style={{
99
+ gridColumn: filter ? '1' : '2',
100
+ gridRow: filter ? '2' : undefined
101
+ }} />"
102
+ `)
103
+ })
104
+ })
105
+
106
+ describe("non-aliased style props", () => {
107
+ const transform = stylePropTransformFactory({
108
+ stylesToRemove: [],
109
+ targetComponent: "Box",
110
+ targetPackage: "@planningcenter/tapestry-react",
111
+ })
112
+
113
+ it("preserves the prop name for non-aliased identifier expression props", () => {
114
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box marginTop={spacing} />`
115
+ const result = applyTransform(transform, input)
116
+ expect(result).toMatchInlineSnapshot(`
117
+ "import { Box } from "@planningcenter/tapestry-react";<Box style={{
118
+ marginTop: spacing
119
+ }} />"
120
+ `)
121
+ })
122
+ })
123
+
124
+ describe("value-transforming aliases", () => {
125
+ const transform = stylePropTransformFactory({
126
+ stylesToRemove: [],
127
+ targetComponent: "Box",
128
+ targetPackage: "@planningcenter/tapestry-react",
129
+ })
130
+
131
+ it("comments out the renamed `wrap` → `flexWrap` and emits a TODO for expression values", () => {
132
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box wrap={someBool} />`
133
+ const result = applyTransform(transform, input)
134
+ expect(result).toMatchInlineSnapshot(`
135
+ "import { Box } from "@planningcenter/tapestry-react";/* TODO: tapestry-migration (styleProp): migrated from \`wrap\`; value may need manual transformation: flexWrap: someBool */
136
+ <Box />"
137
+ `)
138
+ })
139
+
140
+ it("comments out renamed `grow`/`shrink`/`basis` props with TODOs", () => {
141
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box grow={g} shrink={s} basis={b} />`
142
+ const result = applyTransform(transform, input)
143
+ expect(result).toMatchInlineSnapshot(`
144
+ "import { Box } from "@planningcenter/tapestry-react";/* TODO: tapestry-migration (styleProp): migrated from \`grow\`; value may need manual transformation: flexGrow: g */
145
+ /* TODO: tapestry-migration (styleProp): migrated from \`shrink\`; value may need manual transformation: flexShrink: s */
146
+ /* TODO: tapestry-migration (styleProp): migrated from \`basis\`; value may need manual transformation: flexBasis: b */
147
+ <Box />"
148
+ `)
149
+ })
150
+
151
+ it("comments out fanned-out `marginHorizontal` → marginLeft/marginRight with a TODO", () => {
152
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box marginHorizontal={spacing} />`
153
+ const result = applyTransform(transform, input)
154
+ expect(result).toMatchInlineSnapshot(`
155
+ "import { Box } from "@planningcenter/tapestry-react";/* TODO: tapestry-migration (styleProp): migrated from \`marginHorizontal\`; value may need manual transformation: marginLeft: spacing; marginRight: spacing */
156
+ <Box />"
157
+ `)
158
+ })
159
+
160
+ it("anchors commented-out renamed-key props inline when a real style key is present", () => {
161
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box wrap={someBool} marginTop={spacing} />`
162
+ const result = applyTransform(transform, input)
163
+ expect(result).toMatchInlineSnapshot(`
164
+ "import { Box } from "@planningcenter/tapestry-react";<Box
165
+ style={{
166
+ /* TODO: tapestry-migration (styleProp): migrated from \`wrap\`; value may need manual transformation */
167
+ // flexWrap: someBool,
168
+ marginTop: spacing
169
+ }} />"
170
+ `)
171
+ })
172
+
173
+ it("comments out fanned-out `paddingVertical` → paddingTop/paddingBottom", () => {
174
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box paddingVertical={p} />`
175
+ const result = applyTransform(transform, input)
176
+ expect(result).toMatchInlineSnapshot(`
177
+ "import { Box } from "@planningcenter/tapestry-react";/* TODO: tapestry-migration (styleProp): migrated from \`paddingVertical\`; value may need manual transformation: paddingTop: p; paddingBottom: p */
178
+ <Box />"
179
+ `)
180
+ })
181
+
182
+ it("comments out fanned-out `radius` → all four borderRadius corners", () => {
183
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box radius={r} />`
184
+ const result = applyTransform(transform, input)
185
+ expect(result).toMatchInlineSnapshot(`
186
+ "import { Box } from "@planningcenter/tapestry-react";/* TODO: tapestry-migration (styleProp): migrated from \`radius\`; value may need manual transformation: borderTopLeftRadius: r; borderTopRightRadius: r; borderBottomRightRadius: r; borderBottomLeftRadius: r */
187
+ <Box />"
188
+ `)
189
+ })
190
+
191
+ it("comments out fanned-out `radiusTop` → the two top corners only", () => {
192
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box radiusTop={rt} />`
193
+ const result = applyTransform(transform, input)
194
+ expect(result).toMatchInlineSnapshot(`
195
+ "import { Box } from "@planningcenter/tapestry-react";/* TODO: tapestry-migration (styleProp): migrated from \`radiusTop\`; value may need manual transformation: borderTopLeftRadius: rt; borderTopRightRadius: rt */
196
+ <Box />"
197
+ `)
198
+ })
199
+
200
+ // `x`, `y`, `rotate`, `scale`, `elevation` produce CSS by combining
201
+ // multiple values or by theme lookup, so we comment out a draft of the
202
+ // target CSS property under the migrated name.
203
+ it("comments out `x` → `transform` anchored to the next style key", () => {
204
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box x={dx} marginLeft={ml} />`
205
+ const result = applyTransform(transform, input)
206
+ expect(result).toMatchInlineSnapshot(`
207
+ "import { Box } from "@planningcenter/tapestry-react";<Box
208
+ style={{
209
+ /* TODO: tapestry-migration (styleProp): migrated from \`x\`; value may need manual transformation */
210
+ // transform: dx,
211
+ marginLeft: ml
212
+ }} />"
213
+ `)
214
+ })
215
+
216
+ it("commented-out-only elements emit JSX-level TODO comments and no style attribute", () => {
217
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box x={dx} y={dy} rotate={r} elevation={e} />`
218
+ const result = applyTransform(transform, input)
219
+ expect(result).toMatchInlineSnapshot(`
220
+ "import { Box } from "@planningcenter/tapestry-react";/* TODO: tapestry-migration (styleProp): migrated from \`x\`; value may need manual transformation: transform: dx */
221
+ /* TODO: tapestry-migration (styleProp): migrated from \`y\`; value may need manual transformation: transform: dy */
222
+ /* TODO: tapestry-migration (styleProp): migrated from \`rotate\`; value may need manual transformation: transform: r */
223
+ /* TODO: tapestry-migration (styleProp): migrated from \`elevation\`; value may need manual transformation: boxShadow: e */
224
+ <Box />"
225
+ `)
226
+ })
227
+
228
+ it("primitive `x` literals still go through splitStyles and produce a transform string", () => {
229
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box x={5} />`
230
+ const result = applyTransform(transform, input)
231
+ expect(result).toMatchInlineSnapshot(`
232
+ "import { Box } from "@planningcenter/tapestry-react";<Box style={{
233
+ transform: "translateX(5) "
234
+ }} />"
235
+ `)
236
+ })
237
+
238
+ it("does not emit a TODO comment for safely renamed grid aliases", () => {
239
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box column={col} />`
240
+ const result = applyTransform(transform, input)
241
+ expect(result).toMatchInlineSnapshot(`
242
+ "import { Box } from "@planningcenter/tapestry-react";<Box style={{
243
+ gridColumn: col
244
+ }} />"
245
+ `)
246
+ })
247
+
248
+ // Literal values flow through splitStyles, which expands shorthands like
249
+ // `marginHorizontal` into the underlying CSS properties. No TODO needed.
250
+ it("expands `marginHorizontal` string literals via splitStyles without a TODO", () => {
251
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box marginHorizontal="8px" />`
252
+ const result = applyTransform(transform, input)
253
+ expect(result).toMatchInlineSnapshot(`
254
+ "import { Box } from "@planningcenter/tapestry-react";<Box style={{
255
+ marginRight: "8px",
256
+ marginLeft: "8px"
257
+ }} />"
258
+ `)
259
+ })
260
+
261
+ it("expands `paddingHorizontal` numeric literals via splitStyles without a TODO", () => {
262
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box paddingHorizontal={1} />`
263
+ const result = applyTransform(transform, input)
264
+ expect(result).toMatchInlineSnapshot(`
265
+ "import { Box } from "@planningcenter/tapestry-react";<Box style={{
266
+ paddingRight: "8px",
267
+ paddingLeft: "8px"
268
+ }} />"
269
+ `)
270
+ })
271
+
272
+ it("transforms `wrap`/`grow` boolean literals via splitStyles without a TODO", () => {
273
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box wrap={true} grow={true} />`
274
+ const result = applyTransform(transform, input)
275
+ expect(result).toMatchInlineSnapshot(`
276
+ "import { Box } from "@planningcenter/tapestry-react";<Box
277
+ style={{
278
+ flexWrap: "wrap",
279
+ flexGrow: 1
280
+ }} />"
281
+ `)
282
+ })
283
+
284
+ it("expands `radius` shortcut strings via splitStyles without a TODO", () => {
285
+ const input = `import { Box } from "@planningcenter/tapestry-react";<Box radius="pill" />`
286
+ const result = applyTransform(transform, input)
287
+ expect(result).toMatchInlineSnapshot(`
288
+ "import { Box } from "@planningcenter/tapestry-react";<Box style={{
289
+ borderRadius: "10em"
290
+ }} />"
291
+ `)
292
+ })
293
+ })
294
+ })
@@ -12,7 +12,7 @@ import {
12
12
  splitStyles,
13
13
  stylePropNames,
14
14
  } from "../../../../dist/tapestry-react-shim.cjs"
15
- import { addComment } from "../../shared/actions/addComment"
15
+ import { addComment, formatComment } from "../../shared/actions/addComment"
16
16
  import { addCommentToAttribute } from "../../shared/actions/addCommentToAttribute"
17
17
  import { getAttribute } from "../../shared/actions/getAttribute"
18
18
  import { removeAttribute } from "../../shared/actions/removeAttribute"
@@ -27,6 +27,49 @@ type StylePropMapping = Record<
27
27
  }
28
28
  >
29
29
 
30
+ // JSX expression values bypass splitStyles, so we mirror its plugin behavior
31
+ // for props that bypass it. Pure key renames pass through live; everything
32
+ // else gets commented out under the css target(s) so the developer can verify
33
+ // the value before enabling.
34
+ const liveAliases: Record<string, string> = {
35
+ column: "gridColumn",
36
+ columnEnd: "gridColumnEnd",
37
+ columnStart: "gridColumnStart",
38
+ direction: "flexDirection",
39
+ row: "gridRow",
40
+ rowEnd: "gridRowEnd",
41
+ rowStart: "gridRowStart",
42
+ }
43
+
44
+ const commentedAliases: Record<string, string[]> = {
45
+ basis: ["flexBasis"],
46
+ elevation: ["boxShadow"],
47
+ grow: ["flexGrow"],
48
+ marginHorizontal: ["marginLeft", "marginRight"],
49
+ marginVertical: ["marginTop", "marginBottom"],
50
+ paddingHorizontal: ["paddingLeft", "paddingRight"],
51
+ paddingVertical: ["paddingTop", "paddingBottom"],
52
+ radius: [
53
+ "borderTopLeftRadius",
54
+ "borderTopRightRadius",
55
+ "borderBottomRightRadius",
56
+ "borderBottomLeftRadius",
57
+ ],
58
+ radiusBottom: ["borderBottomRightRadius", "borderBottomLeftRadius"],
59
+ radiusLeft: ["borderTopLeftRadius", "borderBottomLeftRadius"],
60
+ radiusRight: ["borderTopRightRadius", "borderBottomRightRadius"],
61
+ radiusTop: ["borderTopLeftRadius", "borderTopRightRadius"],
62
+ rotate: ["transform"],
63
+ scale: ["transform"],
64
+ shrink: ["flexShrink"],
65
+ strokeAlign: ["strokeAlign"],
66
+ strokeWeight: ["strokeWeight"],
67
+ uppercase: ["textTransform"],
68
+ wrap: ["flexWrap"],
69
+ x: ["transform"],
70
+ y: ["transform"],
71
+ }
72
+
30
73
  function extractPropValue(attr: JSXAttribute, j: JSCodeshift) {
31
74
  if (!attr.value) {
32
75
  return true
@@ -50,6 +93,20 @@ function extractPropValue(attr: JSXAttribute, j: JSCodeshift) {
50
93
  }
51
94
  }
52
95
 
96
+ type ManualReviewEntry = {
97
+ cssTargets: string[]
98
+ originalProp: string
99
+ originalValue: string
100
+ }
101
+
102
+ function reviewHeading(entry: ManualReviewEntry) {
103
+ return `migrated from \`${entry.originalProp}\`; value may need manual transformation`
104
+ }
105
+
106
+ function reviewBody(entry: ManualReviewEntry) {
107
+ return entry.cssTargets.map((key) => `${key}: ${entry.originalValue}`)
108
+ }
109
+
53
110
  function processKeepStyleProps({
54
111
  attributes,
55
112
  j,
@@ -62,10 +119,12 @@ function processKeepStyleProps({
62
119
  source: Collection
63
120
  }): {
64
121
  directProps: Record<string, unknown>
122
+ manualReviewEntries: ManualReviewEntry[]
65
123
  themeProps: Record<string, unknown>
66
124
  } {
67
125
  const themeProps: Record<string, unknown> = {}
68
126
  const directProps: Record<string, unknown> = {}
127
+ const manualReviewEntries: ManualReviewEntry[] = []
69
128
 
70
129
  attributes.forEach((attr) => {
71
130
  const propName = attr.name.name as string
@@ -76,7 +135,15 @@ function processKeepStyleProps({
76
135
  propValue.startsWith("{") &&
77
136
  propValue.endsWith("}")
78
137
  ) {
79
- directProps[propName] = propValue
138
+ if (propName in commentedAliases) {
139
+ manualReviewEntries.push({
140
+ cssTargets: commentedAliases[propName],
141
+ originalProp: propName,
142
+ originalValue: propValue.slice(1, -1),
143
+ })
144
+ } else {
145
+ directProps[liveAliases[propName] ?? propName] = propValue
146
+ }
80
147
  } else {
81
148
  themeProps[propName] = propValue
82
149
  }
@@ -84,7 +151,7 @@ function processKeepStyleProps({
84
151
  removeAttribute(propName, { element, j, source })
85
152
  })
86
153
 
87
- return { directProps, themeProps }
154
+ return { directProps, manualReviewEntries, themeProps }
88
155
  }
89
156
 
90
157
  function processRemoveStyleProps({
@@ -160,20 +227,36 @@ function processStylePropMappings({
160
227
  return styleProps
161
228
  }
162
229
 
230
+ function buildManualReviewComments(
231
+ entries: ManualReviewEntry[],
232
+ j: JSCodeshift
233
+ ) {
234
+ return entries.flatMap((entry) => [
235
+ j.commentBlock(
236
+ formatComment(reviewHeading(entry), "styleProp"),
237
+ true,
238
+ false
239
+ ),
240
+ ...reviewBody(entry).map((line) => j.commentLine(` ${line},`)),
241
+ ])
242
+ }
243
+
163
244
  function applyStylesToComponent({
164
245
  j,
165
246
  element,
166
247
  styles,
248
+ manualReviewEntries = [],
167
249
  }: {
168
250
  element: JSXElement
169
251
  j: JSCodeshift
252
+ manualReviewEntries?: ManualReviewEntry[]
170
253
  source: Collection
171
254
  styles: Record<string, unknown>
172
255
  }) {
173
256
  const styleAttr = getAttribute({ element, name: "style" })
174
257
  const styleValue = j.jsxExpressionContainer(
175
258
  j.objectExpression(
176
- Object.entries(styles).map(([key, value]) => {
259
+ Object.entries(styles).map(([key, value], index) => {
177
260
  let valueNode
178
261
  if (
179
262
  typeof value === "string" &&
@@ -198,7 +281,11 @@ function applyStylesToComponent({
198
281
  valueNode = j.literal(value as string | number | boolean | null)
199
282
  }
200
283
 
201
- return j.objectProperty(j.identifier(key), valueNode)
284
+ const property = j.objectProperty(j.identifier(key), valueNode)
285
+ if (index === 0 && manualReviewEntries.length > 0) {
286
+ property.comments = buildManualReviewComments(manualReviewEntries, j)
287
+ }
288
+ return property
202
289
  })
203
290
  )
204
291
  )
@@ -286,6 +373,7 @@ export function stylePropTransformFactory(config: {
286
373
  const allAttributes = element.openingElement.attributes || []
287
374
  let allStyleProps: Record<string, unknown> = {}
288
375
  let directStyleProps: Record<string, unknown> = {}
376
+ const manualReviewEntries: ManualReviewEntry[] = []
289
377
  const attributes = allAttributes.filter((attr) => {
290
378
  if (attr.type !== "JSXAttribute") return false
291
379
  const name = attr.name?.name as string
@@ -319,6 +407,7 @@ export function stylePropTransformFactory(config: {
319
407
  ...directStyleProps,
320
408
  ...keepResult.directProps,
321
409
  }
410
+ manualReviewEntries.push(...keepResult.manualReviewEntries)
322
411
 
323
412
  allStyleProps = {
324
413
  ...allStyleProps,
@@ -343,7 +432,8 @@ export function stylePropTransformFactory(config: {
343
432
 
344
433
  if (
345
434
  Object.keys(allStyleProps).length > 0 ||
346
- Object.keys(directStyleProps).length > 0
435
+ Object.keys(directStyleProps).length > 0 ||
436
+ manualReviewEntries.length > 0
347
437
  ) {
348
438
  try {
349
439
  let styles: Record<string, unknown> = {}
@@ -361,7 +451,13 @@ export function stylePropTransformFactory(config: {
361
451
  if (options.verbose) console.log("Final generated styles:", styles)
362
452
 
363
453
  if (Object.keys(styles).length > 0) {
364
- applyStylesToComponent({ element, j, source, styles })
454
+ applyStylesToComponent({
455
+ element,
456
+ j,
457
+ manualReviewEntries,
458
+ source,
459
+ styles,
460
+ })
365
461
 
366
462
  if (options.verbose) {
367
463
  const styleAttr = getAttribute({ element, name: "style" })
@@ -381,6 +477,18 @@ export function stylePropTransformFactory(config: {
381
477
  }
382
478
  }
383
479
  }
480
+ } else if (manualReviewEntries.length > 0) {
481
+ // No anchoring property in the style block — emit each manual-review
482
+ // entry as a JSX-level TODO comment instead.
483
+ for (const entry of manualReviewEntries) {
484
+ addComment({
485
+ element,
486
+ j,
487
+ scope: "styleProp",
488
+ source,
489
+ text: `${reviewHeading(entry)}: ${reviewBody(entry).join("; ")}`,
490
+ })
491
+ }
384
492
  }
385
493
  } catch (error) {
386
494
  console.log("Error processing style props:", error)