Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

fix($parse): respect the interceptor.$stateful flag #16015

Merged
merged 3 commits into from
Jul 17, 2017

Conversation

jbedard
Copy link
Collaborator

@jbedard jbedard commented May 26, 2017

This actually depends on #16009 because that (unnecessary) $stateful flag was previously being ignored in the case of the failing test.

@jbedard
Copy link
Collaborator Author

jbedard commented Jun 2, 2017

For the failing test (which depends on #16009 the second commit): previously it only passed because the inputsWatchDelegate was incorrectly used due to this block assigning the inputs then the inputsWatchDelegate was later used because of those inputs. This is incorrect because a $stateful filter/interceptor must always be invoked, not only if it's inputs change, like the added tests demonstrate.


// Propagate or create inputs / $$watchDelegates
useInputs = !parsedExpression.inputs;
if (watchDelegate && watchDelegate !== inputsWatchDelegate) {
Copy link
Member

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)?

Copy link
Collaborator Author

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"...

Copy link
Member

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? 😛

Copy link
Member

@gkalpak gkalpak Jun 6, 2017

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?

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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'...

Copy link
Collaborator Author

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"

Copy link
Member

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.

@Narretz
Copy link
Contributor

Narretz commented Jun 12, 2017

Looks like a few tests failed. And there's also an eslint error

@jbedard jbedard force-pushed the parse-interceptor-stateful branch from d946866 to c2941b9 Compare June 12, 2017 16:09
@jbedard
Copy link
Collaborator Author

jbedard commented Jun 12, 2017

Fixed the lint issue. Not sure what the test errors were (don't think they failed before), we'll see if they fail again...

@jbedard jbedard added this to the 1.7.0 milestone Jun 12, 2017
@jbedard jbedard force-pushed the parse-interceptor-stateful branch from c2941b9 to 798d277 Compare June 16, 2017 04:33
@jbedard
Copy link
Collaborator Author

jbedard commented Jun 16, 2017

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...

@jbedard jbedard force-pushed the parse-interceptor-stateful branch 2 times, most recently from 48ede5f to 33701f1 Compare June 16, 2017 15:32
@jbedard jbedard force-pushed the parse-interceptor-stateful branch from d57f8a4 to 7fe7421 Compare June 17, 2017 17:35
@jbedard jbedard force-pushed the parse-interceptor-stateful branch from 7fe7421 to 730dc11 Compare June 28, 2017 03:02
@jbedard jbedard force-pushed the parse-interceptor-stateful branch 2 times, most recently from 7730530 to 139bbe0 Compare July 8, 2017 08:29
@jbedard
Copy link
Collaborator Author

jbedard commented Jul 10, 2017

@lgalfaso would be great to get your review/opinion if you have a chance

Copy link
Contributor

@lgalfaso lgalfaso left a 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);
Copy link
Contributor

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?

Copy link
Collaborator Author

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...

Copy link
Collaborator Author

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...

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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);
Copy link
Contributor

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.

Copy link
Collaborator Author

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;
Copy link
Member

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?

Copy link
Collaborator Author

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 execute stringify(x) per-digest so we set x itself as the input and useInputs = true to handle this
  • $parse("x | number", stringify): x | number already has x as an input so we just continue using that input (and useInputs = false)

Copy link
Collaborator Author

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...

Copy link
Member

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... 😃

Copy link
Collaborator Author

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 ^_^

Copy link
Member

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.)

Copy link
Collaborator Author

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.

@jbedard jbedard force-pushed the parse-interceptor-stateful branch from 139bbe0 to 8654503 Compare July 14, 2017 09:10
@jbedard jbedard merged commit 2fb2d09 into angular:master Jul 17, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants