diff --git a/README.md b/README.md index 74e5df4b..2480d57e 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,17 @@ When there's an error, `prettier-eslint` will log it to the console. To disable `disableLog` as an option to the call to `format` or you can set: `format.options.disableLog = true` to disable it "globally." +#### eslintPath (?String) + +By default, `prettier-eslint` will try to find your project's version of `eslint` (and `prettier`). If it cannot find +one, then it will use the version that `prettier-eslint` has installed locally. If you'd like to specify a path to the +`eslint` module you would like to have `prettier-eslint` use, then you can provide the full path to it with the +`eslintPath` option. + +#### prettierPath (?String) + +This is basically the same as `eslintPath` except for the `prettier` module. + ### throws `prettier-eslint` will propagate errors when either `prettier` or `eslint` fails for one reason or another. In addition diff --git a/src/index.js b/src/index.js index 36810ea2..1344f803 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,5 @@ -/* eslint no-console:0 */ +/* eslint no-console:0, global-require:0, import/no-dynamic-require:0 */ import path from 'path' -import {CLIEngine} from 'eslint' -import prettier from 'prettier' import {getPrettierOptionsFromESLintRules} from './utils' const options = {disableLog: false} @@ -15,6 +13,10 @@ module.exports.options = options * @param {String} options.filePath - the path of the file being formatted * can be used in leu of `eslintConfig` (eslint will be used to find the * relevant config for the file) + * @param {String} options.eslintPath - the path to the eslint module to use. + * Will default to require.resolve('eslint') + * @param {String} options.prettierPath - the path to the prettier module to use. + * Will default to require.resovlve('prettierPath') * @param {Boolean} options.disableLog - disables any logging * @param {String} options.eslintConfig - the config to use for formatting * with ESLint. @@ -26,23 +28,32 @@ module.exports.options = options function format({ text, filePath, + eslintPath = require.resolve('eslint'), + prettierPath = require.resolve('prettier'), disableLog = options.disableLog, - eslintConfig = getConfig(filePath), + eslintConfig = getConfig(filePath, eslintPath), prettierOptions = getPrettierOptionsFromESLintRules(eslintConfig), }) { const originalLogValue = options.disableLog options.disableLog = disableLog try { - const pretty = prettify(text, prettierOptions) - const eslintFixed = eslintFix(pretty, eslintConfig) + const pretty = prettify(text, prettierOptions, prettierPath) + const eslintFixed = eslintFix(pretty, eslintConfig, eslintPath) return eslintFixed } finally { options.disableLog = originalLogValue } } -function prettify(text, formatOptions) { +function prettify(text, formatOptions, prettierPath) { + let prettier + try { + prettier = require(prettierPath) + } catch (error) { + logError(`There was trouble getting prettier. Is "prettierPath: ${prettierPath}" a correct path to the prettier module?`) + throw error + } try { return prettier.format(text, formatOptions) } catch (error) { @@ -52,7 +63,7 @@ function prettify(text, formatOptions) { } } -function eslintFix(text, eslintConfig) { +function eslintFix(text, eslintConfig, eslintPath) { const eslintOptions = { // overrideables useEslintrc: false, @@ -67,7 +78,7 @@ function eslintFix(text, eslintConfig) { // for a --fix though so :shrug: globals: [], } - const eslint = new CLIEngine(eslintOptions) + const eslint = getESLintCLIEngine(eslintPath, eslintOptions) try { const report = eslint.executeOnText(text) const [{output}] = report.results @@ -83,12 +94,12 @@ function eslintFix(text, eslintConfig) { } } -function getConfig(filePath) { +function getConfig(filePath, eslintPath) { const eslintOptions = {} if (filePath) { eslintOptions.cwd = path.dirname(filePath) } - const configFinder = new CLIEngine(eslintOptions) + const configFinder = getESLintCLIEngine(eslintPath, eslintOptions) try { const config = configFinder.getConfigForFile(filePath) return config @@ -99,6 +110,16 @@ function getConfig(filePath) { } } +function getESLintCLIEngine(eslintPath, eslintOptions) { + try { + const {CLIEngine} = require(eslintPath) + return new CLIEngine(eslintOptions) + } catch (error) { + logError(`There was trouble creating the ESLint CLIEngine. Is "eslintPath: ${eslintPath}" a correct path to the ESLint module?`) + throw error + } +} + function logError(...args) { if (!options.disableLog) { console.error('prettier-eslint error:', ...args) diff --git a/src/index.test.js b/src/index.test.js index 5cb9a776..df01023d 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -1,4 +1,5 @@ /* eslint no-console:0 */ +import path from 'path' import stripIndent from 'strip-indent' import eslintMock from 'eslint' import prettierMock from 'prettier' @@ -54,6 +55,9 @@ const tests = [ beforeEach(() => { console.error.mockClear() + eslintMock.mock.executeOnText.mockClear() + eslintMock.mock.getConfigForFile.mockClear() + prettierMock.format.mockClear() }) tests.forEach(({title, modifier, input, output}) => { @@ -126,6 +130,40 @@ test('can disable log on a single call as part of the options', () => { prettierMockFormat.throwError = null }) +test('can accept a path to an eslint module and uses that instead.', () => { + const eslintPath = path.join(__dirname, './__mocks__/eslint') + const {executeOnText} = eslintMock.mock + format({text: '', eslintPath}) + expect(executeOnText).toHaveBeenCalledTimes(1) +}) + +test('fails with an error if the eslint module cannot be resolved.', () => { + const eslintPath = path.join(__dirname, './__mocks__/non-existant-eslint-module') + expect(() => format({text: '', eslintPath})).toThrowError(/non-existant-eslint-module/) + expect(console.error).toHaveBeenCalledTimes(1) + expect(console.error).toHaveBeenCalledWith( + 'prettier-eslint error:', + expect.stringMatching(/ESLint.*?eslintPath.*non-existant-eslint-module/), + ) +}) + +test('can accept a path to a prettier module and uses that instead.', () => { + const prettierPath = path.join(__dirname, './__mocks__/prettier') + const {format: prettierMockFormat} = prettierMock + format({text: '', prettierPath}) + expect(prettierMockFormat).toHaveBeenCalledTimes(1) +}) + +test('fails with an error if the prettier module cannot be resolved.', () => { + const prettierPath = path.join(__dirname, './__mocks__/non-existant-prettier-module') + expect(() => format({text: '', prettierPath})).toThrowError(/non-existant-prettier-module/) + expect(console.error).toHaveBeenCalledTimes(1) + expect(console.error).toHaveBeenCalledWith( + 'prettier-eslint error:', + expect.stringMatching(/prettier.*?prettierPath.*non-existant-prettier-module/), + ) +}) + function getESLintConfigWithDefaultRules(overrides) { return { parserOptions: {