Defining dynamic comments on the fly

Description

Tagged template literals are commonly used by web components libraries. They can also include CSS code, HTML code and embedded expressions (a.k.a. substitutions).

The problem

ES sometimes abuses the string-tightness of the CSS and HTML comments when those exist within tagged template literals. This is done when comments, for some reason, include expressions and basktick (`).

Examples

Please, have a look at the following links:

Solution
Α new method has to be adopted which will instruct the ES parser on the fly to consider a string as ES comment, although it is not marked as a such one, and ignore it.

Note
I do not know if ES does explicitly or implicitly interfere with other languages (apart from CSS, HTML), but general speaking, developers may conditionally use such a technique to ensure that features of one language do not interfere with those of another one.

1 Like

That's probably not a workable solution. You'd have to somehow teach the ES parser the syntax of the language you're embedding in the template, so that it can find the end of the embedded comment.

With the exception of the unescaped backtick terminating the template early, which is a minor inconvenience easily fixed with a backslash; all the other examples you linked work exactly like they should.

html`<!-- commented ${this.value} must still appear in output -->`

If you really want to avoid having to escape embedded backticks, you'd need to change the template start/end tags to something that's unlikely to appear inside the embedded code.

For example, allow the template to start with an arbitrary number (3+) of consecutive quotes, and require the same amount to terminate:

css""" /* single " will not terminate the template */ """;

Unfortunately backticks cannot be extended this way, because foo```bar``` is currently valid syntax. But perhaps they could be escaped with a backslash and then work like that:

css\``` /* single ` will not terminate the template */ ```

Or take inspiration from Lua, where you can wrap multiline strings between [==[ and ]==] with an arbitrary number of = signs between the brackets:

css[===[
/* can put anything except === between two ]s here */
]===]

@lightmare

That's probably not a workable solution. You'd have to somehow teach the ES parser the syntax of the language you're embedding in the template, so that it can find the end of the embedded comment.

Both //... and /*...*/ statically declare an ES comment for which the parser is aware. I am not a programmer / developer but I believe that a built-in mechanism which may define either

  • new comment identifiers such as <!--...--> , /*...*/ and ${...} with exclusively local scope and instruct the parser to ignore the content between them, or
  • global status semantics which will make the parser to explicitly jump from one point to the other despite of the start and end of the string

could help.

For example, a built-in function such as comment(start, end) which internally declares the text between the two arguments as a comment is not something incredible. After that, comment("<!--", "-->") , comment("/*", "*/") and comment("${", "}") could be used in the content of the tagged template literals to track the concerned strings and "simulate" them as ES comments.

I am sure that people who knows the internals of ES will easily find how to implement the best generic solution.

... which is a minor inconvenience easily fixed with a backslash;

Apart from inconvenience is also a logical mistake. I assume that I write CSS code and use CSS comment. I expect to behave in the well-known standard CSS way. Backslash is not a fix at all; it's just a temporary workaround! Escaping backtick (`) is acceptable only in JS code and scope, not within HTML, CSS code parts even if they are included within JS code.

ES source code is first parsed, then executed. You cannot alter the parser's behaviour with a run-time function.

What you want has nothing to do with comments, actually. You're asking for backticks to be treated literally in a chunk of code, instead of terminating the template as they do now. I understand that. But that's not exclusive to comments, it's a problem outside comments, too:

