🐛Fix broken args in expander.#22508
🐛Fix broken args in expander.#22508calebcordry merged 7 commits intoampproject:masterfrom calebcordry:expander-args-bug
Conversation
zhouyx
left a comment
There was a problem hiding this comment.
Thank you for the summary. LGTM
src/service/url-expander/expander.js
Outdated
| // Trim space in between args that builder has collected. | ||
| if (builder.trim().length) { | ||
| results.push(builder); | ||
| results.push(builder.trim()); |
There was a problem hiding this comment.
If I understand the comment correctly, we only want to trip space between arguments. If so I don't think it's necessary to trim at (? For example abc MACRO(test). Please let me know if I miss anything.
There was a problem hiding this comment.
After talking offline I tried to move this trimming all to the processArgs* calls, but there is a problem with handling the back tick functionality. At time of resolving we do not know if the arg should be not trimmed. For example REPLACE(foo bar, ` `, -) at call time we have some arg list like ['foo bar', ' ', '-'] but we have lost track of what should be raw or not.
This problem also exists with trying to only trim when we see a ) or ,. I thought about keeping track of which args should be trimmed and passing this data around, but I think it is cleaner to just trim the args when we see a macro and we know we are in nested context. I updated the code to do so. Let me know if you think there is a better way.
src/service/url-expander/expander.js
Outdated
|
|
||
| // Collect any chars that may be prefixing the macro, if we are in | ||
| // a nested context trim the args. | ||
| if (builder.trim().length) { |
There was a problem hiding this comment.
nit: Since we parse the string from left to right could we append the builder to result first before we handle the matched binding.
There was a problem hiding this comment.
Also I understand this is used for
CLIENT_ID( SUBSTR(abc, 1, 2))
or
CLIENT_ID( testSUBSTR(abc, 1, 2))
However it also applies to
CLIENT_ID ( test SUBSTR(abc, 1, 2))
What do you think should be the different between these cases?
There was a problem hiding this comment.
for the first comment, sure we can move it up before finding the binding. For the second comment you are right these will all behave the same. The hard part is the distinction between CLIENT_ID ( test SUBSTR(abc, 1, 2)) and CLIENT_ID (a, test SUBSTR(abc, 1, 2)). We could only trim the first space instead of trimming the whole thing. What do you think?
There was a problem hiding this comment.
Hmmm CLIENT_ID ( test SUBSTR(abc, 1, 2)) maybe a bad example, because there's a space between CLIENT_ID and (. I don't think we can handle that. Let me know if that's true and expected.
CLIENT_ID(a, test SUBSTR(abc, 1, 2)). So I assume the test will be trimmed to test. And it equals to CLIENT_ID(a,testSUBSTR(abc, 1, 2)). Is that right?
Do you think we should only remove the trialing space in such case?
There was a problem hiding this comment.
you are correct these would be equivalent. Do you mean the leading space? If so I think that may be a good idea.
There was a problem hiding this comment.
just so we are on the same page, I am thinking something like trimLeft instead of trim.
There was a problem hiding this comment.
Ah, you're right. leading space it is : )
There was a problem hiding this comment.
👍changed to trimStart and moved the builder push before the binding stuff.
There was a problem hiding this comment.
also added test with the space. $IF(QUERY_PARAM(test), foo TIMESTAMP, ``)
| const args = []; | ||
|
|
||
| while (urlIndex < url.length && matchIndex <= matches.length) { | ||
| if (match && urlIndex === match.start) { |
There was a problem hiding this comment.
Have a question about the usage of backtick.
What's the expected output with
abc`TIMESTAMP`def
I couldn't find where we respect the PARSER_IGNORE_FLAG when handling with macros. Do you think that's an issue?
There was a problem hiding this comment.
You are right this macro is still resolved. The backtick only really works for spaces, parens, and commas. I think this is ok as we note in docs that macro names are reserved, but happy to revisit this. If we were to change it would need to be a separate PR.
There was a problem hiding this comment.
I think this is ok as we note in docs that macro names are reserved
As long as we've made that decision, I think it makes sense. could you please remind me when did we add support for backtick (before nested macro support, or after).
There was a problem hiding this comment.
Cool. I think reserve macros makes sense because I believe we have no plan to support keeping the ${vars} anyway.
zhouyx
left a comment
There was a problem hiding this comment.
LGTM. Thank you for the fix
The expander works by walking the input string. When it sees a macro it pushes the promise of a value (async) or the immediate resulting value (sync) into an array, that will be joined at the end of string or the next
).For example in a async call:
'abcMACROdef' => ['abc', Promise, 'def']This can become a problem in nested context because this array is what is passed to the function that resolves the function through
function.apply.'OUTER(INNER)' => OUTER.apply([INNER]) // good'OUTER(foo, INNER)' => OUTER.apply(['foo', INNER]) // good'OUTER(fooINNER, bar)' => OUTER.apply(['foo', INNER, 'bar']) // bad - three args passed to OUTER instead of twoThis fix changes this to use an array of arrays, where we can keep track of each argument in its own array, and therefore pass the correct number of args. The inner array(s) are then flattened before passing to
.apply().'OUTER(fooINNER, bar)' => [['foo', INNER], ['bar']] => flatten =>OUTER.apply(['fooINNER', 'bar'] // good :)An added bonus is that this casts any argument to a string before invoking the function :) This should help us with any changes to preserve types.
Also added a bunch of comments since this code is hairy, and its fresh in my mind for now.
Closes #20816