microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.08k stars 12.37k forks source link

Add native support for twoslash comment queries #52839

Closed orta closed 1 year ago

orta commented 1 year ago

Suggestion

const hi = "Hi"
const msg = `${hi} world` as const
//    ^?

Should add an inline recommendation to editors what the type is at msg.

I think there are a significant amount of new TypeScript users who are learning the language through video tools/tutorials. Probably much more than we expect. Lots of the folks teaching use this syntax via a vscode extension I wrote a few years back.

I initially punted on discussing internally about upstreaming it into TSServer, but I've slowly turned around on the idea as it feels like the teaching community is changing to be more video focused. So, I think it's worth the complexity, at least to bring it up.

🔍 Search Terms

inline comments show types messages print types twoslash

✅ Viability Checklist

My suggestion meets these guidelines:

⭐ Suggestion

TSServer already has inlay hint support for other features, so it would be expanding support, perhaps that means at parse/bind time checking comments for // ^? and caching their locations, then printing the results when an editor asks for hints in a particular range.

The current code simply uses the same kind of regex as the TSC tests ```ts export function activate(context: vscode.ExtensionContext) { const provider: vscode.InlayHintsProvider = { provideInlayHints: async (model, iRange, cancel) => { const offset = model.offsetAt(iRange.start); const text = model.getText(iRange); const results: vscode.InlayHint[] = []; const m = text.matchAll(/^\s*\/\/\s*\^\?/gm); for (const match of m) { if (match.index === undefined) { return; } const end = match.index + match[0].length - 1; // Add the start range for the inlay hint const endPos = model.positionAt(end + offset); const inspectionPos = new vscode.Position( endPos.line - 1, endPos.character ); if (cancel.isCancellationRequested) { return []; } const { scheme, fsPath, authority, path } = model.uri; const hint: any = await vscode.commands.executeCommand( "typescript.tsserverRequest", "quickinfo", { _: "%%%", file: scheme === 'file' ? fsPath : `^/${scheme}/${authority || 'ts-nul-authority'}/${path.replace(/^\//, '')}`, line: inspectionPos.line + 1, offset: inspectionPos.character, } ); if (!hint || !hint.body) { continue; } // Make a one-liner let text = hint.body.displayString .replace(/\\n/g, " ") .replace(/\/n/g, " ") .replace(/ /g, " ") .replace(/[\u0000-\u001F\u007F-\u009F]/g, ""); if (text.length > 120) { text = text.slice(0, 119) + "..."; } const inlay: vscode.InlayHint = { kind: 0, position: new vscode.Position(endPos.line, endPos.character + 1), label: text, paddingLeft: true, }; results.push(inlay); } return results; }, }; ```

📃 Motivating Example

I think there are 2 main uses for this, and I've framed the initial part of this issue as being about education because that's what changed my opinion. The other part is that it is genuinely useful to pull out types while you are working on a more complex type, in which case twoslash comments act like a way of seeing your work in progress on complex types.

For example when I built a "wordle" in the type system, each type I created I would create an example of it in use and print it in order to see how types flow through the system. You can see a few left in here: https://t.co/OmXoSdi1Y1

Downsides

There are a few interesting downsides to the syntax, which I think are fine but at east worth highlighting:

Extensions

We could also add support for drilling into the types:

type Person = {
  name: string;
  address: {
    line1: string;
    country: string;
  }
}

const person = bob;
//     ^?['address']  { line1: string; country: string }
Ciantic commented 1 year ago

I just found out that VSCode TypeScript supports inline type hints, the gray types there are not written by user but elicited by the IDE

image

It doesn't work for const for some reason but it does for let... It of course works if you use only VSCode during your video lectures. Rust is a language that is unusable for me without inlay hints, TypeScript seems to benefit from those too, it might be hard to get used to it at first because they might add noise you don't want.

All TypeScript and Deno settings.json for inlay hints

{
  // For TypeScript
  "typescript.inlayHints.variableTypes.enabled": true,
  "typescript.inlayHints.parameterNames.enabled": "all",
  "typescript.inlayHints.functionLikeReturnTypes.enabled": true,
  "typescript.inlayHints.parameterTypes.enabled": true,
  "typescript.inlayHints.propertyDeclarationTypes.enabled": true,
  "typescript.inlayHints.enumMemberValues.enabled": true,
  "typescript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": false,
  "typescript.inlayHints.variableTypes.suppressWhenTypeMatchesName": false,

  // Color settings
  "workbench.colorCustomizations": {
    "editorInlayHint.foreground": "#7e7e7e",
    "editorInlayHint.background": "#ff000000"
  },

  // For deno
  "deno.inlayHints.variableTypes.enabled": true,
  "deno.inlayHints.parameterNames.enabled": "all",
  "deno.inlayHints.functionLikeReturnTypes.enabled": true,
  "deno.inlayHints.parameterTypes.enabled": true,
  "deno.inlayHints.propertyDeclarationTypes.enabled": true,
  "deno.inlayHints.enumMemberValues.enabled": true,
  "deno.inlayHints.parameterNames.suppressWhenArgumentMatchesName": false,
  "deno.inlayHints.variableTypes.suppressWhenTypeMatchesName": false,
}
fatcerberus commented 1 year ago

It doesn't work for const for some reason

If you write e.g. const foo = "bar" then the type is "bar" so the inlay hint isn’t shown since it would be redundant. I think the same is true for new Class() because there again, the type is obvious. Outside of that you should still get inlay hints for const.

fatcerberus commented 1 year ago

the teaching community is changing to be more video focused

I’m probably in the minority on this but I always felt like video was the wrong medium for teaching programming, because there’s very little temporal component to it. Sure, it takes time to enter code, but that’s entirely mechanical; if I could beam all the code I need into the computer at once with just my thoughts, I would. Thus why AI tools like GitHub Copilot are successful.

Video coding tutorials make me think of those old boring YouTube “tutorials” where it’s just someone typing stuff into Notepad for 10 minutes - just give me the text file to read myself! 😅

Then again, I’ve always learned better from API references than coding tutorials (which tend to just make me impatient), so yeah.

orta commented 1 year ago

Yep, the inlay hints feature in editors is what this feature on the playground and the vscode extension linked use, the current support doesn't handle the types of sorts of introspection I was showcasing though (and for me are mostly noise).

Hah, everyone trying to drag my proposal off-topic! I also prefer text, but the availability + accessibility of youtube (and maybe tiktok? though I struggle to imagine it's a great learning tool for depth) have changed what its like at the beginning of expertise even if it's not really doing much for the intermediate to experts for a topic!

fatcerberus commented 1 year ago

Hah, everyone trying to drag my proposal off-topic!

Yep, sorry about that. 😅

rauschma commented 1 year ago

I just used the Playground and it’s neat that the ^? syntax already works there.

[Edit: slightly off-topic, apologies] For written content (printed books, blog posts, etc.), I’d love to additionally be able to state the inferred type and have a tool check that it’s correct – e.g.:

const str = "Hello" as const;
//     ^? const str: "Hello"

Alternative 1 – shorter:

const str = "Hello" as const;
//     ^? "Hello"

Alternative 2 – prefix:

// @inferred-type: "Hello"
const str = "Hello" as const;
orta commented 1 year ago

There is support for that as an eslint plugin, https://github.com/JoshuaKGoldberg/eslint-plugin-expect-type/pull/47

RyanCavanaugh commented 1 year ago

This would be a VS Code or other editor thing, since there's (AFAIK) nothing TS Server could tell the editor to do here that would make this happen automatically.

orta commented 1 year ago

VS Code would need a minor change to always ask for inlay hints, but I'd expect the core of the support to live in provideInlayHints in TSServer which is what does all the heavy lifting for this in here

DanielRosenwasser commented 1 year ago

So we chatted about this in our editor sync. We all love and use query comments (// ^?) in the playground, and some of us have it installed in VS Code itself.

Here's some of what came out of the conversation - some of it just describing our own next steps, and some of it feedback that might or might not be interesting/useful

  1. A user has to know to enable inlay hints in the first place to get this - and we likely wouldn't turn query comments on by default, even if inlay hints was turned on. Of course, going through settings to turn this on is a way to discover this feature, but we're not sure if it's the right gateway yet.
  2. The extension is a good proofing ground for whether this should be part of TypeScript direclty - over time, if we really saw broader usage, we could reconsider this. This is often how VS Code's core feature set is developed, with extensions paving the way.
  3. We think the discoverability of the extension could be improved if the name reflected its specific feature. If you know what twoslash is, then you might have an idea of what it does - but query comments are really their own thing! Ultimately it's your call, but I really think it would help if people could more-easily put a name to this feature.
  4. One really cool idea we had is whether it could make sense for the extension to work on other languages. There's no reason query comments have to be specific to TypeScript and JavaScript. Is there a world where this extension could operate on multiple languages?

    VS Code already provides a command for requesting hover (see an example here). An extension could just leverage that instead of talking directly to TSServer.

    Unfortunately there's public no mechanism for asking for the current token scope to see whether a position falls in a comment. I'm pretty sure _workbench.captureSyntaxTokens is not meant to be public. But I believe the playground doesn't really care if something really falls in a comment anyway:

    image

    The comment pattern could be special-cased on a per-language basis - but so many languages use // that they'd all benefit from just that.

So the TL;DR? No for now - but we're happy to revisit in the future.

We do think that there's a really cool opportunity for the extension to generalize to more languages if it's something you'd be interested in.

jasikpark commented 1 year ago

++ to improving the extension discoverability && adding additional language support -- a simple step could be TS-ish languages like Astro components :)

comment queries for rust-analyzer when 👀

jakebailey commented 1 year ago

comment queries for rust-analyzer when 👀

With the linked code:

return vscode.commands.executeCommand<vscode.Hover[]>(
    'vscode.executeHoverProvider',
    editor.document.uri,
    editor.selection.active);

The extension generalizes to any language which has a hover provider. The trick is just finding the comments.

jasikpark commented 1 year ago

comment queries for rust-analyzer when 👀

With the linked code:

return vscode.commands.executeCommand<vscode.Hover[]>(
  'vscode.executeHoverProvider',
  editor.document.uri,
  editor.selection.active);

The extension generalizes to any language which has a hover provider. The trick is just finding the comments.

Is there an LSP version of this that generalizes to use in other editors like Neovim, etc? Something that @Princesseuh was thinking of as an approach.

jakebailey commented 1 year ago

Every editor has its own way of hooking into LSP, so, every editor will have (or not have) a way to call the language servers its interacting with. I can't really offer any recommendations to that end.

jasikpark commented 1 year ago

err I'm thinking of it more from the "if I, the LSP, see a comment with this structure, I'll add an inline hint that shows the type of the identifier the comment "points" to"

jakebailey commented 1 year ago

That's basically what this issue was asking for (though, TS does not use LSP), with the caveat that there is no LSP API which allows you to arbitrarily tell the client to render inlay hints; only an API where the client can request them and the server answers (which TS already implements).

jasikpark commented 1 year ago

I see, so this is more explicitly an editor tooling feature for requesting inlay hints that should work with any LSP that implements inlay hints?

jakebailey commented 1 year ago

It's a request that tsserver implement a new feature that does (or does something like) inlay hints for // ^?, and that VS Code supports it, however that might work. This issue has nothing to do with the LSP as TypeScript's tsserver does not implement the LSP.

orta commented 1 year ago

All very great arguments which I fundamentally agree with!

I think exploring converting the twoslash extension into a more generalized 'query comments' system is totally doable, and once that's got some steam, chat with the vscode team about whether the idea is good for upstreaming further down the line. Thanks for the feedback!

Maybe it means helping out https://marketplace.visualstudio.com/items?itemName=YiJie.vscode-comment-queries /cc @NWYLZW

Happy to close as a NOOP on the TS side

NWYLZW commented 1 year ago

comment queries for rust-analyzer when 👀

With the linked code:

return vscode.commands.executeCommand<vscode.Hover[]>(
  'vscode.executeHoverProvider',
  editor.document.uri,
  editor.selection.active);

The extension generalizes to any language which has a hover provider. The trick is just finding the comments.

Now I have implemented support for both Golang and Python using this feature.

NWYLZW commented 1 year ago

It is true that the current TypeScript LSP cannot provide more structured data, which is a difficult point for plugin development. Providing more support in this aspect would be very helpful for me.

jasikpark commented 1 year ago

@NWYLZW where can I find this implementation? (esp for Golang)

NWYLZW commented 1 year ago

@NWYLZW where can I find this implementation? (esp for Golang)

image

https://marketplace.visualstudio.com/items?itemName=YiJie.vscode-comment-queries

https://github.com/NWYLZW/vscode-comment-queries/blob/main/langs/golang/main.go