Skip to content

Commit 3054500

Browse files
a-tarasyukDanielRosenwasser
authored andcommitted
fix(36068): Incorrect quick fix for undeclared private field i… (#36373)
1 parent 18cd79e commit 3054500

File tree

3 files changed

+65
-8
lines changed

3 files changed

+65
-8
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5064,6 +5064,10 @@
50645064
"category": "Message",
50655065
"code": 90034
50665066
},
5067+
"Declare a private field named '{0}'.": {
5068+
"category": "Message",
5069+
"code": 90053
5070+
},
50675071
"Convert function to an ES2015 class": {
50685072
"category": "Message",
50695073
"code": 95001

src/services/codefixes/fixAddMissingMember.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ namespace ts.codefix {
2323
const { parentDeclaration, declSourceFile, inJs, makeStatic, token, call } = info;
2424
const methodCodeAction = call && getActionForMethodDeclaration(context, declSourceFile, parentDeclaration, token, call, makeStatic, inJs, context.preferences);
2525
const addMember = inJs && !isInterfaceDeclaration(parentDeclaration) ?
26-
singleElementArray(getActionsForAddMissingMemberInJavascriptFile(context, declSourceFile, parentDeclaration, token.text, makeStatic)) :
26+
singleElementArray(getActionsForAddMissingMemberInJavascriptFile(context, declSourceFile, parentDeclaration, token, makeStatic)) :
2727
getActionsForAddMissingMemberInTypeScriptFile(context, declSourceFile, parentDeclaration, token, makeStatic);
2828
return concatenate(singleElementArray(methodCodeAction), addMember);
2929
},
@@ -70,7 +70,7 @@ namespace ts.codefix {
7070
}
7171
else {
7272
if (inJs && !isInterfaceDeclaration(parentDeclaration)) {
73-
addMissingMemberInJs(changes, declSourceFile, parentDeclaration, token.text, makeStatic);
73+
addMissingMemberInJs(changes, declSourceFile, parentDeclaration, token, makeStatic);
7474
}
7575
else {
7676
const typeNode = getTypeNode(program.getTypeChecker(), parentDeclaration, token);
@@ -140,7 +140,7 @@ namespace ts.codefix {
140140
if (classOrInterface && !program.isSourceFileFromExternalLibrary(classOrInterface.getSourceFile())) {
141141
const makeStatic = ((leftExpressionType as TypeReference).target || leftExpressionType) !== checker.getDeclaredTypeOfSymbol(symbol);
142142
// Static private identifier properties are not supported yet.
143-
if (makeStatic && isPrivateIdentifier(token)) { return undefined; }
143+
if (makeStatic && isPrivateIdentifier(token)) return undefined;
144144
const declSourceFile = classOrInterface.getSourceFile();
145145
const inJs = isSourceFileJS(declSourceFile);
146146
const call = tryCast(parent.parent, isCallExpression);
@@ -153,13 +153,20 @@ namespace ts.codefix {
153153
return undefined;
154154
}
155155

156-
function getActionsForAddMissingMemberInJavascriptFile(context: CodeFixContext, declSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined {
157-
const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, declSourceFile, classDeclaration, tokenName, makeStatic));
158-
return changes.length === 0 ? undefined
159-
: createCodeFixAction(fixName, changes, [makeStatic ? Diagnostics.Initialize_static_property_0 : Diagnostics.Initialize_property_0_in_the_constructor, tokenName], fixId, Diagnostics.Add_all_missing_members);
156+
function getActionsForAddMissingMemberInJavascriptFile(context: CodeFixContext, declSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier | PrivateIdentifier, makeStatic: boolean): CodeFixAction | undefined {
157+
const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, declSourceFile, classDeclaration, token, makeStatic));
158+
if (changes.length === 0) {
159+
return undefined;
160+
}
161+
162+
const diagnostic = makeStatic ? Diagnostics.Initialize_static_property_0 :
163+
isPrivateIdentifier(token) ? Diagnostics.Declare_a_private_field_named_0 : Diagnostics.Initialize_property_0_in_the_constructor;
164+
165+
return createCodeFixAction(fixName, changes, [diagnostic, token.text], fixId, Diagnostics.Add_all_missing_members);
160166
}
161167

162-
function addMissingMemberInJs(changeTracker: textChanges.ChangeTracker, declSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): void {
168+
function addMissingMemberInJs(changeTracker: textChanges.ChangeTracker, declSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier | PrivateIdentifier, makeStatic: boolean): void {
169+
const tokenName = token.text;
163170
if (makeStatic) {
164171
if (classDeclaration.kind === SyntaxKind.ClassExpression) {
165172
return;
@@ -168,6 +175,23 @@ namespace ts.codefix {
168175
const staticInitialization = initializePropertyToUndefined(createIdentifier(className), tokenName);
169176
changeTracker.insertNodeAfter(declSourceFile, classDeclaration, staticInitialization);
170177
}
178+
else if (isPrivateIdentifier(token)) {
179+
const property = createProperty(
180+
/*decorators*/ undefined,
181+
/*modifiers*/ undefined,
182+
tokenName,
183+
/*questionToken*/ undefined,
184+
/*type*/ undefined,
185+
/*initializer*/ undefined);
186+
187+
const lastProp = getNodeToInsertPropertyAfter(classDeclaration);
188+
if (lastProp) {
189+
changeTracker.insertNodeAfter(declSourceFile, lastProp, property);
190+
}
191+
else {
192+
changeTracker.insertNodeAtClassStart(declSourceFile, classDeclaration, property);
193+
}
194+
}
171195
else {
172196
const classConstructor = getFirstConstructorWithBody(classDeclaration);
173197
if (!classConstructor) {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowjs: true
4+
// @checkJs: true
5+
6+
// @Filename: /a.js
7+
////class Foo {
8+
//// constructor(name) {
9+
//// this.[|#name|] = name;
10+
//// }
11+
////}
12+
13+
verify.codeFixAvailable([
14+
{ description: "Declare a private field named '#name'." },
15+
{ description: "Ignore this error message" },
16+
{ description: "Disable checking for this file" },
17+
{ description: "Infer parameter types from usage" },
18+
]);
19+
20+
verify.codeFix({
21+
index: 0,
22+
description: [ts.Diagnostics.Declare_a_private_field_named_0.message, '#name'],
23+
newFileContent: `class Foo {
24+
#name;
25+
constructor(name) {
26+
this.#name = name;
27+
}
28+
}`
29+
});

0 commit comments

Comments
 (0)