Skip to content

Commit 08ffbd3

Browse files
committed
fix: convertFunctionToEs6Class cannot recognize x.prototype = {} pattern
1 parent 196c0aa commit 08ffbd3

File tree

1 file changed

+85
-43
lines changed

1 file changed

+85
-43
lines changed

src/services/codefixes/convertFunctionToEs6Class.ts

Lines changed: 85 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -60,40 +60,65 @@ namespace ts.codefix {
6060
symbol.members.forEach(member => {
6161
const memberElement = createClassElement(member, /*modifiers*/ undefined);
6262
if (memberElement) {
63-
memberElements.push(memberElement);
63+
memberElements.push(...memberElement);
6464
}
6565
});
6666
}
6767

6868
// all static members are stored in the "exports" array of symbol
6969
if (symbol.exports) {
7070
symbol.exports.forEach(member => {
71-
const memberElement = createClassElement(member, [createToken(SyntaxKind.StaticKeyword)]);
72-
if (memberElement) {
73-
memberElements.push(memberElement);
71+
if (member.name === "prototype") {
72+
const firstDeclaration = member.declarations[0];
73+
// only one "x.prototype = { ... }" will pass
74+
if (member.declarations.length === 1 &&
75+
isPropertyAccessExpression(firstDeclaration) &&
76+
isBinaryExpression(firstDeclaration.parent) &&
77+
firstDeclaration.parent.operatorToken.kind === SyntaxKind.EqualsToken &&
78+
isObjectLiteralExpression(firstDeclaration.parent.right)
79+
) {
80+
const prototypes = firstDeclaration.parent.right;
81+
const memberElement = createClassElement(prototypes.symbol, /** modifiers */ undefined);
82+
if (memberElement) {
83+
memberElements.push(...memberElement);
84+
}
85+
}
86+
}
87+
else {
88+
const memberElement = createClassElement(member, [createToken(SyntaxKind.StaticKeyword)]);
89+
if (memberElement) {
90+
memberElements.push(...memberElement);
91+
}
7492
}
7593
});
7694
}
7795

7896
return memberElements;
7997

80-
function shouldConvertDeclaration(_target: PropertyAccessExpression, source: Expression) {
98+
function shouldConvertDeclaration(_target: PropertyAccessExpression | ObjectLiteralExpression, source: Expression) {
8199
// Right now the only thing we can convert are function expressions - other values shouldn't get
82100
// transformed. We can update this once ES public class properties are available.
83-
return isFunctionLike(source);
101+
if (isPropertyAccessExpression(_target)) {
102+
return isFunctionLike(source);
103+
}
104+
else {
105+
return every(_target.properties, x => isPropertyAssignment(x) && isFunctionLike(x.initializer) && !!x.name);
106+
}
84107
}
85108

86-
function createClassElement(symbol: Symbol, modifiers: Modifier[] | undefined): ClassElement | undefined {
109+
function createClassElement(symbol: Symbol, modifiers: Modifier[] | undefined): ClassElement[] {
87110
// Right now the only thing we can convert are function expressions, which are marked as methods
88-
if (!(symbol.flags & SymbolFlags.Method)) {
89-
return;
111+
// or { x: y } type prototype assignments, which are marked as ObjectLiteral
112+
const members: ClassElement[] = [];
113+
if (!(symbol.flags & SymbolFlags.Method) && !(symbol.flags & SymbolFlags.ObjectLiteral)) {
114+
return members;
90115
}
91116

92-
const memberDeclaration = symbol.valueDeclaration as PropertyAccessExpression;
117+
const memberDeclaration = symbol.valueDeclaration as PropertyAccessExpression | ObjectLiteralExpression;
93118
const assignmentBinaryExpression = memberDeclaration.parent as BinaryExpression;
94119

95120
if (!shouldConvertDeclaration(memberDeclaration, assignmentBinaryExpression.right)) {
96-
return;
121+
return members;
97122
}
98123

99124
// delete the entire statement if this expression is the sole expression to take care of the semicolon at the end
@@ -102,50 +127,67 @@ namespace ts.codefix {
102127
changes.delete(sourceFile, nodeToDelete);
103128

104129
if (!assignmentBinaryExpression.right) {
105-
return createProperty([], modifiers, symbol.name, /*questionToken*/ undefined,
106-
/*type*/ undefined, /*initializer*/ undefined);
130+
members.push(createProperty([], modifiers, symbol.name, /*questionToken*/ undefined,
131+
/*type*/ undefined, /*initializer*/ undefined));
132+
return members;
107133
}
108134

109135
switch (assignmentBinaryExpression.right.kind) {
110-
case SyntaxKind.FunctionExpression: {
111-
const functionExpression = assignmentBinaryExpression.right as FunctionExpression;
112-
const fullModifiers = concatenate(modifiers, getModifierKindFromSource(functionExpression, SyntaxKind.AsyncKeyword));
113-
const method = createMethod(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, memberDeclaration.name, /*questionToken*/ undefined,
114-
/*typeParameters*/ undefined, functionExpression.parameters, /*type*/ undefined, functionExpression.body);
115-
copyLeadingComments(assignmentBinaryExpression, method, sourceFile);
116-
return method;
117-
}
118-
119-
case SyntaxKind.ArrowFunction: {
120-
const arrowFunction = assignmentBinaryExpression.right as ArrowFunction;
121-
const arrowFunctionBody = arrowFunction.body;
122-
let bodyBlock: Block;
123-
124-
// case 1: () => { return [1,2,3] }
125-
if (arrowFunctionBody.kind === SyntaxKind.Block) {
126-
bodyBlock = arrowFunctionBody as Block;
127-
}
128-
// case 2: () => [1,2,3]
129-
else {
130-
bodyBlock = createBlock([createReturn(arrowFunctionBody)]);
131-
}
132-
const fullModifiers = concatenate(modifiers, getModifierKindFromSource(arrowFunction, SyntaxKind.AsyncKeyword));
133-
const method = createMethod(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, memberDeclaration.name, /*questionToken*/ undefined,
134-
/*typeParameters*/ undefined, arrowFunction.parameters, /*type*/ undefined, bodyBlock);
135-
copyLeadingComments(assignmentBinaryExpression, method, sourceFile);
136-
return method;
136+
case SyntaxKind.FunctionExpression:
137+
case SyntaxKind.ArrowFunction:
138+
return createFunctionLikeExpressionMember(members, <FunctionExpression | ArrowFunction>assignmentBinaryExpression.right, (<PropertyAccessExpression>memberDeclaration).name);
139+
case SyntaxKind.ObjectLiteralExpression: {
140+
return map(
141+
(<ObjectLiteralExpression>assignmentBinaryExpression.right).properties,
142+
property => createFunctionLikeExpressionMember(members, <FunctionExpression | ArrowFunction>(<PropertyAssignment>property).initializer, property.name!)
143+
).reduce((x, y) => x.concat(y));
137144
}
138145

139146
default: {
140147
// Don't try to declare members in JavaScript files
141148
if (isSourceFileJS(sourceFile)) {
142-
return;
149+
return members;
143150
}
144-
const prop = createProperty(/*decorators*/ undefined, modifiers, memberDeclaration.name, /*questionToken*/ undefined,
151+
const prop = createProperty(/*decorators*/ undefined, modifiers, (<PropertyAccessExpression>memberDeclaration).name, /*questionToken*/ undefined,
145152
/*type*/ undefined, assignmentBinaryExpression.right);
146153
copyLeadingComments(assignmentBinaryExpression.parent, prop, sourceFile);
147-
return prop;
154+
members.push(prop);
155+
return members;
156+
}
157+
}
158+
159+
type MethodName = Parameters<typeof createMethod>[3];
160+
161+
function createFunctionLikeExpressionMember(members: readonly ClassElement[], expression: FunctionExpression | ArrowFunction, name: MethodName) {
162+
if (isFunctionExpression(expression)) return createFunctionExpressionMember(members, expression, name);
163+
else return createArrowFunctionExpressionMember(members, expression, name);
164+
}
165+
166+
function createFunctionExpressionMember(members: readonly ClassElement[], functionExpression: FunctionExpression, name: MethodName) {
167+
const fullModifiers = concatenate(modifiers, getModifierKindFromSource(functionExpression, SyntaxKind.AsyncKeyword));
168+
const method = createMethod(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, name, /*questionToken*/ undefined,
169+
/*typeParameters*/ undefined, functionExpression.parameters, /*type*/ undefined, functionExpression.body);
170+
copyLeadingComments(assignmentBinaryExpression, method, sourceFile);
171+
return members.concat(method);
172+
}
173+
174+
function createArrowFunctionExpressionMember(members: readonly ClassElement[], arrowFunction: ArrowFunction, name: MethodName) {
175+
const arrowFunctionBody = arrowFunction.body;
176+
let bodyBlock: Block;
177+
178+
// case 1: () => { return [1,2,3] }
179+
if (arrowFunctionBody.kind === SyntaxKind.Block) {
180+
bodyBlock = arrowFunctionBody as Block;
181+
}
182+
// case 2: () => [1,2,3]
183+
else {
184+
bodyBlock = createBlock([createReturn(arrowFunctionBody)]);
148185
}
186+
const fullModifiers = concatenate(modifiers, getModifierKindFromSource(arrowFunction, SyntaxKind.AsyncKeyword));
187+
const method = createMethod(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, name, /*questionToken*/ undefined,
188+
/*typeParameters*/ undefined, arrowFunction.parameters, /*type*/ undefined, bodyBlock);
189+
copyLeadingComments(assignmentBinaryExpression, method, sourceFile);
190+
return members.concat(method);
149191
}
150192
}
151193
}

0 commit comments

Comments
 (0)