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

Make it so relative proxyquired paths are relative to the module being required. #190

Merged
merged 3 commits into from
Mar 2, 2018

Conversation

jwalton
Copy link
Contributor

@jwalton jwalton commented Mar 1, 2018

Fixes #189.

I added a test case that fails without this fix, and passes with it.

This should definitely be a major version rev. It's possible someone is currently doing something like:

// src/a.js
require('../b.js');
// b.js
require('./c.js');
// test.js
proxyquire('./src/a.js', {'./c.js': ...});

Which would no longer work with this change. I can do a writeup for the README.md that explains this breaking change.

// be resolved.
Proxyquire.prototype._resolveRelativePath = function(path, moduleFilename) {
try {
if(path[0] === '.') {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps I'm overthinking this, and we should just resolve everything, instead of only the relative paths. Then we wouldn't have to resolve them all a second time in the cleanup part of _withoutCache()... Yeah, I think I'm going to make that change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except, when I try this it breaks a bunch of unit tests. Maybe I'll leave it alone. :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (

Copy link
Collaborator

@bendrucker bendrucker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Until there is only a single place for resolving paths, I don't think I'm ready to merge this.

There's also no need to prepend resolved to stubs. It may seem clear but it just removes clarity from the functions that have both stubs and resolvedStubs in scope.

package.json Outdated
@@ -4,7 +4,8 @@
"description": "Proxies nodejs require in order to allow overriding dependencies during testing.",
"main": "index.js",
"scripts": {
"test": "mocha"
"test": "mocha",
"lint": "jshint index.js lib"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey please don't do this, contributing a linter in an unrelated PR is quite unwelcome

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I noticed there was a .jshintrc in the root folder. :P I'll pull it out.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah yeah my fault I didn't even realize there was jshintrc stuff in there. Since it hasn't been in CI for a while I'm just going to replace it with something a bit more ambitious/opinionated that's actually catching bugs.

// be resolved.
Proxyquire.prototype._resolveRelativePath = function(path, moduleFilename) {
try {
if(path[0] === '.') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (

var resolvedPath = Module._resolveFilename(path, module);
var self = this;

var resolvedStubs = {};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try not to assign into objects as a side effect like this. stubs = Object.keys(stubs).reduce(reducer, {}).

Proxyquire.prototype._withoutCache = function(module, stubs, path, func) {
// Temporarily disable the cache - either per-module or globally if we have global stubs
var restoreCache = this._disableCache(module, path);
var resolvedPath = Module._resolveFilename(path, module);
var self = this;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use self = this except when necessary. [].reduce and its derivatives (e.g. map) take a thisArg as their last argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really? The MDN page doesn't mention this. In ES6 I would just use an arrow function. I suppose I could just use 'bind'.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I guess it's only reduce that's missing it. It has an extra arg (the initial value) but I always consider it the "base" list function since it's the only one you can use to make all the others without loops or closures. Feel free to use self = this for [].reduce or bind. No perf concern here so either is ok.

I wouldn't mind adopting some light ES6 syntax (node 4 compatible) sometime in the future but let's not change that now.

@bendrucker
Copy link
Collaborator

Oh I see there's some jshint stuff sprinkled around. It's not maintained. I'll land a linting change separately, probably standard.

@bendrucker
Copy link
Collaborator

Just pushed a big update that uses standard, my preferred tool. The conflicts shouldn't be too bad (just toss package.json). asi (automatic semicolon insertion) was on for jshint in some modules, standard just assumes it everywhere.

Hopefully this helps make style consistency less ambiguous/easier. I've also been kicking around the idea of spending some time each day touching up the tests as a way to re-familiarize myself with the project.

Keen on trying to resolve bugs like this and caching issues. I've never personally run into them which is probably a reflection of how I structure projects.

Some folks at my new job are starting to adopt pq so I should have some opportunities to focus on it more regularly.

@jwalton
Copy link
Contributor Author

jwalton commented Mar 1, 2018

There we go - I think that grabs all your changes, and it's rebased on master so it's all "standard"ized.

@bendrucker
Copy link
Collaborator

bendrucker commented Mar 1, 2018

Nice! I should have time to have a look at this again today. Particularly keen to see what's failing with respect to non-relative packages.

@jwalton
Copy link
Contributor Author

jwalton commented Mar 1, 2018

The problem is that the unit tests try to require global packages that don't actually exist. Before, this was fine, because we didn't resolve them until the cleanup step, and we just ignored the errors (and possibly polluted the cache a little tiny bit? But with modules that don't actually exist, so I suppose that's OK.)

Now that we resolve them ahead of time, though, so we hit the error earlier, and have to decide what to do. This fix just leaves them unchanged. It also means we'll try to clean them up from the cache now.

Copy link
Collaborator

@bendrucker bendrucker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, appreciate all your efforts here! Made a few comments, will take another pass pre-merge and verify that the test fails without the source changes.

// "relative" paths (e.g. "./foo") but not non-relative modules (e.g. "react").
// Also, if noPreserveCache(), then we won't throw if the module can't
// be resolved.
Proxyquire.prototype._resolveRelativePath = function (path, moduleFilename) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_resolvePath, it may only be doing stuff with relative paths but it's responsible for parsing all stub paths.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good catch. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment here is now wildly incorrect, too. :)

@@ -4,6 +4,7 @@
var Module = require('module')
var resolve = require('resolve')
var dirname = require('path').dirname
var pathResolve = require('path').resolve
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much as it'll be a bit disruptive I think we should just do var path = require('path') and rename other "path" names to be more descriptive. I'd rather someone see path.* especially for fns that aren't obviously named (resolve).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was thinking the same thing. I'll do this.

if (hasOwnProperty.call(stubs, path)) {
var stub = stubs[path]
var resolvedPath = this._resolveRelativePath(path, module.filename)

Copy link
Collaborator

@bendrucker bendrucker Mar 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no linebreak since this gets used into the if block immediately after


if (stub === null) {
// Mimic the module-not-found exception thrown by node.js.
throw moduleNotFoundError(path)
throw moduleNotFoundError(resolvedPath)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be the original user input? This is simulating the error message node would generate and that would be based on the require id the user passes, not the resolved path.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. :)

})
var ids = [id].concat(stubIds.filter(Boolean))

var stubIds = Object.keys(stubs)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can merge this line with the next now (Object.keys(stubs).filter(Boolean))

} catch (err) {
// If this is not a relative path (e.g. "foo" as opposed to "./foo"), and
// we couldn't resolve it, then we just let the path through unchanged.
if (path[0] !== '.') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on using https://github.com/mattdesl/relative-require-regex here?

IMO an absolute path that couldn't be resolved should also pass through. External module also has the benefits of separate tests and docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could do - it's not a very exciting module: https://github.com/mattdesl/relative-require-regex/blob/master/index.js :P I tend to lean towards not using dependencies if I can avoid them.

The only thing different between their version and mine is, they would count something that starts with a "/" as a relative path. We're only going to hit this case when the path doesn't exist, so it's already a super rare case. Really this is here to keep behavior consistent with what used to happen (and to make the unit tests pass, since they bring in global modules that don't exist).

If you have strong feelings about it one way or the other, though, I'm happy to go either way.

Copy link
Collaborator

@bendrucker bendrucker Mar 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me it comes down to whether you exactly reproduce node or mostly reproduce it in the ways we think matter. I'd rather err on the side of trying to replicate node behavior directly and that errs on the side of external packages since there are lots of cases to manage. I agree that this is exceptionally unlikely to matter but I'd rather build to spec.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We aren't really trying to reproduce node behavior here, though. Any path that starts with a "." or ".." is unsafe for us to let through, because it wasn't resolved, but it could resolve relative to some other source file (that's exactly the issue I raised in the first place). A file that starts with a "/", on the other hand, can only match one file on the disk, so it's safe to let through even if the file doesn't exist. We just want to make sure that anything we return from this function is a "unique" module name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh... And if I change this to use relative-require-regex, two existing tests fail:

screen shot 2018-03-01 at 9 39 54 pm

So these tests would need some rework... We could add the noPreserveCache() setting to these tests, so it falls into the second case, I suppose.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Keep it is as for now. I'll look to remove that path eventually.

@bendrucker bendrucker merged commit c375628 into thlorenz:master Mar 2, 2018
@bendrucker
Copy link
Collaborator

Thank you again for your help here! I verified that the test fails in the absence of your changes (git checkout master -- lib/*). Waiting for Travis now just to be safe and then I'll push a new major. Since this is a bug and the major bump is just to be conservative/practical I don't think there's a need to mention the change in the readme.

@jwalton
Copy link
Contributor Author

jwalton commented Mar 3, 2018 via email

@gr2m
Copy link

gr2m commented Mar 3, 2018

It would be great if you could put a note into the v2.0.0 release: https://github.com/thlorenz/proxyquire/releases/tag/v2.0.0

or create a changelog

Lots of folks will look for that :)
https://github.com/search?q=%22Update+proxyquire+to+the+latest+version%22&type=Issues&utf8=%E2%9C%93

if you like to automate releases (incl. the change logs) check out https://github.com/semantic-release/semantic-release ;)

@bendrucker
Copy link
Collaborator

Updated the release with a short note. Not a fan of changelogs for small projects with low volume since it creates an expectation of updates on every release versus asking users to actually look at the changes. Putting some bullets on GH releases that are noteworthy is fine though.

espy added a commit to hoodiehq/hoodie-server that referenced this pull request Apr 20, 2018
Previous code was based on
an error in proxyquire: <2.0.0
thlorenz/proxyquire#190
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Proxyquire merges together files with the same relative path.
3 participants