@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.
- package/cli.js +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +43 -0
- package/dist/cli.js.map +1 -0
- package/dist/tds-v2/codemod/commands/badge-props-rename.cjs +81 -0
- package/dist/tds-v2/codemod/commands/badge-size-rename.cjs +89 -0
- package/dist/tds-v2/codemod/commands/boardrow-rightarrow-rename.cjs +89 -0
- package/dist/tds-v2/codemod/commands/bottomcta-component-rename.cjs +70 -0
- package/dist/tds-v2/codemod/commands/button-export-rename.cjs +120 -0
- package/dist/tds-v2/codemod/commands/button-props-rename.cjs +205 -0
- package/dist/tds-v2/codemod/commands/button-size-rename.cjs +209 -0
- package/dist/tds-v2/codemod/commands/iconbutton-label-to-aria-label.cjs +84 -0
- package/dist/tds-v2/codemod/commands/listrow-verticalpadding-rename.cjs +101 -0
- package/dist/tds-v2/codemod/commands/textbutton-typography-to-size.cjs +229 -0
- package/dist/tds-v2/codemod/commands/top-subtitle-props-rename.cjs +66 -0
- package/dist/tds-v2/index.d.ts +2 -0
- package/dist/tds-v2/index.d.ts.map +1 -0
- package/dist/tds-v2/index.js +2 -0
- package/dist/tds-v2/index.js.map +1 -0
- package/dist/tds-v2/runCodemod.d.ts +5 -0
- package/dist/tds-v2/runCodemod.d.ts.map +1 -0
- package/dist/tds-v2/runCodemod.js +37 -0
- package/dist/tds-v2/runCodemod.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/v2/codemod/commands/provider.cjs +97 -0
- package/dist/v2/index.d.ts +2 -0
- package/dist/v2/index.d.ts.map +1 -0
- package/dist/v2/index.js +2 -0
- package/dist/v2/index.js.map +1 -0
- package/dist/v2/runCodemod.d.ts +5 -0
- package/dist/v2/runCodemod.d.ts.map +1 -0
- package/dist/v2/runCodemod.js +37 -0
- package/dist/v2/runCodemod.js.map +1 -0
- 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;
|