html`<div>can I have a backtick ` here? no.</div>`

A proven technique for embedding arbitrary text in a different language is with custom start/end tags.

  • Shell: <<"TAG" \n anytext \n TAG
  • PostgreSQL: $TAG$ anytext $TAG$
  • C++: R"TAG( anytext )TAG"
  • Lua: [====[ anytext ]====]
  • Markdown: 3+ backticks on both ends
1 Like

In this related proposal GitHub - tc39/proposal-string-dedent: TC39 Proposal for multi-backtick templates with automatic margin stripping single backticks don’t need to be escaped, so could help a little here.

For the inner language comments issue, this is perhaps better solved by tools/editors than at runtime. When commenting out a section within a template string, the editor could have the capability to also comment out the expressions:

html`<p>${v}</p>`

Select text and run the comment command:

html`<!--<p>${''/*v*/}</p>-->`

@lightmare

ES source code is first parsed, then executed. You cannot alter the parser's behaviour with a run-time function.

In that case, not only backtick, but also the ("invalid") embedded expressions will cause problem when exist within comments because they are unconditionally parsed.

Then, I think that the adoption of global (highest priority) comment semantics (eg //* ...*/, consider //* as a convenient extension of already existing // or /*) might help. These will explicitly de-activate/isolate the content between them while parsing regardless if there are within pure JS, CSS, or HTML code.

What you want has nothing to do with comments, actually.

What I really want is to be able to safely de-activate/isolate, for the purpose of testing, any part (large or small) of the code following the standard ways WITHOUT tricks! I have a (mixed) code consisting of JS, CSS and HTML semantics which JS does not "respect" the CSS and HTML semantics of comment which is the only way to de-activate/isolate code.

At the same time, within CSS and HTML tagged template literals, JS comment semantics // cannot be used. Also, JS comment semantics /* ... */ can not be used within HTML tagged template literals.

But that's not exclusive to comments, it's a problem outside comments, too:

html`<div>can I have a backtick ` here? no.</div>`

No, it is not. There is a convention which says html starts and ends with backticks (`) which is respected. But <!-- ... --> is official part of HTML which permits the use of backticks (`) inside. Logically, it should be treated as a sealed container, but unfortunately not.

A proven technique for embedding arbitrary text in a different language is with custom start/end tags.

It makes more sense, but does this solve also the problem with embedded expressions which are first parsed?

It may make more sense and be easier implemented something like:

html`<!--//* <p>${v}<p> */-->`

or

css`/*//* host:[...] /*/*`

where //* ... */ is a high priority global ES comment semantics which guides ES parser to avoid parsing the expression ${v}. And later, in turn, html parser avoids parsing the comment.

In fact, existing ES "comment" action range should be globally extended to include also tagged template literals. Is it difficult to be done? There are global variables; why not global "comments"?

That is already a valid JavaScript string. So we can't change the behavior of it, as it could break existing code.

