Skip to content

Commit fd4cea7

Browse files
committed
fix #4008: allow quoted define and pure names
1 parent 68573ab commit fd4cea7

File tree

3 files changed

+54
-28
lines changed

3 files changed

+54
-28
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,20 @@
2121

2222
The above code will be considered valid starting with this release. This change to esbuild follows a [similar change to TypeScript](https://github.com/microsoft/TypeScript/pull/60225) which will allow this syntax starting with TypeScript 5.7.
2323

24+
* Allow quoted property names in `--define` and `--pure` ([#4008](https://github.com/evanw/esbuild/issues/4008))
25+
26+
The `define` and `pure` API options now accept identifier expressions containing quoted property names. Previously all identifiers in the identifier expression had to be bare identifiers. This change now makes `--define` and `--pure` consistent with `--global-name`, which already supported quoted property names. For example, the following is now possible:
27+
28+
```js
29+
// The following code now transforms to "return true;\n"
30+
console.log(esbuild.transformSync(
31+
`return process.env['SOME-TEST-VAR']`,
32+
{ define: { 'process.env["SOME-TEST-VAR"]': 'true' } },
33+
))
34+
```
35+
36+
Note that if you're passing values like this on the command line using esbuild's `--define` flag, then you'll need to know how to escape quote characters for your shell. You may find esbuild's JavaScript API more ergonomic and portable than writing shell code.
37+
2438
* Minify empty `try`/`catch`/`finally` blocks ([#4003](https://github.com/evanw/esbuild/issues/4003))
2539

2640
With this release, esbuild will now attempt to minify empty `try` blocks:

pkg/api/api_impl.go

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -386,11 +386,11 @@ func validateSupported(log logger.Log, supported map[string]bool) (
386386
return
387387
}
388388

389-
func validateGlobalName(log logger.Log, text string) []string {
389+
func validateGlobalName(log logger.Log, text string, path string) []string {
390390
if text != "" {
391391
source := logger.Source{
392-
KeyPath: logger.Path{Text: "(global path)"},
393-
PrettyPath: "(global name)",
392+
KeyPath: logger.Path{Text: path},
393+
PrettyPath: path,
394394
Contents: text,
395395
}
396396

@@ -560,20 +560,11 @@ func validateDefines(
560560

561561
for _, key := range sortedKeys {
562562
value := defines[key]
563-
keyParts := strings.Split(key, ".")
564-
mapKey := mapKeyForDefine(keyParts)
565-
566-
// The key must be a dot-separated identifier list
567-
for _, part := range keyParts {
568-
if !js_ast.IsIdentifier(part) {
569-
if part == key {
570-
log.AddError(nil, logger.Range{}, fmt.Sprintf("The define key %q must be a valid identifier", key))
571-
} else {
572-
log.AddError(nil, logger.Range{}, fmt.Sprintf("The define key %q contains invalid identifier %q", key, part))
573-
}
574-
continue
575-
}
563+
keyParts := validateGlobalName(log, key, "(define name)")
564+
if keyParts == nil {
565+
continue
576566
}
567+
mapKey := mapKeyForDefine(keyParts)
577568

578569
// Parse the value
579570
defineExpr, injectExpr := js_parser.ParseDefineExprOrJSON(value)
@@ -678,16 +669,11 @@ func validateDefines(
678669
}
679670

680671
for _, key := range pureFns {
681-
keyParts := strings.Split(key, ".")
682-
mapKey := mapKeyForDefine(keyParts)
683-
684-
// The key must be a dot-separated identifier list
685-
for _, part := range keyParts {
686-
if !js_ast.IsIdentifier(part) {
687-
log.AddError(nil, logger.Range{}, fmt.Sprintf("Invalid pure function: %q", key))
688-
continue
689-
}
672+
keyParts := validateGlobalName(log, key, "(pure name)")
673+
if keyParts == nil {
674+
continue
690675
}
676+
mapKey := mapKeyForDefine(keyParts)
691677

692678
// Merge with any previously-specified defines
693679
define := rawDefines[mapKey]
@@ -1285,7 +1271,7 @@ func validateBuildOptions(
12851271
ASCIIOnly: validateASCIIOnly(buildOpts.Charset),
12861272
IgnoreDCEAnnotations: buildOpts.IgnoreAnnotations,
12871273
TreeShaking: validateTreeShaking(buildOpts.TreeShaking, buildOpts.Bundle, buildOpts.Format),
1288-
GlobalName: validateGlobalName(log, buildOpts.GlobalName),
1274+
GlobalName: validateGlobalName(log, buildOpts.GlobalName, "(global name)"),
12891275
CodeSplitting: buildOpts.Splitting,
12901276
OutputFormat: validateFormat(buildOpts.Format),
12911277
AbsOutputFile: validatePath(log, realFS, buildOpts.Outfile, "outfile path"),
@@ -1729,7 +1715,7 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult
17291715
SourceRoot: transformOpts.SourceRoot,
17301716
ExcludeSourcesContent: transformOpts.SourcesContent == SourcesContentExclude,
17311717
OutputFormat: validateFormat(transformOpts.Format),
1732-
GlobalName: validateGlobalName(log, transformOpts.GlobalName),
1718+
GlobalName: validateGlobalName(log, transformOpts.GlobalName, "(global name)"),
17331719
MinifySyntax: transformOpts.MinifySyntax,
17341720
MinifyWhitespace: transformOpts.MinifyWhitespace,
17351721
MinifyIdentifiers: transformOpts.MinifyIdentifiers,

scripts/js-api-tests.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6088,7 +6088,7 @@ class Foo {
60886088
assert.strictEqual(code, `if (x) ;\n`)
60896089
},
60906090

6091-
async define({ esbuild }) {
6091+
async defineProcessEnvNodeEnv({ esbuild }) {
60926092
const define = { 'process.env.NODE_ENV': '"something"' }
60936093

60946094
const { code: code1 } = await esbuild.transform(`console.log(process.env.NODE_ENV)`, { define })
@@ -6165,6 +6165,32 @@ class Foo {
61656165
`)
61666166
},
61676167

6168+
async defineQuotedPropertyNameTransform({ esbuild }) {
6169+
const { code: code1 } = await esbuild.transform(`return x.y['z']`, { define: { 'x.y["z"]': 'true' } })
6170+
assert.strictEqual(code1, `return true;\n`)
6171+
6172+
const { code: code2 } = await esbuild.transform(`foo(x['y'].z, x.y['z'], x['y']['z'])`, { define: { 'x.y.z': 'true' } })
6173+
assert.strictEqual(code2, `foo(true, true, true);\n`)
6174+
6175+
const { code: code3 } = await esbuild.transform(`foo(x['y'].z, x.y['z'], x['y']['z'])`, { define: { 'x["y"].z': 'true' } })
6176+
assert.strictEqual(code3, `foo(true, true, true);\n`)
6177+
6178+
const { code: code4 } = await esbuild.transform(`foo(x['y'].z, x.y['z'], x['y']['z'])`, { define: { 'x.y["z"]': 'true' } })
6179+
assert.strictEqual(code4, `foo(true, true, true);\n`)
6180+
6181+
const { code: code5 } = await esbuild.transform(`foo(x['y'].z, x.y['z'], x['y']['z'])`, { define: { 'x["y"][\'z\']': 'true' } })
6182+
assert.strictEqual(code5, `foo(true, true, true);\n`)
6183+
},
6184+
6185+
async defineQuotedPropertyNameBuild({ esbuild }) {
6186+
const { outputFiles } = await esbuild.build({
6187+
stdin: { contents: `return process.env['SOME-TEST-VAR']` },
6188+
define: { 'process.env["SOME-TEST-VAR"]': 'true' },
6189+
write: false,
6190+
})
6191+
assert.strictEqual(outputFiles[0].text, `return true;\n`)
6192+
},
6193+
61686194
async json({ esbuild }) {
61696195
const { code } = await esbuild.transform(`{ "x": "y" }`, { loader: 'json' })
61706196
assert.strictEqual(code, `module.exports = { x: "y" };\n`)

0 commit comments

Comments
 (0)