Brackets and postfix= in @param add undefined (#22514)

* Brackets and postfix= in `@param` add undefined

Previously they only added optionality.
Note that, unlike Typescript, when a parameter initializer is specified
in jsdoc, it does not remove undefined in the *body* of the function.
That's because TS will generate initialisation code, but JS won't, so
the author will have to manually write code to remove undefined from the
type.

```js
/** @param {number} [a=101] */
function f(a) {
  // a: number | undefined here
  if (!a) {
    a = 101
  }
  // a: number here
}
```

Note that we don't check that
1. the initializer value is actually assigned to the parameter.
2. the initializer's type matches the declared type of the parameter.

Pretty much we just parse it and leave it alone.

* Address PR comments
This commit is contained in:
Nathan Shively-Sanders
2018-03-13 15:56:38 -07:00
committed by GitHub
parent 23a64fe804
commit 0fa838a3ef
5 changed files with 133 additions and 16 deletions

View File

@@ -4167,7 +4167,17 @@ namespace ts {
return getTypeForBindingElement(<BindingElement>declaration);
}
const isOptional = !isBindingElement(declaration) && !isVariableDeclaration(declaration) && !!declaration.questionToken && includeOptionality;
let isOptional = false;
if (includeOptionality) {
if (isInJavaScriptFile(declaration) && isParameter(declaration)) {
const parameterTags = getJSDocParameterTags(declaration);
isOptional = !!(parameterTags && parameterTags.length > 0 && find(parameterTags, tag => tag.isBracketed));
}
if (!isBindingElement(declaration) && !isVariableDeclaration(declaration) && !!declaration.questionToken) {
isOptional = true;
}
}
// Use type from type annotation if one is present
const declaredType = tryGetTypeFromEffectiveTypeNode(declaration);
if (declaredType) {
@@ -8670,9 +8680,10 @@ namespace ts {
return getTypeFromIntersectionTypeNode(<IntersectionTypeNode>node);
case SyntaxKind.JSDocNullableType:
return getTypeFromJSDocNullableTypeNode(<JSDocNullableType>node);
case SyntaxKind.JSDocOptionalType:
return addOptionality(getTypeFromTypeNode((node as JSDocOptionalType).type));
case SyntaxKind.ParenthesizedType:
case SyntaxKind.JSDocNonNullableType:
case SyntaxKind.JSDocOptionalType:
case SyntaxKind.JSDocTypeExpression:
return getTypeFromTypeNode((<ParenthesizedTypeNode | JSDocTypeReferencingNode | JSDocTypeExpression>node).type);
case SyntaxKind.JSDocVariadicType:

View File

@@ -23,18 +23,18 @@ normal(12);
* @param {string} [opts1.w="hi"] doc5
*/
function foo1(opts1) {
>foo1 : (opts1: { x: string; y?: string; z?: string; w?: string; }) => void
>opts1 : { x: string; y?: string; z?: string; w?: string; }
>foo1 : (opts1: { x: string; y?: string | undefined; z?: string; w?: string; }) => void
>opts1 : { x: string; y?: string | undefined; z?: string; w?: string; }
opts1.x;
>opts1.x : string
>opts1 : { x: string; y?: string; z?: string; w?: string; }
>opts1 : { x: string; y?: string | undefined; z?: string; w?: string; }
>x : string
}
foo1({x: 'abc'});
>foo1({x: 'abc'}) : void
>foo1 : (opts1: { x: string; y?: string; z?: string; w?: string; }) => void
>foo1 : (opts1: { x: string; y?: string | undefined; z?: string; w?: string; }) => void
>{x: 'abc'} : { x: string; }
>x : string
>'abc' : "abc"
@@ -45,20 +45,20 @@ foo1({x: 'abc'});
* @param {string=} opts2[].anotherY
*/
function foo2(/** @param opts2 bad idea theatre! */opts2) {
>foo2 : (opts2: { anotherX: string; anotherY?: string; }[]) => void
>opts2 : { anotherX: string; anotherY?: string; }[]
>foo2 : (opts2: { anotherX: string; anotherY?: string | undefined; }[]) => void
>opts2 : { anotherX: string; anotherY?: string | undefined; }[]
opts2[0].anotherX;
>opts2[0].anotherX : string
>opts2[0] : { anotherX: string; anotherY?: string; }
>opts2 : { anotherX: string; anotherY?: string; }[]
>opts2[0] : { anotherX: string; anotherY?: string | undefined; }
>opts2 : { anotherX: string; anotherY?: string | undefined; }[]
>0 : 0
>anotherX : string
}
foo2([{anotherX: "world"}]);
>foo2([{anotherX: "world"}]) : void
>foo2 : (opts2: { anotherX: string; anotherY?: string; }[]) => void
>foo2 : (opts2: { anotherX: string; anotherY?: string | undefined; }[]) => void
>[{anotherX: "world"}] : { anotherX: string; }[]
>{anotherX: "world"} : { anotherX: string; }
>anotherX : string
@@ -92,20 +92,20 @@ foo3({x: 'abc'});
* @param {string} [opts4[].w="hi"]
*/
function foo4(opts4) {
>foo4 : (opts4: { x: string; y?: string; z?: string; w?: string; }[]) => void
>opts4 : { x: string; y?: string; z?: string; w?: string; }[]
>foo4 : (opts4: { x: string; y?: string | undefined; z?: string; w?: string; }[]) => void
>opts4 : { x: string; y?: string | undefined; z?: string; w?: string; }[]
opts4[0].x;
>opts4[0].x : string
>opts4[0] : { x: string; y?: string; z?: string; w?: string; }
>opts4 : { x: string; y?: string; z?: string; w?: string; }[]
>opts4[0] : { x: string; y?: string | undefined; z?: string; w?: string; }
>opts4 : { x: string; y?: string | undefined; z?: string; w?: string; }[]
>0 : 0
>x : string
}
foo4([{ x: 'hi' }]);
>foo4([{ x: 'hi' }]) : void
>foo4 : (opts4: { x: string; y?: string; z?: string; w?: string; }[]) => void
>foo4 : (opts4: { x: string; y?: string | undefined; z?: string; w?: string; }[]) => void
>[{ x: 'hi' }] : { x: string; }[]
>{ x: 'hi' } : { x: string; }
>x : string

View File

@@ -0,0 +1,38 @@
=== tests/cases/conformance/jsdoc/a.js ===
/**
* @param {number} [p]
* @param {number=} q
* @param {number} [r=101]
*/
function f(p, q, r) {
>f : Symbol(f, Decl(a.js, 0, 0))
>p : Symbol(p, Decl(a.js, 5, 11))
>q : Symbol(q, Decl(a.js, 5, 13))
>r : Symbol(r, Decl(a.js, 5, 16))
p = undefined
>p : Symbol(p, Decl(a.js, 5, 11))
>undefined : Symbol(undefined)
q = undefined
>q : Symbol(q, Decl(a.js, 5, 13))
>undefined : Symbol(undefined)
// note that, unlike TS, JSDOC [r=101] retains | undefined because
// there's no code emitted to get rid of it.
r = undefined
>r : Symbol(r, Decl(a.js, 5, 16))
>undefined : Symbol(undefined)
}
f()
>f : Symbol(f, Decl(a.js, 0, 0))
f(undefined, undefined, undefined)
>f : Symbol(f, Decl(a.js, 0, 0))
>undefined : Symbol(undefined)
>undefined : Symbol(undefined)
>undefined : Symbol(undefined)
f(1, 2, 3)
>f : Symbol(f, Decl(a.js, 0, 0))

View File

@@ -0,0 +1,47 @@
=== tests/cases/conformance/jsdoc/a.js ===
/**
* @param {number} [p]
* @param {number=} q
* @param {number} [r=101]
*/
function f(p, q, r) {
>f : (p?: number | undefined, q?: number | undefined, r?: number | undefined) => void
>p : number | undefined
>q : number | undefined
>r : number | undefined
p = undefined
>p = undefined : undefined
>p : number | undefined
>undefined : undefined
q = undefined
>q = undefined : undefined
>q : number | undefined
>undefined : undefined
// note that, unlike TS, JSDOC [r=101] retains | undefined because
// there's no code emitted to get rid of it.
r = undefined
>r = undefined : undefined
>r : number | undefined
>undefined : undefined
}
f()
>f() : void
>f : (p?: number | undefined, q?: number | undefined, r?: number | undefined) => void
f(undefined, undefined, undefined)
>f(undefined, undefined, undefined) : void
>f : (p?: number | undefined, q?: number | undefined, r?: number | undefined) => void
>undefined : undefined
>undefined : undefined
>undefined : undefined
f(1, 2, 3)
>f(1, 2, 3) : void
>f : (p?: number | undefined, q?: number | undefined, r?: number | undefined) => void
>1 : 1
>2 : 2
>3 : 3

View File

@@ -0,0 +1,21 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @strict: true
// @Filename: a.js
/**
* @param {number} [p]
* @param {number=} q
* @param {number} [r=101]
*/
function f(p, q, r) {
p = undefined
q = undefined
// note that, unlike TS, JSDOC [r=101] retains | undefined because
// there's no code emitted to get rid of it.
r = undefined
}
f()
f(undefined, undefined, undefined)
f(1, 2, 3)