Skip to content

Commit d895aa8

Browse files
committed
fix: detect duplicate redundant braces in pattern
1 parent a7f8f29 commit d895aa8

File tree

2 files changed

+62
-23
lines changed

2 files changed

+62
-23
lines changed

lib/validateBraces.js

+30-6
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import { incorrectBraces } from './messages.js'
1717
* braces from user configuration, but this is left to the user (after seeing the warning).
1818
*
1919
* @example <caption>Globs with brace expansions</caption>
20-
* - *.{js,tx} // expanded as *.js, *.ts
21-
* - *.{{j,t}s,css} // expanded as *.js, *.ts, *.css
20+
* - *.{js,tx} // expanded as *.js, *.ts
21+
* - *.{{j,t}s,css} // expanded as *.js, *.ts, *.css
2222
* - file_{1..10}.css // expanded as file_1.css, file_2.css, …, file_10.css
2323
*
2424
* @example <caption>Globs with incorrect brace expansions</caption>
@@ -28,17 +28,17 @@ import { incorrectBraces } from './messages.js'
2828
* - *.${js} // dollar-sign inhibits expansion, so treated literally
2929
* - *.{js\,ts} // the comma is escaped, so treated literally
3030
*/
31-
export const BRACES_REGEXP = /(?<![\\$])({)(?:(?!(?<!\\),|\.\.|\{|\}).)*?(?<!\\)(})/g
31+
export const INCORRECT_BRACES_REGEXP = /(?<![\\$])({)(?:(?!(?<!\\),|\.\.|\{|\}).)*?(?<!\\)(})/g
3232

