Skip to content

Commit e4c1568

Browse files
authored
fix(41078): add element access expressions support in convertFunctionToEs6Class (microsoft#41089)
1 parent 8ed251d commit e4c1568

11 files changed

+258
-15
lines changed

src/services/codefixes/convertFunctionToEs6Class.ts

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ namespace ts.codefix {
55
registerCodeFix({
66
errorCodes,
77
getCodeActions(context: CodeFixContext) {
8-
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, context.span.start, context.program.getTypeChecker()));
8+
const changes = textChanges.ChangeTracker.with(context, t =>
9+
doChange(t, context.sourceFile, context.span.start, context.program.getTypeChecker(), context.preferences, context.program.getCompilerOptions()));
910
return [createCodeFixAction(fixId, changes, Diagnostics.Convert_function_to_an_ES2015_class, fixId, Diagnostics.Convert_all_constructor_functions_to_classes)];
1011
},
1112
fixIds: [fixId],
12-
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => doChange(changes, err.file, err.start, context.program.getTypeChecker())),
13+
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) =>
14+
doChange(changes, err.file, err.start, context.program.getTypeChecker(), context.preferences, context.program.getCompilerOptions())),
1315
});
1416

15-
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void {
17+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker, preferences: UserPreferences, compilerOptions: CompilerOptions): void {
1618
const ctorSymbol = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position))!;
1719
if (!ctorSymbol || !(ctorSymbol.flags & (SymbolFlags.Function | SymbolFlags.Variable))) {
1820
// Bad input
@@ -86,12 +88,12 @@ namespace ts.codefix {
8688

8789
return memberElements;
8890

89-
function shouldConvertDeclaration(_target: PropertyAccessExpression | ObjectLiteralExpression, source: Expression) {
91+
function shouldConvertDeclaration(_target: AccessExpression | ObjectLiteralExpression, source: Expression) {
9092
// Right now the only thing we can convert are function expressions, get/set accessors and methods
9193
// other values like normal value fields ({a: 1}) shouldn't get transformed.
9294
// We can update this once ES public class properties are available.
93-
if (isPropertyAccessExpression(_target)) {
94-
if (isConstructorAssignment(_target)) return true;
95+
if (isAccessExpression(_target)) {
96+
if (isPropertyAccessExpression(_target) && isConstructorAssignment(_target)) return true;
9597
return isFunctionLike(source);
9698
}
9799
else {
@@ -115,10 +117,9 @@ namespace ts.codefix {
115117
return members;
116118
}
117119

118-
const memberDeclaration = symbol.valueDeclaration as PropertyAccessExpression | ObjectLiteralExpression;
120+
const memberDeclaration = symbol.valueDeclaration as AccessExpression | ObjectLiteralExpression;
119121
const assignmentBinaryExpression = memberDeclaration.parent as BinaryExpression;
120122
const assignmentExpr = assignmentBinaryExpression.right;
121-
122123
if (!shouldConvertDeclaration(memberDeclaration, assignmentExpr)) {
123124
return members;
124125
}
@@ -135,8 +136,13 @@ namespace ts.codefix {
135136
}
136137

137138
// f.x = expr
138-
if (isPropertyAccessExpression(memberDeclaration) && (isFunctionExpression(assignmentExpr) || isArrowFunction(assignmentExpr))) {
139-
return createFunctionLikeExpressionMember(members, assignmentExpr, memberDeclaration.name);
139+
if (isAccessExpression(memberDeclaration) && (isFunctionExpression(assignmentExpr) || isArrowFunction(assignmentExpr))) {
140+
const quotePreference = getQuotePreference(sourceFile, preferences);
141+
const name = tryGetPropertyName(memberDeclaration, compilerOptions, quotePreference);
142+
if (name) {
143+
return createFunctionLikeExpressionMember(members, assignmentExpr, name);
144+
}
145+
return members;
140146
}
141147
// f.prototype = { ... }
142148
else if (isObjectLiteralExpression(assignmentExpr)) {
@@ -166,22 +172,20 @@ namespace ts.codefix {
166172
return members;
167173
}
168174

169-
type MethodName = Parameters<typeof factory.createMethodDeclaration>[3];
170-
171-
function createFunctionLikeExpressionMember(members: readonly ClassElement[], expression: FunctionExpression | ArrowFunction, name: MethodName) {
175+
function createFunctionLikeExpressionMember(members: readonly ClassElement[], expression: FunctionExpression | ArrowFunction, name: PropertyName) {
172176
if (isFunctionExpression(expression)) return createFunctionExpressionMember(members, expression, name);
173177
else return createArrowFunctionExpressionMember(members, expression, name);
174178
}
175179

176-
function createFunctionExpressionMember(members: readonly ClassElement[], functionExpression: FunctionExpression, name: MethodName) {
180+
function createFunctionExpressionMember(members: readonly ClassElement[], functionExpression: FunctionExpression, name: PropertyName) {
177181
const fullModifiers = concatenate(modifiers, getModifierKindFromSource(functionExpression, SyntaxKind.AsyncKeyword));
178182
const method = factory.createMethodDeclaration(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, name, /*questionToken*/ undefined,
179183
/*typeParameters*/ undefined, functionExpression.parameters, /*type*/ undefined, functionExpression.body);
180184
copyLeadingComments(assignmentBinaryExpression, method, sourceFile);
181185
return members.concat(method);
182186
}
183187

184-
function createArrowFunctionExpressionMember(members: readonly ClassElement[], arrowFunction: ArrowFunction, name: MethodName) {
188+
function createArrowFunctionExpressionMember(members: readonly ClassElement[], arrowFunction: ArrowFunction, name: PropertyName) {
185189
const arrowFunctionBody = arrowFunction.body;
186190
let bodyBlock: Block;
187191

@@ -243,4 +247,23 @@ namespace ts.codefix {
243247
if (isIdentifier(x.name) && x.name.text === "constructor") return true;
244248
return false;
245249
}
250+
251+
function tryGetPropertyName(node: AccessExpression, compilerOptions: CompilerOptions, quotePreference: QuotePreference): PropertyName | undefined {
252+
if (isPropertyAccessExpression(node)) {
253+
return node.name;
254+
}
255+
256+
const propName = node.argumentExpression;
257+
if (isNumericLiteral(propName)) {
258+
return propName;
259+
}
260+
261+
if (isStringLiteralLike(propName)) {
262+
return isIdentifierText(propName.text, compilerOptions.target) ? factory.createIdentifier(propName.text)
263+
: isNoSubstitutionTemplateLiteral(propName) ? factory.createStringLiteral(propName.text, quotePreference === QuotePreference.Single)
264+
: propName;
265+
}
266+
267+
return undefined;
268+
}
246269
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowNonTsExtensions: true
4+
// @Filename: foo.js
5+
////function Foo() {
6+
//// this.a = 0;
7+
////}
8+
////Foo["a b c"] = function () {
9+
////}
10+
11+
verify.codeFix({
12+
description: "Convert function to an ES2015 class",
13+
newFileContent:
14+
`class Foo {
15+
constructor() {
16+
this.a = 0;
17+
}
18+
static "a b c"() {
19+
}
20+
}
21+
`
22+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowNonTsExtensions: true
4+
// @Filename: foo.js
5+
////function Foo() {
6+
//// this.a = 0;
7+
////}
8+
////Foo[1] = function () {
9+
////}
10+
11+
verify.codeFix({
12+
description: "Convert function to an ES2015 class",
13+
newFileContent:
14+
`class Foo {
15+
constructor() {
16+
this.a = 0;
17+
}
18+
static 1() {
19+
}
20+
}
21+
`
22+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowNonTsExtensions: true
4+
// @Filename: foo.js
5+
////function Foo() {
6+
//// this.a = 0;
7+
////}
8+
////Foo[`a b c`] = function () {
9+
////}
10+
11+
verify.codeFix({
12+
description: "Convert function to an ES2015 class",
13+
newFileContent:
14+
`class Foo {
15+
constructor() {
16+
this.a = 0;
17+
}
18+
static "a b c"() {
19+
}
20+
}
21+
`
22+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowNonTsExtensions: true
4+
// @Filename: foo.js
5+
////function Foo() {
6+
//// this.a = 0;
7+
////}
8+
////Foo[`b`] = function () {
9+
////}
10+
11+
verify.codeFix({
12+
description: "Convert function to an ES2015 class",
13+
newFileContent:
14+
`class Foo {
15+
constructor() {
16+
this.a = 0;
17+
}
18+
static b() {
19+
}
20+
}
21+
`
22+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowNonTsExtensions: true
4+
// @Filename: foo.js
5+
////function Foo() {
6+
//// this.a = 0;
7+
////}
8+
////Foo.prototype["b"] = function () {
9+
////}
10+
11+
verify.codeFix({
12+
description: "Convert function to an ES2015 class",
13+
newFileContent:
14+
`class Foo {
15+
constructor() {
16+
this.a = 0;
17+
}
18+
b() {
19+
}
20+
}
21+
`
22+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowNonTsExtensions: true
4+
// @Filename: foo.js
5+
////function Foo() {
6+
//// this.a = 0;
7+
////}
8+
////Foo.prototype["a b c"] = function () {
9+
////}
10+
11+
verify.codeFix({
12+
description: "Convert function to an ES2015 class",
13+
newFileContent:
14+
`class Foo {
15+
constructor() {
16+
this.a = 0;
17+
}
18+
"a b c"() {
19+
}
20+
}
21+
`
22+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowNonTsExtensions: true
4+
// @Filename: foo.js
5+
////function Foo() {
6+
//// this.a = 0;
7+
////}
8+
////Foo.prototype[1] = function () {
9+
////}
10+
11+
verify.codeFix({
12+
description: "Convert function to an ES2015 class",
13+
newFileContent:
14+
`class Foo {
15+
constructor() {
16+
this.a = 0;
17+
}
18+
1() {
19+
}
20+
}
21+
`
22+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowNonTsExtensions: true
4+
// @Filename: foo.js
5+
////function Foo() {
6+
//// this.a = 0;
7+
////}
8+
////Foo.prototype[`a b c`] = function () {
9+
////}
10+
11+
verify.codeFix({
12+
description: "Convert function to an ES2015 class",
13+
newFileContent:
14+
`class Foo {
15+
constructor() {
16+
this.a = 0;
17+
}
18+
"a b c"() {
19+
}
20+
}
21+
`
22+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowNonTsExtensions: true
4+
// @Filename: foo.js
5+
////function Foo() {
6+
//// this.a = 0;
7+
////}
8+
////Foo.prototype[`b`] = function () {
9+
////}
10+
11+
verify.codeFix({
12+
description: "Convert function to an ES2015 class",
13+
newFileContent:
14+
`class Foo {
15+
constructor() {
16+
this.a = 0;
17+
}
18+
b() {
19+
}
20+
}
21+
`
22+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowNonTsExtensions: true
4+
// @Filename: foo.js
5+
////function Foo() {
6+
//// this.a = 0;
7+
////}
8+
////Foo["b"] = function () {
9+
////}
10+
11+
verify.codeFix({
12+
description: "Convert function to an ES2015 class",
13+
newFileContent:
14+
`class Foo {
15+
constructor() {
16+
this.a = 0;
17+
}
18+
static b() {
19+
}
20+
}
21+
`
22+
});

0 commit comments

Comments
 (0)