Skip to content

Commit a49e83f

Browse files
author
Andy
authored
annotateWithTypeFromJSDoc: Use changes.insertTypeAnnotation instead of replaceNode (#22404)
1 parent e4610e3 commit a49e83f

7 files changed

+53
-84
lines changed

src/compiler/utilities.ts

+13-24
Original file line numberDiff line numberDiff line change
@@ -2957,49 +2957,38 @@ namespace ts {
29572957
* Gets the effective type annotation of a variable, parameter, or property. If the node was
29582958
* parsed in a JavaScript file, gets the type annotation from JSDoc.
29592959
*/
2960-
export function getEffectiveTypeAnnotationNode(node: Node, checkJSDoc?: boolean): TypeNode | undefined {
2961-
if (hasType(node)) {
2962-
return node.type;
2963-
}
2964-
if (checkJSDoc || isInJavaScriptFile(node)) {
2965-
return getJSDocType(node);
2966-
}
2960+
export function getEffectiveTypeAnnotationNode(node: Node): TypeNode | undefined {
2961+
return (node as HasType).type || (isInJavaScriptFile(node) ? getJSDocType(node) : undefined);
29672962
}
29682963

29692964
/**
29702965
* Gets the effective return type annotation of a signature. If the node was parsed in a
29712966
* JavaScript file, gets the return type annotation from JSDoc.
29722967
*/
2973-
export function getEffectiveReturnTypeNode(node: SignatureDeclaration, checkJSDoc?: boolean): TypeNode | undefined {
2974-
if (node.type) {
2975-
return node.type;
2976-
}
2977-
if (checkJSDoc || isInJavaScriptFile(node)) {
2978-
return getJSDocReturnType(node);
2979-
}
2968+
export function getEffectiveReturnTypeNode(node: SignatureDeclaration): TypeNode | undefined {
2969+
return node.type || (isInJavaScriptFile(node) ? getJSDocReturnType(node) : undefined);
29802970
}
29812971

29822972
/**
29832973
* Gets the effective type parameters. If the node was parsed in a
29842974
* JavaScript file, gets the type parameters from the `@template` tag from JSDoc.
29852975
*/
2986-
export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters, checkJSDoc?: boolean): ReadonlyArray<TypeParameterDeclaration> {
2987-
if (node.typeParameters) {
2988-
return node.typeParameters;
2989-
}
2990-
if (checkJSDoc || isInJavaScriptFile(node)) {
2991-
const templateTag = getJSDocTemplateTag(node);
2992-
return templateTag && templateTag.typeParameters;
2993-
}
2976+
export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray<TypeParameterDeclaration> | undefined {
2977+
return node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined);
2978+
}
2979+
2980+
export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray<TypeParameterDeclaration> {
2981+
const templateTag = getJSDocTemplateTag(node);
2982+
return templateTag && templateTag.typeParameters;
29942983
}
29952984

29962985
/**
29972986
* Gets the effective type annotation of the value parameter of a set accessor. If the node
29982987
* was parsed in a JavaScript file, gets the type annotation from JSDoc.
29992988
*/
3000-
export function getEffectiveSetAccessorTypeAnnotationNode(node: SetAccessorDeclaration, checkJSDoc?: boolean): TypeNode {
2989+
export function getEffectiveSetAccessorTypeAnnotationNode(node: SetAccessorDeclaration): TypeNode {
30012990
const parameter = getSetAccessorValueParameter(node);
3002-
return parameter && getEffectiveTypeAnnotationNode(parameter, checkJSDoc);
2991+
return parameter && getEffectiveTypeAnnotationNode(parameter);
30032992
}
30042993

30052994
export function emitNewLineBeforeLeadingComments(lineMap: ReadonlyArray<number>, writer: EmitTextWriter, node: TextRange, leadingComments: ReadonlyArray<CommentRange>) {

src/services/codefixes/annotateWithTypeFromJSDoc.ts

+27-56
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,34 @@ namespace ts.codefix {
4242

4343
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, decl: DeclarationWithType): void {
4444
if (isFunctionLikeDeclaration(decl) && (getJSDocReturnType(decl) || decl.parameters.some(p => !!getJSDocType(p)))) {
45-
findAncestor(decl, isFunctionLike);
46-
const fn = findAncestor(decl, isFunctionLikeDeclaration);
47-
const functionWithType = addTypesToFunctionLike(fn);
48-
suppressLeadingAndTrailingTrivia(functionWithType);
49-
changes.replaceNode(sourceFile, fn, functionWithType, textChanges.useNonAdjustedPositions);
50-
return;
45+
const typeParameters = getJSDocTypeParameterDeclarations(decl);
46+
const returnType = getJSDocReturnType(decl);
47+
const returnTypeNode = returnType && transformJSDocType(returnType);
48+
49+
if (isArrowFunction(decl) && !findChildOfKind(decl, SyntaxKind.OpenParenToken, sourceFile)) {
50+
const params = decl.parameters.map(p => {
51+
const paramType = getJSDocType(p);
52+
return paramType && !p.type ? updateParameter(p, p.decorators, p.modifiers, p.dotDotDotToken, p.name, p.questionToken, transformJSDocType(paramType), p.initializer) : p;
53+
});
54+
changes.replaceNode(sourceFile, decl, updateArrowFunction(decl, decl.modifiers, decl.typeParameters || typeParameters, params, decl.type || returnTypeNode, decl.equalsGreaterThanToken, decl.body));
55+
}
56+
else {
57+
if (typeParameters && !decl.typeParameters) {
58+
changes.insertTypeParameters(sourceFile, decl, typeParameters);
59+
}
60+
for (const param of decl.parameters) {
61+
if (!param.type) {
62+
const paramType = getJSDocType(param);
63+
if (paramType) changes.insertTypeAnnotation(sourceFile, param, transformJSDocType(paramType));
64+
}
65+
}
66+
if (returnTypeNode && !decl.type) changes.insertTypeAnnotation(sourceFile, decl, returnTypeNode);
67+
}
5168
}
5269
else {
5370
const jsdocType = Debug.assertDefined(getJSDocType(decl)); // If not defined, shouldn't have been an error to fix
5471
Debug.assert(!decl.type); // If defined, shouldn't have been an error to fix.
55-
const declarationWithType = addType(decl, transformJSDocType(jsdocType) as TypeNode);
56-
suppressLeadingAndTrailingTrivia(declarationWithType);
57-
changes.replaceNode(sourceFile, decl, declarationWithType, textChanges.useNonAdjustedPositions);
72+
changes.insertTypeAnnotation(sourceFile, decl, transformJSDocType(jsdocType));
5873
}
5974
}
6075

@@ -65,48 +80,7 @@ namespace ts.codefix {
6580
node.kind === SyntaxKind.PropertyDeclaration;
6681
}
6782

68-
function addTypesToFunctionLike(decl: FunctionLikeDeclaration) {
69-
const typeParameters = getEffectiveTypeParameterDeclarations(decl, /*checkJSDoc*/ true);
70-
const parameters = decl.parameters.map(
71-
p => createParameter(p.decorators, p.modifiers, p.dotDotDotToken, p.name, p.questionToken, transformJSDocType(getEffectiveTypeAnnotationNode(p, /*checkJSDoc*/ true)) as TypeNode, p.initializer));
72-
const returnType = transformJSDocType(getEffectiveReturnTypeNode(decl, /*checkJSDoc*/ true)) as TypeNode;
73-
switch (decl.kind) {
74-
case SyntaxKind.FunctionDeclaration:
75-
return createFunctionDeclaration(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, typeParameters, parameters, returnType, decl.body);
76-
case SyntaxKind.Constructor:
77-
return createConstructor(decl.decorators, decl.modifiers, parameters, decl.body);
78-
case SyntaxKind.FunctionExpression:
79-
return createFunctionExpression(decl.modifiers, decl.asteriskToken, decl.name, typeParameters, parameters, returnType, decl.body);
80-
case SyntaxKind.ArrowFunction:
81-
return createArrowFunction(decl.modifiers, typeParameters, parameters, returnType, decl.equalsGreaterThanToken, decl.body);
82-
case SyntaxKind.MethodDeclaration:
83-
return createMethod(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.questionToken, typeParameters, parameters, returnType, decl.body);
84-
case SyntaxKind.GetAccessor:
85-
return createGetAccessor(decl.decorators, decl.modifiers, decl.name, decl.parameters, returnType, decl.body);
86-
case SyntaxKind.SetAccessor:
87-
return createSetAccessor(decl.decorators, decl.modifiers, decl.name, parameters, decl.body);
88-
default:
89-
return Debug.assertNever(decl, `Unexpected SyntaxKind: ${(decl as any).kind}`);
90-
}
91-
}
92-
93-
function addType(decl: DeclarationWithType, jsdocType: TypeNode) {
94-
switch (decl.kind) {
95-
case SyntaxKind.VariableDeclaration:
96-
return createVariableDeclaration(decl.name, jsdocType, decl.initializer);
97-
case SyntaxKind.PropertySignature:
98-
return createPropertySignature(decl.modifiers, decl.name, decl.questionToken, jsdocType, decl.initializer);
99-
case SyntaxKind.PropertyDeclaration:
100-
return createProperty(decl.decorators, decl.modifiers, decl.name, decl.questionToken, jsdocType, decl.initializer);
101-
default:
102-
return Debug.fail(`Unexpected SyntaxKind: ${decl.kind}`);
103-
}
104-
}
105-
106-
function transformJSDocType(node: Node): Node | undefined {
107-
if (node === undefined) {
108-
return undefined;
109-
}
83+
function transformJSDocType(node: TypeNode): TypeNode | undefined {
11084
switch (node.kind) {
11185
case SyntaxKind.JSDocAllType:
11286
case SyntaxKind.JSDocUnknownType:
@@ -121,12 +95,10 @@ namespace ts.codefix {
12195
return transformJSDocVariadicType(node as JSDocVariadicType);
12296
case SyntaxKind.JSDocFunctionType:
12397
return transformJSDocFunctionType(node as JSDocFunctionType);
124-
case SyntaxKind.Parameter:
125-
return transformJSDocParameter(node as ParameterDeclaration);
12698
case SyntaxKind.TypeReference:
12799
return transformJSDocTypeReference(node as TypeReferenceNode);
128100
default:
129-
const visited = visitEachChild(node, transformJSDocType, /*context*/ undefined) as TypeNode;
101+
const visited = visitEachChild(node, transformJSDocType, /*context*/ undefined);
130102
setEmitFlags(visited, EmitFlags.SingleLine);
131103
return visited;
132104
}
@@ -145,8 +117,7 @@ namespace ts.codefix {
145117
}
146118

147119
function transformJSDocFunctionType(node: JSDocFunctionType) {
148-
const parameters = node.parameters && node.parameters.map(transformJSDocType);
149-
return createFunctionTypeNode(emptyArray, parameters as ParameterDeclaration[], node.type);
120+
return createFunctionTypeNode(emptyArray, node.parameters.map(transformJSDocParameter), node.type);
150121
}
151122

152123
function transformJSDocParameter(node: ParameterDeclaration) {

src/services/textChanges.ts

+9
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,10 @@ namespace ts.textChanges {
327327
return this;
328328
}
329329

330+
private insertNodesAt(sourceFile: SourceFile, pos: number, newNodes: ReadonlyArray<Node>, options: InsertNodeOptions = {}): void {
331+
this.changes.push({ kind: ChangeKind.ReplaceWithMultipleNodes, sourceFile, options, nodes: newNodes, range: { pos, end: pos } });
332+
}
333+
330334
public insertNodeAtTopOfFile(sourceFile: SourceFile, newNode: Statement, blankLineBetween: boolean): void {
331335
const pos = getInsertionPositionAtSourceFileTop(sourceFile);
332336
this.insertNodeAt(sourceFile, pos, newNode, {
@@ -353,6 +357,11 @@ namespace ts.textChanges {
353357
this.insertNodeAt(sourceFile, end, type, { prefix: ": " });
354358
}
355359

360+
public insertTypeParameters(sourceFile: SourceFile, node: SignatureDeclaration, typeParameters: ReadonlyArray<TypeParameterDeclaration>): void {
361+
const lparen = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile)!.pos;
362+
this.insertNodesAt(sourceFile, lparen, typeParameters, { prefix: "<", suffix: ">" });
363+
}
364+
356365
private getOptionsForInsertNodeBefore(before: Node, doubleNewlines: boolean): ChangeNodeOptions {
357366
if (isStatement(before) || isClassElement(before)) {
358367
return { suffix: doubleNewlines ? this.newLineCharacter + this.newLineCharacter : this.newLineCharacter };

tests/cases/fourslash/annotateWithTypeFromJSDoc13.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ verify.codeFix({
99
newFileContent:
1010
`class C {
1111
/** @return {number} */
12-
get c(): number { return 12; }
12+
get c(): number { return 12 }
1313
}`,
1414
});

tests/cases/fourslash/annotateWithTypeFromJSDoc18.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ verify.codeFix({
99
newFileContent:
1010
`class C {
1111
/** @param {number} value */
12-
set c(value: number) { return 12; }
12+
set c(value: number) { return 12 }
1313
}`,
1414
});

tests/cases/fourslash/annotateWithTypeFromJSDoc5.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ verify.codeFix({
1010
newFileContent:
1111
`class C {
1212
/** @type {number | null} */
13-
p: number | null = null;
13+
p: number | null = null
1414
}`,
1515
});

tests/cases/fourslash/annotateWithTypeFromJSDoc8.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ verify.codeFix({
1414
* @param {number} x
1515
* @returns {number}
1616
*/
17-
var f = function(x: number): number {
17+
var f = function (x: number): number {
1818
}`,
1919
});

0 commit comments

Comments
 (0)