Skip to content

Commit 586ac95

Browse files
stkevintanKingwlsandersn
authored andcommitted
implement code fix for override of js files (microsoft#45780)
* feat: code fix for override in js files Co-Authored-By: Wenlu Wang <[email protected]> * fix comments Co-Authored-By: Wenlu Wang <[email protected]> * remove tryMergeJsdocTags * fix: bring the two methods back as functions * revert emitter changes * fix comments * fix: test failures Co-authored-by: Wenlu Wang <[email protected]> Co-authored-by: Nathan Shively-Sanders <[email protected]>
1 parent fea559e commit 586ac95

File tree

6 files changed

+177
-75
lines changed

6 files changed

+177
-75
lines changed

src/compiler/emitter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1604,6 +1604,7 @@ namespace ts {
16041604
return emitJSDocSignature(node as JSDocSignature);
16051605
case SyntaxKind.JSDocTag:
16061606
case SyntaxKind.JSDocClassTag:
1607+
case SyntaxKind.JSDocOverrideTag:
16071608
return emitJSDocSimpleTag(node as JSDocTag);
16081609
case SyntaxKind.JSDocAugmentsTag:
16091610
case SyntaxKind.JSDocImplementsTag:
@@ -1616,7 +1617,6 @@ namespace ts {
16161617
case SyntaxKind.JSDocPrivateTag:
16171618
case SyntaxKind.JSDocProtectedTag:
16181619
case SyntaxKind.JSDocReadonlyTag:
1619-
case SyntaxKind.JSDocOverrideTag:
16201620
return;
16211621
case SyntaxKind.JSDocCallbackTag:
16221622
return emitJSDocCallbackTag(node as JSDocCallbackTag);

src/services/codefixes/fixOverrideModifier.ts

+78-22
Original file line numberDiff line numberDiff line change
@@ -17,37 +17,81 @@ namespace ts.codefix {
1717
Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class.code,
1818
Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0.code,
1919
Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0.code,
20-
Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0.code
20+
Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0.code,
21+
Diagnostics.This_member_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0.code,
22+
Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_containing_class_0_does_not_extend_another_class.code,
23+
Diagnostics.This_parameter_property_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0.code,
24+
Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0.code,
2125
];
2226

23-
const errorCodeFixIdMap: Record<number, [DiagnosticMessage, string | undefined, DiagnosticMessage | undefined]> = {
24-
[Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0.code]: [
25-
Diagnostics.Add_override_modifier, fixAddOverrideId, Diagnostics.Add_all_missing_override_modifiers,
26-
],
27-
[Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class.code]: [
28-
Diagnostics.Remove_override_modifier, fixRemoveOverrideId, Diagnostics.Remove_all_unnecessary_override_modifiers
29-
],
30-
[Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0.code]: [
31-
Diagnostics.Add_override_modifier, fixAddOverrideId, Diagnostics.Add_all_missing_override_modifiers,
32-
],
33-
[Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0.code]: [
34-
Diagnostics.Add_override_modifier, fixAddOverrideId, Diagnostics.Remove_all_unnecessary_override_modifiers
35-
],
36-
[Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0.code]: [
37-
Diagnostics.Remove_override_modifier, fixRemoveOverrideId, Diagnostics.Remove_all_unnecessary_override_modifiers
38-
]
27+
interface ErrorCodeFixInfo {
28+
descriptions: DiagnosticMessage;
29+
fixId?: string | undefined;
30+
fixAllDescriptions?: DiagnosticMessage | undefined;
31+
}
32+
33+
const errorCodeFixIdMap: Record<number, ErrorCodeFixInfo> = {
34+
// case #1:
35+
[Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0.code]: {
36+
descriptions: Diagnostics.Add_override_modifier,
37+
fixId: fixAddOverrideId,
38+
fixAllDescriptions: Diagnostics.Add_all_missing_override_modifiers,
39+
},
40+
[Diagnostics.This_member_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0.code]: {
41+
descriptions: Diagnostics.Add_override_modifier,
42+
fixId: fixAddOverrideId,
43+
fixAllDescriptions: Diagnostics.Add_all_missing_override_modifiers
44+
},
45+
// case #2:
46+
[Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class.code]: {
47+
descriptions: Diagnostics.Remove_override_modifier,
48+
fixId: fixRemoveOverrideId,
49+
fixAllDescriptions: Diagnostics.Remove_all_unnecessary_override_modifiers,
50+
},
51+
[Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_containing_class_0_does_not_extend_another_class.code]: {
52+
descriptions: Diagnostics.Remove_override_modifier,
53+
fixId: fixRemoveOverrideId,
54+
fixAllDescriptions: Diagnostics.Remove_override_modifier
55+
},
56+
// case #3:
57+
[Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0.code]: {
58+
descriptions: Diagnostics.Add_override_modifier,
59+
fixId: fixAddOverrideId,
60+
fixAllDescriptions: Diagnostics.Add_all_missing_override_modifiers,
61+
},
62+
[Diagnostics.This_parameter_property_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0.code]: {
63+
descriptions: Diagnostics.Add_override_modifier,
64+
fixId: fixAddOverrideId,
65+
fixAllDescriptions: Diagnostics.Add_all_missing_override_modifiers,
66+
},
67+
// case #4:
68+
[Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0.code]: {
69+
descriptions: Diagnostics.Add_override_modifier,
70+
fixId: fixAddOverrideId,
71+
fixAllDescriptions: Diagnostics.Remove_all_unnecessary_override_modifiers,
72+
},
73+
// case #5:
74+
[Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0.code]: {
75+
descriptions: Diagnostics.Remove_override_modifier,
76+
fixId: fixRemoveOverrideId,
77+
fixAllDescriptions: Diagnostics.Remove_all_unnecessary_override_modifiers,
78+
},
79+
[Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0.code]: {
80+
descriptions: Diagnostics.Remove_override_modifier,
81+
fixId: fixRemoveOverrideId,
82+
fixAllDescriptions: Diagnostics.Remove_all_unnecessary_override_modifiers,
83+
}
3984
};
4085

4186
registerCodeFix({
4287
errorCodes,
4388
getCodeActions: context => {
44-
const { errorCode, span, sourceFile } = context;
89+
const { errorCode, span } = context;
4590

4691
const info = errorCodeFixIdMap[errorCode];
4792
if (!info) return emptyArray;
4893

49-
const [ descriptions, fixId, fixAllDescriptions ] = info;
50-
if (isSourceFileJS(sourceFile)) return emptyArray;
94+
const { descriptions, fixId, fixAllDescriptions } = info;
5195
const changes = textChanges.ChangeTracker.with(context, changes => dispatchChanges(changes, context, errorCode, span.start));
5296

5397
return [
@@ -57,9 +101,9 @@ namespace ts.codefix {
57101
fixIds: [fixName, fixAddOverrideId, fixRemoveOverrideId],
58102
getAllCodeActions: context =>
59103
codeFixAll(context, errorCodes, (changes, diag) => {
60-
const { code, start, file } = diag;
104+
const { code, start } = diag;
61105
const info = errorCodeFixIdMap[code];
62-
if (!info || info[1] !== context.fixId || isSourceFileJS(file)) {
106+
if (!info || info.fixId !== context.fixId) {
63107
return;
64108
}
65109

@@ -74,11 +118,15 @@ namespace ts.codefix {
74118
pos: number) {
75119
switch (errorCode) {
76120
case Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0.code:
121+
case Diagnostics.This_member_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0.code:
77122
case Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0.code:
78123
case Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0.code:
124+
case Diagnostics.This_parameter_property_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0.code:
79125
return doAddOverrideModifierChange(changeTracker, context.sourceFile, pos);
80126
case Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0.code:
127+
case Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0.code:
81128
case Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class.code:
129+
case Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_containing_class_0_does_not_extend_another_class.code:
82130
return doRemoveOverrideModifierChange(changeTracker, context.sourceFile, pos);
83131
default:
84132
Debug.fail("Unexpected error code: " + errorCode);
@@ -87,6 +135,10 @@ namespace ts.codefix {
87135

88136
function doAddOverrideModifierChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) {
89137
const classElement = findContainerClassElementLike(sourceFile, pos);
138+
if (isSourceFileJS(sourceFile)) {
139+
changeTracker.addJSDocTags(sourceFile, classElement, [factory.createJSDocOverrideTag(factory.createIdentifier("override"))]);
140+
return;
141+
}
90142
const modifiers = classElement.modifiers || emptyArray;
91143
const staticModifier = find(modifiers, isStaticModifier);
92144
const abstractModifier = find(modifiers, isAbstractModifier);
@@ -101,6 +153,10 @@ namespace ts.codefix {
101153

102154
function doRemoveOverrideModifierChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) {
103155
const classElement = findContainerClassElementLike(sourceFile, pos);
156+
if (isSourceFileJS(sourceFile)) {
157+
changeTracker.filterJSDocTags(sourceFile, classElement, not(isJSDocOverrideTag));
158+
return;
159+
}
104160
const overrideModifier = classElement.modifiers && find(classElement.modifiers, modifier => modifier.kind === SyntaxKind.OverrideKeyword);
105161
Debug.assertIsDefined(overrideModifier);
106162

src/services/codefixes/inferFromUsage.ts

+4-43
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ namespace ts.codefix {
131131
if (typeNode) {
132132
// Note that the codefix will never fire with an existing `@type` tag, so there is no need to merge tags
133133
const typeTag = factory.createJSDocTypeTag(/*tagName*/ undefined, factory.createJSDocTypeExpression(typeNode), /*comment*/ undefined);
134-
addJSDocTags(changes, sourceFile, cast(parent.parent.parent, isExpressionStatement), [typeTag]);
134+
changes.addJSDocTags(sourceFile, cast(parent.parent.parent, isExpressionStatement), [typeTag]);
135135
}
136136
importAdder.writeFixes(changes);
137137
return parent;
@@ -271,7 +271,7 @@ namespace ts.codefix {
271271
}
272272

273273
function annotateJSDocThis(changes: textChanges.ChangeTracker, sourceFile: SourceFile, containingFunction: SignatureDeclaration, typeNode: TypeNode) {
274-
addJSDocTags(changes, sourceFile, containingFunction, [
274+
changes.addJSDocTags(sourceFile, containingFunction, [
275275
factory.createJSDocThisTag(/*tagName*/ undefined, factory.createJSDocTypeExpression(typeNode)),
276276
]);
277277
}
@@ -311,7 +311,7 @@ namespace ts.codefix {
311311
}
312312
const typeExpression = factory.createJSDocTypeExpression(typeNode);
313313
const typeTag = isGetAccessorDeclaration(declaration) ? factory.createJSDocReturnTag(/*tagName*/ undefined, typeExpression, /*comment*/ undefined) : factory.createJSDocTypeTag(/*tagName*/ undefined, typeExpression, /*comment*/ undefined);
314-
addJSDocTags(changes, sourceFile, parent, [typeTag]);
314+
changes.addJSDocTags(sourceFile, parent, [typeTag]);
315315
}
316316
else if (!tryReplaceImportTypeNodeWithAutoImport(typeNode, declaration, sourceFile, changes, importAdder, getEmitScriptTarget(program.getCompilerOptions()))) {
317317
changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode);
@@ -378,46 +378,7 @@ namespace ts.codefix {
378378
else {
379379
const paramTags = map(inferences, ({ name, typeNode, isOptional }) =>
380380
factory.createJSDocParameterTag(/*tagName*/ undefined, name, /*isBracketed*/ !!isOptional, factory.createJSDocTypeExpression(typeNode), /* isNameFirst */ false, /*comment*/ undefined));
381-
addJSDocTags(changes, sourceFile, signature, paramTags);
382-
}
383-
}
384-
385-
export function addJSDocTags(changes: textChanges.ChangeTracker, sourceFile: SourceFile, parent: HasJSDoc, newTags: readonly JSDocTag[]): void {
386-
const comments = flatMap(parent.jsDoc, j => typeof j.comment === "string" ? factory.createJSDocText(j.comment) : j.comment) as JSDocComment[];
387-
const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags);
388-
const unmergedNewTags = newTags.filter(newTag => !oldTags || !oldTags.some((tag, i) => {
389-
const merged = tryMergeJsdocTags(tag, newTag);
390-
if (merged) oldTags[i] = merged;
391-
return !!merged;
392-
}));
393-
const tag = factory.createJSDocComment(factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))), factory.createNodeArray([...(oldTags || emptyArray), ...unmergedNewTags]));
394-
const jsDocNode = parent.kind === SyntaxKind.ArrowFunction ? getJsDocNodeForArrowFunction(parent) : parent;
395-
jsDocNode.jsDoc = parent.jsDoc;
396-
jsDocNode.jsDocCache = parent.jsDocCache;
397-
changes.insertJsdocCommentBefore(sourceFile, jsDocNode, tag);
398-
}
399-
400-
function getJsDocNodeForArrowFunction(signature: ArrowFunction): HasJSDoc {
401-
if (signature.parent.kind === SyntaxKind.PropertyDeclaration) {
402-
return signature.parent as HasJSDoc;
403-
}
404-
return signature.parent.parent as HasJSDoc;
405-
}
406-
407-
function tryMergeJsdocTags(oldTag: JSDocTag, newTag: JSDocTag): JSDocTag | undefined {
408-
if (oldTag.kind !== newTag.kind) {
409-
return undefined;
410-
}
411-
switch (oldTag.kind) {
412-
case SyntaxKind.JSDocParameterTag: {
413-
const oldParam = oldTag as JSDocParameterTag;
414-
const newParam = newTag as JSDocParameterTag;
415-
return isIdentifier(oldParam.name) && isIdentifier(newParam.name) && oldParam.name.escapedText === newParam.name.escapedText
416-
? factory.createJSDocParameterTag(/*tagName*/ undefined, newParam.name, /*isBracketed*/ false, newParam.typeExpression, newParam.isNameFirst, oldParam.comment)
417-
: undefined;
418-
}
419-
case SyntaxKind.JSDocReturnTag:
420-
return factory.createJSDocReturnTag(/*tagName*/ undefined, (newTag as JSDocReturnTag).typeExpression, oldTag.comment);
381+
changes.addJSDocTags(sourceFile, signature, paramTags);
421382
}
422383
}
423384

src/services/textChanges.ts

+50
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,27 @@ namespace ts.textChanges {
494494
this.insertNodeAt(sourceFile, fnStart, tag, { preserveLeadingWhitespace: false, suffix: this.newLineCharacter + indent });
495495
}
496496

497+
public addJSDocTags(sourceFile: SourceFile, parent: HasJSDoc, newTags: readonly JSDocTag[]): void {
498+
const comments = flatMap(parent.jsDoc, j => typeof j.comment === "string" ? factory.createJSDocText(j.comment) : j.comment) as JSDocComment[];
499+
const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags);
500+
const unmergedNewTags = newTags.filter(newTag => !oldTags.some((tag, i) => {
501+
const merged = tryMergeJsdocTags(tag, newTag);
502+
if (merged) oldTags[i] = merged;
503+
return !!merged;
504+
}));
505+
const tag = factory.createJSDocComment(factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))), factory.createNodeArray([...oldTags, ...unmergedNewTags]));
506+
const host = updateJSDocHost(parent);
507+
this.insertJsdocCommentBefore(sourceFile, host, tag);
508+
}
509+
510+
public filterJSDocTags(sourceFile: SourceFile, parent: HasJSDoc, predicate: (tag: JSDocTag) => boolean): void {
511+
const comments = flatMap(parent.jsDoc, j => typeof j.comment === "string" ? factory.createJSDocText(j.comment) : j.comment) as JSDocComment[];
512+
const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags);
513+
const tag = factory.createJSDocComment(factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))), factory.createNodeArray([...(filter(oldTags, predicate) || emptyArray)]));
514+
const host = updateJSDocHost(parent);
515+
this.insertJsdocCommentBefore(sourceFile, host, tag);
516+
}
517+
497518
public replaceRangeWithText(sourceFile: SourceFile, range: TextRange, text: string): void {
498519
this.changes.push({ kind: ChangeKind.Text, sourceFile, range, text });
499520
}
@@ -920,6 +941,35 @@ namespace ts.textChanges {
920941
}
921942
}
922943

944+
function updateJSDocHost(parent: HasJSDoc): HasJSDoc {
945+
if (parent.kind !== SyntaxKind.ArrowFunction) {
946+
return parent;
947+
}
948+
const jsDocNode = parent.parent.kind === SyntaxKind.PropertyDeclaration ?
949+
parent.parent as HasJSDoc :
950+
parent.parent.parent as HasJSDoc;
951+
jsDocNode.jsDoc = parent.jsDoc;
952+
jsDocNode.jsDocCache = parent.jsDocCache;
953+
return jsDocNode;
954+
}
955+
956+
function tryMergeJsdocTags(oldTag: JSDocTag, newTag: JSDocTag): JSDocTag | undefined {
957+
if (oldTag.kind !== newTag.kind) {
958+
return undefined;
959+
}
960+
switch (oldTag.kind) {
961+
case SyntaxKind.JSDocParameterTag: {
962+
const oldParam = oldTag as JSDocParameterTag;
963+
const newParam = newTag as JSDocParameterTag;
964+
return isIdentifier(oldParam.name) && isIdentifier(newParam.name) && oldParam.name.escapedText === newParam.name.escapedText
965+
? factory.createJSDocParameterTag(/*tagName*/ undefined, newParam.name, /*isBracketed*/ false, newParam.typeExpression, newParam.isNameFirst, oldParam.comment)
966+
: undefined;
967+
}
968+
case SyntaxKind.JSDocReturnTag:
969+
return factory.createJSDocReturnTag(/*tagName*/ undefined, (newTag as JSDocReturnTag).typeExpression, oldTag.comment);
970+
}
971+
}
972+
923973
// find first non-whitespace position in the leading trivia of the node
924974
function startPositionToDeleteNodeInList(sourceFile: SourceFile, node: Node): number {
925975
return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, { leadingTriviaOption: LeadingTriviaOption.IncludeAll }), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);

0 commit comments

Comments
 (0)