3333
/**
3434
* @param {string} pattern
3535
* @returns {string}
3636
*/
37-
const withoutIncorrectBraces = (pattern) => {
37+
const stripIncorrectBraces = (pattern) => {
3838
let output = `${pattern}`
3939
let match = null
4040

41-
while ((match = BRACES_REGEXP.exec(pattern))) {
41+
while ((match = INCORRECT_BRACES_REGEXP.exec(pattern))) {
4242
const fullMatch = match[0]
4343
const withoutBraces = fullMatch.replace(/{/, '').replace(/}/, '')
4444
output = output.replace(fullMatch, withoutBraces)
@@ -47,6 +47,30 @@ const withoutIncorrectBraces = (pattern) => {
4747
return output
4848
}
4949

50+
/**
51+
* This RegExp matches "duplicate" opening and closing braces, without any other braces
52+
* in between, where the duplication is redundant and should be removed.
53+
*
54+
* @example *.{{js,ts}} // should just be *.{js,ts}
55+
*/
56+
export const DOUBLE_BRACES_REGEXP = /{{[^}{]*}}/
57+
58+
/**
59+
* @param {string} pattern
60+
* @returns {string}
61+
*/
62+
const stripDoubleBraces = (pattern) => {
63+
let output = `${pattern}`
64+
const match = DOUBLE_BRACES_REGEXP.exec(pattern)?.[0]
65+
66+
if (match) {
67+
const withoutBraces = match.replace('{{', '{').replace('}}', '}')
68+
output = output.replace(match, withoutBraces)
69+
}
70+
71+
return output
72+
}
73+
5074
/**
5175
* Validate and remove incorrect brace expansions from glob pattern.
5276
* For example `*.{js}` is incorrect because it doesn't contain a `,` or `..`,
@@ -57,7 +81,7 @@ const withoutIncorrectBraces = (pattern) => {
5781
* @returns {string}
5882
*/
5983
export const validateBraces = (pattern, logger) => {
60-
const fixedPattern = withoutIncorrectBraces(pattern)
84+
const fixedPattern = stripDoubleBraces(stripIncorrectBraces(pattern))
6185

6286
if (fixedPattern !== pattern) {
6387
logger.warn(incorrectBraces(pattern, fixedPattern))

test/unit/validateBraces.spec.js

+32-17
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,64 @@
11
import makeConsoleMock from 'consolemock'
22

3-
import { validateBraces, BRACES_REGEXP } from '../../lib/validateBraces.js'
3+
import {
4+
validateBraces,
5+
INCORRECT_BRACES_REGEXP,
6+
DOUBLE_BRACES_REGEXP,
7+
} from '../../lib/validateBraces.js'
48

5-
describe('BRACES_REGEXP', () => {
9+
describe('INCORRECT_BRACES_REGEXP', () => {
610
it(`should match '*.{js}'`, () => {
7-
expect('*.{js}'.match(BRACES_REGEXP)).toBeTruthy()
11+
expect('*.{js}'.match(INCORRECT_BRACES_REGEXP)).toBeTruthy()
812
})
913

1014
it(`should match 'file_{10}'`, () => {
11-
expect('file_{test}'.match(BRACES_REGEXP)).toBeTruthy()
15+
expect('file_{test}'.match(INCORRECT_BRACES_REGEXP)).toBeTruthy()
1216
})
1317

1418
it(`should match '*.{spec\\.js}'`, () => {
15-
expect('*.{spec\\.js}'.match(BRACES_REGEXP)).toBeTruthy()
19+
expect('*.{spec\\.js}'.match(INCORRECT_BRACES_REGEXP)).toBeTruthy()
1620
})
1721

1822
it(`should match '*.{js\\,ts}'`, () => {
19-
expect('*.{js\\,ts}'.match(BRACES_REGEXP)).toBeTruthy()
23+
expect('*.{js\\,ts}'.match(INCORRECT_BRACES_REGEXP)).toBeTruthy()
2024
})
2125

2226
it("should not match '*.${js}'", () => {
23-
expect('*.${js}'.match(BRACES_REGEXP)).not.toBeTruthy()
27+
expect('*.${js}'.match(INCORRECT_BRACES_REGEXP)).toBeFalsy()
2428
})
2529

2630
it(`should not match '.{js,ts}'`, () => {
27-
expect('.{js,ts}'.match(BRACES_REGEXP)).not.toBeTruthy()
31+
expect('.{js,ts}'.match(INCORRECT_BRACES_REGEXP)).toBeFalsy()
2832
})
2933

3034
it(`should not match 'file_{1..10}'`, () => {
31-
expect('file_{1..10}'.match(BRACES_REGEXP)).not.toBeTruthy()
35+
expect('file_{1..10}'.match(INCORRECT_BRACES_REGEXP)).toBeFalsy()
3236
})
3337

3438
it(`should not match '*.\\{js\\}'`, () => {
35-
expect('*.\\{js\\}'.match(BRACES_REGEXP)).not.toBeTruthy()
39+
expect('*.\\{js\\}'.match(INCORRECT_BRACES_REGEXP)).toBeFalsy()
3640
})
3741

3842
it(`should not match '*.\\{js}'`, () => {
39-
expect('*.\\{js}'.match(BRACES_REGEXP)).not.toBeTruthy()
43+
expect('*.\\{js}'.match(INCORRECT_BRACES_REGEXP)).toBeFalsy()
4044
})
4145

4246
it(`should not match '*.{js\\}'`, () => {
43-
expect('*.{js\\}'.match(BRACES_REGEXP)).not.toBeTruthy()
47+
expect('*.{js\\}'.match(INCORRECT_BRACES_REGEXP)).toBeFalsy()
48+
})
49+
})
50+
51+
describe('DOUBLE_BRACES_REGEXP', () => {
52+
it(`should match '*.{{js,ts}}'`, () => {
53+
expect('*.{{js,ts}}'.match(DOUBLE_BRACES_REGEXP)).toBeTruthy()
54+
})
55+
56+
it(`should not match '*.{{js,ts},{css}}'`, () => {
57+
expect('*.{{js,ts},{css}}'.match(DOUBLE_BRACES_REGEXP)).toBeFalsy()
58+
})
59+
60+
it(`should not match '*.{{js,ts},{css}}'`, () => {
61+
expect('*.{{js,ts},{css}}'.match(DOUBLE_BRACES_REGEXP)).toBeFalsy()
4462
})
4563
})
4664

@@ -84,18 +102,15 @@ describe('validateBraces', () => {
84102
`)
85103
})
86104

87-
/**
88-
* @todo This isn't correctly detected even though the outer braces are invalid.
89-
*/
90-
it.skip('should warn about `*.{{js,ts}}` and return fixed pattern', () => {
105+
it('should warn about `*.{{js,ts}}` and return fixed pattern', () => {
91106
const logger = makeConsoleMock()
92107

93108
const fixedBraces = validateBraces('*.{{js,ts}}', logger)
94109

95110
expect(fixedBraces).toEqual('*.{js,ts}')
96111
expect(logger.printHistory()).toMatchInlineSnapshot(`
97112
"
98-
WARN Detected incorrect braces with only single value: \`*.{{js,ts}}\`. Reformatted as: \`*.{js,ts}\`
113+
WARN Detected incorrect braces with only single value: \`*.{{js,ts}}\`. Reformatted as: \`*.{js,ts}\`
99114
"
100115
`)
101116
})

0 commit comments

Comments
 (0)