Skip to content
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

Need comprehensive review of why libraries use .call/.bind/.apply #12

Closed
theScottyJam opened this issue Oct 6, 2021 · 35 comments
Closed
Labels
documentation Improvements or additions to documentation help wanted Extra attention is needed question Further information is requested

Comments

@theScottyJam
Copy link
Contributor

theScottyJam commented Oct 6, 2021

This proposal's README states the reason we need this bind operator is because:

.bind, .call, and .apply are very useful and very common in JavaScript codebases.
But .bind, .call, and .apply are clunky and unergonomic.

It goes on to do an excellent job at explaining both of these points.

I was curious as to the reason why the .bind() family of functions was used so often, so I decided to look into each example the proposal presented to figure out why the code authors chose to use a bind function there. Here's my findings:


Update: To preserve context for what I'm talking about, here's the code snippets from the README I was originally referring to. It's comparing how current code looks like, and how it would look like if it used the proposed bind syntax.

// [email protected]/index.js
type = toString.call(val);
type = val::toString();

// [email protected]/src/common.js
match = formatter.call(self, val);
match = self::formatter(val);

createDebug.formatArgs.call(self, args);
self::(createDebug.formatArgs)(args);

// [email protected]/errors-browser.js
return _Base.call(this, getMessage(arg1, arg2, arg3)) || this;
return this::_Base(getMessage(arg1, arg2, arg3)) || this;

// [email protected]/lib/_stream_readable.js
var res = Stream.prototype.on.call(this, ev, fn);
var res = this::(Stream.prototype.on)(ev, fn);

var res = Stream.prototype.removeAllListeners.apply(this, arguments);
var res = this::(Stream.prototype.removeAllListeners)(...arguments);

// [email protected]/lib/middleware.js
Array.prototype.push.apply(globalMiddleware, callback)
globalMiddleware::(Array.prototype.push)(...callback)

// [email protected]/lib/command.js
[].push.apply(positionalKeys, parsed.aliases[key])
positionalKeys::([].push)(parsed.aliases[key])

// [email protected]/build-es5/index.js
var code = fn.apply(colorConvert, arguments);
var code = colorConvert::fn(...arguments);

// [email protected]/q.js
return value.apply(thisp, args);
return thisp::value(...args);

// [email protected]/src/internal/operators/every.ts
result = this.predicate.call(this.thisArg, value, this.index++, this.source);
result = this.thisArg::(this.predicate)(value, this.index++, this.source);

// [email protected]/js/release/synchronous_inspection.js
return isPending.call(this._target());
return this._target()::isPending();

var matchesPredicate = tryCatch(item).call(boundTo, e);
var matchesPredicate = boundTo::(tryCatch(item))(e);

// [email protected]/polyfills.js
return fs$read.call(fs, fd, buffer, offset, length, position, callback)
return fs::fs$read(fd, buffer, offset, length, position, callback)

The "kind-of" package uses .call() because they want to call Object.prototype.toString() on a passed-in object, and they're following the good practice of not trusting that .toString() will be present and well-behaved on the passed-in object (e.g. the object could have a null prototype).

There were two examples of .call() from the "debug" package. In both scenarios, they're binding "this" to a custom object, in order to provide additional context for that function. In the first scenario, they're binding "this" while calling a user-provided function - this seems to be an undocumented feature. In the second scenario, they're only binding "this" to internal callbacks.

The "readable-stream"'s usage of .call() is actually pretty irrelevant to this proposal. It's the result of a babel transformation, turning class syntax with super calls into functions and prototypes, with .call() being used to help emulate super().

The "yargs" package was just trying to do what we can do today with the spread operator. The code must have been written before the spread operator existed.

It was a little difficult to follow the pretty-format's code. if anyone else wants to give it a shot, feel free. I think ultimately they were trying to do a form of method extraction from one of their third-party libraries, but they were binding the "this" value at a different location from where they were extracting the methods. From my initial impression, it looked like they might even be binding the wrong "this" value to these methods, but the third-party library didn't care, because the third-party library doesn't actually use "this" anywhere in its source code (it was a smaller library, and a grep for "this" returned zero results). I suspect I'm just misinterpreting what was going on, but maybe not.

The "Q" package uses .apply() as part of some internal logic, which seems to mainly be used by publicly exposed functions such as promise.fapply(), promise.fcall(), and promise.fbind(). Ultimately the purpose is to mimic javascript's bind functions, but with some async logic added to them.

In the case of rxjs, they're exposing functions that accept a callback argument, and an optional this-value that will be applied to the supplied callback when it's called. This is mimicking built-in functions like Array.prototype.map(), which also accept both a callback and an optional "this" parameter.

Bluebird was using .call() for two different reasons. In the first scenario, they were wanting to define similar methods on two different classes. They did so by defining them all on one class, then creating a bunch of tiny, one-line methods on the other class that basically did FirstClass.prototype.call(specialThisValue, ...args). Basically, they were using .call() for code reuse purposes. In the second scenario, .call() is being used because they expose their own promise.bind() function, which accepts a "this" value, then applies that "this" to every callback in the promise chain. Part of the reason for this behavior is to allow the end user to keep state around between callbacks by mutating "this". See here for their rational behind this feature.

graceful-fs was simply a case of method extraction. They pulled off fs.read, and instead of binding it on the spot, they supplied the correct "this" value via .call() later on.


Ok, I think that's a good sample-size of many different possible use cases for bind functions. I'm going to go ahead and categorize these use cases, so that we can discuss the value a bind syntax provides to these general categories.

Irrelevant

  • Readable-stream's use case is irrelevant to this proposal simply because it's not possible to add a new feature to improve the quality of transpiled code. By definition, transpiled code can't use new features.
  • Yargs's use case is irrelevant because that code snippet can already be greatly improved by simply using the spread operator.

Customizing the "this" value of callbacks

Both bluebird's second example and the debug package use .call() to customize the "this" value of callbacks. In general, I think a good rule of thumb to follow is "prefer providing explicit parameters over custom, implicit 'this' parameters". In other words, it's generally better to just pass a context object to a callback instead of trying to conveniently set it's "this" value to this context object.

I'm not saying it's always bad to customize the "this" value of callbacks, I'm just saying we probably shouldn't encourage this kind of behavior, which means I would be against adding a bind syntax if this was the only use case for it.

protection from global mutations

This category wasn't found in any of the examples above, but it's still worth discussing. As shown in the README, node has a lot of code that uses .bind() type functions to protect against mutation of global properties. This is a fair point, and a valid use case for this proposal, but it can't be the only driving force. As discussed here, it seems this proposal can't feed off of "global mutation protection" alone, it needs stronger reasons to exist.

Mimicking existing language semantics

Both Q and rxjs mimic existing language semantics related to this-binding. Q provides .apply()/.call()/.bind() equivalents for promises and rxjs provides a parameter to set the "this" value of a callback.

These types of features are only useful if the end user finds it useful to bind custom this values to their callbacks, which is the type of thing we're trying to discuss right now. Perhaps, there's really not a big need for these features, and the API authors didn't really need to include them. But, then again, perhaps there are good use cases, so lets keep exploring.

Language workarounds

It seems a couple of the packages were using .call() simply because the language itself didn't provide what they needed in a direct way.

  • kind-of simply needed to stringify an unknown object. This is currently not possible without .call().
  • Bluebird was using .call() for code reuse purposes - they wanted to use the logic found within one method inside a method of a different class, so they did so by simply supplying using .call() with a custom "this" value. This particular solution seems to be pretty unidiomatic - if they ever switched to class syntax and private state, they would find that they couldn't keep using this code-reuse pattern. Perhaps there's better solutions to their problems that are more idiomatic that can be done in JavaScript today, or maybe in some future version of JavaScript after features have been added to help them in their use case.

For items that fall into this category, it's probably best to analyze each situation independently and figure out what really needs to be added to the language. Sure, a this-binding shorthand could provide some minor benefits to the readability of these workarounds, but what they really need is a way to do what they're trying to do without resorting to funky workarounds. We're already actively fixing a number of problems in this category, for example, there's the mixin proposal, and there's also the recently added Object.hasOwn() function which is basically a more direct way of doing Object.prototype.hasOwnProperty.call().

Method extraction

Both pretty-format and graceful-fs are basically doing a form a method extraction, except in both cases they're choosing to supply the "this" value at the call location instead of the extract location.

This proposal stated that it doesn't wish to focus on method extraction, but ironically, I think this use case might be the most important one among the ones that have been analyzed.

Others?

Looking at the above categories of use cases, it seems that many uses of .call()/.bind()/.apply() don't actually benefit much from a this-binding syntax, however, what's been presented is not an exhaustive list of possible use cases. So I want to pose the following question:

What specific uses of .call()/.bind()/.apply() does this proposal hope to help out with? Are there use cases beyond the ones listed above that this proposal seeks to make more user-friendly? If so, what are they? This might be a good thing to eventually document in the README as well. The README explains clearly that .call() gets used everywhere, but it doesn't dig deeper to find out why it's getting used all over the place - I think those underlying use cases are the real gold mine we're trying to hit. The uses of ".call()" seems to more-often-than-not be a symptom of a deeper problem that needs addressing.

@js-choi js-choi added documentation Improvements or additions to documentation question Further information is requested labels Oct 7, 2021
@js-choi
Copy link
Collaborator

js-choi commented Oct 7, 2021

Excellent review.

Good catch on readable-stream; I should have recognized what was going on. Although Gzemnid does not usually include transpiled build code in its dataset, it cannot distinguish code that was transpiled in the past and then subsequently used as base source code, as what appears to be happening with readable-stream.


The original use case was motivated by @ljharb’s desire for syntax to change the this receiver of functions—he manages call-bind and the many polyfills that depend on it for protection from prototype pollution and other global mutation. He is also involved in Node, which as you may know uses an elaborate primordials system, also for protection from global mutation.