@aclaymore: Sorry, I do not get the point! Do you mean that also the following (with /*...*/, instead of //*...*/)

html`<!--/* <p>${v}<p> */-->`

really guides the ES parser at first to avoid parsing of ${v}? So, the above code should not create problem at all (ie it's fully legal), shouldn't it?

Those are not a problem at all, as I tried to illustrate earlier.

html`<!--
  perfectly valid way to output comment
  containing meaningful ${this.dynamic} information
-->`

You cannot skip evaluating this.dynamic in this snippet, it must be included in the output.

There's no way without tricks, because the parser doesn't interpret the template text (apart from backslash escapes). But there's a fairly concise and reliable trick available:

html`

active part

${'' && `
deactivated part
`}

active part
`

(the other variation of this trick mentioned before — ${''/* stuff */} — would break with CSS comments in stuff)

1 Like

You cannot skip evaluating this.dynamic in this snippet, it must be included in the output.

But when it is not inside a tagged template literals, I can skip evaluation using comment semantics! I can de-activate/isolate any part of the code.

There's no way without tricks

For god sake, after a few years from now we shall go to the moon for vacations, and you tell me there is no way because the parser doesn't interpret the template text (apart from backslash escapes). Do we rule the parser, or parser does rule us?

Does parser interpret the ES comments outside the tagged template literals? Yes, it does.
Does parser interpret the ES expressions inside the tagged template literals? Yes, it does.
Does parser interpret the ES comments inside the tagged template literals? No, it does not! And then, make it to do that because this feature is required any more.

But there's a fairly concise and reliable trick available:

So, I will have to modify 10-20 expressions and bring them back when I want to make tests. Sorry, but this is not the human wise way.

ES parser should parse ES, not every language in existence.

Let's take your suggestion with //* ... */ then — which would not get past TC39 committee because it would change meaning of existing valid code — but for argument's sake let's use it:

html`
<!-- deactivate the following chunk: //*

<code>
/* an example of CSS rule */
p { margin: 1em }
</code>

<code>
# an example of Python code that prints C code
print("""
/* auto-generated function */
int foo() {
  return 1;
}
""");
</code>

*/
end of deactivated chunk -->
`

For the above to work as you expect, the ES parser has to magically figure out that part of the embedded HTML is actually CSS, correctly parse the CSS code without terminating the deactivated chunk at the first */, then magically figure out the following part is Python code, correctly parse it and ignore the */ in the string.

See how ridiculous that would be?

How is that different from wrapping 10-20 expressions in <!-- and -->? It's only 3 more characters on the left end, and 1 less character on the right end.

edit: actually if we consider your suggestion:

html`<!--//* stuff */-->`

that pretty much the same amount of clutter as:

html`${''&&` stuff `}`

ES parser should parse ES, not every language in existence.

For God sake, I do not ask ES parses every language. But, I do find very reasonable ES parser to evaluate ES comment semantics even if there are within tagged template literals which are its own, ES, feature.

Let's take your suggestion with //* ... */ then

For God sake, I tried to codify an idea; it is not a suggestion; it is not a solution.

Do not get stuck how it will be done. First, accept that there is a real practical problem for which a solution has to be found. This is the most important. The implementation comes after that.

How is that different from wrapping 10-20 expressions in <!-- and -->? It's only 3 more characters on the left end, and 1 less character on the right end.

I will wrap 20 lines together. Not one by one the expressions! This is the practical problem.

One more time, first we have to accept that something does not work in the correct way. This is the most difficult part. To make people to accept that there is a problem. After that, everything is simple and easy!

IMHO, the following code should work as reasonably expected:

html`<!--/* <p>${v}</p><p>Batcktick(`)</p> */-->`
  • ES parser does not complain at all because comment /* ... */ is considered as ES comment, and
  • html parser does not complain at all because comment \<!-- ... --> is considered as HTML comment. Same for CSS parser.

Do you agree that this is the ideal behaviour?

Of course! Nobody suggested you wrap one by one.

html`
${this} whole ${thing} is ${active}
`
html`${''&&`
${this} whole ${thing} is ${deactivated}
`}`

No. Nobody expects the Spanish Inquisition /* ... */ to be magically skipped inside string templates.

A solution must allow the developer to choose custom start/end tags that don't clash with whatever language they're embedding. Otherwise, if you define fixed start/end tags, no matter what they are, sooner or later someone will face the exact same problem you now have with unescaped backtick.

1 Like

Of course! Nobody suggested you wrap one by one.

Sorry! I did not imagine that it can work like that!

No. Nobody expects the Spanish Inquisition

Don't be rude! We do not get involve ethnicity, color, gender, etc. Try to keep a good level to the discussion. Hot, yes, tempered yes, with personal hints, yeeeees! But without generalization!

/* ... */ to be magically skipped inside string templates

Why not? This semantics are occupied for a specific reason; to declare a must-not-parsed string! They are used in ES language in a JS file. This is exactly were I want to used them.

Otherwise, if you define fixed start/end tags, no matter what they are, sooner or later someone will face the exact same problem you now have with unescaped backtick.

Unstuck from backtick (`). It's not the problem. It's just the cause!

The fact is that ES parser does not interpret the following in a human logical manner.

js-type-file.js

html`
  <!--
    /*
      <p>${v}</p>
      <p>Backtick(`)</p>
    */
  -->
`

because it fails to parse /* ... */ as its own defined comment so as to ignore it!

So, the clue is that:

  • dynamic comments cannot be defined and exist because the parser's behaviour cannot be altered with a run-time function
  • different choice of custom start/end tags can be used to solve the problem,
  • ES parser fails to recognize and, hence, parse its own comments within tagged template literals (also ES feature), and
  • from a humorous point of view, ES parser rules us and not the opposite!

Note

  • I will open a new issue about the inline comment interpretation.
  • Many thanks to all for your time and hot-tempered discussion.

I could have done a better job of explaining my earlier comment.

In current JavaScript:

console.log(`<!--/* <p>${123}<p> */-->`);  // '<\!--/* <p>123<p> */-->'
console.log(`<!--//* <p>${123}<p> */-->`); // '<\!--//* <p>123<p> */-->'

So we can't change the language to mean that //* is now the start of a special comment that is treated differently by the parser. As that would change the behavior of existing code on the web.

@aclaymore

//* ... */ was just an effort to codify an idea not to suggest a solution.