@toss/tds-mobile-migration 2.1.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 (35) hide show
  1. package/cli.js +2 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +43 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/tds-v2/codemod/commands/badge-props-rename.cjs +81 -0
  7. package/dist/tds-v2/codemod/commands/badge-size-rename.cjs +89 -0
  8. package/dist/tds-v2/codemod/commands/boardrow-rightarrow-rename.cjs +89 -0
  9. package/dist/tds-v2/codemod/commands/bottomcta-component-rename.cjs +70 -0
  10. package/dist/tds-v2/codemod/commands/button-export-rename.cjs +120 -0
  11. package/dist/tds-v2/codemod/commands/button-props-rename.cjs +205 -0
  12. package/dist/tds-v2/codemod/commands/button-size-rename.cjs +209 -0
  13. package/dist/tds-v2/codemod/commands/iconbutton-label-to-aria-label.cjs +84 -0
  14. package/dist/tds-v2/codemod/commands/listrow-verticalpadding-rename.cjs +101 -0
  15. package/dist/tds-v2/codemod/commands/textbutton-typography-to-size.cjs +229 -0
  16. package/dist/tds-v2/codemod/commands/top-subtitle-props-rename.cjs +66 -0
  17. package/dist/tds-v2/index.d.ts +2 -0
  18. package/dist/tds-v2/index.d.ts.map +1 -0
  19. package/dist/tds-v2/index.js +2 -0
  20. package/dist/tds-v2/index.js.map +1 -0
  21. package/dist/tds-v2/runCodemod.d.ts +5 -0
  22. package/dist/tds-v2/runCodemod.d.ts.map +1 -0
  23. package/dist/tds-v2/runCodemod.js +37 -0
  24. package/dist/tds-v2/runCodemod.js.map +1 -0
  25. package/dist/tsconfig.tsbuildinfo +1 -0
  26. package/dist/v2/codemod/commands/provider.cjs +97 -0
  27. package/dist/v2/index.d.ts +2 -0
  28. package/dist/v2/index.d.ts.map +1 -0
  29. package/dist/v2/index.js +2 -0
  30. package/dist/v2/index.js.map +1 -0
  31. package/dist/v2/runCodemod.d.ts +5 -0
  32. package/dist/v2/runCodemod.d.ts.map +1 -0
  33. package/dist/v2/runCodemod.js +37 -0
  34. package/dist/v2/runCodemod.js.map +1 -0
  35. package/package.json +23 -0
