Skip to content
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

Open
msadeqhe opened this issue Sep 12, 2023 · 37 comments
Open

Can we have a simple syntax to rule them all? :comment. #188

msadeqhe opened this issue Sep 12, 2023 · 37 comments

Comments

@msadeqhe
Copy link

msadeqhe commented Sep 12, 2023

What do you think about this syntax? Let's find a rule for it:

(code:comment,code:comment,...,code:comment)

These are the rules:

  • code can be any JavaScript code.
  • comment can be anything except ,, ; or ).
  • ,, ; or ) won't end the comment 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:

{code:comment,code:comment,...,code:comment}
[code:comment,code:comment,...,code:comment]

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 []?

code:comment code

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:

  • Put the entire statement inside (), {} or [].
  • Explicitly end the comment with , or ;.
  • Put the comment inside (), {}, [], <>, "" or ''.

Considering the above rules, let's write some JavaScript code with type information:

function name(a: string, b: string): "string" { ... }

:{interface someone {
    name: string;
}}

class something:<T, U> {
    a: T;
    b: U;
}

let x = something:<number, number>();

In this way, the type as comment would have a general syntax for type checkers.

EDIT: The final grammar is ...

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 { (as separator).
    • The comment will end before ) (as separator) if it's enclosed within ().
    • The comment will end before ] (as separator) if it's enclosed within [].
    • The comment will end before } (as separator) 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.
  • Declaration Comment (code identifier <comment> code):
    • They are only valid within class, function and variable declarations.
    • The 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.
  • If the comment contains a nested bracket (quote) pair, it won't end in the middle of the nested bracket (quote) pair (e.g. (), [], {}, <>, "" or '').
  • The behavior of quote pairs (e.g. "" and '') are like string literals. Their content cannot be multiple lines. Also escape sequences are meaningful in their content.

For example:

  • Statement Comment:
    • :: comment is equal to nothing.
  • Expression Comment:
    • (code:comment) is equal to (code).
    • code:comment, code is equal to code, code.
  • Enclosed Expression Comment:
    • code:(comment) code is equal to code code.
    • :(comment) & comment is equal to nothing.
  • Declaration Comment:
    • class name<comment> { ... } is equal to class name { ... }.
    • function name<comment>(...) { ... } is equal to function name(...) { ... }.
    • let name<comment> = ... is equal to let name = ....
      • Although we don't have this feature in TS, Flow, etc yet, but it's a possibility.

Additionally the following part of the proposal from README.md can also be included:

  • Optional arguments arg?:type
  • Non-nullable assertions obj!.member
  • this argument

In this way, most of the written code in current TypeScript, Flow, etc will be supported by JavaScript.


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);
@spenserblack
Copy link

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}

@msadeqhe
Copy link
Author

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.

@msadeqhe
Copy link
Author

msadeqhe commented Sep 13, 2023

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.

@msadeqhe
Copy link
Author

msadeqhe commented Sep 13, 2023

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.

@msadeqhe
Copy link
Author

msadeqhe commented Sep 13, 2023

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; }

@msadeqhe
Copy link
Author

msadeqhe commented Sep 13, 2023

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:

  1. Return types should be inside "" (or any other bracket). (NOTE 1)
  2. Template arguments and parameters must be after : instead of ::. (NOTE 2)
  3. interfaces, types, ... should be inside :{/*here*/} or any other syntax. (NOTE 1)
  4. 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)
  5. 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 as function).
    • or any other syntax (NOTE 1)

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:

  • Optional arguments arg?:type
  • Non-nullable assertions obj!.member
  • this argument

EDIT: The syntax of function types is changed.

@msadeqhe
Copy link
Author

msadeqhe commented Sep 13, 2023

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.

@msadeqhe
Copy link
Author

msadeqhe commented Sep 13, 2023

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:

  1. 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 as function).
    • 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:

  1. Return types should be inside "" (or any other bracket). (NOTE 1)
  2. Template arguments and parameters must be after : instead of ::. (NOTE 2)
  3. interfaces, types, ... should be inside :{/*here*/} or any other syntax. (NOTE 1)
  4. 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)

@msadeqhe
Copy link
Author

msadeqhe commented Sep 13, 2023

So, in a nutshell, the difference between this suggestion and the proposal from README.md are:

  1. 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 =).

@msadeqhe msadeqhe changed the title Can we have a simple syntax to rule them all? (:comment) and etc. Can we have a simple syntax to rule them all? :comment. Sep 13, 2023
@trusktr
Copy link

trusktr commented Sep 15, 2023

I love how much thought you put into this, and it is a very interesting idea.

@msadeqhe
Copy link
Author

msadeqhe commented Sep 17, 2023

Thanks. I hope to make the rules as simple and general and compatible with TypeScript, Flow, etc as possible.

@msadeqhe
Copy link
Author

msadeqhe commented Sep 17, 2023

So, in a nutshell, the difference between this suggestion and the proposal from README.md are:

  1. 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:

  1. There is operator & between them.
  2. It doesn't have another operator before { /* 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:

  1. 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)

@msadeqhe
Copy link
Author

msadeqhe commented Sep 17, 2023

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:
    • Any character enclosed within (), [], {}, <>, "" or ''. For example: {A, B}
    • Otherwise any character except ,, ;, = 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:

  • Optional arguments arg?:type
  • Non-nullable assertions obj!.member
  • this argument

