Open msadeqhe opened 1 year ago
Similarly, we can have the following syntax for
{}
and[]
blocks:{code:comment,code:comment,...,code:comment}
Maybe I'm misunderstanding your suggestion, but doesn't this conflict with objects?
{key:value}
Similarly, we can have the following syntax for
{}
and[]
blocks:{code:comment,code:comment,...,code:comment}
Maybe I'm misunderstanding your suggestion, but doesn't this conflict with objects?
{key:value}
Sorry. I forgot to mention that inner comments within ()
, {}
and []
are allowed only for declarations, because type as comments are useful for declarations. I'll update my suggestion to explain it. Thanks.
In a nutshell, the syntax of the suggestion is like this:
With comment | Without comment | Which part was comment? |
---|---|---|
(code:comment) |
(code) |
:comment |
{code:comment} |
{code} |
:comment |
[code:comment] |
[code] |
:comment |
code1:(comment) code2 |
code1 code2 |
:(comment) |
code1:{comment} code2 |
code1 code2 |
:{comment} |
code1:[comment] code2 |
code1 code2 |
:[comment] |
code1:<comment> code2 |
code1 code2 |
:<comment> |
code1:"comment" code2 |
code1 code2 |
:"comment" |
code1:'comment' code2 |
code1 code2 |
:'comment' |
code1:comment, code2 |
code1, code2 |
:comment |
code1:comment; code2 |
code1; code2 |
:comment |
code1:comment= code2 |
code1= code2 |
:comment |
They are sorted by precedence, and they are comments only outside object construction (e.g. outside {var: value}
).
Also both ,
, ;
and =
will be treated as a part of comment if they are within nested ()
, {}
, []
, <>
, ""
or ''
.
For example:
With comment | Without comment | Which part was comment? |
---|---|---|
(code1:(comment, comment), code2) |
(code1, code2) |
:(comment, comment) |
(code1:comment (comment, comment), code2) |
(code1, code2) |
:comment (comment, comment) |
EDIT: I fixed the syntax in the table.
Why am I suggest this syntax? Because this syntax is close to TypeScript, Flow, ... with some changes to make it a general syntax for all possible types as comments. Additionally the rules are simple to follow.
Optionally JavaScript may define a standard format for the content of comment
, so the way we specify the types as comments will be similar in all type checkers. Also type checkers can have their own additional type checking formats.
In a nutshell, the syntax of the suggestion is like this:
With comment Without comment Which part was comment? (code:comment)
(code)
:comment
{code:comment}
{code}
:comment
[code:comment]
[code]
:comment
code1:(comment) code2
code1 code2
:(comment)
code1:{comment} code2
code1 code2
:{comment}
code1:[comment] code2
code1 code2
:[comment]
code1:<comment> code2
code1 code2
:<comment>
code1:"comment" code2
code1 code2
:"comment"
code1:'comment' code2
code1 code2
:'comment'
code1:comment, code2
code1, code2
:comment
code1:comment; code2
code1; code2
:comment
code1:comment= code2
code1= code2
:comment
They are sorted by precedence, and they are comments only outside object construction (e.g. outside
{var: value}
).Also both
,
,;
and=
will be treated as a part of comment if they are within nested()
,{}
,[]
,<>
,""
or''
.For example:
With comment Without comment Which part was comment? (code1:(comment, comment), code2)
(code1, code2)
:(comment, comment)
(code1:comment (comment, comment), code2)
(code1, code2)
:comment (comment, comment)
EDIT: I fixed the syntax in the table.
The rules can be combined. For example, the following line:
code0 (code1: (comment, comment), code2: comment): "comment" code3
... is equal to this after ignoring comments:
code0 (code1, code2) code3
The above line can be a function declaration:
// function name(a: (comment, comment), b: comment): "comment" { return a + b; }
function name(a , b ) { return a + b; }
In which:
code0
is function name
code1
is a
code2
is b
code3
is { return a + b; }
I've changed the syntax of the example from README.md:
let x: string;
function equals(x: number, y: number): "boolean" {
return x === y;
}
:{
interface Person {
name: string;
age: number;
}
type CoolBool = boolean;
}
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
getGreeting(): "string" {
return `Hello, my name is ${this.name}`;
}
}
function all(a: Set<string>,
b: { name: string, age: number },
c: number[],
d: (x: string): string,
e: new (b: Bread): Duck,
f: [number, number],
g: string | number,
h: Named & Dog,
j: T[K]) {
// ...
}
function split(str: string, separator?: string) {
// ...
}
// TypeScript
let value = 1 :as number;
// Flow
let value = 1;
(value: number);
// assert that we have a valid 'HTMLElement', not 'HTMLElement | null'
document.getElementById("entry")!.innerText = "...";
:{
type Foo<T> = T[];
interface Bar<T> {
x: T;
}
}
function foo:<T>(x: T) {
return x;
}
class Box:<T> {
value: T;
constructor(value: T) {
this.value = value;
}
}
// Types Annotations - example syntax solution
add:<number>(4, 5);
new Point:<bigint>(4n, 5n);
function sum(this: SomeType, x: number, y: number) {
// ...
}
So, in a nutshell, the difference between this suggestion and the proposal from README.md are:
""
(or any other bracket). (NOTE 1):
instead of ::
. (NOTE 2)interfaces
, types
, ... should be inside :{/*here*/}
or any other syntax. (NOTE 1)VAR as TYPE
to either one of:
VAR :as TYPE
, if it ends with ,
or ;
.VAR :(as TYPE)
(VAR: TYPE)
(similar to Flow syntax)(ARGS) => TYPE
to either one of:
(ARGS): TYPE
((ARGS) => TYPE)
keyword (ARGS) => TYPE
(keyword
can be a meaningful name such as function
).NOTE 1: The above syntax changes depend on the decision of type checkers to choose how to express the types as they want. For example, TypeScript may choose to wrap the return type inside
""
but Flow may choose to wrap them inside''
, or any other type checker may choose to wrap them inside()
or ...NOTE 2: I have to explain about case 2. When the template arguments are inside the comment, we may ommit
:
. For example:// Multiple `:` won't end the comment in function argument list. function all(a: Set:<string>) { /* ... */ } :{ type Foo:<T> = T[]; interface Bar:<T> { x: T; } }
We can omit
:
from them, because they are already a comment:function all(a: Set<string>) { /* ... */ } :{ type Foo<T> = T[]; interface Bar<T> { x: T; } }
Additionally the following part of the proposal from README.md can also be included:
arg?:type
obj!.member
this
argumentEDIT: The syntax of function types is changed.
I should mention that with multiple :
we can extend the comment. For example:
With comment | Without comment | Which part was comment? |
---|---|---|
let a: (x: string) => string; |
let a => string; (INVALID) |
: (x: string) |
In above example, => string
is not a part of comment, so it's invalid. To make it valid, we can extend the comment part with a sequence of comments:
With comment | Without comment | Which part was comment? |
---|---|---|
let a: (x: string) :=> string; |
let a; |
: (x: string) :=> string |
If you feel (x: string) :=> string
is ugly for function types, that's why we should change the syntax of function types to simply one of the following options, or any other syntax desired by type checker:
With comment | Without comment | Which part was comment? |
---|---|---|
let a: (x: string): string; |
let a; |
: (x: string): string |
let a: ((x: string) => string); |
let a; |
: ((x: string) => string) |
let a: function (x: string) => string; |
let a; |
: function (x: string) => string |
BTW, I'm thinking about alternative ways such as allowing a special case for function types to be treated as comments.
BTW, I'm thinking about alternative ways such as allowing a special case for function types to be treated as comments.
OK, I find a solution. The alternative way to solve this is if there is an operator after ()
in code1:(comment) code2
, then the comment part will be extended until it reaches to one of ,
, ;
or =
.
With comment | Without comment | Which part was comment? |
---|---|---|
code1:(comment) op comment, code2 |
code1, code2 |
:(comment) op comment |
code1:(comment) op comment; code2 |
code1; code2 |
:(comment) op comment |
code1:(comment) op comment= code2 |
code1= code2 |
:(comment) op comment |
op
can be any operator such as =>
, |
, &
, ...
For example:
let name: (string) => string = /*...*/;
Its variable is name
and its type is a function which gets a string an returns a string. The whole : (string) => string
is the comment part, because operator =>
exists after ()
, the comment will ends with =
.
The same set of rules can be applied to :{comment}
, :[comment]
, :<comment>
, :"comment"
and :'comment'
.
These rules also is useful to write types such as:
let x: (A & B) | C = /*...*/;
function caller(x: (A & B) | C,
y: { name: string, age: number } & { id: number },
z: "true" | "false") {
// ...
}
So this restriction (change) is relaxed by the new rules:
- The syntax of function type must be changed from
(ARGS) => TYPE
to either one of:
(ARGS): TYPE
((ARGS) => TYPE)
keyword (ARGS) => TYPE
(keyword
can be a meaningful name such asfunction
).- or any other syntax (NOTE 1)
But these restrictions (changes) are still required:
So, in a nutshell, the difference between this suggestion and the proposal from README.md are:
- Return types should be inside
""
(or any other bracket). (NOTE 1)- Template arguments and parameters must be after
:
instead of::
. (NOTE 2)interfaces
,types
, ... should be inside:{/*here*/}
or any other syntax. (NOTE 1)- The syntax of type conversion must be changed from
VAR as TYPE
to either one of:
VAR :as TYPE
, if it ends with,
or;
.VAR :(as TYPE)
(VAR: TYPE)
(similar to Flow syntax)- or any other syntax. (NOTE 1)
So, in a nutshell, the difference between this suggestion and the proposal from README.md are:
- Return types should be inside
""
(or any other bracket). (NOTE 1)
The problem for this one is a little hard to resolve, it needs to be context aware, because unfortunately the return type of a function doesn't have a separator unlike arguments and parameters (which are separated by ,
), member variables (which are separated by ;
) and variable declarations (in which its value is after =
).
I love how much thought you put into this, and it is a very interesting idea.
Thanks. I hope to make the rules as simple and general and compatible with TypeScript, Flow, etc as possible.
So, in a nutshell, the difference between this suggestion and the proposal from README.md are:
- Return types should be inside
""
(or any other bracket). (NOTE 1)The problem for this one is a little hard to resolve, it needs to be context aware, because unfortunately the return type of a function doesn't have a separator unlike arguments and parameters (which are separated by
,
), member variables (which are separated by;
) and variable declarations (in which its value is after=
).
To solve this problem, we can exclude a special pattern from the :comment
syntax. Because of the syntax of function declarations in JavaScript, we have to make a special rule for {}
like this with a higher precedence than the last rule:
With comment | Without comment | Which part was comment? |
---|---|---|
code1:comment {code2} |
code1 {code2} |
:comment |
Simply that means in a function declaration or where we have something like code1 {code2}
, we can place a comment before {...}
. So the table of rules for the :comment
syntax will be like this.
With comment | Without comment |
---|---|
openblock code:comment closeblock |
openblock code closeblock |
code1:open comment close op comment sep code2 |
code1 sep code2 |
code1:open comment close code2 |
code1 code2 |
code1:comment {code2} |
code1 {code2} |
code1:comment sep code2 |
code1 sep code2 |
Or in this format (bold and italic text is the comment):
openblock code:comment closeblock
code1:open comment close op comment sep code2
code1:open comment close code2
code1:comment {code2}
code1:comment sep code2
In which:
openblock
can be either {
, (
or [
.closeblock
must be the corresponding closing-bracket to openblock
such as }
, )
or ]
.open
can be either {
, (
, [
, <
, "
or '
.close
must be the corresponding closing-bracket to open
such as }
, )
, ]
, >
, "
or '
.op
can be any operator except =
.sep
can be either ,
, ;
or =
.comment
is outside object literal syntax (e.g. {var:value}
or {key:value}
).The table is sorted by precedence. The first row has the highest and the last row has the lowest precedence.
As an example for the above rules if we have parentheses ()
and ,
as the separator and =>
as the operator:
With comment | Without comment | Which part was comment? |
---|---|---|
(code:comment) |
(code) |
:comment |
code1:(comment) => comment, code2 |
code1, code2 |
:(comment) => comment |
code1:(comment) code2 |
code1 code2 |
:(comment) |
code1:comment {code2} |
code1 {code2} |
:comment |
code1:comment, code2 |
code1, code2 |
:comment |
In the same way we can rewrite the rules for other brackets and separators and operators.
So within function declaration:
function something(): string { /* statements... */ }
: string
is the comment and the comment ends with { /* statements... */ }
. But in the following example:
function something(): T[K] { /* statements... */ }
: T[K]
is the comment and the comment doesn't end with [K]
, because there isn't a rule for it.
Now consider this example:
function something(): { x: number } & { y: number } { /* statements... */ }
: { x: number } & { y: number }
is the comment because:
&
between them.{ /* statements... */ }
, so the comment ends with it.Finally, this restriction (change) is relaxed by this new rule:
So, in a nutshell, the difference between this suggestion and the proposal from README.md are:
- Return types should be inside
""
(or any other bracket). (NOTE 1)
But these restrictions (changes) are still required:
So, in a nutshell, the difference between this suggestion and the proposal from README.md are:
- Template arguments and parameters must be after
:
instead of::
. (NOTE 2)interfaces
,types
, ... should be inside:{/*here*/}
or any other syntax. (NOTE 1)- The syntax of type conversion must be changed from
VAR as TYPE
to either one of:
VAR :as TYPE
, if it ends with,
or;
.VAR :(as TYPE)
(VAR: TYPE)
(similar to Flow syntax)- or any other syntax. (NOTE 1)
I think I've to clear the rules to make it simple. So in a nutshell, the grammar is like this:
With comment | Without comment | What was the comment? |
---|---|---|
(code:cmntgrp) |
(code) |
:cmntgrp |
[code:cmntgrp] |
[code] |
:cmntgrp |
{code:cmntgrp} |
{code} |
:cmntgrp |
code:cmntgrp,code |
code,code |
:cmntgrp |
code:cmntgrp;code |
code;code |
:cmntgrp |
code:cmntgrp=code |
code=code |
:cmntgrp |
code:cmntgrp{code} |
code{code} |
:cmntgrp |
In which:
code
is any JavaScript code.cmntgrp
is either:
comment
comment op comment
comment op comment op ...
comment
is either:
()
, []
, {}
, <>
, ""
or ''
. For example: {A, B}
,
, ;
, =
or {
. For example: T[K]
op
is any operator except ,
or =
. For example: (A) => B
The rules are valid only outside object literal syntax (e.g. {var:value}
or {key:value}
).
Additionally the following part of the proposal from README.md can also be included:
arg?:type
obj!.member
this
argumentIn this way, most of the written code in current TypeScript, Flow, etc will be supported by JavaScript.
This looks really great! Thank you for your time and effort into looking into the plausibility of a route like this. I'm especially impressed that you're working to solve lots of edge cases (such as the return type of functions) that often gets forgotten.
Some questions/thoughts:
How would you handle syntax like the following:
import type ... from ...
or import { type A, B } from ...
)readonly
and protected, e.g. class MyClass { readonly x: number }
class MyClass implements MyInterface { ... }
Would there be special syntax provided to help with these? Some of these could easily be done using the :
syntax with only minor deviation from TS, such as class MyClass: implements MyInterface { ... }
and maybe class MyClass { (:readonly) x: number }
?
You said in an earlier comment the following:
keyword (ARGS) => TYPE (keyword can be a meaningful name such as function).
Why is the keyword required? I was understanding that the mere presence of =>
would extend the comment further.
I'm noticing that one of the things you rely on is an explicit semicolon to end a type comment, like with let a: b;
. This does add the ASI hazard where if you forget a semicolon, the following line would accidentally get included as part of the comment. This is sort of a variation of the "token soup" problem that was explained in an earlier TC39 conference, which any "flexible-syntax" proposal will likely be susceptible to. It also wreaks havoc on those who like to code without semicolons (though I personally think that's fine, some of the other upcoming proposals will also be making semicolon-less coding more difficult than what it has been in the past). I'm mostly just pointing this out, not really expecting it to be solved.
I guess one exception to this generic code: comment
pattern would be if it conflicts with the label: statement
syntax. i.e. maybe if the "code" part is the first part of a statement, and if an identifier, then it will be interpreted as label: statement
instead of code: comment
?
Thanks for your review/thoughts.
How would you handle syntax like the following:
- The import type syntax (e.g.
import type ... from ...
orimport { type A, B } from ...
)- Modifiers like
readonly
and protected, e.g.class MyClass { readonly x: number }
- "implements", e.g.
class MyClass implements MyInterface { ... }
Would there be special syntax provided to help with these? Some of these could easily be done using the
:
syntax with only minor deviation from TS, such asclass MyClass: implements MyInterface { ... }
and maybeclass MyClass { (:readonly) x: number }
?
Yes, to keep the :
syntax with only minor deviation from TS:
In the import type
syntax from TypeScript, we have to use the syntax in which imported objects are within { ... }
:
// TypeScript
import type ... from ...
import type { ... } from ... // Same as above
import { type A, B } from ...
// JavaScript
import: type { ... } from ...
import { A: type, B } from ...
readonly
and protected
is a little hard to express with :
-style comment syntax.
// TypeScript
class MyClass { readonly x: number }
There are alternative ways to resolve it. We may:
// JavaScript
class MyClass { x: number readonly; }
()
, []
, {}
, <>
, ""
or ''
:
(IMO it looks verbose)
// JavaScript
class MyClass { :{readonly} x: number; }
// JavaScript
class MyClass { @readonly x: number; }
::
:
(IMO I don't like this one, because this is a waste of symbol and too much specific)
// JavaScript
class MyClass { ::readonly x: number; }
Also implements
in class declaration would be like this as you've mentioned:
// TypeScript
class MyClass implements MyInterface { ... }
// JavaScript
class MyClass: implements MyInterface { ... }
The difference between (:comment)
and :(comment)
is that if we remove the comments, the first one is equal to ()
, but the second one is equal to nothing.
You said in an earlier comment the following:
keyword (ARGS) => TYPE (keyword can be a meaningful name such as function).
Why is the keyword required? I was understanding that the mere presence of
=>
would extend the comment further.
Because at that moment, I didn't think about a rule to extend the comment further if there is an operator.
I'm noticing that one of the things you rely on is an explicit semicolon to end a type comment, like with
let a: b;
. This does add the ASI hazard where if you forget a semicolon, the following line would accidentally get included as part of the comment. This is sort of a variation of the "token soup" problem that was explained in an earlier TC39 conference, which any "flexible-syntax" proposal will likely be susceptible to. It also wreaks havoc on those who like to code without semicolons (though I personally think that's fine, some of the other upcoming proposals will also be making semicolon-less coding more difficult than what it has been in the past). I'm mostly just pointing this out, not really expecting it to be solved.
Good point. So if a statement has :
-style comment in any part of it, it must end with semicolon ;
, otherwise it would be a syntax error. I should add the rule that semicolon ;
is required for a statement if it contains :
-style comment in itself.
I guess one exception to this generic
code: comment
pattern would be if it conflicts with thelabel: statement
syntax. i.e. maybe if the "code" part is the first part of a statement, and if an identifier, then it will be interpreted aslabel: statement
instead ofcode: comment
?
That's a good solution. So code: something
would be label: statement
if its code
part is an identifier at the start of the statement, and if it's placed outside class declarations and object literals.
Thanks a lot. I'll update my suggestion to include these notes.
Also :
-style comment is not valid within Ternary Operator ?:
.
So in general, :
-style comments are not valid inside:
{key: value}
)
{key: comment}
condition ? true : false
)
condition ? value1 : comment : value2
// OR
condition ? value1 : value2 : comment
label1: label2: statement
)
label1: label2: statement: comment;
// OR
label1: label2: label_a: statement;
But if :
-style comments are written in inner expressions, they can be valid inside:
{key: (value: comment)}
key
cannot be written as (key: comment)
because (key)
is not a valid syntax in JavaScript:
{(key): (value)} // ERROR!
{ key : (value)] // OK.
But if JavaScript changes its behavior and allows (key)
in object literals, then we would have:
{(key: comment): (value: comment)}
(condition: comment) ? (value1: comment) : (value2: comment)
Label Statements. For example:
label1: label2: (statement: comment);
// Or we may put labels in their own line.
label1: label2:
statement: comment;
In TypeScript, Flow, etc their syntax is in a way that we can distinguish the comment and the statement within ternary operators and label statements.
Good point. So if a statement has :-style comment in any part of it, it must end with semicolon ;, otherwise it would be a syntax error. I should add the rule that semicolon ; is required for a statement if it contains :-style comment in itself.
I don't feel like that really solves the problem. Take this as an example:
let x: number // <-- oops, forgot a semicolon
x = f(2);
Because JavaScript is (generally) not sensitive to newlines, the above could also be written as the following, and it should mean the same:
let x: number x = f(2);
Which passes your check - it's a statement that ends in a semicolon. It's valid syntax as well. If we strip out all of the comments, we'd be left with this:
let x;
Basically, the fact that we forgot a semicolon has caused it to eat the entire next line without us noticing, which would be fun to debug.
Maybe there's some stuff that can be done to help avoid this issue. While JavaScript is generally not sensitive to new lines, its grammar does contain special rules saying "you are not allowed to place a new line here". Maybe that could be done here to some extent. For example, we could say that, unless a comment is inside a bracket pair, it's not allowed to contain new lines. Thus, this would be a syntax error (either that, or it'll automatically add a semicolon for you, or something):
let x: number // <-- oops, forgot a semicolon
x = f(2);
But spreading a type definition over new lines inside a bracket pair like the following would still be allowed:
let x: {
a: number
};
// or this should work too
let x: (
number |
string
);
Because JavaScript is (generally) not sensitive to newlines, the above could also be written as the following, and it should mean the same:
let x: number x = f(2);
Which passes your check - it's a statement that ends in a semicolon. It's valid syntax as well. If we strip out all of the comments, we'd be left with this:
let x;
Infact if we strip out all of the comments:
let x = f(2);
Because the comment ends with =
.
Thanks for the explanation about how new-line within the comment should not be allowed.
The question is which one of these two would the following be without types:
let x: number
x = f(2);
let x;
x = f(2);
let x x = f(2);
In other words, should the newline mark the end of a type? Should the presence of a following identity mark the end of a type (this AFAIK is going to require one to define a type syntax instead of just ignoring them completely)?
In other words, should the newline mark the end of a type? Should the presence of a following identity mark the end of a type (this AFAIK is going to require one to define a type syntax instead of just ignoring them completely)?
The newline should always mark the end of a type or require the programmer to put the semicolon at the end (otherwise it would be a syntax error), but it shouldn't be optional. In this way, the grammar of :
-style comment would be simple to parse. Also multi-line comments are possible within bracket pair (@theScottyJam's idea is here).
IMO semicolon ;
should be required for a statement if it contains :
-style comment in itself, because if the comment ends with newline, a programmer may mistakenly think that the comment can be extended to the next line.
Is there a single line type comment? Maybe it could be useful for shorthands, and for minification (ship a library, but perhaps the types are still available, minified on a single line):
// Top level
: type Foo = { whatever } & whatever
function foo() {
// Top level of a block
: interface Foo { whatever }
}
IMO semicolon
;
should be required for a statement if it contains:
-style comment in itself, because if the comment ends with newline,
What do you mean exactly? Can you provide an example?
Is there a single line type comment?
No. Thanks for your idea, it would be more pleasant than :{comment}
. We may use ::
to have statement-like comments. In your example:
// Top level
: type Foo = { whatever } & whatever
function foo() {
// Top level of a block
: interface Foo { whatever }
}
It would be equal to this one without :
-style comments (by current rules):
// Top level
= { whatever } & whatever
function foo() {
// Top level of a block
{ whatever }
}
Because :
-style comments would end with either ,
, ;
, =
or {
. That's why we can have :
-style comments for variable and function declarations:
let x: comment = value;
function x(): comment { /*...*/ }
Back to the first example, if we use another symbol like ::
with different rule-set, we may write that example like this:
// Top level
:: type Foo = { whatever } & whatever
function foo() {
// Top level of a block
:: interface Foo { whatever }
}
::
has a different rule-set. It doesn't end with ,
, =
or {
, so it can be used to make the whole statement as a comment. But still ;
would end the comment.
Unlike :
-style comments, ;
may be optional to write at the end of the statement in ::
-style comments. So the rule to find out when the comment ends after ::
, follows the rule of bracket pair (we can write newline inside bracket pair, but newline will end the comment outside bracket pair).
What do you mean exactly? Can you provide an example?
If :
-style comment ends with newline. The programmer may think it's possible to extend the comment to multiple lines:
let x: comment = call(): comment +
comment;
But it would be equal to the following code without comments:
let x = call()
comment;
That is invalid. comment;
should've been removed, but it didn't.
If we make ;
to be optional in statements with :
-style comments, then more rules have to be specified for them. If you think the rule of allowing newline within bracket pair is enough for optional ;
, so the semicolon ;
doesn't have to be required.
The main advantage this has over
is that it reduces characters due to not needing comment-closing characters, even in the middle of code due to special rules it can have that existing-comments can't, f.e.
function foo(f: Foo): Bar {...}
// vs
function foo(f /*: Foo */) /* : Bar */ {...}
// vs
function foo(
f //: Foo
) /* : Bar */ {...}
// vs
function foo(
f //: Foo
) { //: Bar
...
}
For inside-existing-comment syntax, I like #192's direction more than JSDoc for sure. But something about this is also tempting due to the parsing rules allowing the multiline space without closing characters:
:: import Foo from './Foo' // this is a type import, the `type` keyword is not needed now.
import Bar from './Bar' // actual runtime JavaScript
:: interface Foo {
// ...
}
let f: Foo = {...};
(The ::
comment syntax came from #184).
I'm no grammar expert yet: anything undersirable about this from a grammar/implementation perspective?
One thing that is nice about #192 is that it (so far, but maybe in too much of a TS-specific way, but maybe that can be adjusted) allows specifically for documentation description space.
Let's try to see what types and documentation look like with a hybrid of the above type comment syntax and existing-comment syntax from #192. Here's just one example:
:: interface Foo {
//: description for n
n: number
}
//: a - description for a
//: b - description for b
//: c - description for c
//: - description of return value
function method(a: number, b: Foo, c: Bar): Baz {
return "Val:"+a + b + c;
}
@msadeqhe What happens with multi-line type import? Is there a possible rule for it without actual import
syntax being part of the proposal?
:: import {
Foo,
Bar,
} from 'somewhere'
EDIT: Hmmm, maybe :: import
should be officially part of the proposal because JS languages should not deviate from this! Make a special import
syntax that is ignored at runtime?
And for hybrid, something like so?
import { :Type, RuntimeValue } from 'somewhere'
import {
:Type,
RuntimeValue
} from 'somewhere'
@msadeqhe What happens with multi-line type import? Is there a possible rule for it without actual
import
syntax being part of the proposal?:: import { Foo, Bar, } from 'somewhere'
If Foo
is an interface type from TS and Bar
is a regular JS class, we have to use :
-style comment if we want to keep the rules as simple as possible:
import {
Foo: type,
Bar,
} from 'somewhere'
Therefore we have to explicitly write type
.
If both Foo
and Bar
are a TS thing, we can use code:comment{code}
syntax in this case:
import: type {
Foo,
Bar,
} from 'somewhere'
EDIT: sry, we cannot, because it would end up with: import from 'somewhere'
. But if JS could support import
nothing, we could use it. So the only option is :: import
as your suggestion.
or simply ::
-style comment is enough as you write it:
...
:: import { Foo, Bar, } from 'somewhere'
Hmmm, maybe
:: import
should be officially part of the proposal because JS languages should not deviate from this!
or simply
::
-style comment is enough as you write it:...
:: import { Foo, Bar, } from 'somewhere'
What's the rule for where the comment ends? It will not end after the }
? That's why I thought maybe the whole :: import {} from '...'
statement would be an unique comment specifically for official module syntax.
For that matter, we could also just adopt import type
and import {type ...}
from TypeScript, which would also make it official module syntax, because I believe that we do not want to allow all type systems arbitrarily define their own module syntax.
:: import {} more code
would end with either ;
or newline (of course if they are not inside inner bracket (quote) pair).
For import {type A, ...}
from TS, we can use syntax import {A: type, ...}
. For import type {...}
if we want to avoid to use ::
-style comment, if JS supports import from something
which means import nothing from a module, then we can use syntax import from something: type (...)
(which is equal to import from something
without the comment part) or even import: type (...)
(which is equal to import
without the comment part).
EDIT: @trusktr, I've fixed the syntax to use (...)
instead of {...}
.
In a nutshell the rules are:
:: comment
):
::
must be stand alone, so only ;
or white-space can be before ::
.comment
will end before ;
or newline.code :comment separator code
):
comment
may not start with an opening bracket (quote) (either (
, [
, {
, <
, "
or '
).comment
will end before ,
, ;
, =
or {
(as separator
).comment
will end before )
(as separator
) if it's enclosed within ()
.comment
will end before ]
(as separator
) if it's enclosed within []
.comment
will end before }
(as separator
) if it's enclosed within {}
.code :comment code
):
comment
may start with an opening bracket (quote) (either (
, [
, {
, <
, "
or '
).comment
will end with the matching closing bracket (quote) (correspondingly either )
, ]
, }
, >
, "
or '
).code identifier <comment> code
):
comment
must be enclosed within <>
, and it must be directly after the identifier.In all cases:
:
doesn't start a comment within Object Literals, Ternary Operators and Label Statements.()
, []
, {}
, <>
, ""
or ''
).""
and ''
) are like string literals. Their content cannot be multiple lines. Also escape sequences are meaningful in their content.For example:
:: comment
is equal to nothing.(code:comment)
is equal to (code)
.code:comment, code
is equal to code, code
.code:(comment) code
is equal to code code
.:(comment) & comment
is equal to nothing.class name<comment> { ... }
is equal to class name { ... }
.function name<comment>(...) { ... }
is equal to function name(...) { ... }
.let name<comment> = ...
is equal to let name = ...
.In a nutshell the rules are:
Statement Comment (
:: comment
):
::
must be stand alone, so only;
or white-space can be before::
.- The
comment
will end before;
or newline.Expression Comment (
code :comment separator code
):
- The
comment
may not start with an opening bracket (quote) (either(
,[
,{
,<
,"
or'
).- The
comment
will end before,
,;
,=
or{
(asseparator
).- The
comment
will end before)
(asseparator
) if it's enclosed within()
.- The
comment
will end before]
(asseparator
) if it's enclosed within[]
.- The
comment
will end before}
(asseparator
) if it's enclosed within{}
.Enclosed Expression Comment (
code :comment code
):
- The
comment
may start with an opening bracket (quote) (either(
,[
,{
,<
,"
or'
).- The
comment
will end with the matching closing bracket (quote) (correspondingly either)
,]
,}
,>
,"
or'
).- The end of nested expression comment may be extended with a valid JS operator until it ends with the rules of Expression Comment.
In all cases:
:
doesn't start a comment within Object Literals, Ternary Operators and Label Statements.- If the comment contains a nested bracket pair, it won't end in the middle of the nested bracket pair.
Let's add another comment category named Declaration Comments. They are enclosed within <>
, and they are only allowed in declarations. In JavaScript, it's already an error to write <>
in declarations, so it's safe to be added to JS syntax. By this rule, we would be able to write generic arguments in every class, function, etc declaration without extra :
symbol uniformly:
class A<T> { /*...*/ }
function B<T>() { /*...*/ }
:: interface C<T> { /*...*/ }
:: type D<T> = /*...*/
But for instantiation and generic parameters (invocation) we always use :<>
syntax:
let x = new A:<number>();
let y = B:<number>();
// Consider we need an extra nested bracket pair to do this within object literals.
// With comment
let z = { a: (new A:<number>()), b: (B:<number>()) };
// Without comment
let z = { a: (new A ()), b: (B ()) };
I've added this new comment category to the rules.
This is an example, what it would look like:
import: type (InterfaceX, TypeAlias) { ClassA, ClassB } from 'somewhere'
:: interface Abc<T> {
name(): string;
[i: T]: number;
}
function check<T>(a: { x: number , y: number } & { z: number }): classA<T> | undefined {
// statements...
}
class Base<T> {
constructor(width: T, height: T) {
// statements...
}
// declarations...
}
class X<T> extends Base<T>: implements Abc<T> {
@readonly count: number = 10;
id(): string {
return (count: as string);
}
// declarations...
}
// This part is not yet a feature of TS, Flow, etc.
let Pi<T> = (3.141592654: T);
let p = Pi:<number>;
let o = new Base:<number>(10, 2);
let u = (10: satisfies number);
Without comments:
import { ClassA, ClassB } from 'somewhere'
function check(a) {
// statements...
}
class Base {
constructor(width, height) {
// statements...
}
// declarations...
}
class X extends Base {
@readonly count = 10;
id() {
return (count);
}
// declarations...
}
// This part is not yet a feature of TS, Flow, etc.
let Pi = (3.141592654);
let p = Pi;
let o = new Base(10, 2);
let u = (10);
The import
feels a little awkward. What if
import theDefault, {foo, bar}: comment from "somewhere"
// f.e. all the following are the same, the differing part is only type comment:
import theDefault, {foo, bar}: {One, Two} from "somewhere"
import theDefault, {foo, bar}: type {One, Two} from "somewhere"
import theDefault, {foo, bar}: {type One, type Two} from "somewhere"
// with named imports only
import {foo, bar}: this-is (a) {type} <comment> ! from "somewhere"
// with default import only
import theDefault: blah blah from "somewhere"
// side-effect import only
import: blah blah "somewhere"
As a potential end user of this possible feature, the direction is interesting! But I'm not knowledgeable in parsers/grammars yet. Does this keep the grammar within LR(1) or no?
With the current rules, your suggested import
syntax would be like this wihtout comments:
import theDefault, {foo, bar}
// f.e. all the following are the same, the differing part is only type comment:
import theDefault, {foo, bar} from "somewhere"
import theDefault, {foo, bar} {One, Two} from "somewhere"
import theDefault, {foo, bar} from "somewhere"
// with named imports only
import {foo, bar} {type} <comment> ! from "somewhere"
// with default import only
import theDefault
// side-effect import only
import
Does this keep the grammar within LR(1) or no?
I don't have enough knowledge about compilers/transpilers/..., but from what I've researched, it seems it can keep the grammar within LR(1).
If I can get one take away of all of this accounting for any and every kind of edge case is that it convinced me even more that inline type comments/declarations is a bad idea.
And this was something I tried to address by https://github.com/tc39/proposal-type-annotations/issues/176 by asking for them to be as a sort of C header declaration or better a Haskell like type definition that will be followed by implementation free of anything of the sort.
Wouldn't it be easier for compilers because it would provide for simpler syntax? I know that I personally find it easier for my human eyes and my human (non-AI) mind to reason about.
Just raising it as a concern here and wouldn't want to comment it more here as to not detract from this issue subject
Thanks. I'll continue this subject (using regular comments instead of :
-style comments) in #176.
With the current rules, your suggested
import
syntax would be like this wihtout comments:
I was thinking that because import syntax is standard, comments like that are ok up until the string, as a rule. I don't think anyone needs a string inside the import type comment, but they could with :()
Why not opt for a syntax which can be parsed more easily ? Instead of :
, i propose ::
by default.
With this syntax, we can add type where we currently cannot with typescript.
For exemple with object destructuring;
E.g
function myFunction(firstParam:: string, {optionalPram1:: boolean = true, optionalParam2:: string | null = null, optionalParam3: param3:: number = 0} = {}):: boolean { .... }
Instead of just copy what is existing, i think it's important to fix what's blocking in the current syntax.
I think :
is overuses in JS to be usable in every situation
What do you think about this syntax? Let's find a rule for it:
These are the rules:
code
can be any JavaScript code.comment
can be anything except,
,;
or)
.,
,;
or)
won't end thecomment
if they are inside another nested block like()
,{}
,[]
,<>
,""
or''
.In this way, we can write multiple inner comments within a block between
()
.Similarly, we can have the following syntax for
{}
and[]
blocks:In the first syntax,
comment
will end with,
,;
or}
except if they are inside another nested block like()
,{}
,[]
,<>
,""
or''
.In the second syntax,
comment
will end with,
,;
or]
except if they are inside another nested block like()
,{}
,[]
,<>
,""
or''
.Until now, it was really the syntax of TypeScript, Flow, etc.
EDIT: Inner comments between
()
,{}
and[]
are only allowed for declarations. That's the syntax of type checkers. Javascript engines easily can distinguish between declarations and expressions such as object creations, function calls and etc.So, what if
comment
isn't within a block between()
,{}
or[]
?This needs to be resolved. These comments can be written either within declarations or expressions and statements. We can resolve it in one of the following ways:
()
,{}
or[]
.,
or;
.()
,{}
,[]
,<>
,""
or''
.Considering the above rules, let's write some JavaScript code with type information:
In this way, the type as comment would have a general syntax for type checkers.
EDIT: The final grammar is ...