1- const fs = require ( 'fs' ) ;
2- const chalk = require ( 'chalk' ) ;
31const pkgDir = require ( 'pkg-dir' ) ;
42const globby = require ( 'globby' ) ;
5- const stringUtils = require ( 'ember-cli-string-utils' ) ;
6- const j = require ( 'jscodeshift' ) . withParser ( 'ts' ) ;
73const debug = require ( 'debug' ) ( 'tagless-ember-components-codemod' ) ;
84
9- const EVENT_HANDLER_METHODS = [
10- // Touch events
11- 'touchStart' ,
12- 'touchMove' ,
13- 'touchEnd' ,
14- 'touchCancel' ,
15-
16- // Keyboard events
17- 'keyDown' ,
18- 'keyUp' ,
19- 'keyPress' ,
20-
21- // Mouse events
22- 'mouseDown' ,
23- 'mouseUp' ,
24- 'contextMenu' ,
25- 'click' ,
26- 'doubleClick' ,
27- 'focusIn' ,
28- 'focusOut' ,
29-
30- // Form events
31- 'submit' ,
32- 'change' ,
33- 'focusIn' ,
34- 'focusOut' ,
35- 'input' ,
36-
37- // Drag and drop events
38- 'dragStart' ,
39- 'drag' ,
40- 'dragEnter' ,
41- 'dragLeave' ,
42- 'dragOver' ,
43- 'dragEnd' ,
44- 'drop' ,
45- ] ;
5+ const { transform } = require ( './transform' ) ;
466
477async function run ( ) {
488 let path = await pkgDir ( ) ;
@@ -67,263 +27,4 @@ async function runForPath(path, options = {}) {
6727 debug ( 'runForPath() finished' ) ;
6828}
6929
70- function transform ( componentPath ) {
71- let dimPath = chalk . dim ( componentPath ) ;
72-
73- let source = fs . readFileSync ( componentPath , 'utf8' ) ;
74-
75- let root = j ( source ) ;
76-
77- // find `export default Component.extend({ ... });` AST node
78- let exportDefaultDeclarations = root . find ( j . ExportDefaultDeclaration , {
79- declaration : {
80- type : 'CallExpression' ,
81- callee : {
82- type : 'MemberExpression' ,
83- object : { name : 'Component' } ,
84- property : { name : 'extend' } ,
85- } ,
86- } ,
87- } ) ;
88-
89- if ( exportDefaultDeclarations . length !== 1 ) {
90- console . log (
91- chalk . yellow ( `${ dimPath } : Could not find \`export default Component.extend({ ... });\`` )
92- ) ;
93- return ;
94- }
95-
96- let exportDefaultDeclaration = exportDefaultDeclarations . get ( ) ;
97-
98- // find first `{ ... }` inside `Component.extend()` arguments
99- let extendObjectArgs = exportDefaultDeclaration
100- . get ( 'declaration' , 'arguments' )
101- . filter ( path => path . value . type === 'ObjectExpression' ) ;
102-
103- let objectArg = extendObjectArgs [ 0 ] ;
104- if ( ! objectArg ) {
105- console . log (
106- chalk . yellow (
107- `${ dimPath } : Could not find object argument in \`export default Component.extend({ ... });\``
108- )
109- ) ;
110- return ;
111- }
112-
113- // find `tagName` property if it exists
114- let properties = objectArg . get ( 'properties' ) ;
115-
116- let tagName = findTagName ( properties ) ;
117- if ( tagName . constructor . name === 'NodePath' ) {
118- console . log ( chalk . yellow ( `${ dimPath } : Unexpected \`tagName\` value: ${ j ( tagName ) . toSource ( ) } ` ) ) ;
119- return ;
120- }
121-
122- // skip tagless components (silent)
123- if ( tagName === '' ) {
124- debug ( `${ componentPath } : tagName: %o -> skip` , tagName ) ;
125- return ;
126- }
127-
128- debug ( `${ componentPath } : tagName: %o` , tagName ) ;
129-
130- // skip components that use `this.element`
131- let thisElementPaths = j ( objectArg ) . find ( j . MemberExpression , {
132- object : { type : 'ThisExpression' } ,
133- property : { name : 'element' } ,
134- } ) ;
135- if ( thisElementPaths . length !== 0 ) {
136- console . log (
137- chalk . yellow ( `${ dimPath } : Using \`this.element\` is not supported in tagless components` )
138- ) ;
139- return ;
140- }
141-
142- // skip components that use `click()` etc.
143- for ( let methodName of EVENT_HANDLER_METHODS ) {
144- let handlerMethod = properties . filter ( path => isMethod ( path , methodName ) ) [ 0 ] ;
145- if ( handlerMethod ) {
146- console . log (
147- chalk . yellow ( `${ dimPath } : Using \`${ methodName } ()\` is not supported in tagless components` )
148- ) ;
149- return ;
150- }
151- }
152-
153- // analyze `elementId`, `attributeBindings`, `classNames` and `classNameBindings`
154- let elementId = findElementId ( properties ) ;
155- if ( elementId !== null && elementId . constructor . name === 'NodePath' ) {
156- console . log (
157- chalk . yellow ( `${ dimPath } : Unexpected \`elementId\` value: ${ j ( elementId ) . toSource ( ) } ` )
158- ) ;
159- return ;
160- }
161- debug ( `${ componentPath } : elementId: %o` , elementId ) ;
162-
163- let attributeBindings = findAttributeBindings ( properties ) ;
164- if ( attributeBindings . constructor . name === 'NodePath' ) {
165- console . log (
166- chalk . yellow (
167- `${ dimPath } : Unexpected \`attributeBindings\` value: ${ j ( attributeBindings ) . toSource ( ) } `
168- )
169- ) ;
170- return ;
171- }
172- debug ( `${ componentPath } : attributeBindings: %o` , attributeBindings ) ;
173-
174- let classNames = findClassNames ( properties ) ;
175- if ( classNames . constructor . name === 'NodePath' ) {
176- console . log (
177- chalk . yellow ( `${ dimPath } : Unexpected \`classNames\` value: ${ j ( classNames ) . toSource ( ) } ` )
178- ) ;
179- return ;
180- }
181- debug ( `${ componentPath } : classNames: %o` , classNames ) ;
182-
183- let classNameBindings = findClassNameBindings ( properties ) ;
184- if ( classNameBindings . constructor . name === 'NodePath' ) {
185- console . log (
186- chalk . yellow (
187- `${ dimPath } : Unexpected \`classNameBindings\` value: ${ j ( classNameBindings ) . toSource ( ) } `
188- )
189- ) ;
190- return ;
191- }
192- debug ( `${ componentPath } : classNameBindings: %o` , classNameBindings ) ;
193-
194- // TODO skip on error (warn incl. please report)
195-
196- // TODO find corresponding template files
197- // TODO skip if not found (warn)
198-
199- // TODO set `tagName: ''` and remove `attributeBindings`, `classNames`, ...
200- // TODO wrap existing template with root element
201- }
202-
203- function isProperty ( path , name ) {
204- let node = path . value ;
205- return node . type === 'ObjectProperty' && node . key . type === 'Identifier' && node . key . name === name ;
206- }
207-
208- function isMethod ( path , name ) {
209- let node = path . value ;
210- return node . type === 'ObjectMethod' && node . key . type === 'Identifier' && node . key . name === name ;
211- }
212-
213- function findTagName ( properties ) {
214- let tagNameProperty = properties . filter ( path => isProperty ( path , 'tagName' ) ) [ 0 ] ;
215- if ( ! tagNameProperty ) {
216- return 'div' ;
217- }
218-
219- let tagNamePath = tagNameProperty . get ( 'value' ) ;
220- if ( tagNamePath . value . type === 'StringLiteral' ) {
221- return tagNamePath . value . value ;
222- }
223-
224- return tagNamePath ;
225- }
226-
227- function findElementId ( properties ) {
228- let elementIdProperty = properties . filter ( path => isProperty ( path , 'elementId' ) ) [ 0 ] ;
229- if ( ! elementIdProperty ) {
230- return null ;
231- }
232-
233- let elementIdPath = elementIdProperty . get ( 'value' ) ;
234- if ( elementIdPath . value . type === 'StringLiteral' ) {
235- return elementIdPath . value . value ;
236- }
237-
238- return elementIdPath ;
239- }
240-
241- function findAttributeBindings ( properties ) {
242- let attrBindingsProperty = properties . filter ( path => isProperty ( path , 'attributeBindings' ) ) [ 0 ] ;
243- if ( ! attrBindingsProperty ) {
244- return new Map ( ) ;
245- }
246-
247- let arrayPath = attrBindingsProperty . get ( 'value' ) ;
248- if ( arrayPath . value . type !== 'ArrayExpression' ) {
249- return arrayPath ;
250- }
251-
252- let elements = arrayPath . get ( 'elements' ) . value ;
253-
254- let attrBindings = new Map ( ) ;
255- for ( let element of elements ) {
256- if ( element . type !== 'StringLiteral' ) {
257- return arrayPath ;
258- }
259-
260- let [ from , to ] = element . value . split ( ':' ) ;
261- attrBindings . set ( from , to || from ) ;
262- }
263-
264- return attrBindings ;
265- }
266-
267- function findClassNames ( properties ) {
268- let classNamesProperty = properties . filter ( path => isProperty ( path , 'classNames' ) ) [ 0 ] ;
269- if ( ! classNamesProperty ) {
270- return [ ] ;
271- }
272-
273- let arrayPath = classNamesProperty . get ( 'value' ) ;
274- if ( arrayPath . value . type !== 'ArrayExpression' ) {
275- return arrayPath ;
276- }
277-
278- let elements = arrayPath . get ( 'elements' ) . value ;
279-
280- let classNames = [ ] ;
281- for ( let element of elements ) {
282- if ( element . type !== 'StringLiteral' ) {
283- return arrayPath ;
284- }
285-
286- classNames . push ( element . value ) ;
287- }
288-
289- return classNames ;
290- }
291-
292- function findClassNameBindings ( properties ) {
293- let classNameBindingsProperty = properties . filter ( path =>
294- isProperty ( path , 'classNameBindings' )
295- ) [ 0 ] ;
296- if ( ! classNameBindingsProperty ) {
297- return new Map ( ) ;
298- }
299-
300- let arrayPath = classNameBindingsProperty . get ( 'value' ) ;
301- if ( arrayPath . value . type !== 'ArrayExpression' ) {
302- return arrayPath ;
303- }
304-
305- let elements = arrayPath . get ( 'elements' ) . value ;
306-
307- let classNameBindings = new Map ( ) ;
308- for ( let element of elements ) {
309- if ( element . type !== 'StringLiteral' ) {
310- return arrayPath ;
311- }
312-
313- let parts = element . value . split ( ':' ) ;
314-
315- if ( parts . length === 1 ) {
316- classNameBindings . set ( parts [ 0 ] , [ stringUtils . dasherize ( parts [ 0 ] ) , null ] ) ;
317- } else if ( parts . length === 2 ) {
318- classNameBindings . set ( parts [ 0 ] , [ parts [ 1 ] , null ] ) ;
319- } else if ( parts . length === 3 ) {
320- classNameBindings . set ( parts [ 0 ] , [ parts [ 1 ] || null , parts [ 2 ] ] ) ;
321- } else {
322- return arrayPath ;
323- }
324- }
325-
326- return classNameBindings ;
327- }
328-
32930module . exports = { run } ;
0 commit comments