From ff1f33729b33c670a14c30694af64d786bc57136 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 8 Jun 2017 16:44:42 -0700 Subject: [PATCH] Improve contextual types and return type checking --- src/compiler/checker.ts | 162 +++++++++--------- src/compiler/utilities.ts | 52 +++++- .../reference/checkJsdocReturnTag2.errors.txt | 25 +++ .../reference/checkJsdocTypeTag2.errors.txt | 21 ++- .../baselines/reference/checkJsdocTypeTag2.js | 15 +- .../reference/contextualTypeFromJSDoc.symbols | 10 +- .../reference/contextualTypeFromJSDoc.types | 34 ++-- .../conformance/jsdoc/checkJsdocTypeTag2.ts | 8 +- .../jsdoc/contextualTypeFromJSDoc.ts | 6 +- 9 files changed, 200 insertions(+), 133 deletions(-) create mode 100644 tests/baselines/reference/checkJsdocReturnTag2.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 73a7dcc692c..b6b01a5b4b1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4200,16 +4200,6 @@ namespace ts { // Return the inferred type for a variable, parameter, or property declaration function getTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, includeOptionality: boolean): Type { - if (declaration.flags & NodeFlags.JavaScriptFile) { - // If this is a variable in a JavaScript file, then use the JSDoc type (if it has - // one as its type), otherwise fallback to the below standard TS codepaths to - // try to figure it out. - const type = getTypeForDeclarationFromJSDocComment(declaration); - if (type && type !== unknownType) { - return type; - } - } - // A variable declared in a for..in statement is of type string, or of type keyof T when the // right hand expression is of a type parameter type. if (declaration.parent.parent.kind === SyntaxKind.ForInStatement) { @@ -4231,8 +4221,9 @@ namespace ts { } // Use type from type annotation if one is present - if (declaration.type) { - const declaredType = getTypeFromTypeNode(declaration.type); + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + const declaredType = getTypeFromTypeNode(typeNode); return addOptionality(declaredType, /*optional*/ declaration.questionToken && includeOptionality); } @@ -4523,10 +4514,11 @@ namespace ts { function getAnnotatedAccessorType(accessor: AccessorDeclaration): Type { if (accessor) { if (accessor.kind === SyntaxKind.GetAccessor) { - return accessor.type && getTypeFromTypeNode(accessor.type); + const getterTypeAnnotation = getEffectiveReturnTypeNode(accessor); + return getterTypeAnnotation && getTypeFromTypeNode(getterTypeAnnotation); } else { - const setterTypeAnnotation = getSetAccessorTypeAnnotationNode(accessor); + const setterTypeAnnotation = getEffectiveSetAccessorTypeAnnotationNode(accessor); return setterTypeAnnotation && getTypeFromTypeNode(setterTypeAnnotation); } } @@ -4679,7 +4671,7 @@ namespace ts { function reportCircularityError(symbol: Symbol) { // Check if variable has type annotation that circularly references the variable itself - if ((symbol.valueDeclaration).type) { + if (getEffectiveTypeAnnotationNode(symbol.valueDeclaration)) { error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); return unknownType; @@ -5265,14 +5257,18 @@ namespace ts { // A variable-like declaration is considered independent (free of this references) if it has a type annotation // that specifies an independent type, or if it has no type annotation and no initializer (and thus of type any). function isIndependentVariableLikeDeclaration(node: VariableLikeDeclaration): boolean { - return node.type && isIndependentType(node.type) || !node.type && !node.initializer; + const typeNode = getEffectiveTypeAnnotationNode(node); + return typeNode ? isIndependentType(typeNode) : !node.initializer; } // A function-like declaration is considered independent (free of this references) if it has a return type // annotation that is considered independent and if each parameter is considered independent. function isIndependentFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { - if (node.kind !== SyntaxKind.Constructor && (!node.type || !isIndependentType(node.type))) { - return false; + if (node.kind !== SyntaxKind.Constructor) { + const typeNode = getEffectiveReturnTypeNode(node); + if (!typeNode || !isIndependentType(typeNode)) { + return false; + } } for (const parameter of node.parameters) { if (!isIndependentVariableLikeDeclaration(parameter)) { @@ -6424,15 +6420,10 @@ namespace ts { else if (classType) { return classType; } - else if (declaration.type) { - return getTypeFromTypeNode(declaration.type); - } - if (declaration.flags & NodeFlags.JavaScriptFile) { - const type = getReturnTypeFromJSDocComment(declaration); - if (type && type !== unknownType) { - return type; - } + const typeNode = getEffectiveReturnTypeNode(declaration); + if (typeNode) { + return getTypeFromTypeNode(typeNode); } // TypeScript 1.0 spec (April 2014): @@ -8333,7 +8324,7 @@ namespace ts { return false; } // Functions with any parameters that lack type annotations are context sensitive. - if (forEach(node.parameters, p => !p.type)) { + if (forEach(node.parameters, p => !getEffectiveTypeAnnotationNode(p))) { return true; } // For arrow functions we now know we're not context sensitive. @@ -12752,8 +12743,9 @@ namespace ts { function getContextualTypeForInitializerExpression(node: Expression): Type { const declaration = node.parent; if (node === declaration.initializer) { - if (declaration.type) { - return getTypeFromTypeNode(declaration.type); + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + return getTypeFromTypeNode(typeNode); } if (isInJavaScriptFile(declaration)) { const jsDocType = getTypeForDeclarationFromJSDocComment(declaration); @@ -12773,12 +12765,13 @@ namespace ts { if (isBindingPattern(declaration.parent)) { const parentDeclaration = declaration.parent.parent; const name = declaration.propertyName || declaration.name; - if (parentDeclaration.kind !== SyntaxKind.BindingElement && - parentDeclaration.type && - !isBindingPattern(name)) { - const text = getTextOfPropertyName(name); - if (text) { - return getTypeOfPropertyOfType(getTypeFromTypeNode(parentDeclaration.type), text); + if (parentDeclaration.kind !== SyntaxKind.BindingElement) { + const parentTypeNode = getEffectiveTypeAnnotationNode(parentDeclaration); + if (parentTypeNode && !isBindingPattern(name)) { + const text = getTextOfPropertyName(name); + if (text) { + return getTypeOfPropertyOfType(getTypeFromTypeNode(parentTypeNode), text); + } } } } @@ -12832,10 +12825,9 @@ namespace ts { function getContextualReturnType(functionDecl: FunctionLikeDeclaration): Type { // If the containing function has a return type annotation, is a constructor, or is a get accessor whose // corresponding set accessor has a type annotation, return statements in the function are contextually typed - if (functionDecl.type || - (isInJavaScriptFile(functionDecl) && getJSDocReturnType(functionDecl)) || - functionDecl.kind === SyntaxKind.Constructor || - functionDecl.kind === SyntaxKind.GetAccessor && getSetAccessorTypeAnnotationNode(getDeclarationOfKind(functionDecl.symbol, SyntaxKind.SetAccessor), /*includeJSDocType*/ true)) { + if (functionDecl.kind === SyntaxKind.Constructor || + getEffectiveReturnTypeNode(functionDecl) || + isGetAccessorWithAnnotatedSetAccessor(functionDecl)) { return getReturnTypeOfSignature(getSignatureFromDeclaration(functionDecl)); } @@ -16369,8 +16361,9 @@ namespace ts { if (checkMode === CheckMode.Inferential) { for (let i = 0; i < len; i++) { const declaration = signature.parameters[i].valueDeclaration; - if (declaration.type) { - inferTypes((mapper).inferences, getTypeFromTypeNode(declaration.type), getTypeAtPosition(context, i)); + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + inferTypes((mapper).inferences, getTypeFromTypeNode(typeNode), getTypeAtPosition(context, i)); } } } @@ -16385,14 +16378,14 @@ namespace ts { } for (let i = 0; i < len; i++) { const parameter = signature.parameters[i]; - if (!(parameter.valueDeclaration).type) { + if (!getEffectiveTypeAnnotationNode(parameter.valueDeclaration)) { const contextualParameterType = getTypeAtPosition(context, i); assignTypeToParameterAndFixTypeParameters(parameter, contextualParameterType, mapper, checkMode); } } if (signature.hasRestParameter && isRestParameterIndex(context, signature.parameters.length - 1)) { const parameter = lastOrUndefined(signature.parameters); - if (!(parameter.valueDeclaration).type) { + if (!getEffectiveTypeAnnotationNode(parameter.valueDeclaration)) { const contextualParameterType = getTypeOfSymbol(lastOrUndefined(context.parameters)); assignTypeToParameterAndFixTypeParameters(parameter, contextualParameterType, mapper, checkMode); } @@ -16460,14 +16453,6 @@ namespace ts { } } - function getReturnTypeFromJSDocComment(func: SignatureDeclaration | FunctionDeclaration): Type { - const jsdocType = getJSDocReturnType(func); - if (jsdocType) { - return getTypeFromTypeNode(jsdocType); - } - return undefined; - } - function createPromiseType(promisedType: Type): Type { // creates a `Promise` type where `T` is the promisedType argument const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); @@ -16693,16 +16678,16 @@ namespace ts { const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; if (returnType && returnType.flags & TypeFlags.Never) { - error(func.type, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point); + error(getEffectiveReturnTypeNode(func), Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point); } else if (returnType && !hasExplicitReturn) { // minimal check: function has syntactic return type annotation and no explicit return statements in the body // this function does not conform to the specification. // NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present - error(func.type, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); + error(getEffectiveReturnTypeNode(func), Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); } else if (returnType && strictNullChecks && !isTypeAssignableTo(undefinedType, returnType)) { - error(func.type, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); + error(getEffectiveReturnTypeNode(func), Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); } else if (compilerOptions.noImplicitReturns) { if (!returnType) { @@ -16717,7 +16702,7 @@ namespace ts { return; } } - error(func.type || func, Diagnostics.Not_all_code_paths_return_a_value); + error(getEffectiveReturnTypeNode(func) || func, Diagnostics.Not_all_code_paths_return_a_value); } } @@ -16757,7 +16742,7 @@ namespace ts { if (contextSensitive) { assignContextualParameterTypes(signature, contextualSignature, getContextualMapper(node), checkMode); } - if (mightFixTypeParameters || !node.type && !signature.resolvedReturnType) { + if (mightFixTypeParameters || !getEffectiveReturnTypeNode(node) && !signature.resolvedReturnType) { const returnType = getReturnTypeFromBody(node, checkMode); if (!signature.resolvedReturnType) { signature.resolvedReturnType = returnType; @@ -16785,10 +16770,11 @@ namespace ts { Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); const functionFlags = getFunctionFlags(node); - const returnOrPromisedType = node.type && + const returnTypeNode = getEffectiveReturnTypeNode(node); + const returnOrPromisedType = returnTypeNode && ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async ? checkAsyncFunctionReturnType(node) : // Async function - getTypeFromTypeNode(node.type)); // AsyncGenerator function, Generator function, or normal function + getTypeFromTypeNode(returnTypeNode)); // AsyncGenerator function, Generator function, or normal function if ((functionFlags & FunctionFlags.Generator) === 0) { // Async function or normal function // return is not necessary in the body of generators @@ -16796,7 +16782,7 @@ namespace ts { } if (node.body) { - if (!node.type) { + if (!returnTypeNode) { // There are some checks that are only performed in getReturnTypeFromBody, that may produce errors // we need. An example is the noImplicitAny errors resulting from widening the return expression // of a function. Because checking of function expression bodies is deferred, there was never an @@ -17582,8 +17568,9 @@ namespace ts { // There is no point in doing an assignability check if the function // has no explicit return type because the return type is directly computed // from the yield expressions. - if (func.type) { - const signatureElementType = getIteratedTypeOfGenerator(getTypeFromTypeNode(func.type), (functionFlags & FunctionFlags.Async) !== 0) || anyType; + const returnType = getEffectiveReturnTypeNode(func); + if (returnType) { + const signatureElementType = getIteratedTypeOfGenerator(getTypeFromTypeNode(returnType), (functionFlags & FunctionFlags.Async) !== 0) || anyType; if (nodeIsYieldStar) { checkTypeAssignableTo( functionFlags & FunctionFlags.Async @@ -18111,13 +18098,15 @@ namespace ts { forEach(node.parameters, checkParameter); + // TODO(rbuckton): Should we start checking JSDoc types? if (node.type) { checkSourceElement(node.type); } if (produceDiagnostics) { checkCollisionWithArgumentsInGeneratedCode(node); - if (noImplicitAny && !node.type) { + const returnTypeNode = getEffectiveReturnTypeNode(node); + if (noImplicitAny && !returnTypeNode) { switch (node.kind) { case SyntaxKind.ConstructSignature: error(node, Diagnostics.Construct_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); @@ -18128,12 +18117,12 @@ namespace ts { } } - if (node.type) { + if (returnTypeNode) { const functionFlags = getFunctionFlags(node); if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Generator)) === FunctionFlags.Generator) { - const returnType = getTypeFromTypeNode(node.type); + const returnType = getTypeFromTypeNode(returnTypeNode); if (returnType === voidType) { - error(node.type, Diagnostics.A_generator_cannot_have_a_void_type_annotation); + error(returnTypeNode, Diagnostics.A_generator_cannot_have_a_void_type_annotation); } else { const generatorElementType = getIteratedTypeOfGenerator(returnType, (functionFlags & FunctionFlags.Async) !== 0) || anyType; @@ -18147,7 +18136,7 @@ namespace ts { // interface BadGenerator extends Iterable, Iterator { } // function* g(): BadGenerator { } // Iterable and Iterator have different types! // - checkTypeAssignableTo(iterableIteratorInstantiation, returnType, node.type); + checkTypeAssignableTo(iterableIteratorInstantiation, returnType, returnTypeNode); } } else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { @@ -19159,7 +19148,8 @@ namespace ts { // then(...): Promise; // } // - const returnType = getTypeFromTypeNode(node.type); + const returnTypeNode = getEffectiveReturnTypeNode(node); + const returnType = getTypeFromTypeNode(returnTypeNode); if (languageVersion >= ScriptTarget.ES2015) { if (returnType === unknownType) { @@ -19169,21 +19159,21 @@ namespace ts { if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) { // The promise type was not a valid type reference to the global promise type, so we // report an error and return the unknown type. - error(node.type, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type); + error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type); return unknownType; } } else { // Always mark the type node as referenced if it points to a value - markTypeNodeAsReferenced(node.type); + markTypeNodeAsReferenced(returnTypeNode); if (returnType === unknownType) { return unknownType; } - const promiseConstructorName = getEntityNameFromTypeNode(node.type); + const promiseConstructorName = getEntityNameFromTypeNode(returnTypeNode); if (promiseConstructorName === undefined) { - error(node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, typeToString(returnType)); + error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, typeToString(returnType)); return unknownType; } @@ -19191,10 +19181,10 @@ namespace ts { const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : unknownType; if (promiseConstructorType === unknownType) { if (promiseConstructorName.kind === SyntaxKind.Identifier && promiseConstructorName.text === "Promise" && getTargetType(returnType) === getGlobalPromiseType(/*reportErrors*/ false)) { - error(node.type, Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); + error(returnTypeNode, Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); } else { - error(node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); + error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); } return unknownType; } @@ -19203,11 +19193,11 @@ namespace ts { if (globalPromiseConstructorLikeType === emptyObjectType) { // If we couldn't resolve the global PromiseConstructorLike type we cannot verify // compatibility with __awaiter. - error(node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); + error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); return unknownType; } - if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, node.type, + if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value)) { return unknownType; } @@ -19352,7 +19342,8 @@ namespace ts { } function getParameterTypeNodeForDecoratorCheck(node: ParameterDeclaration): TypeNode { - return node.dotDotDotToken ? getRestParameterElementType(node.type) : node.type; + const typeNode = getEffectiveTypeAnnotationNode(node); + return isRestParameter(node) ? getRestParameterElementType(typeNode) : typeNode; } /** Check the decorators of a node */ @@ -19398,14 +19389,15 @@ namespace ts { markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); } - markDecoratorMedataDataTypeNodeAsReferenced((node).type); + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node)); break; case SyntaxKind.PropertyDeclaration: - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node)); + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node)); break; + case SyntaxKind.Parameter: - markDecoratorMedataDataTypeNodeAsReferenced((node).type); + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node)); break; } } @@ -19470,14 +19462,15 @@ namespace ts { checkSourceElement(node.body); + const returnTypeNode = getEffectiveReturnTypeNode(node); if ((functionFlags & FunctionFlags.Generator) === 0) { // Async function or normal function - const returnOrPromisedType = node.type && (functionFlags & FunctionFlags.Async + const returnOrPromisedType = returnTypeNode && (functionFlags & FunctionFlags.Async ? checkAsyncFunctionReturnType(node) // Async function - : getTypeFromTypeNode(node.type)); // normal function + : getTypeFromTypeNode(returnTypeNode)); // normal function checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnOrPromisedType); } - if (produceDiagnostics && !node.type) { + if (produceDiagnostics && !returnTypeNode) { // Report an implicit any error if there is no body, no explicit return type, and node is not a private method // in an ambient context if (noImplicitAny && nodeIsMissing(node.body) && !isPrivateWithinAmbient(node)) { @@ -20607,7 +20600,8 @@ namespace ts { } function isGetAccessorWithAnnotatedSetAccessor(node: FunctionLikeDeclaration) { - return !!(node.kind === SyntaxKind.GetAccessor && getSetAccessorTypeAnnotationNode(getDeclarationOfKind(node.symbol, SyntaxKind.SetAccessor))); + return node.kind === SyntaxKind.GetAccessor + && getEffectiveSetAccessorTypeAnnotationNode(getDeclarationOfKind(node.symbol, SyntaxKind.SetAccessor)) !== undefined; } function isUnwrappedReturnTypeVoidOrAny(func: FunctionLikeDeclaration, returnType: Type): boolean { @@ -20651,7 +20645,7 @@ namespace ts { error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); } } - else if (func.type || isGetAccessorWithAnnotatedSetAccessor(func)) { + else if (getEffectiveReturnTypeNode(func) || isGetAccessorWithAnnotatedSetAccessor(func)) { if (functionFlags & FunctionFlags.Async) { // Async function const promisedType = getPromisedTypeOfPromise(returnType); const awaitedType = checkAwaitedType(exprType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c6ae9bfc2fd..681e3f625cc 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2620,20 +2620,19 @@ namespace ts { }); } - /** Get the type annotation for the value parameter. */ - export function getSetAccessorTypeAnnotationNode(accessor: SetAccessorDeclaration, includeJSDocType?: boolean): TypeNode { + function getSetAccessorValueParameter(accessor: SetAccessorDeclaration): ParameterDeclaration | undefined { if (accessor && accessor.parameters.length > 0) { const hasThis = accessor.parameters.length === 2 && parameterIsThisKeyword(accessor.parameters[0]); - const parameter = accessor.parameters[hasThis ? 1 : 0]; - if (parameter.type) { - return parameter.type; - } - if (includeJSDocType && parameter.flags & NodeFlags.JavaScriptFile) { - return getJSDocType(parameter); - } + return accessor.parameters[hasThis ? 1 : 0]; } } + /** Get the type annotation for the value parameter. */ + export function getSetAccessorTypeAnnotationNode(accessor: SetAccessorDeclaration): TypeNode { + const parameter = getSetAccessorValueParameter(accessor); + return parameter && parameter.type; + } + export function getThisParameter(signature: SignatureDeclaration): ParameterDeclaration | undefined { if (signature.parameters.length) { const thisParameter = signature.parameters[0]; @@ -2712,6 +2711,41 @@ 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 { + if (node.type) { + return node.type; + } + if (node.flags & NodeFlags.JavaScriptFile) { + return getJSDocType(node); + } + } + + /** + * 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 { + if (node.type) { + return node.type; + } + if (node.flags & NodeFlags.JavaScriptFile) { + return getJSDocReturnType(node); + } + } + + /** + * 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 { + const parameter = getSetAccessorValueParameter(node); + return parameter && getEffectiveTypeAnnotationNode(parameter); + } + export function emitNewLineBeforeLeadingComments(lineMap: number[], writer: EmitTextWriter, node: TextRange, leadingComments: CommentRange[]) { emitNewLineBeforeLeadingCommentsOfPosition(lineMap, writer, node.pos, leadingComments); } diff --git a/tests/baselines/reference/checkJsdocReturnTag2.errors.txt b/tests/baselines/reference/checkJsdocReturnTag2.errors.txt new file mode 100644 index 00000000000..23a450c83ca --- /dev/null +++ b/tests/baselines/reference/checkJsdocReturnTag2.errors.txt @@ -0,0 +1,25 @@ +tests/cases/conformance/jsdoc/returns.js(6,5): error TS2322: Type '5' is not assignable to type 'string'. +tests/cases/conformance/jsdoc/returns.js(13,5): error TS2322: Type 'true | 5' is not assignable to type 'string | number'. + Type 'true' is not assignable to type 'string | number'. + + +==== tests/cases/conformance/jsdoc/returns.js (2 errors) ==== + // @ts-check + /** + * @returns {string} This comment is not currently exposed + */ + function f() { + return 5; + ~~~~~~~~~ +!!! error TS2322: Type '5' is not assignable to type 'string'. + } + + /** + * @returns {string | number} This comment is not currently exposed + */ + function f1() { + return 5 || true; + ~~~~~~~~~~~~~~~~~ +!!! error TS2322: Type 'true | 5' is not assignable to type 'string | number'. +!!! error TS2322: Type 'true' is not assignable to type 'string | number'. + } \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocTypeTag2.errors.txt b/tests/baselines/reference/checkJsdocTypeTag2.errors.txt index 5f21aa61f6f..3590c10e278 100644 --- a/tests/baselines/reference/checkJsdocTypeTag2.errors.txt +++ b/tests/baselines/reference/checkJsdocTypeTag2.errors.txt @@ -1,13 +1,13 @@ tests/cases/conformance/jsdoc/0.js(3,5): error TS2322: Type 'true' is not assignable to type 'string'. tests/cases/conformance/jsdoc/0.js(6,5): error TS2322: Type '"hello"' is not assignable to type 'number'. tests/cases/conformance/jsdoc/0.js(10,4): error TS2345: Argument of type '"string"' is not assignable to parameter of type 'number'. -tests/cases/conformance/jsdoc/0.js(13,7): error TS2451: Cannot redeclare block-scoped variable 'x2'. tests/cases/conformance/jsdoc/0.js(17,1): error TS2322: Type 'number' is not assignable to type 'string'. -tests/cases/conformance/jsdoc/0.js(20,7): error TS2451: Cannot redeclare block-scoped variable 'x2'. tests/cases/conformance/jsdoc/0.js(20,21): error TS2339: Property 'concat' does not exist on type 'number'. +tests/cases/conformance/jsdoc/0.js(24,7): error TS2322: Type '(a: number) => number' is not assignable to type '(arg0: number) => string'. + Type 'number' is not assignable to type 'string'. -==== tests/cases/conformance/jsdoc/0.js (7 errors) ==== +==== tests/cases/conformance/jsdoc/0.js (6 errors) ==== // @ts-check /** @type {String} */ var S = true; @@ -27,8 +27,6 @@ tests/cases/conformance/jsdoc/0.js(20,21): error TS2339: Property 'concat' does /** @type {function (number): number} */ const x2 = (a) => a + 1; - ~~ -!!! error TS2451: Cannot redeclare block-scoped variable 'x2'. /** @type {string} */ var a; @@ -37,9 +35,14 @@ tests/cases/conformance/jsdoc/0.js(20,21): error TS2339: Property 'concat' does !!! error TS2322: Type 'number' is not assignable to type 'string'. /** @type {function (number): number} */ - const x2 = (a) => a.concat("hi"); - ~~ -!!! error TS2451: Cannot redeclare block-scoped variable 'x2'. + const x3 = (a) => a.concat("hi"); ~~~~~~ !!! error TS2339: Property 'concat' does not exist on type 'number'. - x2(0); \ No newline at end of file + x3(0); + + /** @type {function (number): string} */ + const x4 = (a) => a + 1; + ~~ +!!! error TS2322: Type '(a: number) => number' is not assignable to type '(arg0: number) => string'. +!!! error TS2322: Type 'number' is not assignable to type 'string'. + x4(0); \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocTypeTag2.js b/tests/baselines/reference/checkJsdocTypeTag2.js index 23b436e2031..0e550d3ba33 100644 --- a/tests/baselines/reference/checkJsdocTypeTag2.js +++ b/tests/baselines/reference/checkJsdocTypeTag2.js @@ -18,8 +18,12 @@ var a; a = x2(0); /** @type {function (number): number} */ -const x2 = (a) => a.concat("hi"); -x2(0); +const x3 = (a) => a.concat("hi"); +x3(0); + +/** @type {function (number): string} */ +const x4 = (a) => a + 1; +x4(0); //// [0.js] // @ts-check @@ -36,5 +40,8 @@ var x2 = function (a) { return a + 1; }; var a; a = x2(0); /** @type {function (number): number} */ -var x2 = function (a) { return a.concat("hi"); }; -x2(0); +var x3 = function (a) { return a.concat("hi"); }; +x3(0); +/** @type {function (number): string} */ +var x4 = function (a) { return a + 1; }; +x4(0); diff --git a/tests/baselines/reference/contextualTypeFromJSDoc.symbols b/tests/baselines/reference/contextualTypeFromJSDoc.symbols index e2be9362644..5ebe1e01ca7 100644 --- a/tests/baselines/reference/contextualTypeFromJSDoc.symbols +++ b/tests/baselines/reference/contextualTypeFromJSDoc.symbols @@ -11,7 +11,7 @@ const arr = [ ]; -/** @return {function(): Array<[string, {x?:number, y?:number}]>} */ +/** @return {Array<[string, {x?:number, y?:number}]>} */ function f() { >f : Symbol(f, Decl(index.js, 4, 2)) @@ -28,13 +28,13 @@ function f() { class C { >C : Symbol(C, Decl(index.js, 12, 1)) - /** @param {function(): Array<[string, {x?:number, y?:number}]>} value */ + /** @param {Array<[string, {x?:number, y?:number}]>} value */ set x(value) { } ->x : Symbol(C.x, Decl(index.js, 14, 9)) +>x : Symbol(C.x, Decl(index.js, 14, 9), Decl(index.js, 16, 20)) >value : Symbol(value, Decl(index.js, 16, 10)) - get () { ->get : Symbol(C.get, Decl(index.js, 16, 20)) + get x() { +>x : Symbol(C.x, Decl(index.js, 14, 9), Decl(index.js, 16, 20)) return [ ['a', { x: 1 }], diff --git a/tests/baselines/reference/contextualTypeFromJSDoc.types b/tests/baselines/reference/contextualTypeFromJSDoc.types index c1d273c8f70..f1121935d38 100644 --- a/tests/baselines/reference/contextualTypeFromJSDoc.types +++ b/tests/baselines/reference/contextualTypeFromJSDoc.types @@ -20,24 +20,24 @@ const arr = [ ]; -/** @return {function(): Array<[string, {x?:number, y?:number}]>} */ +/** @return {Array<[string, {x?:number, y?:number}]>} */ function f() { ->f : () => () => [string, { x?: number; y?: number; }][] +>f : () => [string, { x?: number; y?: number; }][] return [ ->[ ['a', { x: 1 }], ['b', { y: 2 }] ] : ((string | { [x: string]: any; x: number; })[] | (string | { [x: string]: any; y: number; })[])[] +>[ ['a', { x: 1 }], ['b', { y: 2 }] ] : ([string, { x: number; }] | [string, { y: number; }])[] ['a', { x: 1 }], ->['a', { x: 1 }] : (string | { [x: string]: any; x: number; })[] +>['a', { x: 1 }] : [string, { x: number; }] >'a' : "a" ->{ x: 1 } : { [x: string]: any; x: number; } +>{ x: 1 } : { x: number; } >x : number >1 : 1 ['b', { y: 2 }] ->['b', { y: 2 }] : (string | { [x: string]: any; y: number; })[] +>['b', { y: 2 }] : [string, { y: number; }] >'b' : "b" ->{ y: 2 } : { [x: string]: any; y: number; } +>{ y: 2 } : { y: number; } >y : number >2 : 2 @@ -47,28 +47,28 @@ function f() { class C { >C : C - /** @param {function(): Array<[string, {x?:number, y?:number}]>} value */ + /** @param {Array<[string, {x?:number, y?:number}]>} value */ set x(value) { } ->x : any ->value : () => [string, { x?: number; y?: number; }][] +>x : [string, { x?: number; y?: number; }][] +>value : [string, { x?: number; y?: number; }][] - get () { ->get : () => ((string | { [x: string]: any; x: number; })[] | (string | { [x: string]: any; y: number; })[])[] + get x() { +>x : [string, { x?: number; y?: number; }][] return [ ->[ ['a', { x: 1 }], ['b', { y: 2 }] ] : ((string | { [x: string]: any; x: number; })[] | (string | { [x: string]: any; y: number; })[])[] +>[ ['a', { x: 1 }], ['b', { y: 2 }] ] : ([string, { x: number; }] | [string, { y: number; }])[] ['a', { x: 1 }], ->['a', { x: 1 }] : (string | { [x: string]: any; x: number; })[] +>['a', { x: 1 }] : [string, { x: number; }] >'a' : "a" ->{ x: 1 } : { [x: string]: any; x: number; } +>{ x: 1 } : { x: number; } >x : number >1 : 1 ['b', { y: 2 }] ->['b', { y: 2 }] : (string | { [x: string]: any; y: number; })[] +>['b', { y: 2 }] : [string, { y: number; }] >'b' : "b" ->{ y: 2 } : { [x: string]: any; y: number; } +>{ y: 2 } : { y: number; } >y : number >2 : 2 diff --git a/tests/cases/conformance/jsdoc/checkJsdocTypeTag2.ts b/tests/cases/conformance/jsdoc/checkJsdocTypeTag2.ts index a7dffdb90a7..14c6fffd6d6 100644 --- a/tests/cases/conformance/jsdoc/checkJsdocTypeTag2.ts +++ b/tests/cases/conformance/jsdoc/checkJsdocTypeTag2.ts @@ -21,5 +21,9 @@ var a; a = x2(0); /** @type {function (number): number} */ -const x2 = (a) => a.concat("hi"); -x2(0); \ No newline at end of file +const x3 = (a) => a.concat("hi"); +x3(0); + +/** @type {function (number): string} */ +const x4 = (a) => a + 1; +x4(0); \ No newline at end of file diff --git a/tests/cases/conformance/types/contextualTypes/jsdoc/contextualTypeFromJSDoc.ts b/tests/cases/conformance/types/contextualTypes/jsdoc/contextualTypeFromJSDoc.ts index 89fdd420d4a..c9d7cc54d58 100644 --- a/tests/cases/conformance/types/contextualTypes/jsdoc/contextualTypeFromJSDoc.ts +++ b/tests/cases/conformance/types/contextualTypes/jsdoc/contextualTypeFromJSDoc.ts @@ -10,7 +10,7 @@ const arr = [ ['b', { y: 2 }] ]; -/** @return {function(): Array<[string, {x?:number, y?:number}]>} */ +/** @return {Array<[string, {x?:number, y?:number}]>} */ function f() { return [ ['a', { x: 1 }], @@ -19,9 +19,9 @@ function f() { } class C { - /** @param {function(): Array<[string, {x?:number, y?:number}]>} value */ + /** @param {Array<[string, {x?:number, y?:number}]>} value */ set x(value) { } - get () { + get x() { return [ ['a', { x: 1 }], ['b', { y: 2 }]