From 51433bed947d31e5f3df26bdf6eee10ad4344efa Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 25 May 2019 19:26:13 +0200 Subject: [PATCH] Implement helper for our ESLint plugin --- eslint-plugin-helper.js | 37 ++++++++ lib/globs.js | 2 + lib/load-config.js | 6 +- profile.js | 2 +- test/eslint-plugin-helper.js | 94 +++++++++++++++++++ .../eslint-plugin-helper/ava.config.js | 6 ++ .../fixture/eslint-plugin-helper/package.json | 1 + test/load-config.js | 9 +- 8 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 eslint-plugin-helper.js create mode 100644 test/eslint-plugin-helper.js create mode 100644 test/fixture/eslint-plugin-helper/ava.config.js create mode 100644 test/fixture/eslint-plugin-helper/package.json diff --git a/eslint-plugin-helper.js b/eslint-plugin-helper.js new file mode 100644 index 000000000..3766ed2f3 --- /dev/null +++ b/eslint-plugin-helper.js @@ -0,0 +1,37 @@ +'use strict'; +const babelPipeline = require('./lib/babel-pipeline'); +const normalizeExtensions = require('./lib/extensions'); +const {hasExtension, normalizeGlobs, classify} = require('./lib/globs'); +const loadConfig = require('./lib/load-config'); + +const cache = new Map(); + +function load(projectDir) { + if (cache.has(projectDir)) { + return cache.get(projectDir); + } + + const conf = loadConfig(projectDir); + const babelConfig = babelPipeline.validate(conf.babel); + const extensions = normalizeExtensions(conf.extensions || [], babelConfig); + const globs = {cwd: projectDir, ...normalizeGlobs(conf.files, conf.helpers, conf.sources, extensions.all)}; + + const helper = Object.freeze({ + classifyFile: file => classify(file, globs), + classifyImport: importPath => { + if (hasExtension(globs.extensions, importPath)) { + // The importPath has one of the test file extensions: we can classify + // it directly. + return classify(importPath, globs); + } + + // Add the first extension. If multiple extensions are available, assume + // patterns are not biased to any particular extension. + return classify(`${importPath}.${globs.extensions[0]}`, globs); + } + }); + cache.set(projectDir, helper); + return helper; +} + +exports.load = load; diff --git a/lib/globs.js b/lib/globs.js index a2e0b5562..d3825bfe7 100644 --- a/lib/globs.js +++ b/lib/globs.js @@ -92,6 +92,8 @@ exports.normalizeGlobs = normalizeGlobs; const hasExtension = (extensions, file) => extensions.includes(path.extname(file).slice(1)); +exports.hasExtension = hasExtension; + const findFiles = async (cwd, patterns) => { const files = await globby(patterns, { absolute: true, diff --git a/lib/load-config.js b/lib/load-config.js index eafcaf7b6..526137421 100644 --- a/lib/load-config.js +++ b/lib/load-config.js @@ -7,10 +7,10 @@ const pkgConf = require('pkg-conf'); const NO_SUCH_FILE = Symbol('no ava.config.js file'); const MISSING_DEFAULT_EXPORT = Symbol('missing default export'); -function loadConfig(defaults = {}) { - const packageConf = pkgConf.sync('ava'); +function loadConfig(resolveFrom = process.cwd(), defaults = {}) { + const packageConf = pkgConf.sync('ava', {cwd: resolveFrom}); const filepath = pkgConf.filepath(packageConf); - const projectDir = filepath === null ? process.cwd() : path.dirname(filepath); + const projectDir = filepath === null ? resolveFrom : path.dirname(filepath); let fileConf; try { diff --git a/profile.js b/profile.js index d3615ce95..5b13c1152 100644 --- a/profile.js +++ b/profile.js @@ -32,7 +32,7 @@ function resolveModules(modules) { Promise.longStackTraces(); -const conf = loadConfig({ +const conf = loadConfig(undefined, { babel: { testOptions: {} }, diff --git a/test/eslint-plugin-helper.js b/test/eslint-plugin-helper.js new file mode 100644 index 000000000..343e403e7 --- /dev/null +++ b/test/eslint-plugin-helper.js @@ -0,0 +1,94 @@ +'use strict'; +const path = require('path'); +const {test} = require('tap'); + +const {load} = require('../eslint-plugin-helper'); + +const projectDir = path.join(__dirname, 'fixture/eslint-plugin-helper'); + +test('caches loaded configuration', t => { + const expected = load(projectDir); + const actual = load(projectDir); + t.is(expected, actual); + t.end(); +}); + +test('classifies files according to the configuration', t => { + const helper = load(projectDir); + t.deepEqual(helper.classifyFile(path.join(projectDir, 'tests/test.foo')), { + isHelper: false, + isSource: false, + isTest: true + }); + t.deepEqual(helper.classifyFile(path.join(projectDir, 'tests/_helper.foo')), { + isHelper: true, + isSource: false, + isTest: false + }); + t.deepEqual(helper.classifyFile(path.join(projectDir, 'helpers/helper.foo')), { + isHelper: true, + isSource: false, + isTest: false + }); + t.deepEqual(helper.classifyFile(path.join(projectDir, 'source.foo')), { + isHelper: false, + isSource: true, + isTest: false + }); + t.deepEqual(helper.classifyFile(path.join(projectDir, 'tests/test.js')), { + isHelper: false, + isSource: false, + isTest: false + }); + t.end(); +}); + +test('classifies imports with extension according to the configuration', t => { + const helper = load(projectDir); + t.deepEqual(helper.classifyImport(path.join(projectDir, 'tests/test.foo')), { + isHelper: false, + isSource: false, + isTest: true + }); + t.deepEqual(helper.classifyImport(path.join(projectDir, 'tests/_helper.foo')), { + isHelper: true, + isSource: false, + isTest: false + }); + t.deepEqual(helper.classifyImport(path.join(projectDir, 'helpers/helper.foo')), { + isHelper: true, + isSource: false, + isTest: false + }); + t.deepEqual(helper.classifyImport(path.join(projectDir, 'source.foo')), { + isHelper: false, + isSource: true, + isTest: false + }); + t.end(); +}); + +test('classifies imports without extension according to the configuration', t => { + const helper = load(projectDir); + t.deepEqual(helper.classifyImport(path.join(projectDir, 'tests/test')), { + isHelper: false, + isSource: false, + isTest: true + }); + t.deepEqual(helper.classifyImport(path.join(projectDir, 'tests/_helper')), { + isHelper: true, + isSource: false, + isTest: false + }); + t.deepEqual(helper.classifyImport(path.join(projectDir, 'helpers/helper')), { + isHelper: true, + isSource: false, + isTest: false + }); + t.deepEqual(helper.classifyImport(path.join(projectDir, 'source')), { + isHelper: false, + isSource: true, + isTest: false + }); + t.end(); +}); diff --git a/test/fixture/eslint-plugin-helper/ava.config.js b/test/fixture/eslint-plugin-helper/ava.config.js new file mode 100644 index 000000000..a60653214 --- /dev/null +++ b/test/fixture/eslint-plugin-helper/ava.config.js @@ -0,0 +1,6 @@ +export default { + babel: false, + files: ['tests/**/*'], + helpers: ['helpers/*'], + extensions: ['foo'] +}; diff --git a/test/fixture/eslint-plugin-helper/package.json b/test/fixture/eslint-plugin-helper/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/fixture/eslint-plugin-helper/package.json @@ -0,0 +1 @@ +{} diff --git a/test/load-config.js b/test/load-config.js index c15413f50..ed68fc475 100644 --- a/test/load-config.js +++ b/test/load-config.js @@ -21,6 +21,13 @@ test('finds config in package.json', t => { t.end(); }); +test('loads config from a particular directory', t => { + changeDir('throws'); + const conf = loadConfig(path.resolve(__dirname, 'fixture', 'load-config', 'package-only')); + t.is(conf.failFast, true); + t.end(); +}); + test('throws a warning of both configs are present', t => { changeDir('package-yes-file-yes'); t.throws(loadConfig); @@ -32,7 +39,7 @@ test('merges in defaults passed with initial call', t => { const defaults = { files: ['123', '!456'] }; - const {files, failFast} = loadConfig(defaults); + const {files, failFast} = loadConfig(undefined, defaults); t.is(failFast, true, 'preserves original props'); t.is(files, defaults.files, 'merges in extra props'); t.end();