-
Notifications
You must be signed in to change notification settings - Fork 142
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
imitate / replace things deeply (instead of shallow) #268
Conversation
This was causing all our metaprogramming that checked types to break. See: nodejs/node#13827
This is necessary for avoiding cases where even accessing the property will set off a getter, or blow up in strict mode (e.g. accessing arguments or caller/callee on something). Instead, we'll look at the descriptor's `value` property, which is relatively "inert" by comparison
When copying props, you can adjust the value with a func arg
Does not work but figured like I may as well save my place
Should overwrite fields, but only if they're both writable & configurable. If the target has a field that's somehow neither of these, then it's almost certainly some kind of built-in like Function.prototype.length
It no longer needs the original because it has the full property descriptor
This required using the most-valuey of types as base cases in the recursion, because it'd muck with the perceived value of things like Date and moreover would fake 30+ string methods for probably zero benefit and lots of confusion. (Only affects boxed instances of these prims)
Surprisingly lodash isEqual fails on two errors but only if one is cloned. Meanwhile, assert module will pass deepEqual on two errors if the messages differ. Fantastic
Push all the naming chains into the imitation object, since it's the only call site with sufficient context
This got the tests passing, but may save us the need for extending types options (thanks to just using prototype and not class syntax)
Add a Map polyfill to get things working under older browsers, also refer to Symbol in such a way that it's backwards compatible
I did this in an example project to be sure it was a native (not transpiled) class being "extended" by td.js. This is the case that was failing during the 2.x series and necessitated the extendCo… option for 3.x This test passes, so we can delete that option next
The improvements in the new imitate function sidestep the babel class extension issue that prompted the introduction of this option. All fakes will pass instanceof checks as a matter of course now. Of course, this means that a major bump will be necessary, since that config option will have been removed and anyone setting it will blow up…
I feel like I earn 3 points for lazily giving people a let-me-github-that-for-you URL and lose 2 points for lazily pasting in an error message
Doing this in prep of adding a second teenytest suite under test/safe
Also we stopped printing '#' as shorthand for 'prototype'. It was complicating the code, and on further reflection it makes the messages harder for anyone who's not "in the know" with respect to '#' being a symbol for "instance method" (as opposed to '.' for plain function)
This refactor started by teasing the tangled function into its separate roles, then pushing the behavior down into 3 new units. The unit test for the refactored thing is particularly interesting, because it required use of argument captors to trigger the downstream behavior. This was necessary because the method was recursive, and we needed to invoke the subject twice, once indirectly. It's hard to find good examples of argument captors, so here you go.
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.
Same comment, just repeated everywhere: naming the default export to provide a useful function name when debugging/stack-trace
I'm excited that this reduces the complexity of the imitator logic!!
import copyProps from './share/copy-props' | ||
import gatherProps from './share/gather-props' | ||
import filterFunctions from './share/filter-functions' | ||
import imitate from './imitate' | ||
|
||
export default (typeOrNames) => |
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'd suggest naming the function so you'll get more useful stack.
import gatherProps from './share/gather-props' | ||
import copyProps from './share/copy-props' | ||
import filterFunctions from './share/filter-functions' | ||
import imitate from './imitate' | ||
|
||
export default (nameOrFunc, __optionalName) => |
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.
same here (name the exported func)
@@ -1,14 +1,12 @@ | |||
import _ from '../wrap/lodash' | |||
|
|||
import create from './create' | |||
import imitate from './imitate' | |||
import imitate from '../imitate' | |||
import remember from './remember' | |||
|
|||
export default (nameOrFunc) => { |
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.
samesies
src/imitate/copy-props.js
Outdated
@@ -0,0 +1,31 @@ | |||
import _ from '../wrap/lodash' | |||
|
|||
export default (target, props, visitor) => { |
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.
more anon default exports...
|
||
import tdFunction from '../function' | ||
|
||
export default (original, names) => { |
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.
same
@@ -0,0 +1,16 @@ | |||
import _ from '../wrap/lodash' | |||
|
|||
export default (original, names) => { |
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.
same
src/imitate/overwrite-children.js
Outdated
import gatherProps from './gather-props' | ||
import copyProps from './copy-props' | ||
|
||
export default (original, target, overwriteChild) => { |
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.
same
@@ -7,11 +9,16 @@ quibble.ignoreCallsFromThisFile() | |||
export default function (path, stub) { |
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.
same
lol @jasonkarns why did you leave the same comment a million times. FWIW, I tend not to name these functions since the stack trace gives me the file name & I tend to rename really aggressively, and naming the function adds yet another thing I have to change (in addition to file name, test file name, the test's import, and any production imports). Because I tend to create a lot of small things and change them pretty frequently, I tend not to value naming functions like this until well after the dust has settled on the design |
Was just tracking all the spots so nobody else had to search :)
Didn't realize babel did this now (always?). Hoping it works for the browserified version. 🤷♂️ |
Turns out most of the logic was pretty dumb and it's new behavior so I just axed most of it
Re: babel, it depends on how you compile it. We compile via a filter operation into |
These are just the node-level replacement, their properties (or for arrays, elements) will be replaced by overwriteChildren Simplied somewhat, when I learned through the test that underscore's clone works well enough for primitives (one would trust, efficiently enough)
Lots of complexity got swept under this rug, really causing some pain at the periphery. Since it's a leaf node, will I ever look at it again? Probably not. Some notes: * ../../../.. is outrageous. I should figure out how to go about getting the unit tests colocated with the source soon. * 5 arguments for chain-prototype.js? Seriously? That can't be necessary * This overwrite-children/index test is really the kitchen sink of edge cases for delegator objects. It has replaced deps, it has one-off test double functions passed as arguments, it has argument captors for getting callbacks under isolated tests, it has verifications that certain dependencies were absolutely not called. It's got it all! All of these are smells that this unit is weird/awkward. It's possible that things got complex because the code originated while trying to tidy up the behavior originally in the context of a big method with lots of closure-scoped variables available. That explains the 5 arg function (once extracted and closure-scope is lost), as well as the visitor pattern sort of callbacks to keep the recursion step out-of-sight of the extracted unit. Unsure how I'd rework it at this point to breathe like a normal function, though. That's why I should have test-drove this from scratch instead of test-aftering and pushing down all these things! Whoops!
Landed in 3.2.0 |
Build on #257 by creating a single unit responsible for all "imitation" of real things, be they plain functions, objects, or constructors.
This does a few neat things for the library in terms of enhancement:
instanceof
/inheritance issues that forced the 3.0 major bump. If this turns out to be true, it means the config property can be eradicated and we can put that whole mess behind ustd.func
/td.object
/td.constructor
, in fact, most of the implementation of each of those can be thrown outThis is an incremental improvement that shook out of the Big Epic Rewrite in #263
Concerns
If you are in the habit of calling
td.replace
on massive modules (with hundreds or thousands of cumulative properties), this is going to be realllllllly slow compared to what you're used to, at potentially no added benefit. Trade-offs here:maxImitationDepth
config setting may be appropriate (note that the current implementation successfully breaks cycles, but will otherwise recurse to arbitrary depth)Todo
I made a bit of a mess in the process of getting this over the hump and the build passing. Some to-dos remain:
instanceof
, then remove any remnants of the optioncopyProps
if imitate has successfully obviated ittest/
to/regression
unit
totest/unit
and fix all the importstest/safe
and put the functional imitation test thereFixes #262