Reputation: 155802
I have a JS file with a // @ts-check
directive, using JSDoc comments to denote types.
The problem is that the elements fail type checks when retrieved from the document
.
So we have in HTML:
<input id="myInput">...
When I get this element in JS the type checking throws an error:
// @ts-check
const myInput = document.getElementById('myInput');
myInput.value = 'foobar';
Property 'value' does not exist on type 'HTMLElement'
If I specify the expected type with a JSDoc @type
comment then that also fires an error:
// @ts-check
/** @type {HTMLInputElement} */
const myInput = document.getElementById('myInput');
myInput.value = 'foobar';
Type 'HTMLElement' is not assignable to type 'HTMLInputElement'.
Property 'accept' is missing in type 'HTMLElement'.
If I was in TS, I could use document.getElementById('myInput') as HTMLInputElement
to tell it that I expect this type.
How do I do this in JS with @ts-check
?
Upvotes: 14
Views: 2350
Reputation: 29511
The answer by @Keith is excellent, but it only covers individual Element nodes via:
@type {HTMLInputElement}
We also need to deal with HTMLCollections - which we can do via:
@type {HTMLCollectionOf<Element>}
Faced with a long file of javascript which may contain any number of Element and HTMLCollection captures, we can take advantage of the following two search and replace Regexes:
Find Element captures:
((let|const|var)\s([^\s=]+)\s?=)\s?(([^\.]+)\.(((get|query)[^\(]+\(([^\)]+\))\[\d+\])|(getElementById|querySelector)\(([^\)]+\))))
Replace with:
$1 /** @type {HTMLInputElement} */ ($4)
Then...
Find HTMLCollection captures:
((let|const|var)\s([^\s=]+)\s?=)\s?(document\.(get|query)[^\(]+\(([^\)]+\)))
Replace with:
$1 /** @type {HTMLCollectionOf<Element>} */ ($4)
Upvotes: 0
Reputation: 1168
You can use a runtime check if you want to be absolutely sure, this also persuades typescript that the assignment is safe.
const myInput = document.getElementById('myInput');
if (myInput instanceof HTMLInputElement) {
myInput.value = 'foobar';
}
Upvotes: 2
Reputation: 155802
The fix for this is to put the @type
declaration between the variable and the retrieval, and adding ()
.
Like this:
// @ts-check
const myInput = /** @type {HTMLInputElement} */ (document.getElementById('myInput'));
myInput.value = 'foobar';
This syntax is fairly clunky and horrible, but they've closed the bug so I guess the above syntax is the official way to handle this.
Upvotes: 20