Skip to content

Commit

Permalink
fix #4008: allow quoted define and pure names
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Dec 19, 2024
1 parent 68573ab commit fd4cea7
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 28 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@

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.

* Allow quoted property names in `--define` and `--pure` ([#4008](https://github.com/evanw/esbuild/issues/4008))

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:

```js
// The following code now transforms to "return true;\n"
console.log(esbuild.transformSync(
`return process.env['SOME-TEST-VAR']`,
{ define: { 'process.env["SOME-TEST-VAR"]': 'true' } },
))
```

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.

* Minify empty `try`/`catch`/`finally` blocks ([#4003](https://github.com/evanw/esbuild/issues/4003))

With this release, esbuild will now attempt to minify empty `try` blocks:
Expand Down
40 changes: 13 additions & 27 deletions pkg/api/api_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,11 +386,11 @@ func validateSupported(log logger.Log, supported map[string]bool) (
return
}

func validateGlobalName(log logger.Log, text string) []string {
func validateGlobalName(log logger.Log, text string, path string) []string {
if text != "" {
source := logger.Source{
KeyPath: logger.Path{Text: "(global path)"},
PrettyPath: "(global name)",
KeyPath: logger.Path{Text: path},
PrettyPath: path,
Contents: text,
}

Expand Down Expand Up @@ -560,20 +560,11 @@ func validateDefines(

for _, key := range sortedKeys {
value := defines[key]
keyParts := strings.Split(key, ".")
mapKey := mapKeyForDefine(keyParts)

// The key must be a dot-separated identifier list
for _, part := range keyParts {
if !js_ast.IsIdentifier(part) {
if part == key {
log.AddError(nil, logger.Range{}, fmt.Sprintf("The define key %q must be a valid identifier", key))
} else {
log.AddError(nil, logger.Range{}, fmt.Sprintf("The define key %q contains invalid identifier %q", key, part))
}
continue
}
keyParts := validateGlobalName(log, key, "(define name)")
if keyParts == nil {
continue
}
mapKey := mapKeyForDefine(keyParts)

// Parse the value
defineExpr, injectExpr := js_parser.ParseDefineExprOrJSON(value)
Expand Down Expand Up @@ -678,16 +669,11 @@ func validateDefines(
}

for _, key := range pureFns {
keyParts := strings.Split(key, ".")
mapKey := mapKeyForDefine(keyParts)

// The key must be a dot-separated identifier list
for _, part := range keyParts {
if !js_ast.IsIdentifier(part) {
log.AddError(nil, logger.Range{}, fmt.Sprintf("Invalid pure function: %q", key))
continue
}
keyParts := validateGlobalName(log, key, "(pure name)")
if keyParts == nil {
continue
}
mapKey := mapKeyForDefine(keyParts)

// Merge with any previously-specified defines
define := rawDefines[mapKey]
Expand Down Expand Up @@ -1285,7 +1271,7 @@ func validateBuildOptions(
ASCIIOnly: validateASCIIOnly(buildOpts.Charset),
IgnoreDCEAnnotations: buildOpts.IgnoreAnnotations,
TreeShaking: validateTreeShaking(buildOpts.TreeShaking, buildOpts.Bundle, buildOpts.Format),
GlobalName: validateGlobalName(log, buildOpts.GlobalName),
GlobalName: validateGlobalName(log, buildOpts.GlobalName, "(global name)"),
CodeSplitting: buildOpts.Splitting,
OutputFormat: validateFormat(buildOpts.Format),
AbsOutputFile: validatePath(log, realFS, buildOpts.Outfile, "outfile path"),
Expand Down Expand Up @@ -1729,7 +1715,7 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult
SourceRoot: transformOpts.SourceRoot,
ExcludeSourcesContent: transformOpts.SourcesContent == SourcesContentExclude,
OutputFormat: validateFormat(transformOpts.Format),
GlobalName: validateGlobalName(log, transformOpts.GlobalName),
GlobalName: validateGlobalName(log, transformOpts.GlobalName, "(global name)"),
MinifySyntax: transformOpts.MinifySyntax,
MinifyWhitespace: transformOpts.MinifyWhitespace,
MinifyIdentifiers: transformOpts.MinifyIdentifiers,
Expand Down
28 changes: 27 additions & 1 deletion scripts/js-api-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -6088,7 +6088,7 @@ class Foo {
assert.strictEqual(code, `if (x) ;\n`)
},

async define({ esbuild }) {
async defineProcessEnvNodeEnv({ esbuild }) {
const define = { 'process.env.NODE_ENV': '"something"' }

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

async defineQuotedPropertyNameTransform({ esbuild }) {
const { code: code1 } = await esbuild.transform(`return x.y['z']`, { define: { 'x.y["z"]': 'true' } })
assert.strictEqual(code1, `return true;\n`)

const { code: code2 } = await esbuild.transform(`foo(x['y'].z, x.y['z'], x['y']['z'])`, { define: { 'x.y.z': 'true' } })
assert.strictEqual(code2, `foo(true, true, true);\n`)

const { code: code3 } = await esbuild.transform(`foo(x['y'].z, x.y['z'], x['y']['z'])`, { define: { 'x["y"].z': 'true' } })
assert.strictEqual(code3, `foo(true, true, true);\n`)

const { code: code4 } = await esbuild.transform(`foo(x['y'].z, x.y['z'], x['y']['z'])`, { define: { 'x.y["z"]': 'true' } })
assert.strictEqual(code4, `foo(true, true, true);\n`)

const { code: code5 } = await esbuild.transform(`foo(x['y'].z, x.y['z'], x['y']['z'])`, { define: { 'x["y"][\'z\']': 'true' } })
assert.strictEqual(code5, `foo(true, true, true);\n`)
},

async defineQuotedPropertyNameBuild({ esbuild }) {
const { outputFiles } = await esbuild.build({
stdin: { contents: `return process.env['SOME-TEST-VAR']` },
define: { 'process.env["SOME-TEST-VAR"]': 'true' },
write: false,
})
assert.strictEqual(outputFiles[0].text, `return true;\n`)
},

async json({ esbuild }) {
const { code } = await esbuild.transform(`{ "x": "y" }`, { loader: 'json' })
assert.strictEqual(code, `module.exports = { x: "y" };\n`)
Expand Down

0 comments on commit fd4cea7

Please sign in to comment.