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

Bug: Apostrophes are destroyed inside of angulars interpolation #9

Closed
Ulrikop opened this issue Jul 7, 2019 · 27 comments · Fixed by #10
Closed

Bug: Apostrophes are destroyed inside of angulars interpolation #9

Ulrikop opened this issue Jul 7, 2019 · 27 comments · Fixed by #10
Assignees
Labels
framework: Angular Related to the framework Angular help wanted We are looking for community help type: bug Functionality that does not work as intended/expected

Comments

@Ulrikop
Copy link

Ulrikop commented Jul 7, 2019

Used configuration:

{
  "printWidth": 120,
  "tabWidth": 2,
  "useTabs": false,
  "singleQuote": false,
  "bracketSpacing": true
}

Input file:

.without-apostrophe(foo="{{'hello world' | uppercase}}")
  | {{'hello world' | uppercase}}

.with-apostrophe(foo="{{'it\'s me' | uppercase}}")
  | {{"it's me" | uppercase}}

Output file:

.without-apostrophe(foo="{{'hello world' | uppercase}}")
  | {{'hello world' | uppercase}}

.with-apostrophe(foo="{{'it's me' | uppercase}}")
  | {{ 'it"s me' | uppercase }}

Expected behavior:
It should not change a text which will be displayed (it's me was changed to it"s me).

Additional the handling of spaces after {{ and before }} should be consistently. The added spaces at the second {{ ... }} is the expected behavior.

@Shinigami92 Shinigami92 self-assigned this Jul 7, 2019
@Shinigami92 Shinigami92 added the type: bug Functionality that does not work as intended/expected label Jul 7, 2019
@Shinigami92 Shinigami92 added framework: Angular Related to the framework Angular help wanted We are looking for community help labels Jul 7, 2019
@Shinigami92
Copy link
Member

Shinigami92 commented Jul 7, 2019

I fixed it in attributes, but I did not find a way to handle that in text 🤔

The thing is that the correct format needs to be

.with-apostrophe(foo="{{ 'it\'s me' | uppercase }}")

and not

.with-apostrophe(foo='{{ "it's me" | uppercase }}')
                            ~ The end of the string reached with no closing bracket ) found

Therefore, we cant quote the text in a way without to escape the single quote
Needs this

.with-apostrophe(foo='{{ "it\'s me" | uppercase }}')

So I have written code to procude

.with-apostrophe(foo="{{ 'it\'s me' | uppercase }}")

and it is working.
BUT it brokes another test

// Quotes › should format single to double quotes
// and
// Quotes › should use double quotes by default

- Expected
+ Received

- .grey--text {{ $t('my-language-key.title') }}
+ .grey--text {{ $t("my-language-key.title") }}

I don't know how to pass them

Hope someone can help me here: https://github.com/prettier/plugin-pug/blob/0817c64/src/index.ts#L391-L407

@Shinigami92
Copy link
Member

@j-f1 Do you know if we have any regex/JS experts in @prettier/core who can help me here?

@azz
Copy link
Member

azz commented Jul 9, 2019

@lydell!

@lydell
Copy link
Member

lydell commented Jul 10, 2019

Hehe, yeah, I like regexp, but I'm not quite sure what you are trying to do here? People write Angular using Pug and therefore embed pieces of Angular template code inside attributes, and you're trying to do regex replacements on them (instead of parsing)?

@Shinigami92
Copy link
Member

@lydell Yea... something like that. One problem is that I cant use prettier to format js code in example mustache brackets because the prettier will use linebreaks and semicolons and stuff and breaks the formatting completely.

Therefore I want to handle quotes on this place:

plugin-pug/src/index.ts

Lines 401 to 410 in 8890d4c

code = code.replace(/['"]/g, (match: string, index: number, raw: string) => {
if (index !== 0 || index !== raw.length - 1) {
const prevChar = raw[index - 1];
const nextChar = raw[index + 1];
if (/[\S]/.test(prevChar) && /[\S ]/.test(nextChar)) {
return match;
}
}
return match === '"' ? "'" : '"';
});

But it has sideeffects and breaks my other tests in ./test/quotes/**

.grey--text {{ $t('my-language-key.title') }}

formats to:

.grey--text {{ $t("my-language-key.title") }}
//                ~                     ~

and I don't know why 🤔

See also Build-62 and click on Test

@lydell
Copy link
Member

lydell commented Jul 12, 2019

It's not possible to do this safely with regex. Consider this example:

`"${ `Jean ${ '"John"' } d'Arc`.toUpperCase() }"`

@Shinigami92
Copy link
Member

Besides this example hurts my brain, do you know another strategy?

I will build a test for you example 😁

@lydell
Copy link
Member

lydell commented Jul 12, 2019

I think you'll have to parse it as JS and then print is somehow. If it is JS. But btw, how can you know that the attribute contains Angular code?

@Shinigami92
Copy link
Member

Shinigami92 commented Jul 12, 2019

If text starts and ends with mustache brackets, I assume js code.
I cant use prettier format js because that produce newlines. I need everything in a single line.

Maybe I should check if there are different quotes in the string and if so then dont touch it?

Edit: also in most cases it is not directly valid js code because a prepending const x = is missing

@ikatyang
Copy link
Member

FYI, there is a __ng_interpolation parser, which is used for this purpose.

@Shinigami92
Copy link
Member

@ikatyang Thank you, you have definitely put me on the right track!
I didn't used __ng_interpolation directly, but I tried format with different parsers.

At first I tested format(code, { parser: 'angular' }).
That solves already some problems like whitespaces, but didn't produces the final wanted result.
It doesn't change quotes.

So I tried format(code, { parser: 'vue' })... same as 'angular' ^^

Then I tried format(code), so no parser at all? No it takes 'babel' and prints a warning in console...
BUT it produces greate results 🎉

I needed to remove the last : semi-colon and also the eof. And I needed to use an internal printWidth of a large number so it don't wraps... Over 9000 😁 seams legit...

I think I can merge this to release a next alpha, but keep in mind that it will break code that uses more then one statement in a text. But in my opinion, something like this should be done on the Controller/Component side.

@j-f1
Copy link
Member

j-f1 commented Jul 14, 2019

Why didn’t you want to use the __ng_interpolation parser?

@Shinigami92
Copy link
Member

Shinigami92 commented Jul 14, 2019

Um ... I thought it's used internally
Besides, it works just fine at the moment

And I do not really know how to import the function __ng_interpolation into my TypeScript code

@j-f1
Copy link
Member

j-f1 commented Jul 14, 2019

You should be able to specify parse: '__ng_interpolation' in your call to format().

@Shinigami92
Copy link
Member

Shinigami92 commented Jul 14, 2019

Ohhhhhh, this thing IS the parser .-.
💡

Mind Blow

@Shinigami92
Copy link
Member

Shinigami92 commented Jul 14, 2019

Ah, sadly it throws an error:
SyntaxError: Unexpected token Lexer Error: Unexpected character [`]

code = format(code, { parser: '__ng_interpolation' as any, singleQuote: !singleQuote, printWidth: 9000 });

So nevermind 🤷‍♂️


Details:

SyntaxError: Unexpected token Lexer Error: Unexpected character [`]

      at o (node_modules/prettier/parser-angular.js:1:43202)
      at t.parseNgInterpolation (node_modules/prettier/parser-angular.js:1:44326)     
      at n (node_modules/prettier/parser-angular.js:1:54823)
      at Object.t.parseInterpolation (node_modules/prettier/parser-angular.js:1:55161)
      at node_modules/prettier/parser-angular.js:1:55798
      at Object.parse (node_modules/prettier/parser-angular.js:1:55454)
      at Object.parse$2 [as parse] (node_modules/prettier/index.js:10629:19)
      at coreFormat (node_modules/prettier/index.js:13888:23)
      at format (node_modules/prettier/index.js:14146:73)
      at formatWithCursor (node_modules/prettier/index.js:14162:12)

@j-f1
Copy link
Member

j-f1 commented Jul 15, 2019

What’s in the code variable when the error is thrown?

@Shinigami92
Copy link
Member

`"${ `Jean ${ '"John"' } d'Arc`.toUpperCase() }"`

@j-f1
Copy link
Member

j-f1 commented Jul 15, 2019

I think you’re supposed to pass in the whole interpolation, i.e. {{ `"${ `Jean ${ '"John"' } d'Arc`.toUpperCase() }"` }}.

@Shinigami92
Copy link
Member

No it's not working 😕

code = format(
  `{{ ${`"${`Jean ${'"John"'} d'Arc`.toUpperCase()}"`} }}`,
  {
    parser: '__ng_interpolation' as any,
    singleQuote: !singleQuote,
    printWidth: 9000
  }
);
SyntaxError: Unexpected token {, expected identifier, keyword, or string

      at o (node_modules/prettier/parser-angular.js:1:43202)
      at t.parseNgInterpolation (node_modules/prettier/parser-angular.js:1:44326)
      at n (node_modules/prettier/parser-angular.js:1:54823)
      at Object.t.parseInterpolation (node_modules/prettier/parser-angular.js:1:55161)
      at node_modules/prettier/parser-angular.js:1:55798
      at Object.parse (node_modules/prettier/parser-angular.js:1:55454)
      at Object.parse$2 [as parse] (node_modules/prettier/index.js:10629:19)
      at coreFormat (node_modules/prettier/index.js:13888:23)
      at format (node_modules/prettier/index.js:14146:73)
      at formatWithCursor (node_modules/prettier/index.js:14162:12)

@j-f1
Copy link
Member

j-f1 commented Jul 15, 2019

It looks like the Angular parser doesn’t support template strings :(

Prettier 1.18.2
Playground link

--parser __ng_interpolation

Input:

foo(`hello`) 

Output:

SyntaxError: Unexpected token Lexer Error: Unexpected character [`]
    at o (https://prettier.io/lib/parser-angular.js:1:43175)
    at t.parseNgInterpolation (https://prettier.io/lib/parser-angular.js:1:44299)
    at n (https://prettier.io/lib/parser-angular.js:1:54796)
    at Object.t.parseInterpolation (https://prettier.io/lib/parser-angular.js:1:55134)
    at https://prettier.io/lib/parser-angular.js:1:55771
    at Object.parse (https://prettier.io/lib/parser-angular.js:1:55427)
    at Object.parse$2 [as parse] (https://prettier.io/lib/standalone.js:12947:19)
    at coreFormat (https://prettier.io/lib/standalone.js:16216:23)
    at format (https://prettier.io/lib/standalone.js:16476:73)
    at formatWithCursor (https://prettier.io/lib/standalone.js:16492:12)

@Shinigami92
Copy link
Member

Yea, checked it at stackblitz.com.
Angular doesn't support template strings in html 😌
But Vue does!

@ikatyang
Copy link
Member

Their grammars are different, you should use __vue_expression if you'd like to parse vue's {{expression}}.

@Shinigami92
Copy link
Member

@ikatyang Nice to know that there are so many internal things I didn't noticed ^^'
I only see the typescript .d.ts definitions and use them as docu and learn how to use prettier

So I will try that.
But it seams that __vue_expression use the babel-parser.

I also don't know how to assume whether it is using vue or angular.
I could introduce an option for framework, but I think that is not really smart 😕

Do you have an example for testing? So a vue and an anguar snippet that are formatted differently?

@ikatyang
Copy link
Member

But it seams that __vue_expression use the babel-parser.

More precisely, the babel expression parser. Though they use the same parser, their |s are printed differently, | is printed as a filter in vue but a bitwise OR in js.

I also don't know how to assume whether it is using vue or angular.
I could introduce an option for framework, but I think that is not really smart 😕
Do you have an example for testing? So a vue and an anguar snippet that are formatted differently?

IIRC, they are formatted in the same way, the only difference between them is that they support different grammars. I guess you could try to guess which parser to use based on attribute names next to it (vue: v-*, :*, @*, etc.; angular: [*], (*), [(*)], etc.), and try another parser if the parsing failed, just like what typescript parser did.

@Shinigami92
Copy link
Member

Uh... That would work for attributes. But currently I'm working on text,
so there is no attribute key like v-* and so on.

I only have something like:

span {{ my.angular.value }} {{ someInputFromComponent | uppercase }}

@ikatyang
Copy link
Member

You can simply choose either parser to use, and use another one if the previous parsing failed. The attribute names are just hints for better guessing, it does not really matter if it's wrong since one of the two parsers must be the correct one and their formatting results are the same.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
framework: Angular Related to the framework Angular help wanted We are looking for community help type: bug Functionality that does not work as intended/expected
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants