fix(49080): --exactOptionalPropertyTypes seems to be ignored for JSDoc types (#49910)

* fix(49080): handle JSDocPropertyTag as optional prop

* move condition to utility function isOptionalDeclaration

* add additional tests

* update baseline
This commit is contained in:
Oleksandr T 2022-12-13 01:03:49 +02:00 committed by GitHub
parent 355991c806
commit 708a522ddf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 197 additions and 13 deletions

View File

@ -535,9 +535,11 @@ import {
isJSDocNode,
isJSDocNonNullableType,
isJSDocNullableType,
isJSDocOptionalParameter,
isJSDocOptionalType,
isJSDocParameterTag,
isJSDocPropertyLikeTag,
isJSDocPropertyTag,
isJSDocReturnTag,
isJSDocSignature,
isJSDocTemplateTag,
@ -599,6 +601,7 @@ import {
isOmittedExpression,
isOptionalChain,
isOptionalChainRoot,
isOptionalDeclaration,
isOptionalJSDocPropertyLikeTag,
isOptionalTypeNode,
isOutermostOptionalChain,
@ -10164,11 +10167,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return getTypeForBindingElement(declaration as BindingElement);
}
const isProperty = isPropertyDeclaration(declaration) && !hasAccessorModifier(declaration) || isPropertySignature(declaration);
const isOptional = includeOptionality && (
isProperty && !!declaration.questionToken ||
isParameter(declaration) && (!!declaration.questionToken || isJSDocOptionalParameter(declaration)) ||
isOptionalJSDocPropertyLikeTag(declaration));
const isProperty = (isPropertyDeclaration(declaration) && !hasAccessorModifier(declaration)) || isPropertySignature(declaration) || isJSDocPropertyTag(declaration);
const isOptional = includeOptionality && isOptionalDeclaration(declaration);
// Use type from type annotation if one is present
const declaredType = tryGetTypeFromEffectiveTypeNode(declaration);
@ -13991,14 +13991,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return result;
}
function isJSDocOptionalParameter(node: ParameterDeclaration) {
return isInJSFile(node) && (
// node.type should only be a JSDocOptionalType when node is a parameter of a JSDocFunctionType
node.type && node.type.kind === SyntaxKind.JSDocOptionalType
|| getJSDocParameterTags(node).some(({ isBracketed, typeExpression }) =>
isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType));
}
function tryFindAmbientModule(moduleName: string, withAugmentations: boolean) {
if (isExternalModuleNameRelative(moduleName)) {
return undefined;

View File

@ -414,6 +414,7 @@ import {
PropertyDeclaration,
PropertyName,
PropertyNameLiteral,
PropertySignature,
PseudoBigInt,
QualifiedName,
ReadonlyCollection,
@ -9139,3 +9140,26 @@ export function canUsePropertyAccess(name: string, languageVersion: ScriptTarget
export function hasTabstop(node: Node): boolean {
return getSnippetElement(node)?.kind === SnippetKind.TabStop;
}
export function isJSDocOptionalParameter(node: ParameterDeclaration) {
return isInJSFile(node) && (
// node.type should only be a JSDocOptionalType when node is a parameter of a JSDocFunctionType
node.type && node.type.kind === SyntaxKind.JSDocOptionalType
|| getJSDocParameterTags(node).some(({ isBracketed, typeExpression }) =>
isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType));
}
export function isOptionalDeclaration(declaration: Declaration): boolean {
switch (declaration.kind) {
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
return !!(declaration as PropertyDeclaration | PropertySignature).questionToken;
case SyntaxKind.Parameter:
return !!(declaration as ParameterDeclaration).questionToken || isJSDocOptionalParameter(declaration as ParameterDeclaration);
case SyntaxKind.JSDocPropertyTag:
case SyntaxKind.JSDocParameterTag:
return isOptionalJSDocPropertyLikeTag(declaration);
default:
return false;
}
}

View File

@ -8766,6 +8766,8 @@ declare namespace ts {
parent: ConstructorDeclaration;
name: Identifier;
};
function isJSDocOptionalParameter(node: ParameterDeclaration): boolean;
function isOptionalDeclaration(declaration: Declaration): boolean;
function createUnparsedSourceFile(text: string): UnparsedSource;
function createUnparsedSourceFile(inputFile: InputFiles, type: "js" | "dts", stripInternal?: boolean): UnparsedSource;
function createUnparsedSourceFile(text: string, mapPath: string | undefined, map: string | undefined): UnparsedSource;

View File

@ -4832,6 +4832,8 @@ declare namespace ts {
parent: ConstructorDeclaration;
name: Identifier;
};
function isJSDocOptionalParameter(node: ParameterDeclaration): boolean;
function isOptionalDeclaration(declaration: Declaration): boolean;
function createUnparsedSourceFile(text: string): UnparsedSource;
function createUnparsedSourceFile(inputFile: InputFiles, type: "js" | "dts", stripInternal?: boolean): UnparsedSource;
function createUnparsedSourceFile(text: string, mapPath: string | undefined, map: string | undefined): UnparsedSource;

View File

@ -0,0 +1,32 @@
tests/cases/compiler/a.js(7,7): error TS2375: Type '{ value: undefined; }' is not assignable to type 'A' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
Types of property 'value' are incompatible.
Type 'undefined' is not assignable to type 'number'.
tests/cases/compiler/a.js(14,7): error TS2375: Type '{ value: undefined; }' is not assignable to type 'B' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
Types of property 'value' are incompatible.
Type 'undefined' is not assignable to type 'number'.
==== tests/cases/compiler/a.js (2 errors) ====
/**
* @typedef {object} A
* @property {number} [value]
*/
/** @type {A} */
const a = { value: undefined }; // error
~
!!! error TS2375: Type '{ value: undefined; }' is not assignable to type 'A' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
!!! error TS2375: Types of property 'value' are incompatible.
!!! error TS2375: Type 'undefined' is not assignable to type 'number'.
/**
* @typedef {{ value?: number }} B
*/
/** @type {B} */
const b = { value: undefined }; // error
~
!!! error TS2375: Type '{ value: undefined; }' is not assignable to type 'B' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
!!! error TS2375: Types of property 'value' are incompatible.
!!! error TS2375: Type 'undefined' is not assignable to type 'number'.

View File

@ -0,0 +1,22 @@
=== tests/cases/compiler/a.js ===
/**
* @typedef {object} A
* @property {number} [value]
*/
/** @type {A} */
const a = { value: undefined }; // error
>a : Symbol(a, Decl(a.js, 6, 5))
>value : Symbol(value, Decl(a.js, 6, 11))
>undefined : Symbol(undefined)
/**
* @typedef {{ value?: number }} B
*/
/** @type {B} */
const b = { value: undefined }; // error
>b : Symbol(b, Decl(a.js, 13, 5))
>value : Symbol(value, Decl(a.js, 13, 11))
>undefined : Symbol(undefined)

View File

@ -0,0 +1,24 @@
=== tests/cases/compiler/a.js ===
/**
* @typedef {object} A
* @property {number} [value]
*/
/** @type {A} */
const a = { value: undefined }; // error
>a : A
>{ value: undefined } : { value: undefined; }
>value : undefined
>undefined : undefined
/**
* @typedef {{ value?: number }} B
*/
/** @type {B} */
const b = { value: undefined }; // error
>b : B
>{ value: undefined } : { value: undefined; }
>value : undefined
>undefined : undefined

View File

@ -0,0 +1,22 @@
=== tests/cases/compiler/a.js ===
/**
* @typedef Foo
* @property {number} [foo]
*/
const x = /** @type {Foo} */ ({});
>x : Symbol(x, Decl(a.js, 5, 5))
x.foo; // number | undefined
>x.foo : Symbol(foo, Decl(a.js, 2, 3))
>x : Symbol(x, Decl(a.js, 5, 5))
>foo : Symbol(foo, Decl(a.js, 2, 3))
const y = /** @type {Required<Foo>} */ ({});
>y : Symbol(y, Decl(a.js, 8, 5))
y.foo; // number
>y.foo : Symbol(foo, Decl(a.js, 2, 3))
>y : Symbol(y, Decl(a.js, 8, 5))
>foo : Symbol(foo, Decl(a.js, 2, 3))

View File

@ -0,0 +1,26 @@
=== tests/cases/compiler/a.js ===
/**
* @typedef Foo
* @property {number} [foo]
*/
const x = /** @type {Foo} */ ({});
>x : Foo
>({}) : Foo
>{} : {}
x.foo; // number | undefined
>x.foo : number | undefined
>x : Foo
>foo : number | undefined
const y = /** @type {Required<Foo>} */ ({});
>y : Required<Foo>
>({}) : Required<Foo>
>{} : {}
y.foo; // number
>y.foo : number
>y : Required<Foo>
>foo : number

View File

@ -0,0 +1,21 @@
// @strictNullChecks: true
// @exactOptionalPropertyTypes: true
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @filename: a.js
/**
* @typedef {object} A
* @property {number} [value]
*/
/** @type {A} */
const a = { value: undefined }; // error
/**
* @typedef {{ value?: number }} B
*/
/** @type {B} */
const b = { value: undefined }; // error

View File

@ -0,0 +1,17 @@
// @strictNullChecks: true
// @exactOptionalPropertyTypes: true
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @filename: a.js
/**
* @typedef Foo
* @property {number} [foo]
*/
const x = /** @type {Foo} */ ({});
x.foo; // number | undefined
const y = /** @type {Required<Foo>} */ ({});
y.foo; // number