-
Notifications
You must be signed in to change notification settings - Fork 27.4k
fix($parse): respect the interceptor.$stateful flag #16015
Conversation
712ece3
to
9e8ba79
Compare
For the failing test (which depends on |
|
||
// Propagate or create inputs / $$watchDelegates | ||
useInputs = !parsedExpression.inputs; | ||
if (watchDelegate && watchDelegate !== inputsWatchDelegate) { |
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.
If we remove this, what happens with "custom" $$watchDelegates
(such as the ones added by $interpolate
)?
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 actually fixes a bug because that lost $$watchDelegate
knows nothing about the interceptor and is built specifically for that interpolate fn.
For example watching $parse($interpolate("{{foo}}"), function() { return "bar"; })
will always output the "foo" var even though it is wrapped in an interceptor that always returns "bar", yet executing that parsed-expression will always return "bar"...
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.
Where is the test for this fix then? 😛
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.
FWIW, I am still confused about what happens to $$watchDelegates
when combined with interceptors? Are they just ignored?
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.
Yeah they are just ignored, because the interceptor might have made that $$watchDelegate
completely invalid. However the returned ("interecepted"?) fn might have some constant/oneTime/inputs properties that might cause a $$watchDelegate
to be added.
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.
Here's an example, I'll try to add a test for this...
$parse($interpolate("a{{b}}"), function(x) { return x + 'c'; })
$interpolate
adds a special $$watchDelegate
for an efficient way to watch the interpolated-expression, but that delegate of course knows nothing about the x + 'c'
interceptor. Previously this line we're commenting on would blindly copy the delegate creating an expression which produces the correct value when executed (with the 'c'
concatenated at the end) but the wrong value when watched due to the use of a $$watchDelegate
that knows nothing about the x + 'c'
...
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 added a test for this: "should not propogate $$watchDelegate to the interceptor wrapped expression"
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 see. And we are fine with that because we never use $$watchDelegate
with interceptors in core, right?
I might add a comment (where?) about that, i.e. the you can't use $$watchDelegate
with interceptors.
9e8ba79
to
d946866
Compare
Looks like a few tests failed. And there's also an eslint error |
d946866
to
c2941b9
Compare
Fixed the lint issue. Not sure what the test errors were (don't think they failed before), we'll see if they fail again... |
c2941b9
to
798d277
Compare
I think the remaining test failure is actually caused by #16021. I'll probably end up trying to fix both in this PR since they seem overlap a lot... |
48ede5f
to
33701f1
Compare
d57f8a4
to
7fe7421
Compare
7fe7421
to
730dc11
Compare
7730530
to
139bbe0
Compare
@lgalfaso would be great to get your review/opinion if you have a chance |
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.
Overall, I think it is a good change and a nice cleanup. Just some minor comments.
src/ng/parse.js
Outdated
|
||
function chainInterceptors(first, second) { | ||
function chainedInterceptor(value, scope, locals) { | ||
return second(first(value, scope, locals), scope, locals); |
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 sure that locals
should be sent to both interceptors?
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.
Good question. I'll look into it and try to add some 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.
This is equivalent to today. The interceptor is passed the scope and locals. Then if it is double-intercepted then it will pass them through until reaching the original expression.
I would be tempted to remove these args from the interceptor though, and only pass them to the original wrapped expression. Probably in a separate PR though...
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.
See #16091 which might be nice to merge before 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.
This method is now simplified after #16091 merged.
src/ng/parse.js
Outdated
// Extract any existing interceptors out of the parsedExpression | ||
// to ensure the original parsedExpression is always the $$intercepted | ||
if (parsedExpression.$$interceptor) { | ||
interceptorFn = chainInterceptors(parsedExpression.$$interceptor, interceptorFn); |
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 understand why this chaining is needed, just wonder if there is a cleaner solution.
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 all internal so it can always be changed later. I'm open to any ideas though 👍
var exp = parsedExpression.$$intercepted || parsedExpression; | ||
var post = parsedExpression.$$interceptor || identity; | ||
|
||
var useInputs = parsedExpression.inputs && !exp.inputs; |
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 could parsedExpresion.inputs
be truthy and parsedException.$$intercepted.inputs
be falsy?
Can you give an example scenario?
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.
It's basically detecting this case. This is when the intercepted expression had no inputs, so the expression itself becomes the input.
For example
$parse("x", stringify)
:x
has no inputs, but we don't want to executestringify(x)
per-digest so we setx
itself as the input anduseInputs = true
to handle this$parse("x | number", stringify)
:x | number
already hasx
as an input so we just continue using that input (anduseInputs = false
)
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 fact that this line basically "detects" the useInputs = true
from addInterceptor
is something I don't like about this PR atm. It already existed before, but I guess now it is spread across two methods which seems worse to me. But it's all internal and could always 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 can't say I can fully wrap my head around this, but the tests pass, so... 😃
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.
Which part? Anything I can explain more/better? There's a couple weird things being changed in this PR, and I also get confused after not looking at it for a while ^_^
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 useInputs
bit still confuses me (but in tests we trust).
(At some point I need to grab my debugger and go through some usecases, but this doesn't have to hold this PR back.)
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 useInputs
is also the part I don't like.
Note that I think it should really be renamed to something such as theInputIsTheInterceptedExpression
(where normally the input is the input to the intercepted expression).
It is also just a performance thing, dropping all useInputs == true
logic would still function correctly.
139bbe0
to
8654503
Compare
This actually depends on #16009 because that (unnecessary)$stateful
flag was previously being ignored in the case of the failing test.