Commit graph

214 commits

Author SHA1 Message Date
camc314
4d4e805691 feat(minifier): collapse if stmt with empty consequent (#8577) 2025-01-18 03:51:40 +00:00
sapphi-red
991a22f907
feat(minifier): fold Array::concat into literal (#8442)
Compress `[].concat(a, [b])` into `[a, b]`.

**References**
- [Spec of `Array::concat`](https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.concat)
2025-01-17 06:00:07 +00:00
sapphi-red
3dc2d8b8e9
feat(minifier): fold string concat chaining (#8441)
Compress `"".concat(a).concat(b)` into `"".concat(a, b)`.

**References**
- [Spec of `String::concat`](https://tc39.es/ecma262/multipage/text-processing.html#sec-string.prototype.concat)
2025-01-17 06:00:06 +00:00
sapphi-red
a4ae4505f1
feat(minifier): fold array concat chaining (#8440)
Compress `[].concat(a).concat(b)` into `[].concat(a, b)`.

**References**
- [Spec of `Array::concat`](https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.concat)

---

### The new assumption

I added a new assumption description in `crates/oxc_minifier/README.md`: "Errors thrown when creating a String or an Array that exceeds the maximum length can disappear or moved".
This is an assumption held by other minifiers. Without this assumption, we have to treat `+` and array creation/update to have a sideeffect and that limits the minification.

For input:
```js
[...Array(Number(2n ** 32n - 1n)),""]
" ".repeat(Number(2n ** 29n - 24n - 1n)) + ' '
export {}
```
(Note that `2 ** 32 - 1` is the max array length and `2 ** 29 - 24` is the max string length on V8.)
This code errors with too long array and too long string on V8 based runtimes.

terser outputs:
```js
Array(Number(2n**32n-1n))," ".repeat(Number(536870887n));export{};
```
No error will happen with this code.

SWC outputs:
```js
[...Array(Number(2n**32n-1n))]," ".repeat(Number(2n**29n-24n-1n));export{};
```
No error will happen with this code.
[playground](https://play.swc.rs/?version=1.10.7&code=H4sIAAAAAAAAA4vW09NzLCpKrNTwK81NSi3SMMpT0NJSMAZSugqGeZqaOkpKsbxcSgpKekWpBamJJajqjCxB6oxMYKoVtBXUFdR5uVIrCvKLShSqa3m5AGRqy%2FBiAAAA&config=H4sIAAAAAAAAA32UO3LjMAyG%2B5zCozrFjostcoDtcgYOTYIyvXxoCNCxJuO7L0RJtjeG3En48AMkAOL7bbfrTmi6j903f%2FLPoAtCuf2zBcdE%2BsKWDkzUaIofqHtf6Qkn5HRAaKbrTDrSpQdqKtz%2F2u8XRRdyRlgViy365N34mNPkOBRAfLCxlUPWCInwf%2F3CSv6aAJX6aD%2FkHECnF0RpVD4R9FCkwCaHoAcEddZFiDKdVBePWUoxwUpg1VDyIPJkPfmcOOcztaCtMtmCgHwBQ%2F4MkoxzsSwhX0%2B4T8MWDrXvW59%2FqOGsQ9Uk5IRLawmfVoh6zB5JuZqkEs5wowYzXIr7U%2BmdKkC1pGfdKfu00ZO%2FAFyBoBGTjiDFbR6O52lL7V4qfXI8sjQKnOdbumWCnouqvHdCZafKQCEvdbOArQamyhrpOAveKB96Cwqc41kRQuOXJ3OUktI4QHYC4P5qJ03VDNTtFW7w6UG8wH%2F4liQP2OIRNR23KY7xkMOLBBHomO0LB24F5W1ceEtchm1ekwUeDbCiS8UGnpcAPwDKKrR9%2BTQb%2FDw4oupDPtzXxOJwve3hqFN%2Ff%2B%2FzKn5bHLqYbW1wWfJTf%2BfV%2FLu7O61beD1B5%2FFzFbac13%2FKOhgeLwYAAA%3D%3D)

esbuild outputs:
```js
[...Array(Number(2n**32n-1n))]," ".repeat(Number(2n**29n-24n-1n))+"";export{};
```
No error will happen with this code.
[esbuild try](https://esbuild.github.io/try/#dAAwLjI0LjIALS1taW5pZnkAWy4uLkFycmF5KE51bWJlcigybiAqKiAzMm4gLSAxbikpLCIiXQoiICIucmVwZWF0KE51bWJlcigybiAqKiAyOW4gLSAyNG4gLSAxbikpICsgJyAnCmV4cG9ydCB7fQ)

OXC outputs:
```js
[...Array(Number(2n**32n-1n))]," ".repeat(Number(2n**29n-24n-1n))+" ";export{};
```
The array error won't happen and the String error will happen with this code.
[playground](https://playground.oxc.rs/#eNpVT71OwzAQfhXrlv4QIhpgIBtLR8SOGZxwCUH22To7baMo746dNEVMd5/u+7sRaijhI8/zV2Y1bN96UyFvCxL7vXiM414caLfLJEj4lCRBSMgZHarwn1u8JG7xtCrEndiIjSS8OMtBjJMkyMBCOQL3lIYfKKgLlIF7zEB3FKBslPYR+No6XC9+MJXVKwqsyDeWzZU8ZeAUe+TZ0vZc47HTSMpEAwjoQ/7jY7JjjKQTvitG8n/ilDuL437zXtyC4hZjKUBfPByeITJq+4UtpvoRmI66plu4tTUpIRovNc/fXcx2qr69YRS1+opmJwps9VHbc9KfkCvr43npNU2/v/WIsw==)
2025-01-17 05:53:54 +00:00
Boshen
b1d018622b fix(minifier): do not fold !!void b (#8533) 2025-01-16 05:52:03 +00:00
Boshen
53adde5003
fix(minifier): x['-2147483648'] -> x[-2147483648] (#8528) 2025-01-16 13:43:23 +08:00
Boshen
1860411656 feat(minifier): remove last redundant return statement (#8523) 2025-01-16 02:07:28 +00:00
Boshen
8accfefa74 feat(minifier): minify var x; void x -> void 0 (#8466) 2025-01-13 16:07:59 +00:00
Boshen
3c9354983d fix(minifier): dce if statement should keep side effects and vars (#8433)
closes #7209

Note: Current output is sub-optimal.
2025-01-11 14:02:12 +00:00
sapphi-red
d56020b84c
feat(minifier): drop 0 from new Int8Array(0) and other TypedArrays (#8431)
Compresses `new Int8Array(0)` into `new Int8Array()`. (then will be compress into `new Int8Array`).

Partial quote from the [spec](https://tc39.es/ecma262/multipage/indexed-collections.html#sec-typedarray):

> 5. If numberOfArgs = 0, then
>   a. Return ? AllocateTypedArray(constructorName, NewTarget, proto, 0).
> 6. Else,
>   c. Else,
>       ii. Let elementLength be ? ToIndex(firstArgument).
>      iii. Return ? AllocateTypedArray(constructorName, NewTarget, proto, elementLength).
2025-01-11 07:45:36 +00:00
sapphi-red
f935d9434f
feat(minifier): remove new from NativeErrors / AggregateError (#8430)
Remove `new` in the some cases:

- NativeErrors (e.g. `new EvalError(...)` -> `EvalError(...)`): [spec](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-nativeerror-constructors:~:text=the%20function%20call%20NativeError(%E2%80%A6)%20is%20equivalent%20to%20the%20object%20creation%20expression%20new%20NativeError(%E2%80%A6)%20with%20the%20same%20arguments.), [the list of NativeErrors in spec](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-native-error-types-used-in-this-standard)
- `new AggregateError(...)` -> `AggregateError(...)`: [spec](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-aggregate-error-constructor:~:text=the%20function%20call%20AggregateError(%E2%80%A6)%20is%20equivalent%20to%20the%20object%20creation%20expression%20new%20AggregateError(%E2%80%A6)%20with%20the%20same%20arguments.)
2025-01-11 07:23:07 +00:00
Boshen
dab7a51e78 feat(minifier): minimize not !(x === undefined) -> x !== undefined (#8429) 2025-01-11 06:30:36 +00:00
Boshen
357b61d179 fix(minifier): do not minify Object.defineProperty in sequence expressions (#8416) 2025-01-10 12:04:39 +00:00
Boshen
5b5b8443f4
feat(minifier): fold ambiguous if else (#8415) 2025-01-10 19:51:31 +08:00
Boshen
fb2acd87b3 refactor(minifier): change minimize conditionals into a loop (#8413) 2025-01-10 09:49:09 +00:00
Boshen
baaec6020c refactor(minifier): remove the buggy ?? transform (#8411)
e.g. `(a != null ? a : b);` is not `a ?? b`

There are also no unit tests.
2025-01-10 09:33:09 +00:00
Boshen
438a6e7abc feat(minifier): minimize conditions in boolean context (#8381) 2025-01-10 03:47:10 +00:00
camc314
793cb43138 feat(minifier): a != null ? a : b -> a ?? b (#8352) 2025-01-09 10:12:52 +00:00
sapphi-red
814da55f81
feat(minifier): compress x = x || 1 to x ||= 1 (#8368)
The simplified version of the evaluation of `a = a || b` is:

> AssignmentExpression : LeftHandSideExpression = LogicalORExpression || LogicalANDExpression
> 1. Let lRef be ? Evaluation of LeftHandSideExpression.
> 2. Let llRef be ? Evaluation of LogicalORExpression.
> 3. Let llVal be ? GetValue(llRef).
> 4. If ToBoolean(llVal) is true
>   a. Perform ? PutValue(lRef, llVal).
>   b. return llVal.
> 5. Let lrRef be ? Evaluation of LogicalANDExpression.
> 6. Let rRef be ? GetValue(lrRef).
> 7. Let rVal be ? GetValue(rRef). [Note GetValue(rRef) returns rRef itself]
> 8. Perform ? PutValue(lRef, rVal).
> 9. Return rVal.

The simplified version of the evaluation of `a ||= b` is:

> AssignmentExpression : LeftHandSideExpression ||= AssignmentExpression
> 1. Let lRef be ? Evaluation of LeftHandSideExpression.
> 2. Let lVal be ? GetValue(lRef).
> 3. If ToBoolean(lVal) is true, return lVal.
> 4. Let rRef be ? Evaluation of AssignmentExpression.
> 5. Let rVal be ? GetValue(rRef).
> 6. Perform ? PutValue(lRef, rVal).
> 7. Return rVal.

The difference of these is that

- the evaluation of `a` is done twice for `a = a || b`, one with `1. Let lRef be ? Evaluation of LeftHandSideExpression` and one with `2. Let llRef be ? Evaluation of LogicalORExpression.`. This is same with #8366, #8367.
- `PutValue(lRef, llVal)` is performed when `ToBoolean(lVal)` is `true`.

So `x = x || 1` can be compressed to `x ||= 1` when the conditions written in #8366 are met and `PutValue(lRef, llVal)` does not have a side effect. When `a` is a non-global identifier (and not a reference created by a `with` statement), these conditions are met.

**References**
- [Spec of `||`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-binary-logical-operators-runtime-semantics-evaluation)
- [Spec of `=` / `||=`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-binary-logical-operators-runtime-semantics-evaluation)
2025-01-09 03:47:12 +00:00
sapphi-red
a596821d85
feat(minifier): compress a.b = a.b + c to a.b += c (#8367)
The simplified version of the evaluation of `a += b` is:

> AssignmentExpression : LeftHandSideExpression AssignmentOperator AssignmentExpression
> 1. Let lRef be ? Evaluation of LeftHandSideExpression.
> 2. Let lVal be ? GetValue(lRef).
> 3. Let rRef be ? Evaluation of AssignmentExpression.
> 4. Let rVal be ? GetValue(rRef).
> 5. Let r be ? ApplyStringOrNumericBinaryOperator(lVal, opText, rVal).
> 6. Perform ? PutValue(lRef, r).
> 7. Return r.

The simplified version of the evaluation of `a = a + b` is:

> AssignmentExpression : LeftHandSideExpression = AssignmentExpressionLeft + AssignmentExpressionRight
> 1. Let lRef be ? Evaluation of LeftHandSideExpression.
> 2. Let alRef be ? Evaluation of AssignmentExpressionLeft.
> 3. Let alVal be ? GetValue(alRef).
> 4. Let arRef be ? Evaluation of AssignmentExpressionRight.
> 5. Let arVal be ? GetValue(arRef).
> 6. Let rRef be ? ApplyStringOrNumericBinaryOperator(alVal, opText, arVal).
> 7. Let rVal be ? GetValue(rRef). [Note GetValue(rRef) returns rRef itself]
> 8. Perform ? PutValue(lRef, rVal).
> 9. Return rVal.

The difference of these is that the evaluation of `a` is done twice for `a = a + b`, one with `1. Let lRef be ? Evaluation of LeftHandSideExpression` and one with `2. Let alRef be ? Evaluation of AssignmentExpressionLeft.`

So this is same with #8366 and can be compressed similarly when the conditions are met (`a.b = a.b + c` -> `a.b += c`).

**References**
- [Spec of `=`, `+=`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-assignment-operators-runtime-semantics-evaluation)
- [Spec of `+`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-addition-operator-plus-runtime-semantics-evaluation)
2025-01-09 03:47:11 +00:00
sapphi-red
579eb603d5
feat(minifier): compress a.b || (a.b = c) to a.b ||= c (#8366)
As noted in https://github.com/oxc-project/oxc/pull/8315, the only difference of `x ||= y` and `x || (x = y)` is that `Let lRef be ? Evaluation of LeftHandSideExpression` is done twice.

If the LeftHandSideExpression is a MemberExpression, `GetValue(baseReference)` is the only place that might have a side effect. Further more, if the `baseReference` is an IdentifierReference that is not a global reference (and not a reference created by a `with` statement), `GetValue(baseReference)` does not have a side effect.

When those conditions are met, `a.b || (a.b = c)` can be compress to `a.b ||= c`.

**References**
- [Spec of `a.b`](<https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-property-accessors-runtime-semantics-evaluation:~:text=%2C%20strict).-,MemberExpression,4.%20Return%20MakePrivateReference(baseValue%2C%20fieldNameString).,-CallExpression>)
- [Spec of `GetValue`](https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-getvalue)
- [Spec of `IdentifierReference`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-identifiers-runtime-semantics-evaluation)
-  [Spec of `DeclarativeEnvironmentRecord.GetBindingValue`](https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-declarative-environment-records-getbindingvalue-n-s)
2025-01-09 03:47:10 +00:00
camc314
f367a16364 feat(minifier): port esbuild conditional expr minification (#8351)
still some TODOs, but the main framework is there
2025-01-09 02:49:33 +00:00
Boshen
09f0f483f6 refactor(minifier): remove the buggy minimize_exit_points implementation (#8349) 2025-01-09 02:49:32 +00:00
sapphi-red
8d52cd0a5e
feat(minifier): merge assign expression in conditional expression (#8345)
compresses `a ? b = 0 : b = 1` into `b = a ? 0 : 1`

This can be done when `b` is an IdentifierReference and the assignment operator is `=`.

In this circumstance, the evaluation of `b = a ? 0 : 1` is:

1. Let lref be ? Evaluation of LeftHandSideExpression. (this does not have a side effect when LeftHandSideExpression is an IdentifierReference)
2. Let rref be ? Evaluation of AssignmentExpression. (ConditionalExpression is evaluated here)
3. Let rval be ? GetValue(rref).
4. Perform ? PutValue(lref, rval).
5. Return rval.

**References**
- [spec of `=`](https://262.ecma-international.org/15.0/index.html#sec-assignment-operators-runtime-semantics-evaluation)
- [spec of `? :`](https://262.ecma-international.org/15.0/index.html#sec-conditional-operator-runtime-semantics-evaluation)
2025-01-08 13:34:12 +00:00
sapphi-red
a69d15f299
feat(minifier): compress new Array(2) -> [,,] (#8344)
For an integer value `n` smaller than 6, `Array(n)` can be compressed to `[,,]` (the number of `,` is `n`).
2025-01-08 12:07:46 +00:00
Boshen
6220e05f38 feat(minifier): remove empty if statment if (test) {} -> test (#8336) 2025-01-08 08:50:10 +00:00
sapphi-red
ec88c68c28
feat(minifier): compress a || (a = b) to a ||= b (#8315)
Compress `a || (a = b)` to `a ||= b` and for other logical operators that are possible to.

I didn't find other minifiers doing this, but this is safe for identifiers. [Quoting MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR_assignment#description):

> Logical OR assignment short-circuits, meaning that `x ||= y` is equivalent to `x || (x = y)`, except that the expression x is only evaluated once.

I actually checked the spec and the only difference was that `Let lRef be ? Evaluation of LeftHandSideExpression` was done twice. Evaluating an IdentifierReference is idempotent so it should be safe to do this compression.

References:

- [Spec of `&&=`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-assignment-operators:~:text=Return%20r.-,AssignmentExpression,7.%20Return%20rVal.,-AssignmentExpression)
- [Spec of `=`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#prod-AssignmentExpression:~:text=Runtime%20Semantics%3A%20Evaluation-,AssignmentExpression,6.%20Return%20rVal.,-AssignmentExpression)
- [Spec of `&&`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-binary-logical-operators:~:text=Runtime%20Semantics%3A%20Evaluation-,LogicalANDExpression,5.%20Return%20%3F%C2%A0GetValue(rRef).,-LogicalORExpression)

I think this is safe for `a.b || (a.b = foo)` as well because the number of `GetValue` and `SetValue` does not change, but I didn't include that in this PR.
2025-01-07 23:19:52 +00:00
sapphi-red
e6fe84d674
feat(minifier): compress a = a + b to a += b (#8314)
Compress `a = a + b` to `a += b` and for other binary operators that are possible to.
2025-01-07 23:19:50 +00:00
sapphi-red
9ea4e31ba3
feat(minifier): remove new from new Error/new Function/new RegExp (#8313)
Remove `new` from some cases:

- `new Error(...)` -> `Error(...)`: `new` can be removed unconditionally, [spec](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-error-constructor:~:text=Thus%20the%20function%20call%20Error(%E2%80%A6)%20is%20equivalent%20to%20the%20object%20creation%20expression%20new%20Error(%E2%80%A6)%20with%20the%20same%20arguments.)
- `new Function(...)` -> `Function(...)`: `new` can be removed unconditionally, [spec](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-function-constructor:~:text=Thus%20the%20function%20call%20Function(%E2%80%A6)%20is%20equivalent%20to%20the%20object%20creation%20expression%20new%20Function(%E2%80%A6)%20with%20the%20same%20arguments.)
- `new RegExp(nonRegexp)` -> `RegExp(nonRegexp)`: `new` can be removed if there's no argument or the first argument is not regex object, [mdn](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp#return_value), [spec](https://tc39.es/ecma262/multipage/text-processing.html#sec-regexp-constructor:~:text=when%20called%20as%20a%20function%20rather%20than%20as%20a%20constructor%2C%20returns%20either%20a%20new%20RegExp%20object%2C%20or%20the%20argument%20itself%20if%20the%20only%20argument%20is%20a%20RegExp%20object.). I made this to happen if the first argument is not an object.
2025-01-07 14:48:56 +00:00
Boshen
051fbb6909 feat(minifier): minimize x["0"] -> x[0] (#8316) 2025-01-07 13:12:35 +00:00
Boshen
a542013773 feat(minifier): minimize do{}while(true) -> do;while(true) (#8311) 2025-01-07 12:55:03 +00:00
Boshen
e3ff81ef82 feat(minifier): minimize (x = 1) === 1 -> (x = 1) == 1 (#8310) 2025-01-07 07:48:35 +00:00
camc314
5ed439bcaf feat(minifier): minify typeof in binary expressions (#8302) 2025-01-06 23:26:19 +00:00
Boshen
b8d26eab17 refactor(minifier): move optional catch param to peephole_substitute_alternate_syntax (#8282) 2025-01-06 10:47:56 +00:00
Boshen
f87da160df fix(minifier): do not fold literals in -0 != +0 (#8278) 2025-01-06 10:08:57 +00:00
camc314
e446c15619 feat(minifier): improve minimizing unary not expressions (#8261) 2025-01-06 02:21:26 +00:00
camc314
7f19211736 feat(minifier): minimize unary expression statements (#8256)
`delete` cannot be minified as it may have side effect.
2025-01-06 02:21:24 +00:00
Boshen
4d8a08d2ac feat(minifier): improve constant evaluation (#8252) 2025-01-05 12:41:57 +00:00
Boshen
2f52f333fa feat(minifier): minsize !!!foo ? bar : baz -> foo ? baz : bar (#8244) 2025-01-04 09:10:22 +00:00
Boshen
ce2b5a994b chore(minifier): disable RemoveUnusedCode (#8243)
A lot of edge cases fail in test262 and monitor-oxc.
2025-01-04 08:49:20 +00:00
Boshen
ad9a0a9c4a feat(mininifier): minimize variants of a instanceof b == true (#8241) 2025-01-04 06:04:52 +00:00
Boshen
ccdc039f54 feat(minifier): always put literals on the rhs of equal op 1==x => x==1 (#8240) 2025-01-04 04:07:54 +00:00
Boshen
bf0fbcea6e refactor(minifier): improve constant fold numbers (#8239)
Ported esbuild's implementation
2025-01-04 03:30:20 +00:00
Cameron
39353b22e9
feat(minifier): improve minimizing conditionals (#8238) 2025-01-04 08:36:54 +08:00
camc314
c90fc16bba feat(minifier): restore conditional minification and fix edge case (#8235)
This restore's the changes made in #8233, but fixing the edge cases.

If the conditional expression is not a child of an `IfStatementTest`, `WhileStatementTest`, `DoWhileStatementText` or `ExpressionStatementExpression`, we must coerce the test to a boolean.
2025-01-03 14:20:42 +00:00
Boshen
6c8ee9fdef feat(minifier): remove last redundant return statement (#8234) 2025-01-03 12:24:57 +00:00
Boshen
a698deff51 fix(minifier): fix incorrect return value for (x ? true : y) (#8233)
Need to check if the return value is used or not:

```
function foo() {
    return foo ? true : bar; // no transformed
}

foo ? true : bar; // transformed to `foo || bar;`
```
2025-01-03 11:58:28 +00:00
Boshen
51f47926ef feat(minifier): minimize foo ? foo : bar and foo ? bar : foo (#8229) 2025-01-03 10:55:59 +00:00
Boshen
6e2ec17d51 feat(minifier): statement fusion switch cases; improved minimize exit poitns (#8228) 2025-01-03 10:07:04 +00:00
Boshen
574a2428fd feat(minifier): minimize all variants of typeof x == 'undefined' (#8227) 2025-01-03 07:05:55 +00:00