11

I am using Visual Studio Code version 1.17.1.

In *.js file when I type document.querySelector("#elementId").style. I have no IntelliSense hints for styles (like margin, display, etc.). Even no onclick event hint after document.querySelector("#elementId").

enter image description here

enter image description here

I don't use any npm packages. It is just simple html\css\js project.

How to turn on correct IntelliSense hints? Thanks.

Alex
  • 59,571
  • 22
  • 137
  • 126
Stanislav
  • 193
  • 1
  • 8

2 Answers2

9

Because result of the querySelector is either:

Element - the most general base class or null

If you already know id you can use document.getElementById() - which returns instance of more specific class - HTMLElement - autocomplete will work as expected.

document.getElementById('elementId').

If you don't know id, but want autocomplete you can use JSDoc type annotations:

/** @type {HTMLElement} */
var el =  document.querySelector(".myclass");

el.

// or without extra variable:
/** @type {HTMLElement} */
(document.querySelector(".myclass")).

I haven't really tested it but you can try something like that:

/**
 * @type {function(string): HTMLElement}
 */
var querySelector = document.querySelector.bind(document);

querySelector('.myclass').

Another choice would be alter typescript types:

  1. Create file dom.d.ts
  2. Append to it:
interface NodeSelector {
    querySelector<K extends keyof ElementTagNameMap>(selectors: K): ElementTagNameMap[K] | null;
    querySelector<E extends HTMLElement = HTMLElement>(selectors: string): E | null;
    querySelectorAll<K extends keyof ElementListTagNameMap>(selectors: K): ElementListTagNameMap[K];
    querySelectorAll<E extends HTMLElement = HTMLElement>(selectors: string): NodeListOf<E>;
}

Now querySelector returns HTMLElement.

Alex
  • 59,571
  • 22
  • 137
  • 126
  • 1
    Thanks! Can I set up VS Code to implicitly add this JSDoc before "querySelector"? I don't want extra variable. Something like this: /** @type {HTMLElement} */(document.querySelector(".className")).style.display = "none"; – Stanislav Oct 17 '17 at 20:02
  • Interesting, the /** @type {HTMLElement} */ only works with querySelector on an id, not on a class. So some collection of HTMLElements needs to be used instead of just {HTMLElement}? – Mark Oct 17 '17 at 20:31
  • Yes, this works. Actually, I don't really like it. It would be better to force querySelector return HTMLElement than Element. But unfortunatelly it's impossible. But thanks. – Stanislav Oct 17 '17 at 20:32
  • Do I need to compile this typescript? Just adding this TS to root folder does not help. Now I am writing on vanilla javascript. – Stanislav Oct 17 '17 at 20:40
  • Not sure, I have installed and configured typescript in folder. Does adding comment // @ts-check on top of the js file or reloading the editor help? – Alex Oct 17 '17 at 20:44
  • Hmm, cool. Reloading the editor helps. Now querySelector returns HTMLElement. Nice solution! Do you know about some typescript types collections (libraries) just to add them to project folder and to force all similar methods return more specific types? (not only querySelector, there are getElementsByClassName and others). – Stanislav Oct 17 '17 at 20:49
  • No. I don't know any. You can create your own though: [lib.dom.ts](https://github.com/Microsoft/TypeScript/blob/master/lib/lib.dom.d.ts). Like I just did. – Alex Oct 17 '17 at 21:05
  • Alex : See my comment above. Your first solution /** @type {HTMLElement} */ doesn't work when querySelector'ing a class, only an id. Obviously they are returning different things. When I added your dom.d.ts then querySelector'ing a class does work. So you may want to edit your answer since it uses a class example. Otherwise this is an extremely good answer. +1 – Mark Oct 17 '17 at 22:11
  • @Mark, I think you are doing something wrong. `querySelector` returns **1** `Element`. It can correctly guess tag name and make autocomplete better with it, but it doesn't distinct class and id. Maybe you mistake it with `querySelectorAll`? For that method you definitely need another type: `/** @type {HTMLElement[]} */` – Alex Oct 17 '17 at 23:26
  • Yes, you are right on the return of a single Element. But I've tested it a bunch of times now and it just doesn't work with querySelector(".someClass") and the type notation. type and querySelector("#someID") does work. I hate to say it is a VSCode bug but .... As I said both work with your dom.d.ts addition. – Mark Oct 18 '17 at 01:25
1

The other answer points to the correct answer – type casting with jsdoc – but I've found that this only works consistently when you do it exactly as typescript wants when dealing with union types: you need to wrap the casted expression in parentheses and place the type cast doc on the same line. The snippet from a permalink to the wiki:

// We can "cast" types to other types using a JSDoc type assertion
// by adding an `@type` tag around any parenthesized expression.

/**
 * @type {number | string}
 */
var numberOrString = Math.random() < 0.5 ? "hello" : 100;
var typeAssertedNumber = /** @type {number} */ (numberOrString)
Ben Creasy
  • 3,825
  • 4
  • 40
  • 50