Skip to content

Commit 8f56771

Browse files
committed
fix #4121: map js regexp flags to go regexp flags
1 parent 36b458d commit 8f56771

File tree

3 files changed

+56
-4
lines changed

3 files changed

+56
-4
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
* Support flags in regular expressions for the API ([#4121](https://github.com/evanw/esbuild/issues/4121))
6+
7+
The JavaScript plugin API for esbuild takes JavaScript regular expression objects for the `filter` option. Internally these are translated into Go regular expressions. However, this translation previously ignored the `flags` property of the regular expression. With this release, esbuild will now translate JavaScript regular expression flags into Go regular expression flags. Specifically the JavaScript regular expression `/\.[jt]sx?$/i` is turned into the Go regular expression `` `(?i)\.[jt]sx?$` `` internally inside of esbuild's API. This should make it possible to use JavaScript regular expressions with the `i` flag. Note that JavaScript and Go don't support all of the same regular expression features, so this mapping is only approximate.
8+
59
* Fix node-specific annotations for string literal export names ([#4100](https://github.com/evanw/esbuild/issues/4100))
610

711
When node instantiates a CommonJS module, it scans the AST to look for names to expose via ESM named exports. This is a heuristic that looks for certain patterns such as `exports.NAME = ...` or `module.exports = { ... }`. This behavior is used by esbuild to "annotate" CommonJS code that was converted from ESM with the original ESM export names. For example, when converting the file `export let foo, bar` from ESM to CommonJS, esbuild appends this to the end of the file:

lib/shared/common.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,8 @@ function pushCommonFlags(flags: string[], options: CommonOptions, keys: OptionKe
190190
if (ignoreAnnotations) flags.push(`--ignore-annotations`)
191191
if (drop) for (let what of drop) flags.push(`--drop:${validateStringValue(what, 'drop')}`)
192192
if (dropLabels) flags.push(`--drop-labels=${Array.from(dropLabels).map(what => validateStringValue(what, 'dropLabels')).join(',')}`)
193-
if (mangleProps) flags.push(`--mangle-props=${mangleProps.source}`)
194-
if (reserveProps) flags.push(`--reserve-props=${reserveProps.source}`)
193+
if (mangleProps) flags.push(`--mangle-props=${jsRegExpToGoRegExp(mangleProps)}`)
194+
if (reserveProps) flags.push(`--reserve-props=${jsRegExpToGoRegExp(reserveProps)}`)
195195
if (mangleQuoted !== void 0) flags.push(`--mangle-quoted=${mangleQuoted}`)
196196

197197
if (jsx) flags.push(`--jsx=${jsx}`)
@@ -1316,7 +1316,7 @@ let handlePlugins = async (
13161316
if (filter == null) throw new Error(`onResolve() call is missing a filter`)
13171317
let id = nextCallbackID++
13181318
onResolveCallbacks[id] = { name: name!, callback, note: registeredNote }
1319-
plugin.onResolve.push({ id, filter: filter.source, namespace: namespace || '' })
1319+
plugin.onResolve.push({ id, filter: jsRegExpToGoRegExp(filter), namespace: namespace || '' })
13201320
},
13211321

13221322
onLoad(options, callback) {
@@ -1329,7 +1329,7 @@ let handlePlugins = async (
13291329
if (filter == null) throw new Error(`onLoad() call is missing a filter`)
13301330
let id = nextCallbackID++
13311331
onLoadCallbacks[id] = { name: name!, callback, note: registeredNote }
1332-
plugin.onLoad.push({ id, filter: filter.source, namespace: namespace || '' })
1332+
plugin.onLoad.push({ id, filter: jsRegExpToGoRegExp(filter), namespace: namespace || '' })
13331333
},
13341334

13351335
onDispose(callback) {
@@ -1859,3 +1859,9 @@ function convertOutputFiles({ path, contents, hash }: protocol.BuildOutputFile):
18591859
},
18601860
}
18611861
}
1862+
1863+
function jsRegExpToGoRegExp(regexp: RegExp): string {
1864+
let result = regexp.source
1865+
if (regexp.flags) result = `(?${regexp.flags})${result}`
1866+
return result
1867+
}

scripts/plugin-tests.js

+42
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,48 @@ let pluginTests = {
145145
}
146146
},
147147

148+
async caseInsensitiveRegExp({ esbuild, testDir }) {
149+
const inputJs = path.join(testDir, 'in.js')
150+
await writeFileAsync(inputJs, `export default 123`)
151+
152+
const inputCpp = path.join(testDir, 'in.CpP')
153+
await writeFileAsync(inputCpp, `export default 123`)
154+
155+
// onResolve
156+
const onResolveResult = await esbuild.build({
157+
entryPoints: ['example.CpP'],
158+
write: false,
159+
plugins: [{
160+
name: 'name',
161+
setup(build) {
162+
build.onResolve({ filter: /\.c(pp|xx)?$/i }, args => {
163+
assert.strictEqual(args.path, 'example.CpP')
164+
return { path: inputJs }
165+
})
166+
},
167+
}],
168+
})
169+
assert.strictEqual(onResolveResult.outputFiles.length, 1)
170+
assert.strictEqual(onResolveResult.outputFiles[0].text, `export default 123;\n`)
171+
172+
// onLoad
173+
const onLoadResult = await esbuild.build({
174+
entryPoints: [inputCpp],
175+
write: false,
176+
plugins: [{
177+
name: 'name',
178+
setup(build) {
179+
build.onLoad({ filter: /\.c(pp|xx)?$/i }, args => {
180+
assert(args.path.endsWith('in.CpP'))
181+
return { contents: 'export default true' }
182+
})
183+
},
184+
}],
185+
})
186+
assert.strictEqual(onLoadResult.outputFiles.length, 1)
187+
assert.strictEqual(onLoadResult.outputFiles[0].text, `export default true;\n`)
188+
},
189+
148190
async pluginMissingName({ esbuild }) {
149191
try {
150192
await esbuild.build({

0 commit comments

Comments
 (0)