Skip to content

Commit 40b2e55

Browse files
fix: directory symlink handling (#200)
Co-authored-by: JounQin <[email protected]>
1 parent 5bc6618 commit 40b2e55

5 files changed

+56
-1
lines changed

.changeset/wet-sloths-destroy.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"pretty-quick": patch
3+
---
4+
5+
fix: directory symlink handling - close #196

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
"npm-run-all": "^4.1.5",
8585
"prettier": "^3.5.3",
8686
"pretty-quick": "link:.",
87+
"simple-git-hooks": "^2.9.0",
8788
"size-limit": "^11.2.0",
8889
"size-limit-preset-node-lib": "^0.3.0",
8990
"ts-jest": "^29.2.6",

src/isSupportedExtension.ts

+12
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
/* eslint-disable unicorn/filename-case */
22

3+
import fs from 'fs/promises'
4+
35
import {
46
type FileInfoOptions,
57
getFileInfo,
68
resolveConfig as prettierResolveConfig,
79
} from 'prettier'
810

911
export default (resolveConfig?: boolean) => async (file: string) => {
12+
const stat = await fs.stat(file).catch(_error => null)
13+
/* If file exists but is actually a directory, getFileInfo might end up trying
14+
* to open it and read it as a file, so let's not let that happen. On the
15+
* other hand, the tests depend on our not failing out with files that don't
16+
* exist, so permit nonexistent files to go through (they appear to be
17+
* detected by suffix, so never get read).
18+
*/
19+
if (stat?.isDirectory()) {
20+
return false
21+
}
1022
const config = (await prettierResolveConfig(file, {
1123
editorconfig: true,
1224
})) as FileInfoOptions

test/isSupportedExtension.spec.ts

+37-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
/* eslint-disable unicorn/filename-case */
22

3+
import mock from 'mock-fs'
34
import { getFileInfo } from 'prettier'
45

56
import isSupportedExtension from 'pretty-quick/isSupportedExtension'
67

7-
afterEach(() => jest.clearAllMocks())
8+
beforeEach(() => {
9+
mock({
10+
'banana.js': 'banana()',
11+
'banana.txt': 'yellow',
12+
'bsym.js': mock.symlink({ path: 'banana.js' }),
13+
'bsym.txt': mock.symlink({ path: 'banana.js' }), // Yes extensions don't match
14+
dsym: mock.symlink({ path: 'subdir' }),
15+
subdir: {},
16+
})
17+
})
18+
19+
afterEach(() => {
20+
mock.restore()
21+
jest.clearAllMocks()
22+
})
823

924
test('return true when file with supported extension passed in', async () => {
1025
expect(await isSupportedExtension(true)('banana.js')).toEqual(true)
@@ -30,3 +45,24 @@ test('do not resolve config when false passed', async () => {
3045
resolveConfig: false,
3146
})
3247
})
48+
49+
test('return true when file symlink with supported extension passed in', async () => {
50+
expect(await isSupportedExtension(true)('bsym.js')).toEqual(true)
51+
expect(getFileInfo).toHaveBeenCalledWith('bsym.js', {
52+
file: 'bsym.js',
53+
resolveConfig: true,
54+
})
55+
})
56+
57+
test('return false when file symlink with unsupported extension passed in', async () => {
58+
expect(await isSupportedExtension(true)('bsym.txt')).toEqual(false)
59+
expect(getFileInfo).toHaveBeenCalledWith('bsym.txt', {
60+
file: 'bsym.txt',
61+
resolveConfig: true,
62+
})
63+
})
64+
65+
test('return false when directory symlink passed in', async () => {
66+
expect(await isSupportedExtension(true)('dsym')).toEqual(false)
67+
expect(getFileInfo).not.toHaveBeenCalled()
68+
})

yarn.lock

+1
Original file line numberDiff line numberDiff line change
@@ -12946,6 +12946,7 @@ __metadata:
1294612946
picomatch: ^4.0.2
1294712947
prettier: ^3.5.3
1294812948
pretty-quick: "link:."
12949+
simple-git-hooks: ^2.9.0
1294912950
size-limit: ^11.2.0
1295012951
size-limit-preset-node-lib: ^0.3.0
1295112952
tinyexec: ^0.3.2

0 commit comments

Comments
 (0)