JSDoc codefix:getTypeFromTypeNode >>> typeToString

Instead of trying to walk the type structure in the codefix, I changed
to call getTypeFromTypeNode in the checker and then calling
typeToString. Together, these two functions normalise out JSDoc.

Note that you only get `T | null` for `?T` if you have --strict on. This
is technically correct -- adding a null union does nothing without
strict -- but it would still serve as documentation.
This commit is contained in:
Nathan Shively-Sanders 2017-07-17 14:11:35 -07:00
parent cbe7b4dba3
commit b13de0547e
3 changed files with 25 additions and 57 deletions

View File

@ -117,6 +117,7 @@ namespace ts {
},
getParameterType: getTypeAtPosition,
getReturnTypeOfSignature,
getNullableType,
getNonNullableType,
typeToTypeNode: nodeBuilder.typeToTypeNode,
indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration,

View File

@ -2520,6 +2520,7 @@ namespace ts {
* Returns `any` if the index is not valid.
*/
/* @internal */ getParameterType(signature: Signature, parameterIndex: number): Type;
getNullableType(type: Type, flags: TypeFlags): Type;
getNonNullableType(type: Type): Type;
/** Note that the resulting nodes cannot be checked. */

View File

@ -10,65 +10,31 @@ namespace ts.codefix {
const node = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false);
const decl = ts.findAncestor(node, n => n.kind === SyntaxKind.VariableDeclaration);
if (!decl) return;
const checker = context.program.getTypeChecker();
const jsdocType = (decl as VariableDeclaration).type;
let cheesyHacks = false;
// TODO: Only if get(jsdoctype) !== jsdoctype
// TODO: Create cheesy hacks to support | null | undefined -- just flip a boolean and rerun with that boolean set
const trk = textChanges.ChangeTracker.fromCodeFixContext(context);
trk.replaceNode(sourceFile, jsdocType, getTypeFromJSDocType(jsdocType));
const changes = [{
// TODO: This seems like the LEAST SAFE way to get the new text
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_0_to_1), [getTextOfNode(jsdocType), trk.getChanges()[0].textChanges[0].newText]),
changes: trk.getChanges(),
}];
if (cheesyHacks) {
const trk = textChanges.ChangeTracker.fromCodeFixContext(context);
trk.replaceNode(sourceFile, jsdocType, getTypeFromJSDocType(jsdocType));
changes.push({
// TODO: This seems like the LEAST SAFE way to get the new text
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_0_to_1), [getTextOfNode(jsdocType), trk.getChanges()[0].textChanges[0].newText]),
changes: trk.getChanges(),
});
const original = getTextOfNode(jsdocType);
const type = checker.getTypeFromTypeNode(jsdocType);
const actions = [createAction(jsdocType, sourceFile.fileName, original, checker.typeToString(type))];
if (jsdocType.kind === SyntaxKind.JSDocNullableType) {
// for nullable types, suggest the flow-compatible `T | null | undefined`
// in addition to the jsdoc/closure-compatible `T | null`
const replacementWithUndefined = checker.typeToString(checker.getNullableType(type, TypeFlags.Undefined));
actions.push(createAction(jsdocType, sourceFile.fileName, original, replacementWithUndefined));
}
return changes;
return actions;
}
function getTypeFromJSDocType(type: TypeNode): TypeNode {
switch (type.kind) {
case SyntaxKind.JSDocUnknownType:
case SyntaxKind.JSDocAllType:
return createToken(SyntaxKind.AnyKeyword) as TypeNode;
case SyntaxKind.JSDocVariadicType:
return createArrayTypeNode(getTypeFromJSDocType((type as JSDocVariadicType).type));
case SyntaxKind.JSDocNullableType:
if (cheesyHacks) {
return createUnionTypeNode([getTypeFromJSDocType((type as JSDocNullableType).type), createNull(), createToken(SyntaxKind.UndefinedKeyword) as TypeNode]);
}
else {
cheesyHacks = true;
return createUnionTypeNode([getTypeFromJSDocType((type as JSDocNullableType).type), createNull()]);
}
case SyntaxKind.JSDocNonNullableType:
return getTypeFromJSDocType((type as JSDocNullableType).type);
case SyntaxKind.ArrayType:
// TODO: Only create an error if the get(type.type) !== type.type.
return createArrayTypeNode(getTypeFromJSDocType((type as ArrayTypeNode).elementType));
case SyntaxKind.TypeReference:
return getTypeReferenceFromJSDocType(type as TypeReferenceNode);
case SyntaxKind.Identifier:
return type;
}
// TODO: Need to recur on all relevant nodes. Is a call to visit enough?
return type;
}
function getTypeReferenceFromJSDocType(type: TypeReferenceNode) {
if (type.typeArguments && type.typeName.jsdocDotPos) {
return createTypeReferenceNode(type.typeName, map(type.typeArguments, getTypeFromJSDocType));
}
return getTypeFromJSDocType(type);
}
function createAction(declaration: TypeNode, fileName: string, original: string, replacement: string): CodeAction {
return {
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_0_to_1), [original, replacement]),
changes: [{
fileName,
textChanges: [{
span: { start: declaration.getStart(), length: declaration.getWidth() },
newText: replacement
}]
}],
};
}
}