-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Fixed #2219 - formatting of new Angular control flow syntax #2221
Fixed #2219 - formatting of new Angular control flow syntax #2221
Conversation
|
Hi again, |
90afe0a
to
7c96d48
Compare
The already existing tests are successfully run, I've yet to write some tests which have the new angular control flow. I'll do it early next week. |
1756232
to
08ecdd3
Compare
Hello, any updates on this pull request? More and more developers are migrating to Angular 17 and the new syntax is really counterproductive without correct indentation. I'm sure you guys have a lot of work, so take this as a gentle reminder 🥇 |
It will sure take some time until this formatting improvement is usable in VSCode. Until then, personally, I decided not to use the new syntax. While I've upgraded to Angular 17, I have postponed the migration of the control flow syntax. Good thing is that until then, the old syntax can be used. Also, if the review comes in, I'll fix the findings as soon as possible. |
That's what I did as well but the new control flow seems very exciting :-) I'll be patient, thanks |
Will this be merged anytime soon? If not I guess I can look at Prettier. |
js/src/html/tokenizer.js
Outdated
return token; | ||
} | ||
|
||
if (c === '@' && /[a-zA-Z0-9]/.test(this._input.peek(1))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOTE: This is on by default for HTML users. This has the possibility of causing significant breakage for some users.
We need to limit the chances of this triggering when not intentional.
This should look for a known list of control flow words.
https://angular.io/guide/control_flow seems to indicate the following list: @if
, @else
, @for
, @empty
, @switch
, @case
, @default
.
You probably should us the pattern facilities already present in the library. This section shows who CDATA
sections are checked for and read.
The cdata
pattern checks if the starting regex is present and then reads until it is finds the closing regex:
cdata: pattern_reader.starting_with(/<!\[CDATA\[/).until_after(/]]>/),
You don't need to read the whole control string using a Pattern but you can use to provide the opening guard. Something like:
angular_control_flow: pattern_reader.starting_with(/\@(if|else|for|empty|switch|case|default)[ ]*\(/).until(/\(/),
Keep the check for c === '@'
to improve performance, but then call read()
on that pattern. If it returns empty string, you have no match.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that there are other keywords: https://angular.io/guide/defer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ssougnez
Okay, I guess it's pretty obvious I'm obviously not an Angular user.
A more flexible pattern might be:
pattern_reader.starting_with(/\@[a-zA-z]+[ ]*/).until(/[({]/),
Still I'd rather lean toward a specific list of @-strings if possible.
As an alternative, I'd also be okay with having this handler be off by default but that has it's own problems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To answer this question, we should have a look to see if it's possible to create custom control flow directives. It is possible to create custom structural directives (which are the "old" way of doing control flow) so it's not impossible that it is (will be) possible for control flow as well.
Even though I understand that a white list is more secure and easy to test/maintain, this would break if (when) angular allow this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To answer this question, we should have a look to see if it's possible to create custom control flow directives. It is possible to create custom structural directives (which are the "old" way of doing control flow) so it's not impossible that it is (will be) possible for control flow as well.
Even though I understand that a white list is more secure and easy to test/maintain, this would break if (when) angular allow this.
That was my exact thought when I've written the code.
NOTE: This is on by default for HTML users. This has the possibility of causing significant breakage for some users.
We need to limit the chances of this triggering when not intentional.
This should look for a known list of control flow words. https://angular.io/guide/control_flow seems to indicate the following list:
@if
,@else
,@for
,@empty
,@switch
,@case
,@default
.You probably should us the pattern facilities already present in the library. This section shows who
CDATA
sections are checked for and read.The
cdata
pattern checks if the starting regex is present and then reads until it is finds the closing regex:cdata: pattern_reader.starting_with(/<!\[CDATA\[/).until_after(/]]>/),
You don't need to read the whole control string using a Pattern but you can use to provide the opening guard. Something like:
angular_control_flow: pattern_reader.starting_with(/\@(if|else|for|empty|switch|case|default)[ ]*\(/).until(/\(/),
Keep the check for
c === '@'
to improve performance, but then callread()
on that pattern. If it returns empty string, you have no match.
Your main point is that this beautifier is used in every HTML file, even if it's not an Angular file. That's why I added a new templating option. If angular
is not set, then no control flow token will be set (so everything will remain as it was before this modification). If angular
is set, then everything which is written after an @
character, is treated as a control flow phrase from Angular's side.
So it makes no sense to only match for the reserved keywords, as for Angular, everything is treated as control flow from Angular's side (even if it is a non-existing one).
But let me look into your other suggestions, I'll try to respond today.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pattern_reader.starting_with(/\@[a-zA-z]+[ ]*/).until(/[({]/),
This solution marks only the @
character and the control flow's name, but not the whole expression until it's opening curly brace.
Example:
@if(condition) {
The problem with this is that the
@if(condition) {part would be treated as simple text.
If the opening curly brace would be written in a new line, this would mess up the indentation.
<!-- Unformatted -->
@if()
{
<div></div>
}
<!-- Formatted -->
@if()
{
<div></div>
}
Before commiting the solution I wrote, I tried to write a pattern, which selects the whole control flow opening part.
@if(condition) {
If we see just this simple example, we could say "read everything untilAfter an {
character". That works, but not in all cases.
The problem with this is that, taking the @if() {
for example, there can be an unknown amount of curly braces and parentheses inside the condition. So, if we had @if({value: true} !== undefined) {
, then the pattern reader would only read until the first opening curly brace.
@if({value: true} !== undefined) {
This is, unfortunately, definitely not correct. And to select the whole expression via REGEX does not seem possible (at least not with the ES version this library uses). That's why I implemented the selection in pure JS, counting the pairs of opening and closing parentheses, until they're the same amount and we get to the opening curly brace character.
While selecting the whole expression as one token is not the best and most beautiful solution, breaking down this token to multiple ones (even, currently not existing tokens inside parentheses) would make this PR too big and would require a serious amount of time. By selecting the whole expression as one token, at least the indentation is solved which seems to be the biggest issue, and later we can work on some tweaks to have more formatting possibilities (e.g. opening curly brace in a new line, formatting the condition of the @if
and so on)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only a minor change request
The problem with this is that, taking the @if() { for example, there can be an unknown amount of curly braces and parentheses inside the condition. So, if we had @if({value: true} !== undefined) {, then the pattern reader would only read until the first opening curly brace.
I'm sorry I should have explained this better. I'm not asking you to read the whole token using one call to read()
. I'm asking that you use the first call to read()
to make your matching check stronger.
I think the guard pattern we want is pattern_reader.matching(/\@[a-zA-z]+[\n\t ]*[({]/)
. This will only read if it finds this pattern, otherwise it returns blank and does not advance the input scanner. If it returns a matching string, then you can continue with the rest of your code with only minor changes.
if (c === '@' && /[a-zA-Z0-9]/.test(this._input.peek(1))) { | |
// add to the __patterns initialization around line 72: | |
// this.__patterns.angular_at_string_start = pattern_reader.matching(/\@[a-zA-z]+[\n\t ]*[({]/), | |
if (c === '@') { | |
resulting_string = this.__patterns.angular_at_string_start.read(); | |
if (resulting_string === '') { | |
return token; // null, not a control flow at string | |
} | |
var opening_parentheses_count = resulting_string.endsWith('(') ? 1 : 0; | |
var closing_parentheses_count = 0; | |
// [ ... the rest of your implementation ...] |
On by default for HTML
Your main point is that this beautifier is used in every HTML file, even if it's not an Angular file. That's why I added a new templating option. If
angular
is not set, then no control flow token will be set (so everything will remain as it was before this modification).
It looks like this is what your code does, but it contradicts the behavior described for the option:
--templating [...] auto = none in JavaScript, all in html
^^^^^^^^^^^
What are our other options?
-
Add a new option called
indent-templating
. It would be a selection list liketemplating
but would default tonone
and have only two valid valueshandlebars
andangular
. It would deprecate the currentindent_handlebars
setting - if that is set it addshandlebars
toindent-templating
. -
Do a major version bump and make some long needed changes including changing
templating
to default tonone
. Then we wouldn't have to worry about this causing unexpected effects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry I should have explained this better. I'm not asking you to read the whole token using one call to
read()
. I'm asking that you use the first call toread()
to make your matching check stronger.I think the guard pattern we want is
pattern_reader.matching(/\@[a-zA-z]+[\n\t ]*[({]/)
. This will only read if it finds this pattern, otherwise it returns blank and does not advance the input scanner. If it returns a matching string, then you can continue with the rest of your code with only minor changes.
I see, I didn't think of writing a pattern which doesn't match the whole token, but only a part of it. I'll do it asap. Thanks for the explanation!
It looks like this is what your code does, but it contradicts the behavior described for the option:
--templating [...] auto = none in JavaScript, all in html ^^^^^^^^^^^
Yes, I saw that this option will be on by default. I just haven't thought that this is the scope of this PR. But I understand that why it could cause conflicts and frustrate the users.
I can agree with both of your recommendations. The first sound like a quick fix, but it doesn't sound like a real solution, just a bandage to the current implementation. The latter is what I'd fancy, but it seems like that would require a bigger modification.
A follow-up task/issue should be to make those long needed changes to the templating
option, then we could tokenize better. I'd be happy to do it in the future, but I cannot make any promises, that's why implementing the safer option now sounds a bit better.
What really bothers me in the current setup is that the @
, {
, }
characters became reserved characters in angular (so you cannot use it in any other context, but only for control flow blocks), but they are normal characters in a non-angular scenario...
@@ -3839,6 +3839,304 @@ exports.test_data = { | |||
'</span>' | |||
] | |||
}] | |||
}, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are a solid set of tests. We need a couple more:
- do not indent an unrecognized
@-string
-@foo (true) {
should be treated as regular content. - do not indent invalid syntax -
@if [] {
is missing the required open parenthesis, treat as regular content. - do not match escaped
@
symbol - I don't know how angular escapes, but ... - do not match if
@-string
is not preceded by}
or whitespace (including newline)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- do not match if
@-string
is not preceded by}
or whitespace (including newline)
For this, could you please provide an example of what you mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is valid angular control flow and should match and be indented:
FYI: I want you to know that @if (a > b) {
{{a}} is greater than {{b}}.
}@else{
{{a}} is less than or equal to {{b}}.
}
Or to say it another way, {{b}} @if (a > b) {is less than}@else{is greater than or equal to} {{a}}.
There's a bunch of missing spaces, but the @-strings are always preceded by a space (tab or newline would also be okay), or a }
.
The following might be valid control flow but are too close to other strings you might see in non-angular scenarios, so do not match or indent:
My email is [email protected] (only for work).
bitwiseman@if (true) { }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My email is [email protected] (only for work). bitwiseman@if (true) { }
For this, the code is not selecting any control flow token, as the TK_TEXT
token selector is not being cut when a @
character is found, only at spaces.
Or to say it another way, {{b}} @if (a > b) {is less than}@else{is greater than or equal to} {{a}}.
For this, I've found a bug in my code: the @if (a > b) {
is registered as a control flow opening token, but the }
character is not as a closing one (because it is a part of the than}@else{is
TK_TEXT token.
When we implement the improvement of the templating option, we can improve the selectors and register the tokens in a better way, but for now I'm planning to just check for a }
character : if currently a control flow is opened, it's a control flow closing character. Any other way: it's just text. I'm doing it today-tomorrow also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bitwiseman I tried a lot of different approaches, but because the special characters ({
, }
, @
) can have a lot of meanings in different templating languages (@
may be just a character, while it's a reserved character in Angular; {
and }
may be block opening characters, handlebar characters or just simple characters), it's simply just not possible to make a 100% percent match for those characters, especially if they are not preceded by a whitespace character in the current implementation.
Example:
We'd like to match this as an angular control flow syntax: {{b}} @if (a > b) {is less than}@else{is greater than or equal to} {{a}}
.
But we'd like to not match the following as an angular control flow syntax: bitwiseman@if (true) { }
.
Unfortunately, by code I cannot differentiate the two cases if it is unknown what templating language we are using. A really good match could be achieved if we could aim only for the specific templating language, hope we can do it one day, as you suggested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a high quality PR! good tests, well implemented.
A few changes requested, mostly in the interest of avoiding breaking other users.
So, I've written a lot of stuff here, but to summarize: |
I've come up with a solution for this: if we have a control flow syntax open token, then we know that the Just one sidenote to your idea, saying that we should change the |
Thanks for all the hard work. |
js/src/html/tokenizer.js
Fixed
single_quote: templatable_reader.until_after(/'/), | ||
double_quote: templatable_reader.until_after(/"/), | ||
attribute: templatable_reader.until(/[\n\r\t =>]|\/>/), | ||
element_name: templatable_reader.until(/[\n\r\t >\/]/), | ||
|
||
angular_control_flow_start: pattern_reader.matching(/\@([a-zA-z]+[\n\t ]*)+[({]/), |
Check warning
Code scanning / CodeQL
Overly permissive regular expression range Medium
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gergely-gyorgy-both
the problem looks like you wrote [a-zA-z]
(lowercase second z
) and what you wanted was [a-zA-Z]
(uppercase second z
). Otherwise, ths was fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it was not selecting every case anyways :)
…en if indent_handlebars is true
4014fd6
to
8900b86
Compare
@gergely-gyorgy-both I do have one request: if you can, try to avoid force pushing. I do not care about a clean commit history inside a PR, it is much more important to be able to backtrack to a previous commit and see the progression of changes. If I can see the whole commit history, I have a better chance of being able to help. Thanks again for all your hard work on this. |
Sorry for the force pushing, I'll avoid it in the future. I'm curious what you will come up with. Sorry for pushing commits which result in a failing pipeline. Anyway, the code now works, doesn't affect any non-angular users. I don't feel burnt out, it just took me time until I could write a code that actually works. Interesting that even Angular's own VSCode plugin has problems with this syntax. |
I couldn't stop thinking why the build fails, because I didn't modify anything except the html beautifier, the tokenizer and the html test file since the last successful build. It seems like that for some reason, when the |
It's the build action for the project itself that's broken down, unrelated to this PR. Same error in all branches if I look at the github "Actions" tab. |
Doh. It looks like anew version of npm broke things. 😭 Fixed now in |
Hi team, Many thanks for your hard work on this pull request. I think the entire Angular ecosystem is waiting for this awesome work to be merged. Does anyone know what the process or timeline would be in getting this into vscode asap. Regards, |
@bitwiseman , could you provide some estimation for your review please? |
Is this happening? |
Limiting this to a smaller set.
@liesahead @NachmanBerkowitz @thaoula @antur84 @gergely-gyorgy-both |
@bitwiseman , thanks for finding the time to review this. I understand that it is a risky one (as it targets specific framework), so understandable it takes much time. And thanks @gergely-gyorgy-both for doing so much work on this. |
First of all, thanks for re-reviewing this PR. While Again, thank you for your efforts in reviewing this PR and helping me. |
Whoa, this is finally happening? :D |
Was it forgotten again? 😿 |
It's not up to me, and I'm pretty sure that @bitwiseman is very busy. If there's anything to do, I'll try to respond quickly, but we should be patient. |
I understand both of these points, just keeping this thread alive so this (piece of cake) work wouldn't be abandoned. |
Yeah, I meant no pressure, sorry. This is really something that will ease my mind while "angularing" on vs code. 🕺🏼 |
Sorry for the delay folks. |
Hoorayyyyyyyyy Thanks a lot :-) |
Thank you. Is there any config necessary to get it to work? |
* Format index.html * Fix package.json files config * Ignore Pythong .eggs directory * update strings for move from beautify-web to beautifier org * Bump actions/setup-python from 4 to 5 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](actions/setup-python@v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> * Bump github/codeql-action from 2 to 3 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> * Update codemirror and other web page depenedencies (#2234) * Fixed #2219 - formatting of new Angular control flow syntax (#2221) * Fixed #2219 - formatting of new Angular control flow syntax * Add 'angular' templating option; use it for html beautifier control flow syntax * Add more precise selection for angular control flow close tag * Print angular control flow tokens with basic formatting * Add tests for fixing issue #2219 * Change angular control flow selection to do via pattern * Fix selecting control flow closing brace if it is not preceded by whitespace * Fix regex for control flow start pattern; only select control flow open if indent_handlebars is true * Changing angular at-string detection regex Limiting this to a smaller set. --------- Co-authored-by: Liam Newman <[email protected]> * Update Changelog * Bump version numbers for 1.15.0 * Release: 1.15.0 --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: Liam Newman <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: gergely-gyorgy-both <[email protected]> Co-authored-by: GitHub Action <[email protected]>
Description
main
)Fixes Issue: #2219
Before Merge Checklist
These items can be completed after PR is created.
(Check any items that are not applicable (NA) for this PR)