diff --git a/.meteor/release b/.meteor/release new file mode 100644 index 0000000000..613e92781a --- /dev/null +++ b/.meteor/release @@ -0,0 +1,8 @@ +# This is a static file for test purposes. +# It simulates the npm package root as being a Meteor Project. +# +# When requiring lib/index.js (dist/index.js) in tests, that file tries to +# find out all possible Meteor project root directories based on cwd before +# returning the rules. +# To stop it from searching anywhere, the existence of this file simulates the +# npm package to be a Meteor project, so the search stops immediately. diff --git a/lib/index.js b/lib/index.js index 6a4ea3dc12..cf71da5131 100755 --- a/lib/index.js +++ b/lib/index.js @@ -1,13 +1,11 @@ -import getMeteorProjectRootPath from './util/getMeteorProjectRootPath.js' +import getProjectRootPaths from './util/getProjectRootPaths' +import isMeteorProject from './util/isMeteorProject' -// const getMeteorProjectRootPath = require('./util/getMeteorProjectRootPath.js'); - -const cwd = process.cwd() -const rootPath = getMeteorProjectRootPath(cwd) +const rootPaths = getProjectRootPaths(process.cwd(), isMeteorProject) function unpack (rule) { const packedRule = require(rule) - const unpackedRule = packedRule(rootPath) + const unpackedRule = packedRule(rootPaths) Object.keys(packedRule).map(function (key) { unpackedRule[key] = packedRule[key] }) diff --git a/lib/rules/no-session.js b/lib/rules/no-session.js index d4981e0eb9..2254e16696 100644 --- a/lib/rules/no-session.js +++ b/lib/rules/no-session.js @@ -9,8 +9,8 @@ // Rule Definition // ----------------------------------------------------------------------------- -module.exports = (/* rootPath */) => context => { - // const fileInfo = getMeteorMeta(rootPath, context.getFilename()) +module.exports = (/* rootPaths */) => context => { + // const fileInfo = getMeteorMeta(rootPaths, context.getFilename()) // console.log(fileInfo) // fileInfo is false => not in Meteor Project diff --git a/lib/util/getMeteorMeta.js b/lib/util/getMeteorMeta.js index 916b4992c2..30fb7dbccf 100644 --- a/lib/util/getMeteorMeta.js +++ b/lib/util/getMeteorMeta.js @@ -89,14 +89,27 @@ function getMeteorFileInfo (rootPath, filename) { } } -export default function getMeteorMeta (rootPath, filename) { +function hasFile (parent, filename) { + return filename.substr(0, parent.length) === parent +} - if (typeof rootPath === 'object') { +export default function getMeteorMeta (rootPaths, filename) { - // rule is in test-mode. return the given environment - return rootPath + if (!Array.isArray(rootPaths)) { + if (typeof rootPaths === 'object') { + + // rule is in test-mode. return the given environment + return rootPaths + } + + throw new Error('rootPath must be an Array') // or object for test-mode } + + const rootPath = rootPaths.find(function (currentPath) { + return hasFile(currentPath, filename) + }) + if (!rootPath) { // not in a Meteor Project diff --git a/lib/util/getMeteorProjectRootPath.js b/lib/util/getMeteorProjectRootPath.js deleted file mode 100644 index 6a28933af2..0000000000 --- a/lib/util/getMeteorProjectRootPath.js +++ /dev/null @@ -1,19 +0,0 @@ -import path from 'path' - -// must be require for rewire to work -var pathExists = require('path-exists') - -export default function getMeteorProjectRootPath (currentDirectory) { - - // No folder with '.meteor/release' in it found - if (currentDirectory === path.sep) { - return false - } - - const meteorPath = path.join(currentDirectory, '.meteor', 'release') - if (pathExists.sync(meteorPath)) { - return currentDirectory - } - - return getMeteorProjectRootPath(path.join(currentDirectory, '..')) -} diff --git a/lib/util/getProjectRootPaths.js b/lib/util/getProjectRootPaths.js new file mode 100644 index 0000000000..e903b67bad --- /dev/null +++ b/lib/util/getProjectRootPaths.js @@ -0,0 +1,48 @@ +import path from 'path' +var walk = require('walkdir') + +function findOneUpwards (currentDirectory, matcher, attempts = 0) { + + // No folder with '.meteor/release' in it found + if (attempts > 50 || currentDirectory === path.sep) { + return false + } + + if (matcher(currentDirectory)) { + return currentDirectory + } + + return findOneUpwards(path.join(currentDirectory, '..'), matcher, attempts + 1) +} + + +function findAllDownwards (startPath, matcher) { + const matchedPaths = [] + const options = {follow_symlinks: false, no_recurse: false, max_depth: 20} + walk.sync( + startPath, + options, + function (currentPath) { + if (matcher(currentPath)) { + matchedPaths.push(currentPath) + this.ignore(currentPath) + } + } + ) + return matchedPaths +} + + +export default function getProjectRootPaths (currentDirectory, matcher) { + + if (matcher(currentDirectory)) { + return [currentDirectory] + } + + const upwards = findOneUpwards(currentDirectory, matcher) + if (upwards) { + return [upwards] + } + + return findAllDownwards(currentDirectory, matcher) +} diff --git a/lib/util/isMeteorProject.js b/lib/util/isMeteorProject.js new file mode 100644 index 0000000000..de2d7a04d4 --- /dev/null +++ b/lib/util/isMeteorProject.js @@ -0,0 +1,7 @@ +import path from 'path' +import pathExists from 'path-exists' + +export default function isMeteorProject (currentDirectory) { + const meteorPath = path.join(currentDirectory, '.meteor', 'release') + return pathExists.sync(meteorPath) +} diff --git a/package.json b/package.json index 269eb77a61..7eb3e9ca44 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,7 @@ "prepublish": "npm run build", "semantic-release": "semantic-release pre && npm publish && semantic-release post", "test": "npm run lint && npm run build && npm run unit-test", - "unit-test": "istanbul cover --dir reports/coverage node_modules/mocha/bin/_mocha tests/**/*.js -- --reporter dot --compilers js:babel/register", - "unit-test:n": "node_modules/mocha/bin/_mocha tests/**/*.js --recursive --reporter dot --compilers js:babel/register" + "unit-test": "istanbul cover --dir reports/coverage node_modules/mocha/bin/_mocha tests/**/*.js -- --reporter dot --compilers js:babel/register" }, "files": [ "LICENSE", @@ -29,7 +28,8 @@ "bugs": "https://github.com/dferber90/eslint-plugin-meteor/issues", "dependencies": { "invariant": "2.1.1", - "path-exists": "2.0.0" + "path-exists": "2.0.0", + "walkdir": "0.0.10" }, "devDependencies": { "babel": "5.8.23", diff --git a/tests/lib/util/getMeteorMeta.js b/tests/lib/util/getMeteorMeta.js index ddd5a308b6..e19fa17e44 100644 --- a/tests/lib/util/getMeteorMeta.js +++ b/tests/lib/util/getMeteorMeta.js @@ -8,6 +8,7 @@ var getMeteorMeta = require('../../../dist/util/getMeteorMeta.js') const rootPath = path.join('User', 'anon', 'meteor-project') +const rootPaths = [rootPath] describe('getMeteorMeta', function () { it('has working rule test-mode', function () { @@ -15,9 +16,16 @@ describe('getMeteorMeta', function () { assert.equal(getMeteorMeta(someObj), someObj) }) + it('does not accept anything that is not an object or array', function () { + assert.throws(getMeteorMeta.bind(null), Error) + assert.throws(getMeteorMeta.bind(null, true), Error) + assert.throws(getMeteorMeta.bind(null, false), Error) + assert.throws(getMeteorMeta.bind(null, 2), Error) + }) + describe('when not in Meteor project', function () { it('returns default env', function () { - var result = getMeteorMeta(false, 'file.js') + var result = getMeteorMeta([], 'file.js') assert.equal(typeof result, 'object') assert.equal(result.isInMeteorProject, false) assert.equal(Object.keys(result).length, 1) @@ -27,7 +35,7 @@ describe('getMeteorMeta', function () { describe('in public', function () { it('detects the environment', function () { var filename = path.join(rootPath, 'public', 'file.js') - var result = getMeteorMeta(rootPath, filename) + var result = getMeteorMeta(rootPaths, filename) assert.equal(typeof result, 'object') assert.equal(result.path, 'public/file.js') assert.equal(result.env, ENVIRONMENT.PUBLIC) @@ -39,7 +47,7 @@ describe('getMeteorMeta', function () { describe('in private', function () { it('detects the environment', function () { var filename = path.join(rootPath, 'private', 'file.js') - var result = getMeteorMeta(rootPath, filename) + var result = getMeteorMeta(rootPaths, filename) assert.equal(typeof result, 'object') assert.equal(result.path, 'private/file.js') assert.equal(result.env, ENVIRONMENT.PRIVATE) @@ -51,7 +59,7 @@ describe('getMeteorMeta', function () { describe('in package', function () { it('detects the environment', function () { var filename = path.join(rootPath, 'packages', 'awesome-pkg', 'file.js') - var result = getMeteorMeta(rootPath, filename) + var result = getMeteorMeta(rootPaths, filename) assert.equal(typeof result, 'object') assert.equal(result.path, 'packages/awesome-pkg/file.js') assert.equal(result.env, ENVIRONMENT.PACKAGE) @@ -63,7 +71,7 @@ describe('getMeteorMeta', function () { describe('on no special folder', function () { it('has universal environment', function () { var filename = path.join(rootPath, 'file.js') - var result = getMeteorMeta(rootPath, filename) + var result = getMeteorMeta(rootPaths, filename) assert.equal(typeof result, 'object') assert.equal(result.path, 'file.js') assert.equal(result.env, ENVIRONMENT.UNIVERSAL) @@ -76,7 +84,7 @@ describe('getMeteorMeta', function () { it('returns file info', function () { var filename = path.join(rootPath, 'client', 'lib', 'file.js') - var result = getMeteorMeta(rootPath, filename) + var result = getMeteorMeta(rootPaths, filename) assert.equal(typeof result, 'object') assert.equal(result.path, 'client/lib/file.js') assert.equal(result.env, ENVIRONMENT.CLIENT) @@ -86,7 +94,7 @@ describe('getMeteorMeta', function () { it('does not detect compatibility when directly in client-folder ', function () { var filename = path.join(rootPath, 'client', 'file.js') - var result = getMeteorMeta(rootPath, filename) + var result = getMeteorMeta(rootPaths, filename) assert.equal(typeof result, 'object') assert.equal(result.path, 'client/file.js') assert.equal(result.env, ENVIRONMENT.CLIENT) @@ -96,7 +104,7 @@ describe('getMeteorMeta', function () { it('detects compatibility mode', function () { var filename = path.join(rootPath, 'client', 'compatibility', 'file.js') - var result = getMeteorMeta(rootPath, filename) + var result = getMeteorMeta(rootPaths, filename) assert.equal(typeof result, 'object') assert.equal(result.path, 'client/compatibility/file.js') assert.equal(result.env, ENVIRONMENT.CLIENT) @@ -108,7 +116,7 @@ describe('getMeteorMeta', function () { describe('on server', function () { it('detects the environment', function () { var filename = path.join(rootPath, 'server', 'file.js') - var result = getMeteorMeta(rootPath, filename) + var result = getMeteorMeta(rootPaths, filename) assert.equal(typeof result, 'object') assert.equal(result.path, 'server/file.js') assert.equal(result.env, ENVIRONMENT.SERVER) @@ -119,7 +127,7 @@ describe('getMeteorMeta', function () { describe('that is nested', function () { it('detects the environment', function () { var filename = path.join(rootPath, 'lib', 'server', 'file.js') - var result = getMeteorMeta(rootPath, filename) + var result = getMeteorMeta(rootPaths, filename) assert.equal(typeof result, 'object') assert.equal(result.path, 'lib/server/file.js') assert.equal(result.env, ENVIRONMENT.SERVER) @@ -131,7 +139,7 @@ describe('getMeteorMeta', function () { describe('in tests', function () { var filename = path.join(rootPath, 'tests', 'file.js') - var result = getMeteorMeta(rootPath, filename) + var result = getMeteorMeta(rootPaths, filename) assert.equal(typeof result, 'object') assert.equal(result.path, 'tests/file.js') assert.equal(result.env, ENVIRONMENT.TEST) @@ -141,7 +149,7 @@ describe('getMeteorMeta', function () { describe('in node_modules', function () { var filename = path.join(rootPath, 'node_modules', 'my-module', 'file.js') - var result = getMeteorMeta(rootPath, filename) + var result = getMeteorMeta(rootPaths, filename) assert.equal(typeof result, 'object') assert.equal(result.path, 'node_modules/my-module/file.js') assert.equal(result.env, ENVIRONMENT.NODE_MODULE) diff --git a/tests/lib/util/getMeteorProjectRootPath.js b/tests/lib/util/getMeteorProjectRootPath.js deleted file mode 100644 index 0abfa2c90a..0000000000 --- a/tests/lib/util/getMeteorProjectRootPath.js +++ /dev/null @@ -1,32 +0,0 @@ -/* eslint-env mocha */ - -var assert = require('assert') -var path = require('path') -var rewire = require('rewire') -var getMeteorProjectRootPath = rewire('../../../dist/util/getMeteorProjectRootPath.js') - - -var isInMeteorProject -getMeteorProjectRootPath.__set__('pathExists', { - sync: function () { - return isInMeteorProject - } -}) - - -describe('getMeteorProjectRootPath', function () { - it('returns false for top-level directory', function () { - assert.equal(getMeteorProjectRootPath(path.sep), false) - }) - - it('returns false when not in meteor project', function () { - isInMeteorProject = false - assert.equal(getMeteorProjectRootPath('/not-in-meteor/sub'), false) - }) - - it('returns the directory when in meteor project', function () { - isInMeteorProject = true - var meteorPath = '/Users/anon/meteor-project' - assert.equal(getMeteorProjectRootPath(meteorPath), meteorPath) - }) -}) diff --git a/tests/lib/util/getProjectRootPaths.js b/tests/lib/util/getProjectRootPaths.js new file mode 100644 index 0000000000..c751122588 --- /dev/null +++ b/tests/lib/util/getProjectRootPaths.js @@ -0,0 +1,64 @@ +/* eslint-env mocha */ + +var assert = require('assert') +var rewire = require('rewire') +var getProjectRootPaths = rewire('../../../dist/util/getProjectRootPaths.js') + +var walkShouldFindRoot +getProjectRootPaths.__set__('walk', { + sync: function (start, options, cb) { + var context = {ignore: function () {}} + if (walkShouldFindRoot) { + cb.call(context, '/User/anon/a/b/meteor-project') + cb.call(context, '/User/anon/a/b/c/meteor-project') + } else { + cb.call(context, '/User/otherguy') + cb.call(context, '/User/otherguy/a') + cb.call(context, '/User/otherguy/b') + cb.call(context, '/User/otherguy/a/b') + } + } +}) + + +function matcher (filename) { + return ( + filename === '/User/anon/a/b/meteor-project' || + filename === '/User/anon/a/b/c/meteor-project' + ) +} + +describe('getProjectRootPaths', function () { + + beforeEach(function () { + walkShouldFindRoot = true + }) + + it(`returns an empty array when no project is found`, function () { + walkShouldFindRoot = false + var result = getProjectRootPaths('/User/otherguy/', matcher) + assert.ok(Array.isArray(result)) + assert.equal(result.length, 0) + }) + + it(`returns the path when it's the same directory`, function () { + var result = getProjectRootPaths('/User/anon/a/b/meteor-project', matcher) + assert.equal(result.length, 1) + assert.equal(result[0], '/User/anon/a/b/meteor-project') + }) + + it(`returns a parent Meteor project`, function () { + var result = getProjectRootPaths('/User/anon/a/b/meteor-project/a/b/c', matcher) + + assert.equal(result.length, 1) + assert.equal(result[0], '/User/anon/a/b/meteor-project') + }) + + it(`returns all child Meteor projects`, function () { + var result = getProjectRootPaths('/User/anon', matcher) + + assert.equal(result.length, 2) + assert.equal(result[0], '/User/anon/a/b/meteor-project') + assert.equal(result[1], '/User/anon/a/b/c/meteor-project') + }) +})