But based on feedback from some TC39 representatives (see #6 (comment), #8, and some discussion on Matrix), I had become a little reluctant to emphasize specific use cases. I’m afraid that associating this proposal with certain use cases—such as individually importable, tree-shakable “extension methods” or the security use case—would make several representatives colder toward this proposal. Instead, I have tried to focus attention on the sheer magnitude of general bind/call/apply usage, while keeping in mind their clunkiness.

Having said that, you are right: in order to strengthen the proposal, we must investigate specifically why libraries (and in particular the most popular libraries) use bind/call/apply. And we have to take any historical use of apply with healthy skepticism, of course, because spread syntax now exists. So we should do an analysis over as much of the open-source JavaScript corpus as possible, like in tc39/proposal-change-array-by-copy#37.


So next actionable items I see:

  • Remove examples from readable-stream and yargs.
  • Review Gzemnid’s text dumps for patterns in why libraries specifically use call etc.
  • Maybe write a paragraph or two summarizing these patterns.
  • Add a warning about .apply’s statistics—or maybe even remove mentions of .apply altogether.

I invite you to download and run the Gzemnid dataset and script yourself.
I’ve also dumped the first 10,000 lines of the .call results, sorted by library popularity, in a Gist.
I’d love your further help in exploring and strengthening this proposal’s use cases based on these real-world data. We’re all volunteers here, after all.

Thanks again for the excellent review.

@js-choi js-choi added the help wanted Extra attention is needed label Oct 7, 2021
@js-choi js-choi changed the title When is it actually useful to use .call()/.bind()/.apply()? Need comprehensive review of why libraries use .call/.bind/.apply Oct 7, 2021
@theScottyJam
Copy link
Contributor Author

Yeah, I'll scour around and see what other use cases I can find 👍️

@ljharb
Copy link
Member

ljharb commented Oct 7, 2021

it's generally better to just pass a context object to a callback instead of trying to conveniently set it's "this" value to this context object.

This is a highly subjective opinion; one that jQuery (arguably the most popular JS library ever) uses heavily; and community opinion on things like this has shifted over time. I don't think it's the committee's place to pass judgement on the use of a fundamental pattern of the language, especially one that every prototype method relies on.

@js-choi
Copy link
Collaborator

js-choi commented Oct 7, 2021

Both pretty-format and graceful-fs are basically doing a form a method extraction, except in both cases they're choosing to supply the "this" value at the call location instead of the extract location.

This proposal stated that it doesn't wish to focus on method extraction, but ironically, I think this use case might be the most important one among the ones that have been analyzed.

I also want to make it clear that I do consider method extraction (along with the calling of extracted methods) to be a core use case of this proposal. For example, method extraction is essential for security from prototype pollution and other global mutation.

The non-goal of this proposal is what I currently call “tacit” method extraction: a term I made up to refer to when a method is implicitly bound to its original owner, creating a bound function.

// “Explicit” method extraction results in a `this`-dependent ordinary function.
// This is handled by this proposal.
const { slice } = arr;
// The `this` value is supplied in the call.
arr::slice(...args);

// “Tacit” method extraction results in a `this`-independent bound function.
const slice = arr&.slice;
// The `this` value is not supplied in the call, because the function is bound.
slice(...args);

I think these two concepts could use better names. I opened #13 about this.

@js-choi
Copy link
Collaborator

js-choi commented Oct 7, 2021

For what it’s worth, there are some matching lines in the top-1000-packages dataset that come from various packages’ standalone.js files, but these lines seem to be not very significant in number. For example, there are 745 .calls in standalone.js files, and there are 93 console.logs in standalone.js files. (Compare to 500084 .calls and 271915 console.logs among all files.)

> ./search.topcode.sh '.call' | awk '/standalone.js/' | awk 'END { print NR }'
745
> ./search.topcode.sh 'console.log' | awk '/standalone.js/' | awk 'END { print NR }'
93

To be clear, Gzemnid only includes code added to Git repositories. No transpiled code is included unless it is manually added to the repository. It doesn’t seem like Git-managed standalone.js files are a significant contributor to any of these numbers.


There also appear to be 53667 instances of .calls on a super-like object, such as in RxJS:

> ./search.topcode.sh '.call\b' | awk '/super/' | awk 'END { print NR }'
53667

These constitute about 10.7% of all .calls in this dataset, which is more significant but still vastly outnumbered by other uses.

@theScottyJam
Copy link
Contributor Author

@js-choi - thanks for the clarification on tactic method extraction, that makes sense.

@ljharb - to clarify, I was just talking about callbacks passed to a function, not methods on a prototype, and I wasn't claiming that it's wrong to bind custom "this" value to callbacks. I think there are numerous good, proper use cases for doing so. I've seen plenty of libraries do it for good, valid reasons.

However, I do stand by my opinion that when you're uncertain what to do, it's better to air on the side of explicit parameters over implicit ones. People generally do this naturally - when you use array.map(), you can access the index and original array parameters by simply accepting more parameters. It could have been designed such that you access those values via this.index and this.originalArray, but it wasn't, and for good reason too. The language designers aired on the side of explicit parameters.

If customizing the "this" on callbacks to provide extra context is the only other, good valid use case, and this syntax is provided primarily to support it, then the committee is in fact passing judgment on this use case. They're blessing it with special syntax, telling the world that they want to see more of this done, to the point that they're even willing to provide special syntax to help out.

Now, I don't believe this use case is the only valid one. But, my objective here is to try and decompose the usages of .call/.bind/.apply to their different use cases and see how a bind operator can help each one of them. This means that, yes, we'd need to analyze each use case in isolation and see if it's really a use case we with to promote or not. If we do want to promote it, how much? How much weight do we give to each use case?

Once we've distilled what we see to a list of use cases we want to promote, then we can focus on how to best support these use cases. For example, if we find that 99% of the time people really just want tactic method extraction, then maybe we need to change the focus of this proposal a bit. Or, if we find that 99% of the time people want to supply an implicit "this" paramter to callbacks, and the committee is willing to promote that idea, then maybe the .call() syntax I proposed in #3 of fn@(customThis, arg1, arg2) would arguably be more appropriate, as this use case isn't about trying to supply the correct instance to a method, rather, it's just trying to supply an implicit context argument, something which the .call() syntax better represents.

@ljharb
Copy link
Member

ljharb commented Oct 7, 2021

I don’t know how it could possibly have been designed that way; mutating the array to provide that info wouldn’t have ever been viable.

@ljharb
Copy link
Member

ljharb commented Oct 7, 2021

To be clear, this is not a “context”, it’s a receiver. Context can imply access to scoping information, or things based on an environment - it’s one purpose of the receiver, but not the generic term or the universal purpose.

My understanding is that a receiver is a concept from smalltalk, where it represents the object that the a message (that the function represents) is being sent to.

The .call API does not represent providing a receiver well, and never has, precisely because everyone is familiar with a.b(), so they expect the receiver to come first, to the left of the function name. For a calling syntax to be ergonomic, it has to beat .call, which means it must have the receiver come first.

@js-choi
Copy link
Collaborator

js-choi commented Oct 7, 2021

For what it’s worth, I’m still trying to focus attention on the sheer magnitude of general bind/call/apply usage, rather than specific use cases.

People seem to rebind this for various reasons. Some libraries like jQuery store information in this. Some libraries use this in order to have their functions mimic existing language semantics. Some libraries cache this-using method functions for protection from global mutation.

But whatever the reasons, they are frequent, and they are clunky. A goal of this proposal is to improve the word order’s ergonomics: obj.fn(arg) and obj::fn(arg)—not obj.fn(arg) and the clunky fn.call(obj, arg).

I think the biggest valuable thing that this review is already giving us is making sure we don’t include any irrelevant examples, like readable-stream’s transpilation from super-using code. If we’re going to make an argument of frequency, then we had better make sure that frequency does not include irrelevant cases (e.g., excluding lines containing super-like identifiers)…while pointing at the remaining large numbers.)

At least, that’s my current approach.

@theScottyJam
Copy link
Contributor Author

theScottyJam commented Oct 7, 2021

@js-choi - those are interesting findings.

I've been looking at the 1000 line dump you shared and have been whittling it down - I've pretty much chopped it in half now after removing these types of things:

  • 340 occurrences of people trying to use Array methods on an array-like object. e.g. a common pattern was Array.prototype.slice.call(arguments).
  • 652 occurrences of stuff that got outputed by bundlers/compilers/transpilers. A prominent reason for the .call() was to emulate super(). There's actually many people who were emulating super() by hand as well that's not included in this number.
  • 1090 occurrences of functions that got stripped off of the prototype and .call()ed directly. This is more common than I expected.
  • 1076 occurrences came from prettier doing path.call(). I haven't yet looked into why this is done - I suspect "path" is a user-supplied function and they're just wanting to provide a custom this context to it, but I'm not sure.
  • 451 occurrences came from es5-ext - their testing framework was designed so that there's one test module per function, and that function is passed as an argument into the main export of the testing module. When testing functions from a prototype, they have to use .call().
  • 587 occurrences came from people using Object.prototype methods on unknown objects that might not properly inherit them.
  • 62 were comments
  • 48 was Error.call - a commonly done pattern for inheriting from the build-in Error class, before class syntax existed.
  • 113 was callback.call or cb.call - these are presumably people who are wanting to provide special this values to a callback.

These are just superficial findings by looking at the overall file. Only so much can be found this way. I want to dig into more individual use cases as well to see if I can find other stuff.

@js-choi
Copy link
Collaborator

js-choi commented Oct 7, 2021

Excellent, thank you for your hard work. Lots of stuff for us to investigate here further. A lot of this is actually reassuring…

How are you checking whether code is coming from transpilers?

@theScottyJam
Copy link
Contributor Author

theScottyJam commented Oct 7, 2021

I don’t know how it could possibly have been designed that way; mutating the array to provide that info wouldn’t have ever been viable.

The array instance isn't the "this" argument in that example. It's just some object literal. Like this:

Array.prototype.forEach = (callback) => {
  for (let i = 0; i < this.length; ++i) {
    callback.call(this[i], { i, array: this })
  }
}

People design APIs like this, all the time.

When I do this:

someHTMLElement.addEventListener('click', function() {
  console.log(this)
})

The "this" value is not someHTMLElement, it's an event object. They also pass the event object in as a parameter, so there's never really a need to use the special "this" and most people don't touch it. I would argue that this is one of the bad examples, that's we're now stuck with in browsers.

@theScottyJam
Copy link
Contributor Author

@js-choi

