diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..544138b --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/constants.js b/constants.js new file mode 100644 index 0000000..196841c --- /dev/null +++ b/constants.js @@ -0,0 +1,7 @@ +// Empty extension takes advantage of Node's default require behavior to check for +// eslint-local-rules.js as well as an eslint-local-rules folder with an index.js +var DEFAULT_EXTENSIONS = ['', '.cjs']; + +module.exports = { + DEFAULT_EXTENSIONS, +}; diff --git a/fixtures/projectWithBadImport/eslint-local-rules.js b/fixtures/projectWithBadImport/eslint-local-rules.js new file mode 100644 index 0000000..9b49287 --- /dev/null +++ b/fixtures/projectWithBadImport/eslint-local-rules.js @@ -0,0 +1,3 @@ +require('./does-not-exist'); + +module.exports = {}; diff --git a/fixtures/projectWithResolution/eslint-local-rules.js b/fixtures/projectWithResolution/eslint-local-rules.js new file mode 100644 index 0000000..f053ebf --- /dev/null +++ b/fixtures/projectWithResolution/eslint-local-rules.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/fixtures/projectWithResolutionCjs/eslint-local-rules.cjs b/fixtures/projectWithResolutionCjs/eslint-local-rules.cjs new file mode 100644 index 0000000..f053ebf --- /dev/null +++ b/fixtures/projectWithResolutionCjs/eslint-local-rules.cjs @@ -0,0 +1 @@ +module.exports = {}; diff --git a/fixtures/projectWithResolutionIndex/eslint-local-rules/index.js b/fixtures/projectWithResolutionIndex/eslint-local-rules/index.js new file mode 100644 index 0000000..f053ebf --- /dev/null +++ b/fixtures/projectWithResolutionIndex/eslint-local-rules/index.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/index.js b/index.js index 2380ca0..a5d348a 100644 --- a/index.js +++ b/index.js @@ -1,46 +1,23 @@ /* eslint-env node */ 'use strict'; -var path = require('path'); +var { requireUp } = require('./requireUp'); +var { DEFAULT_EXTENSIONS } = require('./constants'); -// Empty extension takes advantage of Node's default require behavior to check for -// eslint-local-rules.js as well as an eslint-local-rules folder with an index.js -var exts = ['', '.cjs']; -var rules = requireUp('eslint-local-rules', __dirname); +var rules = requireUp('eslint-local-rules', DEFAULT_EXTENSIONS, __dirname); if (!rules) { throw new Error( 'eslint-plugin-local-rules: ' + - 'Cannot find "eslint-local-rules{' + ['.js'].concat(exts.filter(Boolean)) + "} " + - 'or eslint-local-rules/index.js (checked all ancestors of "' + __dirname + '").' + 'Cannot find "eslint-local-rules{' + + ['.js'].concat(exts.filter(Boolean)) + + '} ' + + 'or eslint-local-rules/index.js (checked all ancestors of "' + + __dirname + + '").' ); } module.exports = { rules: rules, }; - -// Attempt to require a file, recursively checking parent directories until found -// Similar to native `require` behavior, but doesn't check in `node_modules` folders -// Based on https://github.com/js-cli/node-findup-sync -function requireUp(filename, cwd) { - var filepath = path.resolve(cwd, filename); - - for (var i = 0; i < exts.length; i++) { - try { - return require(filepath + exts[i]); - } catch(error) { - // Ignore OS errors (will recurse to parent directory) - if (error.code !== 'MODULE_NOT_FOUND') { - throw error; - } - } - } - - var dir = path.dirname(cwd); - if (dir === cwd) { - return undefined; - } - - return requireUp(filename, dir); -} diff --git a/package.json b/package.json index def5b2f..5af3de4 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "A plugin for ESLint that allows you to use project-specific rules", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "vitest" }, "repository": { "type": "git", @@ -19,5 +19,9 @@ "bugs": { "url": "https://github.com/cletusw/eslint-plugin-local-rules/issues" }, - "homepage": "https://github.com/cletusw/eslint-plugin-local-rules#readme" + "homepage": "https://github.com/cletusw/eslint-plugin-local-rules#readme", + "devDependencies": { + "prettier": "2.6.2", + "vitest": "^0.14.2" + } } diff --git a/requireUp.js b/requireUp.js new file mode 100644 index 0000000..dafb9c0 --- /dev/null +++ b/requireUp.js @@ -0,0 +1,38 @@ +var path = require('path'); + +/** + * Attempt to require a file, recursively checking parent directories until found. + * Similar to native `require` behavior, but doesn't check in `node_modules` folders. + * Based on https://github.com/js-cli/node-findup-sync. + * + * @param filename {string} The name of the target file + * @param exts {string[]} The file extensions to look for (e.g. '.cjs') + * @param cwd {string} The directory to search in + */ +function requireUp(filename, exts, cwd) { + for (var i = 0; i < exts.length; i++) { + var filepath = path.resolve(cwd, filename) + exts[i]; + try { + return require(filepath); + } catch (error) { + var filepathNotFound = + error.code === 'MODULE_NOT_FOUND' && + error.message.includes(`Cannot find module '${filepath}'`); + + // Rethrow unless error is just saying `filepath` not found (in that case, + // let next loop check parent directory instead). + if (!filepathNotFound) { + throw error; + } + } + } + var dir = path.dirname(cwd); + if (dir === cwd) { + return undefined; + } + return requireUp(filename, exts, dir); +} + +module.exports = { + requireUp, +}; diff --git a/requireUp.test.js b/requireUp.test.js new file mode 100644 index 0000000..c697815 --- /dev/null +++ b/requireUp.test.js @@ -0,0 +1,59 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, it, expect } from 'vitest'; + +import { requireUp } from './requireUp'; +import { DEFAULT_EXTENSIONS } from './constants'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +// Path to the 'fixtures' directory +const fixturesDir = path.resolve(__dirname, './fixtures'); + +describe('requireUp', () => { + it('should find the file at eslint-local-rules.js', () => { + const file = requireUp( + 'eslint-local-rules', + DEFAULT_EXTENSIONS, + path.join(fixturesDir, './projectWithResolution/a') + ); + expect(file).toBeDefined(); + }); + + it('should find the file at eslint-local-rules.cjs', () => { + const file = requireUp( + 'eslint-local-rules', + DEFAULT_EXTENSIONS, + path.join(fixturesDir, './projectWithResolutionCjs/a') + ); + expect(file).toBeDefined(); + }); + + it('should find the file at eslint-local-rules/index.js', () => { + const file = requireUp( + 'eslint-local-rules', + DEFAULT_EXTENSIONS, + path.join(fixturesDir, './projectWithResolutionIndex/a') + ); + expect(file).toBeDefined(); + }); + + it('should fail to find a file that does not exist', () => { + const file = requireUp( + 'some-file-that-will-not-resolve', + DEFAULT_EXTENSIONS, + path.join(fixturesDir, './projectWithNoResolution/a') + ); + expect(file).not.toBeDefined(); + }); + + it('should throw MODULE_NOT_FOUND errors for modules other than the target', () => { + expect(() => { + requireUp( + 'eslint-local-rules', + DEFAULT_EXTENSIONS, + path.join(fixturesDir, './projectWithBadImport/a') + ); + }).toThrowError(`Cannot find module './does-not-exist'`); + }); +});