-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature Request] Proposal for type annotations as comments #48650
Comments
Another concise comemnt suggested in that proposal is: //:: TypeA, TypeB => ReturnType
function f(a, b) {
return a.handle(b)
} |
We have to make crystal clear on what kind of JSDoc we are talking about here. As far as I understand from what I read from your comments/issues you are talking about JSDoc without TS. A currently available way of enabling static type checking in .js without the need to compile, is writing all your types in .ts files and then importing them in .js files via JSDoc comments. AFAIK the only TS feature that is not supported like this, is enum. But is that an intrinsic inability of that way of enabling static type checking? I would happily discuss with you about anything you think is un ergonomic, verbose, or lacking features regarding this way of static type checking. In my experience it is none of that.
Take a look on how they are suggesting to implement generics. They will introduce breaking changes.
It is already present with what I suggest. |
@Jack-Works That's interesting, and definitely Then, I think what TypeScript could add is the ability to type via function "overloading", such that this would become valid: //:: function f(a: TypeA, b: typeB): typeA
function f(a, b) {
return a.handle(b)
} In other words, I think it's a much bigger ask if the existing code in comments is not valid TypeScript. Right now, in the proposal, it's basically drop-in search / replace (other than flagging certain TS as invalid in a comment, as noted). |
You're right! There are clever workarounds. But writing types inline is often more self-documenting / easier to reason about, and JSDoc still wouldn't be as concise when it comes to assigning those types to vars / params. And, I think even with those, you're still missing some things when it comes to type-checking, although I can't remember what off the type of my head. That is, I think that
Exactly. It can't be dropped in as-is. This proposal could. |
@lillallol From the TC39 proposal:
The motivation is the same. |
We should strive for separation of intent and implementation since it is a best practice. But I have to admit that inline types are preferred when I create utility functions that I intend to use in multiple projects (lodash like utility functions), because like this I have to copy only a single thing from a file, and not two things (concretion and abstraction). Another use case for inline types is for defining simple types (e.g. boolean, number etc) for some variables that are declared inside functions (my projects are just functions and singletons), but again when the types get more involved I add them in .ts files. For non lodash like projects, I usually write all the types into these files:
I think you will also find that self explanatory and easy to reason about. If you want to see the types of a concretion then you can hover over it and VSCode will show the type. If you are still not satisfied with what VSCode shows then you can ctrl+click on the imported type for VSCode to go to its definition.
This argument is not a real concern. At least in my own experience (e.g. the projects I have done). Do you really actually press more the keyboard when using JSDoc? If yes then how much? 1 key? 2 keys? You wanna get rid of those taps and introduce a new comment syntax? Is it worth for the extra fragmentation it will create? If you (and a substantial amount of people) have created projects (10k+ lines of code) with the way of static type checking I suggest, and you find these extra taps reduce DX, then fine I am with you. Strictly speaking, when someone sticks to separation of intend and implementation, importing types via JSDoc is not necessarily more verbose when compared to writing everything in .ts files. In fact sometimes it can be less verbose. Regarding which is more readable, I have to say that this is a matter of what someone has gotten used to. For example initially I found ts code not as readable as js code. But then I got used to it. Same for the importing types via JSDoc.
Just do not ask me how to type classes, because I have no clue about classes. Still the same question remains:
or is it something that can be supported in the future? |
I'm confused where you are coming from or what your argument is. In no way would what I'm (or others) proposing have a negative impact on JSDoc-style typing. If it works for you in a way that matches your development workflow, great. That's not everyone's experience, and I think it's clearly articulated by even people on the TypeScript team that the JSDoc flow is not the greatest experience, from their perspective. So, if this doesn't match a need you have, that's okay. This isn't for you. Just like, if someone is fine with transpiling TypeScript / having a build step, this isn't for them either. But it's a clearly articulated need by other developers. This would be an alternate style of embedding types that would be compatible with the existing TypeScript ecosystem, including JSDoc-typed files. |
Lets be more specific with examples here.
What do you mean by JSDoc flow? You mean the way I suggest? If yes then I would like to have some links, or at least if ts maintainers see that, have a discussion on that, here.
There is already a solution for that need, which actually promotes best practices (separation of intent and implementation) rather than embracing bad practices (embracing of mixing intent with implementation). From what you suggest we end up hard coding .js files with ts. This is not done with the way I suggest : If your response is :
then I would like to make myself crystal clear on that one: compiling ts to js as inferior way of developing to what I suggest (read here for more). |
Teeny note, there's already I suspect this would be quite simple to design, add and document, especially with prior art, and few downsides (let me know if I'm wrong!) It's not even all that much in conflict with the ECMA proposal, which can have significantly nicer experience with inline annotations. |
When Flow first introduced its streamlined comment syntax, they imported it wholesale from flotate, a piece of software built pretty much entirely by one person. Rather than a written proposal, I think what's needed here is a working prototype. I think it makes sense to experiment with Flow's syntax, and even to build on it with (I wonder whether function f(a, b) {
//:: TypeA, TypeB => ReturnType
return a.handle(b)
} |
@dfabulich one of the things I like about the Flow behavior is it's extremely straightforward: just remove the wrapping I actually took a stab already at adding it to a fork of There is an interesting issue though: the following code would seem to be valid, but give different results when run through the transpiler from directly: let runtime = "native";
/*::
runtime = "transpiled";
*/
console.log(runtime); Not sure if that's a bug or a feature! |
Yeah, I just think it's a hassle to add four characters ( function method(a /*: number */, b /*: number */, c /*: number */, ) /*: number */ {
return a + b + c;
} function method(a, b, c) {
//: number, number, number => number
return a + b + c;
} |
So there is a very low but important risk if you left this as is -- it's possible that someone is using Essentially, I feel you're not wrong (although I still disagree with this syntax as not very TS-y or JS-y); I just feel it's a huge mistake to conflate these two things in one feature, as it's a much bigger ask. These should be two different proposals.
|
@simonbuchan As to this: let runtime = "native";
/*::
runtime = "transpiled";
*/
console.log(runtime);
This should definitely be treated as a bug / type-checking error by TypeScript, and IMO this is a bug if Flow supports that. The resulting code is runnable but the result is not expected. So ideally, only "typing" forms would be allowed within comments. And definitely this code should be entirely thrown out (throw an error): /*::
let runtime = "transpiled";
*/
console.log(runtime); (TypeScript should throw an error as When I have time, I can refine the proposal with specifying what is allowed / disallowed in these comment blocks, and not just escaping "any TypeScript". |
I've been working on an implementation of this for a while: Note also that it's extremely WIP and still has print debugging statements (D:) - there are still quite a number of issues, especially (or mostly?) with incremental parsing Of note is that runtime statements (mostly) error as expected; enums and const enums are completely banned; /*::declare const FOO: unique symbol;*/ (Forgot to mention - for TypeScript do |
@somebody1234 Awesome! I still think it needs documentation of the technical details -- how / what / which things are allowed, so I want to add that to this proposal. Are there any other things you've caught in the implementation that need to be considered? |
(Note: For reference, there's the test file I use at the bottom. It should contain all the things that are allowed) Misc notes
What is allowed
function foo(); Test file/*:: type Left = 1; type Right = 2; type Test = Left | Right; type Test2 = Left | Right; let invalid: Test; */ type Invalid = 1; |
namespace / module namespace X {
const a = 1
}
import react = require('react') |
the fix is simple though (and it's relatively minor) so i'll hold off on committing it for now (if you want it asap though, search for |
Ah... also worth noting that a bunch of diagnostic messages will be On that note, feel free to suggest improvements to the new diagnostic messages as well |
Interesting approach, seems like a lot of work! I was thinking banning bad uses would be done with something like a |
Does seem like a lot of work - but I think it's relatively low effort compared to the alternatives. Plus this way you get a huge amount of control over where exactly they're valid, error recovery etc. Offtopic but, one concern would be, it adds quite a bit of complexity to parser logic so it might cause performance issues - however I'm guessing it's not that bad since most of the time is probably usually typecheck time anyway |
The profiles I've seen are all dominated by FS access, binding and type checking, but with JavaScript you can always fall off a performance cliff and have some code run 100x worse. You'd probably notice the tests being noticably slower though! Not sure exactly what you mean by find and replace - doing that before parse would break location reporting along with all the other issues! My attempt was to get the scanner to treat |
That is not valid. You can import any type from /**
* @type {import("./privateApi.js").IMethod}
*/
function method(a, b) {
/**
* No need to use that annotation anyway since TS infers types.
* @type {import("./privateApi.js").INumber}
*/
let x = a + b;
/**
* No need to use that annotation anyway since TS infers types.
* @type {import("./privateApi.js").INumber}
*/
let y = a * b;
return x / y;
} //./privateApi.ts
export type IMethod = (a : number, b : number) => number;
export type INumber = number; Regarding the type annotations proposal the enforcement of separation of implementation and abstractions in different files enables someone to standardise type-import annotations without the need to define a type system and simultaneously not restricting its syntax. That is exactly the proposal I want to present to tc39. |
OK. How do you annotate the type of /**
* @type {import("./privateApi.js").IMethod}
*/
function method(a, b) {
/**
* No need to use that annotation anyway since TS infers types.
* @type {import("./privateApi.js").INumber}
*/
let x = a + b;
/**
* No need to use that annotation anyway since TS infers types.
* @type {import("./privateApi.js").INumber}
*/
let y = a * b;
return x / y;
} This is a simple example, but in complicated function bodies, we need a direct way to use TypeScript features within function bodies. |
Please give an example. |
Here is one. But then again it is a matter of support, i.e. not an intrinsic inability of the way of static typing I suggest. |
This is an example from VSCode repository: public selectToBracket(selectBrackets: boolean): void {
if (!this._editor.hasModel()) {
return;
}
const model = this._editor.getModel();
const newSelections: Selection[] = [];
this._editor.getSelections().forEach(selection => {
const position = selection.getStartPosition();
let brackets = model.bracketPairs.matchBracket(position);
if (!brackets) {
brackets = model.bracketPairs.findEnclosingBrackets(position);
if (!brackets) {
const nextBracket = model.bracketPairs.findNextBracket(position);
if (nextBracket && nextBracket.range) {
brackets = model.bracketPairs.matchBracket(nextBracket.range.getStartPosition());
}
}
}
let selectFrom: Position | null = null;
let selectTo: Position | null = null;
if (brackets) {
brackets.sort(Range.compareRangesUsingStarts);
const [open, close] = brackets;
selectFrom = selectBrackets ? open.getStartPosition() : open.getEndPosition();
selectTo = selectBrackets ? close.getEndPosition() : close.getStartPosition();
if (close.containsPosition(position)) {
// select backwards if the cursor was on the closing bracket
const tmp = selectFrom;
selectFrom = selectTo;
selectTo = tmp;
}
}
if (selectFrom && selectTo) {
newSelections.push(new Selection(selectFrom.lineNumber, selectFrom.column, selectTo.lineNumber, selectTo.column));
}
});
if (newSelections.length > 0) {
this._editor.setSelections(newSelections);
this._editor.revealRange(newSelections[0]);
}
}
|
What about local functions and local types? If we put all of them to a |
This is not valid /**@type {import("./some/place.js").ISelectToBracket}*/
selectToBracket(selectBrackets) {
if (!this._editor.hasModel()) {
return;
}
const model = this._editor.getModel();
const newSelections: Selection[] = [];
this._editor.getSelections().forEach(selection => {
const position = selection.getStartPosition();
let brackets = model.bracketPairs.matchBracket(position);
if (!brackets) {
brackets = model.bracketPairs.findEnclosingBrackets(position);
if (!brackets) {
const nextBracket = model.bracketPairs.findNextBracket(position);
if (nextBracket && nextBracket.range) {
brackets = model.bracketPairs.matchBracket(nextBracket.range.getStartPosition());
}
}
}
/**@type {import("./from/somewhere.js").IPostionNull}*/
let selectFrom = null;
/**@type {import("./from/somewhere.js").IPostionNull}*/
let selectTo = null;
if (brackets) {
brackets.sort(Range.compareRangesUsingStarts);
const [open, close] = brackets;
selectFrom = selectBrackets ? open.getStartPosition() : open.getEndPosition();
selectTo = selectBrackets ? close.getEndPosition() : close.getStartPosition();
if (close.containsPosition(position)) {
// select backwards if the cursor was on the closing bracket
const tmp = selectFrom;
selectFrom = selectTo;
selectTo = tmp;
}
}
if (selectFrom && selectTo) {
newSelections.push(new Selection(selectFrom.lineNumber, selectFrom.column, selectTo.lineNumber, selectTo.column));
}
});
if (newSelections.length > 0) {
this._editor.setSelections(newSelections);
this._editor.revealRange(newSelections[0]);
}
}
There is no need to use a single file for your types. I usually use these:
I never had a problem with spaghetti code. |
@lillallol Here's a simple repo with the first commit written in TypeScript, https://github.com/trusktr/buildless-typescript
EDIT, nevermind, I updated it to JS with no build. That one wasn't so bad! Better to not have separate files for the classes in that case. Here's one class: /**
* @abstract
* @template {object} T
*/
export class Foo {
foo = "456"
/**
* @abstract
* @returns {T}
*/
method() {
throw "subclass must implement"
}
doFoo() { this.foo; this.method() }
}
import { Bar } from "./Bar.js"
import { Foo } from "./Foo.js"
const FooBar = /** @type {typeof Foo<Bar>} */ (Foo)
export class Test extends FooBar {
/** @override */
method() {
return new Bar()
}
}
const b = new Test().doFoo()
b.logBar() with terser comments, it could be: export /*: abstract */ class Foo/*:<T extends object>*/ {
foo = "456"
/*: abstract */method() { //: T
throw "subclass must implement"
}
doFoo() { this.foo; this.method() }
} import { Bar } from "./Bar.js"
import { Foo } from "./Foo.js"
export class Test extends Foo/*:<Bar>*/ {
/* :override */ method() {
return new Bar()
}
}
const b = new Test().doFoo()
b.logBar() That still feels a little awkward and noisy to me. Here's an alternative that I like more: //: abstract
export class Foo { //:<T extends object>
foo = "456"
//: abstract
method() {} //: T
doFoo() { this.foo; this.method() }
} import { Bar } from "./Bar.js"
import { Foo } from "./Foo.js"
export class Test extends Foo { //:<Bar>
//: override
method() {
return new Bar()
}
}
const b = new Test().doFoo()
b.logBar() Ideas are varied at the moment, but I think that last one is cleanest so far. There would need to be rules that associate the comments to the parts they annotate. For example, the SIde by side: /**
* @abstract
* @template {object} T
*/
export class Foo {
foo = "456"
/**
* @abstract
* @returns {T}
*/
method() {
throw "subclass must implement"
}
doFoo() { this.foo; this.method() }
} //: abstract
export class Foo { //:<T extends object>
foo = "456"
//: abstract
method() {} //: T
doFoo() { this.foo; this.method() }
} |
I ended up spending some time working on this problem, and was able to put together a fork of TypeScript that allows TypeScript syntax to be inside of comments - it took more work than I thought it would :p. Anyways, it's up one NPM and GitHub. As for putting JSDocs inside of a "TS Comment" (What I'm calling these /*::
interface User {
readonly username: string
readonly birthday: Date
/** @deprecated *//*::
readonly age: number
}
*/ It's a little funky, but it works good enough for now. |
Given this is a pure fork and seems to have the same repo structure, is this change PR-able? |
Maybe not this fork specifically - it has things like changes to the README in order to give the npm package an updated README. But I can make another fork that strips that kind of stuff out, then put together a PR. |
Also, the current fork contains a couple of extra command line options that let's you transform your project from TypeScripr to JavaScript with type comments and vice versa (using the includes and excludes lists from tsconfig.json to know what to convert) - I assume I'd need to strip that out as well as that wasn't part of the original feature request. |
@theScottyJam, have you added a flag to enforce using inline comments instead of regular Typescript syntax? Or that would be better implemented as a linter rule? And since there's a flag to convert Javascript to Typescript and viceversa, does it add types to current Javascript projects? |
@piranna if you're using inline comments you're probably writing JS anyway. so it's enforced in the sense that any TS syntax is invalid syntax |
@piranna For that first half of your question, yes, what @somebody1234 said - you'd typically be using JS files anyways, so it's already enforced in that sense (though the comment syntax should work in TS files too - there's not much use for them except, say, doing a slow manual transition or something - I haven't really tested that sort of thing much - yet).
All the converters do is add or remove comment delimiters (the |
Yes, my intention is to write Javascript, not Typescript. My question was about writting Typescript, but with inline comments. But if all the Typescript syntax (not only types) can be put inside inline comments, then effectively you are writting Javascript with comments :-)
So, if I have actual Javascript code, it will not add types comments, is that? And if I want them, I need to convert the Javascript code to Typescript, and later back to Javascript with types, is that correct? Do you know any tool that can fill all the Typescript types in their place? |
Ok, I've found https://github.com/airbnb/ts-migrate and https://github.com/JoshuaKGoldberg/TypeStat, latest one is in active development and looks promising :-) |
@theScottyJam since you are working on this, and considering that the new syntax is not JSDoc-compatible, any chance you'd please consider adding support for the more concise ideas I mentioned in these comments 🙏?
For example this one is a lot cleaner than the //: abstract
export class Foo { //:<T extends object>
foo = "456"
//: abstract
method() {} //: T
doFoo() { this.foo; this.method() }
} The export class Foo { //:<T extends object> export class Foo /*:<T extends object>*/ { |
One of the things I dislike about JSDocs-for-types is that it's a fairly different syntax from normal TypeScript, which makes it more difficult for an existing TypeScript user to pick up and start reading or writing it. What it looks like you're suggesting, is to allow us to move the TypeScript syntax to different places in order to make it easier to put that syntax in a line comment instead of a block comment, such as moving the return type after the functions opening brace ( It can also cause clashes if we're not careful - in your above examples, you show that generic parameters can be put after the opening bracket, but you also show that return types can occupy the same location, so what if you want to do both? // How would you do the following TypeScript code using TS-in-comments?
function fn<T extends object>(x: T): T { ... }
// Maybe like this? The two things are separated by a space?
function fn(x /*: T */) { //:<T extends object> T
// Or like this?
function fn(x /*: T */) { //:<T extends object> //: T
// Maybe this combination isn't allowed and you're forced to do something like this instead?
// picking one or the other to go after the "{" but not both?
function fn/*::<T extends object>*/(x /*: T */) { //: T
// Maybe generic functions always have to be written with their type in the correct spot,
// and the shorthand for moving generics after the "{" can only be used with classes? We could come up with rules to define this behavior - but the fact that there isn't any clear-cut way to handle these scenarios worry me, and makes me less inclined to want to go that route. Plus, I don't really find the (Some vocabulary for this next part - The special comments where arbitrary syntax can go inside that's being proposed in this thread - I'm calling those "TS comments") That being said - there is one shorthand syntax that I would really love to have (which I've seen others mention in different places) - and that's the ability to put the types of a function onto the preceding line, something kind-of like this: //: (string, { x: number, y: number }) => string
function doThatThing(name, { x, y }) {
...
} I hesitate to actually add anything like that for a few reasons:
|
Indeed, we'd need to settle on some rules. I believe that the // We can use /*: */ comments:
function fn /*: <T extends object> */ (x /*: T */) /*: T */ { /*...*/ }
// Here's the same thing but with the function split onto separate lines:
function fn /*: <T extends object> */ (
x /*: T */
) /*: T */ {
// ...
}
// Here's the same thing as the previous, but with added documentation:
function fn /*: <T extends object> - description of T */ (
x /*: T - the description for `x` (**note**, this is markdown) */
) /*: T - the return value description */ {
// ...
}
// A rule for the end-of-line comments could be that they apply to the last
// annotatable position prior to them on that line, so the following would be
// equivalent to the previous sample:
function fn ( //: <T extends object> - description of T
x //: T - the description for `x` (**note**, this is markdown)
) { //: T - the return value description
// ...
}
// A mix can be used as desired. Here only the return annotation is an
// end-of-line comment:
function fn /*: <T extends object> - description of T */ (x /*: T - the description for `x` */) { //: T - the return value description
// ...
}
// Now, how about for describing a function not inline? Maybe end-of-line
// comments could be used, and because there is nothing in front of them on the
// same line, they'd apply to the next item below. Note how in the next example
// I left the comment for <T> in the same spot, so it applies to the generic T
// parameter.
//: - This is a description for the function (it is *markdown*)
//: x: T - This comment has a name (between the ::) so it will apply to the function's `x` parameter
//: Perhaps this line is automatically a continuation of the description of `x` similar to JSDoc comments.
//: return: T - The description of the return value.
function fn ( //: <T extends object> - description of T
x
) {
// ...
}
// If we were to hoist the T parameter comment, it would fit into the overall
// comment for the function:
//: - This is a description for the function (it is *markdown*)
//: <T extends object>: - description of T (T is defined between the ::, but maybe that's not needed if the <> symbols are enough)
//: x: T - This comment has a name (between the ::) so it will apply to the function's `x` parameter
//: Perhaps this line is automatically a continuation of the description of `x` similar to JSDoc comments.
//: return: T - The description of the return value.
function fn (x) {
// ...
}
// Maybe the "return" word is not required, so two colons would associate with
// the return:
//: - This is a description for the function (it is *markdown*)
//: <T extends object>: - description of T (T is defined between the ::, but maybe that's not needed if the <> symbols are enough)
//: x: T - This comment has a name (between the ::) so it will apply to the function's `x` parameter
//: Perhaps this line is automatically a continuation of the description of `x` similar to JSDoc comments.
//: : T - The description of the return value (no "return" comment name here).
function fn (x) {
// ...
}
// Taking advantage of the rule that an end-of-line comment applies to the
// previous annotatble location, it could be convenient in various places:
const arr = [1,2,3] //: as const - this description is ignored, but could be useful
const foo = {
// ...
} //: satifies Foo
// In this one we have both:
const bar /*: HTMLDivElement */ = document.querySelector('.foo') //: as HTMLDivElement
// These have the same result:
let a /*: number | null */ = 123
let a //: number | null
= 123
// These all have the same result (using `a` from before)
let b /*: number */ = a /*:! - force a not null (this description is ignored) */
let b /*: number */ = a /*:!*/ // (no description)
let b /*: number */ = a /*!*/ // Maybe /*!*/ is a unique shorthand
let b //: number
= a //:! - with a description (ignored)
let b //: number
= a //:!
let b = //: number - this still annotates b (perhaps less desirable formatting)
a //:!
// Here are some examples of end-of-line comments annotating the next thing:
//: number - some description
let b = a /*:! - force a not null (this description is ignored) */
//: number
let b = a /*!*/
//: SomeType - this is a special object
let o = {} //: as SomeType
//: abstract
class Base {
//: number | null
n = 123
//: (a: number, b: string) => boolean - Do something.
doSomething(a, b) {
}
//: - Do something else.
//: a: number - some number
//: b: string - some string
//: :boolean - returns a boolean indicating something
doSomethingElse(a, b) {
}
}
// Finally here is a terser option without descriptions for the function
// definition. This would be similar to JSDoc using @type instead all the individual
// parts (@param, @return, etc):
//: <T>(x: T) => T - This is a description for the function (it is *markdown*)
function fn (x) {
// ...
} I'm thinking about this from the perspective of a user: what would make it as concise as possible? On the other hand, I'm not sure if this makes it more complicated to implement, but as a user I would enjoy having terser comments. Here's a hand-crafted basic example of what syntax highlight could look like: |
One more with types inline instead of in header comments: //: - This is a description for the function (it is *markdown*)
//: T: - description of T (T is defined between the ::, but maybe that's not needed if the <> symbols are enough)
//: x: - This comment has a name (between the ::) so it will apply to the function's `x` parameter
//: : - The description of the return value
function fn /*:<T extends Object>*/ (x /*: T*/ ) /*: T*/ {
// ...
}
// Spaces are optional:
//:- This is a description for the function (it is *markdown*)
//:T:- description of T (T is defined between the ::, but maybe that's not needed if the <> symbols are enough)
//:x:- This comment has a name (between the ::) so it will apply to the function's `x` parameter
//::- The description of the return value
function fn /*:<T extends Object>*/ (x /*:T*/ ) /*:T*/ {
// ...
} Type definitions: // Maybe the rule is, if this is on a line on its own (not annotating anything) then its just type space for TS type syntax:
/*:
export interface Foo { ... }
export type Other = Foo | string
*/
//: <T extends Foo>(x: T) => T
function fn (x) {
// This one is scoped inside the function (can't use `export`)
/*: interface Bar { ... } */
// ...
} TSDoc broke compatibility with original JSDoc, I mean they are basically incompatible in various ways (namely TS type syntax is not part of JSDoc). On that note, it might make just as much sense to make a new (terser) syntax and people can make new tooling for it (just like they had to for TSDoc because JSDoc tooling didn't work). Any other ideas/thoughts? |
Definitely i like It. |
My opinions to help reign in scope a little bit:
@theScottyJam I applaud your initiative 🎆
JSDoc has also been a maintenance burden creating distinct syntax for every addition to
I agree the more minimal the better for MVP 💯 Important to get this accepted in principle before bike shedding design and implementation of more convenient shorthand.
The XY problem this solves is to use types-in-comments alone instead of in conjunction with JSDoc. That doesn't seem achievable short-term. Also consider the example above would require case analysis of the construct being parsed to associate the docstring with the correct identifier. That's similar to the maintenance cost already inherent in JSDoc.
This I conceptually love though!
The XY problem here is to reduce verbosity without breaking Prettier's default formatting. Last annotatable condition would be ambiguous though consider:
But it would be valid to have an access modifier like
This syntax is unfortunate too and should probably use a different character sequence for nested comments. All the other ideas feel out of scope for an initial version. |
I was initially worried about the Another option would be to allow jsdocs to be written after triple slashes (both inside and outside of the TS comments). /*::
interface User {
/// @deprecated
readonly age: number
}
*/ It would make for a fairly nice solution, but it could also cause conflicts with TypeScript's existing triple-slash directives. I'll spitball a few more options. Another single-line option that tries to look similar to /*::
interface User {
//** @deprecated
readonly age: number
}
*/ Mangle the closing token somehow? /*::
interface User {
/** @deprecated * /
readonly age: number
}
*/ Swap the star character? /*::
interface User {
/:: @deprecated :/
readonly age: number
}
*/ Swap the slash character? (I kind of like this one) /*::
interface User {
\** @deprecated *\
readonly age: number
}
*/ |
Here's a draft PR: #58601 It still has some broken tests that I'm trying to sort through, and I haven't added test coverage for the new stuff yet, and there's a few other changes I know will need to be made. If possible, I would like some guidance on a couple of issues. Perhaps, first, let me give some background on how I implemented this PR. How I implemented the PRWhenever the scanner runs into a In src/compiler/program.ts there's existing logic that'll report an error if it runs across TypeScript syntax inside of a JavaScript file. I hijacked this section of the code to add the error-reporting that this PR needs - as it walks across the tree, looking for TypeScript syntax, I made it so it'll additionally scan the regions between tokens looking for TS comment delimiters. It records the comment delimiters it finds so it can know if a particular node is in a TS comment when it shouldn't be or vice-versa, and then it'll report the appropriate errors. Problem 1Consider the following expression: a */* XXX */ b Without the block comment in the middle, this simply evaluates to Ideally, I'd only ignore the Even if this can be done, I'm not sure if it's the right way to go - I assume the scanner was intentionally designed to not carry around state like this, and perhaps that's a design decision that we don't want to change. I just don't know yet how else to implement the feature. Problem 2At the moment, it'll always try and parse the contents of a The issue could be partially fixed by having configuration from your tsconfig.json get passed into the scanner, then the scanner can change behavior depending on if you have the allowJS/checkJS flags set or not. TypeScript also adds support for the An alternative solution would be to invent a new tsconfig option for this feature, and to not provide a JavaScript directive to go with it - you either globally enable it, or you don't. |
Problem
Suggestion
This is not a new idea. It is a fleshed-out proposal for #9694 and is also based on the prior art of https://flow.org/en/docs/types/comments/. #9694 was marked as "Needs Proposal" so this is an attempt at that proposal. (There is also prior art / similar conclusions & asks in the issue threads of the TC39 proposal.)
🔍 Search Terms
"jsdoc alternative", "jsdoc", "flotate", "flow comments"
✅ Viability Checklist
My suggestion meets these guidelines:
⭐ Proposal
A JavaScript file would be preceded by
// @ts
The reason this is needed is to indicate the author's intent at
@ts-check
.Types of comment blocks:
/*: Foo*/
is the equivalent of: Foo
(a type) in TypeScript. Other type modifiers like/*?: Foo */
are also interpreted plainly as?: Foo
/*:: statement*/
is the equivalent ofstatement
in TypeScript, and used to mark completetype
/interface
blocks and other types of assertions.//:
and//::
when the type / type import occupies the whole line / remainder of the lineHere's a basic example, borrowed from Flow:
The TypeScript compiler would interpret
/*: */
and/*:: */
as type annotations for TypeScript, making the entire JavaScript file a complete and valid TypeScript file, something that JSDoc does not provide.Here are some other examples, borrowed from the TC39 proposal:
Important Note: an author should not be able to put any content in
/*:: */
blocks. For example, this should be flagged as invalid:Yes, the content of the
/*:: */
is "valid TypeScript", but the engine should distinguish between type annotations / assertions from code that is to be available at runtime.📃 Motivating Example
A lot of the motivations for this are the exact same as https://github.com/tc39/proposal-type-annotations; but this approach just solves it a different way, and could be done much sooner. The TypeScript engine would need to do little more than replace comment blocks in conforming
.js
files and then just immediately treat it as if it were a plain ol' TypeScript file.💻 Use Cases
What do you want to use this for?
This would allow teams / individuals / myself to use TypeScript without a build step! Gone would be "compile times" while developing.
What shortcomings exist with other approaches?
What shortcomings exist with this approach?
/*:: */
because that would defeat the purpose. So there may be some initial confusion around usage. See the above example.What workarounds are you using in the meantime?
There are no current workarounds, to meet these particular goals. If you a) want to use all of TypeScript, b) don't want a build step in your JS files, there is no solution. Also, to again re-iterate the point, the TC39 proposal would also not meet these goals (like JSDoc, it also cannot support all of TypeScript), so there are benefits of doing this regardless of the outcome of that proposal.
The text was updated successfully, but these errors were encountered: