@planningcenter/tapestry-migration-cli 3.1.0-rc.6 → 3.1.0-rc.7

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 (52) hide show
  1. package/package.json +3 -3
  2. package/src/components/button/transforms/convertStyleProps.test.ts +97 -0
  3. package/src/components/button/transforms/removeTypeButton.test.ts +0 -1
  4. package/src/components/checkbox/transforms/moveCheckboxImport.test.ts +3 -0
  5. package/src/components/input/index.ts +66 -0
  6. package/src/components/input/transformableInput.ts +8 -0
  7. package/src/components/input/transforms/auditSpreadProps.test.ts +192 -0
  8. package/src/components/input/transforms/auditSpreadProps.ts +26 -0
  9. package/src/components/input/transforms/autoWidthTransform.test.ts +172 -0
  10. package/src/components/input/transforms/autoWidthTransform.ts +41 -0
  11. package/src/components/input/transforms/convertStyleProps.test.ts +128 -0
  12. package/src/components/input/transforms/convertStyleProps.ts +12 -0
  13. package/src/components/input/transforms/highlightOnInteractionToSelectTextOnFocus.test.ts +186 -0
  14. package/src/components/input/transforms/highlightOnInteractionToSelectTextOnFocus.ts +27 -0
  15. package/src/components/input/transforms/inputLabelToLabelProp.test.ts +319 -0
  16. package/src/components/input/transforms/inputLabelToLabelProp.ts +203 -0
  17. package/src/components/input/transforms/mergeFieldIntoInput.test.ts +391 -0
  18. package/src/components/input/transforms/mergeFieldIntoInput.ts +213 -0
  19. package/src/components/input/transforms/mergeInputLabel.test.ts +458 -0
  20. package/src/components/input/transforms/mergeInputLabel.ts +204 -0
  21. package/src/components/input/transforms/moveInputImport.test.ts +166 -0
  22. package/src/components/input/transforms/moveInputImport.ts +14 -0
  23. package/src/components/input/transforms/numberFieldAddTypeNumber.test.ts +92 -0
  24. package/src/components/input/transforms/numberFieldAddTypeNumber.ts +14 -0
  25. package/src/components/input/transforms/numberFieldRenameToInput.test.ts +126 -0
  26. package/src/components/input/transforms/numberFieldRenameToInput.ts +9 -0
  27. package/src/components/input/transforms/removeAsInput.test.ts +139 -0
  28. package/src/components/input/transforms/removeAsInput.ts +20 -0
  29. package/src/components/input/transforms/removeDuplicateKeys.test.ts +302 -0
  30. package/src/components/input/transforms/removeDuplicateKeys.ts +10 -0
  31. package/src/components/input/transforms/removeInputBox.test.ts +352 -0
  32. package/src/components/input/transforms/removeInputBox.ts +109 -0
  33. package/src/components/input/transforms/removeRedundantAriaLabel.test.ts +128 -0
  34. package/src/components/input/transforms/removeRedundantAriaLabel.ts +21 -0
  35. package/src/components/input/transforms/removeTypeText.test.ts +160 -0
  36. package/src/components/input/transforms/removeTypeText.ts +18 -0
  37. package/src/components/input/transforms/sizeMapping.test.ts +198 -0
  38. package/src/components/input/transforms/sizeMapping.ts +17 -0
  39. package/src/components/input/transforms/skipRenderSideProps.test.ts +236 -0
  40. package/src/components/input/transforms/skipRenderSideProps.ts +27 -0
  41. package/src/components/input/transforms/stateToInvalid.test.ts +208 -0
  42. package/src/components/input/transforms/stateToInvalid.ts +59 -0
  43. package/src/components/input/transforms/stateToInvalidTernary.test.ts +159 -0
  44. package/src/components/input/transforms/stateToInvalidTernary.ts +13 -0
  45. package/src/components/input/transforms/unsupportedProps.test.ts +566 -0
  46. package/src/components/input/transforms/unsupportedProps.ts +84 -0
  47. package/src/components/link/transforms/reviewStyles.test.ts +0 -1
  48. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +52 -0
  49. package/src/components/shared/transformFactories/helpers/manageImports.ts +14 -12
  50. package/src/components/shared/transformFactories/sizeMappingFactory.ts +9 -2
  51. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +54 -16
  52. package/src/components/shared/transformFactories/ternaryConditionalToPropFactory.ts +65 -0
