Transform jsdoc types in the refactor, not emitter

The emitter now understands JSDoc types but emits them in the original
format.
This commit is contained in:
Nathan Shively-Sanders 2017-09-26 12:42:08 -07:00
parent 96b8093890
commit fc933d7c33
3 changed files with 277 additions and 31 deletions

View File

@ -545,8 +545,9 @@ namespace ts {
case SyntaxKind.TypeReference:
return emitTypeReference(<TypeReferenceNode>node);
case SyntaxKind.FunctionType:
case SyntaxKind.JSDocFunctionType:
return emitFunctionType(<FunctionTypeNode>node);
case SyntaxKind.JSDocFunctionType:
return emitJSDocFunctionType(node as JSDocFunctionType);
case SyntaxKind.ConstructorType:
return emitConstructorType(<ConstructorTypeNode>node);
case SyntaxKind.TypeQuery:
@ -576,8 +577,10 @@ namespace ts {
case SyntaxKind.LiteralType:
return emitLiteralType(<LiteralTypeNode>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<ParameterDeclaration>) {
function canEmitSimpleArrowHead(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray<ParameterDeclaration>) {
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<ParameterDeclaration>) {
function emitParametersForArrow(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray<ParameterDeclaration>) {
if (canEmitSimpleArrowHead(parentNode, parameters)) {
emitList(parentNode, parameters, ListFormat.Parameters & ~ListFormat.Parenthesis);
}

View File

@ -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);
}
}

View File

@ -0,0 +1,158 @@
/// <reference path='fourslash.ts' />
// @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<number>} epsilon
//// * @param {promise<String>} 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<number>} epsilon
* @param {promise<String>} 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<number>} epsilon
* @param {promise<String>} 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<number>} epsilon
* @param {promise<String>} 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<number>} epsilon
* @param {promise<String>} 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<number>} epsilon
* @param {promise<String>} 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<number>} epsilon
* @param {promise<String>} zeta
*/
function f(x: boolean, y: string, z: number, alpha: object, beta: Date, gamma: Promise<any>, 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<number>} epsilon
* @param {promise<String>} zeta
*/
function f(x: boolean, y: string, z: number, alpha: object, beta: Date, gamma: Promise<any>, delta: Array<any>, 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<number>} epsilon
* @param {promise<String>} zeta
*/
function f(x: boolean, y: string, z: number, alpha: object, beta: Date, gamma: Promise<any>, delta: Array<any>, epsilon: Array<number>, 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<number>} epsilon
* @param {promise<String>} zeta
*/
function f(x: boolean, y: string, z: number, alpha: object, beta: Date, gamma: Promise<any>, delta: Array<any>, epsilon: Array<number>, zeta: Promise<string>) {
}`, 'Annotate with type from JSDoc', 'annotate');