From 6d218e2a48ac9ed05a843707a08f4a44851721f1 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 25 Sep 2017 08:56:51 -0700 Subject: [PATCH 01/14] Refactor JSDoc types to Typescript types When the caret is on a Typescript declaration that has no type, but does have a JSDoc annotation with a type, this refactor will add the Typescript equivalent of the JSDoc type. Notes: 1. This doesn't delete the JSDoc comment or delete parts of it. In fact, due to bugs in trivia handling, it sometimes duplicates the comment. These bugs are tracked in #18626. 2. As a bonus, when `noImplicitAny: true`, this shows up as a code fix in VS Code whenever there is a no-implicit-any error. With `noImplicityAny: false`, this code must be invoked via the refactoring command. --- src/compiler/diagnosticMessages.json | 4 + src/compiler/emitter.ts | 46 +++++- src/compiler/utilities.ts | 9 +- src/services/refactors/convertJSDocToTypes.ts | 137 ++++++++++++++++++ src/services/refactors/refactors.ts | 1 + 5 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 src/services/refactors/convertJSDocToTypes.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 662e87d3159af..b3741af80e84f 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3703,5 +3703,9 @@ "Extract to {0}": { "category": "Message", "code": 95004 + }, + "Convert to Typescript type": { + "category": "Message", + "code": 95005 } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index d458e7e5ef5df..80b08c60d343f 100755 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -545,6 +545,7 @@ namespace ts { case SyntaxKind.TypeReference: return emitTypeReference(node); case SyntaxKind.FunctionType: + case SyntaxKind.JSDocFunctionType: return emitFunctionType(node); case SyntaxKind.ConstructorType: return emitConstructorType(node); @@ -574,6 +575,18 @@ namespace ts { return emitMappedType(node); case SyntaxKind.LiteralType: return emitLiteralType(node); + case SyntaxKind.JSDocAllType: + case SyntaxKind.JSDocUnknownType: + write("any"); + break; + 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,7 +927,15 @@ namespace ts { emitDecorators(node, node.decorators); emitModifiers(node, node.modifiers); emitIfPresent(node.dotDotDotToken); - emit(node.name); + 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); emitExpressionWithPrefix(" = ", node.initializer); @@ -1035,6 +1056,20 @@ namespace ts { emit(node.type); } + function emitJSDocNullableType(node: JSDocNullableType) { + emit(node.type); + write(" | null"); + } + + function emitJSDocNonNullableType(node: JSDocNonNullableType) { + emit(node.type); + } + + function emitJSDocOptionalType(node: JSDocOptionalType) { + emit(node.type); + write(" | undefined"); + } + function emitConstructorType(node: ConstructorTypeNode) { write("new "); emitTypeParameters(node, node.typeParameters); @@ -1060,6 +1095,11 @@ namespace ts { write("[]"); } + function emitJSDocVariadicType(node: JSDocVariadicType) { + emit(node.type); + write("[]"); + } + function emitTupleType(node: TupleTypeNode) { write("["); emitList(node, node.elementTypes, ListFormat.TupleTypeElements); @@ -2357,7 +2397,7 @@ namespace ts { emitList(parentNode, parameters, ListFormat.Parameters); } - function canEmitSimpleArrowHead(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { + function canEmitSimpleArrowHead(parentNode: FunctionTypeNode | ArrowFunction | JSDocFunctionType, parameters: NodeArray) { const parameter = singleOrUndefined(parameters); return parameter && parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter @@ -2374,7 +2414,7 @@ namespace ts { && isIdentifier(parameter.name); // parameter name must be identifier } - function emitParametersForArrow(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { + function emitParametersForArrow(parentNode: FunctionTypeNode | ArrowFunction | JSDocFunctionType, parameters: NodeArray) { if (canEmitSimpleArrowHead(parentNode, parameters)) { emitList(parentNode, parameters, ListFormat.Parameters & ~ListFormat.Parenthesis); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index cc7002ca0590c..2fee52f1e3fe3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5034,7 +5034,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; } /** diff --git a/src/services/refactors/convertJSDocToTypes.ts b/src/services/refactors/convertJSDocToTypes.ts new file mode 100644 index 0000000000000..562c26bf50fc8 --- /dev/null +++ b/src/services/refactors/convertJSDocToTypes.ts @@ -0,0 +1,137 @@ +/* @internal */ +namespace ts.refactor.convertJSDocToTypes { + const actionName = "convert"; + + const convertJSDocToTypes: Refactor = { + name: "Convert to Typescript type", + description: Diagnostics.Convert_to_Typescript_type.message, + getEditsForAction, + getAvailableActions + }; + + type DeclarationWithType = + | FunctionLikeDeclaration + | VariableDeclaration + | ParameterDeclaration + | PropertySignature + | PropertyDeclaration; + + registerRefactor(convertJSDocToTypes); + + 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, isTypedNode); + if (decl && (getJSDocType(decl) || getJSDocReturnType(decl)) && !decl.type) { + return [ + { + name: convertJSDocToTypes.name, + description: convertJSDocToTypes.description, + actions: [ + { + description: convertJSDocToTypes.description, + name: actionName + } + ] + } + ]; + } + } + + function getEditsForAction(context: RefactorContext, action: string): RefactorEditInfo | undefined { + // Somehow wrong action got invoked? + if (actionName !== action) { + Debug.fail(`actionName !== action: ${actionName} !== ${action}`); + return undefined; + } + + const start = context.startPosition; + 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}`); + return undefined; + } + + const changeTracker = textChanges.ChangeTracker.fromContext(context); + if (isParameterOfSimpleArrowFunction(decl)) { + // `x => x` becomes `(x: number) => x`, but in order to make the changeTracker generate the parentheses, + // we have to replace the entire function; it doesn't check that the node it's replacing might require + // 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 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)); + } + return { + edits: changeTracker.getChanges(), + renameFilename: undefined, + renameLocation: undefined + }; + } + + function isTypedNode(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 replaceType(decl: DeclarationWithType, jsdocType: TypeNode, jsdocReturn: TypeNode) { + switch (decl.kind) { + case SyntaxKind.VariableDeclaration: + return createVariableDeclaration(decl.name, jsdocType, decl.initializer); + case SyntaxKind.Parameter: + return createParameter(decl.decorators, decl.modifiers, decl.dotDotDotToken, decl.name, decl.questionToken, 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); + case SyntaxKind.FunctionDeclaration: + return createFunctionDeclaration(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.typeParameters, decl.parameters, jsdocReturn, decl.body); + case SyntaxKind.FunctionExpression: + return createFunctionExpression(decl.modifiers, decl.asteriskToken, decl.name, decl.typeParameters, decl.parameters, jsdocReturn, decl.body); + case SyntaxKind.ArrowFunction: + return createArrowFunction(decl.modifiers, decl.typeParameters, decl.parameters, jsdocReturn, 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); + case SyntaxKind.GetAccessor: + return createGetAccessor(decl.decorators, decl.modifiers, decl.name, decl.parameters, jsdocReturn, decl.body); + default: + Debug.fail(`Unexpected SyntaxKind: ${decl.kind}`); + return undefined; + } + } + + function isParameterOfSimpleArrowFunction(decl: DeclarationWithType) { + return decl.kind === SyntaxKind.Parameter && decl.parent.kind === SyntaxKind.ArrowFunction && isSimpleArrowFunction(decl.parent); + } + + function isSimpleArrowFunction(parentNode: FunctionTypeNode | ArrowFunction | JSDocFunctionType) { + const parameter = singleOrUndefined(parentNode.parameters); + return parameter + && parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter + && !(isArrowFunction(parentNode) && parentNode.type) // arrow function may not have return type annotation + && !some(parentNode.decorators) // parent may not have decorators + && !some(parentNode.modifiers) // parent may not have modifiers + && !some(parentNode.typeParameters) // parent may not have type parameters + && !some(parameter.decorators) // parameter may not have decorators + && !some(parameter.modifiers) // parameter may not have modifiers + && !parameter.dotDotDotToken // parameter may not be rest + && !parameter.questionToken // parameter may not be optional + && !parameter.type // parameter may not have a type annotation + && !parameter.initializer // parameter may not have an initializer + && isIdentifier(parameter.name); // parameter name must be identifier + } +} diff --git a/src/services/refactors/refactors.ts b/src/services/refactors/refactors.ts index 3a33ccc83c2b8..d1d50cb50f9d0 100644 --- a/src/services/refactors/refactors.ts +++ b/src/services/refactors/refactors.ts @@ -1,2 +1,3 @@ +/// /// /// From 8996d11096d3c1de718883de97991b1a41fd3f70 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 25 Sep 2017 09:02:42 -0700 Subject: [PATCH 02/14] Test:refactor JSDoc types to Typescript types --- tests/cases/fourslash/convertJSDocToTypes1.ts | 10 ++ .../cases/fourslash/convertJSDocToTypes10.ts | 15 +++ .../cases/fourslash/convertJSDocToTypes11.ts | 15 +++ .../cases/fourslash/convertJSDocToTypes12.ts | 21 ++++ .../cases/fourslash/convertJSDocToTypes13.ts | 11 ++ tests/cases/fourslash/convertJSDocToTypes2.ts | 6 + tests/cases/fourslash/convertJSDocToTypes3.ts | 54 ++++++++ tests/cases/fourslash/convertJSDocToTypes4.ts | 115 ++++++++++++++++++ tests/cases/fourslash/convertJSDocToTypes5.ts | 15 +++ tests/cases/fourslash/convertJSDocToTypes6.ts | 15 +++ tests/cases/fourslash/convertJSDocToTypes7.ts | 17 +++ tests/cases/fourslash/convertJSDocToTypes8.ts | 17 +++ tests/cases/fourslash/convertJSDocToTypes9.ts | 15 +++ 13 files changed, 326 insertions(+) create mode 100644 tests/cases/fourslash/convertJSDocToTypes1.ts create mode 100644 tests/cases/fourslash/convertJSDocToTypes10.ts create mode 100644 tests/cases/fourslash/convertJSDocToTypes11.ts create mode 100644 tests/cases/fourslash/convertJSDocToTypes12.ts create mode 100644 tests/cases/fourslash/convertJSDocToTypes13.ts create mode 100644 tests/cases/fourslash/convertJSDocToTypes2.ts create mode 100644 tests/cases/fourslash/convertJSDocToTypes3.ts create mode 100644 tests/cases/fourslash/convertJSDocToTypes4.ts create mode 100644 tests/cases/fourslash/convertJSDocToTypes5.ts create mode 100644 tests/cases/fourslash/convertJSDocToTypes6.ts create mode 100644 tests/cases/fourslash/convertJSDocToTypes7.ts create mode 100644 tests/cases/fourslash/convertJSDocToTypes8.ts create mode 100644 tests/cases/fourslash/convertJSDocToTypes9.ts diff --git a/tests/cases/fourslash/convertJSDocToTypes1.ts b/tests/cases/fourslash/convertJSDocToTypes1.ts new file mode 100644 index 0000000000000..7f90febaca112 --- /dev/null +++ b/tests/cases/fourslash/convertJSDocToTypes1.ts @@ -0,0 +1,10 @@ +/// + +// @Filename: test123.ts +/////** @type {number} */ +////var /*1*/x; + +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`/** @type {number} */ +var x: number;`, 'Convert to Typescript type', 'convert'); diff --git a/tests/cases/fourslash/convertJSDocToTypes10.ts b/tests/cases/fourslash/convertJSDocToTypes10.ts new file mode 100644 index 0000000000000..1e925db81e0c3 --- /dev/null +++ b/tests/cases/fourslash/convertJSDocToTypes10.ts @@ -0,0 +1,15 @@ +/// + +/////** +//// * @param {?} x +//// * @returns {number} +//// */ +////var f = /*1*/(/*2*/x) => x + +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`/** + * @param {?} x + * @returns {number} + */ +var f = (x): number => x`, 'Convert to Typescript type', 'convert'); diff --git a/tests/cases/fourslash/convertJSDocToTypes11.ts b/tests/cases/fourslash/convertJSDocToTypes11.ts new file mode 100644 index 0000000000000..0315e506dc12f --- /dev/null +++ b/tests/cases/fourslash/convertJSDocToTypes11.ts @@ -0,0 +1,15 @@ +/// + +/////** +//// * @param {?} x +//// * @returns {number} +//// */ +////var f = /*1*/(/*2*/x) => x + +verify.applicableRefactorAvailableAtMarker('2'); +verify.fileAfterApplyingRefactorAtMarker('2', +`/** + * @param {?} x + * @returns {number} + */ +var f = (x: any) => x`, 'Convert to Typescript type', 'convert'); diff --git a/tests/cases/fourslash/convertJSDocToTypes12.ts b/tests/cases/fourslash/convertJSDocToTypes12.ts new file mode 100644 index 0000000000000..5015cb825d8da --- /dev/null +++ b/tests/cases/fourslash/convertJSDocToTypes12.ts @@ -0,0 +1,21 @@ +/// + +////class C { +//// /** +//// * @return {...*} +//// */ +//// /*1*/m(x) { +//// } +////} +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`class C { + /** + * @return {...*} + */ + /** + * @return {...*} + */ + m(x): any[] { + } +}`, 'Convert to Typescript type', 'convert'); diff --git a/tests/cases/fourslash/convertJSDocToTypes13.ts b/tests/cases/fourslash/convertJSDocToTypes13.ts new file mode 100644 index 0000000000000..4ee58da1caa3e --- /dev/null +++ b/tests/cases/fourslash/convertJSDocToTypes13.ts @@ -0,0 +1,11 @@ +/// +////class C { +//// /** @return {number} */ +//// get /*1*/c() { return 12 } +////} +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`class C { + /** @return {number} */ + get c(): number { return 12; } +}`, 'Convert to Typescript type', 'convert'); diff --git a/tests/cases/fourslash/convertJSDocToTypes2.ts b/tests/cases/fourslash/convertJSDocToTypes2.ts new file mode 100644 index 0000000000000..85f28de4a9973 --- /dev/null +++ b/tests/cases/fourslash/convertJSDocToTypes2.ts @@ -0,0 +1,6 @@ +/// + +// @Filename: test123.ts +/////** @type {number} */ +////var /*1*/x: string; +verify.not.applicableRefactorAvailableAtMarker('1'); diff --git a/tests/cases/fourslash/convertJSDocToTypes3.ts b/tests/cases/fourslash/convertJSDocToTypes3.ts new file mode 100644 index 0000000000000..3d3c1d35f449c --- /dev/null +++ b/tests/cases/fourslash/convertJSDocToTypes3.ts @@ -0,0 +1,54 @@ +/// +/////** +//// * @param {number} x - the first parameter +//// * @param {{ a: string, b: Date }} y - the most complex parameter +//// * @param z - the best parameter +//// * @param alpha - the other best parameter +//// * @param {*} beta - I have no idea how this got here +//// */ +////function f(/*1*/x, /*2*/y, /*3*/z: string, /*4*/alpha, /*5*/beta) { +////} + +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`/** + * @param {number} x - the first parameter + * @param {{ a: string, b: Date }} y - the most complex parameter + * @param z - the best parameter + * @param alpha - the other best parameter + * @param {*} beta - I have no idea how this got here + */ +function f(x: number, y, z: string, alpha, beta) { +}`, 'Convert to Typescript type', 'convert'); + +verify.applicableRefactorAvailableAtMarker('2'); +verify.fileAfterApplyingRefactorAtMarker('2', +`/** + * @param {number} x - the first parameter + * @param {{ a: string, b: Date }} y - the most complex parameter + * @param z - the best parameter + * @param alpha - the other best parameter + * @param {*} beta - I have no idea how this got here + */ +function f(x: number, y: { + a: string; + b: Date; +}, z: string, alpha, beta) { +}`, 'Convert to Typescript type', 'convert'); + +verify.not.applicableRefactorAvailableAtMarker('3'); +verify.not.applicableRefactorAvailableAtMarker('4'); +verify.applicableRefactorAvailableAtMarker('5'); +verify.fileAfterApplyingRefactorAtMarker('5', +`/** + * @param {number} x - the first parameter + * @param {{ a: string, b: Date }} y - the most complex parameter + * @param z - the best parameter + * @param alpha - the other best parameter + * @param {*} beta - I have no idea how this got here + */ +function f(x: number, y: { + a: string; + b: Date; +}, z: string, alpha, beta: any) { +}`, 'Convert to Typescript type', 'convert'); diff --git a/tests/cases/fourslash/convertJSDocToTypes4.ts b/tests/cases/fourslash/convertJSDocToTypes4.ts new file mode 100644 index 0000000000000..ea80bf0452dd8 --- /dev/null +++ b/tests/cases/fourslash/convertJSDocToTypes4.ts @@ -0,0 +1,115 @@ +/// +// @strict: true +/////** +//// * @param {*} x +//// * @param {?} y +//// * @param {number=} z +//// * @param {...number} alpha +//// * @param {function(this:{ a: string}, string, number): boolean} beta +//// * @param {number?} gamma +//// * @param {number!} delta +//// */ +////function f(/*1*/x, /*2*/y, /*3*/z, /*4*/alpha, /*5*/beta, /*6*/gamma, /*7*/delta) { +////} + +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`/** + * @param {*} x + * @param {?} y + * @param {number=} z + * @param {...number} alpha + * @param {function(this:{ a: string}, string, number): boolean} beta + * @param {number?} gamma + * @param {number!} delta + */ +function f(x: any, y, z, alpha, beta, gamma, delta) { +}`, 'Convert to Typescript type', 'convert'); + +verify.applicableRefactorAvailableAtMarker('2'); +verify.fileAfterApplyingRefactorAtMarker('2', +`/** + * @param {*} x + * @param {?} y + * @param {number=} z + * @param {...number} alpha + * @param {function(this:{ a: string}, string, number): boolean} beta + * @param {number?} gamma + * @param {number!} delta + */ +function f(x: any, y: any, z, alpha, beta, gamma, delta) { +}`, 'Convert to Typescript type', 'convert'); + +verify.applicableRefactorAvailableAtMarker('3'); +verify.fileAfterApplyingRefactorAtMarker('3', +`/** + * @param {*} x + * @param {?} y + * @param {number=} z + * @param {...number} alpha + * @param {function(this:{ a: string}, string, number): boolean} beta + * @param {number?} gamma + * @param {number!} delta + */ +function f(x: any, y: any, z: number | undefined, alpha, beta, gamma, delta) { +}`, 'Convert to Typescript type', 'convert'); +verify.applicableRefactorAvailableAtMarker('4'); +verify.fileAfterApplyingRefactorAtMarker('4', +`/** + * @param {*} x + * @param {?} y + * @param {number=} z + * @param {...number} alpha + * @param {function(this:{ a: string}, string, number): boolean} beta + * @param {number?} gamma + * @param {number!} delta + */ +function f(x: any, y: any, z: number | undefined, alpha: number[], beta, gamma, delta) { +}`, 'Convert to Typescript type', 'convert'); + +verify.applicableRefactorAvailableAtMarker('5'); +verify.fileAfterApplyingRefactorAtMarker('5', +`/** + * @param {*} x + * @param {?} y + * @param {number=} z + * @param {...number} alpha + * @param {function(this:{ a: string}, string, number): boolean} beta + * @param {number?} gamma + * @param {number!} delta + */ +function f(x: any, y: any, z: number | undefined, alpha: number[], beta: (this: { + a: string; +}, arg1: string, arg2: number) => boolean, gamma, delta) { +}`, 'Convert to Typescript type', 'convert'); +verify.applicableRefactorAvailableAtMarker('6'); +verify.fileAfterApplyingRefactorAtMarker('6', +`/** + * @param {*} x + * @param {?} y + * @param {number=} z + * @param {...number} alpha + * @param {function(this:{ a: string}, string, number): boolean} beta + * @param {number?} gamma + * @param {number!} delta + */ +function f(x: any, y: any, z: number | undefined, alpha: number[], beta: (this: { + a: string; +}, arg1: string, arg2: number) => boolean, gamma: number | null, delta) { +}`, 'Convert to Typescript type', 'convert'); + +verify.applicableRefactorAvailableAtMarker('7'); +verify.fileAfterApplyingRefactorAtMarker('7', +`/** + * @param {*} x + * @param {?} y + * @param {number=} z + * @param {...number} alpha + * @param {function(this:{ a: string}, string, number): boolean} beta + * @param {number?} gamma + * @param {number!} delta + */ +function f(x: any, y: any, z: number | undefined, alpha: number[], beta: (this: { + a: string; +}, arg1: string, arg2: number) => boolean, gamma: number | null, delta: number) { +}`, 'Convert to Typescript type', 'convert'); diff --git a/tests/cases/fourslash/convertJSDocToTypes5.ts b/tests/cases/fourslash/convertJSDocToTypes5.ts new file mode 100644 index 0000000000000..7647e7be2f8cc --- /dev/null +++ b/tests/cases/fourslash/convertJSDocToTypes5.ts @@ -0,0 +1,15 @@ +/// + +////class C { +//// /** @type {number | null} */ +//// /*1*/p = null +////} + +// NOTE: The duplicated comment is unintentional but needs a serious fix in trivia handling +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`class C { + /** @type {number | null} */ + /** @type {number | null} */ + p: number | null = null; +}`, 'Convert to Typescript type', 'convert'); diff --git a/tests/cases/fourslash/convertJSDocToTypes6.ts b/tests/cases/fourslash/convertJSDocToTypes6.ts new file mode 100644 index 0000000000000..991e84b76b648 --- /dev/null +++ b/tests/cases/fourslash/convertJSDocToTypes6.ts @@ -0,0 +1,15 @@ +/// + +////declare class C { +//// /** @type {number | null} */ +//// /*1*/p; +////} + +// NOTE: The duplicated comment is unintentional but needs a serious fix in trivia handling +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`declare class C { + /** @type {number | null} */ + /** @type {number | null} */ + p: number | null; +}`, 'Convert to Typescript type', 'convert'); diff --git a/tests/cases/fourslash/convertJSDocToTypes7.ts b/tests/cases/fourslash/convertJSDocToTypes7.ts new file mode 100644 index 0000000000000..5046b4237ec9d --- /dev/null +++ b/tests/cases/fourslash/convertJSDocToTypes7.ts @@ -0,0 +1,17 @@ +/// + +/////** +//// * @param {number} x +//// * @returns {number} +//// */ +/////*1*/function f(x) { +////} + +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`/** + * @param {number} x + * @returns {number} + */ +function f(x): number { +}`, 'Convert to Typescript type', 'convert'); diff --git a/tests/cases/fourslash/convertJSDocToTypes8.ts b/tests/cases/fourslash/convertJSDocToTypes8.ts new file mode 100644 index 0000000000000..b3d0f82183639 --- /dev/null +++ b/tests/cases/fourslash/convertJSDocToTypes8.ts @@ -0,0 +1,17 @@ +/// + +/////** +//// * @param {number} x +//// * @returns {number} +//// */ +////var f = /*1*/function (x) { +////} + +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`/** + * @param {number} x + * @returns {number} + */ +var f = function(x): number { +}`, 'Convert to Typescript type', 'convert'); diff --git a/tests/cases/fourslash/convertJSDocToTypes9.ts b/tests/cases/fourslash/convertJSDocToTypes9.ts new file mode 100644 index 0000000000000..7f682a2459c5e --- /dev/null +++ b/tests/cases/fourslash/convertJSDocToTypes9.ts @@ -0,0 +1,15 @@ +/// + +/////** +//// * @param {?} x +//// * @returns {number} +//// */ +////var f = /*1*/x => x + +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`/** + * @param {?} x + * @returns {number} + */ +var f = (x: any) => x`, 'Convert to Typescript type', 'convert'); From 13b37a482593340d12bfbd4a3243f18d01067d7a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 26 Sep 2017 08:58:18 -0700 Subject: [PATCH 03/14] Change refactoring name and description --- src/compiler/diagnosticMessages.json | 6 ++- src/services/refactors/convertJSDocToTypes.ts | 40 ++++++++++++------- tests/cases/fourslash/convertJSDocToTypes1.ts | 2 +- .../cases/fourslash/convertJSDocToTypes10.ts | 2 +- .../cases/fourslash/convertJSDocToTypes11.ts | 2 +- .../cases/fourslash/convertJSDocToTypes12.ts | 2 +- .../cases/fourslash/convertJSDocToTypes13.ts | 2 +- .../cases/fourslash/convertJSDocToTypes14.ts | 11 +++++ tests/cases/fourslash/convertJSDocToTypes3.ts | 6 +-- tests/cases/fourslash/convertJSDocToTypes4.ts | 14 +++---- tests/cases/fourslash/convertJSDocToTypes5.ts | 2 +- tests/cases/fourslash/convertJSDocToTypes6.ts | 2 +- tests/cases/fourslash/convertJSDocToTypes7.ts | 2 +- tests/cases/fourslash/convertJSDocToTypes8.ts | 2 +- tests/cases/fourslash/convertJSDocToTypes9.ts | 2 +- 15 files changed, 61 insertions(+), 36 deletions(-) create mode 100644 tests/cases/fourslash/convertJSDocToTypes14.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b3741af80e84f..c6024e0974c39 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3704,8 +3704,12 @@ "category": "Message", "code": 95004 }, - "Convert to Typescript type": { + "Annotate with type from JSDoc": { "category": "Message", "code": 95005 + }, + "Annotate with return type from JSDoc": { + "category": "Message", + "code": 95006 } } diff --git a/src/services/refactors/convertJSDocToTypes.ts b/src/services/refactors/convertJSDocToTypes.ts index 562c26bf50fc8..c9915d85961fe 100644 --- a/src/services/refactors/convertJSDocToTypes.ts +++ b/src/services/refactors/convertJSDocToTypes.ts @@ -1,10 +1,16 @@ /* @internal */ namespace ts.refactor.convertJSDocToTypes { - const actionName = "convert"; + const actionName = "annotate"; - const convertJSDocToTypes: Refactor = { - name: "Convert to Typescript type", - description: Diagnostics.Convert_to_Typescript_type.message, + const annotateTypeFromJSDoc: Refactor = { + name: "Annotate with type from JSDoc", + description: Diagnostics.Annotate_with_type_from_JSDoc.message, + getEditsForAction, + getAvailableActions + }; + const annotateReturnTypeFromJSDoc: Refactor = { + name: "Annotate with return type from JSDoc", + description: Diagnostics.Annotate_with_return_type_from_JSDoc.message, getEditsForAction, getAvailableActions }; @@ -16,7 +22,8 @@ namespace ts.refactor.convertJSDocToTypes { | PropertySignature | PropertyDeclaration; - registerRefactor(convertJSDocToTypes); + registerRefactor(annotateTypeFromJSDoc); + registerRefactor(annotateReturnTypeFromJSDoc); function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined { if (isInJavaScriptFile(context.file)) { @@ -25,19 +32,22 @@ namespace ts.refactor.convertJSDocToTypes { const node = getTokenAtPosition(context.file, context.startPosition, /*includeJsDocComment*/ false); const decl = findAncestor(node, isTypedNode); - if (decl && (getJSDocType(decl) || getJSDocReturnType(decl)) && !decl.type) { - return [ - { - name: convertJSDocToTypes.name, - description: convertJSDocToTypes.description, + if (decl && !decl.type) { + const annotate = getJSDocType(decl) ? annotateTypeFromJSDoc : + getJSDocReturnType(decl) ? annotateReturnTypeFromJSDoc : + undefined; + if (annotate) { + return [{ + name: annotate.name, + description: annotate.description, actions: [ { - description: convertJSDocToTypes.description, - name: actionName - } + description: annotate.description, + name: actionName + } ] - } - ]; + }]; + } } } diff --git a/tests/cases/fourslash/convertJSDocToTypes1.ts b/tests/cases/fourslash/convertJSDocToTypes1.ts index 7f90febaca112..99cf5f8db1c15 100644 --- a/tests/cases/fourslash/convertJSDocToTypes1.ts +++ b/tests/cases/fourslash/convertJSDocToTypes1.ts @@ -7,4 +7,4 @@ verify.applicableRefactorAvailableAtMarker('1'); verify.fileAfterApplyingRefactorAtMarker('1', `/** @type {number} */ -var x: number;`, 'Convert to Typescript type', 'convert'); +var x: number;`, 'Annotate with type from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/convertJSDocToTypes10.ts b/tests/cases/fourslash/convertJSDocToTypes10.ts index 1e925db81e0c3..88b565fa9328a 100644 --- a/tests/cases/fourslash/convertJSDocToTypes10.ts +++ b/tests/cases/fourslash/convertJSDocToTypes10.ts @@ -12,4 +12,4 @@ verify.fileAfterApplyingRefactorAtMarker('1', * @param {?} x * @returns {number} */ -var f = (x): number => x`, 'Convert to Typescript type', 'convert'); +var f = (x): number => x`, 'Annotate with return type from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/convertJSDocToTypes11.ts b/tests/cases/fourslash/convertJSDocToTypes11.ts index 0315e506dc12f..63b2d85fbfe89 100644 --- a/tests/cases/fourslash/convertJSDocToTypes11.ts +++ b/tests/cases/fourslash/convertJSDocToTypes11.ts @@ -12,4 +12,4 @@ verify.fileAfterApplyingRefactorAtMarker('2', * @param {?} x * @returns {number} */ -var f = (x: any) => x`, 'Convert to Typescript type', 'convert'); +var f = (x: any) => x`, 'Annotate with type from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/convertJSDocToTypes12.ts b/tests/cases/fourslash/convertJSDocToTypes12.ts index 5015cb825d8da..95fa0b55cd292 100644 --- a/tests/cases/fourslash/convertJSDocToTypes12.ts +++ b/tests/cases/fourslash/convertJSDocToTypes12.ts @@ -18,4 +18,4 @@ verify.fileAfterApplyingRefactorAtMarker('1', */ m(x): any[] { } -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with return type from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/convertJSDocToTypes13.ts b/tests/cases/fourslash/convertJSDocToTypes13.ts index 4ee58da1caa3e..caa3315b87fb2 100644 --- a/tests/cases/fourslash/convertJSDocToTypes13.ts +++ b/tests/cases/fourslash/convertJSDocToTypes13.ts @@ -8,4 +8,4 @@ verify.fileAfterApplyingRefactorAtMarker('1', `class C { /** @return {number} */ get c(): number { return 12; } -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with return type from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/convertJSDocToTypes14.ts b/tests/cases/fourslash/convertJSDocToTypes14.ts new file mode 100644 index 0000000000000..43ac95a1c4a52 --- /dev/null +++ b/tests/cases/fourslash/convertJSDocToTypes14.ts @@ -0,0 +1,11 @@ +/// +/////** @return {number} */ +////function f() { +//// /*1*/return 12; +////} +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`/** @return {number} */ +function f(): number { + return 12; +}`, 'Annotate with return type from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/convertJSDocToTypes3.ts b/tests/cases/fourslash/convertJSDocToTypes3.ts index 3d3c1d35f449c..985b89ed70901 100644 --- a/tests/cases/fourslash/convertJSDocToTypes3.ts +++ b/tests/cases/fourslash/convertJSDocToTypes3.ts @@ -19,7 +19,7 @@ verify.fileAfterApplyingRefactorAtMarker('1', * @param {*} beta - I have no idea how this got here */ function f(x: number, y, z: string, alpha, beta) { -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with type from JSDoc', 'annotate'); verify.applicableRefactorAvailableAtMarker('2'); verify.fileAfterApplyingRefactorAtMarker('2', @@ -34,7 +34,7 @@ function f(x: number, y: { a: string; b: Date; }, z: string, alpha, beta) { -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with type from JSDoc', 'annotate'); verify.not.applicableRefactorAvailableAtMarker('3'); verify.not.applicableRefactorAvailableAtMarker('4'); @@ -51,4 +51,4 @@ function f(x: number, y: { a: string; b: Date; }, z: string, alpha, beta: any) { -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with type from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/convertJSDocToTypes4.ts b/tests/cases/fourslash/convertJSDocToTypes4.ts index ea80bf0452dd8..d4d5384b19c34 100644 --- a/tests/cases/fourslash/convertJSDocToTypes4.ts +++ b/tests/cases/fourslash/convertJSDocToTypes4.ts @@ -24,7 +24,7 @@ verify.fileAfterApplyingRefactorAtMarker('1', * @param {number!} delta */ function f(x: any, y, z, alpha, beta, gamma, delta) { -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with type from JSDoc', 'annotate'); verify.applicableRefactorAvailableAtMarker('2'); verify.fileAfterApplyingRefactorAtMarker('2', @@ -38,7 +38,7 @@ verify.fileAfterApplyingRefactorAtMarker('2', * @param {number!} delta */ function f(x: any, y: any, z, alpha, beta, gamma, delta) { -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with type from JSDoc', 'annotate'); verify.applicableRefactorAvailableAtMarker('3'); verify.fileAfterApplyingRefactorAtMarker('3', @@ -52,7 +52,7 @@ verify.fileAfterApplyingRefactorAtMarker('3', * @param {number!} delta */ function f(x: any, y: any, z: number | undefined, alpha, beta, gamma, delta) { -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with type from JSDoc', 'annotate'); verify.applicableRefactorAvailableAtMarker('4'); verify.fileAfterApplyingRefactorAtMarker('4', `/** @@ -65,7 +65,7 @@ verify.fileAfterApplyingRefactorAtMarker('4', * @param {number!} delta */ function f(x: any, y: any, z: number | undefined, alpha: number[], beta, gamma, delta) { -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with type from JSDoc', 'annotate'); verify.applicableRefactorAvailableAtMarker('5'); verify.fileAfterApplyingRefactorAtMarker('5', @@ -81,7 +81,7 @@ verify.fileAfterApplyingRefactorAtMarker('5', function f(x: any, y: any, z: number | undefined, alpha: number[], beta: (this: { a: string; }, arg1: string, arg2: number) => boolean, gamma, delta) { -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with type from JSDoc', 'annotate'); verify.applicableRefactorAvailableAtMarker('6'); verify.fileAfterApplyingRefactorAtMarker('6', `/** @@ -96,7 +96,7 @@ verify.fileAfterApplyingRefactorAtMarker('6', function f(x: any, y: any, z: number | undefined, alpha: number[], beta: (this: { a: string; }, arg1: string, arg2: number) => boolean, gamma: number | null, delta) { -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with type from JSDoc', 'annotate'); verify.applicableRefactorAvailableAtMarker('7'); verify.fileAfterApplyingRefactorAtMarker('7', @@ -112,4 +112,4 @@ verify.fileAfterApplyingRefactorAtMarker('7', function f(x: any, y: any, z: number | undefined, alpha: number[], beta: (this: { a: string; }, arg1: string, arg2: number) => boolean, gamma: number | null, delta: number) { -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with type from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/convertJSDocToTypes5.ts b/tests/cases/fourslash/convertJSDocToTypes5.ts index 7647e7be2f8cc..1f15bf5992460 100644 --- a/tests/cases/fourslash/convertJSDocToTypes5.ts +++ b/tests/cases/fourslash/convertJSDocToTypes5.ts @@ -12,4 +12,4 @@ verify.fileAfterApplyingRefactorAtMarker('1', /** @type {number | null} */ /** @type {number | null} */ p: number | null = null; -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with type from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/convertJSDocToTypes6.ts b/tests/cases/fourslash/convertJSDocToTypes6.ts index 991e84b76b648..91bc1523ea301 100644 --- a/tests/cases/fourslash/convertJSDocToTypes6.ts +++ b/tests/cases/fourslash/convertJSDocToTypes6.ts @@ -12,4 +12,4 @@ verify.fileAfterApplyingRefactorAtMarker('1', /** @type {number | null} */ /** @type {number | null} */ p: number | null; -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with type from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/convertJSDocToTypes7.ts b/tests/cases/fourslash/convertJSDocToTypes7.ts index 5046b4237ec9d..c78f8949b18ce 100644 --- a/tests/cases/fourslash/convertJSDocToTypes7.ts +++ b/tests/cases/fourslash/convertJSDocToTypes7.ts @@ -14,4 +14,4 @@ verify.fileAfterApplyingRefactorAtMarker('1', * @returns {number} */ function f(x): number { -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with return type from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/convertJSDocToTypes8.ts b/tests/cases/fourslash/convertJSDocToTypes8.ts index b3d0f82183639..502b945819ccc 100644 --- a/tests/cases/fourslash/convertJSDocToTypes8.ts +++ b/tests/cases/fourslash/convertJSDocToTypes8.ts @@ -14,4 +14,4 @@ verify.fileAfterApplyingRefactorAtMarker('1', * @returns {number} */ var f = function(x): number { -}`, 'Convert to Typescript type', 'convert'); +}`, 'Annotate with return type from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/convertJSDocToTypes9.ts b/tests/cases/fourslash/convertJSDocToTypes9.ts index 7f682a2459c5e..cf2581f5d8f97 100644 --- a/tests/cases/fourslash/convertJSDocToTypes9.ts +++ b/tests/cases/fourslash/convertJSDocToTypes9.ts @@ -12,4 +12,4 @@ verify.fileAfterApplyingRefactorAtMarker('1', * @param {?} x * @returns {number} */ -var f = (x: any) => x`, 'Convert to Typescript type', 'convert'); +var f = (x: any) => x`, 'Annotate with type from JSDoc', 'annotate'); From 96b80938909438a80d84c33ce342e7e1a45eda99 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 26 Sep 2017 09:08:39 -0700 Subject: [PATCH 04/14] Move filenames to match refactoring rename --- .../{convertJSDocToTypes.ts => annotateWithTypeFromJSDoc.ts} | 2 +- src/services/refactors/refactors.ts | 2 +- .../{convertJSDocToTypes1.ts => annotateWithTypeFromJSDoc1.ts} | 0 ...{convertJSDocToTypes10.ts => annotateWithTypeFromJSDoc10.ts} | 0 ...{convertJSDocToTypes11.ts => annotateWithTypeFromJSDoc11.ts} | 0 ...{convertJSDocToTypes12.ts => annotateWithTypeFromJSDoc12.ts} | 0 ...{convertJSDocToTypes13.ts => annotateWithTypeFromJSDoc13.ts} | 0 ...{convertJSDocToTypes14.ts => annotateWithTypeFromJSDoc14.ts} | 0 .../{convertJSDocToTypes2.ts => annotateWithTypeFromJSDoc2.ts} | 0 .../{convertJSDocToTypes3.ts => annotateWithTypeFromJSDoc3.ts} | 0 .../{convertJSDocToTypes4.ts => annotateWithTypeFromJSDoc4.ts} | 0 .../{convertJSDocToTypes5.ts => annotateWithTypeFromJSDoc5.ts} | 0 .../{convertJSDocToTypes6.ts => annotateWithTypeFromJSDoc6.ts} | 0 .../{convertJSDocToTypes7.ts => annotateWithTypeFromJSDoc7.ts} | 0 .../{convertJSDocToTypes8.ts => annotateWithTypeFromJSDoc8.ts} | 0 .../{convertJSDocToTypes9.ts => annotateWithTypeFromJSDoc9.ts} | 0 16 files changed, 2 insertions(+), 2 deletions(-) rename src/services/refactors/{convertJSDocToTypes.ts => annotateWithTypeFromJSDoc.ts} (97%) rename tests/cases/fourslash/{convertJSDocToTypes1.ts => annotateWithTypeFromJSDoc1.ts} (100%) rename tests/cases/fourslash/{convertJSDocToTypes10.ts => annotateWithTypeFromJSDoc10.ts} (100%) rename tests/cases/fourslash/{convertJSDocToTypes11.ts => annotateWithTypeFromJSDoc11.ts} (100%) rename tests/cases/fourslash/{convertJSDocToTypes12.ts => annotateWithTypeFromJSDoc12.ts} (100%) rename tests/cases/fourslash/{convertJSDocToTypes13.ts => annotateWithTypeFromJSDoc13.ts} (100%) rename tests/cases/fourslash/{convertJSDocToTypes14.ts => annotateWithTypeFromJSDoc14.ts} (100%) rename tests/cases/fourslash/{convertJSDocToTypes2.ts => annotateWithTypeFromJSDoc2.ts} (100%) rename tests/cases/fourslash/{convertJSDocToTypes3.ts => annotateWithTypeFromJSDoc3.ts} (100%) rename tests/cases/fourslash/{convertJSDocToTypes4.ts => annotateWithTypeFromJSDoc4.ts} (100%) rename tests/cases/fourslash/{convertJSDocToTypes5.ts => annotateWithTypeFromJSDoc5.ts} (100%) rename tests/cases/fourslash/{convertJSDocToTypes6.ts => annotateWithTypeFromJSDoc6.ts} (100%) rename tests/cases/fourslash/{convertJSDocToTypes7.ts => annotateWithTypeFromJSDoc7.ts} (100%) rename tests/cases/fourslash/{convertJSDocToTypes8.ts => annotateWithTypeFromJSDoc8.ts} (100%) rename tests/cases/fourslash/{convertJSDocToTypes9.ts => annotateWithTypeFromJSDoc9.ts} (100%) diff --git a/src/services/refactors/convertJSDocToTypes.ts b/src/services/refactors/annotateWithTypeFromJSDoc.ts similarity index 97% rename from src/services/refactors/convertJSDocToTypes.ts rename to src/services/refactors/annotateWithTypeFromJSDoc.ts index c9915d85961fe..d8b8e849bb608 100644 --- a/src/services/refactors/convertJSDocToTypes.ts +++ b/src/services/refactors/annotateWithTypeFromJSDoc.ts @@ -1,5 +1,5 @@ /* @internal */ -namespace ts.refactor.convertJSDocToTypes { +namespace ts.refactor.annotateWithTypeFromJSDoc { const actionName = "annotate"; const annotateTypeFromJSDoc: Refactor = { diff --git a/src/services/refactors/refactors.ts b/src/services/refactors/refactors.ts index d1d50cb50f9d0..a536d5a8b6263 100644 --- a/src/services/refactors/refactors.ts +++ b/src/services/refactors/refactors.ts @@ -1,3 +1,3 @@ -/// +/// /// /// diff --git a/tests/cases/fourslash/convertJSDocToTypes1.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc1.ts similarity index 100% rename from tests/cases/fourslash/convertJSDocToTypes1.ts rename to tests/cases/fourslash/annotateWithTypeFromJSDoc1.ts diff --git a/tests/cases/fourslash/convertJSDocToTypes10.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc10.ts similarity index 100% rename from tests/cases/fourslash/convertJSDocToTypes10.ts rename to tests/cases/fourslash/annotateWithTypeFromJSDoc10.ts diff --git a/tests/cases/fourslash/convertJSDocToTypes11.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc11.ts similarity index 100% rename from tests/cases/fourslash/convertJSDocToTypes11.ts rename to tests/cases/fourslash/annotateWithTypeFromJSDoc11.ts diff --git a/tests/cases/fourslash/convertJSDocToTypes12.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc12.ts similarity index 100% rename from tests/cases/fourslash/convertJSDocToTypes12.ts rename to tests/cases/fourslash/annotateWithTypeFromJSDoc12.ts diff --git a/tests/cases/fourslash/convertJSDocToTypes13.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc13.ts similarity index 100% rename from tests/cases/fourslash/convertJSDocToTypes13.ts rename to tests/cases/fourslash/annotateWithTypeFromJSDoc13.ts diff --git a/tests/cases/fourslash/convertJSDocToTypes14.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc14.ts similarity index 100% rename from tests/cases/fourslash/convertJSDocToTypes14.ts rename to tests/cases/fourslash/annotateWithTypeFromJSDoc14.ts diff --git a/tests/cases/fourslash/convertJSDocToTypes2.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc2.ts similarity index 100% rename from tests/cases/fourslash/convertJSDocToTypes2.ts rename to tests/cases/fourslash/annotateWithTypeFromJSDoc2.ts diff --git a/tests/cases/fourslash/convertJSDocToTypes3.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc3.ts similarity index 100% rename from tests/cases/fourslash/convertJSDocToTypes3.ts rename to tests/cases/fourslash/annotateWithTypeFromJSDoc3.ts diff --git a/tests/cases/fourslash/convertJSDocToTypes4.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc4.ts similarity index 100% rename from tests/cases/fourslash/convertJSDocToTypes4.ts rename to tests/cases/fourslash/annotateWithTypeFromJSDoc4.ts diff --git a/tests/cases/fourslash/convertJSDocToTypes5.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc5.ts similarity index 100% rename from tests/cases/fourslash/convertJSDocToTypes5.ts rename to tests/cases/fourslash/annotateWithTypeFromJSDoc5.ts diff --git a/tests/cases/fourslash/convertJSDocToTypes6.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc6.ts similarity index 100% rename from tests/cases/fourslash/convertJSDocToTypes6.ts rename to tests/cases/fourslash/annotateWithTypeFromJSDoc6.ts diff --git a/tests/cases/fourslash/convertJSDocToTypes7.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc7.ts similarity index 100% rename from tests/cases/fourslash/convertJSDocToTypes7.ts rename to tests/cases/fourslash/annotateWithTypeFromJSDoc7.ts diff --git a/tests/cases/fourslash/convertJSDocToTypes8.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc8.ts similarity index 100% rename from tests/cases/fourslash/convertJSDocToTypes8.ts rename to tests/cases/fourslash/annotateWithTypeFromJSDoc8.ts diff --git a/tests/cases/fourslash/convertJSDocToTypes9.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc9.ts similarity index 100% rename from tests/cases/fourslash/convertJSDocToTypes9.ts rename to tests/cases/fourslash/annotateWithTypeFromJSDoc9.ts From fc933d7c33bbc14add9f73a4639ee20439293787 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 26 Sep 2017 12:42:08 -0700 Subject: [PATCH 05/14] 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 | 39 +++-- .../refactors/annotateWithTypeFromJSDoc.ts | 109 ++++++++++-- .../fourslash/annotateWithTypeFromJSDoc15.ts | 158 ++++++++++++++++++ 3 files changed, 276 insertions(+), 30 deletions(-) create mode 100644 tests/cases/fourslash/annotateWithTypeFromJSDoc15.ts diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 80b08c60d343f..61f9773c7a027 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 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 d8b8e849bb608..dde367f0ac97e 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 0000000000000..487b456d561f4 --- /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'); From d797b4ab7692a995662ddb11ffa7b6b83004fb13 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 28 Sep 2017 11:40:56 -0700 Subject: [PATCH 06/14] Correctly transform jsdoc parameter types And give a better name for rest params --- src/compiler/emitter.ts | 4 ++-- src/services/refactors/annotateWithTypeFromJSDoc.ts | 7 +++++-- tests/cases/fourslash/annotateWithTypeFromJSDoc16.ts | 9 +++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 tests/cases/fourslash/annotateWithTypeFromJSDoc16.ts diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 61f9773c7a027..808e8aeaf81d7 100755 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -578,10 +578,10 @@ namespace ts { return emitLiteralType(node); case SyntaxKind.JSDocAllType: write("*"); - break; + return; case SyntaxKind.JSDocUnknownType: write("?"); - break; + return; case SyntaxKind.JSDocNullableType: return emitJSDocNullableType(node as JSDocNullableType); case SyntaxKind.JSDocNonNullableType: diff --git a/src/services/refactors/annotateWithTypeFromJSDoc.ts b/src/services/refactors/annotateWithTypeFromJSDoc.ts index dde367f0ac97e..3111c0d3ed901 100644 --- a/src/services/refactors/annotateWithTypeFromJSDoc.ts +++ b/src/services/refactors/annotateWithTypeFromJSDoc.ts @@ -189,8 +189,11 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { } 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); + 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 visitJSDocTypeReference(node: TypeReferenceNode) { diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc16.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc16.ts new file mode 100644 index 0000000000000..2f5ab2bcc72ee --- /dev/null +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc16.ts @@ -0,0 +1,9 @@ +/// +// @strict: true +/////** @type {function(*, ...number, ...boolean): void} */ +////var /*1*/x; + +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`/** @type {function(*, ...number, ...boolean): void} */ +var x: (arg0: any, arg1: number[], ...rest: boolean[]) => void;`, 'Annotate with type from JSDoc', 'annotate'); From 4930cad653443ec228e2fba8b9e4ba5d0c9c4fdf Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 11 Oct 2017 13:33:31 -0700 Subject: [PATCH 07/14] Convert all JSDoc parameters and return types of functions --- src/compiler/diagnosticMessages.json | 2 +- .../refactors/annotateWithTypeFromJSDoc.ts | 113 +++++++++--------- .../refactors/convertFunctionToEs6Class.ts | 2 +- 3 files changed, 57 insertions(+), 60 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index ab90d47f2a414..99b45639cac94 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3738,7 +3738,7 @@ "category": "Message", "code": 95007 }, - "Annotate with return type from JSDoc": { + "Annotate with types from JSDoc": { "category": "Message", "code": 95008 } diff --git a/src/services/refactors/annotateWithTypeFromJSDoc.ts b/src/services/refactors/annotateWithTypeFromJSDoc.ts index 3111c0d3ed901..529793b7cd1cb 100644 --- a/src/services/refactors/annotateWithTypeFromJSDoc.ts +++ b/src/services/refactors/annotateWithTypeFromJSDoc.ts @@ -5,13 +5,13 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { const annotateTypeFromJSDoc: Refactor = { name: "Annotate with type from JSDoc", description: Diagnostics.Annotate_with_type_from_JSDoc.message, - getEditsForAction, + getEditsForAction: getEditsForAnnotation, getAvailableActions }; - const annotateReturnTypeFromJSDoc: Refactor = { - name: "Annotate with return type from JSDoc", - description: Diagnostics.Annotate_with_return_type_from_JSDoc.message, - getEditsForAction, + const annotateFunctionFromJSDoc: Refactor = { + name: "Annotate with types from JSDoc", + description: Diagnostics.Annotate_with_types_from_JSDoc.message, + getEditsForAction: getEditsForFunctionAnnotation, getAvailableActions }; @@ -23,7 +23,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { | PropertyDeclaration; registerRefactor(annotateTypeFromJSDoc); - registerRefactor(annotateReturnTypeFromJSDoc); + registerRefactor(annotateFunctionFromJSDoc); function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined { if (isInJavaScriptFile(context.file)) { @@ -33,8 +33,10 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { const node = getTokenAtPosition(context.file, context.startPosition, /*includeJsDocComment*/ false); const decl = findAncestor(node, isTypedNode); if (decl && !decl.type) { - const annotate = getJSDocType(decl) ? annotateTypeFromJSDoc : - getJSDocReturnType(decl) ? annotateReturnTypeFromJSDoc : + const type = getJSDocType(decl); + const returnType = getJSDocReturnType(decl); + const annotate = (returnType || type && decl.kind === SyntaxKind.Parameter) ? annotateFunctionFromJSDoc : + type ? annotateTypeFromJSDoc : undefined; if (annotate) { return [{ @@ -51,16 +53,14 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { } } - function getEditsForAction(context: RefactorContext, action: string): RefactorEditInfo | undefined { - // Somehow wrong action got invoked? + function getEditsForAnnotation(context: RefactorContext, action: string): RefactorEditInfo | undefined { if (actionName !== action) { Debug.fail(`actionName !== action: ${actionName} !== ${action}`); return undefined; } - const start = context.startPosition; const sourceFile = context.file; - const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); + const token = getTokenAtPosition(sourceFile, context.startPosition, /*includeJsDocComment*/ false); const decl = findAncestor(token, isTypedNode); const jsdocType = getJSDocReturnType(decl) || getJSDocType(decl); if (!decl || !jsdocType || decl.type) { @@ -69,19 +69,25 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { } const changeTracker = textChanges.ChangeTracker.fromContext(context); - if (isParameterOfSimpleArrowFunction(decl)) { - // `x => x` becomes `(x: number) => x`, but in order to make the changeTracker generate the parentheses, - // we have to replace the entire function; it doesn't check that the node it's replacing might require - // 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, 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, transformJSDocType(jsdocType) as TypeNode)); + changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, addType(decl, transformJSDocType(jsdocType) as TypeNode)); + return { + edits: changeTracker.getChanges(), + renameFilename: undefined, + renameLocation: undefined + }; + } + + function getEditsForFunctionAnnotation(context: RefactorContext, action: string): RefactorEditInfo | undefined { + if (actionName !== action) { + Debug.fail(`actionName !== action: ${actionName} !== ${action}`); + return undefined; } + + const sourceFile = context.file; + const token = getTokenAtPosition(sourceFile, context.startPosition, /*includeJsDocComment*/ false); + const decl = findAncestor(token, isFunctionLikeDeclaration); + const changeTracker = textChanges.ChangeTracker.fromContext(context); + changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, addTypesToFunctionLike(decl)); return { edits: changeTracker.getChanges(), renameFilename: undefined, @@ -97,53 +103,44 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { node.kind === SyntaxKind.PropertyDeclaration; } - function replaceType(decl: DeclarationWithType, jsdocType: TypeNode) { + function addTypesToFunctionLike(decl: FunctionLikeDeclaration) { + const returnType = decl.type || transformJSDocType(getJSDocReturnType(decl)) as TypeNode; + const parameters = decl.parameters.map( + p => createParameter(p.decorators, p.modifiers, p.dotDotDotToken, p.name, p.questionToken, p.type || transformJSDocType(getJSDocType(p)) as TypeNode, p.initializer)); + switch (decl.kind) { + case SyntaxKind.FunctionDeclaration: + return createFunctionDeclaration(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.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, decl.typeParameters, parameters, returnType, decl.body); + case SyntaxKind.ArrowFunction: + return createArrowFunction(decl.modifiers, decl.typeParameters, parameters, returnType, decl.equalsGreaterThanToken, decl.body); + case SyntaxKind.MethodDeclaration: + return createMethod(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.questionToken, decl.typeParameters, parameters, returnType, decl.body); + case SyntaxKind.GetAccessor: + return createGetAccessor(decl.decorators, decl.modifiers, decl.name, parameters, returnType, decl.body); + case SyntaxKind.SetAccessor: + return createSetAccessor(decl.decorators, decl.modifiers, decl.name, parameters, decl.body); + default: + return Debug.fail(`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.Parameter: - return createParameter(decl.decorators, decl.modifiers, decl.dotDotDotToken, decl.name, decl.questionToken, 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); - case SyntaxKind.FunctionDeclaration: - 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, jsdocType, decl.body); - case SyntaxKind.ArrowFunction: - 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, jsdocType, decl.body); - case SyntaxKind.GetAccessor: - return createGetAccessor(decl.decorators, decl.modifiers, decl.name, decl.parameters, jsdocType, decl.body); default: Debug.fail(`Unexpected SyntaxKind: ${decl.kind}`); return undefined; } } - function isParameterOfSimpleArrowFunction(decl: DeclarationWithType) { - return decl.kind === SyntaxKind.Parameter && decl.parent.kind === SyntaxKind.ArrowFunction && isSimpleArrowFunction(decl.parent); - } - - function isSimpleArrowFunction(parentNode: FunctionTypeNode | ArrowFunction | JSDocFunctionType) { - const parameter = singleOrUndefined(parentNode.parameters); - return parameter - && parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter - && !(isArrowFunction(parentNode) && parentNode.type) // arrow function may not have return type annotation - && !some(parentNode.decorators) // parent may not have decorators - && !some(parentNode.modifiers) // parent may not have modifiers - && !some(parentNode.typeParameters) // parent may not have type parameters - && !some(parameter.decorators) // parameter may not have decorators - && !some(parameter.modifiers) // parameter may not have modifiers - && !parameter.dotDotDotToken // parameter may not be rest - && !parameter.questionToken // parameter may not be optional - && !parameter.type // parameter may not have a type annotation - && !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; diff --git a/src/services/refactors/convertFunctionToEs6Class.ts b/src/services/refactors/convertFunctionToEs6Class.ts index e4cd1a420832c..6634dd6121ad8 100644 --- a/src/services/refactors/convertFunctionToEs6Class.ts +++ b/src/services/refactors/convertFunctionToEs6Class.ts @@ -261,4 +261,4 @@ namespace ts.refactor.convertFunctionToES6Class { return cls; } } -} \ No newline at end of file +} From 1a1c1f9e93a6383192aca0a94d10d6d5ce9334b3 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 11 Oct 2017 13:34:16 -0700 Subject: [PATCH 08/14] Add and update jsdoc annotation refactoring tests --- .../fourslash/annotateWithTypeFromJSDoc10.ts | 2 +- .../fourslash/annotateWithTypeFromJSDoc11.ts | 2 +- .../fourslash/annotateWithTypeFromJSDoc12.ts | 2 +- .../fourslash/annotateWithTypeFromJSDoc13.ts | 2 +- .../fourslash/annotateWithTypeFromJSDoc14.ts | 2 +- .../fourslash/annotateWithTypeFromJSDoc15.ts | 130 +----------------- .../fourslash/annotateWithTypeFromJSDoc16.ts | 4 +- .../fourslash/annotateWithTypeFromJSDoc17.ts | 18 +++ .../fourslash/annotateWithTypeFromJSDoc18.ts | 11 ++ .../fourslash/annotateWithTypeFromJSDoc3.ts | 34 +---- .../fourslash/annotateWithTypeFromJSDoc4.ts | 88 +----------- .../fourslash/annotateWithTypeFromJSDoc7.ts | 4 +- .../fourslash/annotateWithTypeFromJSDoc8.ts | 4 +- .../fourslash/annotateWithTypeFromJSDoc9.ts | 2 +- 14 files changed, 47 insertions(+), 258 deletions(-) create mode 100644 tests/cases/fourslash/annotateWithTypeFromJSDoc17.ts create mode 100644 tests/cases/fourslash/annotateWithTypeFromJSDoc18.ts diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc10.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc10.ts index 88b565fa9328a..0553a411c2e6f 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc10.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc10.ts @@ -12,4 +12,4 @@ verify.fileAfterApplyingRefactorAtMarker('1', * @param {?} x * @returns {number} */ -var f = (x): number => x`, 'Annotate with return type from JSDoc', 'annotate'); +var f = (x: any): number => x`, 'Annotate with types from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc11.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc11.ts index 63b2d85fbfe89..aaa7aaefa5b7e 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc11.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc11.ts @@ -12,4 +12,4 @@ verify.fileAfterApplyingRefactorAtMarker('2', * @param {?} x * @returns {number} */ -var f = (x: any) => x`, 'Annotate with type from JSDoc', 'annotate'); +var f = (x: any): number => x`, 'Annotate with types from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc12.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc12.ts index 95fa0b55cd292..c3c1aad5a90ef 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc12.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc12.ts @@ -18,4 +18,4 @@ verify.fileAfterApplyingRefactorAtMarker('1', */ m(x): any[] { } -}`, 'Annotate with return type from JSDoc', 'annotate'); +}`, 'Annotate with types from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc13.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc13.ts index caa3315b87fb2..a47c090cd17e9 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc13.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc13.ts @@ -8,4 +8,4 @@ verify.fileAfterApplyingRefactorAtMarker('1', `class C { /** @return {number} */ get c(): number { return 12; } -}`, 'Annotate with return type from JSDoc', 'annotate'); +}`, 'Annotate with types from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc14.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc14.ts index 43ac95a1c4a52..13b4591de4764 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc14.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc14.ts @@ -8,4 +8,4 @@ verify.fileAfterApplyingRefactorAtMarker('1', `/** @return {number} */ function f(): number { return 12; -}`, 'Annotate with return type from JSDoc', 'annotate'); +}`, 'Annotate with types from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc15.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc15.ts index 487b456d561f4..c316430b71803 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc15.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc15.ts @@ -13,134 +13,6 @@ //// */ ////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', `/** @@ -155,4 +27,4 @@ verify.fileAfterApplyingRefactorAtMarker('9', * @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'); +}`, 'Annotate with types from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc16.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc16.ts index 2f5ab2bcc72ee..a332a84d9109d 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc16.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc16.ts @@ -1,9 +1,9 @@ /// // @strict: true /////** @type {function(*, ...number, ...boolean): void} */ -////var /*1*/x; +////var /*1*/x = (x, ys, ...zs) => { }; verify.applicableRefactorAvailableAtMarker('1'); verify.fileAfterApplyingRefactorAtMarker('1', `/** @type {function(*, ...number, ...boolean): void} */ -var x: (arg0: any, arg1: number[], ...rest: boolean[]) => void;`, 'Annotate with type from JSDoc', 'annotate'); +var x: (arg0: any, arg1: number[], ...rest: boolean[]) => void = (x, ys, ...zs) => { };`, 'Annotate with type from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc17.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc17.ts new file mode 100644 index 0000000000000..ed3180d3bd8be --- /dev/null +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc17.ts @@ -0,0 +1,18 @@ +/// +////class C { +//// /** +//// * @param {number} x - the first parameter +//// */ +//// constructor(/*1*/x) { +//// } +////} +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`class C { + /** + * @param {number} x - the first parameter + */ + constructor(x: number) { + } +}`, 'Annotate with types from JSDoc', 'annotate'); + diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc18.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc18.ts new file mode 100644 index 0000000000000..9cfcb5c2471f4 --- /dev/null +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc18.ts @@ -0,0 +1,11 @@ +/// +////class C { +//// /** @param {number} value */ +//// set c(/*1*/value) { return 12 } +////} +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`class C { + /** @param {number} value */ + set c(value: number) { return 12; } +}`, 'Annotate with types from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc3.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc3.ts index 985b89ed70901..ca5dbe312e3a0 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc3.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc3.ts @@ -9,37 +9,10 @@ ////function f(/*1*/x, /*2*/y, /*3*/z: string, /*4*/alpha, /*5*/beta) { ////} -verify.applicableRefactorAvailableAtMarker('1'); -verify.fileAfterApplyingRefactorAtMarker('1', -`/** - * @param {number} x - the first parameter - * @param {{ a: string, b: Date }} y - the most complex parameter - * @param z - the best parameter - * @param alpha - the other best parameter - * @param {*} beta - I have no idea how this got here - */ -function f(x: number, y, z: string, alpha, beta) { -}`, 'Annotate with type from JSDoc', 'annotate'); - -verify.applicableRefactorAvailableAtMarker('2'); -verify.fileAfterApplyingRefactorAtMarker('2', -`/** - * @param {number} x - the first parameter - * @param {{ a: string, b: Date }} y - the most complex parameter - * @param z - the best parameter - * @param alpha - the other best parameter - * @param {*} beta - I have no idea how this got here - */ -function f(x: number, y: { - a: string; - b: Date; -}, z: string, alpha, beta) { -}`, 'Annotate with type from JSDoc', 'annotate'); - verify.not.applicableRefactorAvailableAtMarker('3'); verify.not.applicableRefactorAvailableAtMarker('4'); -verify.applicableRefactorAvailableAtMarker('5'); -verify.fileAfterApplyingRefactorAtMarker('5', +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', `/** * @param {number} x - the first parameter * @param {{ a: string, b: Date }} y - the most complex parameter @@ -51,4 +24,5 @@ function f(x: number, y: { a: string; b: Date; }, z: string, alpha, beta: any) { -}`, 'Annotate with type from JSDoc', 'annotate'); +}`, 'Annotate with types from JSDoc', 'annotate'); + diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc4.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc4.ts index d4d5384b19c34..2a2d7f943e2e3 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc4.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc4.ts @@ -12,94 +12,8 @@ ////function f(/*1*/x, /*2*/y, /*3*/z, /*4*/alpha, /*5*/beta, /*6*/gamma, /*7*/delta) { ////} -verify.applicableRefactorAvailableAtMarker('1'); -verify.fileAfterApplyingRefactorAtMarker('1', -`/** - * @param {*} x - * @param {?} y - * @param {number=} z - * @param {...number} alpha - * @param {function(this:{ a: string}, string, number): boolean} beta - * @param {number?} gamma - * @param {number!} delta - */ -function f(x: any, y, z, alpha, beta, gamma, delta) { -}`, 'Annotate with type from JSDoc', 'annotate'); - -verify.applicableRefactorAvailableAtMarker('2'); -verify.fileAfterApplyingRefactorAtMarker('2', -`/** - * @param {*} x - * @param {?} y - * @param {number=} z - * @param {...number} alpha - * @param {function(this:{ a: string}, string, number): boolean} beta - * @param {number?} gamma - * @param {number!} delta - */ -function f(x: any, y: any, z, alpha, beta, gamma, delta) { -}`, 'Annotate with type from JSDoc', 'annotate'); - -verify.applicableRefactorAvailableAtMarker('3'); -verify.fileAfterApplyingRefactorAtMarker('3', -`/** - * @param {*} x - * @param {?} y - * @param {number=} z - * @param {...number} alpha - * @param {function(this:{ a: string}, string, number): boolean} beta - * @param {number?} gamma - * @param {number!} delta - */ -function f(x: any, y: any, z: number | undefined, alpha, beta, gamma, delta) { -}`, 'Annotate with type from JSDoc', 'annotate'); -verify.applicableRefactorAvailableAtMarker('4'); -verify.fileAfterApplyingRefactorAtMarker('4', -`/** - * @param {*} x - * @param {?} y - * @param {number=} z - * @param {...number} alpha - * @param {function(this:{ a: string}, string, number): boolean} beta - * @param {number?} gamma - * @param {number!} delta - */ -function f(x: any, y: any, z: number | undefined, alpha: number[], beta, gamma, delta) { -}`, 'Annotate with type from JSDoc', 'annotate'); - verify.applicableRefactorAvailableAtMarker('5'); verify.fileAfterApplyingRefactorAtMarker('5', -`/** - * @param {*} x - * @param {?} y - * @param {number=} z - * @param {...number} alpha - * @param {function(this:{ a: string}, string, number): boolean} beta - * @param {number?} gamma - * @param {number!} delta - */ -function f(x: any, y: any, z: number | undefined, alpha: number[], beta: (this: { - a: string; -}, arg1: string, arg2: number) => boolean, gamma, delta) { -}`, 'Annotate with type from JSDoc', 'annotate'); -verify.applicableRefactorAvailableAtMarker('6'); -verify.fileAfterApplyingRefactorAtMarker('6', -`/** - * @param {*} x - * @param {?} y - * @param {number=} z - * @param {...number} alpha - * @param {function(this:{ a: string}, string, number): boolean} beta - * @param {number?} gamma - * @param {number!} delta - */ -function f(x: any, y: any, z: number | undefined, alpha: number[], beta: (this: { - a: string; -}, arg1: string, arg2: number) => boolean, gamma: number | null, delta) { -}`, 'Annotate with type from JSDoc', 'annotate'); - -verify.applicableRefactorAvailableAtMarker('7'); -verify.fileAfterApplyingRefactorAtMarker('7', `/** * @param {*} x * @param {?} y @@ -112,4 +26,4 @@ verify.fileAfterApplyingRefactorAtMarker('7', function f(x: any, y: any, z: number | undefined, alpha: number[], beta: (this: { a: string; }, arg1: string, arg2: number) => boolean, gamma: number | null, delta: number) { -}`, 'Annotate with type from JSDoc', 'annotate'); +}`, 'Annotate with types from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc7.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc7.ts index c78f8949b18ce..a99ea8233894d 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc7.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc7.ts @@ -13,5 +13,5 @@ verify.fileAfterApplyingRefactorAtMarker('1', * @param {number} x * @returns {number} */ -function f(x): number { -}`, 'Annotate with return type from JSDoc', 'annotate'); +function f(x: number): number { +}`, 'Annotate with types from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc8.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc8.ts index 502b945819ccc..72ddb36988e0a 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc8.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc8.ts @@ -13,5 +13,5 @@ verify.fileAfterApplyingRefactorAtMarker('1', * @param {number} x * @returns {number} */ -var f = function(x): number { -}`, 'Annotate with return type from JSDoc', 'annotate'); +var f = function(x: number): number { +}`, 'Annotate with types from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc9.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc9.ts index cf2581f5d8f97..6b967e8e004ac 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc9.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc9.ts @@ -12,4 +12,4 @@ verify.fileAfterApplyingRefactorAtMarker('1', * @param {?} x * @returns {number} */ -var f = (x: any) => x`, 'Annotate with type from JSDoc', 'annotate'); +var f = (x: any): number => x`, 'Annotate with types from JSDoc', 'annotate'); From 123347d5c42390068325590f348b4e7be3b78f87 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 12 Oct 2017 11:40:07 -0700 Subject: [PATCH 09/14] Convert @template tag to type parameters in refactor --- src/compiler/utilities.ts | 16 ++++++------ .../refactors/annotateWithTypeFromJSDoc.ts | 25 ++++++++++--------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2c1e25f74afd4..c4d3de453ba5c 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -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 { + export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters, checkJSDoc?: boolean): ReadonlyArray { 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, writer: EmitTextWriter, node: TextRange, leadingComments: ReadonlyArray) { diff --git a/src/services/refactors/annotateWithTypeFromJSDoc.ts b/src/services/refactors/annotateWithTypeFromJSDoc.ts index 529793b7cd1cb..bc6dcd073bf49 100644 --- a/src/services/refactors/annotateWithTypeFromJSDoc.ts +++ b/src/services/refactors/annotateWithTypeFromJSDoc.ts @@ -31,11 +31,11 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { } const node = getTokenAtPosition(context.file, context.startPosition, /*includeJsDocComment*/ false); - const decl = findAncestor(node, isTypedNode); + const decl = findAncestor(node, isDeclarationWithType); if (decl && !decl.type) { const type = getJSDocType(decl); - const returnType = getJSDocReturnType(decl); - const annotate = (returnType || type && decl.kind === SyntaxKind.Parameter) ? annotateFunctionFromJSDoc : + const isFunctionWithJSDoc = isFunctionLikeDeclaration(decl) && (getJSDocReturnType(decl) || decl.parameters.some(p => !!getJSDocType(p))); + const annotate = (isFunctionWithJSDoc || type && decl.kind === SyntaxKind.Parameter) ? annotateFunctionFromJSDoc : type ? annotateTypeFromJSDoc : undefined; if (annotate) { @@ -61,7 +61,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { const sourceFile = context.file; const token = getTokenAtPosition(sourceFile, context.startPosition, /*includeJsDocComment*/ false); - const decl = findAncestor(token, isTypedNode); + const decl = findAncestor(token, isDeclarationWithType); const jsdocType = getJSDocReturnType(decl) || getJSDocType(decl); if (!decl || !jsdocType || decl.type) { Debug.fail(`!decl || !jsdocType || decl.type: !${decl} || !${jsdocType} || ${decl.type}`); @@ -95,7 +95,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { }; } - function isTypedNode(node: Node): node is DeclarationWithType { + function isDeclarationWithType(node: Node): node is DeclarationWithType { return isFunctionLikeDeclaration(node) || node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.Parameter || @@ -104,22 +104,23 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { } function addTypesToFunctionLike(decl: FunctionLikeDeclaration) { - const returnType = decl.type || transformJSDocType(getJSDocReturnType(decl)) as TypeNode; + const typeParameters = getEffectiveTypeParameterDeclarations(decl, /*checkJSDoc*/ true); const parameters = decl.parameters.map( - p => createParameter(p.decorators, p.modifiers, p.dotDotDotToken, p.name, p.questionToken, p.type || transformJSDocType(getJSDocType(p)) as TypeNode, p.initializer)); + 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, decl.typeParameters, parameters, returnType, decl.body); + 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, decl.typeParameters, parameters, returnType, decl.body); + return createFunctionExpression(decl.modifiers, decl.asteriskToken, (decl as FunctionExpression).name, typeParameters, parameters, returnType, decl.body); case SyntaxKind.ArrowFunction: - return createArrowFunction(decl.modifiers, decl.typeParameters, parameters, returnType, decl.equalsGreaterThanToken, decl.body); + 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, decl.typeParameters, parameters, returnType, decl.body); + 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, parameters, returnType, decl.body); + 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: From b440d75bc427636211c46eb2b1140d51af34820a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 12 Oct 2017 11:40:40 -0700 Subject: [PATCH 10/14] Test refactor of JSDoc @template tag --- .../fourslash/annotateWithTypeFromJSDoc19.ts | 19 +++++++++++++++++++ .../fourslash/annotateWithTypeFromJSDoc20.ts | 17 +++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/cases/fourslash/annotateWithTypeFromJSDoc19.ts create mode 100644 tests/cases/fourslash/annotateWithTypeFromJSDoc20.ts diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc19.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc19.ts new file mode 100644 index 0000000000000..9e7ef6d25fd63 --- /dev/null +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc19.ts @@ -0,0 +1,19 @@ +/// +// @strict: true +/////** +//// * @template T +//// * @param {number} a +//// * @param {T} b +//// */ +////function /*1*/f(a, b) { +////} + +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`/** + * @template T + * @param {number} a + * @param {T} b + */ +function f(a: number, b: T) { +}`, 'Annotate with types from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc20.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc20.ts new file mode 100644 index 0000000000000..a45eb788c738c --- /dev/null +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc20.ts @@ -0,0 +1,17 @@ +/// +// @strict: true +/////** +//// * @param {number} a +//// * @param {T} b +//// */ +////function /*1*/f(a, b) { +////} + +verify.applicableRefactorAvailableAtMarker('1'); +verify.fileAfterApplyingRefactorAtMarker('1', +`/** + * @param {number} a + * @param {T} b + */ +function f(a: number, b: T) { +}`, 'Annotate with types from JSDoc', 'annotate'); From f35764d4ecf69e5c6be292fa7018fdc6f27f70c9 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 12 Oct 2017 14:28:34 -0700 Subject: [PATCH 11/14] Fix duplicated JSDoc comments Incorporate suppressLeadingAndTrailingTrivia just added by @amcasey. --- src/services/refactors/annotateWithTypeFromJSDoc.ts | 10 +++++++--- tests/cases/fourslash/annotateWithTypeFromJSDoc12.ts | 3 --- tests/cases/fourslash/annotateWithTypeFromJSDoc5.ts | 1 - tests/cases/fourslash/annotateWithTypeFromJSDoc6.ts | 1 - 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/services/refactors/annotateWithTypeFromJSDoc.ts b/src/services/refactors/annotateWithTypeFromJSDoc.ts index bc6dcd073bf49..1e32df4a51a02 100644 --- a/src/services/refactors/annotateWithTypeFromJSDoc.ts +++ b/src/services/refactors/annotateWithTypeFromJSDoc.ts @@ -62,14 +62,16 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { const sourceFile = context.file; const token = getTokenAtPosition(sourceFile, context.startPosition, /*includeJsDocComment*/ false); const decl = findAncestor(token, isDeclarationWithType); - const jsdocType = getJSDocReturnType(decl) || getJSDocType(decl); + const jsdocType = getJSDocType(decl); if (!decl || !jsdocType || decl.type) { Debug.fail(`!decl || !jsdocType || decl.type: !${decl} || !${jsdocType} || ${decl.type}`); return undefined; } const changeTracker = textChanges.ChangeTracker.fromContext(context); - changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, addType(decl, transformJSDocType(jsdocType) as TypeNode)); + 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, @@ -87,7 +89,9 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { const token = getTokenAtPosition(sourceFile, context.startPosition, /*includeJsDocComment*/ false); const decl = findAncestor(token, isFunctionLikeDeclaration); const changeTracker = textChanges.ChangeTracker.fromContext(context); - changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, addTypesToFunctionLike(decl)); + const functionWithType = addTypesToFunctionLike(decl); + suppressLeadingAndTrailingTrivia(functionWithType); + changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, functionWithType); return { edits: changeTracker.getChanges(), renameFilename: undefined, diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc12.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc12.ts index c3c1aad5a90ef..e541bad4e4f29 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc12.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc12.ts @@ -10,9 +10,6 @@ verify.applicableRefactorAvailableAtMarker('1'); verify.fileAfterApplyingRefactorAtMarker('1', `class C { - /** - * @return {...*} - */ /** * @return {...*} */ diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc5.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc5.ts index 1f15bf5992460..83e0888ddf589 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc5.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc5.ts @@ -9,7 +9,6 @@ verify.applicableRefactorAvailableAtMarker('1'); verify.fileAfterApplyingRefactorAtMarker('1', `class C { - /** @type {number | null} */ /** @type {number | null} */ p: number | null = null; }`, 'Annotate with type from JSDoc', 'annotate'); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc6.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc6.ts index 91bc1523ea301..f6fce6c449f86 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc6.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc6.ts @@ -9,7 +9,6 @@ verify.applicableRefactorAvailableAtMarker('1'); verify.fileAfterApplyingRefactorAtMarker('1', `declare class C { - /** @type {number | null} */ /** @type {number | null} */ p: number | null; }`, 'Annotate with type from JSDoc', 'annotate'); From c83daa64811c7705318de6c35d23f76a9c24c744 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 13 Oct 2017 09:38:01 -0700 Subject: [PATCH 12/14] JSDoc->type refactor:Renames+improve never handling --- .../refactors/annotateWithTypeFromJSDoc.ts | 66 +++++++++---------- .../fourslash/annotateWithTypeFromJSDoc5.ts | 1 - .../fourslash/annotateWithTypeFromJSDoc6.ts | 1 - 3 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/services/refactors/annotateWithTypeFromJSDoc.ts b/src/services/refactors/annotateWithTypeFromJSDoc.ts index 1e32df4a51a02..52d3fe30e8d60 100644 --- a/src/services/refactors/annotateWithTypeFromJSDoc.ts +++ b/src/services/refactors/annotateWithTypeFromJSDoc.ts @@ -32,24 +32,25 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { const node = getTokenAtPosition(context.file, context.startPosition, /*includeJsDocComment*/ false); const decl = findAncestor(node, isDeclarationWithType); - if (decl && !decl.type) { - const type = getJSDocType(decl); - const isFunctionWithJSDoc = isFunctionLikeDeclaration(decl) && (getJSDocReturnType(decl) || decl.parameters.some(p => !!getJSDocType(p))); - const annotate = (isFunctionWithJSDoc || type && decl.kind === SyntaxKind.Parameter) ? annotateFunctionFromJSDoc : - type ? annotateTypeFromJSDoc : - undefined; - if (annotate) { - return [{ - name: annotate.name, - description: annotate.description, - actions: [ - { - description: annotate.description, - name: actionName - } - ] - }]; - } + 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 + } + ] + }]; } } @@ -64,8 +65,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { const decl = findAncestor(token, isDeclarationWithType); const jsdocType = getJSDocType(decl); if (!decl || !jsdocType || decl.type) { - Debug.fail(`!decl || !jsdocType || decl.type: !${decl} || !${jsdocType} || ${decl.type}`); - return undefined; + return Debug.fail(`!decl || !jsdocType || decl.type: !${decl} || !${jsdocType} || ${decl.type}`); } const changeTracker = textChanges.ChangeTracker.fromContext(context); @@ -128,7 +128,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { case SyntaxKind.SetAccessor: return createSetAccessor(decl.decorators, decl.modifiers, decl.name, parameters, decl.body); default: - return Debug.fail(`Unexpected SyntaxKind: ${(decl as any).kind}`); + return Debug.assertNever(decl,`Unexpected SyntaxKind: ${(decl as any).kind}`); } } @@ -155,42 +155,42 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { case SyntaxKind.JSDocUnknownType: return createTypeReferenceNode("any", emptyArray); case SyntaxKind.JSDocOptionalType: - return visitJSDocOptionalType(node as JSDocOptionalType); + return transformJSDocOptionalType(node as JSDocOptionalType); case SyntaxKind.JSDocNonNullableType: return transformJSDocType((node as JSDocNonNullableType).type); case SyntaxKind.JSDocNullableType: - return visitJSDocNullableType(node as JSDocNullableType); + return transformJSDocNullableType(node as JSDocNullableType); case SyntaxKind.JSDocVariadicType: - return visitJSDocVariadicType(node as JSDocVariadicType); + return transformJSDocVariadicType(node as JSDocVariadicType); case SyntaxKind.JSDocFunctionType: - return visitJSDocFunctionType(node as JSDocFunctionType); + return transformJSDocFunctionType(node as JSDocFunctionType); case SyntaxKind.Parameter: - return visitJSDocParameter(node as ParameterDeclaration); + return transformJSDocParameter(node as ParameterDeclaration); case SyntaxKind.TypeReference: - return visitJSDocTypeReference(node as TypeReferenceNode); + return transformJSDocTypeReference(node as TypeReferenceNode); default: return visitEachChild(node, transformJSDocType, /*context*/ undefined) as TypeNode; } } - function visitJSDocOptionalType(node: JSDocOptionalType) { + function transformJSDocOptionalType(node: JSDocOptionalType) { return createUnionTypeNode([visitNode(node.type, transformJSDocType), createTypeReferenceNode("undefined", emptyArray)]); } - function visitJSDocNullableType(node: JSDocNullableType) { + function transformJSDocNullableType(node: JSDocNullableType) { return createUnionTypeNode([visitNode(node.type, transformJSDocType), createTypeReferenceNode("null", emptyArray)]); } - function visitJSDocVariadicType(node: JSDocVariadicType) { + function transformJSDocVariadicType(node: JSDocVariadicType) { return createArrayTypeNode(visitNode(node.type, transformJSDocType)); } - function visitJSDocFunctionType(node: JSDocFunctionType) { + function transformJSDocFunctionType(node: JSDocFunctionType) { const parameters = node.parameters && node.parameters.map(transformJSDocType); return createFunctionTypeNode(emptyArray, parameters as ParameterDeclaration[], node.type); } - function visitJSDocParameter(node: ParameterDeclaration) { + 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); @@ -198,7 +198,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { return createParameter(node.decorators, node.modifiers, dotdotdot, name, node.questionToken, visitNode(node.type, transformJSDocType), node.initializer); } - function visitJSDocTypeReference(node: TypeReferenceNode) { + function transformJSDocTypeReference(node: TypeReferenceNode) { let name = node.typeName; let args = node.typeArguments; if (isIdentifier(node.typeName)) { diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc5.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc5.ts index 83e0888ddf589..1ae2949ef3ca4 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc5.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc5.ts @@ -5,7 +5,6 @@ //// /*1*/p = null ////} -// NOTE: The duplicated comment is unintentional but needs a serious fix in trivia handling verify.applicableRefactorAvailableAtMarker('1'); verify.fileAfterApplyingRefactorAtMarker('1', `class C { diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc6.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc6.ts index f6fce6c449f86..53e8170533df6 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc6.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc6.ts @@ -5,7 +5,6 @@ //// /*1*/p; ////} -// NOTE: The duplicated comment is unintentional but needs a serious fix in trivia handling verify.applicableRefactorAvailableAtMarker('1'); verify.fileAfterApplyingRefactorAtMarker('1', `declare class C { From 84e3507151a80334e1e1ecfefe6141f77e3e3b6e Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 13 Oct 2017 09:45:41 -0700 Subject: [PATCH 13/14] return more Debug.fails instead of undefined. --- src/services/refactors/annotateWithTypeFromJSDoc.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/services/refactors/annotateWithTypeFromJSDoc.ts b/src/services/refactors/annotateWithTypeFromJSDoc.ts index 52d3fe30e8d60..17d910b309162 100644 --- a/src/services/refactors/annotateWithTypeFromJSDoc.ts +++ b/src/services/refactors/annotateWithTypeFromJSDoc.ts @@ -56,8 +56,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { function getEditsForAnnotation(context: RefactorContext, action: string): RefactorEditInfo | undefined { if (actionName !== action) { - Debug.fail(`actionName !== action: ${actionName} !== ${action}`); - return undefined; + return Debug.fail(`actionName !== action: ${actionName} !== ${action}`); } const sourceFile = context.file; @@ -81,8 +80,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { function getEditsForFunctionAnnotation(context: RefactorContext, action: string): RefactorEditInfo | undefined { if (actionName !== action) { - Debug.fail(`actionName !== action: ${actionName} !== ${action}`); - return undefined; + return Debug.fail(`actionName !== action: ${actionName} !== ${action}`); } const sourceFile = context.file; @@ -141,8 +139,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { case SyntaxKind.PropertyDeclaration: return createProperty(decl.decorators, decl.modifiers, decl.name, decl.questionToken, jsdocType, decl.initializer); default: - Debug.fail(`Unexpected SyntaxKind: ${decl.kind}`); - return undefined; + return Debug.fail(`Unexpected SyntaxKind: ${decl.kind}`); } } From 4cf06bbb02a2835021542571ddadfc0cf68cb3f6 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 13 Oct 2017 10:02:04 -0700 Subject: [PATCH 14/14] Fix spacing lint --- src/services/refactors/annotateWithTypeFromJSDoc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/refactors/annotateWithTypeFromJSDoc.ts b/src/services/refactors/annotateWithTypeFromJSDoc.ts index 17d910b309162..60c0517f68113 100644 --- a/src/services/refactors/annotateWithTypeFromJSDoc.ts +++ b/src/services/refactors/annotateWithTypeFromJSDoc.ts @@ -126,7 +126,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { 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}`); + return Debug.assertNever(decl, `Unexpected SyntaxKind: ${(decl as any).kind}`); } }