Merge pull request #18747 from Microsoft/refactor-jsdoc-types-to-typescript

Refactor jsdoc types to typescript
This commit is contained in:
Nathan Shively-Sanders
2017-10-13 10:21:54 -07:00
committed by GitHub
26 changed files with 629 additions and 14 deletions
+10 -2
View File
@@ -3758,12 +3758,20 @@
"category": "Message",
"code": 95008
},
"Infer type of '{0}' from usage.": {
"Annotate with type from JSDoc": {
"category": "Message",
"code": 95009
},
"Infer parameter types from usage.": {
"Annotate with types from JSDoc": {
"category": "Message",
"code": 95010
},
"Infer type of '{0}' from usage.": {
"category": "Message",
"code": 95011
},
"Infer parameter types from usage.": {
"category": "Message",
"code": 95012
}
}
+53 -2
View File
@@ -546,6 +546,8 @@ namespace ts {
return emitTypeReference(<TypeReferenceNode>node);
case SyntaxKind.FunctionType:
return emitFunctionType(<FunctionTypeNode>node);
case SyntaxKind.JSDocFunctionType:
return emitJSDocFunctionType(node as JSDocFunctionType);
case SyntaxKind.ConstructorType:
return emitConstructorType(<ConstructorTypeNode>node);
case SyntaxKind.TypeQuery:
@@ -574,6 +576,20 @@ namespace ts {
return emitMappedType(<MappedTypeNode>node);
case SyntaxKind.LiteralType:
return emitLiteralType(<LiteralTypeNode>node);
case SyntaxKind.JSDocAllType:
write("*");
return;
case SyntaxKind.JSDocUnknownType:
write("?");
return;
case SyntaxKind.JSDocNullableType:
return emitJSDocNullableType(node as JSDocNullableType);
case SyntaxKind.JSDocNonNullableType:
return emitJSDocNonNullableType(node as JSDocNonNullableType);
case SyntaxKind.JSDocOptionalType:
return emitJSDocOptionalType(node as JSDocOptionalType);
case SyntaxKind.JSDocVariadicType:
return emitJSDocVariadicType(node as JSDocVariadicType);
// Binding patterns
case SyntaxKind.ObjectBindingPattern:
@@ -914,9 +930,16 @@ namespace ts {
emitDecorators(node, node.decorators);
emitModifiers(node, node.modifiers);
emitIfPresent(node.dotDotDotToken);
emit(node.name);
if (node.name) {
emit(node.name);
}
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);
}
@@ -1035,6 +1058,29 @@ namespace ts {
emit(node.type);
}
function emitJSDocFunctionType(node: JSDocFunctionType) {
write("function");
emitParameters(node, node.parameters);
write(":");
emit(node.type);
}
function emitJSDocNullableType(node: JSDocNullableType) {
write("?");
emit(node.type);
}
function emitJSDocNonNullableType(node: JSDocNonNullableType) {
write("!");
emit(node.type);
}
function emitJSDocOptionalType(node: JSDocOptionalType) {
emit(node.type);
write("=");
}
function emitConstructorType(node: ConstructorTypeNode) {
write("new ");
emitTypeParameters(node, node.typeParameters);
@@ -1060,6 +1106,11 @@ namespace ts {
write("[]");
}
function emitJSDocVariadicType(node: JSDocVariadicType) {
write("...");
emit(node.type);
}
function emitTupleType(node: TupleTypeNode) {
write("[");
emitList(node, node.elementTypes, ListFormat.TupleTypeElements);
+16 -9
View File
@@ -2700,11 +2700,11 @@ namespace ts {
* Gets the effective type annotation of a variable, parameter, or property. If the node was
* parsed in a JavaScript file, gets the type annotation from JSDoc.
*/
export function getEffectiveTypeAnnotationNode(node: VariableLikeDeclaration): TypeNode | undefined {
export function getEffectiveTypeAnnotationNode(node: VariableLikeDeclaration, checkJSDoc?: boolean): TypeNode | undefined {
if (node.type) {
return node.type;
}
if (isInJavaScriptFile(node)) {
if (checkJSDoc || isInJavaScriptFile(node)) {
return getJSDocType(node);
}
}
@@ -2713,11 +2713,11 @@ namespace ts {
* Gets the effective return type annotation of a signature. If the node was parsed in a
* JavaScript file, gets the return type annotation from JSDoc.
*/
export function getEffectiveReturnTypeNode(node: SignatureDeclaration): TypeNode | undefined {
export function getEffectiveReturnTypeNode(node: SignatureDeclaration, checkJSDoc?: boolean): TypeNode | undefined {
if (node.type) {
return node.type;
}
if (isInJavaScriptFile(node)) {
if (checkJSDoc || isInJavaScriptFile(node)) {
return getJSDocReturnType(node);
}
}
@@ -2726,11 +2726,11 @@ namespace ts {
* Gets the effective type parameters. If the node was parsed in a
* JavaScript file, gets the type parameters from the `@template` tag from JSDoc.
*/
export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray<TypeParameterDeclaration> {
export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters, checkJSDoc?: boolean): ReadonlyArray<TypeParameterDeclaration> {
if (node.typeParameters) {
return node.typeParameters;
}
if (isInJavaScriptFile(node)) {
if (checkJSDoc || isInJavaScriptFile(node)) {
const templateTag = getJSDocTemplateTag(node);
return templateTag && templateTag.typeParameters;
}
@@ -2740,9 +2740,9 @@ namespace ts {
* Gets the effective type annotation of the value parameter of a set accessor. If the node
* was parsed in a JavaScript file, gets the type annotation from JSDoc.
*/
export function getEffectiveSetAccessorTypeAnnotationNode(node: SetAccessorDeclaration): TypeNode {
export function getEffectiveSetAccessorTypeAnnotationNode(node: SetAccessorDeclaration, checkJSDoc?: boolean): TypeNode {
const parameter = getSetAccessorValueParameter(node);
return parameter && getEffectiveTypeAnnotationNode(parameter);
return parameter && getEffectiveTypeAnnotationNode(parameter, checkJSDoc);
}
export function emitNewLineBeforeLeadingComments(lineMap: ReadonlyArray<number>, writer: EmitTextWriter, node: TextRange, leadingComments: ReadonlyArray<CommentRange>) {
@@ -5130,7 +5130,14 @@ namespace ts {
|| kind === SyntaxKind.UndefinedKeyword
|| kind === SyntaxKind.NullKeyword
|| kind === SyntaxKind.NeverKeyword
|| kind === SyntaxKind.ExpressionWithTypeArguments;
|| kind === SyntaxKind.ExpressionWithTypeArguments
|| kind === SyntaxKind.JSDocAllType
|| kind === SyntaxKind.JSDocUnknownType
|| kind === SyntaxKind.JSDocNullableType
|| kind === SyntaxKind.JSDocNonNullableType
|| kind === SyntaxKind.JSDocOptionalType
|| kind === SyntaxKind.JSDocFunctionType
|| kind === SyntaxKind.JSDocVariadicType;
}
/**
@@ -0,0 +1,226 @@
/* @internal */
namespace ts.refactor.annotateWithTypeFromJSDoc {
const actionName = "annotate";
const annotateTypeFromJSDoc: Refactor = {
name: "Annotate with type from JSDoc",
description: Diagnostics.Annotate_with_type_from_JSDoc.message,
getEditsForAction: getEditsForAnnotation,
getAvailableActions
};
const annotateFunctionFromJSDoc: Refactor = {
name: "Annotate with types from JSDoc",
description: Diagnostics.Annotate_with_types_from_JSDoc.message,
getEditsForAction: getEditsForFunctionAnnotation,
getAvailableActions
};
type DeclarationWithType =
| FunctionLikeDeclaration
| VariableDeclaration
| ParameterDeclaration
| PropertySignature
| PropertyDeclaration;
registerRefactor(annotateTypeFromJSDoc);
registerRefactor(annotateFunctionFromJSDoc);
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
if (isInJavaScriptFile(context.file)) {
return undefined;
}
const node = getTokenAtPosition(context.file, context.startPosition, /*includeJsDocComment*/ false);
const decl = findAncestor(node, isDeclarationWithType);
if (!decl || decl.type) {
return undefined;
}
const jsdocType = getJSDocType(decl);
const isFunctionWithJSDoc = isFunctionLikeDeclaration(decl) && (getJSDocReturnType(decl) || decl.parameters.some(p => !!getJSDocType(p)));
const refactor = (isFunctionWithJSDoc || jsdocType && decl.kind === SyntaxKind.Parameter) ? annotateFunctionFromJSDoc :
jsdocType ? annotateTypeFromJSDoc :
undefined;
if (refactor) {
return [{
name: refactor.name,
description: refactor.description,
actions: [
{
description: refactor.description,
name: actionName
}
]
}];
}
}
function getEditsForAnnotation(context: RefactorContext, action: string): RefactorEditInfo | undefined {
if (actionName !== action) {
return Debug.fail(`actionName !== action: ${actionName} !== ${action}`);
}
const sourceFile = context.file;
const token = getTokenAtPosition(sourceFile, context.startPosition, /*includeJsDocComment*/ false);
const decl = findAncestor(token, isDeclarationWithType);
const jsdocType = getJSDocType(decl);
if (!decl || !jsdocType || decl.type) {
return Debug.fail(`!decl || !jsdocType || decl.type: !${decl} || !${jsdocType} || ${decl.type}`);
}
const changeTracker = textChanges.ChangeTracker.fromContext(context);
const declarationWithType = addType(decl, transformJSDocType(jsdocType) as TypeNode);
suppressLeadingAndTrailingTrivia(declarationWithType);
changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, declarationWithType);
return {
edits: changeTracker.getChanges(),
renameFilename: undefined,
renameLocation: undefined
};
}
function getEditsForFunctionAnnotation(context: RefactorContext, action: string): RefactorEditInfo | undefined {
if (actionName !== action) {
return Debug.fail(`actionName !== action: ${actionName} !== ${action}`);
}
const sourceFile = context.file;
const token = getTokenAtPosition(sourceFile, context.startPosition, /*includeJsDocComment*/ false);
const decl = findAncestor(token, isFunctionLikeDeclaration);
const changeTracker = textChanges.ChangeTracker.fromContext(context);
const functionWithType = addTypesToFunctionLike(decl);
suppressLeadingAndTrailingTrivia(functionWithType);
changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, functionWithType);
return {
edits: changeTracker.getChanges(),
renameFilename: undefined,
renameLocation: undefined
};
}
function isDeclarationWithType(node: Node): node is DeclarationWithType {
return isFunctionLikeDeclaration(node) ||
node.kind === SyntaxKind.VariableDeclaration ||
node.kind === SyntaxKind.Parameter ||
node.kind === SyntaxKind.PropertySignature ||
node.kind === SyntaxKind.PropertyDeclaration;
}
function addTypesToFunctionLike(decl: FunctionLikeDeclaration) {
const typeParameters = getEffectiveTypeParameterDeclarations(decl, /*checkJSDoc*/ true);
const parameters = decl.parameters.map(
p => createParameter(p.decorators, p.modifiers, p.dotDotDotToken, p.name, p.questionToken, transformJSDocType(getEffectiveTypeAnnotationNode(p, /*checkJSDoc*/ true)) as TypeNode, p.initializer));
const returnType = transformJSDocType(getEffectiveReturnTypeNode(decl, /*checkJSDoc*/ true)) as TypeNode;
switch (decl.kind) {
case SyntaxKind.FunctionDeclaration:
return createFunctionDeclaration(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, typeParameters, parameters, returnType, decl.body);
case SyntaxKind.Constructor:
return createConstructor(decl.decorators, decl.modifiers, parameters, decl.body);
case SyntaxKind.FunctionExpression:
return createFunctionExpression(decl.modifiers, decl.asteriskToken, (decl as FunctionExpression).name, typeParameters, parameters, returnType, decl.body);
case SyntaxKind.ArrowFunction:
return createArrowFunction(decl.modifiers, typeParameters, parameters, returnType, decl.equalsGreaterThanToken, decl.body);
case SyntaxKind.MethodDeclaration:
return createMethod(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.questionToken, typeParameters, parameters, returnType, decl.body);
case SyntaxKind.GetAccessor:
return createGetAccessor(decl.decorators, decl.modifiers, decl.name, decl.parameters, returnType, decl.body);
case SyntaxKind.SetAccessor:
return createSetAccessor(decl.decorators, decl.modifiers, decl.name, parameters, decl.body);
default:
return Debug.assertNever(decl, `Unexpected SyntaxKind: ${(decl as any).kind}`);
}
}
function addType(decl: DeclarationWithType, jsdocType: TypeNode) {
switch (decl.kind) {
case SyntaxKind.VariableDeclaration:
return createVariableDeclaration(decl.name, jsdocType, decl.initializer);
case SyntaxKind.PropertySignature:
return createPropertySignature(decl.modifiers, decl.name, decl.questionToken, jsdocType, decl.initializer);
case SyntaxKind.PropertyDeclaration:
return createProperty(decl.decorators, decl.modifiers, decl.name, decl.questionToken, jsdocType, decl.initializer);
default:
return Debug.fail(`Unexpected SyntaxKind: ${decl.kind}`);
}
}
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 transformJSDocOptionalType(node as JSDocOptionalType);
case SyntaxKind.JSDocNonNullableType:
return transformJSDocType((node as JSDocNonNullableType).type);
case SyntaxKind.JSDocNullableType:
return transformJSDocNullableType(node as JSDocNullableType);
case SyntaxKind.JSDocVariadicType:
return transformJSDocVariadicType(node as JSDocVariadicType);
case SyntaxKind.JSDocFunctionType:
return transformJSDocFunctionType(node as JSDocFunctionType);
case SyntaxKind.Parameter:
return transformJSDocParameter(node as ParameterDeclaration);
case SyntaxKind.TypeReference:
return transformJSDocTypeReference(node as TypeReferenceNode);
default:
return visitEachChild(node, transformJSDocType, /*context*/ undefined) as TypeNode;
}
}
function transformJSDocOptionalType(node: JSDocOptionalType) {
return createUnionTypeNode([visitNode(node.type, transformJSDocType), createTypeReferenceNode("undefined", emptyArray)]);
}
function transformJSDocNullableType(node: JSDocNullableType) {
return createUnionTypeNode([visitNode(node.type, transformJSDocType), createTypeReferenceNode("null", emptyArray)]);
}
function transformJSDocVariadicType(node: JSDocVariadicType) {
return createArrayTypeNode(visitNode(node.type, transformJSDocType));
}
function transformJSDocFunctionType(node: JSDocFunctionType) {
const parameters = node.parameters && node.parameters.map(transformJSDocType);
return createFunctionTypeNode(emptyArray, parameters as ParameterDeclaration[], node.type);
}
function transformJSDocParameter(node: ParameterDeclaration) {
const index = node.parent.parameters.indexOf(node);
const isRest = node.type.kind === SyntaxKind.JSDocVariadicType && index === node.parent.parameters.length - 1;
const name = node.name || (isRest ? "rest" : "arg" + index);
const dotdotdot = isRest ? createToken(SyntaxKind.DotDotDotToken) : node.dotDotDotToken;
return createParameter(node.decorators, node.modifiers, dotdotdot, name, node.questionToken, visitNode(node.type, transformJSDocType), node.initializer);
}
function transformJSDocTypeReference(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);
}
}
@@ -269,4 +269,4 @@ namespace ts.refactor.convertFunctionToES6Class {
return filter(source.modifiers, modifier => modifier.kind === kind);
}
}
}
}
+1
View File
@@ -1,2 +1,3 @@
/// <reference path="annotateWithTypeFromJSDoc.ts" />
/// <reference path="convertFunctionToEs6Class.ts" />
/// <reference path="extractSymbol.ts" />