microsoft / TypeScript

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

Add `async`ness to semantic highlighting #57593

Open OldStarchy opened 8 months ago

OldStarchy commented 8 months ago

πŸ” Search Terms

"expose type syntax highlight", "semantic syntax highlighting for promise", "highlight variables based on type"

βœ… Viability Checklist

⭐ Suggestion

Expose (some) type information to the syntax highlighter for semantic styling so we can highlight variables / function calls of particular types.

πŸ“ƒ Motivating Example

Hungarian notation has long since marked as something to be avoided, yet a common practice I see is naming async functions with an Async suffix, or with an await prefix.

fs.readFile();
await fs.readFileAsync();

await awaitMyFunction();

This indicates that whether or not something returns a promise is important enough that we're marking async functions with these prefixes/suffixes.

Hungarian notation went away because IDE's improved and we could start to rely on it to give us type information (on hover) and via type checking.

While researching this I stumbled upon the semantic highlighting config in VSCode and decided to take a leaf out of rusts book which underlines mut values and tried to apply an underline to any variable T where T extends Thenable<any>.

However the best I could get was to mark functions tagged as async as this is the only information available to the syntax highlighter (as seen by running the "Developer: Inspect Editor Tokens and Scopes" command)

    "editor.semanticTokenColorCustomizations": {
        "[Default Dark Modern]": {
            "rules": {
                "*.async": {
                    "underline": true,
                },
            },
        }
    },

But this falls over in a few places

interface Foo {
  doThing(): Promise<string>;
}

class Bar extends Foo {
  async doThing() { // <- underlines correctly here
    return 'hi';
  }
}

const bar = new Bar();
bar.doThing(); // <- and here

const barAsFoo: Foo = bar;

barAsFoo.doThing(); // <- no underline here.

It also doesn't do anything for thenable valued variables

const prom = bar.doThing(); // <- prom is not underlined

console.log('doing something else");

await prom;

image

In my head, the ideal situation is that a textmate modifier is added to any variable whose value (or method whose return type) exclusively extends Promise (or Thenable). That way prom would be underlined.

I understand there will be some complexities and many edge cases for figuring this out; its not necessarily true that Promise refers to a promise

type Promise<TAction extends string = string> = `I promise I will ${TAction}`;

function getActionFromPromise(prom: Promise) {
  const match = /^I promise I will (.*)$/.exec(prom);

  return match[1];
}

console.log(getActionFromPromise('I promise I will not write esoteric code'));

or

function getUserData(): Promise<User> | null {
  if (!db.isConnected()) return null;

  return db.getUser(ctx.currentUserId);
}

also

function doNothing<T>(val: T): T {
  return val;
}

const foo = doNothing(Promise.resolve());

There may be ways around these problems (maybe something like T extends Promise<any> ? Promise<any> extends T ? true : false : false)

πŸ’» Use Cases

  1. What do you want to use this for? As mentioned in "motivating example"
  2. What shortcomings exist with current approaches? ' '
  3. What workarounds are you using in the meantime? ' '
OldStarchy commented 7 months ago

Another use case relevant to the original title of this issue "Expose type information to syntax highlighter" being able to mark functions that mutate / return this different to those that are "const" and return a new value, this would help to catch errors like

const normalized = vec.normalize();

console.log(normalized === vec); // true (woops, needed .clone())