Commit graph

7684 commits

Author SHA1 Message Date
Boshen
a1752a062c fix(codegen): fix incorrect minified return 1n output (#8374) 2025-01-09 05:01:41 +00:00
Boshen
cb098c7d3c fix(minifier): computed property key prototype cannot be changed (#8373) 2025-01-09 04:24:03 +00:00
Boshen
82ee77ef19 fix(minifier): do not remove shadowned undefined in return statement (#8371)
closes #8370
2025-01-09 04:03:18 +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
overlookmotel
aa5e65ff3c refactor(transformer/private-methods): simplify finding parent statement of class expression (#8364)
Pure refactor. Simplify code a little.
2025-01-09 03:17:41 +00:00
overlookmotel
c786fd1add refactor(transformer/private-methods): TODO comments (#8363) 2025-01-09 03:17:40 +00:00
overlookmotel
ac72adbade fix(transformer/private-methods): fix panic if instance private accessor in class (#8362)
Fix panic if a private accessor is present in class which also has property. e.g.:

```js
let C = class C {
  prop = 1;
  accessor #private = 2;
};
```

Panic occurred due to trying to unwrap `brand` property, but it's `None` because no private instance methods in the class.
2025-01-09 03:17:39 +00:00
overlookmotel
f1f129b09d fix(transformer/private-methods): create brand binding var in hoist scope (#8361)
Brand binding is a `var`, so is bound in hoist scope.
2025-01-09 03:17:38 +00:00
overlookmotel
ab6142503f fix(transformer/private-methods): no temp var for class when unused private methods (#8360)
No temp var is required for class if it contains static private method which is not referenced within class. e.g.:

```js
let C = class {
  static #method() {}
};
```

->

```js
let C = class {};
function _method() {}
```
2025-01-09 03:17:37 +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
翠 / green
3ba0b5316d
chore(tasks/coverage): fix test262 cases to run on windows (#8358)
`skip_test_path` was not working on windows because the path included
`\\`.
2025-01-09 10:48:37 +08:00
Alexander S.
b6c1546d07
test(linter): use plugin name instead of category for finding rule (#8353)
needed for #8329

I wanted to use `proc_macro::Span::SourceFile` to auto detect the plugin
name. But this feature is unstable :/
2025-01-09 10:48:11 +08:00
overlookmotel
78d7c97357 fix(transformer/typescript): create Reference for Infinity (#8354)
Create a `Reference` when generating new `IdentifierReference` for `Infinity`.
2025-01-09 02:26:33 +00:00
overlookmotel
6790d1d219 refactor(transformer/class-properties): simplify determining if class is declaration (#8357)
Small improvement. Use `Class::type` field to determine if class is declaration/expression. Not sure why it did it in such a complicated way previously!
2025-01-09 02:20:50 +00:00
Dunqing
e4d66e4636 fix(transformer/arrow-functions): store super_methods on a Stack to fix nested async methods (#8331)
In the following case, async methods can be nested in another async method. The implementation is changing to store `super_methods` on a stack, and then we can store super method information in the correct `super_methods` map.

```js
const outer = {
  value: 0,
  async method() {
    () => super.value;

    const inner = {
      value: 0,
      async method() {
        () => super.value;
      }
    };

    () => super.value;
  }
};
```
2025-01-08 21:00:45 +00:00
overlookmotel
e0a09ab023 docs(data_structures): improve docs for stack types (#8356)
Improve docs for `Stack`, `NonEmptyStack` and `SparseStack`.
2025-01-08 20:49:28 +00:00
Dunqing
fb389f724a refactor(transformer/arrow-function): create a new ident instead of clone (#8338)
There seems no reason to use a cloned identifier.
2025-01-08 19:43:53 +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
Boshen
5a648bc3cd fix(codegen): fix white space issue with do statements (#8348) 2025-01-08 13:28:10 +00:00
sapphi-red
3149fe0aa4
test(minifier): add anonymous function test case for logical expression to logical assignment compression (#8347)
Added a test case that tells having an anonymous function on the right hand side for "logical expression to logical assignment" compression is fine.
2025-01-08 13:22:50 +00:00
Boshen
9a5c66ac6a refactor(minifier): clean up (#8346) 2025-01-08 13:04:10 +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
sapphi-red
819c47536c
feat(minifier): compress new Array(7n) -> [7n] (#8343)
`Array(7n)` evaluates to `[7n]`
2025-01-08 12:07:45 +00:00
Boshen
e085d66c78 feat(minifier): remove empty IIFE (#8340) 2025-01-08 09:34:10 +00:00
Boshen
2c2e483d35 feat(minifier): fold object spread ({ ...null }) -> ({}) (#8339) 2025-01-08 09:11:28 +00:00
Yuji Sugiura
8e3eed7562
refactor(prettier): Update tasks/prettier to correctly handle snapshots (#8337)
I refactored the code in `tasks/prettier_conformance` primarily to make
the output more readable when using `--filter`.

But I also discovered that our previous implementation did not correctly
handle Prettier's behavior of adding a blank line at the EOF.

In addition, I resolved a problem where test specs that used patterns
like `runFormatTest(_, parsers)` were unable to locate the correct
snapshot output.

As a result, compatibility has also improved slightly. 😉

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-01-08 17:10:30 +08:00
Boshen
6220e05f38 feat(minifier): remove empty if statment if (test) {} -> test (#8336) 2025-01-08 08:50:10 +00:00
Boshen
a76dfae8e3 feat(minifier): remove label statement with empty body (#8333) 2025-01-08 07:38:39 +00:00
Boshen
e88a6bdd94 feat(minifier): minimize !0 + null !== 1 -> !0 + null != 1 (#8332) 2025-01-08 04:36:42 +00:00
Boshen
98f2b1ccd5 refactor(minifier): clean up peephole_substitute_alternate_syntax (#8327) 2025-01-08 03:44:19 +00:00
overlookmotel
ce6c4457c2 refactor(transformer/arrow-functions): add TODO comments (#8328)
Add some TODO comments for this transform.
2025-01-08 01:52:46 +00:00
overlookmotel
73d0025a75 refactor(transformer/arrow-functions): reduce repeated code (#8323)
Follow-on after #8024. Pure refactor. Make `self.visit_statement(stmt)` only get called in one place, rather than 2.
2025-01-08 01:52:45 +00:00
overlookmotel
62e3f7e525 perf(transformer/arrow-functions): reduce size of inlined visitor (#8322)
Follow-on after #8024.

Move code out of `visit_expression`.

`visit_expression` is marked `#[inline]`, which is a good idea because it's a hot path and most of the time the `Expression` is not `super()` and the function does nothing.

But this means we want `visit_expression` to be as small as possible to reduce the cost of inlining it. So only retain the fast path for "Is it `super()`? No it's not, so just continue traversal." and move everything else out into a separate function.
2025-01-08 01:52:45 +00:00
overlookmotel
3dd08e9786 refactor(transformer/arrow-functions): do not inline non-trivial visitor method (#8321)
Follow-on after #8024. Don't inline `visit_statements` visitor method. This visitor is not so small, and always executes unconditionally. The compiler can decide whether to inline it or not. Where `#[inline]` is really valuable is:

1. Where the function is tiny so the cost of moving it into the caller is small. and
2. The function gets called a lot e.g. `visit_expression`, `visit_statement`, `visit_identifier_reference`.
3. Most commonly the function checks something and exits "nothing to do here". e.g. `visit_expression` where you only need to transform a particular type of `Expression`.
2025-01-08 01:52:44 +00:00
overlookmotel
ea9cefb5c3 refactor(transformer/arrow-functions): reorder visitor methods (#8320)
Follow-on after #8024. Pure refactor.

Re-order the methods of the `ConstructorBodyThisAfterSuperInserter` visitor. Intent is that the call path flows from top to bottom, so the "story" makes sense when you read the code starting from the top.
2025-01-08 01:52:43 +00:00
overlookmotel
37199a4cd6 refactor(transformer/arrow-functions): rename lifetime (#8319)
Follow-on after #8024. Pure refactor. Rename lifetime to `'v` ("v" for visitor).
2025-01-08 01:52:43 +00:00
1zumii
45eb35434d
chore(linter): add vscode debugger launch config (#8271)
refer to this
[reply](https://discord.com/channels/1079625926024900739/1080101477672046712/1265758176754794598),
add CodeLLDB launch config for debugging oxlint.

usage for real dev like:
```json
{
  "type": "lldb",
  "request": "launch",
  "name": "Debug Oxlint",
  "cargo": {
    "env": {
      "RUSTFLAGS": "-g"
    },
    "args": ["build", "--bin=oxlint", "--package=oxlint"],
    "filter": {
      "name": "oxlint",
      "kind": "bin"
    }
  },
  "args": ["--ignore-pattern=./ignore/**"],
  "cwd": "/home/xxx/temp/oxlint-7102"
}
```

besides, i tired add config in `args` to override `Cargo.toml`
`[profile.dev] debug = false`, but cargo in CodeLLDB throw error.
```json
"args": [
  "build", "--bin=oxlint", "--package=oxlint", 
  "--config profile.dev.debug=true"
]
```

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-01-08 09:51:47 +08:00
overlookmotel
aebe0ea46d perf(transformer/arrow-functions): use NonEmptyStack instead of Stack (#8318)
Follow-on after #8024.

Use `NonEmptyStack` instead of `Stack`.

`NonEmptyStack` is cheaper as `last` / `last_mut` always returns `T`, rather than `Option<T>`, so there's no need to branch on whether it's `Some` or not.

The only downside of `NonEmptyStack` is that it always contains 1 dummy entry at the start (so that it's never empty). This means that it always allocates, unlike `Stack` which doesn't allocate unless you push something into it.

So `NonEmptyStack` is always the better choice unless either:

1. The stack will likely never have anything pushed to it. or
2. The type the stack holds is large or expensive to construct, so there's a high cost in having to create an initial dummy value.

In this case:

* The type is `bool` - very small and cheap to construct.
* The stack will be pushed to when entering a function. Very few JS files contain no functions at all, so the stack will always need to allocate, even if we used `Stack`.

So this case doesn't satisfy either of the circumstances in which `Stack` is the better choice.
2025-01-08 01:32:56 +00:00
overlookmotel
57e9dcf046 refactor(transformer/arrow-functions): shorten AstBuilder call (#8317)
Follow-on after #8024. Pure refactor, just shorten code.
2025-01-08 01:32:55 +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
fc662b7896 refactor(minifier): handle big int values later (#8324) 2025-01-07 14:25:22 +00:00
Dunqing
775a289a55 fix(transformer/arrow-functions): _this = this should be inserted after super call expression (#8024)
related: #7792

This PR doesn't contain fixing the async arrow function in `super()`, which is difficult for our architecture. I just found that `esbuild`'s implementation is quite simpler! https://esbuild.github.io/try/#dAAwLjI0LjAALS10YXJnZXQ9ZXMyMDE2AGNsYXNzIEMgZXh0ZW5kcyBTIHsKICBjb25zdHJ1Y3RvcigpIHsKICAgIHN1cGVyKGFzeW5jICgpID0+IHRoaXMpOwogICAgYXN5bmMoKSA9PiB7fQogIH0KfQ
2025-01-07 13:53:44 +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