@@ -0,0 +1,566 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./unsupportedProps"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(source: string): string {
9
+ const fileInfo = { path: "test.tsx", source }
10
+ const result = transform(
11
+ fileInfo,
12
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
13
+ {}
14
+ ) as string | null
15
+ return result || source
16
+ }
17
+
18
+ describe("unsupportedProps transform", () => {
19
+ describe("removed InputBox props", () => {
20
+ // renderLeft/renderRight are handled by skipRenderSideProps — unsupportedProps skips those elements
21
+ it("should return null for Input with renderLeft (handled by skipRenderSideProps)", () => {
22
+ const input = `
23
+ import { Input } from "@planningcenter/tapestry-react"
24
+
25
+ function Test() {
26
+ return <Input renderLeft={<Icon />} label="Name" />
27
+ }
28
+ `.trim()
29
+
30
+ const result = transform(
31
+ { path: "test.tsx", source: input },
32
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
33
+ {}
34
+ )
35
+ expect(result).toBeNull()
36
+ })
37
+
38
+ it("should return null for Input with renderRight (handled by skipRenderSideProps)", () => {
39
+ const input = `
40
+ import { Input } from "@planningcenter/tapestry-react"
41
+
42
+ function Test() {
43
+ return <Input renderRight={<Icon />} label="Name" />
44
+ }
45
+ `.trim()
46
+
47
+ const result = transform(
48
+ { path: "test.tsx", source: input },
49
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
50
+ {}
51
+ )
52
+ expect(result).toBeNull()
53
+ })
54
+
55
+ it("should flag isLoading", () => {
56
+ const input = `
57
+ import { Input } from "@planningcenter/tapestry-react"
58
+
59
+ function Test() {
60
+ return <Input isLoading label="Name" />
61
+ }
62
+ `.trim()
63
+
64
+ const result = applyTransform(input)
65
+ expect(result).toContain("TODO: tapestry-migration (isLoading)")
66
+ })
67
+
68
+ it("should flag onClear", () => {
69
+ const input = `
70
+ import { Input } from "@planningcenter/tapestry-react"
71
+
72
+ function Test() {
73
+ return <Input onClear={handleClear} label="Name" />
74
+ }
75
+ `.trim()
76
+
77
+ const result = applyTransform(input)
78
+ expect(result).toContain("TODO: tapestry-migration (onClear)")
79
+ })
80
+
81
+ it("should flag naked", () => {
82
+ const input = `
83
+ import { Input } from "@planningcenter/tapestry-react"
84
+
85
+ function Test() {
86
+ return <Input naked label="Name" />
87
+ }
88
+ `.trim()
89
+
90
+ const result = applyTransform(input)
91
+ expect(result).toContain("TODO: tapestry-migration (naked)")
92
+ })
93
+
94
+ it("should flag subdued", () => {
95
+ const input = `
96
+ import { Input } from "@planningcenter/tapestry-react"
97
+
98
+ function Test() {
99
+ return <Input subdued label="Name" />
100
+ }
101
+ `.trim()
102
+
103
+ const result = applyTransform(input)
104
+ expect(result).toContain("TODO: tapestry-migration (subdued)")
105
+ })
106
+
107
+ it("should flag secondaryStroke", () => {
108
+ const input = `
109
+ import { Input } from "@planningcenter/tapestry-react"
110
+
111
+ function Test() {
112
+ return <Input secondaryStroke label="Name" />
113
+ }
114
+ `.trim()
115
+
116
+ const result = applyTransform(input)
117
+ expect(result).toContain("TODO: tapestry-migration (secondaryStroke)")
118
+ })
119
+
120
+ it("should flag evenPadding", () => {
121
+ const input = `
122
+ import { Input } from "@planningcenter/tapestry-react"
123
+
124
+ function Test() {
125
+ return <Input evenPadding label="Name" />
126
+ }
127
+ `.trim()
128
+
129
+ const result = applyTransform(input)
130
+ expect(result).toContain("TODO: tapestry-migration (evenPadding)")
131
+ })
132
+
133
+ it("should flag focus style object", () => {
134
+ const input = `
135
+ import { Input } from "@planningcenter/tapestry-react"
136
+
137
+ function Test() {
138
+ return <Input focus={{ strokeWeight: 2 }} label="Name" />
139
+ }
140
+ `.trim()
141
+
142
+ const result = applyTransform(input)
143
+ expect(result).toContain("TODO: tapestry-migration (focus)")
144
+ })
145
+
146
+ it("should flag hover style object", () => {
147
+ const input = `
148
+ import { Input } from "@planningcenter/tapestry-react"
149
+
150
+ function Test() {
151
+ return <Input hover={{ strokeWeight: 2 }} label="Name" />
152
+ }
153
+ `.trim()
154
+
155
+ const result = applyTransform(input)
156
+ expect(result).toContain("TODO: tapestry-migration (hover)")
157
+ })
158
+
159
+ it("should flag autoFocus", () => {
160
+ const input = `
161
+ import { Input } from "@planningcenter/tapestry-react"
162
+
163
+ function Test() {
164
+ return <Input autoFocus label="Name" />
165
+ }
166
+ `.trim()
167
+
168
+ const result = applyTransform(input)
169
+ expect(result).toContain("TODO: tapestry-migration (autoFocus)")
170
+ })
171
+
172
+ it("should flag readOnlyBackgroundColor", () => {
173
+ const input = `
174
+ import { Input } from "@planningcenter/tapestry-react"
175
+
176
+ function Test() {
177
+ return <Input readOnlyBackgroundColor="gray" label="Name" />
178
+ }
179
+ `.trim()
180
+
181
+ const result = applyTransform(input)
182
+ expect(result).toContain(
183
+ "TODO: tapestry-migration (readOnlyBackgroundColor)"
184
+ )
185
+ })
186
+
187
+ it("should add extra message for css prop", () => {
188
+ const input = `
189
+ import { Input } from "@planningcenter/tapestry-react"
190
+
191
+ function Test() {
192
+ return <Input css={{ color: "red" }} label="Name" />
193
+ }
194
+ `.trim()
195
+
196
+ const result = applyTransform(input)
197
+ expect(result).toContain("TODO: tapestry-migration (css)")
198
+ expect(result).toContain(
199
+ "CSS prop is not supported. Use className or style prop instead."
200
+ )
201
+ })
202
+ })
203
+
204
+ describe("supported props — no comment added", () => {
205
+ it("should not flag supported Input props", () => {
206
+ const input = `
207
+ import { Input } from "@planningcenter/tapestry-react"
208
+
209
+ function Test() {
210
+ return (
211
+ <Input
212
+ label="Name"
213
+ description="Helper text"
214
+ invalid={false}
215
+ disabled
216
+ readOnly
217
+ required
218
+ placeholder="Enter name"
219
+ type="email"
220
+ autoWidth
221
+ selectTextOnFocus
222
+ size="md"
223
+ value=""
224
+ onChange={() => {}}
225
+ />
226
+ )
227
+ }
228
+ `.trim()
229
+
230
+ const result = applyTransform(input)
231
+ expect(result).not.toContain("TODO: tapestry-migration")
232
+ })
233
+
234
+ it("should not flag standard HTML attributes", () => {
235
+ const input = `
236
+ import { Input } from "@planningcenter/tapestry-react"
237
+
238
+ function Test() {
239
+ return (
240
+ <Input
241
+ label="Name"
242
+ id="name-input"
243
+ name="name"
244
+ className="custom"
245
+ style={{ marginTop: 8 }}
246
+ tabIndex={0}
247
+ autoComplete="off"
248
+ defaultValue="foo"
249
+ min={0}
250
+ max={100}
251
+ minLength={2}
252
+ maxLength={50}
253
+ onFocus={() => {}}
254
+ onBlur={() => {}}
255
+ onKeyDown={() => {}}
256
+ onKeyUp={() => {}}
257
+ />
258
+ )
259
+ }
260
+ `.trim()
261
+
262
+ const result = applyTransform(input)
263
+ expect(result).not.toContain("TODO: tapestry-migration")
264
+ })
265
+
266
+ it("should not flag step on a numeric input", () => {
267
+ const input = `
268
+ import { Input } from "@planningcenter/tapestry-react"
269
+
270
+ function Test() {
271
+ return <Input type="number" label="Amount" min={0} max={100} step={1} />
272
+ }
273
+ `.trim()
274
+
275
+ const result = applyTransform(input)
276
+ expect(result).not.toContain("TODO: tapestry-migration")
277
+ })
278
+
279
+ it("should flag step on type='range' (not an accepted Input type)", () => {
280
+ const input = `
281
+ import { Input } from "@planningcenter/tapestry-react"
282
+
283
+ function Test() {
284
+ return <Input type="range" step={0.5} label="Amount" />
285
+ }
286
+ `.trim()
287
+
288
+ const result = applyTransform(input)
289
+ expect(result).toContain("TODO: tapestry-migration (step)")
290
+ })
291
+
292
+ it("should flag step on type={'range'} expression container (not an accepted Input type)", () => {
293
+ const input = `
294
+ import { Input } from "@planningcenter/tapestry-react"
295
+
296
+ function Test() {
297
+ return <Input type={"range"} step={0.5} label="Amount" />
298
+ }
299
+ `.trim()
300
+
301
+ const result = applyTransform(input)
302
+ expect(result).toContain("TODO: tapestry-migration (step)")
303
+ expect(result).toContain("TODO: tapestry-migration (type)")
304
+ })
305
+
306
+ it("should be permissive with type-specific props when type is dynamic", () => {
307
+ const input = `
308
+ import { Input } from "@planningcenter/tapestry-react"
309
+
310
+ function Test() {
311
+ return <Input type={inputType} step={0.5} label="Amount" />
312
+ }
313
+ `.trim()
314
+
315
+ const result = applyTransform(input)
316
+ expect(result).not.toContain("TODO: tapestry-migration (step)")
317
+ expect(result).not.toContain("TODO: tapestry-migration (type)")
318
+ })
319
+
320
+ it("should flag accept on type='file' (not an accepted Input type)", () => {
321
+ const input = `
322
+ import { Input } from "@planningcenter/tapestry-react"
323
+
324
+ function Test() {
325
+ return <Input type="file" accept=".pdf" label="File" />
326
+ }
327
+ `.trim()
328
+
329
+ const result = applyTransform(input)
330
+ expect(result).toContain("TODO: tapestry-migration (accept)")
331
+ })
332
+
333
+ it("should not flag pattern on type='text'", () => {
334
+ const input = `
335
+ import { Input } from "@planningcenter/tapestry-react"
336
+
337
+ function Test() {
338
+ return <Input type="text" pattern="[0-9]+" label="Number" />
339
+ }
340
+ `.trim()
341
+
342
+ const result = applyTransform(input)
343
+ expect(result).not.toContain("TODO: tapestry-migration")
344
+ })
345
+
346
+ it("should not flag multiple on type='email'", () => {
347
+ const input = `
348
+ import { Input } from "@planningcenter/tapestry-react"
349
+
350
+ function Test() {
351
+ return <Input type="email" multiple label="Emails" />
352
+ }
353
+ `.trim()
354
+
355
+ const result = applyTransform(input)
356
+ expect(result).not.toContain("TODO: tapestry-migration")
357
+ })
358
+
359
+ it("should not flag step when type is dynamic", () => {
360
+ const input = `
361
+ import { Input } from "@planningcenter/tapestry-react"
362
+
363
+ function Test() {
364
+ return <Input type={inputType} step={1} label="Amount" />
365
+ }
366
+ `.trim()
367
+
368
+ const result = applyTransform(input)
369
+ expect(result).not.toContain("TODO: tapestry-migration")
370
+ })
371
+
372
+ it("should flag step without a type attr", () => {
373
+ const input = `
374
+ import { Input } from "@planningcenter/tapestry-react"
375
+
376
+ function Test() {
377
+ return <Input step={1} label="Amount" />
378
+ }
379
+ `.trim()
380
+
381
+ const result = applyTransform(input)
382
+ expect(result).toContain("TODO: tapestry-migration (step)")
383
+ })
384
+
385
+ it("should flag step on type='text'", () => {
386
+ const input = `
387
+ import { Input } from "@planningcenter/tapestry-react"
388
+
389
+ function Test() {
390
+ return <Input type="text" step={1} label="Amount" />
391
+ }
392
+ `.trim()
393
+
394
+ const result = applyTransform(input)
395
+ expect(result).toContain("TODO: tapestry-migration (step)")
396
+ })
397
+
398
+ it("should flag accept without a type attr", () => {
399
+ const input = `
400
+ import { Input } from "@planningcenter/tapestry-react"
401
+
402
+ function Test() {
403
+ return <Input accept=".pdf" label="File" />
404
+ }
405
+ `.trim()
406
+
407
+ const result = applyTransform(input)
408
+ expect(result).toContain("TODO: tapestry-migration (accept)")
409
+ })
410
+
411
+ it("should not flag aria-* props", () => {
412
+ const input = `
413
+ import { Input } from "@planningcenter/tapestry-react"
414
+
415
+ function Test() {
416
+ return (
417
+ <Input
418
+ aria-label="Name"
419
+ aria-describedby="desc"
420
+ aria-labelledby="label"
421
+ label="Name"
422
+ />
423
+ )
424
+ }
425
+ `.trim()
426
+
427
+ const result = applyTransform(input)
428
+ expect(result).not.toContain("TODO: tapestry-migration")
429
+ })
430
+
431
+ it("should not flag data-* props", () => {
432
+ const input = `
433
+ import { Input } from "@planningcenter/tapestry-react"
434
+
435
+ function Test() {
436
+ return (
437
+ <Input
438
+ data-testid="name-input"
439
+ data-cy="name-field"
440
+ label="Name"
441
+ />
442
+ )
443
+ }
444
+ `.trim()
445
+
446
+ const result = applyTransform(input)
447
+ expect(result).not.toContain("TODO: tapestry-migration")
448
+ })
449
+ })
450
+
451
+ describe("unsupported type values", () => {
452
+ it.each([
453
+ "range",
454
+ "date",
455
+ "time",
456
+ "datetime-local",
457
+ "week",
458
+ "month",
459
+ "file",
460
+ "color",
461
+ "checkbox",
462
+ "radio",
463
+ "hidden",
464
+ "image",
465
+ "submit",
466
+ "reset",
467
+ "button",
468
+ ])("should flag type='%s' as unsupported", (type) => {
469
+ const input = `
470
+ import { Input } from "@planningcenter/tapestry-react"
471
+
472
+ function Test() {
473
+ return <Input type="${type}" label="Field" />
474
+ }
475
+ `.trim()
476
+
477
+ const result = applyTransform(input)
478
+ expect(result).toContain("TODO: tapestry-migration (type)")
479
+ expect(result).toContain("is not a supported Input type")
480
+ })
481
+
482
+ it("should not flag accepted type values", () => {
483
+ const types = [
484
+ "email",
485
+ "number",
486
+ "password",
487
+ "search",
488
+ "tel",
489
+ "text",
490
+ "url",
491
+ ]
492
+ for (const type of types) {
493
+ const input = `
494
+ import { Input } from "@planningcenter/tapestry-react"
495
+
496
+ function Test() {
497
+ return <Input type="${type}" label="Field" />
498
+ }
499
+ `.trim()
500
+
501
+ const result = applyTransform(input)
502
+ expect(result).not.toContain("TODO: tapestry-migration")
503
+ }
504
+ })
505
+
506
+ it("should not flag dynamic type values", () => {
507
+ const input = `
508
+ import { Input } from "@planningcenter/tapestry-react"
509
+
510
+ function Test() {
511
+ return <Input type={inputType} label="Field" />
512
+ }
513
+ `.trim()
514
+
515
+ const result = applyTransform(input)
516
+ expect(result).not.toContain("TODO: tapestry-migration")
517
+ })
518
+ })
519
+
520
+ describe("edge cases", () => {
521
+ it("should not affect Input with only supported props", () => {
522
+ const input = `
523
+ import { Input } from "@planningcenter/tapestry-react"
524
+
525
+ function Test() {
526
+ return <Input label="Name" />
527
+ }
528
+ `.trim()
529
+
530
+ const result = applyTransform(input)
531
+ expect(result).toBe(input)
532
+ })
533
+
534
+ it("should not affect other components", () => {
535
+ const input = `
536
+ import { Input, Button } from "@planningcenter/tapestry-react"
537
+
538
+ function Test() {
539
+ return (
540
+ <div>
541
+ <Button naked>Naked button</Button>
542
+ <Input label="Name" />
543
+ </div>
544
+ )
545
+ }
546
+ `.trim()
547
+
548
+ const result = applyTransform(input)
549
+ expect(result).not.toContain("TODO: tapestry-migration")
550
+ expect(result).toContain("naked")
551
+ })
552
+
553
+ it("should not transform if not imported from @planningcenter/tapestry-react", () => {
554
+ const input = `
555
+ import { Input } from "other-library"
556
+
557
+ function Test() {
558
+ return <Input renderLeft={<Icon />} label="Name" />
559
+ }
560
+ `.trim()
561
+
562
+ const result = applyTransform(input)
563
+ expect(result).toBe(input)
564
+ })
565
+ })
566
+ })
@@ -0,0 +1,84 @@
1
+ import { JSXAttribute, Transform } from "jscodeshift"
2
+
3
+ import { addCommentToUnsupportedProps } from "../../shared/actions/addCommentToUnsupportedProps"
4
+ import { getAttribute } from "../../shared/actions/getAttribute"
5
+ import { getAttributeValue } from "../../shared/actions/getAttributeValue"
6
+ import {
7
+ ACCEPTED_INPUT_TYPES,
8
+ INPUT_SUPPORTED_PROPS,
9
+ TYPE_SPECIFIC_PROPS,
10
+ } from "../../shared/helpers/unsupportedPropsHelpers"
11
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
12
+ import { transformableInput } from "../transformableInput"
13
+
14
+ const transform: Transform = attributeTransformFactory({
15
+ condition: transformableInput,
16
+ targetComponent: "Input",
17
+ targetPackage: "@planningcenter/tapestry-react",
18
+ transform: (element, { j }) => {
19
+ const attrs = element.openingElement.attributes || []
20
+
21
+ const typeAttr = getAttribute({ element, name: "type" })
22
+
23
+ const typeValue = typeAttr
24
+ ? getAttributeValue({ attribute: typeAttr, j })
25
+ : null
26
+
27
+ // getAttributeValue resolves both type="text" and type={"text"} to "text",
28
+ // but returns source-code representations for truly dynamic expressions
29
+ // (e.g. type={someVar} → "{someVar}"). We need to distinguish static
30
+ // string values from dynamic ones so we can flag unsupported types without
31
+ // false-positiving on variables, and be permissive with type-specific props
32
+ // when the type can't be determined statically.
33
+ const isStaticType =
34
+ typeAttr?.value?.type === "StringLiteral" ||
35
+ (typeAttr?.value?.type === "JSXExpressionContainer" &&
36
+ typeAttr.value.expression.type === "StringLiteral")
37
+
38
+ const hasUnsupportedTypeValue =
39
+ isStaticType &&
40
+ typeValue !== null &&
41
+ !ACCEPTED_INPUT_TYPES.includes(
42
+ typeValue as (typeof ACCEPTED_INPUT_TYPES)[number]
43
+ )
44
+
45
+ const typeAllowedProps: string[] =
46
+ typeAttr === undefined
47
+ ? []
48
+ : isStaticType
49
+ ? (TYPE_SPECIFIC_PROPS[typeValue!] ?? [])
50
+ : Object.values(TYPE_SPECIFIC_PROPS).flat()
51
+
52
+ const UNSUPPORTED_PROPS = attrs
53
+ .filter(
54
+ (attr) =>
55
+ attr.type === "JSXAttribute" &&
56
+ !INPUT_SUPPORTED_PROPS.includes(attr.name.name as string) &&
57
+ !typeAllowedProps.includes(attr.name.name as string) &&
58
+ !(attr.name.name as string).startsWith("aria-") &&
59
+ !(attr.name.name as string).startsWith("data-")
60
+ )
61
+ .map((attr) => (attr as JSXAttribute).name.name as string)
62
+
63
+ if (hasUnsupportedTypeValue) {
64
+ UNSUPPORTED_PROPS.push("type")
65
+ }
66
+
67
+ return addCommentToUnsupportedProps({
68
+ element,
69
+ j,
70
+ messageSuffix: (prop) => {
71
+ if (prop === "css") {
72
+ return "\n * CSS prop is not supported. Use className or style prop instead.\n"
73
+ }
74
+ if (prop === "type") {
75
+ return `\n * '${typeValue}' is not a supported Input type. Accepted types: ${ACCEPTED_INPUT_TYPES.join(", ")}.\n`
76
+ }
77
+ return ""
78
+ },
79
+ props: UNSUPPORTED_PROPS,
80
+ })
81
+ },
82
+ })
83
+
84
+ export default transform
@@ -5,7 +5,6 @@ import transform from "./reviewStyles"
5
5
 
