microsoft / TypeScript

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

Typescript type annotations as comments #9694

Open nojvek opened 8 years ago

nojvek commented 8 years ago

Typescript’s goal is simply be Javascript + Types.

There are many use cases where one might want to use the excellent typechecker but not really have any emit stage.

Projects already written in javascript work with allowJS. Typescript already supports parsing types from jsdoc comments.

What would be really awesome is just comment annotating your javascript with types and the project gets the benefit of being type checked. This could be a boon for a lot of existing javascript projects. Getting intellisense, VSCode typechecking on the fly and a lot of language server awesomeness.

e.g.

/// <reference path="..." />

class Hello {
    /*:: private*/ hello(message /*: string*/) /*: Promise<{}>*/{
                const promise /*: Promise<void>*/ = null 
        return /*:: <Promise<{}>>*/ null;
    }
}

i.e

/: [type] */ /:: [tscode] */

ixis-doug commented 2 years ago

@DanielRosenwasser

Types can always be stripped away/"downleveled" for older browsers.

Stated differently, could we say "well you could still run your code through a typescript compiler to strip the types"? If so, this leaves us in the in the same position we're currently in, no?

HolgerJeromin commented 2 years ago

I think one of the use case is: Develop with your fancy new browser and develop faster without compile. But in production you still use the transpiled/stripped/minified output even for some ie8 if you like/must.

DanielRosenwasser commented 2 years ago

Well if you're targeting Node, Deno, or primarily evergreen browsers, you can run as-is (especially for the development-time scenarios that @HolgerJeromin just mentioned). If you really need to accommodate older runtimes, you can add this as a production-time build step.

joeytwiddle commented 2 years ago

By the way, I have tried using JSDoc in a project, and I learned something I didn't know before:

We don't have to stick to the JSDoc way of declaring types, which usually involves lots of @ tags, and splitting up of objects.

We can instead use TypeScript types inside the JSDocs, to keep things small and familiar!

Here is an example with a function that accepts two strings, and returns an object whose two properties are dictionaries.

This is how we might expect to annotate the function using JSDoc

/**
 * @typedef {Object} MemberGroupsResponse
 * @property {Object.<string, MemberGroup>} memberGroups
 * @property {Object.<string, Array.<string>>} memberGroupsByUser
 *
 * @function getMemberGroupsUncached
 * @async
 * @param {string} month
 * @param {string} memberGroupsSheet
 * @returns {Promise.<MemberGroupsResponse>}
 */
async function getMemberGroupsUncached(month, memberGroupsSheet) { /* ... */ }

But actually we can annotate the function more simply, using a single TypeScript type!

/**
 * @type {(month: string, memberGroupSheet: string) => Promise<{
 *   memberGroups: Record<string, MemberGroup>;
 *   memberGroupsByUser: Record<string, string[]>;
 * }>}
 */
async function getMemberGroupsUncached(month, memberGroupsSheet) { /* ... */ }

Advantage:

We can also put comments like /** @type string | number */ above a variable

Disadvantages:

So if you don't need traditional JSDoc but you would like some TS-checking in your JavaScript files, this hybrid approach might work for you.

(I am using TypeScript type checking in VSCode, with the option js/ts.implicitProjectConfig.checkJs but // @ts-check would also work.)

ShadSterling commented 1 year ago

I'm trying to use tsc to generate .d.ts files for my fork of a JavaScript project, making minimal changes to the original .js files. For almost everything tsc's inference is good enough, and for most of the rest adding comments as described by @joeytwiddle fills in the gaps. But here's one that fails:

The original code:

delete Number.prototype.then;

Types that work in TypeScript:

interface MaybeThenable { then?: Function }
delete (Number.prototype as Number & MaybeThenable).then;

The same types in JSDoc annotation in a .js file:

/** @typedef { { then?: Function } } MaybeThenable */
/** @type { Number & MaybeThenable } */
var klass = Number.prototype;
delete klass.then;

Is rejected by tsc:

Type 'Number' is not assignable to type '{ then?: Function; } & number'.
  Type 'Number' is not assignable to type 'number'.
    'number' is a primitive, but 'Number' is a wrapper object. Prefer using 'number' when possible.

73             var klass = Number.prototype;
                   ~~~~~

It seems that tsc doesn't respect wrapper types given in TSDoc annotations

ShadSterling commented 1 year ago

Turns out it works if I go to constructors, but it would be better if tsc handled types the same way in both TypeScript annotations and JSDoc annotations

/** @typedef { { prototype: Object & { then?: Function } } } MaybeThenableConstructor */
/** @type { NumberConstructor & MaybeThenableConstructor } */
var konstructor = Number
delete konstructor.prototype.then;