refactor(): support matching Meteor projects downwards

Until now the path was only checked upwards for Meteor projects. Now it checks all children recursively. This enables having one common eslint configuration above multiple child directories containing Meteor applications.
This commit is contained in:
Dominik Ferber
2015-10-01 23:01:11 +02:00
parent f1da865eca
commit 85bb02e317
11 changed files with 173 additions and 78 deletions

8
.meteor/release Normal file
View File

@@ -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.

View File

@@ -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]
})

View File

@@ -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

View File

@@ -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

View File

@@ -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, '..'))
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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",

View File

@@ -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)

View File

@@ -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)
})
})

View File

@@ -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')
})
})