Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4164,7 +4164,7 @@ namespace ts {
}

const isOptional = includeOptionality && (
isInJavaScriptFile(declaration) && isParameter(declaration) && getJSDocParameterTags(declaration).some(tag => tag.isBracketed)
isParameter(declaration) && isJSDocOptionalParameter(declaration)
|| !isBindingElement(declaration) && !isVariableDeclaration(declaration) && !!declaration.questionToken);

// Use type from type annotation if one is present
Expand Down Expand Up @@ -6571,9 +6571,17 @@ namespace ts {

function isJSDocOptionalParameter(node: ParameterDeclaration) {
return isInJavaScriptFile(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));
isBracketed || !!typeExpression && skipJSDocPrefixTypes(typeExpression.type).kind === SyntaxKind.JSDocOptionalType));
}

function skipJSDocPrefixTypes(type: TypeNode): TypeNode {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In ?!a=, maybe this should be parsed as (?!a)= and this wouldn't be necessary?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered that and will look at that next. The reason is that, for ?T= the parser does

  1. parsePostfix
  2. parseNonArrayType
  3. parseJSDocNullableType
  4. parseType
  5. ...
  6. parsePostfix

parsePostfix at step (1) doesn't parse the trailing = in this case like we would want, the one at step (6) does. This might be fixed by changing step (4) to be parseNonArrayType. I'll try that and see what breaks.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, parseJSDocNodeWithType(SyntaxKind.JSDocNonNullable) does this already, so it's probably a mistake that parseJSDocNullableType doesn't.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks the existing parsing test in a way that I think is good (... is no longer allowed in the middle of a type), but I want to test more and maybe revise the parsing of ... too. I'll do that in a separate PR.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it also changes how tightly ?T binds -- if it's tighter than =, then it's tighter than [] too. So ?T[] is now (T | null)[] where before it was T[] | null.

while (type.kind === SyntaxKind.JSDocNullableType || type.kind === SyntaxKind.JSDocNonNullableType) {
type = (type as JSDocNullableType | JSDocNonNullableType).type;
}
return type;
}

function tryFindAmbientModule(moduleName: string, withAugmentations: boolean) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
tests/cases/conformance/jsdoc/a.js(4,5): error TS2322: Type 'null' is not assignable to type 'number | undefined'.
tests/cases/conformance/jsdoc/a.js(8,3): error TS2345: Argument of type 'null' is not assignable to parameter of type 'number | undefined'.


==== tests/cases/conformance/jsdoc/a.js (2 errors) ====
/** @param {number=} a */
function f(a) {
a = 1
a = null // should not be allowed
~
!!! error TS2322: Type 'null' is not assignable to type 'number | undefined'.
a = undefined
}
f()
f(null) // should not be allowed
~~~~
!!! error TS2345: Argument of type 'null' is not assignable to parameter of type 'number | undefined'.
f(undefined)
f(1)

/** @param {???!?number?=} a */
function g(a) {
a = 1
a = null
a = undefined
}
g()
g(null)
g(undefined)
g(1)

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
=== tests/cases/conformance/jsdoc/a.js ===
/** @param {number=} a */
function f(a) {
>f : Symbol(f, Decl(a.js, 0, 0))
>a : Symbol(a, Decl(a.js, 1, 11))

a = 1
>a : Symbol(a, Decl(a.js, 1, 11))

a = null // should not be allowed
>a : Symbol(a, Decl(a.js, 1, 11))

a = undefined
>a : Symbol(a, Decl(a.js, 1, 11))
>undefined : Symbol(undefined)
}
f()
>f : Symbol(f, Decl(a.js, 0, 0))

f(null) // should not be allowed
>f : Symbol(f, Decl(a.js, 0, 0))

f(undefined)
>f : Symbol(f, Decl(a.js, 0, 0))
>undefined : Symbol(undefined)

f(1)
>f : Symbol(f, Decl(a.js, 0, 0))

/** @param {???!?number?=} a */
function g(a) {
>g : Symbol(g, Decl(a.js, 9, 4))
>a : Symbol(a, Decl(a.js, 12, 11))

a = 1
>a : Symbol(a, Decl(a.js, 12, 11))

a = null
>a : Symbol(a, Decl(a.js, 12, 11))

a = undefined
>a : Symbol(a, Decl(a.js, 12, 11))
>undefined : Symbol(undefined)
}
g()
>g : Symbol(g, Decl(a.js, 9, 4))

g(null)
>g : Symbol(g, Decl(a.js, 9, 4))

g(undefined)
>g : Symbol(g, Decl(a.js, 9, 4))
>undefined : Symbol(undefined)

g(1)
>g : Symbol(g, Decl(a.js, 9, 4))

79 changes: 79 additions & 0 deletions tests/baselines/reference/jsdocPostfixEqualsAddsOptionality.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
=== tests/cases/conformance/jsdoc/a.js ===
/** @param {number=} a */
function f(a) {
>f : (a?: number | undefined) => void
>a : number | undefined

a = 1
>a = 1 : 1
>a : number | undefined
>1 : 1

a = null // should not be allowed
>a = null : null
>a : number | undefined
>null : null

a = undefined
>a = undefined : undefined
>a : number | undefined
>undefined : undefined
}
f()
>f() : void
>f : (a?: number | undefined) => void

f(null) // should not be allowed
>f(null) : void
>f : (a?: number | undefined) => void
>null : null

f(undefined)
>f(undefined) : void
>f : (a?: number | undefined) => void
>undefined : undefined

f(1)
>f(1) : void
>f : (a?: number | undefined) => void
>1 : 1

/** @param {???!?number?=} a */
function g(a) {
>g : (a?: number | null | undefined) => void
>a : number | null | undefined

a = 1
>a = 1 : 1
>a : number | null | undefined
>1 : 1

a = null
>a = null : null
>a : number | null | undefined
>null : null

a = undefined
>a = undefined : undefined
>a : number | null | undefined
>undefined : undefined
}
g()
>g() : void
>g : (a?: number | null | undefined) => void

g(null)
>g(null) : void
>g : (a?: number | null | undefined) => void
>null : null

g(undefined)
>g(undefined) : void
>g : (a?: number | null | undefined) => void
>undefined : undefined

g(1)
>g(1) : void
>g : (a?: number | null | undefined) => void
>1 : 1

Loading