If a line of code contained any of these strings, I considered it to be the fault of the transpiler that .call() got used:

  • 258: _super.call( // Result of babel transpiling super()
  • 126: _super.prototype. // Result of typescript compiling super.f() calls
  • 79: _getPrototypeOf and _possibleConstructorReturn // Artifact of babel transpiling.
  • 140: __super__ // Artifacts of CoffeeScript compilation, or people creating classes by hand.
  • 49: WEBPACK VAR INJECTION // Output of webpack merging files.

I wasn't clear with what I originally stated. I was just looking for code that that was using .call() because it was transpiled, I left other transpiled code untouched.

I'm sure I'm catching some false positives. For example, I did see some people using __super__ by hand, in code that wasn't being transpiled. And, as with any of those categories, I'm sure I'm also missing a bunch of places where transpiled code caused the .call() to happen, but there's no easy way to tell from a simple text search. But, it's all rough estimates anyways.

@js-choi
Copy link
Collaborator

js-choi commented Oct 7, 2021

Thanks sharing for your methodology; it makes sense.
So when checking the entire top-1000-packages dataset:

> ./search.topcode.sh '\.call\b' | awk '/super|_getPrototypeOf|_possibleConstructorReturn|WEBPACK VAR INJECTION/' | awk 'END { print NR }'
85121

These constitute about 17.0% of .call occurrences in the dataset. 414963 occurrences: still not bad, still way more than console.log’s 271915.

It’ll be interesting to check the outputs for .bind and .apply later, too.

(CC: @tabatkins, who was wondering about this earlier too.)

@theScottyJam
Copy link
Contributor Author

Honestly, I was thinking that transpiled code could potentially account for a lot larger fraction of these. 17% is a fair amount, but it's not the majority, so that's good to note.

Certainly, there seems to also be a lot of legacy uses for .call(), and it's hard to filter out all of those. But, there also does seem to be a lot of legitimate uses for it.

@js-choi
Copy link
Collaborator

js-choi commented Oct 7, 2021

@theScottyJam: How did you determine that readable-stream’s .calls were also the result of a Babel transformation? They don’t fit any of your listed criteria.

@theScottyJam
Copy link
Contributor Author

theScottyJam commented Oct 7, 2021

That was this one:

// [email protected]/errors-browser.js
return _Base.call(this, getMessage(arg1, arg2, arg3)) || this;

For those first examples, I was going into the source code and looking through it by hand to figure out the reasons why they were using .call(). It became clear that the whole structure was a babel transformation of a class once the source code was opened up, but there's no good way to determine that fact by looking at just the one line of code listed in the dump.

The basic text-searching approach I did wasn't able to pick up this initial scenario either, like you mentioned. The text searching should be taken as "at minimum, there's roughly X amount that fall into this category, there could be many, many more that we're missing", not "we've identified all entries from this file that fall into this category".

However, I'm willing to bet a good portion of Whatever.call() (where the first letter of the variable name is capitalized) is being done for the purposes of a constructor trying to call a parent constructor without class syntax, whether it's with babel or without. That might be a good next thing for me to look into - I can take a look at a sample of Whatever.call() and see if they follow this prediction, and if so, make the assumption that most Whatever.call()s are being done for this purpose.

@js-choi
Copy link
Collaborator

js-choi commented Oct 7, 2021

@theScottyJam: A good heuristic might be to exclude base.call, Base.call, baseClass.call, or BaseClass.call.

> ./search.topcode.sh '\.call\b' | awk '/[bB]ase([cC]lass)?\.call/'
139612972	readable-stream-3.4.0.tgz/errors-browser.js:26:      return _Base.call(this, getMessage(arg1, arg2, arg3)) || this;
27506106	are-we-there-yet-1.1.5.tgz/tracker.js:6:  TrackerBase.call(this, name)
27506106	are-we-there-yet-1.1.5.tgz/tracker-group.js:8:  TrackerBase.call(this, name)
24117131	md5.js-1.3.5.tgz/index.js:9:  HashBase.call(this, 64)
23595018	ripemd160-2.0.2.tgz/index.js:44:  HashBase.call(this, 64)
23360946	asn1.js-5.0.1.tgz/lib/asn1/api.js:35:    base.call(this, entity);
23155285	elliptic-6.4.1.tgz/lib/elliptic/curve/short.js:12:  Base.call(this, 'short', conf);
23155285	elliptic-6.4.1.tgz/lib/elliptic/curve/mont.js:12:  Base.call(this, 'mont', conf);
23155285	elliptic-6.4.1.tgz/lib/elliptic/curve/edwards.js:17:  Base.call(this, 'edwards', conf);
23155285	elliptic-6.4.1.tgz/lib/elliptic/curve/edwards.js:12:  // NOTE: Important as we are creating point in Base.call()
22396297	browserify-des-1.0.2.tgz/index.js:19:  CipherBase.call(this)
22332516	create-hash-1.2.0.tgz/browser.js:9:  Base.call(this, 'digest')
22316210	create-hmac-1.1.7.tgz/legacy.js:11:  Base.call(this, 'digest')
22316210	create-hmac-1.1.7.tgz/browser.js:14:  Base.call(this, 'digest')
21954287	cipher-base-1.0.4.tgz/test.js:9:    CipherBase.call(this)
…

There are 2358 occurrences (4.4%) that match base.call, Base.call, baseClass.call, or BaseClass.call.


Incidentally, I’m fine with people trying to call a parent constructor without class syntax. Just because class syntax exists doesn’t mean that it’s invalid to do FooBase.call(this, opts). It’s just another use case…as long as a human, not a transpiler, wrote it.

@theScottyJam
Copy link
Contributor Author

Incidentally, I’m fine with people trying to call a parent constructor without class syntax. Just because class syntax exists doesn’t mean that it’s invalid to do FooBase.call(this, opts). It’s just another use case…as long as a human, not a transpiler, wrote it.

That's a pretty hard thing to gage though - most of that kind of code that was written by hand without class syntax was probably done because it was written before class syntax existed, or because they wanted to write it without a transpiler, not necessarily because they didn't like using class syntax. But, point taken that there's probably some valid uses of .call() intermixed in all of that.

@theScottyJam
Copy link
Contributor Author

theScottyJam commented Oct 8, 2021

I've started going through source code of different projects, one by one, to find more ways that .call() gets used. I've been findind some interesting stuff, that I'll report on later when I'm done.

One major use case for .call() I'm seeing is when the library author wishes to wrap a callback and preserve the this binding of that callback. I hadn't thought of this use case before, but it appears every once in a while.

Here's another interesting finding.

I wanted to see how many packages used .call() at all.

I presume the slim.topcode.1000.txt contains the first 1000 packages along with their dependencies? As they're many, many more packages listed in there than 1000. I took the dump you gave me of the first 1000 occurrences of .call() and figured out what the last package was that was listed in the dump (mocha). I then found that mocha package in slim.topcode.1000.txt and counted how many packages were analyzed from the start to this mocha package. There were 1001 (including mocha). Finally, I counted how many packages were listed in your dump. 421.

I then removed what I thought were irrelevant use cases from the dump to see how it would change. This includes uses of Object.prototype.hasOwnProperty.call, transpiler-created .call()s, Error.call(), comments, and similar stuff. I included stuff that just had hasOwnProperty.call without Object.prototype, because there's many cases where people plucked hasOwnProperty from the prototype first before calling it. I did a regex search in some of these cases to also find stuff like hasOwnProperty$2.call - rollup likes to put a dollarsign-number in front of local variable names.

This certainly doesn't get rid of all irrelevant use cases, just the ones that are easy to spot.

After all of that, I was left with 346 packages that used .call().

It seems only roughly 1/3 of packages (at most) would benefit from a bind syntax to replace .call(). I bet .bind() would have more occurrences, because I know it's common for people to want to extract methods from an instance.

Perhaps next I'll write a script that'll perform these calculations across the whole slim.topcode.1000.txt file, looking for both occurrences of .bind() and .call(), and seeing what percent of packages actually use them.

Perhaps it would be easier to do this kind of analysis if we had a dataset that only contained packages that were published within the last three years or something. Then we could see how many non-legacy uses of .call() exist. But, I'm not sure how easy that is to do. And, it still wouldn't account for things like Object.prototype.hasOwnProperty.call()

@ljharb
Copy link
Member

ljharb commented Oct 8, 2021

Benefiting 10% of users would be massive; benefiting a third would be quite high, so I’m encouraged by those results.

@js-choi
Copy link
Collaborator

js-choi commented Oct 8, 2021

Note that my dump was the first 10,000 occurrences, not the first 1,000 occurrences.

Anyways great job and I’ll take a closer look at some of these findings soon.

@js-choi
Copy link
Collaborator

js-choi commented Oct 8, 2021

> ./search.topcode.sh '\.call\b' | awk '/\/\/.*\.call/' | awk 'END { print NR }' # Exclude comments.
3933
> ./search.topcode.sh '\.call\b' | awk '/hasOwnProperty[a-zA-Z0-9_$]*\.call/' | awk 'END { print NR }'
71479
> ./search.topcode.sh '\.call\b' | awk '/Error\.call\( *this/' | awk 'END { print NR }'
1607
> ./search.topcode.sh '\.call\b' | awk '/[^a-zA-Z][A-Z][a-zA-Z0-9_$]*\.call\( *this/' | awk 'END { print NR }'
38147

These respectively are 0.8%, 14.3%, 0.3%, and 7.6% of 500084. (Note that the fourth search is for any occurrences of Foo.call(this, where Foo is a capitalized identifier, possibly preceded by a $ or _, such as postcss’s _Container.call(this, defaults). These are a superset of the third search, for Error.call(this occurrences.)

Even excluding all of these, .call occurrences way exceed those of console.log and friends, which is also reassuring. Remember, we’re just verifying that non-obsoleted uses of .call/.bind are used “frequently”(around the same ballpark as frequently used functions like console.log) and broadly characterizing why people do use them.

This review has already been very valuable for:

  • Excluding “obsolete” uses such as using hasOwnProperty or explicitly calling supers. (I am using calls with capitalized functions on this/[^a-zA-Z][A-Z][a-zA-Z0-9_$]*\.call\( *this/—as a surrogate for explicitly calling supers).
  • Confirming that many codebases use Array.prototype methods on array-like objects, as well as other prototype-extracted methods on various objects (like Object.prototype methods on unknown objects). I would love specific examples of both these cases.
  • Confirming that many codebases use this objects as “context” objects whose ergonomics are improved by making them call receivers.
  • Confirming that the sheer magnitude of non-obsolete uses is huge and might dwarf even that of console.log.

@theScottyJam
Copy link
Contributor Author

My opinions on this matter have certainly shifted as I've looked over all of this stuff. The uses of .call() are much more frequent than I would have thought.

Certainly, I believe we've proved the point here that they're used quite often for non-obsolete use cases. I'm seeing similar results as I look over different examples by hand. I probably want to spend one more morning looking at more examples by hand - the information is intriguing to me, and I'm hoping to find as many different uses for it as I can. After which, I'll see if I can summarize what I've been learning and interesting things I've found as I've looked at specific usage scenarios.

I would love specific examples of both these cases.

I can pluck some of those out for you.

@js-choi
Copy link
Collaborator

js-choi commented Oct 10, 2021

(FYI: I actually would love a complete list of the array-like uses you find, because actually affects another proposal’s issue: tc39/proposal-array-from-async#7, whether Array.fromAsync should accept such array-like objects like how Array.from does.)

@theScottyJam
Copy link
Contributor Author

theScottyJam commented Oct 10, 2021

Alright, so here's some finer details about what I've found.

I analyzed the first 310 occurrences of .call() from the dump file. Of those, I categorized 113 of them using a script that simply does text search. The remaining 197 I looked over by hand, peering into the source code to understand the context of many of them.

This was the categorization logic I used for the automatic categorization (in python):

def auto_determin_type(line):
    if '.hasOwnProperty.call' in line: return 'ownProp'
    if 'Object.prototype.toString.call' in line: return 'objectProto'
    if '{}.toString' in line: return 'objectProto'
    if '.prototype.propertyIsEnumerable' in line: return 'objectProto'
    if re.search(r'\d+:\s*//', line): return 'irrelevant'
    if re.search(r'\d+:\s*\*', line): return 'irrelevant'
    if 'Error.call' in line: return 'super'

    # Bundlers/transpilers/compilers
    if '_super.call(' in line: return 'transpiled' # Result of babel transpiling super()
    if '_super.prototype.' in line: return 'transpiled' # Result of typescript compiling super.f() calls
    if '_getPrototypeOf' in line: return 'transpiled' # Artifact of babel transpiling.
    if '_possibleConstructorReturn' in line: return 'transpiled' # Artifact of babel transpiling.
    if '__super__' in line: return 'transpiled' # Artifacts of CoffeeScript compilation, or people creating classes by hand.
    if 'WEBPACK VAR INJECTION' in line: return 'transpiled' # Output of webpack merging files.

    # Operating on array-like objects. (Some of these operated on non-array classes, like Uint8Array)
    # (These didn't actually capture anything in the few hundred I was analyzing)
    if '.slice.call' in line: return 'arrayLike'
    if '.splice.call' in line: return 'arrayLike'
    if '.indexOf.call' in line: return 'arrayLike'
    if '.lastIndexOf.call' in line: return 'arrayLike'
    if re.search(r'Array\.prototype\.[a-zA-Z]+\.call', line): return 'arrayLike'
    if re.search(r'\[\].[a-zA-Z]+.call', line): return 'arrayLike'

    # The following seem to either be picked off of the prototype and/or polyfilled. (for prototypal protection, or maybe tree shaking)
    if 'hasOwnProperty.call' in line: return 'protect'
    if 'hasOwnProperty[$\d]+.call' in line: return 'protect'
    if 'propertyIsEnumerable.call' in line: return 'protect'
    if 'toString.call' in line: return 'protect'
    if 'toString[$\d]+\.call' in line: return 'protect'
    if 'bind.call' in line: return 'protect'
    if 'hasOwn.call' in line: return 'protect'
    if 'slice.call' in line: return 'protect'
    if 'slice[$\d]+\.call' in line: return 'protect'
    if 'splice.call' in line: return 'protect'
    if 'indexOf.call' in line: return 'protect'
    if 'forEach.call' in line: return 'protect'
    if 'call.bind' in line: return 'protect'

Here are the results I got from the auto-categorization:

ownProp: 24          // Calling Object.prototype.hasOwnProperty directly
arrayLike: 8          // Calling functions directly on the Array prototype
objectProto: 1          // Calling functions directly on Object.prototype (except Object.prototype.hasOwnProperty)
irrelevant: 3          // Comments
protect: 77          // These are scenarios where functions were plucked off of the prototype and called directly. It won't be categorized here if it falls into any of the above categories.

Of the 197 I looked over by hand, here's how I had categorized them:

// These are the same categories as above
ownProp: 7
arrayLike: 8
objectProto: 9
irrelevant: 19          // Comments and duplicate lines of code
protect: 17

unknown: 1          // I gave up trying to figure out this one
monkeypatch: 14          // People were monkey patching objects, and had to use .call() to call the original version.
multistepCall: 24          // For whatever reason, they split the logic of doing `obj.f()` into two steps - this is further explained below.
callback: 7          // They were wanting to pass custom information to a callback - usually this was unrelated to the concept of methods and instances, it's just a custom "this" value.
super: 26          // These were places where people were performing inheritance without es classes
useless?: 2          // As far as I could tell, these .call()s were not needed.
wraps: 7          // A function that was supposed to accurately wrap another function, which means it also had to forward the receiver object to the wrapped function.
forwardThis: 30          // Logic that primarily exists to allow the API user to supply a "this" value to their callback. There's different variations of this.
helperFn: 15          // They were using a helper function that was not kept on the instance's prototype, so this passed "this" in via .call(). Presumably this was done to keep the helpers private.
etc: 11          // Other unique scenarios

The "multistepCall" category listed above is for scenarios where obj.f() was split up into multiple steps for whatever reason. Some simplified examples:

// Example 1
const method = obj.f ?? obj.g
method.call(obj, ...)

// Example 2
assertFunction(obj.f).call(obj, f)

Here are some interesting specific scenarios I found, many of which didn't fit well into any of the specific categories above (so they went in the "etc" category).

readable-stream was doing Buffer.prototype.copy.call(...). It took me a bit to figure out why, but I think I nailed it down. EcmaScript provides a Uint8Array. Node had previously provided a Buffer class, that they've now made conform to Uint8Array as much as possible, i.e. Buffer is (almost) a subclass of Uint8Array. I think readable-stream's APIs are also designed to work with both Uint8Arrays and Buffers. They wanted to use the copy method, which was provided by Buffer, but not by Uint8Array, so they did Buffer.prototype.copy.call(...) to call the copy method on what could potentially be a Uint8Array.

yargs did something interesting. They had this piece of code: value = value.call(). Any idea why anyone would ever use .call() without any parameters? Isn't that line of code exactly the same as value = value()? Unless, they were expecting someone to have monkey patched or shadowed .call() to be something different?

core-js used .call() a whole lot. At one point, they were defining a function that was intended to be applied to a built-in class's prototype. Elsewhere, they needed to use the logic from that function. Since it was possible that this first function never got placed on the built-in prototype (depending on which polyfills the user wanted applied), they needed to import the function stand-alone and .call() it to use it properly.


Here are all of the scenarios I ran into where an array method was being invoked via .call():

 1. return arrayToLocaleString.apply(TO_LOCALE_BUG ? arraySlice.call(aTypedArray(this)) : aTypedArray(this), arguments); // core-js-3.1.3.tgz/modules/es.typed-array.to-locale-string.js:24
 2. return arrayJoin.call(this);                              // core-js-3.1.3.tgz/modules/es.typed-array.to-string.js:13
 3. arrayToLocaleString.call(new Int8Array(1));               // core-js-3.1.3.tgz/modules/es.typed-array.to-locale-string.js:13
 4. return arraySort.call(aTypedArray(this), comparefn);      // core-js-3.1.3.tgz/modules/es.typed-array.sort.js:10
 5. return arrayKeys.call(aTypedArray(this));                 // core-js-3.1.3.tgz/modules/es.typed-array.iterator.js:31
 6. return arrayEntries.call(aTypedArray(this));              // core-js-3.1.3.tgz/modules/es.typed-array.iterator.js:26
 7. return arrayValues.call(aTypedArray(this));               // core-js-3.1.3.tgz/modules/es.typed-array.iterator.js:20
 8. return arrayCopyWithin.call(aTypedArray(this), target, start, arguments.length > 2 ? arguments[2] : undefined); // core-js-3.1.3.tgz/modules/es.typed-array.copy-within.js:10
 9. var args = Array.prototype.slice.call(arguments, 0)       // semver-6.1.1.tgz/semver.js:10
10. const args = [].slice.call(callerArguments)               // yargs-13.2.4.tgz/lib/argsert.js:22
11. var options = [].slice.call(this.options);                // commander-2.20.0.tgz/index.js:986
12. const args = [].slice.call(arguments);                    // chalk-2.4.2.tgz/index.js:35
13. const args = [].slice.call(arguments, 2);                 // chalk-2.4.2.tgz/index.js:213
14. return [].slice.call(arguments, 1).join(' ');             // chalk-2.4.2.tgz/index.js:210
15. argv: [].slice.call(system.args),                         // esprima-4.0.1.tgz/bin/esvalidate.js:57

Example 1-8 is all from core-js wanting to use array methods on type array instances, to help implement polyfills for those type arrays.
Example 9 and 12-14 are all legacy cases of converting arguments to an array.
Example 10 is found in a helper function for yargs, and will convert an array-like argument to an array. However, every place in the codebase that uses this helper only passes in an array literal, so this functionality isn't actually used.
Example 11 is commander converting a property to an array. From what I can tell, this property will only be an array to begin with, so I'm not sure why this was happening. Perhaps they were expecting the end-user to override the property with a custom array-like value, but this particular property seemed to be undocumented.
Example 15 is turning system.args from phantom.js into an array. From the documentation, I think it's already supposed to be an array (see here).

There's a lot of uses of Array.prototype.slice.call() that seem to not be doing anything - perhaps I'm just missing something important about how it works. Or, perhaps it's been a popular practice to program defensively and make sure whatever you have really is an array. Dunno.

Those 15 scenarios of Array.prototype.whatever.call() are just what I found from doing a deep analysis of these 310 .call() scenarios. I plan on looking through other examples of using .call() on Array on a different day - I can just report what I find in the ticket you linked to. It seems like none of the above examples are that helpful for the purposes of Array.fromAsync().


Here are all of the scenarios I ran into while looking for usages of .call() on Object.prototype (I'm omitting Object.prototype.hasOwnProperty.call()):

return nativeObjectToString.call(value);                           // lodash-4.17.11.tgz/lodash.js:6540:
var result = nativeObjectToString.call(value);                     // lodash-4.17.11.tgz/lodash.js:5999:
value = nativeObjectToString.call(value);                          // lodash-4.17.11.tgz/lodash.js:13249:
value = nativeObjectToString.call(value);                          // lodash-4.17.11.tgz/lodash.js:13214:
value = nativeObjectToString.call(value);                          // lodash-4.17.11.tgz/invertBy.js:46:
value = nativeObjectToString.call(value);                          // lodash-4.17.11.tgz/invert.js:36:
return nativeObjectToString.call(value);                           // lodash-4.17.11.tgz/core.js:1421:
return nativeObjectToString.call(value);                           // lodash-4.17.11.tgz/_objectToString.js:19:
var result = nativeObjectToString.call(value);                     // lodash-4.17.11.tgz/_getRawTag.js:35:
return Object.prototype.toString.call(obj) === '[object RegExp]';  // qs-6.7.0.tgz/lib/utils.js:205:

All of these are examples of trying to safely stringify an object. In qs's case, it was done for the purpose of trying to check if the object was a regular expression. Here's a couple of complete examples of real-world scenarios for Object.prototype.toString.call (in lodash, nativeObjectToString is Object.prototype.toString, they also appear to participate in global mutation protection, which makes these examples a bit harder to read).

/**
 * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
 *
 * @private
 * @param {*} value The value to query.
 * @returns {string} Returns the raw `toStringTag`.
 */
function getRawTag(value) {
  var isOwn = hasOwnProperty.call(value, symToStringTag),
      tag = value[symToStringTag];

  try {
    value[symToStringTag] = undefined;
    var unmasked = true;
  } catch (e) {}

  var result = nativeObjectToString.call(value);
  if (unmasked) {
    if (isOwn) {
      value[symToStringTag] = tag;
    } else {
      delete value[symToStringTag];
    }
  }
  return result;
}
/**
 * Converts `value` to a string using `Object.prototype.toString`.
 *
 * @private
 * @param {*} value The value to convert.
 * @returns {string} Returns the converted string.
 */
function objectToString(value) {
  return nativeObjectToString.call(value);
}

And here's qs's complete example:

var isRegExp = function isRegExp(obj) {
    return Object.prototype.toString.call(obj) === '[object RegExp]';
};

These specific examples probably aren't the greatest for sharing around, they mostly seem to center on brand-checking, which I know that @ljharb is hoping to eventually have as its own proposal. I can also spend a bit of time to dig up better examples of using Object.prototype methods on a different day.

Anyways, that's my deeper analysis of the first 300-ish occurrences of .call() from your file. Let me know if there's anything other specific details you would like me to share about my findings. In the mean time, I'll get back to you soon on better examples for Object.prototype, and anything I can find that pertains to Array.fromAsync.

It's been really interesting looking into all of this and seeing the different ways it gets used.
I probably went in a lot of different directions with this that weren't as important to the task at hand, but it has been fun.

@js-choi
Copy link
Collaborator

js-choi commented Oct 10, 2021

@theScottyJam: This is wonderful. Thank you so much for your hard work.

It’s going to take a few days for me to properly process these results, particularly the automatically sorted stuff, since we should be able to run searches on all of those categories across the entire dataset. But with this the case will become even clearer.

Would you be able to publish your 197 categorizations by hand, maybe inside a <details> element in a comment?

@theScottyJam
Copy link
Contributor Author

theScottyJam commented Oct 10, 2021

Sure thing.

There are my raw notes for each entry. I prefixed each category with "##", e.g. ##callback: .... If I had some additional comment I wanted to add about that line, I placed a "*" next to it, like this ##callback:* ..., then at the end of the line, you would see my comment, prefixed with //#, and/or you may find additional information in notes I had to the side that I'll also post. The weird choice of characters for this was to allow me to run a script through it afterwards to look at what was done.

I just went in order through your dump file, and stopped in the middle of the core-js package. They had a lot more usages of .call() that I didn't look at. I also don't claim 100% accuracy on this all, I'm sure I misunderstood the intent of the code in some scenarios :).

// -- KEY --
// ##callback: Binding a this value to a callback to provide extra context.
// ##super: Using .call() to call a super class's constructor or method
// ##objectProto: Using methods on Object.prototype safely (unused)
// ##arrayLike: Using methods on Array.prototype for array-like objects
// ##ownProp: using Object.prototype.hasOwnProperty
// ##protect: They're protecting against prototype mutation
// ##unknown
// ##useless?
// ##etc: Description noted elsewhere
// ##irrelevant: comments, duplicates, etc
// ##wraps: Preserves the "this" when wrapping a function
// ##forwardThis: Logic that allows an end-user to forward a this-binding to a callback.
// ##monkeypatch: Similar purpose to "wraps", expect you're doing the wrapping on the spot.
// ##helperFn: A helper function that's not on the prototype. Presumably this is done to keep the function private.
// ##multistepCall: e.g. `const method = obj.f ?? obj.g; method.call(obj, ...)`. or `assertFunction(obj.f).call(obj, f)`

##unknown: 135308610	source-map-0.7.3.tgz/lib/source-map-consumer.js:382:        aCallback.call(context, mapping);
##protect: 91794154	lodash-4.17.11.tgz/lodash.js:6773:          return funcToString.call(func);
##objectProto: 91794154	lodash-4.17.11.tgz/lodash.js:6540:      return nativeObjectToString.call(value);
##objectProto: 91794154	lodash-4.17.11.tgz/lodash.js:5999:      var result = nativeObjectToString.call(value);
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:4240:        return symbolToString ? symbolToString.call(value) : ''; //# Duplicate
##protect: 91794154	lodash-4.17.11.tgz/lodash.js:1458:      funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
##protect: 91794154	lodash-4.17.11.tgz/lodash.js:1451:    var objectCtorString = funcToString.call(Object);
##objectProto: 91794154	lodash-4.17.11.tgz/lodash.js:13249:        value = nativeObjectToString.call(value);
##objectProto: 91794154	lodash-4.17.11.tgz/lodash.js:13214:        value = nativeObjectToString.call(value);
##protect: 91794154	lodash-4.17.11.tgz/lodash.js:12047:        funcToString.call(Ctor) == objectCtorString;
##protect: 91794154	lodash-4.17.11.tgz/isPlainObject.js:59:    funcToString.call(Ctor) == objectCtorString;
##protect: 91794154	lodash-4.17.11.tgz/isPlainObject.js:19:var objectCtorString = funcToString.call(Object);
##objectProto: 91794154	lodash-4.17.11.tgz/invertBy.js:46:    value = nativeObjectToString.call(value);
##objectProto: 91794154	lodash-4.17.11.tgz/invert.js:36:    value = nativeObjectToString.call(value);
##objectProto: 91794154	lodash-4.17.11.tgz/core.js:1421:    return nativeObjectToString.call(value);
##protect: 91794154	lodash-4.17.11.tgz/_toSource.js:17:      return funcToString.call(func);
##objectProto: 91794154	lodash-4.17.11.tgz/_objectToString.js:19:  return nativeObjectToString.call(value);
##objectProto: 91794154	lodash-4.17.11.tgz/_getRawTag.js:35:  var result = nativeObjectToString.call(value);
##protect: 91794154	lodash-4.17.11.tgz/_baseToString.js:31:    return symbolToString ? symbolToString.call(value) : '';
##protect: 91794154	lodash-4.17.11.tgz/_baseIsNative.js:27:  funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
##monkeypatch: 79340182	iconv-lite-0.4.24.tgz/lib/extend-node.js:43:                return original.SlowBufferToString.call(this, encoding, start, end);
##monkeypatch: 79340182	iconv-lite-0.4.24.tgz/lib/extend-node.js:118:                return original.BufferToString.call(this, encoding, start, end);
##etc:* 78366653	core-js-3.1.3.tgz/modules/es.typed-array.to-string.js:11:if (fails(function () { arrayToString.call({}); })) { //# Feature detection
##arrayLike: 78366653	core-js-3.1.3.tgz/modules/es.typed-array.to-locale-string.js:24:  return arrayToLocaleString.apply(TO_LOCALE_BUG ? arraySlice.call(aTypedArray(this)) : aTypedArray(this), arguments);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/es.typed-array.slice.js:18:  var list = arraySlice.call(aTypedArray(this), start, end);
##callback: 139612972	readable-stream-3.4.0.tgz/lib/internal/streams/end-of-stream.js:68:      return callback.call(stream, err);
##callback: 139612972	readable-stream-3.4.0.tgz/lib/internal/streams/end-of-stream.js:63:      return callback.call(stream, err);
##callback: 139612972	readable-stream-3.4.0.tgz/lib/internal/streams/end-of-stream.js:55:    callback.call(stream, err);
##callback: 139612972	readable-stream-3.4.0.tgz/lib/internal/streams/end-of-stream.js:51:    if (!writable) callback.call(stream);
##callback: 139612972	readable-stream-3.4.0.tgz/lib/internal/streams/end-of-stream.js:43:    if (!readable) callback.call(stream);

##callback 177726827	debug-4.1.1.tgz/src/common.js:111:			createDebug.formatArgs.call(self, args);
##callback 177726827	debug-4.1.1.tgz/src/common.js:101:					match = formatter.call(self, val);

##super 139612972	readable-stream-3.4.0.tgz/lib/_stream_writable.js:248:  Stream.call(this);
##super 139612972	readable-stream-3.4.0.tgz/lib/_stream_transform.js:186:  Duplex.prototype._destroy.call(this, err, function (err2) {
##super 139612972	readable-stream-3.4.0.tgz/lib/_stream_transform.js:139:  return Duplex.prototype.push.call(this, chunk, encoding);
##super 139612972	readable-stream-3.4.0.tgz/lib/_stream_transform.js:100:  Duplex.call(this, options);
##super 139612972	readable-stream-3.4.0.tgz/lib/_stream_readable.js:816:  var res = Stream.prototype.removeListener.call(this, ev, fn);
##super 139612972	readable-stream-3.4.0.tgz/lib/_stream_readable.js:786:  var res = Stream.prototype.on.call(this, ev, fn);
##super 139612972	readable-stream-3.4.0.tgz/lib/_stream_readable.js:183:  Stream.call(this);
##super 139612972	readable-stream-3.4.0.tgz/lib/_stream_passthrough.js:34:  Transform.call(this, options);
##super 139612972	readable-stream-3.4.0.tgz/lib/_stream_duplex.js:61:  Writable.call(this, options);
##super 139612972	readable-stream-3.4.0.tgz/lib/_stream_duplex.js:60:  Readable.call(this, options);
##etc: 139612972	readable-stream-3.4.0.tgz/lib/internal/streams/buffer_list.js:16:  Buffer.prototype.copy.call(src, target, offset);
##super 139612972	readable-stream-3.4.0.tgz/errors-browser.js:26:      return _Base.call(this, getMessage(arg1, arg2, arg3)) || this;
##etc: 139612972	readable-stream-3.4.0.tgz/lib/_stream_writable.js:236:  if (!isDuplex && !realHasInstance.call(Writable, this)) return new Writable(options);
##etc: 139612972	readable-stream-3.4.0.tgz/lib/_stream_writable.js:214:      if (realHasInstance.call(this, object)) return true;

##super: 117497282	yargs-13.2.4.tgz/yargs.js:262:      value = value.call()
##etc: 117497282	yargs-13.2.4.tgz/yargs.js:262:      value = value.call()

##useless?:* 98425737	async-3.0.1.tgz/reflectAll.js:88:            results[key] = _reflect2.default.call(this, tasks[key]); //# uses .call() on a function that doesn't utilize "this"

##etc: 109400789	chalk-2.4.2.tgz/index.js:88:				return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model);
##etc: 109400789	chalk-2.4.2.tgz/index.js:68:		return build.call(this, this._styles || [], true, 'visible');
##etc: 109400789	chalk-2.4.2.tgz/index.js:61:			return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, key);
##etc: 109400789	chalk-2.4.2.tgz/index.js:111:				return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model);

##protect:* 92901375	qs-6.7.0.tgz/test/stringify.js:543:            'a=' + Date.prototype.toISOString.call(mutatedDate).replace(/:/g, '%3A'), //# Used .call() to verify prototypal protection worked in a test case.
##ownProp: 92901375	qs-6.7.0.tgz/lib/utils.js:92:        if (has.call(acc, key)) {
##ownProp: 92901375	qs-6.7.0.tgz/lib/utils.js:75:            if (has.call(target, i)) {
##ownProp: 92901375	qs-6.7.0.tgz/lib/utils.js:54:            if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) {
##protect:* 92901375	qs-6.7.0.tgz/lib/stringify.js:41:        return toISO.call(date);
##ownProp: 92901375	qs-6.7.0.tgz/lib/stringify.js:162:        if (!has.call(formats.formatters, opts.format)) {
##ownProp: 92901375	qs-6.7.0.tgz/lib/parse.js:90:        if (has.call(obj, key)) {
##ownProp: 92901375	qs-6.7.0.tgz/lib/parse.js:172:        if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) {
##ownProp: 92901375	qs-6.7.0.tgz/lib/parse.js:158:        if (!options.plainObjects && has.call(Object.prototype, parent)) {
##protect:* 92901375	qs-6.7.0.tgz/lib/formats.js:10:            return replace.call(value, percentTwenties, '+');

##wraps: 98425737	async-3.0.1.tgz/internal/initialParams.js:10:        return fn.call(this, args, callback);

##super: 91946480	postcss-7.0.16.tgz/lib/rule.js:37:    _this = _Container.call(this, defaults) || this;
##super: 91946480	postcss-7.0.16.tgz/lib/root.js:49:    var nodes = _Container.prototype.normalize.call(this, child);
##super: 91946480	postcss-7.0.16.tgz/lib/root.js:45:    return _Container.prototype.removeChild.call(this, child);
##super: 91946480	postcss-7.0.16.tgz/lib/root.js:30:    _this = _Container.call(this, defaults) || this;
##super: 91946480	postcss-7.0.16.tgz/lib/declaration.js:31:    _this = _Node.call(this, defaults) || this;
##super: 91946480	postcss-7.0.16.tgz/lib/css-syntax-error.js:73:    _this = _Error.call(this, message) || this;
##super: 91946480	postcss-7.0.16.tgz/lib/container.js:458:    _Node.prototype.cleanRaws.call(this, keepBetween);
##super: 91946480	postcss-7.0.16.tgz/lib/comment.js:28:    _this = _Node.call(this, defaults) || this;
##super: 91946480	postcss-7.0.16.tgz/lib/at-rule.js:66:    return (_Container$prototype$2 = _Container.prototype.prepend).call.apply(_Container$prototype$2, [this].concat(children));
##super: 91946480	postcss-7.0.16.tgz/lib/at-rule.js:54:    return (_Container$prototype$ = _Container.prototype.append).call.apply(_Container$prototype$, [this].concat(children));
##super: 91946480	postcss-7.0.16.tgz/lib/at-rule.js:38:    _this = _Container.call(this, defaults) || this;

##protect: 91794154	lodash-4.17.11.tgz/template.js:218:        "function print() { __p += __j.call(arguments, '') }\n"
##protect: 91794154	lodash-4.17.11.tgz/reverse.js:31:  return array == null ? array : nativeReverse.call(array);
##wraps: 91794154	lodash-4.17.11.tgz/overArgs.js:55:      args[index] = transforms[index].call(this, args[index]);
##etc: 91794154	lodash-4.17.11.tgz/result.js:51:    object = isFunction(value) ? value.call(object) : value;
##wraps: 91794154	lodash-4.17.11.tgz/negate.js:34:      case 3: return !predicate.call(this, args[0], args[1], args[2]);
##wraps: 91794154	lodash-4.17.11.tgz/negate.js:33:      case 2: return !predicate.call(this, args[0], args[1]);
##wraps: 91794154	lodash-4.17.11.tgz/negate.js:32:      case 1: return !predicate.call(this, args[0]);
##wraps: 91794154	lodash-4.17.11.tgz/negate.js:31:      case 0: return !predicate.call(this);
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:7894:      return array == null ? array : nativeReverse.call(array); //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:7594:      return array == null ? '' : nativeJoin.call(array, separator); //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:5756:            return symbolValueOf.call(object) == symbolValueOf.call(other); //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:5124:            result = funcs[index].call(this, result); //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:476:      case 3: return func.call(thisArg, args[0], args[1], args[2]); //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:475:      case 2: return func.call(thisArg, args[0], args[1]); //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:474:      case 1: return func.call(thisArg, args[0]); //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:473:      case 0: return func.call(thisArg); //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:4570:      return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {}; //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:17107:}.call(this)); //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:14859:            "function print() { __p += __j.call(arguments, '') }\n" //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:13641:        object = isFunction(value) ? value.call(object) : value; //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:10664:          args[index] = transforms[index].call(this, args[index]); //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:10594:          case 3: return !predicate.call(this, args[0], args[1], args[2]); //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:10593:          case 2: return !predicate.call(this, args[0], args[1]); //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:10592:          case 1: return !predicate.call(this, args[0]); //# duplicate
##irrelevant:* 91794154	lodash-4.17.11.tgz/lodash.js:10591:          case 0: return !predicate.call(this); //# duplicate
##protect: 91794154	lodash-4.17.11.tgz/join.js:23:  return array == null ? '' : nativeJoin.call(array, separator);
##useless?: 91794154	lodash-4.17.11.tgz/core.js:3854:}.call(this));
##irrelevant:* 91794154	lodash-4.17.11.tgz/core.js:3358:    return isFunction(value) ? value.call(object) : value; //# duplicate
##protect: 91794154	lodash-4.17.11.tgz/_equalByTag.js:106:        return symbolValueOf.call(object) == symbolValueOf.call(other);
##wraps: 91794154	lodash-4.17.11.tgz/_createFlow.js:71:        result = funcs[index].call(this, result);
##protect: 91794154	lodash-4.17.11.tgz/_cloneSymbol.js:15:  return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
##forwardThis: 91794154	lodash-4.17.11.tgz/_apply.js:16:    case 3: return func.call(thisArg, args[0], args[1], args[2]);
##forwardThis: 91794154	lodash-4.17.11.tgz/_apply.js:15:    case 2: return func.call(thisArg, args[0], args[1]);
##forwardThis: 91794154	lodash-4.17.11.tgz/_apply.js:14:    case 1: return func.call(thisArg, args[0]);
##forwardThis: 91794154	lodash-4.17.11.tgz/_apply.js:13:    case 0: return func.call(thisArg);

##forwardThis: 90905746	ajv-6.10.0.tgz/lib/dotjs/ref.js:86:      out += ' ' + ($refCode) + '.call(this, ';
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/dotjs/custom.js:79:    out += '  ' + ($validateCode) + '.call( ';
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/resolve.js:95:  return getJsonPointer.call(this, p, baseId, root.schema, root);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/resolve.js:77:      return resolveRecursive.call(this, root, refVal, p);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/resolve.js:54:        : compile.call(this, schema, root, undefined, baseId);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/resolve.js:50:    v = schema.validate || compile.call(this, schema.schema, root, undefined, baseId);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/resolve.js:41:  var res = resolveSchema.call(this, root, ref);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/resolve.js:31:    else return resolve.call(this, compile, root, refVal);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/resolve.js:134:          var res = resolveSchema.call(this, root, $ref);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/resolve.js:109:    return getJsonPointer.call(this, parsedRef, baseId, schema, root);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/resolve.js:102:  var res = resolveSchema.call(this, root, ref);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/index.js:84:      return compile.call(self, _schema, _root, localRefs, baseId);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/index.js:69:    endCompiling.call(this, schema, root, baseId);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/index.js:47:  var c = checkCompiling.call(this, schema, root, baseId);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/index.js:338:  var i = compIndex.call(this, schema, root, baseId);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/index.js:317:  var index = compIndex.call(this, schema, root, baseId);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/index.js:287:      validate = inline.call(self, it, rule.keyword, schema, parentSchema);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/index.js:284:      validate = macro.call(self, schema, parentSchema, it);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/index.js:282:      validate = compile.call(self, schema, parentSchema, it);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/index.js:195:            : compile.call(self, localSchema, root, localRefs, baseId);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/index.js:189:    var v = resolve.call(self, localCompile, root, ref);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/compile/async.js:48:            ? compileAsync.call(self, { $ref: $schema }, true)
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/ajv.js:347:  try { v = compileSchema.call(this, schemaObj.schema, root, schemaObj.localRefs); }
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/ajv.js:308:  var localRefs = resolve.ids.call(this, schema);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/ajv.js:216:    var v = compileSchema.call(self, schema, root, undefined, baseId);
##forwardThis: 90905746	ajv-6.10.0.tgz/lib/ajv.js:211:  var res = resolve.schema.call(self, { schema: {} }, ref);

##monkeypatch: 83868660	cross-spawn-6.0.5.tgz/lib/enoent.js:30:                return originalEmit.call(cp, 'error', err);

##super: 79340182	iconv-lite-0.4.24.tgz/lib/streams.js:81:    Transform.call(this, options);
##super: 79340182	iconv-lite-0.4.24.tgz/lib/streams.js:34:    Transform.call(this, options);
##monkeypatch: 79340182	iconv-lite-0.4.24.tgz/lib/extend-node.js:81:                return original.SlowBufferWrite.call(this, string, offset, length, encoding);
##monkeypatch: 79340182	iconv-lite-0.4.24.tgz/lib/extend-node.js:147:                return original.BufferWrite.call(this, string, _offset, _length, _encoding);
##monkeypatch: 79340182	iconv-lite-0.4.24.tgz/lib/extend-node.js:106:                return original.BufferByteLength.call(this, str, encoding);
##super: 79340182	iconv-lite-0.4.24.tgz/encodings/internal.js:56:    StringDecoder.call(this, codec.enc);

##helperFn: 78366653	core-js-3.1.3.tgz/modules/web.url.js:982:  return serializeURL.call(this);
##helperFn: 78366653	core-js-3.1.3.tgz/modules/web.url.js:976:  return serializeURL.call(this);
##helperFn: 78366653	core-js-3.1.3.tgz/modules/web.url.js:768:    that.hash = getHash.call(that);
##helperFn: 78366653	core-js-3.1.3.tgz/modules/web.url.js:767:    that.searchParams = getSearchParams.call(that);
##helperFn: 78366653	core-js-3.1.3.tgz/modules/web.url.js:766:    that.search = getSearch.call(that);
##helperFn: 78366653	core-js-3.1.3.tgz/modules/web.url.js:765:    that.pathname = getPathname.call(that);
##helperFn: 78366653	core-js-3.1.3.tgz/modules/web.url.js:764:    that.port = getPort.call(that);
##helperFn: 78366653	core-js-3.1.3.tgz/modules/web.url.js:763:    that.hostname = getHostname.call(that);
##helperFn: 78366653	core-js-3.1.3.tgz/modules/web.url.js:762:    that.host = getHost.call(that);
##helperFn: 78366653	core-js-3.1.3.tgz/modules/web.url.js:761:    that.password = getPassword.call(that);
##helperFn: 78366653	core-js-3.1.3.tgz/modules/web.url.js:760:    that.username = getUsername.call(that);
##helperFn: 78366653	core-js-3.1.3.tgz/modules/web.url.js:759:    that.protocol = getProtocol.call(that);
##helperFn: 78366653	core-js-3.1.3.tgz/modules/web.url.js:758:    that.origin = getOrigin.call(that);
##helperFn: 78366653	core-js-3.1.3.tgz/modules/web.url.js:757:    that.href = serializeURL.call(that);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/web.url-search-params.js:136:        iterator = iteratorMethod.call(init);
##helperFn: 78366653	core-js-3.1.3.tgz/modules/esnext.string.replace-all.js:35:        return $replaceAll.call(searchValue, O, replaceValue);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.string.replace-all.js:33:        return replacer.call(searchValue, O, replaceValue);
##etc: 78366653	core-js-3.1.3.tgz/modules/esnext.string.replace-all.js:17:  var flags = String('flags' in RegExpPrototype ? rx.flags : getRegExpFlags.call(rx));
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.set.symmetric-difference.js:19:      remover.call(newSet, value) || adder.call(newSet, value);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.set.map.js:22:      adder.call(newSet, boundFunction(value = step.value, value, set));
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.set.is-superset-of.js:17:      if (hasCheck.call(set, value) === false) return BREAK;
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.set.is-subset-of.js:24:      if (hasCheck.call(otherSet, value) === false) return BREAK;
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.set.is-disjoint-from.js:17:      if (hasCheck.call(set, value) === true) return BREAK;
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.set.intersection.js:19:      if (hasCheck.call(set, value)) adder.call(newSet, value);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.set.filter.js:22:      if (boundFunction(value = step.value, value, set)) adder.call(newSet, value);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.set.difference.js:18:      remover.call(newSet, value);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.promise.any.js:30:        promiseResolve.call(C, promise).then(function (value) {
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.promise.all-settled.js:26:        promiseResolve.call(C, promise).then(function (value) {
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.observable.js:61:    if (start = getMethod(observer.start)) start.call(observer, this);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.observable.js:173:      var observable = anObject(method.call(x));
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.observable.js:137:        if (m) m.call(observer);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.observable.js:122:        if (m) m.call(observer, value);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.observable.js:108:        if (m) m.call(observer, value);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.map.map-values.js:23:      setter.call(newMap, key = entry[0], boundFunction(entry[1], key, map));
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.map.map-keys.js:23:      setter.call(newMap, boundFunction(value = entry[1], entry[0], map), value);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.map.key-by.js:14:      setter.call(newMap, keyDerivative(element), element);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.map.group-by.js:18:      else get.call(newMap, derivedKey).push(element);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.map.group-by.js:17:      if (!has.call(newMap, derivedKey)) set.call(newMap, derivedKey, [element]);
##multistepCall: 78366653	core-js-3.1.3.tgz/modules/esnext.map.filter.js:23:      if (boundFunction(value = entry[1], key = entry[0], map)) setter.call(newMap, key, value);
##monkeypatch: 78366653	core-js-3.1.3.tgz/modules/es.weak-map.js:63:      } else nativeSet.call(this, key, value);
##monkeypatch: 78366653	core-js-3.1.3.tgz/modules/es.weak-map.js:62:        nativeHas.call(this, key) ? nativeSet.call(this, key, value) : state.frozen.set(key, value);
##monkeypatch: 78366653	core-js-3.1.3.tgz/modules/es.weak-map.js:56:      } return nativeGet.call(this, key);
##monkeypatch: 78366653	core-js-3.1.3.tgz/modules/es.weak-map.js:55:        return nativeHas.call(this, key) ? nativeGet.call(this, key) : state.frozen.get(key);
##monkeypatch: 78366653	core-js-3.1.3.tgz/modules/es.weak-map.js:49:      } return nativeHas.call(this, key);
##monkeypatch: 78366653	core-js-3.1.3.tgz/modules/es.weak-map.js:48:        return nativeHas.call(this, key) || state.frozen.has(key);
##monkeypatch: 78366653	core-js-3.1.3.tgz/modules/es.weak-map.js:42:      } return nativeDelete.call(this, key);
##monkeypatch: 78366653	core-js-3.1.3.tgz/modules/es.weak-map.js:41:        return nativeDelete.call(this, key) || state.frozen['delete'](key);
##arrayLike: 78366653	core-js-3.1.3.tgz/modules/es.typed-array.to-string.js:13:    return arrayJoin.call(this);
##arrayLike: 78366653	core-js-3.1.3.tgz/modules/es.typed-array.to-locale-string.js:13:  arrayToLocaleString.call(new Int8Array(1));
##arrayLike: 78366653	core-js-3.1.3.tgz/modules/es.typed-array.sort.js:10:  return arraySort.call(aTypedArray(this), comparefn);
##arrayLike: 78366653	core-js-3.1.3.tgz/modules/es.typed-array.iterator.js:31:  return arrayKeys.call(aTypedArray(this));
##arrayLike: 78366653	core-js-3.1.3.tgz/modules/es.typed-array.iterator.js:26:  return arrayEntries.call(aTypedArray(this));
##arrayLike: 78366653	core-js-3.1.3.tgz/modules/es.typed-array.iterator.js:20:  return arrayValues.call(aTypedArray(this));
##arrayLike: 78366653	core-js-3.1.3.tgz/modules/es.typed-array.copy-within.js:10:  return arrayCopyWithin.call(aTypedArray(this), target, start, arguments.length > 2 ? arguments[2] : undefined);

Additional Notes

These are some extra notes I took about specific "etc" categories. Some of these I already shared.

139612972	readable-stream-3.4.0.tgz/lib/internal/streams/buffer_list.js:16:  Buffer.prototype.copy.call(src, target, offset);
  Buffer is an (almost) subclass of Uint8Array. They likely wanted to accept either Uint8Arrays or Buffers while utilizing a function that only existed on Buffer.

139612972	readable-stream-3.4.0.tgz/lib/_stream_writable.js:236:  if (!isDuplex && !realHasInstance.call(Writable, this)) return new Writable(options);
139612972	readable-stream-3.4.0.tgz/lib/_stream_writable.js:214:      if (realHasInstance.call(this, object)) return true;
  They customized instanceof via Symbol.hasInstance, but needed to use the original Function.prototype[Symbol.hasInstance] before it got customized, so they picked it off and .call()ed it when it was needed.

117497282	yargs-13.2.4.tgz/yargs.js:262:      value = value.call()
  Any idea why you would ever do yourFunction.call()?

109400789	chalk-2.4.2.tgz/index.js:88:				return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model);
109400789	chalk-2.4.2.tgz/index.js:68:		return build.call(this, this._styles || [], true, 'visible');
109400789	chalk-2.4.2.tgz/index.js:61:			return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, key);
109400789	chalk-2.4.2.tgz/index.js:111:				return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model);
  All internal code: They wanted to pass in the current "this" value to a helper function, and they chose to do so via .call(). This helper function seemed to play a similar role to a factory function.

91794154	lodash-4.17.11.tgz/result.js:51:    object = isFunction(value) ? value.call(object) : value;
  _.result() takes an object and an obj path, and invokes the function found on that path with the proper this value.

78366653	core-js-3.1.3.tgz/modules/esnext.string.replace-all.js:17:  var flags = String('flags' in RegExpPrototype ? rx.flags : getRegExpFlags.call(rx));
  This is a use-case specific to polyfills. Specifically, core-js defines functions, and uses other function definitions that perhaps the user has not chosen to polyfill. Thus, it must directly use those functions off of the prototype, via .call().

I also went ahead and ran my script against the whole data set, having the script filter everything that didn't have .call( in it. This is the exact script I used (I made some modifications, mostly to how it detected the objectProto category to help it find more results):

from collections import defaultdict
import re

def auto_determin_type(line):
    if re.search(r'\d+:\s*//', line): return 'irrelevant'
    if re.search(r'\d+:\s*\*', line): return 'irrelevant'

    if '.hasOwnProperty.call' in line: return 'ownProp'
    if re.search(r'Object\.prototype\.[a-zA-Z]+\.call', line): return 'objectProto'
    if re.search(r'\{\s*\}\.[a-zA-Z]+\.call', line): return 'objectProto'
    if 'Error.call' in line: return 'super'

    # Bundlers/transpilers/compilers
    if '_super.call(' in line: return 'transpiled' # Result of babel transpiling super()
    if '_super.prototype.' in line: return 'transpiled' # Result of typescript compiling super.f() calls
    if '_getPrototypeOf' in line: return 'transpiled' # Artifact of babel transpiling.
    if '_possibleConstructorReturn' in line: return 'transpiled' # Artifact of babel transpiling.
    if '__super__' in line: return 'transpiled' # Artifacts of CoffeeScript compilation, or people creating classes by hand.
    if 'WEBPACK VAR INJECTION' in line: return 'transpiled' # Output of webpack merging files.

    # Operating on array-like objects. (Some of these operated on non-array classes, like Uint8Array)
    if re.search(r'Array\.prototype\.[a-zA-Z]+\.call', line): return 'arrayLike'
    if re.search(r'\[\s*\].[a-zA-Z]+.call', line): return 'arrayLike'

    # The following seem to either be picked off of the prototype and/or polyfilled. (for prototypal protection, or maybe tree shaking)
    if 'hasOwnProperty.call' in line: return 'protect'
    if 'hasOwnProperty[$\d]+.call' in line: return 'protect'
    if 'propertyIsEnumerable.call' in line: return 'protect'
    if 'toString.call' in line: return 'protect'
    if 'toString[$\d]+\.call' in line: return 'protect'
    if 'bind.call' in line: return 'protect'
    if 'hasOwn.call' in line: return 'protect'
    if 'slice.call' in line: return 'protect'
    if 'slice[$\d]+\.call' in line: return 'protect'
    if 'splice.call' in line: return 'protect'
    if 'indexOf.call' in line: return 'protect'
    if 'forEach.call' in line: return 'protect'
    if 'call.bind' in line: return 'protect'

    return 'UNKNOWN'

def parse_org_dump():
    type_count = defaultdict(int)
    total_call_entries = 0
    with open('slim.topcode.1000.txt') as f:
        line_num = 0
        for line in f:
            line_num += 1
            if line_num % 1_000_000 == 0: print(str.format('{:,}/139,849,897', line_num))
            if '.call' not in line: continue
            if re.search(r'\.call\s*\(', line): continue # A slower, more accurate check
            total_call_entries += 1

            type = auto_determin_type(line)
            type_count[type] += 1
    return type_count, total_call_entries

type_count, total_call_entries = parse_org_dump()

print()
print('Total entries with .call():', total_call_entries)
print()
for key, value in type_count.items():
    print(key + ':', value)

And these are the results:

Update: These results are bad, there was a bug in my script. Updated results will be posted below

Total entries with .call(): 157615

UNKNOWN: 143008
protect: 375
irrelevant: 3504
ownProp: 1810
transpiled: 8635
arrayLike: 78
super: 193
objectProto: 12

The Object.prototype numbers are really, really low. I was expecting them to be higher. Perhaps people are just doing Object.prototype.whatever.call() in formats that can't be easily detected by a raw string search, e.g. if they store Object.prototype.toString into a separate variable and then .call() it.

@theScottyJam
Copy link
Contributor Author

If interested, these are the specific occurrences of Object.prototype stuff it found.

162734	w3c-blob-0.0.1.tgz/index.js:4:  , str = {}.toString.call.bind({}.toString)
9134	matcha-0.7.0.tgz/benchmark/type.js:11:  bench('Object.prototype.toString.call', function () {
8541	utility2-2019.4.9.tgz/lib.istanbul.js:1639:W=A|D,X=A|_;o=Array.isArray,o||(o=function(t){return Object.prototype.toString.call
8541	utility2-2019.4.9.tgz/lib.istanbul.js:2123:,f,l,c,h=Array.isArray;h||(h=function(e){return e&&Object.prototype.toString.call
8541	utility2-2019.4.9.tgz/raw.istanbul.js:973:W=A|D,X=A|_;o=Array.isArray,o||(o=function(t){return Object.prototype.toString.call
8541	utility2-2019.4.9.tgz/raw.istanbul.js:1436:,f,l,c,h=Array.isArray;h||(h=function(e){return e&&Object.prototype.toString.call
2623	generator-ndx-0.3.2.tgz/generators/file-utils/templates/file-utils.coffee:29:      type = Object.prototype.toString.call files
1470	stampede-0.6.0.tgz/lib/utils.coffee:15:	myClass = Object.prototype.toString.call obj
1423	ndx-server-0.16.12.tgz/src/index.coffee:35:    type = Object.prototype.toString.call ctrl
1423	ndx-server-0.16.12.tgz/src/index.coffee:42:    type = Object.prototype.toString.call ctrl
1423	ndx-server-0.16.12.tgz/src/index.coffee:187:          if Object.prototype.toString.call message is '[object Object]'
1223	crystal-0.18.0.tgz/src/crystal/process.coffee:115:			if Object.prototype.toString.call model.access[role].permissions == '[object Object]'

Interestingly enough, it picked up a bunch of coffeescript files that were using it :)

@ljharb
Copy link
Member

ljharb commented Oct 10, 2021

That’s because prior to ES6 - when coffeescript was a going concern - Object toString was an unforgeable brand check. ES6 broke this, and so its usage is much less common after that, including in Babel output.

In some of my polyfills I also use Object.prototype.propertyIsEnumerable.call, as well, altho that’s indirect via https://npmjs.com/call-bind and https://npmjs.com/get-intrinsic

@theScottyJam
Copy link
Contributor Author

theScottyJam commented Oct 10, 2021

Ah, that's interesting to know about the broken brand checking.

With the chunk of stuff I looked over by hand, the algorithm could only pick up one occurrence of Object.prototype.toString.call(), the rest came from lodash, and were also done via indirect calls, because they protect against global mutations.

I did glance over other stuff by hand outside of those specific 300-ish I analyzed, and I know I saw other places where Object.prototype functions were used, but those were also done in indirect ways.

Perhaps there's not much use for Object.prototype.toString besides legacy brand checking and that's why we're not seeing much of it.

@ljharb
Copy link
Member

ljharb commented Oct 10, 2021

I think that’s the case; why robustly cache the method if the check is brittle by looking up Symbol.toStringTag

@theScottyJam
Copy link
Contributor Author

theScottyJam commented Oct 11, 2021

My script had an embarrassing bug, so the results from it yesterday are way off. (I was finding every occurrence of ".call" that wasn't being called. I wanted to find the places where .call was being called, i.e. it had "(" afterwards). This is why coffeescript kept showing up as well, coffeescript can call the function without parentheses. I guess I was a little too hasty yesterday with my script modifications.

My post from two days ago should still all be accurate.

Updated script:

from collections import defaultdict
import re

def auto_determin_type(line):
    if re.search(r'\d+:\s*//', line): return 'irrelevant'
    if re.search(r'\d+:\s*\*', line): return 'irrelevant'

    # Bundlers/transpilers/compilers
    if '_super.call(' in line: return 'transpiled' # Result of babel transpiling super()
    if '_super.prototype.' in line: return 'transpiled' # Result of typescript compiling super.f() calls
    if '_getPrototypeOf' in line: return 'transpiled' # Artifact of babel transpiling.
    if '_possibleConstructorReturn' in line: return 'transpiled' # Artifact of babel transpiling.
    if '__super__' in line: return 'transpiled' # Artifacts of CoffeeScript compilation, or people creating classes by hand.
    if 'WEBPACK VAR INJECTION' in line: return 'transpiled' # Output of webpack merging files.
    if '_objectWithoutProperties' in line: return 'transpiled'

    if '.hasOwnProperty.call' in line: return 'ownProp'
    if re.search(r'Object\.prototype\.[a-zA-Z]+\.call', line): return 'objectProto'
    if re.search(r'\{\s*\}\.[a-zA-Z]+\.call', line): return 'objectProto'
    if 'Error.call' in line: return 'super'

    # Operating on array-like objects. (Some of these operated on non-array classes, like Uint8Array)
    if re.search(r'Array\.prototype\.[a-zA-Z]+\.call', line): return 'arrayLike'
    if re.search(r'\[\s*\].[a-zA-Z]+.call', line): return 'arrayLike'

    # The following seem to either be picked off of the prototype and/or polyfilled. (for prototypal protection, or maybe tree shaking)
    if 'hasOwnProperty.call' in line: return 'protect'
    if 'hasOwnProperty[$\d]+.call' in line: return 'protect'
    if 'propertyIsEnumerable.call' in line: return 'protect'
    if 'toString.call' in line: return 'protect'
    if 'toString[$\d]+\.call' in line: return 'protect'
    if 'bind.call' in line: return 'protect'
    if 'hasOwn.call' in line: return 'protect'
    if 'slice.call' in line: return 'protect'
    if 'slice[$\d]+\.call' in line: return 'protect'
    if 'splice.call' in line: return 'protect'
    if 'indexOf.call' in line: return 'protect'
    if 'forEach.call' in line: return 'protect'
    if 'call.bind' in line: return 'protect'

    return 'UNKNOWN'

def parse_org_dump():
    type_count = defaultdict(int)
    total_call_entries = 0
    with open('slim.topcode.1000.txt') as f:
        line_num = 0
        for line in f:
            line_num += 1
            if line_num % 1_000_000 == 0: print(str.format('{:,}/139,849,897', line_num))
            if '.call' not in line: continue
            if not re.search(r'\.call\s*\(', line): continue # A slower, more accurate check
            total_call_entries += 1

            type = auto_determin_type(line)
            type_count[type] += 1
    return type_count, total_call_entries

type_count, total_call_entries = parse_org_dump()

print()
print('Total entries with .call():', total_call_entries)
print()
for key, value in type_count.items():
    print(key + ':', value)

Here are the updated stats:

Total entries with .call(): 480400

UNKNOWN: 271529
protect: 33599
ownProp: 57312
arrayLike: 26126
objectProto: 12664
super: 1737
irrelevant: 3027
transpiled: 74406

Now it was able to automatically categorize 43% of the results, which is pretty good.

Here are some use cases for Object.prototype:

This is Object.prototype.propertyIsEnumerable. I found 315 total usages of this, a good portion of which seemed to be for use cases such as merging or cloning object, or aiding with polyfills. Babel is actually at fault for 146 of them, it looks like it sticks in a helper function when you do spreading, that utalizes propertyIsEnumerable.

From hoek v6.1.3 index.js:

internals.keys = function (obj, options = {}) {

    return options.symbols ? Reflect.ownKeys(obj) : Object.getOwnPropertyNames(obj);
};


// Merge all the properties of source into target, source wins in conflict, and by default null and undefined from source are applied

exports.merge = function (target, source, isNullOverride /* = true */, isMergeArrays /* = true */) {
    ...Handling of unique scenarios, like arrays...

    const keys = internals.keys(source);
    for (let i = 0; i < keys.length; ++i) {
        const key = keys[i];
        if (key === '__proto__' ||
            !Object.prototype.propertyIsEnumerable.call(source, key)) { // <-----

            continue;
        }

        ...merging logic...
    }

    return target;
};

Another use for isEnumerable is to check if a value is an arguments array.

From marsdb v0.6.11 at lib/EJSON.js

function _isArguments(val) {
  return (
    !!val && typeof val == 'object' &&
    Object.prototype.hasOwnProperty.call(val, 'callee') &&
    !Object.prototype.propertyIsEnumerable.call(val, 'callee')
  );
}

An example of .toString()

This is from jscodeshift v-0.6.4, in src/code.js

/**
 * Returns a collection from a node, node path, array of nodes or array of node
 * paths.
 *
 * @ignore
 * @param {Node|NodePath|Array} source
 * @return {Collection}
 */
function fromAST(ast) {
  if (Array.isArray(ast)) {
    if (ast[0] instanceof NodePath || ast.length === 0) {
      return Collection.fromPaths(ast);
    } else if (Node.check(ast[0])) {
      return Collection.fromNodes(ast);
    }
  } else {
    if (ast instanceof NodePath) {
      return Collection.fromPaths([ast]);
    } else if (Node.check(ast)) {
      return Collection.fromNodes([ast]);
    }
  }
  throw new TypeError(
    'Received an unexpected value ' + Object.prototype.toString.call(ast) // <-----
  );
}

The vast majority of toString uses seem to be for tag checking, but every once in a while, it's used for stringifying an unknown value for an error message.

Among the entire slim.topcode.1000.txt file, there are only 6 occurrences of Object.prototype.isPrototypeOf.call:

28669	chain-able-4.0.6.tgz/dists/debugger/index.js:64:	var prototypeOf = function (obj, comparator) { return Object.prototype.isPrototypeOf.call(obj, comparator); };
28669	chain-able-4.0.6.tgz/dists/dev/index.js:64:	var prototypeOf = function (obj, comparator) { return Object.prototype.isPrototypeOf.call(obj, comparator); };
28669	chain-able-4.0.6.tgz/dists/node/index.js:64:	var prototypeOf = function (obj, comparator) { return Object.prototype.isPrototypeOf.call(obj, comparator); };
28669	chain-able-4.0.6.tgz/dists/window/index.js:61:	  Object.prototype.isPrototypeOf.call(obj, comparator);
12425	dash-core-components-0.48.0.tgz/dash_core_components/dash_core_components.dev.js:2688:  return typeof prop === 'function' && !Object.prototype.isPrototypeOf.call(_react.Component, prop) && (!_react.PureComponent || !Object.prototype.isPrototypeOf.call(_react.PureComponent, prop)) && prop.length === 1;
3480	zeta-lang-0.13.327.tgz/.bin/it.js:96:𐅭𐅦op ['∋']= (a,b)=> Object.prototype.isPrototypeOf.call( a.prototype||a ,b )

None of the pakcages used Object.prototype.valueOf or Object.prototype.toLocaleString, which isn't a big surprise.

@js-choi
Copy link
Collaborator

js-choi commented Oct 11, 2021

Should we try to completely exclude, from our statistics, any use of Object.prototype.toString.call as now obsolete due to Symbol.toStringTag—or are there still valid use cases for using Object.prototype.toString.call other than the now-broken brand checking?

@ljharb
Copy link
Member

ljharb commented Oct 11, 2021

There’s still valid use cases for it - it just won’t be ”brand checking”. In particular, some things use the toString output as a tag for a conceptual type union, and don’t care that it’s forgeable.

js-choi added a commit that referenced this issue Oct 25, 2021
js-choi added a commit that referenced this issue Oct 27, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 12, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
documentation Improvements or additions to documentation help wanted Extra attention is needed question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants