-
Notifications
You must be signed in to change notification settings - Fork 47
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
Can we have a simple syntax to rule them all? :comment
.
#188
Comments
Maybe I'm misunderstanding your suggestion, but doesn't this conflict with objects? {key:value} |
Sorry. I forgot to mention that inner comments within |
In a nutshell, the syntax of the suggestion is like this:
They are sorted by precedence, and they are comments only outside object construction (e.g. outside Also both For example:
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 |
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:
|
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:
Additionally the following part of the proposal from README.md can also be included:
EDIT: The syntax of function types is changed. |
I should mention that with multiple
In above example,
If you feel
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
For example: let name: (string) => string = /*...*/; Its variable is The same set of rules can be applied to 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:
But these restrictions (changes) are still required:
|
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 |
(:comment)
and etc.:comment
.
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. |
To solve this problem, we can exclude a special pattern from the
Simply that means in a function declaration or where we have something like
Or in this format (bold and italic text is the comment):
In which:
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
In the same way we can rewrite the rules for other brackets and separators and operators. So within function declaration: function something(): string { /* statements... */ }
function something(): T[K] { /* statements... */ }
Now consider this example: function something(): { x: number } & { y: number } { /* statements... */ }
Finally, this restriction (change) is relaxed by this new rule:
But these restrictions (changes) are still required:
|
I think I've to clear the rules to make it simple. So in a nutshell, the grammar is like this:
In which:
The rules are valid only outside object literal syntax (e.g. Additionally the following part of the proposal from README.md can also be included:
In 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:
Would there be special syntax provided to help with these? Some of these could easily be done using the You said in an earlier comment the following:
Why is the keyword required? I was understanding that the mere presence of I'm noticing that one of the things you rely on is an explicit semicolon to end a type comment, like with I guess one exception to this generic |
Thanks for your review/thoughts.
Yes, to keep the
The difference between
Because at that moment, I didn't think about a rule to extend the comment further if there is an operator.
Good point. So if a statement has
That's a good solution. So Thanks a lot. I'll update my suggestion to include these notes. |
Also So in general,
But if
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. |
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
); |
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);
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 IMO semicolon |
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 }
} |
What do you mean exactly? Can you provide an example? |
No. Thanks for your idea, it would be more pleasant than // Top level
: type Foo = { whatever } & whatever
function foo() {
// Top level of a block
: interface Foo { whatever }
} It would be equal to this one without // Top level
= { whatever } & whatever
function foo() {
// Top level of a block
{ whatever }
} Because let x: comment = value;
function x(): comment { /*...*/ } Back to the first example, if we use another symbol like // Top level
:: type Foo = { whatever } & whatever
function foo() {
// Top level of a block
:: interface Foo { whatever }
}
Unlike |
If let x: comment = call(): comment +
comment; But it would be equal to the following code without comments: let x = call()
comment; That is invalid. If we make |
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 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 {
Foo,
Bar,
} from 'somewhere' EDIT: Hmmm, maybe And for hybrid, something like so? import { :Type, RuntimeValue } from 'somewhere'
import {
:Type,
RuntimeValue
} from 'somewhere' |
If import {
Foo: type,
Bar,
} from 'somewhere' Therefore we have to explicitly write If both import: type {
Foo,
Bar,
} from 'somewhere' EDIT: sry, we cannot, because it would end up with: or simply
|
What's the rule for where the comment ends? It will not end after the For that matter, we could also just adopt |
For EDIT: @trusktr, I've fixed the syntax to use |
In a nutshell the rules are:
In all cases:
For example:
|
Let's add another comment category named Declaration Comments. They are enclosed within class A<T> { /*...*/ }
function B<T>() { /*...*/ }
:: interface C<T> { /*...*/ }
:: type D<T> = /*...*/ But for instantiation and generic parameters (invocation) we always use 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 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 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
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 #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 |
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 E.g Instead of just copy what is existing, i think it's important to fix what's blocking in the current syntax. I think |
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 ...
The text was updated successfully, but these errors were encountered: