@@ -2,6 +2,20 @@ import MagicString from "../node_modules/magic-string";
2
2
import * as assert from 'assert' ;
3
3
import * as fs from "fs" ;
4
4
import * as acorn from 'acorn' ;
5
+ import { createMatcher } from "./match" ;
6
+
7
+ const matchers = {
8
+ assign : createMatcher ( 'Object.assign(_, _ObjectExpression_)' , m => ( {
9
+ target : m [ 0 ] ,
10
+ source : m [ 1 ]
11
+ } ) ) ,
12
+
13
+ subclass : createMatcher ( '_ = Object.assign(Object.create(_), _ObjectExpression_)' , m => ( {
14
+ target : m [ 0 ] ,
15
+ superclass : m [ 1 ] ,
16
+ source : m [ 2 ]
17
+ } ) )
18
+ } ;
5
19
6
20
export default class Module {
7
21
file : string ;
@@ -10,8 +24,11 @@ export default class Module {
10
24
ast : any ;
11
25
12
26
superclasses : Map < string , string > ;
13
- methodBlocks : Map < string , string > ;
27
+
14
28
constructors : Map < string , string > ;
29
+ methods : Map < string , string [ ] > ;
30
+ staticMethods : Map < string , string [ ] > ;
31
+ properties : Map < string , Array < { key : string , value : string } > > ;
15
32
16
33
constructor ( file : string ) {
17
34
this . file = file ;
@@ -24,57 +41,106 @@ export default class Module {
24
41
this . code = new MagicString ( this . source ) ;
25
42
26
43
this . superclasses = new Map ( ) ;
27
- this . methodBlocks = new Map ( ) ;
44
+ this . methods = new Map ( ) ;
45
+ this . staticMethods = new Map ( ) ;
28
46
this . constructors = new Map ( ) ;
47
+ this . properties = new Map ( ) ;
29
48
30
49
this . convert ( ) ;
31
50
}
32
51
33
52
findSuperclasses ( ) {
34
- const { superclasses } = this ;
53
+ this . ast . body . forEach ( node => {
54
+ // AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
55
+ const nodes = matchers . subclass ( node ) ;
56
+ if ( ! nodes ) return ;
57
+
58
+ const { target, superclass, source } = nodes ;
59
+
60
+ const match = / ^ ( \w + ) ( \. p r o t o t y p e ) ? $ / . exec ( this . snip ( target ) ) ;
61
+ assert . ok ( ! ! match [ 2 ] ) ;
62
+
63
+ const name = match [ 1 ] ;
64
+
65
+ if ( name [ 0 ] . toUpperCase ( ) !== name [ 0 ] ) return ; // not a class
66
+
67
+ this . superclasses . set ( name , superclass . name ) ;
68
+ } ) ;
35
69
}
36
70
37
71
findAndConvertMethods ( ) {
38
72
this . ast . body . forEach ( node => {
39
- if ( node . type !== 'ExpressionStatement' ) return ;
40
- if ( node . expression . type !== 'CallExpression' ) return ;
73
+ const nodes = matchers . assign ( node ) || matchers . subclass ( node ) ;
74
+ if ( ! nodes ) return ;
41
75
42
- // check if this is an Object.assign( SomeClass.prototype, { methods })
43
- const callee = this . snip ( node . expression . callee ) ;
44
- if ( callee !== 'Object.assign' ) return ;
76
+ const { target, source } = nodes ;
45
77
46
- assert . equal ( node . expression . arguments . length , 2 ) ;
78
+ const match = / ^ ( \w + ) ( \. p r o t o t y p e ) ? $ / . exec ( this . snip ( target ) ) ;
47
79
48
- const [ targetNode , methodsNode ] = node . expression . arguments ;
80
+ const name = match [ 1 ] ;
81
+ const isStatic = ! match [ 2 ] ;
49
82
50
- const target = this . snip ( targetNode ) ;
51
- const match = / ^ ( \w + ) \. p r o t o t y p e $ / . exec ( target ) ;
83
+ let c : number = source . start + 1 ;
52
84
53
- assert . ok ( match , target ) ;
85
+ // remove constructor
86
+ const index = source . properties . findIndex ( prop => prop . key . name === 'constructor' ) ;
87
+ assert . ok ( index <= 0 ) ;
88
+ if ( index === 0 ) {
89
+ assert . ok ( source . properties . length > 1 ) ;
90
+ const prop = source . properties . shift ( ) ;
54
91
55
- const name = match [ 1 ] ;
92
+ this . code . remove ( prop . start , source . properties [ 1 ] . start ) ;
93
+ c = prop . end + 1 ;
94
+ }
95
+
96
+ while ( / \s / . test ( this . source [ c ] ) ) c += 1 ;
56
97
57
98
// check we're actually assigning methods
58
- assert . ok ( methodsNode . properties . every ( node => node . value . type === 'FunctionExpression' ) ) ;
59
-
60
- methodsNode . properties . forEach ( ( prop , i ) => {
61
- // `foo: function()` -> `foo()`
62
- let c = prop . key . end ;
63
- while ( this . source [ c ] !== '(' ) c += 1 ;
64
- this . code . overwrite ( prop . key . end , c , ' ' ) ;
65
-
66
- if ( i < methodsNode . properties . length - 1 ) {
67
- // remove comma
68
- assert . equal ( this . source [ prop . end ] , ',' ) ;
69
- this . code . remove ( prop . end , prop . end + 1 ) ;
99
+
100
+ source . properties . forEach ( prop => {
101
+ if ( prop . value . type === 'FunctionExpression' ) {
102
+ // `foo: function()` -> `foo()`
103
+ let argsStart = prop . key . end ;
104
+ while ( this . source [ argsStart ] !== '(' ) argsStart += 1 ;
105
+ this . code . overwrite ( prop . key . end , argsStart , ' ' ) ;
106
+
107
+ if ( isStatic ) {
108
+ this . code . overwrite ( prop . key . start , prop . key . end , `static ${ prop . key . name } ` ) ;
109
+
110
+ if ( ! this . staticMethods . get ( name ) ) {
111
+ this . staticMethods . set ( name , [ ] ) ;
112
+ }
113
+
114
+ this . staticMethods . get ( name ) . push ( this . code . slice ( c , prop . end ) ) ;
115
+ } else {
116
+ if ( ! this . methods . get ( name ) ) {
117
+ this . methods . set ( name , [ ] ) ;
118
+ }
119
+
120
+ this . methods . get ( name ) . push ( this . code . slice ( c , prop . end ) ) ;
121
+ }
70
122
}
71
- } ) ;
72
123
73
- const methodBlock = `\t${ this . code . slice ( methodsNode . start + 1 , methodsNode . end - 1 ) . trim ( ) } ` ;
124
+ else {
125
+ if ( ! this . properties . has ( name ) ) {
126
+ this . properties . set ( name , [ ] ) ;
127
+ }
128
+
129
+ this . properties . get ( name ) . push ( {
130
+ key : prop . key . name ,
131
+ value : this . source . slice ( prop . value . start , prop . value . end )
132
+ } ) ;
133
+
134
+ this . code . remove ( c , prop . end ) ;
135
+ }
74
136
75
- this . methodBlocks . set ( name , methodBlock ) ;
137
+ c = prop . end ;
138
+ while ( c < this . source . length && this . source [ c ] !== ',' ) c += 1 ;
139
+ c += 1 ;
140
+ while ( c < this . source . length && / \s / . test ( this . source [ c ] ) ) c += 1 ;
141
+ } ) ;
76
142
77
- let c = node . start ;
143
+ c = node . start ;
78
144
while ( / \s / . test ( this . source [ c - 1 ] ) ) c -= 1 ;
79
145
80
146
this . code . remove ( c , node . end ) ;
@@ -92,8 +158,6 @@ export default class Module {
92
158
93
159
if ( name [ 0 ] . toUpperCase ( ) !== name [ 0 ] ) return ; // not a class
94
160
95
- console . log ( 'converting constructor' , { name } ) ;
96
-
97
161
const superclass = this . superclasses . get ( name ) ;
98
162
99
163
const declaration = superclass
@@ -105,42 +169,27 @@ export default class Module {
105
169
. replace ( / ^ / gm, '\t' )
106
170
. slice ( 1 ) ;
107
171
108
- const methodBlock = this . methodBlocks . get ( name ) ;
172
+ const methods = this . methods . get ( name ) || [ ] ;
173
+ const staticMethods = this . staticMethods . get ( name ) || [ ] ;
174
+
175
+ const combined = [ ...methods , ...staticMethods ]
176
+ . filter ( Boolean )
177
+ . join ( '\n\n\t' ) ;
178
+
179
+ const properties = ( this . properties . get ( name ) || [ ] ) . map ( prop => {
180
+ return `\n\n${ name } .prototype.${ prop . key } = ${ prop . value . replace ( / ^ \t / gm, '' ) } ;` ;
181
+ } ) ;
109
182
110
- this . code . overwrite ( node . start , node . end , `${ declaration } {\n\tconstructor ${ constructorArgsAndBody } \n\n${ methodBlock } \n}` ) ;
183
+ const body = `{\n\tconstructor ${ constructorArgsAndBody } \n\n\t${ combined } \n}` ;
184
+
185
+ this . code . overwrite ( node . start , node . end , `${ declaration } ${ body } ${ properties . join ( '' ) } ` ) ;
111
186
} ) ;
112
187
}
113
188
114
189
convert ( ) {
115
190
this . findSuperclasses ( ) ;
116
191
this . findAndConvertMethods ( ) ;
117
192
this . findAndConvertConstructors ( ) ;
118
-
119
- // function handleDeclaration(node) {
120
- // const { name } = node.id;
121
- // const superclass = superclasses.get(name);
122
-
123
- // const declaration = superclass
124
- // ? `class ${name} extends ${superclass}`
125
- // : `class ${name}`;
126
-
127
- // console.log(node.start, node.id.end);
128
-
129
- // code.overwrite(node.start, node.id.end, `${declaration} {\n\tconstructor`);
130
-
131
-
132
- // }
133
-
134
- // ast.body.forEach(statement => {
135
- // if (statement.type === 'FunctionDeclaration') {
136
- // handleDeclaration(statement);
137
- // }
138
-
139
- // if (statement.type === 'ExportNamedDeclaration') {
140
- // // TODO handle export named functions
141
- // console.log(statement);
142
- // }
143
- // });
144
193
}
145
194
146
195
snip ( { start, end } ) {
0 commit comments