From fc933d7c33bbc14add9f73a4639ee20439293787 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 26 Sep 2017 12:42:08 -0700 Subject: [PATCH] Transform jsdoc types in the refactor, not emitter The emitter now understands JSDoc types but emits them in the original format. --- src/compiler/emitter.ts | 41 +++-- .../refactors/annotateWithTypeFromJSDoc.ts | 109 ++++++++++-- .../fourslash/annotateWithTypeFromJSDoc15.ts | 158 ++++++++++++++++++ 3 files changed, 277 insertions(+), 31 deletions(-) create mode 100644 tests/cases/fourslash/annotateWithTypeFromJSDoc15.ts diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 80b08c60d34..61f9773c7a0 100755 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -545,8 +545,9 @@ namespace ts { case SyntaxKind.TypeReference: return emitTypeReference(node); case SyntaxKind.FunctionType: - case SyntaxKind.JSDocFunctionType: return emitFunctionType(node); + case SyntaxKind.JSDocFunctionType: + return emitJSDocFunctionType(node as JSDocFunctionType); case SyntaxKind.ConstructorType: return emitConstructorType(node); case SyntaxKind.TypeQuery: @@ -576,8 +577,10 @@ namespace ts { case SyntaxKind.LiteralType: return emitLiteralType(node); case SyntaxKind.JSDocAllType: + write("*"); + break; case SyntaxKind.JSDocUnknownType: - write("any"); + write("?"); break; case SyntaxKind.JSDocNullableType: return emitJSDocNullableType(node as JSDocNullableType); @@ -930,14 +933,13 @@ namespace ts { if (node.name) { emit(node.name); } - else if (node.parent.kind === SyntaxKind.JSDocFunctionType) { - const i = (node.parent as JSDocFunctionType).parameters.indexOf(node); - if (i > -1) { - write("arg" + i); - } - } emitIfPresent(node.questionToken); - emitWithPrefix(": ", node.type); + if (node.parent && node.parent.kind === SyntaxKind.JSDocFunctionType && !node.name) { + emit(node.type); + } + else { + emitWithPrefix(": ", node.type); + } emitExpressionWithPrefix(" = ", node.initializer); } @@ -1056,18 +1058,27 @@ namespace ts { emit(node.type); } - function emitJSDocNullableType(node: JSDocNullableType) { + function emitJSDocFunctionType(node: JSDocFunctionType) { + write("function"); + emitParameters(node, node.parameters); + write(":"); + emit(node.type); + } + + + function emitJSDocNullableType(node: JSDocNullableType) { + write("?"); emit(node.type); - write(" | null"); } function emitJSDocNonNullableType(node: JSDocNonNullableType) { + write("!"); emit(node.type); } function emitJSDocOptionalType(node: JSDocOptionalType) { emit(node.type); - write(" | undefined"); + write("="); } function emitConstructorType(node: ConstructorTypeNode) { @@ -1096,8 +1107,8 @@ namespace ts { } function emitJSDocVariadicType(node: JSDocVariadicType) { + write("..."); emit(node.type); - write("[]"); } function emitTupleType(node: TupleTypeNode) { @@ -2397,7 +2408,7 @@ namespace ts { emitList(parentNode, parameters, ListFormat.Parameters); } - function canEmitSimpleArrowHead(parentNode: FunctionTypeNode | ArrowFunction | JSDocFunctionType, parameters: NodeArray) { + function canEmitSimpleArrowHead(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { const parameter = singleOrUndefined(parameters); return parameter && parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter @@ -2414,7 +2425,7 @@ namespace ts { && isIdentifier(parameter.name); // parameter name must be identifier } - function emitParametersForArrow(parentNode: FunctionTypeNode | ArrowFunction | JSDocFunctionType, parameters: NodeArray) { + function emitParametersForArrow(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { if (canEmitSimpleArrowHead(parentNode, parameters)) { emitList(parentNode, parameters, ListFormat.Parameters & ~ListFormat.Parenthesis); } diff --git a/src/services/refactors/annotateWithTypeFromJSDoc.ts b/src/services/refactors/annotateWithTypeFromJSDoc.ts index d8b8e849bb6..dde367f0ac9 100644 --- a/src/services/refactors/annotateWithTypeFromJSDoc.ts +++ b/src/services/refactors/annotateWithTypeFromJSDoc.ts @@ -35,16 +35,16 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { if (decl && !decl.type) { const annotate = getJSDocType(decl) ? annotateTypeFromJSDoc : getJSDocReturnType(decl) ? annotateReturnTypeFromJSDoc : - undefined; + undefined; if (annotate) { return [{ name: annotate.name, description: annotate.description, actions: [ { - description: annotate.description, - name: actionName - } + description: annotate.description, + name: actionName + } ] }]; } @@ -62,10 +62,9 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { const sourceFile = context.file; const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); const decl = findAncestor(token, isTypedNode); - const jsdocType = getJSDocType(decl); - const jsdocReturn = getJSDocReturnType(decl); - if (!decl || !jsdocType && !jsdocReturn || decl.type) { - Debug.fail(`!decl || !jsdocType && !jsdocReturn || decl.type: !${decl} || !${jsdocType} && !{jsdocReturn} || ${decl.type}`); + const jsdocType = getJSDocReturnType(decl) || getJSDocType(decl); + if (!decl || !jsdocType || decl.type) { + Debug.fail(`!decl || !jsdocType || decl.type: !${decl} || !${jsdocType} || ${decl.type}`); return undefined; } @@ -76,12 +75,12 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { // other syntax changes const arrow = decl.parent as ArrowFunction; const param = decl as ParameterDeclaration; - const replacementParam = createParameter(param.decorators, param.modifiers, param.dotDotDotToken, param.name, param.questionToken, jsdocType, param.initializer); + const replacementParam = createParameter(param.decorators, param.modifiers, param.dotDotDotToken, param.name, param.questionToken, transformJSDocType(jsdocType) as TypeNode, param.initializer); const replacement = createArrowFunction(arrow.modifiers, arrow.typeParameters, [replacementParam], arrow.type, arrow.equalsGreaterThanToken, arrow.body); changeTracker.replaceRange(sourceFile, { pos: arrow.getStart(), end: arrow.end }, replacement); } else { - changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, replaceType(decl, jsdocType, jsdocReturn)); + changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, replaceType(decl, transformJSDocType(jsdocType) as TypeNode)); } return { edits: changeTracker.getChanges(), @@ -98,7 +97,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { node.kind === SyntaxKind.PropertyDeclaration; } - function replaceType(decl: DeclarationWithType, jsdocType: TypeNode, jsdocReturn: TypeNode) { + function replaceType(decl: DeclarationWithType, jsdocType: TypeNode) { switch (decl.kind) { case SyntaxKind.VariableDeclaration: return createVariableDeclaration(decl.name, jsdocType, decl.initializer); @@ -109,15 +108,15 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { case SyntaxKind.PropertyDeclaration: return createProperty(decl.decorators, decl.modifiers, decl.name, decl.questionToken, jsdocType, decl.initializer); case SyntaxKind.FunctionDeclaration: - return createFunctionDeclaration(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.typeParameters, decl.parameters, jsdocReturn, decl.body); + return createFunctionDeclaration(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.typeParameters, decl.parameters, jsdocType, decl.body); case SyntaxKind.FunctionExpression: - return createFunctionExpression(decl.modifiers, decl.asteriskToken, decl.name, decl.typeParameters, decl.parameters, jsdocReturn, decl.body); + return createFunctionExpression(decl.modifiers, decl.asteriskToken, decl.name, decl.typeParameters, decl.parameters, jsdocType, decl.body); case SyntaxKind.ArrowFunction: - return createArrowFunction(decl.modifiers, decl.typeParameters, decl.parameters, jsdocReturn, decl.equalsGreaterThanToken, decl.body); + return createArrowFunction(decl.modifiers, decl.typeParameters, decl.parameters, jsdocType, decl.equalsGreaterThanToken, decl.body); case SyntaxKind.MethodDeclaration: - return createMethod(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.questionToken, decl.typeParameters, decl.parameters, jsdocReturn, decl.body); + return createMethod(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.questionToken, decl.typeParameters, decl.parameters, jsdocType, decl.body); case SyntaxKind.GetAccessor: - return createGetAccessor(decl.decorators, decl.modifiers, decl.name, decl.parameters, jsdocReturn, decl.body); + return createGetAccessor(decl.decorators, decl.modifiers, decl.name, decl.parameters, jsdocType, decl.body); default: Debug.fail(`Unexpected SyntaxKind: ${decl.kind}`); return undefined; @@ -144,4 +143,82 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { && !parameter.initializer // parameter may not have an initializer && isIdentifier(parameter.name); // parameter name must be identifier } + + function transformJSDocType(node: Node): Node | undefined { + if (node === undefined) { + return undefined; + } + switch (node.kind) { + case SyntaxKind.JSDocAllType: + case SyntaxKind.JSDocUnknownType: + return createTypeReferenceNode("any", emptyArray); + case SyntaxKind.JSDocOptionalType: + return visitJSDocOptionalType(node as JSDocOptionalType); + case SyntaxKind.JSDocNonNullableType: + return transformJSDocType((node as JSDocNonNullableType).type); + case SyntaxKind.JSDocNullableType: + return visitJSDocNullableType(node as JSDocNullableType); + case SyntaxKind.JSDocVariadicType: + return visitJSDocVariadicType(node as JSDocVariadicType); + case SyntaxKind.JSDocFunctionType: + return visitJSDocFunctionType(node as JSDocFunctionType); + case SyntaxKind.Parameter: + return visitJSDocParameter(node as ParameterDeclaration); + case SyntaxKind.TypeReference: + return visitJSDocTypeReference(node as TypeReferenceNode); + default: + return visitEachChild(node, transformJSDocType, /*context*/ undefined) as TypeNode; + } + } + + function visitJSDocOptionalType(node: JSDocOptionalType) { + return createUnionTypeNode([visitNode(node.type, transformJSDocType), createTypeReferenceNode("undefined", emptyArray)]); + } + + function visitJSDocNullableType(node: JSDocNullableType) { + return createUnionTypeNode([visitNode(node.type, transformJSDocType), createTypeReferenceNode("null", emptyArray)]); + } + + function visitJSDocVariadicType(node: JSDocVariadicType) { + return createArrayTypeNode(visitNode(node.type, transformJSDocType)); + } + + function visitJSDocFunctionType(node: JSDocFunctionType) { + const parameters = node.parameters && node.parameters.map(transformJSDocType); + return createFunctionTypeNode(emptyArray, parameters as ParameterDeclaration[], node.type); + } + + function visitJSDocParameter(node: ParameterDeclaration) { + const name = node.name || "arg" + node.parent.parameters.indexOf(node); + return createParameter(node.decorators, node.modifiers, node.dotDotDotToken, name, node.questionToken, node.type, node.initializer); + } + + function visitJSDocTypeReference(node: TypeReferenceNode) { + let name = node.typeName; + let args = node.typeArguments; + if (isIdentifier(node.typeName)) { + let text = node.typeName.text; + switch (node.typeName.text) { + case "String": + case "Boolean": + case "Object": + case "Number": + text = text.toLowerCase(); + break; + case "array": + case "date": + case "promise": + text = text[0].toUpperCase() + text.slice(1); + break; + } + name = createIdentifier(text); + if ((text === "Array" || text === "Promise") && !node.typeArguments) { + args = createNodeArray([createTypeReferenceNode("any", emptyArray)]); + } + else { + args = visitNodes(node.typeArguments, transformJSDocType); + } + } + return createTypeReferenceNode(name, args); + } } diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc15.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc15.ts new file mode 100644 index 00000000000..487b456d561 --- /dev/null +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc15.ts @@ -0,0 +1,158 @@ +/// +// @strict: true +/////** +//// * @param {Boolean} x +//// * @param {String} y +//// * @param {Number} z +//// * @param {Object} alpha +//// * @param {date} beta +//// * @param {promise} gamma +//// * @param {array} delta +//// * @param {Array} epsilon +//// * @param {promise} zeta +//// */ +////function f(/*1*/x, /*2*/y, /*3*/z, /*4*/alpha, /*5*/beta, /*6*/gamma, /*7*/delta, /*8*/epsilon, /*9*/zeta) { +////} +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`/** + * @param {Boolean} x + * @param {String} y + * @param {Number} z + * @param {Object} alpha + * @param {date} beta + * @param {promise} gamma + * @param {array} delta + * @param {Array} epsilon + * @param {promise} zeta + */ +function f(x: boolean, y, z, alpha, beta, gamma, delta, epsilon, zeta) { +}`, 'Annotate with type from JSDoc', 'annotate'); + +verify.applicableRefactorAvailableAtMarker('2'); +verify.fileAfterApplyingRefactorAtMarker('2', +`/** + * @param {Boolean} x + * @param {String} y + * @param {Number} z + * @param {Object} alpha + * @param {date} beta + * @param {promise} gamma + * @param {array} delta + * @param {Array} epsilon + * @param {promise} zeta + */ +function f(x: boolean, y: string, z, alpha, beta, gamma, delta, epsilon, zeta) { +}`, 'Annotate with type from JSDoc', 'annotate'); + +verify.applicableRefactorAvailableAtMarker('3'); +verify.fileAfterApplyingRefactorAtMarker('3', +`/** + * @param {Boolean} x + * @param {String} y + * @param {Number} z + * @param {Object} alpha + * @param {date} beta + * @param {promise} gamma + * @param {array} delta + * @param {Array} epsilon + * @param {promise} zeta + */ +function f(x: boolean, y: string, z: number, alpha, beta, gamma, delta, epsilon, zeta) { +}`, 'Annotate with type from JSDoc', 'annotate'); + +verify.applicableRefactorAvailableAtMarker('4'); +verify.fileAfterApplyingRefactorAtMarker('4', +`/** + * @param {Boolean} x + * @param {String} y + * @param {Number} z + * @param {Object} alpha + * @param {date} beta + * @param {promise} gamma + * @param {array} delta + * @param {Array} epsilon + * @param {promise} zeta + */ +function f(x: boolean, y: string, z: number, alpha: object, beta, gamma, delta, epsilon, zeta) { +}`, 'Annotate with type from JSDoc', 'annotate'); + +verify.applicableRefactorAvailableAtMarker('5'); +verify.fileAfterApplyingRefactorAtMarker('5', +`/** + * @param {Boolean} x + * @param {String} y + * @param {Number} z + * @param {Object} alpha + * @param {date} beta + * @param {promise} gamma + * @param {array} delta + * @param {Array} epsilon + * @param {promise} zeta + */ +function f(x: boolean, y: string, z: number, alpha: object, beta: Date, gamma, delta, epsilon, zeta) { +}`, 'Annotate with type from JSDoc', 'annotate'); + +verify.applicableRefactorAvailableAtMarker('6'); +verify.fileAfterApplyingRefactorAtMarker('6', +`/** + * @param {Boolean} x + * @param {String} y + * @param {Number} z + * @param {Object} alpha + * @param {date} beta + * @param {promise} gamma + * @param {array} delta + * @param {Array} epsilon + * @param {promise} zeta + */ +function f(x: boolean, y: string, z: number, alpha: object, beta: Date, gamma: Promise, delta, epsilon, zeta) { +}`, 'Annotate with type from JSDoc', 'annotate'); + +verify.applicableRefactorAvailableAtMarker('7'); +verify.fileAfterApplyingRefactorAtMarker('7', +`/** + * @param {Boolean} x + * @param {String} y + * @param {Number} z + * @param {Object} alpha + * @param {date} beta + * @param {promise} gamma + * @param {array} delta + * @param {Array} epsilon + * @param {promise} zeta + */ +function f(x: boolean, y: string, z: number, alpha: object, beta: Date, gamma: Promise, delta: Array, epsilon, zeta) { +}`, 'Annotate with type from JSDoc', 'annotate'); + +verify.applicableRefactorAvailableAtMarker('8'); +verify.fileAfterApplyingRefactorAtMarker('8', +`/** + * @param {Boolean} x + * @param {String} y + * @param {Number} z + * @param {Object} alpha + * @param {date} beta + * @param {promise} gamma + * @param {array} delta + * @param {Array} epsilon + * @param {promise} zeta + */ +function f(x: boolean, y: string, z: number, alpha: object, beta: Date, gamma: Promise, delta: Array, epsilon: Array, zeta) { +}`, 'Annotate with type from JSDoc', 'annotate'); + +verify.applicableRefactorAvailableAtMarker('9'); +verify.fileAfterApplyingRefactorAtMarker('9', +`/** + * @param {Boolean} x + * @param {String} y + * @param {Number} z + * @param {Object} alpha + * @param {date} beta + * @param {promise} gamma + * @param {array} delta + * @param {Array} epsilon + * @param {promise} zeta + */ +function f(x: boolean, y: string, z: number, alpha: object, beta: Date, gamma: Promise, delta: Array, epsilon: Array, zeta: Promise) { +}`, 'Annotate with type from JSDoc', 'annotate');