6
6
  const j = jscodeshift.withParser("tsx")
7
7
 
8
- // Helper to run transform and get result
9
8
  function applyTransform(source: string): string | null {
10
9
  const fileInfo = { path: "test.tsx", source }
11
10
  const api = {
@@ -49,3 +49,55 @@ export const CHECKBOX_RADIO_SUPPORTED_PROPS = [
49
49
  ...CHECKBOX_RADIO_SHARED_PROPS,
50
50
  ...STYLE_PROP_NAMES_WITHOUT_CSS,
51
51
  ]
52
+
53
+ export const INPUT_SPECIFIC_PROPS = [
54
+ "autoComplete",
55
+ "autoWidth",
56
+ "defaultValue",
57
+ "description",
58
+ "disabled",
59
+ "form",
60
+ "hideLabel",
61
+ "invalid",
62
+ "max",
63
+ "maxLength",
64
+ "min",
65
+ "minLength",
66
+ "name",
67
+ "onChange",
68
+ "placeholder",
69
+ "readOnly",
70
+ "required",
71
+ "selectTextOnFocus",
72
+ "spellCheck",
73
+ "type",
74
+ "value",
75
+ ]
76
+
77
+ export const ACCEPTED_INPUT_TYPES = [
78
+ "email",
79
+ "number",
80
+ "password",
81
+ "search",
82
+ "tel",
83
+ "text",
84
+ "url",
85
+ ] as const
86
+
87
+ // Per https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#attributes
88
+ // Only covers the types accepted by the Tapestry Input component.
89
+ export const TYPE_SPECIFIC_PROPS: Record<string, string[]> = {
90
+ email: ["dirname", "list", "multiple", "pattern"],
91
+ number: ["inputMode", "list", "step"],
92
+ password: ["pattern"],
93
+ search: ["dirname", "inputMode", "list", "pattern"],
94
+ tel: ["dirname", "inputMode", "list", "pattern"],
95
+ text: ["dirname", "inputMode", "list", "pattern"],
96
+ url: ["dirname", "inputMode", "list", "pattern"],
97
+ }
98
+
99
+ export const INPUT_SUPPORTED_PROPS = [
100
+ ...COMMON_PROPS,
101
+ ...INPUT_SPECIFIC_PROPS,
102
+ ...STYLE_PROP_NAMES_WITHOUT_CSS,
103
+ ]