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
Handle shorthands in attributes correctly #33
Conversation
The main reason why I created this PR is to get feedback about my direction. |
@@ -57,8 +67,8 @@ function getAttributes( | |||
|
|||
const expr = parseExpression(val === true ? 'true' : val, context); | |||
|
|||
if (!mustEscape && (!t.isStringLiteral(expr) || /(\<\>\&)/.test(val))) { |
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.
Why are you removing the (!t.isStringLiteral(expr) || /(\<\>\&)/.test(val))
?
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.
@ForbesLindesay because it looks impossible to achieve with basic tests. Maybe we can hack it somehow... Otherwise val
starts and ends with "
when it's a string.
And filtering off <>&
looks not obvious for me, why not <script>...
or something like that? Probably we lost g
flag for regex?
Anyway I'm going to use different conditions. All use cases are captured in tests. Let's discuss something, if you want to add to those tests.
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.
The case this was capturing, although arguably not very important, is:
div(data-whatever!="some string that does not contain any special characters")
The idea being that it might make it slightly easier when converting from html based pug templates to react pug templates. Arguably that might not be super important though.
'use strict'; | ||
|
||
// To prevent warnings in console from react | ||
const Custom = () => null |
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.
You could also render an input(checked)
so we can actually see html output in the test.
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.
@ForbesLindesay I started using data-
attributes to keep html output. Let me know if you think that it does not appropriate.
Looks good. Ping me once you've fixed build & made the changes you want to make. |
@ForbesLindesay I rewrote my solution completely (left only test cases), need your attention one more time. Also it looks like I need your help with publishing new version of |
src/visitors/Tag.js
Outdated
throw new Error('Unescaped attributes are not supported in react-pug'); | ||
if (!mustEscape) { | ||
const isStringViaAliases: boolean = | ||
t.isStringLiteral(expr) && !['className', 'id'].includes(name); |
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.
For a list of two, I think I'd prefer just (name === 'className' || name === 'id')
- it takes less time to mentally parse.
src/visitors/Tag.js
Outdated
if (!mustEscape && (!t.isStringLiteral(expr) || /(\<\>\&)/.test(val))) { | ||
throw new Error('Unescaped attributes are not supported in react-pug'); | ||
if (!mustEscape) { | ||
const isStringViaAliases: boolean = |
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.
You shouldn't need to explicitly add the : boolean
here; flow should infer it from context.
src/visitors/Tag.js
Outdated
const isStringViaAliases: boolean = | ||
t.isStringLiteral(expr) && !['className', 'id'].includes(name); | ||
|
||
const isNotStringOrBoolean: boolean = |
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.
ditto
I don't have a strong opinion on whether we should support Please can you address the other small comments though. |
Get rid of .includes method for array of 2 items
@ForbesLindesay thank you for your review. I addressed all your comments and removed my workaround because there is new version of pug-lexer (that made it for us). The question about checking tags in scripts is still open. I would suggest to create a separate issue and discuss it there, because anyway it did not work as expected before this PR. Does it work for you? We can actually remember all use cases of unescaped values and think up how to handle them best... |
src/visitors/Tag.js
Outdated
t.isStringLiteral(expr) && name !== 'className' && name !== 'id'; | ||
|
||
const isNotStringOrBoolean = | ||
!t.isStringLiteral(expr) && !t.isBooleanLiteral(expr); |
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 just wondering if this lot can be clarified. It seems like the allowed case is just:
t.isBooleanLiteral(expr) ||
((name === 'className' || name === 'id') && t.isStringLiteral(expr))
It seems simpler written like that to me, rather than having multiple references to isStringLiteral
. How about we just use:
const isAllowedUnescapedAttribute = (
t.isBooleanLiteral(expr) ||
((name === 'className' || name === 'id') && t.isStringLiteral(expr))
);
if (!isAllowedUnescapedAttribute) {
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.
@ForbesLindesay I agree that my naming can confuse, but it was created with following reason in mind: all not obvious conditions should be commented. Variables are more convenient than just comments, so I used them.
isAllowedUnescapedAttribute
sounds not obvious because we don't use such naming through this plugin and pug (I mean it does not respect spirit of mustEscape
). It's better to keep shouldBeEscaped
, allowedToBeEscaped
.
Let me show how did I achieve my code:
When attribute is marked that we can skip its escaping, we should always show a message about unsupported feature. Two exceptions, where we should not show a message:
- if attribute is a string not from aliases (not from
className
andid
) - if attribute is not primitive (in pug it's everything besides string and booleans)
If I convert it to the code:
if (!mustEscape) {
const isStringNotFromAliases =
t.isStringLiteral(expr) && name !== 'className' && name !== 'id';
const isNotPrimitive =
!t.isStringLiteral(expr) && !t.isBooleanLiteral(expr);
if (isStringNotFromAliases || isNotPrimitive) {
throw context.error(
'INVALID_EXPRESSION',
'Unescaped attributes are not supported in react-pug',
);
}
}
This snippet is what I suggest to leave. But I don't mind to make it much more compact by refactoring javascript, not the logic. If this is the case, then could you suggest any concrete solution that I can use? Maybe you can help me with optimizing the logic?
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'd much rather see a whitelist of cases where it is ok to have unescaped attributes:
className
andid
if the value is a string literal- any attribute if the value is a boolean
As this is much easier for me to interpret. When I read your code, I have to mentally figure out which are the allowed cases. Since the allowed cases are so narrow, I never really feel like I need to figure out what the non-allowed cases are.
allowedToBeEscaped
doesn't make sense because everything can be escaped. mustEscape
is fine, but we already have that with a subtly different meaning. I think something like escapingNotRequired
or canSkipEscaping
makes more sense here.
I agree that variable names are generally preferable to comments, but it's even better if the code makes sense without needing to read the variable names. I think you've ended up with much more complex code here in an effort to give names to things.
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.
@ForbesLindesay you are right that understanding whitelist is much better here. But I still need your help with figuring out the right way. Does following code look better:
if (!mustEscape) {
const canSkipEscaping =
(name === 'className' || name === 'id') && t.isStringLiteral(expr);
if (!canSkipEscaping) {
throw context.error(
'INVALID_EXPRESSION',
'Unescaped attributes are not supported in react-pug',
);
}
}
I noticed that we should not check is the value boolean or not. We don't allow to write !=
constructions at all. Could you please take a look at tests in this PR and tell me is everything appropriate there?
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.
Yes, although you've dropped the BooleanLiteral
case there. Is that intentional?
I'm not sure if it worked for me or not, I haven't actually made use of that bit anyway. |
@ForbesLindesay hey, I pushed updates according to our discussion. Need your attention one more time.
Yes. Now all boolean attributes come to us with But the following example will throw an error and it's intentionally: div(name!=true) |
Thanks :) |
Thank you very much for your time and patience |
It adds supporting of shorthands in attributes. Initial issue #1 (Fails on boolean attributes).
Now following syntax won't throw an error:
But this one will throw:
Checklist:
context.error
instead ofnew Error
to keep well-described errorspug-lexer
and remove workaround to keepmustEscape
correct