1+ /* @internal */
2+ namespace ts . codefix {
3+ registerCodeFix ( {
4+ errorCodes : [ Diagnostics . Convert_function_0_to_ES6_class . code ] ,
5+ getCodeActions,
6+ createCodeFixDiagnosticIfApplicable
7+ } ) ;
8+
9+ function createCodeFixDiagnosticIfApplicable ( node : Node , context : CodeFixDiagnoseContext ) : Diagnostic | undefined {
10+ if ( ! isSourceFileJavaScript ( context . boundSourceFile ) ) {
11+ return undefined ;
12+ }
13+
14+ const checker = context . program . getTypeChecker ( ) ;
15+ const symbol = checker . getSymbolAtLocation ( node ) ;
16+ if ( isClassLikeSymbol ( symbol ) ) {
17+ return createDiagnosticForNode ( node , Diagnostics . Convert_function_0_to_ES6_class , symbol . name ) ;
18+ }
19+
20+ function isClassLikeSymbol ( symbol : Symbol ) {
21+ if ( ! symbol || ! symbol . valueDeclaration ) {
22+ return false ;
23+ }
24+
25+ let targetSymbol : Symbol ;
26+ if ( symbol . valueDeclaration . kind === SyntaxKind . FunctionDeclaration ) {
27+ targetSymbol = symbol ;
28+ }
29+ else if ( isDeclarationOfFunctionOrClassExpression ( symbol ) ) {
30+ targetSymbol = ( symbol . valueDeclaration as VariableDeclaration ) . initializer . symbol ;
31+ }
32+
33+ // if there is a prototype property assignment like:
34+ // foo.prototype.method = function () { }
35+ // then the symbol for "foo" will have a member
36+ return targetSymbol && targetSymbol . members && targetSymbol . members . size > 0 ;
37+ }
38+ }
39+
40+ function getCodeActions ( context : CodeFixContext ) : CodeAction [ ] {
41+ const sourceFile = context . sourceFile ;
42+ const checker = context . program . getTypeChecker ( ) ;
43+ const token = getTokenAtPosition ( sourceFile , context . span . start ) ;
44+ const ctorSymbol = checker . getSymbolAtLocation ( token ) ;
45+
46+ const deletes : ( ( ) => any ) [ ] = [ ] ;
47+
48+ if ( ! ( ctorSymbol . flags & ( SymbolFlags . Function | SymbolFlags . Variable ) ) ) {
49+ return [ ] ;
50+ }
51+
52+ const ctorDeclaration = ctorSymbol . valueDeclaration ;
53+ const changeTracker = textChanges . ChangeTracker . fromCodeFixContext ( context ) ;
54+
55+ let precedingNode : Node ;
56+ let newClassDeclaration : ClassDeclaration ;
57+ switch ( ctorDeclaration . kind ) {
58+ case SyntaxKind . FunctionDeclaration :
59+ precedingNode = ctorDeclaration ;
60+ deletes . push ( ( ) => changeTracker . deleteNode ( sourceFile , ctorDeclaration ) ) ;
61+ newClassDeclaration = createClassFromFunctionDeclaration ( ctorDeclaration as FunctionDeclaration ) ;
62+ break ;
63+
64+ case SyntaxKind . VariableDeclaration :
65+ precedingNode = ctorDeclaration . parent . parent ;
66+ if ( ( < VariableDeclarationList > ctorDeclaration . parent ) . declarations . length === 1 ) {
67+ deletes . push ( ( ) => changeTracker . deleteNode ( sourceFile , precedingNode ) ) ;
68+ }
69+ else {
70+ deletes . push ( ( ) => changeTracker . deleteNodeInList ( sourceFile , ctorDeclaration ) ) ;
71+ }
72+ newClassDeclaration = createClassFromVariableDeclaration ( ctorDeclaration as VariableDeclaration ) ;
73+ break ;
74+ }
75+
76+ if ( ! newClassDeclaration ) {
77+ return [ ] ;
78+ }
79+
80+ // Because the preceding node could be touched, we need to insert nodes before delete nodes.
81+ changeTracker . insertNodeAfter ( sourceFile , precedingNode , newClassDeclaration , { suffix : "\n" } ) ;
82+ for ( const deleteCallback of deletes ) {
83+ deleteCallback ( ) ;
84+ }
85+
86+ return [ {
87+ description : `Convert function ${ ctorSymbol . name } to ES6 class` ,
88+ changes : changeTracker . getChanges ( )
89+ } ] ;
90+
91+ function createClassElementsFromSymbol ( symbol : Symbol ) {
92+ const memberElements : ClassElement [ ] = [ ] ;
93+ // all instance members are stored in the "member" array of symbol
94+ if ( symbol . members ) {
95+ symbol . members . forEach ( member => {
96+ const memberElement = createClassElement ( member , /*modifiers*/ undefined ) ;
97+ if ( memberElement ) {
98+ memberElements . push ( memberElement ) ;
99+ }
100+ } ) ;
101+ }
102+
103+ // all static members are stored in the "exports" array of symbol
104+ if ( symbol . exports ) {
105+ symbol . exports . forEach ( member => {
106+ const memberElement = createClassElement ( member , [ createToken ( SyntaxKind . StaticKeyword ) ] ) ;
107+ if ( memberElement ) {
108+ memberElements . push ( memberElement ) ;
109+ }
110+ } ) ;
111+ }
112+
113+ return memberElements ;
114+
115+ function createClassElement ( symbol : Symbol , modifiers : Modifier [ ] ) : ClassElement {
116+ // both properties and methods are bound as property symbols
117+ if ( ! ( symbol . flags & SymbolFlags . Property ) ) {
118+ return ;
119+ }
120+
121+ const memberDeclaration = symbol . valueDeclaration as PropertyAccessExpression ;
122+ const assignmentBinaryExpression = memberDeclaration . parent as BinaryExpression ;
123+
124+ // delete the entire statement if this expression is the sole expression to take care of the semicolon at the end
125+ const nodeToDelete = assignmentBinaryExpression . parent && assignmentBinaryExpression . parent . kind === SyntaxKind . ExpressionStatement
126+ ? assignmentBinaryExpression . parent : assignmentBinaryExpression ;
127+ deletes . push ( ( ) => changeTracker . deleteNode ( sourceFile , nodeToDelete ) ) ;
128+
129+ if ( ! assignmentBinaryExpression . right ) {
130+ return createProperty ( [ ] , modifiers , symbol . name , /*questionToken*/ undefined ,
131+ /*type*/ undefined , /*initializer*/ undefined ) ;
132+ }
133+
134+ switch ( assignmentBinaryExpression . right . kind ) {
135+ case SyntaxKind . FunctionExpression :
136+ const functionExpression = assignmentBinaryExpression . right as FunctionExpression ;
137+ return createMethodDeclaration ( /*decorators*/ undefined , modifiers , /*asteriskToken*/ undefined , memberDeclaration . name , /*questionToken*/ undefined ,
138+ /*typeParameters*/ undefined , functionExpression . parameters , /*type*/ undefined , functionExpression . body ) ;
139+
140+ case SyntaxKind . ArrowFunction :
141+ const arrowFunction = assignmentBinaryExpression . right as ArrowFunction ;
142+ const arrowFunctionBody = arrowFunction . body ;
143+ let bodyBlock : Block ;
144+
145+ // case 1: () => { return [1,2,3] }
146+ if ( arrowFunctionBody . kind === SyntaxKind . Block ) {
147+ bodyBlock = arrowFunctionBody as Block ;
148+ }
149+ // case 2: () => [1,2,3]
150+ else {
151+ const expression = arrowFunctionBody as Expression ;
152+ bodyBlock = createBlock ( [ createReturn ( expression ) ] ) ;
153+ }
154+ return createMethodDeclaration ( /*decorators*/ undefined , modifiers , /*asteriskToken*/ undefined , memberDeclaration . name , /*questionToken*/ undefined ,
155+ /*typeParameters*/ undefined , arrowFunction . parameters , /*type*/ undefined , bodyBlock ) ;
156+ default :
157+ return createProperty ( /*decorators*/ undefined , modifiers , memberDeclaration . name , /*questionToken*/ undefined ,
158+ /*type*/ undefined , assignmentBinaryExpression . right ) ;
159+ }
160+ }
161+ }
162+
163+ function createClassFromVariableDeclaration ( node : VariableDeclaration ) : ClassDeclaration {
164+ const initializer = node . initializer as FunctionExpression ;
165+ if ( ! initializer || initializer . kind !== SyntaxKind . FunctionExpression ) {
166+ return undefined ;
167+ }
168+
169+ if ( node . name . kind !== SyntaxKind . Identifier ) {
170+ return undefined ;
171+ }
172+
173+ const memberElements = createClassElementsFromSymbol ( initializer . symbol ) ;
174+ if ( initializer . body ) {
175+ memberElements . unshift ( createConstructor ( /*decorators*/ undefined , /*modifiers*/ undefined , initializer . parameters , initializer . body ) ) ;
176+ }
177+
178+ return createClassDeclaration ( /*decorators*/ undefined , /*modifiers*/ undefined , node . name ,
179+ /*typeParameters*/ undefined , /*heritageClauses*/ undefined , memberElements ) ;
180+ }
181+
182+ function createClassFromFunctionDeclaration ( node : FunctionDeclaration ) : ClassDeclaration {
183+ const memberElements = createClassElementsFromSymbol ( ctorSymbol ) ;
184+ if ( node . body ) {
185+ memberElements . unshift ( createConstructor ( /*decorators*/ undefined , /*modifiers*/ undefined , node . parameters , node . body ) ) ;
186+ }
187+ return createClassDeclaration ( /*decorators*/ undefined , /*modifiers*/ undefined , node . name ,
188+ /*typeParameters*/ undefined , /*heritageClauses*/ undefined , memberElements ) ;
189+ }
190+ }
191+ }
0 commit comments