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

Fixes React isCompatTag validator accepting leading dash character #7164

Merged
merged 1 commit into from
Jan 9, 2018

Conversation

claudiopro
Copy link
Contributor

@claudiopro claudiopro commented Jan 6, 2018

Q                       A
Fixed Issues? Fixes #7163
Patch: Bug Fix? Yes
Major: Breaking Change? No
Minor: New Feature? No
Tests Added + Pass? Yes + Yes
Documentation PR No
Any Dependency Changes? No
License MIT

Fixed regexp test in isCompatTag React validator allowing leading dash '-' characters.

Was:

/^[a-z]|-/.test(tagName)

Is:

// Must start with a lowercase ASCII letter
/^[a-z]/.test(tagName)

@nicolo-ribaudo
Copy link
Member

nicolo-ribaudo commented Jan 6, 2018

Quoting the react docs: [1]

When an element type starts with a lowercase letter, it refers to a built-in component like <div> or
<span> and results in a string 'div' or 'span' passed to React.createElement. Types that start with a capital letter like <Foo /> compile to React.createElement(Foo) and correspond to a component defined or imported in your JavaScript file.

Things like aA, a-b-c and a1 should all be threated like built-in components. So I think that the correct regular expression is just /^[a-z]/

This is also the reason why some tests are failing.

[1] https://reactjs.org/docs/jsx-in-depth.html#user-defined-components-must-be-capitalized

@hzoo hzoo assigned bvaughn and gaearon and unassigned bvaughn and gaearon Jan 6, 2018
@hzoo hzoo requested review from bvaughn and gaearon January 6, 2018 15:07
Copy link
Contributor

@bvaughn bvaughn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some thoughts?

assert(t.react.isCompatTag("-div") === false);
// assert(t.react.isCompatTag("-span") === false);
// assert(t.react.isCompatTag("-a") === false);
// assert(t.react.isCompatTag("-input") === false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these commented out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, my mistake! I forgot to uncomment before committing 😅

// assert(t.react.isCompatTag("-a") === false);
// assert(t.react.isCompatTag("-input") === false);
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test for trailing dash might be nice. That's a use case that fails currently (but would be fixed by this PR) I think.

Copy link
Contributor Author

@claudiopro claudiopro Jan 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking the time to review on a Saturday @bvaughn 😄 Let me update the PR.

Let me ask this though as I'm not clear if we're on the same page: trailing dashes are legal for custom elements, i.e. <input-/> is a legal custom element name, or am I misreading the spec?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When @hzoo tags me, I come running. (In case there's Mario Kart.)

@@ -1,4 +1,5 @@
// @flow
export default function isCompatTag(tagName?: string): boolean {
return !!tagName && /^[a-z]|-/.test(tagName);
// Must start with 'a'-'z', can have a dash '-', but must finish with 'a'-'z'
return !!tagName && /^[a-z]+(-?[a-z]+)?$/.test(tagName);
Copy link
Contributor

@bvaughn bvaughn Jan 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern would reject custom element names with more than one hyphen (eg my-custom-tag).

I think this would work though?
/^[^-]([\-a-z]*[^-])*$/

(['a', 'ab', 'abc', 'a-b', 'a-b-c'])
  .map(tag => /^[^-]([\-a-z]*[^-])*$/.test(tag))
  .join(',');
// true,true,true,true,true

(['-a', 'a-', '-', '-a-'])
  .map(tag => /^[^-]([\-a-z]*[^-])*$/.test(tag))
  .join(',');
// false,false,false,false

Edit: This would fail "a--b" though. Could write a simpler regex if you only used it for strings with 3+ characters. 1-2 character strings could be validated with a simple /[a-z]+/ check. Not sure how bulletproof this regex needs to be.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, glancing at the spec it seems like a much broader range of characters than just a-z are allowed for custom elements (eg <emotion-😍>) 😆 Not sure if we want to try to support them all. Probably not?

Copy link
Contributor Author

@claudiopro claudiopro Jan 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep the custom elements spec says:

PotentialCustomElementName ::=
[a-z] (PCENChar)* '-' (PCENChar)*

PCENChar ::=
"-" | "." | [0-9] | "_" | [a-z] | #xB7 | [#xC0-#xD6] | [#xD8-#xF6] |
[#xF8-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x203F-#x2040] |
[#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] |
[#xFDF0-#xFFFD] | [#x10000-#xEFFFF]

Do we want to get this far?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have enough context to answer that. 😄 My guess would be "probably not".

@claudiopro
Copy link
Contributor Author

claudiopro commented Jan 6, 2018

I decided to back out changes to validate the element name and only fix the validator not to accept a leading dash, and add unit tests 😄 The regex can be further complicated if needs to be to match the spec.

Thanks @nicolo-ribaudo @bvaughn for the feedback!

@babel-bot
Copy link
Collaborator

babel-bot commented Jan 6, 2018

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/6482/

Copy link
Contributor

@bvaughn bvaughn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I accidentally approved earlier when I only meant to "comment" 😄 Clumsy.

I don't know that I have enough context to approve of this change with confidence. The regex I mentioned on a previous comment seems closer to ideal, even though not perfect, but maybe there are perf considerations?

I'll defer to someone who has more context. 😄

@claudiopro
Copy link
Contributor Author

@bvaughn +1 on perf concerns. Also, it seems there are other checks happening before the validator as malformed JSX tags like <-div> are rejected by the REPL: https://tinyurl.com/y7wfomel

@nicolo-ribaudo
Copy link
Member

nicolo-ribaudo commented Jan 6, 2018

They are rejected by Babylon (the parser). Anyway, I'd still accept this PR if it removes the - from the test for the first character.

@bvaughn
Copy link
Contributor

bvaughn commented Jan 6, 2018

Also, it seems there are other checks happening before the validator as malformed JSX tags like <-div> are rejected by the REPL: https://tinyurl.com/y7wfomel

Agreed. Leading "-" fails in master now. Trailing "-" doesn't (but still won't if this change lands).

@hzoo
Copy link
Member

hzoo commented Jan 6, 2018

Yeah I didn't think this change was necessary and was never reported before (forgot to mention), just wanted to ping someone on the react team for review

Ya if it errors in the parser already, I think

if (isIdentifierStart(code)) {
then this check is more for babel to pick a string or identifier, new tests are welcome thou!

@bvaughn
Copy link
Contributor

bvaughn commented Jan 6, 2018

react-domwill fail at runtime ifownerDocument.createElement(type)` fails, so I think it's kind of up to Babel how it wants to do compile-time tag name verification.

IMO it's nice if JSX compilation fails earlier for things that are obviously wrong (like leading or trailing slashes) because it's a better developer experience.

Edit: Actually, it looks like trailing slashes are accepted, so clearly I don't know much about this topic:

document.createElement('a-')
// <a->​</a->​

@bvaughn
Copy link
Contributor

bvaughn commented Jan 6, 2018

What about using /^[^-](\-?[a-z]+)*-?$/?

function test(tag) {
  if (/^[^-](\-?[a-z]+)*-?$/.test(tag)) {
    console.log(`creating tag "${tag}"`);
    document.createElement(tag);
  } else {
    console.log(`skipping tag "${tag}"`);
  }
}

// valid tags
(['a', 'ab', 'abc', 'a-b', 'a-b-c', 'a-', 'a-b-']).forEach(test);

// creating tag "a"
// creating tag "ab"
// creating tag "abc"
// creating tag "a-b"
// creating tag "a-b-c"
// creating tag "a-"
// creating tag "a-b-"

// invalid tags
(['-a', '-', '-a-', 'a--b']).forEach(test);

// skipping tag "-a"
// skipping tag "-"
// skipping tag "-a-"
// skipping tag "a--b"

@nicolo-ribaudo
Copy link
Member

nicolo-ribaudo commented Jan 6, 2018

Uppercase letters and numbers are also allowed. e.g. spaN, or h3.
The isCompatTag function should just be "Given this valid* JSX tag, should it become a string or an identifier?". To answer to this question, checking wether the first character is lowercase or not is enough.

* Babel plugins assume that the AST given to them is valid. The check against invalid ASTs should either be in Babylon or in babel-types.

@claudiopro
Copy link
Contributor Author

@nicolo-ribaudo my thoughts exactly, but I relate with @bvaughn, I'm falling prey to nerd sniping too 😅

@bvaughn
Copy link
Contributor

bvaughn commented Jan 6, 2018 via email

assert(t.react.isCompatTag("plastic-button")); // ascii letters
assert(t.react.isCompatTag("math-α")); // non-latin chars
assert(t.react.isCompatTag("img-viewer2")); // numbers
assert(t.react.isCompatTag("emotion-😍")); // emoji
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if anything besides /^[a-z][a-z-]*$/ is supposed to work, but I don't see why ban it unless the JSX spec forbids it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked the spec; the spec actually says nothing about compat tags at all; just that the identifier (JSXIdentifier) is basically the same thing as IdentifierName but it also allows -.

I assume that that part is enforced by babylon instead of isCompatTag so this seems sufficient.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Emojis in source code are cool 😆

@Jessidhia
Copy link
Member

Test failure is unrelated (yarnpkg timeout?)

@nicolo-ribaudo
Copy link
Member

Yes, it's failing often today (and yesterday). I restarted it but it failed again. I think we can marge this PR anyway since it doesn't affect Babylon?

@nicolo-ribaudo nicolo-ribaudo merged commit ce420ba into babel:master Jan 9, 2018
@hzoo hzoo added area: tests PR: Internal 🏠 A type of pull request used for our changelog categories labels Jan 9, 2018
@claudiopro
Copy link
Contributor Author

Thank you all 😊

@lock lock bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Oct 5, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Oct 5, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: tests outdated A closed issue/PR that is archived due to age. Recommended to make a new issue PR: Internal 🏠 A type of pull request used for our changelog categories
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Reac isCompatTag validator accepts leading dash '-'
7 participants