Skip to content

Commit cc5f683

Browse files
author
Kent C. Dodds
authored
feat: support prettier configuration (#119)
This actually is a partial solution to #118. BREAKING CHANGE: the API is now async 100% of the time. It's a shame, but we have to do that due to the async nature of resolving the prettier config.
1 parent c1c29a5 commit cc5f683

File tree

11 files changed

+126
-195
lines changed

11 files changed

+126
-195
lines changed

.babelrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
{
66
"targets": {
77
"node": "4.5"
8-
}
8+
},
9+
"exclude": ["transform-regenerator"]
910
}
1011
]
1112
],

.eslintrc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
],
77
"rules": {
88
"valid-jsdoc": "off",
9-
"max-len": "off"
9+
"max-len": "off",
10+
"semi": "off",
11+
"quotes": "off",
12+
"space-before-function-paren": ["error", {
13+
"anonymous": "never",
14+
"named": "never",
15+
"asyncArrow": "always"
16+
}],
1017
}
1118
}

package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
{
22
"name": "prettier-eslint",
33
"version": "0.0.0-development",
4-
"description": "Formats your JavaScript using prettier followed by eslint --fix",
4+
"description":
5+
"Formats your JavaScript using prettier followed by eslint --fix",
56
"main": "dist/index.js",
67
"scripts": {
78
"start": "nps",
89
"test": "nps test",
910
"precommit": "opt --in pre-commit --exec \"npm start validate\""
1011
},
11-
"files": [
12-
"dist"
13-
],
12+
"files": ["dist"],
1413
"keywords": [],
1514
"author": "Kent C. Dodds <kent@doddsfamily.us> (http://kentcdodds.com/)",
1615
"license": "MIT",

src/__mocks__/eslint.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ mockGetConfigForFileSpy.overrides = {}
99
const mockExecuteOnTextSpy = jest.fn(mockExecuteOnText)
1010

1111
module.exports = Object.assign(eslint, {
12-
CLIEngine: MockCLIEngine,
12+
CLIEngine: jest.fn(MockCLIEngine),
1313
mock: {
1414
getConfigForFile: mockGetConfigForFileSpy,
1515
executeOnText: mockExecuteOnTextSpy,
@@ -27,6 +27,7 @@ function MockCLIEngine(...args) {
2727

2828
MockCLIEngine.prototype = Object.create(CLIEngine.prototype)
2929

30+
// eslint-disable-next-line complexity
3031
function mockGetConfigForFile(filePath) {
3132
if (mockGetConfigForFileSpy.throwError) {
3233
throw mockGetConfigForFileSpy.throwError
@@ -42,11 +43,7 @@ function mockGetConfigForFile(filePath) {
4243
semi: [2, 'never'],
4344
'max-len': [2, 120, 2],
4445
indent: [2, 2, {SwitchCase: 1}],
45-
quotes: [
46-
2,
47-
'single',
48-
{avoidEscape: true, allowTemplateLiterals: true},
49-
],
46+
quotes: [2, 'single', {avoidEscape: true, allowTemplateLiterals: true}],
5047
'comma-dangle': [
5148
2,
5249
{

src/__mocks__/prettier.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ module.exports = prettier
55

66
const mockFormatSpy = jest.fn(mockFormat)
77

8-
Object.assign(prettier, {format: mockFormatSpy})
8+
Object.assign(prettier, {
9+
format: mockFormatSpy,
10+
resolveConfig: jest.fn(prettier.resolveConfig),
11+
})
912

1013
function mockFormat(...args) {
1114
global.__PRETTIER_ESLINT_TEST_STATE__.prettierPath = __filename

src/__tests__/index.js

Lines changed: 64 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ beforeEach(() => {
176176
eslintMock.mock.executeOnText.mockClear()
177177
eslintMock.mock.getConfigForFile.mockClear()
178178
prettierMock.format.mockClear()
179+
prettierMock.resolveConfig.mockClear()
179180
fsMock.readFileSync.mockClear()
180181
loglevelMock.mock.clearAll()
181182
global.__PRETTIER_ESLINT_TEST_STATE__ = {}
@@ -186,93 +187,89 @@ tests.forEach(({title, modifier, input, output}) => {
186187
if (modifier) {
187188
fn = test[modifier]
188189
}
189-
fn(title, () => {
190+
fn(title, async () => {
190191
input.text = stripIndent(input.text).trim()
191192
const expected = stripIndent(output).trim()
192-
const actual = format(input)
193+
const actual = await format(input)
193194
// adding the newline in the expected because
194195
// prettier adds a newline to the end of the input
195196
expect(actual).toBe(`${expected}\n`)
196197
})
197198
})
198199

199-
test('failure to fix with eslint throws and logs an error', () => {
200+
test('failure to fix with eslint throws and logs an error', async () => {
200201
const {executeOnText} = eslintMock.mock
201-
const error = 'Something happened'
202-
executeOnText.throwError = new Error(error)
203-
expect(() => format({text: ''})).toThrowError(error)
204-
expect(logger.error).toHaveBeenCalledTimes(1)
202+
const error = new Error('Something happened')
203+
executeOnText.throwError = error
204+
const errorThrown = await format({text: ''}).catch(e => e)
205205
executeOnText.throwError = null
206+
207+
expect(errorThrown).toBe(error)
208+
expect(logger.error).toHaveBeenCalledTimes(1)
206209
})
207210

208-
test('logLevel is used to configure the logger', () => {
211+
test('logLevel is used to configure the logger', async () => {
209212
logger.setLevel = jest.fn()
210-
format({text: '', logLevel: 'silent'})
213+
await format({text: '', logLevel: 'silent'})
211214
expect(logger.setLevel).toHaveBeenCalledTimes(1)
212215
expect(logger.setLevel).toHaveBeenCalledWith('silent')
213216
})
214217

215-
test(`when prettier throws, log to logger.error and throw the error`, () => {
216-
const {format: prettierMockFormat} = prettierMock
217-
const error = 'something bad happened'
218-
prettierMockFormat.throwError = new Error(error)
218+
test(`when prettier throws, log to logger.error and throw the error`, async () => {
219+
const error = new Error('something bad happened')
220+
prettierMock.format.throwError = error
221+
const errorThrown = await format({text: ''}).catch(e => e)
222+
prettierMock.format.throwError = null
219223

220-
expect(() => format({text: ''})).toThrowError(error)
224+
expect(errorThrown).toBe(error)
221225
expect(logger.error).toHaveBeenCalledTimes(1)
222-
223-
prettierMockFormat.throwError = null
224226
})
225227

226-
test('can accept a path to an eslint module and uses that instead.', () => {
228+
test('can accept a path to an eslint module and uses that instead.', async () => {
227229
const eslintPath = path.join(__dirname, '../__mocks__/eslint')
228-
const {executeOnText} = eslintMock.mock
229-
format({text: '', eslintPath})
230-
expect(executeOnText).toHaveBeenCalledTimes(1)
230+
await format({text: '', eslintPath})
231+
expect(eslintMock.mock.executeOnText).toHaveBeenCalledTimes(1)
231232
})
232233

233-
test('fails with an error if the eslint module cannot be resolved.', () => {
234+
test('fails with an error if the eslint module cannot be resolved.', async () => {
234235
const eslintPath = path.join(
235236
__dirname,
236237
'../__mocks__/non-existant-eslint-module',
237238
)
238239

239-
expect(() => format({text: '', eslintPath})).toThrowError(
240-
/non-existant-eslint-module/,
241-
)
240+
const error = await format({text: '', eslintPath}).catch(e => e)
241+
242+
expect(error.message).toMatch(/non-existant-eslint-module/)
242243
expect(logger.error).toHaveBeenCalledTimes(1)
243244

244245
const errorString = expect.stringMatching(
245-
/ESLint.*?eslintPath.*non-existant-eslint-module/,
246+
/trouble getting.*?eslint.*non-existant-eslint-module/,
246247
)
247248

248249
expect(logger.error).toHaveBeenCalledWith(errorString)
249250
})
250251

251-
test('can accept a path to a prettier module and uses that instead.', () => {
252+
test('can accept a path to a prettier module and uses that instead.', async () => {
252253
const prettierPath = path.join(__dirname, '../__mocks__/prettier')
253-
const {format: prettierMockFormat} = prettierMock
254-
format({text: '', prettierPath})
255-
expect(prettierMockFormat).toHaveBeenCalledTimes(1)
254+
await format({text: '', prettierPath})
255+
expect(prettierMock.format).toHaveBeenCalledTimes(1)
256256
})
257257

258-
test('fails with an error if the prettier module cannot be resolved.', () => {
258+
test('fails with an error if the prettier module cannot be resolved.', async () => {
259259
const prettierPath = path.join(
260260
__dirname,
261261
'../__mocks__/non-existant-prettier-module',
262262
)
263-
expect(() => format({text: '', prettierPath})).toThrowError(
264-
/non-existant-prettier-module/,
265-
)
263+
const error = await format({text: '', prettierPath}).catch(e => e)
264+
expect(error.message).toMatch(/non-existant-prettier-module/)
266265
expect(logger.error).toHaveBeenCalledTimes(1)
267-
const errorString = expect.stringMatching(
268-
/prettier.*?prettierPath.*non-existant-prettier-module/,
269-
)
266+
const errorString = expect.stringMatching(/trouble getting.*prettier/)
270267
expect(logger.error).toHaveBeenCalledWith(errorString)
271268
})
272269

273-
test('resolves to the eslint module relative to the given filePath', () => {
270+
test('resolves to the eslint module relative to the given filePath', async () => {
274271
const filePath = require.resolve('../../tests/fixtures/paths/foo.js')
275-
format({text: '', filePath})
272+
await format({text: '', filePath})
276273
const stateObj = {
277274
eslintPath: require.resolve(
278275
'../../tests/fixtures/paths/node_modules/eslint/index.js',
@@ -284,9 +281,9 @@ test('resolves to the eslint module relative to the given filePath', () => {
284281
expect(global.__PRETTIER_ESLINT_TEST_STATE__).toMatchObject(stateObj)
285282
})
286283

287-
test('resolves to the local eslint module', () => {
284+
test('resolves to the local eslint module', async () => {
288285
const filePath = '/blah-blah/default-config'
289-
format({text: '', filePath})
286+
await format({text: '', filePath})
290287
expect(global.__PRETTIER_ESLINT_TEST_STATE__).toMatchObject({
291288
// without Jest's mocking, these would actually resolve to the
292289
// project modules :) The fact that jest's mocking is being
@@ -296,28 +293,47 @@ test('resolves to the local eslint module', () => {
296293
})
297294
})
298295

299-
test('reads text from fs if filePath is provided but not text', () => {
296+
test('reads text from fs if filePath is provided but not text', async () => {
300297
const filePath = '/blah-blah/some-file.js'
301-
try {
302-
format({filePath})
303-
} catch (e) {
304-
// ignore
305-
}
298+
await format({filePath}).catch(() => {})
306299
// one hit to get the file and one for the eslintignore
307300
expect(fsMock.readFileSync).toHaveBeenCalledTimes(2)
308301
expect(fsMock.readFileSync).toHaveBeenCalledWith(filePath, 'utf8')
309302
})
310303

311-
test('logs error if it cannot read the file from the filePath', () => {
304+
test('logs error if it cannot read the file from the filePath', async () => {
312305
const originalMock = fsMock.readFileSync
313306
fsMock.readFileSync = jest.fn(() => {
314307
throw new Error('some error')
315308
})
316-
expect(() => format({filePath: '/some-path.js'})).toThrowError(/some error/)
309+
const error = await format({filePath: '/some-path.js'}).catch(e => e)
310+
expect(error.message).toMatch(/some error/)
317311
expect(logger.error).toHaveBeenCalledTimes(1)
318312
fsMock.readFileSync = originalMock
319313
})
320314

315+
test('calls prettier.resolveConfig with the file path', async () => {
316+
const filePath = require.resolve('../../tests/fixtures/paths/foo.js')
317+
await format({
318+
filePath,
319+
text: defaultInputText(),
320+
eslintConfig: getESLintConfigWithDefaultRules(),
321+
})
322+
expect(prettierMock.resolveConfig).toHaveBeenCalledTimes(1)
323+
expect(prettierMock.resolveConfig).toHaveBeenCalledWith(filePath)
324+
})
325+
326+
test('logs if there is a problem making the CLIEngine', async () => {
327+
const error = new Error('fake error')
328+
eslintMock.CLIEngine.mockImplementation(() => {
329+
throw error
330+
})
331+
const errorThrown = await format({text: ''}).catch(e => e)
332+
eslintMock.CLIEngine.mockReset()
333+
expect(errorThrown).toBe(error)
334+
expect(logger.error).toHaveBeenCalledTimes(1)
335+
})
336+
321337
function getESLintConfigWithDefaultRules(overrides) {
322338
return {
323339
parserOptions: {ecmaVersion: 7},

0 commit comments

Comments
 (0)