@@ -0,0 +1,205 @@
1
+ /**
2
+ * @type {import('jscodeshift').Transform}
3
+ */
4
+ const transform = (fileInfo, { jscodeshift: j }, options) => {
5
+ const root = j(fileInfo.source);
6
+
7
+ // package name을 options에서 가져와요 (기본값: @tds/mobile)
8
+ const packageName = options['package-name'] || '@tds/mobile';
9
+
10
+ // packageName에서 import된 컴포넌트들을 추적하는 Set
11
+ const tdsImportedComponents = new Set();
12
+
13
+ // packageName import 구문들을 찾아서 컴포넌트 이름들을 수집해요
14
+ root
15
+ .find(j.ImportDeclaration, {
16
+ source: { value: packageName },
17
+ })
18
+ .forEach(path => {
19
+ path.value.specifiers.forEach(specifier => {
20
+ if (specifier.type === 'ImportSpecifier') {
21
+ tdsImportedComponents.add(specifier.local.name);
22
+ }
23
+ });
24
+ });
25
+
26
+ // Button 관련 컴포넌트 이름들을 관리하는 Set
27
+ const buttonComponentNames = new Set(['Button', 'ResultButton', 'CTAButton', 'FixedBottomCTA', 'BottomCTA']);
28
+
29
+ // Button 관련 컴포넌트가 packageName에서 하나도 import되지 않았다면 변환하지 않아요
30
+ const hasAnyButtonComponent = Array.from(buttonComponentNames).some(componentName =>
31
+ tdsImportedComponents.has(componentName)
32
+ );
33
+
34
+ if (!hasAnyButtonComponent) {
35
+ return fileInfo.source;
36
+ }
37
+
38
+ // styled-components로 확장된 Button 기반 컴포넌트들을 찾아서 Set에 추가
39
+ findStyledButtonComponents(j, root, buttonComponentNames);
40
+
41
+ // prop 이름 매핑 규칙
42
+ const propNameMap = {
43
+ type: 'color',
44
+ htmlType: 'type',
45
+ style: 'variant',
46
+ htmlStyle: 'style',
47
+ };
48
+
49
+ // JSX 컴포넌트의 prop 이름 변경 공통 함수
50
+ function transformPropNames(openingElement) {
51
+ const attributes = openingElement.attributes;
52
+
53
+ // 모든 속성을 순회하면서 매핑된 prop 이름 변경해요
54
+ attributes.forEach(attr => {
55
+ if (attr.type === 'JSXAttribute' && propNameMap[attr.name.name]) {
56
+ attr.name.name = propNameMap[attr.name.name];
57
+ }
58
+ });
59
+ }
60
+
61
+ // 컴포넌트 이름이 Button 관련 컴포넌트인지 확인하는 함수
62
+ function isButtonComponent(elementName) {
63
+ if (elementName.type === 'JSXIdentifier') {
64
+ const hasButtonName = buttonComponentNames.has(elementName.name);
65
+ const hasTdsImport = tdsImportedComponents.has(elementName.name);
66
+
67
+ // 기본 Button 컴포넌트들은 import 체크 필요
68
+ const baseButtonComponents = ['Button', 'ResultButton', 'CTAButton', 'FixedBottomCTA', 'BottomCTA'];
69
+ const isBaseComponent = baseButtonComponents.includes(elementName.name);
70
+
71
+ // 기본 컴포넌트는 import 체크 필요, styled 컴포넌트는 Set에만 있으면 됨
72
+ return hasButtonName && (isBaseComponent ? hasTdsImport : true);
73
+ }
74
+ return false;
75
+ }
76
+
77
+ // as prop으로 Button 컴포넌트를 사용하는지 확인하는 함수
78
+ function hasButtonAsProps(openingElement) {
79
+ return openingElement.attributes.some(
80
+ attr =>
81
+ attr.type === 'JSXAttribute' &&
82
+ attr.name.name === 'as' &&
83
+ attr.value.expression &&
84
+ attr.value.expression.type === 'Identifier' &&
85
+ buttonComponentNames.has(attr.value.expression.name) &&
86
+ tdsImportedComponents.has(attr.value.expression.name)
87
+ );
88
+ }
89
+
90
+ // 컴파운드 컴포넌트가 Button 관련인지 확인하는 함수
91
+ function isButtonCompoundComponent(objectName, propertyName) {
92
+ // 해당 객체가 @tds/mobile에서 import되지 않았으면 변환하지 않아요
93
+ if (!tdsImportedComponents.has(objectName)) {
94
+ return false;
95
+ }
96
+
97
+ // FixedBottomCTA.Double
98
+ if (objectName === 'FixedBottomCTA' && propertyName === 'Double') {
99
+ return true;
100
+ }
101
+ // BottomCTA.Single, BottomCTA.Double
102
+ if (objectName === 'BottomCTA' && (propertyName === 'Single' || propertyName === 'Double')) {
103
+ return true;
104
+ }
105
+ // BottomSheet.CTA, BottomSheet.DoubleCTA
106
+ if (objectName === 'BottomSheet' && (propertyName === 'CTA' || propertyName === 'DoubleCTA')) {
107
+ return true;
108
+ }
109
+ // ConfirmDialog.ConfirmButton, ConfirmDialog.CancelButton
110
+ if (objectName === 'ConfirmDialog' && (propertyName === 'ConfirmButton' || propertyName === 'CancelButton')) {
111
+ return true;
112
+ }
113
+ // Top.LowerButton, Top.RightButton, Top.LowerCTAButton - Button 기반
114
+ if (
115
+ objectName === 'Top' &&
116
+ (propertyName === 'LowerButton' || propertyName === 'RightButton' || propertyName === 'LowerCTAButton')
117
+ ) {
118
+ return true;
119
+ }
120
+
121
+ // 제외해야 할 컴포넌트들 (Button을 확장하지 않는 컴포넌트들)
122
+ const excludedComponents = [
123
+ 'AccessoryButton', // PageNavbar.AccessoryButton, NavigationContext.AccessoryButton
124
+ 'IconButton', // 일반적으로 Icon 기반 버튼
125
+ 'TextButton', // TextButton은 별도 컴포넌트 (Button 확장 아님)
126
+ 'LinkButton', // Link 기반 버튼들
127
+ 'FloatingButton', // Floating 액션 버튼들
128
+ 'TitleTextButton', // TextButton을 감싸고 있는 컴포넌트
129
+ 'SubtitleTextButton', // TextButton을 감싸고 있는 컴포넌트
130
+ ];
131
+
132
+ if (excludedComponents.includes(propertyName)) {
133
+ return false;
134
+ }
135
+
136
+ return false;
137
+ }
138
+
139
+ // 모든 JSX 요소를 순회해요
140
+ root.find(j.JSXElement).forEach(path => {
141
+ const openingElement = path.value.openingElement;
142
+ const elementName = openingElement.name;
143
+
144
+ // Button 관련 컴포넌트 직접 사용 (styled 컴포넌트 포함)
145
+ if (isButtonComponent(elementName)) {
146
+ transformPropNames(openingElement);
147
+ }
148
+ // as prop으로 Button 컴포넌트를 사용하는 경우
149
+ else if (hasButtonAsProps(openingElement)) {
150
+ transformPropNames(openingElement);
151
+ }
152
+ // 컴파운드 컴포넌트들
153
+ else if (elementName.type === 'JSXMemberExpression') {
154
+ const objectName = elementName.object.name;
155
+ const propertyName = elementName.property.name;
156
+
157
+ if (isButtonCompoundComponent(objectName, propertyName)) {
158
+ transformPropNames(openingElement);
159
+ }
160
+ }
161
+ });
162
+
163
+ return root.toSource({ quote: 'double' });
164
+ };
165
+
166
+ /**
167
+ * styled-components로 확장된 Button 기반 컴포넌트들을 찾는 함수
168
+ */
169
+ const findStyledButtonComponents = (j, root, buttonComponentNames) => {
170
+ root.find(j.VariableDeclarator).forEach(path => {
171
+ const init = path.node.init;
172
+ if (!init) return;
173
+
174
+ // styled(Button)`` 패턴
175
+ if (
176
+ init.type === 'TaggedTemplateExpression' &&
177
+ init.tag.type === 'CallExpression' &&
178
+ init.tag.callee.name === 'styled' &&
179
+ init.tag.arguments[0] &&
180
+ buttonComponentNames.has(init.tag.arguments[0].name)
181
+ ) {
182
+ if (path.node.id && path.node.id.name) {
183
+ buttonComponentNames.add(path.node.id.name);
184
+ }
185
+ }
186
+
187
+ // styled(Button)({ ... }) 패턴
188
+ if (
189
+ init.type === 'CallExpression' &&
190
+ init.callee.type === 'CallExpression' &&
191
+ init.callee.callee.name === 'styled' &&
192
+ init.callee.arguments[0] &&
193
+ buttonComponentNames.has(init.callee.arguments[0].name)
194
+ ) {
195
+ if (path.node.id && path.node.id.name) {
196
+ buttonComponentNames.add(path.node.id.name);
197
+ }
198
+ }
199
+ });
200
+ };
201
+
202
+ const parser = 'tsx';
203
+
204
+ module.exports = transform;
205
+ module.exports.parser = parser;
@@ -0,0 +1,209 @@
1
+ /**
2
+ * @type {import('jscodeshift').Transform}
3
+ */
4
+ const transform = (fileInfo, { jscodeshift: j }, options) => {
5
+ const root = j(fileInfo.source);
6
+
7
+ // package name을 options에서 가져와요 (기본값: @tds/mobile)
8
+ const packageName = options['package-name'] || '@tds/mobile';
9
+
10
+ // packageName에서 import된 컴포넌트들을 추적하는 Set
11
+ const tdsImportedComponents = new Set();
12
+
13
+ // packageName import 구문들을 찾아서 컴포넌트 이름들을 수집해요
14
+ root
15
+ .find(j.ImportDeclaration, {
16
+ source: { value: packageName },
17
+ })
18
+ .forEach(path => {
19
+ path.value.specifiers.forEach(specifier => {
20
+ if (specifier.type === 'ImportSpecifier') {
21
+ tdsImportedComponents.add(specifier.local.name);
22
+ }
23
+ });
24
+ });
25
+
26
+ // Button 관련 컴포넌트 이름들을 관리하는 Set
27
+ const buttonComponentNames = new Set(['Button', 'ResultButton', 'CTAButton', 'FixedBottomCTA', 'BottomCTA']);
28
+
29
+ // Button 관련 컴포넌트가 packageName에서 하나도 import되지 않았다면 변환하지 않아요
30
+ const hasAnyButtonComponent = Array.from(buttonComponentNames).some(componentName =>
31
+ tdsImportedComponents.has(componentName)
32
+ );
33
+
34
+ if (!hasAnyButtonComponent) {
35
+ return fileInfo.source;
36
+ }
37
+
38
+ // styled-components로 확장된 Button 기반 컴포넌트들을 찾아서 Set에 추가
39
+ findStyledButtonComponents(j, root, buttonComponentNames);
40
+
41
+ // size 값 매핑 규칙
42
+ const sizeValueMap = {
43
+ tiny: 'small',
44
+ big: 'xlarge',
45
+ };
46
+
47
+ // JSX 컴포넌트의 size prop 값 변경 공통 함수
48
+ function transformSizeValues(openingElement) {
49
+ const attributes = openingElement.attributes;
50
+
51
+ // size 속성 찾아서 값 변경해요
52
+ attributes.forEach(attr => {
53
+ if (attr.type === 'JSXAttribute' && attr.name.name === 'size') {
54
+ let sizeValue = null;
55
+
56
+ // 속성 값 추출해요 - StringLiteral과 Literal 둘 다 처리해요
57
+ if (attr.value && (attr.value.type === 'Literal' || attr.value.type === 'StringLiteral')) {
58
+ sizeValue = attr.value.value;
59
+ }
60
+
61
+ // 매핑된 값이 있으면 변경해요
62
+ if (sizeValue && sizeValueMap[sizeValue]) {
63
+ attr.value.value = sizeValueMap[sizeValue];
64
+ }
65
+ }
66
+ });
67
+ }
68
+
69
+ // 컴포넌트 이름이 Button 관련 컴포넌트인지 확인하는 함수
70
+ function isButtonComponent(elementName) {
71
+ if (elementName.type === 'JSXIdentifier') {
72
+ return buttonComponentNames.has(elementName.name) && tdsImportedComponents.has(elementName.name);
73
+ }
74
+ return false;
75
+ }
76
+
77
+ // as prop으로 Button 컴포넌트를 사용하는지 확인하는 함수
78
+ function hasButtonAsProps(openingElement) {
79
+ return openingElement.attributes.some(
80
+ attr =>
81
+ attr.type === 'JSXAttribute' &&
82
+ attr.name.name === 'as' &&
83
+ attr.value.expression &&
84
+ attr.value.expression.type === 'Identifier' &&
85
+ buttonComponentNames.has(attr.value.expression.name) &&
86
+ tdsImportedComponents.has(attr.value.expression.name)
87
+ );
88
+ }
89
+
90
+ // 컴파운드 컴포넌트가 Button 관련인지 확인하는 함수
91
+ function isButtonCompoundComponent(objectName, propertyName) {
92
+ // 해당 객체가 @tds/mobile에서 import되지 않았으면 변환하지 않아요
93
+ if (!tdsImportedComponents.has(objectName)) {
94
+ return false;
95
+ }
96
+
97
+ // FixedBottomCTA.Double
98
+ if (objectName === 'FixedBottomCTA' && propertyName === 'Double') {
99
+ return true;
100
+ }
101
+ // BottomCTA.Single, BottomCTA.Double
102
+ if (objectName === 'BottomCTA' && (propertyName === 'Single' || propertyName === 'Double')) {
103
+ return true;
104
+ }
105
+ // BottomSheet.CTA, BottomSheet.DoubleCTA
106
+ if (objectName === 'BottomSheet' && (propertyName === 'CTA' || propertyName === 'DoubleCTA')) {
107
+ return true;
108
+ }
109
+ // ConfirmDialog.ConfirmButton, ConfirmDialog.CancelButton
110
+ if (objectName === 'ConfirmDialog' && (propertyName === 'ConfirmButton' || propertyName === 'CancelButton')) {
111
+ return true;
112
+ }
113
+ // Top.LowerButton, Top.RightButton, Top.LowerCTAButton - Button 기반
114
+ if (
115
+ objectName === 'Top' &&
116
+ (propertyName === 'LowerButton' || propertyName === 'RightButton' || propertyName === 'LowerCTAButton')
117
+ ) {
118
+ return true;
119
+ }
120
+ // AlertDialog.AlertButton - TextButton 기반이지만 같은 props 사용
121
+ if (objectName === 'AlertDialog' && propertyName === 'AlertButton') {
122
+ return true;
123
+ }
124
+
125
+ // 제외해야 할 컴포넌트들 (Button을 확장하지 않는 컴포넌트들)
126
+ const excludedComponents = [
127
+ 'AccessoryButton', // PageNavbar.AccessoryButton, NavigationContext.AccessoryButton
128
+ 'IconButton', // 일반적으로 Icon 기반 버튼
129
+ 'TextButton', // TextButton은 별도 컴포넌트 (Button 확장 아님)
130
+ 'LinkButton', // Link 기반 버튼들
131
+ 'FloatingButton', // Floating 액션 버튼들
132
+ 'TitleTextButton', // TextButton을 감싸고 있는 컴포넌트
133
+ 'SubtitleTextButton', // TextButton을 감싸고 있는 컴포넌트
134
+ ];
135
+
136
+ if (excludedComponents.includes(propertyName)) {
137
+ return false;
138
+ }
139
+
140
+ return false;
141
+ }
142
+
143
+ // 모든 JSX 요소를 순회해요
144
+ root.find(j.JSXElement).forEach(path => {
145
+ const openingElement = path.value.openingElement;
146
+ const elementName = openingElement.name;
147
+
148
+ // Button 관련 컴포넌트 직접 사용 (styled 컴포넌트 포함)
149
+ if (isButtonComponent(elementName)) {
150
+ transformSizeValues(openingElement);
151
+ }
152
+ // as prop으로 Button 컴포넌트를 사용하는 경우
153
+ else if (hasButtonAsProps(openingElement)) {
154
+ transformSizeValues(openingElement);
155
+ }
156
+ // 컴파운드 컴포넌트들
157
+ else if (elementName.type === 'JSXMemberExpression') {
158
+ const objectName = elementName.object.name;
159
+ const propertyName = elementName.property.name;
160
+
161
+ if (isButtonCompoundComponent(objectName, propertyName)) {
162
+ transformSizeValues(openingElement);
163
+ }
164
+ }
165
+ });
166
+
167
+ return root.toSource({ quote: 'double' });
168
+ };
169
+
170
+ /**
171
+ * styled-components로 확장된 Button 기반 컴포넌트들을 찾는 함수
172
+ */
173
+ const findStyledButtonComponents = (j, root, buttonComponentNames) => {
174
+ root.find(j.VariableDeclarator).forEach(path => {
175
+ const init = path.node.init;
176
+ if (!init) return;
177
+
178
+ // styled(Button)`` 패턴
179
+ if (
180
+ init.type === 'TaggedTemplateExpression' &&
181
+ init.tag.type === 'CallExpression' &&
182
+ init.tag.callee.name === 'styled' &&
183
+ init.tag.arguments[0] &&
184
+ buttonComponentNames.has(init.tag.arguments[0].name)
185
+ ) {
186
+ if (path.node.id && path.node.id.name) {
187
+ buttonComponentNames.add(path.node.id.name);
188
+ }
189
+ }
190
+
191
+ // styled(Button)({ ... }) 패턴
192
+ if (
193
+ init.type === 'CallExpression' &&
194
+ init.callee.type === 'CallExpression' &&
195
+ init.callee.callee.name === 'styled' &&
196
+ init.callee.arguments[0] &&
197
+ buttonComponentNames.has(init.callee.arguments[0].name)
198
+ ) {
199
+ if (path.node.id && path.node.id.name) {
200
+ buttonComponentNames.add(path.node.id.name);
201
+ }
202
+ }
203
+ });
204
+ };
205
+
206
+ const parser = 'tsx';
207
+
208
+ module.exports = transform;
209
+ module.exports.parser = parser;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @type {import('jscodeshift').Transform}
3
+ */
4
+ const transform = (fileInfo, { jscodeshift: j }, options) => {
5
+ const root = j(fileInfo.source);
6
+
7
+ // package name을 options에서 가져와요 (기본값: @tds/mobile)
8
+ const packageName = options['package-name'] || '@tds/mobile';
9
+
10
+ // packageName에서 import된 컴포넌트들을 추적하는 Set
11
+ const tdsImportedComponents = new Set();
12
+
13
+ // packageName import 구문들을 찾아서 컴포넌트 이름들을 수집해요
14
+ root
15
+ .find(j.ImportDeclaration, {
16
+ source: { value: packageName },
17
+ })
18
+ .forEach(path => {
19
+ path.value.specifiers.forEach(specifier => {
20
+ if (specifier.type === 'ImportSpecifier') {
21
+ tdsImportedComponents.add(specifier.local.name);
22
+ }
23
+ });
24
+ });
25
+
26
+ // IconButton 관련 컴포넌트가 packageName에서 import되지 않았다면 변환하지 않아요
27
+ if (!tdsImportedComponents.has('IconButton') && !tdsImportedComponents.has('ListRow')) {
28
+ return fileInfo.source;
29
+ }
30
+
31
+ // JSX 컴포넌트의 label prop을 aria-label로 변경하고 없으면 추가하는 함수
32
+ function transformLabelToAriaLabel(openingElement) {
33
+ const attributes = openingElement.attributes;
34
+ let hasLabel = false;
35
+ let hasAriaLabel = false;
36
+
37
+ // 기존 label과 aria-label 속성 확인해요
38
+ attributes.forEach(attr => {
39
+ if (attr.type === 'JSXAttribute') {
40
+ if (attr.name.name === 'label') {
41
+ hasLabel = true;
42
+ // label을 aria-label로 변경해요
43
+ attr.name.name = 'aria-label';
44
+ } else if (attr.name.name === 'aria-label') {
45
+ hasAriaLabel = true;
46
+ }
47
+ }
48
+ });
49
+
50
+ // label도 aria-label도 없으면 빈 string으로 aria-label 추가해요
51
+ if (!hasLabel && !hasAriaLabel) {
52
+ const ariaLabelAttr = j.jsxAttribute(j.jsxIdentifier('aria-label'), j.literal(''));
53
+ attributes.push(ariaLabelAttr);
54
+ }
55
+ }
56
+
57
+ // 모든 JSX 요소를 순회해요
58
+ root.find(j.JSXElement).forEach(path => {
59
+ const openingElement = path.value.openingElement;
60
+ const elementName = openingElement.name;
61
+
62
+ // IconButton 직접 사용 - packageName에서 import된 경우에만
63
+ if (elementName.name === 'IconButton' && tdsImportedComponents.has('IconButton')) {
64
+ transformLabelToAriaLabel(openingElement);
65
+ }
66
+ // 컴파운드 컴포넌트들
67
+ else if (elementName.type === 'JSXMemberExpression') {
68
+ const objectName = elementName.object.name;
69
+ const propertyName = elementName.property.name;
70
+
71
+ // ListRow.IconButton - ListRow가 packageName에서 import된 경우에만
72
+ if (objectName === 'ListRow' && propertyName === 'IconButton' && tdsImportedComponents.has('ListRow')) {
73
+ transformLabelToAriaLabel(openingElement);
74
+ }
75
+ }
76
+ });
77
+
78
+ return root.toSource({ quote: 'double' });
79
+ };
80
+
81
+ const parser = 'tsx';
82
+
83
+ module.exports = transform;
84
+ module.exports.parser = parser;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @type {import('jscodeshift').Transform}
3
+ */
4
+ const transform = (fileInfo, { jscodeshift: j }, options) => {
5
+ const root = j(fileInfo.source);
6
+
7
+ // package name을 options에서 가져와요 (기본값: @tds/mobile)
8
+ const packageName = options['package-name'] || '@tds/mobile';
9
+
10
+ // packageName에서 import된 컴포넌트들을 추적하는 Set
11
+ const tdsImportedComponents = new Set();
12
+
13
+ // packageName import 구문들을 찾아서 컴포넌트 이름들을 수집해요
14
+ root
15
+ .find(j.ImportDeclaration, {
16
+ source: { value: packageName },
17
+ })
18
+ .forEach(path => {
19
+ path.value.specifiers.forEach(specifier => {
20
+ if (specifier.type === 'ImportSpecifier') {
21
+ tdsImportedComponents.add(specifier.local.name);
22
+ }
23
+ });
24
+ });
25
+
26
+ // ListRow 컴포넌트가 packageName에서 import되지 않았다면 변환하지 않아요
27
+ if (!tdsImportedComponents.has('ListRow')) {
28
+ return fileInfo.source;
29
+ }
30
+
31
+ // verticalPadding 값 매핑 규칙
32
+ const verticalPaddingValueMap = {
33
+ extraSmall: 'small',
34
+ 'extraSmall-8': 'small',
35
+ small: 'medium',
36
+ 'small-16': 'medium',
37
+ medium: 'large',
38
+ 'medium-24': 'large',
39
+ large: 'xlarge',
40
+ 'large-32': 'xlarge',
41
+ };
42
+
43
+ // JSX 컴포넌트의 verticalPadding prop 값 변경 공통 함수
44
+ function transformVerticalPaddingValues(openingElement) {
45
+ const attributes = openingElement.attributes;
46
+
47
+ // verticalPadding 속성 찾아서 값 변경해요
48
+ attributes.forEach(attr => {
49
+ if (attr.type === 'JSXAttribute' && attr.name.name === 'verticalPadding') {
50
+ let paddingValue = null;
51
+
52
+ // 속성 값 추출해요 - StringLiteral, Literal, JSXExpressionContainer 모두 처리해요
53
+ if (attr.value) {
54
+ if (attr.value.type === 'Literal' || attr.value.type === 'StringLiteral') {
55
+ paddingValue = attr.value.value;
56
+ } else if (attr.value.type === 'JSXExpressionContainer' && attr.value.expression.type === 'StringLiteral') {
57
+ paddingValue = attr.value.expression.value;
58
+ }
59
+ }
60
+
61
+ // 매핑된 값이 있으면 변경해요
62
+ if (paddingValue && verticalPaddingValueMap[paddingValue]) {
63
+ // JSXExpressionContainer인 경우와 아닌 경우를 구분해서 처리해요
64
+ if (attr.value.type === 'JSXExpressionContainer') {
65
+ attr.value.expression.value = verticalPaddingValueMap[paddingValue];
66
+ } else {
67
+ attr.value.value = verticalPaddingValueMap[paddingValue];
68
+ }
69
+ }
70
+ }
71
+ });
72
+ }
73
+
74
+ // 모든 JSX 요소를 순회해요
75
+ root.find(j.JSXElement).forEach(path => {
76
+ const openingElement = path.value.openingElement;
77
+ const elementName = openingElement.name;
78
+
79
+ // ListRow 직접 사용 - packageName에서 import된 경우에만
80
+ if (elementName.name === 'ListRow' && tdsImportedComponents.has('ListRow')) {
81
+ transformVerticalPaddingValues(openingElement);
82
+ }
83
+ // 컴파운드 컴포넌트들
84
+ else if (elementName.type === 'JSXMemberExpression') {
85
+ const objectName = elementName.object.name;
86
+ const propertyName = elementName.property.name;
87
+
88
+ // ListRow의 모든 서브 컴포넌트들 - ListRow가 packageName에서 import된 경우에만
89
+ if (objectName === 'ListRow' && tdsImportedComponents.has('ListRow')) {
90
+ transformVerticalPaddingValues(openingElement);
91
+ }
92
+ }
93
+ });
94
+
95
+ return root.toSource({ quote: 'double' });
96
+ };
97
+
98
+ const parser = 'tsx';
99
+
100
+ module.exports = transform;
101
+ module.exports.parser = parser;