1
+ import MagicString from "../node_modules/magic-string" ;
2
+ import * as assert from 'assert' ;
3
+ import * as fs from "fs" ;
4
+ import * as acorn from 'acorn' ;
5
+
6
+ export default class Module {
7
+ file : string ;
8
+ source : string ;
9
+ code : MagicString ;
10
+ ast : any ;
11
+
12
+ superclasses : Map < string , string > ;
13
+ methodBlocks : Map < string , string > ;
14
+ constructors : Map < string , string > ;
15
+
16
+ constructor ( file : string ) {
17
+ this . file = file ;
18
+ this . source = fs . readFileSync ( file , 'utf-8' ) ;
19
+ this . ast = acorn . parse ( this . source , {
20
+ ecmaVersion : 9 ,
21
+ sourceType : 'module'
22
+ } ) ;
23
+
24
+ this . code = new MagicString ( this . source ) ;
25
+
26
+ this . superclasses = new Map ( ) ;
27
+ this . methodBlocks = new Map ( ) ;
28
+ this . constructors = new Map ( ) ;
29
+
30
+ this . convert ( ) ;
31
+ }
32
+
33
+ findSuperclasses ( ) {
34
+ const { superclasses } = this ;
35
+ }
36
+
37
+ findAndConvertMethods ( ) {
38
+ this . ast . body . forEach ( node => {
39
+ if ( node . type !== 'ExpressionStatement' ) return ;
40
+ if ( node . expression . type !== 'CallExpression' ) return ;
41
+
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 ;
45
+
46
+ assert . equal ( node . expression . arguments . length , 2 ) ;
47
+
48
+ const [ targetNode , methodsNode ] = node . expression . arguments ;
49
+
50
+ const target = this . snip ( targetNode ) ;
51
+ const match = / ^ ( \w + ) \. p r o t o t y p e $ / . exec ( target ) ;
52
+
53
+ assert . ok ( match , target ) ;
54
+
55
+ const name = match [ 1 ] ;
56
+
57
+ // 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 ) ;
70
+ }
71
+ } ) ;
72
+
73
+ const methodBlock = `\t${ this . code . slice ( methodsNode . start + 1 , methodsNode . end - 1 ) . trim ( ) } ` ;
74
+
75
+ this . methodBlocks . set ( name , methodBlock ) ;
76
+
77
+ let c = node . start ;
78
+ while ( / \s / . test ( this . source [ c - 1 ] ) ) c -= 1 ;
79
+
80
+ this . code . remove ( c , node . end ) ;
81
+ } ) ;
82
+ }
83
+
84
+ findAndConvertConstructors ( ) {
85
+ this . ast . body . forEach ( node => {
86
+ if ( node . type === 'ExportNamedDeclaration' ) {
87
+ node = node . declaration ;
88
+ }
89
+
90
+ if ( ! node || node . type !== 'FunctionDeclaration' ) return ;
91
+ const { name } = node . id ;
92
+
93
+ if ( name [ 0 ] . toUpperCase ( ) !== name [ 0 ] ) return ; // not a class
94
+
95
+ console . log ( 'converting constructor' , { name } ) ;
96
+
97
+ const superclass = this . superclasses . get ( name ) ;
98
+
99
+ const declaration = superclass
100
+ ? `class ${ name } extends ${ superclass } `
101
+ : `class ${ name } ` ;
102
+
103
+ const constructorArgsAndBody = this . code
104
+ . slice ( node . id . end , node . body . end )
105
+ . replace ( / ^ / gm, '\t' )
106
+ . slice ( 1 ) ;
107
+
108
+ const methodBlock = this . methodBlocks . get ( name ) ;
109
+
110
+ this . code . overwrite ( node . start , node . end , `${ declaration } {\n\tconstructor ${ constructorArgsAndBody } \n\n${ methodBlock } \n}` ) ;
111
+ } ) ;
112
+ }
113
+
114
+ convert ( ) {
115
+ this . findSuperclasses ( ) ;
116
+ this . findAndConvertMethods ( ) ;
117
+ 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
+ }
145
+
146
+ snip ( { start, end } ) {
147
+ return this . source . slice ( start , end ) ;
148
+ }
149
+
150
+ toString ( ) {
151
+ return this . code . toString ( ) ;
152
+ }
153
+ }
0 commit comments