In this way, most of the written code in current TypeScript, Flow, etc will be supported by JavaScript.

@theScottyJam
Copy link

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:

  • The import type syntax (e.g. import type ... from ... or import { 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 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?

@msadeqhe
Copy link
Author

msadeqhe commented Sep 18, 2023

Thanks for your review/thoughts.

How would you handle syntax like the following:

  • The import type syntax (e.g. import type ... from ... or import { 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 as class MyClass: implements MyInterface { ... } and maybe class 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 ...
  • Modifiers like 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:
    • ... put them beside the type as the last element:
      (IMO it's enough, and it just works)
      // JavaScript
      class MyClass { x: number readonly; }
    • ... enclose them within (), [], {}, <>, "" or '':
      (IMO it looks verbose)
      // JavaScript
      class MyClass { :{readonly} x: number; }
    • ... use decorators (it's currently stage 3 proposal) instead of comments:
      (IMO I like this one, because this is a natural syntax for them)
      // JavaScript
      class MyClass { @readonly x: number; }
    • ... define a new syntax to have a single keyword-like comment such as :::
      (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 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?

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.

@msadeqhe
Copy link
Author

msadeqhe commented Sep 18, 2023

Also :-style comment is not valid within Ternary Operator ?:.

So in general, :-style comments are not valid inside:

  • Object Literals (e.g. {key: value})
    • Because the value would change its meaning to be the comment:
      {key: comment}
  • Ternary Operators (e.g. condition ? true : false)
    • Because we cannot distinguish where is the comment, and we cannot know when the comment ends:
      condition ? value1 : comment : value2
      // OR
      condition ? value1 : value2 : comment
  • Label Statements (e.g. label1: label2: statement)
    • Because we cannot distinguish where is the 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:

  • Object Literals. For example:
    {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)}
  • Ternary Operators. For example:
    (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.

@theScottyJam
Copy link

theScottyJam commented Sep 18, 2023

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
);

@msadeqhe
Copy link
Author

msadeqhe commented Sep 18, 2023

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.

@spenserblack
Copy link

spenserblack commented Sep 18, 2023

The question is which one of these two would the following be without types:

let x: number
x = f(2);
  1. let x;
    x = f(2);
  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)?

@msadeqhe
Copy link
Author

msadeqhe commented Sep 19, 2023

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.

@trusktr
Copy link

trusktr commented Sep 20, 2023

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 }
}

@trusktr
Copy link

trusktr commented Sep 20, 2023

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?

@msadeqhe
Copy link
Author

msadeqhe commented Sep 21, 2023

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).

@msadeqhe
Copy link
Author

msadeqhe commented Sep 21, 2023

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.

@trusktr
Copy link

trusktr commented Sep 21, 2023

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
Copy link
Author

msadeqhe commented Sep 21, 2023

@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!

@trusktr
Copy link

trusktr commented Sep 21, 2023

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.

@msadeqhe
Copy link
Author

msadeqhe commented Sep 21, 2023

:: 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 {...}.

@msadeqhe
Copy link
Author

msadeqhe commented Sep 21, 2023

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 { (as separator).
    • The comment will end before ) (as separator) if it's enclosed within ().
    • The comment will end before ] (as separator) if it's enclosed within [].
    • The comment will end before } (as separator) 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.
  • Declaration Comment (code identifier <comment> code):
    • They are only valid within class, function and variable declarations.
    • The 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.
  • If the comment contains a nested bracket (quote) pair, it won't end in the middle of the nested bracket (quote) pair (e.g. (), [], {}, <>, "" or '').
  • The behavior of quote pairs (e.g. "" and '') are like string literals. Their content cannot be multiple lines. Also escape sequences are meaningful in their content.

For example:

  • Statement Comment:
    • :: comment is equal to nothing.
  • Expression Comment:
    • (code:comment) is equal to (code).
    • code:comment, code is equal to code, code.
  • Enclosed Expression Comment:
    • code:(comment) code is equal to code code.
    • :(comment) & comment is equal to nothing.
  • Declaration Comment:
    • class name<comment> { ... } is equal to class name { ... }.
    • function name<comment>(...) { ... } is equal to function name(...) { ... }.
    • let name<comment> = ... is equal to let name = ....
      • Although we don't have this feature in TS, Flow, etc yet, but it's a possibility.

@msadeqhe
Copy link
Author

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 { (as separator).
    • The comment will end before ) (as separator) if it's enclosed within ().
    • The comment will end before ] (as separator) if it's enclosed within [].
    • The comment will end before } (as separator) 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.

@msadeqhe
Copy link
Author

msadeqhe commented Sep 21, 2023

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);

@trusktr
Copy link

trusktr commented Sep 22, 2023

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?

@msadeqhe
Copy link
Author

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).

@azder
Copy link

azder commented Sep 23, 2023

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

@msadeqhe
Copy link
Author

Thanks. I'll continue this subject (using regular comments instead of :-style comments) in #176.

@trusktr
Copy link

trusktr commented Sep 24, 2023

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 :()

@Lookwe69
Copy link

Lookwe69 commented Mar 27, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants