Repository: tdewolff/minify (language: python)

Issue:
js: incorrect behavior when minifying optional chaining with bracket notation (a?.["b"])
## Description

When using the minifier to process JavaScript code that includes optional chaining with bracket notation, such as `a?.["b"]`, the output is incorrect. This issue appears to be the root cause of #744 , where similar behavior was observed. The specific problem lies in how the minifier transforms `a?.["b"]` into `a?..b`, which is invalid JavaScript.

## Reproduce

In `js/js_test.go`

```go
func TestOptionalChainingWithBracket (t *testing.T) {
	m := minify.New()
	o := Minifier{KeepVarNames: true, useAlphabetVarNames: true}
	r := bytes.NewBufferString(`
		let a = {
			"b": "foo"
		};
		console.log(a.b);
		console.log(a?.b);
		console.log(a["b"]);
		console.log(a?.["b"]);    // Unexpected Behavior Here!!
	`)
	w := &bytes.Buffer{}
	o.Minify(m, w, r, nil)
	fmt.Println(w)
}
```

```sh
go test --run TestOptionalChainingWithBracket  -v
```

output:
```js
let a={b:"foo"};console.log(a.b),console.log(a?.b),console.log(a.b),console.log(a?..b)
```

The last line of the output `console.log(a?..b)` is incorrect. The minifier improperly handles the bracket notation in optional chaining, resulting in invalid JavaScript syntax.

## Expected Behavior

The minified code should correctly preserve the bracket notation in optional chaining. The expected output should be:

```js
let a={b:"foo"};console.log(a.b),console.log(a?.b),console.log(a.b),console.log(a?.["b"])
```

---

![image](https://github.com/user-attachments/assets/427cd95c-a677-41ac-b819-2172c1fbf954)

js: incorrect behavior when minifying optional chaining with bracket notation (a?.["b"])
## Description

When using the minifier to process JavaScript code that includes optional chaining with bracket notation, such as `a?.["b"]`, the output is incorrect. This issue appears to be the root cause of #744 , where similar behavior was observed. The specific problem lies in how the minifier transforms `a?.["b"]` into `a?..b`, which is invalid JavaScript.

## Reproduce

In `js/js_test.go`

```go
func TestOptionalChainingWithBracket (t *testing.T) {
	m := minify.New()
	o := Minifier{KeepVarNames: true, useAlphabetVarNames: true}
	r := bytes.NewBufferString(`
		let a = {
			"b": "foo"
		};
		console.log(a.b);
		console.log(a?.b);
		console.log(a["b"]);
		console.log(a?.["b"]);    // Unexpected Behavior Here!!
	`)
	w := &bytes.Buffer{}
	o.Minify(m, w, r, nil)
	fmt.Println(w)
}
```

```sh
go test --run TestOptionalChainingWithBracket  -v
```

output:
```js
let a={b:"foo"};console.log(a.b),console.log(a?.b),console.log(a.b),console.log(a?..b)
```

The last line of the output `console.log(a?..b)` is incorrect. The minifier improperly handles the bracket notation in optional chaining, resulting in invalid JavaScript syntax.

## Expected Behavior

The minified code should correctly preserve the bracket notation in optional chaining. The expected output should be:

```js
let a={b:"foo"};console.log(a.b),console.log(a?.b),console.log(a.b),console.log(a?.["b"])
```

---

![image](https://github.com/user-attachments/assets/427cd95c-a677-41ac-b819-2172c1fbf954)



Discussion / hints:

Fix patch:
diff --git a/js/js.go b/js/js.go
index 7802d21fa..b421bbf78 100644
--- a/js/js.go
+++ b/js/js.go
@@ -1217,12 +1217,18 @@ func (m *jsMinifier) minifyExpr(i js.IExpr, prec js.OpPrec) {
 		} else {
 			m.minifyExpr(expr.X, js.OpMember)
 		}
+
+		// To address issue #757
+		isWrittenOptChainBytes := false
 		if expr.Optional {
 			m.write(optChainBytes)
+			isWrittenOptChainBytes = true
 		}
 		if lit, ok := expr.Y.(*js.LiteralExpr); ok && lit.TokenType == js.StringToken && 2 < len(lit.Data) {
 			if isIdent := js.AsIdentifierName(lit.Data[1 : len(lit.Data)-1]); isIdent {
-				m.write(dotBytes)
+				if !isWrittenOptChainBytes {
+					m.write(dotBytes)
+				}
 				m.write(lit.Data[1 : len(lit.Data)-1])
 				break
 			} else if isNum := js.AsDecimalLiteral(lit.Data[1 : len(lit.Data)-1]); isNum {


Test patch:
diff --git a/js/js_test.go b/js/js_test.go
index e79a7ed98..fc63a27ae 100644
--- a/js/js_test.go
+++ b/js/js_test.go
@@ -3,7 +3,6 @@ package js
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"runtime"
 	"testing"
@@ -679,6 +678,8 @@ func TestJS(t *testing.T) {
 		{`a=obj["3name"]`, `a=obj["3name"]`},
 		{"a=b`tmpl${a?b:b}tmpl`", "a=b`tmpl${a,b}tmpl`"},
 		{`a=b?.[c]`, `a=b?.[c]`},
+		{`a=b?.["c"]`, `a=b?.c`},         // Issue 757
+		{`a=b?.["c d"]`, `a=b?.["c d"]`}, // Issue 757
 		{`a=b.#c`, `a=b.#c`},
 		{`a=b().#c`, `a=b().#c`},
 		{`a=b?.#c`, `a=b?.#c`},
@@ -977,7 +978,7 @@ func TestRenamerIndices(t *testing.T) {
 
 func BenchmarkJQuery(b *testing.B) {
 	m := minify.New()
-	buf, err := ioutil.ReadFile("../benchmarks/sample_jquery.js")
+	buf, err := os.ReadFile("../benchmarks/sample_jquery.js")
 	if err != nil {
 		panic(err)
 	}