Open zrhmn opened 6 years ago
I do not think it is a responsibility of the constructor to clone options. It was a deliberate developer's choice to pass an exactly same reference to two environments. Do not forget that by the reference mutations can be used in some cases for good too.
Besides, if options are huge, deep cloning will take a lot of time. I think it's easier to put the decision about whether it needed on the shoulders of the developer.
opts = this.opts = Object.assign({}, opts || {})
This will work only for non-nested objects (shallow cloning). Second level and above will be passed by reference.
Since only the top-level attributes are mutated, it should be sufficient to do a shallow copy in order to avoid side-effects. This seems like a legitimate, albeit minor, bug.
As it says in the title, the
Environment
class is defined in/nunjucks/src/environment.js
. Here is a copy of the code for theEnvironment
class from master.Now, if I have a setup like:
You can see how that's a problem, as the first time
config.nunjucksOpts
was passed to the constructor, the constructor mutated it, and assigned it to itself. When there's an object on the RHS of the operator, JavaScript actually stores a reference to the object, instead of an instance of it. Any changes thatEnvironemnt#init
then makes toEnvironment#opts
are reflected in the originalconfig.nunjucksOpts
object passed to the constructor (ref a
in the example above). Also, ifconfig.nunjucksOpts
is then updated by the client code (ref b
in the example above), it affectsenv0
too. So in the above example,env0
andenv1
end up being configured with identical options, even though it might not be obvious.Now, I will concede that the approach taken in the example I provided above is naive. And perhaps it is the client code should be doing something like this to achieve correct behavior:
However, I still think that it is primarily the responsibility of the constructor (or the
Environment#init
method which is called byObj#constructor
thatEnvironment
extends) to ensure that instead of storing a reference toopts
a copy of theopts
arg is stored instead. And this safeguard is not complicated to implement. In the implementation ofEnvironment#init
, we can:I cannot think of any negative side-effects of this change. If anyone can see how that would break something, I'll be happy to rethink it. Otherwise, I can submit a merge request with that single line-of-code change. I'm willing to discuss this here.
I haven't gone through the rest of the code, but there may be other constructors with the same issue, and I think those should be updated too.
Edit: fix formatting 🤦♂