-
Notifications
You must be signed in to change notification settings - Fork 570
compiler: support arbitrary value js struct tag values #779
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
Conversation
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.
Thanks for sending this.
Can you check how much of an impact this change makes on the generated file size of a moderately sized program compiled with GopherJS? With minification, before and after gzip compression.
If it's not trivial, we'll probably want to only use this longer form when the string needs it.
js/js_test.go
Outdated
|
||
if want != got { | ||
t.Errorf("Value via non-identifier js tag field gave %q, want %q", got, want) | ||
} |
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.
Should we also add coverage for js.Object.Get
and js.Object.Set
methods? Are they equally affected?
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.
No, they are not affected: https://gopherjs.github.io/playground/#/osNIk-Yqbn
89aa286
to
082df8a
Compare
Good point. I think it's probably harder to come up with a definition for a representative "moderately sized program" than to make the change, so I made the change to implement this fallback logic. Please take a look. |
082df8a
to
ca48d94
Compare
Gentle ping @shurcooL - thanks. |
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 like the change to only switch to the more complicated implementation when needed.
compiler/utils.go
Outdated
// | ||
// Uses definition of an identifier from | ||
// https://developer.mozilla.org/en-US/docs/Glossary/Identifier | ||
func jsTagFormat(jsTag string) string { |
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 entirely a nitpick, so ignore it if you wish... But I wonder if formatJSTag
would be a better name, and more consistent with the other func names here, which use the <verb><object>
convention (i.e. formatExpr
and encodeIdent
etc)
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.
Done.
ca48d94
to
36bc620
Compare
@hajimehoshi - any thoughts? Gentle ping @shurcooL |
I'll look at this.
struct tags for js? |
compiler/utils.go
Outdated
// | ||
// Uses definition of an identifier from | ||
// https://developer.mozilla.org/en-US/docs/Glossary/Identifier | ||
func formatJSTag(jsTag string) string { |
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 feel like the word "js tag" is confusing. Isn't this actually a struct tag for js? How about formatStructTagForJS
? (I'm not an English native speaker so my suggestion might not sound natural)
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.
Addressed in the latest PR - I chose a slightly different version to keep more consistency with https://github.com/gopherjs/gopherjs/wiki/JavaScript-Tips-and-Gotchas
js/js_test.go
Outdated
|
||
type StructWithNonIdentifierJsTags struct { | ||
object *js.Object | ||
Name string `js:"my 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.
How about adding more test cases like starting with a char that is not for IDs?
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 suspect we already have coverage for that elsewhere. It's good to avoid adding same tests in different places.
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.
As there are such tests, the results should be changed?
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've made the test more "exciting" - I don't think any more are required because if we were doing the wrong thing then the generated Javascript would not parse.
compiler/utils.go
Outdated
func formatJSTag(jsTag string) string { | ||
useDot := true | ||
|
||
RuneRange: |
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.
Trivial style nitpick, maybe use Outer
label. It'd be more consistent with:
Line 162 in 558a913
Outer: |
(In general, RuneRange
is not a label I see often, but Outer
is pretty common.)
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.
Done
d7271ca
to
8798d09
Compare
@hajimehoshi I've tweaked the wording of the description (and commit) to be more precise and inline with https://github.com/gopherjs/gopherjs/wiki/JavaScript-Tips-and-Gotchas Revised commit now pushed up. PTAL |
compiler/utils.go
Outdated
return "." + jsTag | ||
} | ||
|
||
return "[\"" + jsTag + "\"]" |
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.
Don't we need escaping some chars like backslash? text/template.JSEscapeString
is needed here.
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.
Ah that's a good catch; but we need strconv.Quote
in this situation.
Also updated the test to include more special characters.
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.
Fixed in latest commit.
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.
Hmm, there are very subtle differences between strconv.Quote
and template.JSEscapeString
...
<
and>
are treated as special chars inJSEscapeString
strconv.Quote
can produce \U000XXXXX for non-BMP and non-printable char, which is not valid escaping in ES5
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're absolutely right - thank you for correcting me!
I've just added a test in the latest commit that does a .Get
in order to ensure the property that has been set via the js struct tag is using the expected key.
PTAL
7b1c965
to
20b1930
Compare
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.
lgtm, thank you!
@shurcooL are you happy with this PR?
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.
Some suggestions on how I think this PR can be improved.
compiler/utils.go
Outdated
@@ -643,3 +645,38 @@ func endsWithReturn(stmts []ast.Stmt) bool { | |||
func encodeIdent(name string) string { | |||
return strings.Replace(url.QueryEscape(name), "%", "$", -1) | |||
} | |||
|
|||
// formatJSStructTagVal returns a string of Javascript code appropriate for |
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.
JavaScript is spelled with a capital S, not "Javascript". See https://en.wikipedia.org/wiki/JavaScript, https://www.javascript.com/.
Also below.
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.
Ok.
compiler/utils.go
Outdated
// Uses definition of an identifier from | ||
// https://developer.mozilla.org/en-US/docs/Glossary/Identifier | ||
func formatJSStructTagVal(jsTag string) string { | ||
useDot := 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.
I think needsEscaping
would be a more readable name.
That way it can start out with zero value:
var needsEscaping bool
And become set to true
when encountering a character in the loop that needs escaping:
needsEscaping = true
break Outer
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 not sure I agree. We want the default behaviour to be to use the dot notation because, as noted before, this is more efficient in output terms. "Dot notation" is taken directly from the MDN. To my mind it's better to have the variable name reflect the intended semantics, hence useDot
.
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.
We want the default behaviour to be to use the dot notation because, as noted before, this is more efficient in output terms.
My suggestion doesn't contradict that. needsEscaping
controls whether non-default behavior of using escaping is used. If its value remains false
, the default behavior (using dot notation) is used.
It's similar to when you handle error conditions (non-default behaviors), you typically write:
var err error // (controls whether non-default path will be used)
f, err = os.Open("file.txt")
if err != nil {
// less common (non-default) path
return fmt.Errorf("couldn't open file.txt: %v", err)
}
// normal (default) path
Instead of:
successfullyOpened := true
f, err := os.Open("file.txt")
if err != nil {
successfullyOpened = false
}
if successfullyOpened {
// normal (default) path
}
// less common (non-default) path
return fmt.Errorf("couldn't open file.txt: %v", err)
It's not a big deal, I just wanted to explain the rationale behind my suggestion.
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 suggestion doesn't contradict that. needsEscaping controls whether non-default behavior of using escaping is used. If its value remains false, the default behavior (using dot notation) is used.
No I understand what you were getting at, it's just the name needsEscaping
that doesn't make sense to me. I'm quite happy having the inverse logic, but if we were to do so then useBracket
would probably be a better name (it's the alternative form).
compiler/utils.go
Outdated
useDot = false | ||
break Outer | ||
} | ||
} |
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 feel like this whole switch statement can be expressed more simply as:
ok := unicode.IsLetter(r) || (i != 0 && unicode.IsNumber(r)) || r == '$' || r == '_'
if !ok {
needsEscaping = true
break
}
Then no need for Outer:
label either.
Inspired by https://github.com/gopherjs/snippet-store/blob/09ea5d200e03052ed3cc332e9c6e96ffbb6d6eb3/id.go#L37-L40.
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.
Nice; yes that reads much better
compiler/utils.go
Outdated
return "." + jsTag | ||
} | ||
|
||
return "[\"" + template.JSEscapeString(jsTag) + "\"]" |
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 think we should indent the less frequent case, and keep the more common/normal case at normal indentation. So:
if needsEscaping {
return "[\"" + template.JSEscapeString(jsTag) + "\"]"
}
return "." + jsTag
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.
Again, per my comment above, I think useDot
is clearer.
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 not just eliminate the variable entirely, and further shorten the function?
func formatJSStructTagVal(jsTag string) string {
for i, r := range jsTag {
ok := unicode.IsLetter(r) || (i != 0 && unicode.IsNumber(r)) || r == '$' || r == '_'
if !ok {
return "[\"" + template.JSEscapeString(jsTag) + "\"]"
}
}
return "." + jsTag
}
To me this is clearer than either variable name alternative.
compiler/utils.go
Outdated
|
||
"github.com/alecthomas/template" |
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 think you meant to import html/template
from the Go standard library rather than a new 3rd party package, right?
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.
Oh I missed that... Thanks. I think text/template
is enough (html/template
is also fine and does the same thing).
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.
goimports
😢 - thank you, good catch
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.
Are you using the latest version of goimports
? I think it's expected to prefer std lib packages over 3rd party ones when it matches both. If not, that's a bug and worth reporting.
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'll take a look, thanks for the tip.
Javascript allows for arbitrary strings to be used as index values on an Object. A good example of where this comes up is React where `"data-*"` and `"aria-*"` indexes are used on objects (https://reactjs.org/docs/dom-elements.html). By switching from property selectors to the index operator (where required) we can support arbitrary string values in js-tagged fields for `*js.Object` special structs. Fixes gopherjs#778
20b1930
to
e78570d
Compare
@shurcooL - thanks for reviewing. I've just pushed up some changes, comments against one of your bits of feedback left above. |
I saw the comments, but not seeing changes. This PR currently has 1 commit, so it's hard to tell what has changed (if anything) from last time. If you don't mind, please avoid squashing and force pushing to the PR branch, but rather feel free to push additional commits. We can easily squash them into one commit when merging the PR. It makes reviewing easier, because the diffs show only what has changed since last time. (Unfortunately, GitHub isn't as good as Gerrit in this sense; Gerrit makes it possible to see the entire history of a change at all times.) |
Sorry, yes. I keep forgetting that. Old habits die hard. Will try to change that. |
Syntax viewer is an incredibly simple application for viewing the syntax tree representations of different languages. As part of this we vendor mvdan.cc/sh/syntax and its deps. We also re-enable AriaSet for now until we get gopherjs/gopherjs#779 merged.
Syntax viewer is an incredibly simple application for viewing the syntax tree representations of different languages. As part of this we vendor mvdan.cc/sh/syntax and its deps. We also re-enable AriaSet for now until we get gopherjs/gopherjs#779 merged. We also add the float CSS attribute. And fix some of the JSX parsing.
Add examples to show what kind of output formatJSStructTagVal returns. Simplify and shorten test. Remove unhelpful blank lines, they're best used sparingly.
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.
Thanks @shurcooL - looks fine. Let's get this merged! |
Thanks again @myitcv! |
Syntax viewer is an incredibly simple application for viewing the syntax tree representations of different languages. As part of this we vendor mvdan.cc/sh/syntax and its deps. We also re-enable AriaSet for now until we get gopherjs/gopherjs#779 merged. We also add the float CSS attribute. And fix some of the JSX parsing.
Javascript allows for arbitrary strings to be used as index values on an
Object. A good example of where this comes up is React where
"data-*"
and
"aria-*"
indexes are used on objects(https://reactjs.org/docs/dom-elements.html).
By switching from property selectors to the index operator (where required) we can support arbitrary string values in js-tagged fields for
*js.Object
special structs.Fixes #778