diff --git a/.github/generated/ast_changes_watch_list.yml b/.github/generated/ast_changes_watch_list.yml index b4c071e8b4c27..3392d57e2f03a 100644 --- a/.github/generated/ast_changes_watch_list.yml +++ b/.github/generated/ast_changes_watch_list.yml @@ -3,6 +3,7 @@ src: - '.github/generated/ast_changes_watch_list.yml' + - '.github/workflows/ci.yml' - 'apps/oxlint/src-js/generated/constants.ts' - 'apps/oxlint/src-js/generated/deserialize.js' - 'apps/oxlint/src-js/generated/keys.ts' @@ -11,66 +12,27 @@ src: - 'apps/oxlint/src-js/generated/visitor.d.ts' - 'apps/oxlint/src-js/generated/walk.js' - 'apps/oxlint/src/generated/raw_transfer_constants.rs' - - 'crates/oxc_allocator/src/generated/assert_layouts.rs' - - 'crates/oxc_allocator/src/generated/fixed_size_constants.rs' - - 'crates/oxc_allocator/src/pool/fixed_size.rs' - - 'crates/oxc_ast/src/ast/comment.rs' - - 'crates/oxc_ast/src/ast/js.rs' - - 'crates/oxc_ast/src/ast/jsx.rs' - - 'crates/oxc_ast/src/ast/literal.rs' - - 'crates/oxc_ast/src/ast/ts.rs' - - 'crates/oxc_ast/src/generated/assert_layouts.rs' - - 'crates/oxc_ast/src/generated/ast_builder.rs' - - 'crates/oxc_ast/src/generated/ast_kind.rs' - - 'crates/oxc_ast/src/generated/derive_clone_in.rs' - - 'crates/oxc_ast/src/generated/derive_content_eq.rs' - - 'crates/oxc_ast/src/generated/derive_dummy.rs' - - 'crates/oxc_ast/src/generated/derive_estree.rs' - - 'crates/oxc_ast/src/generated/derive_get_address.rs' - - 'crates/oxc_ast/src/generated/derive_get_span.rs' - - 'crates/oxc_ast/src/generated/derive_get_span_mut.rs' - - 'crates/oxc_ast/src/generated/derive_take_in.rs' - - 'crates/oxc_ast/src/generated/get_id.rs' - - 'crates/oxc_ast/src/serialize/basic.rs' - - 'crates/oxc_ast/src/serialize/js.rs' - - 'crates/oxc_ast/src/serialize/jsx.rs' - - 'crates/oxc_ast/src/serialize/literal.rs' - - 'crates/oxc_ast/src/serialize/mod.rs' - - 'crates/oxc_ast/src/serialize/ts.rs' + - 'crates/oxc_allocator/src/**' + - 'crates/oxc_ast/src/**' - 'crates/oxc_ast_macros/src/generated/derived_traits.rs' - 'crates/oxc_ast_macros/src/generated/structs.rs' - 'crates/oxc_ast_macros/src/lib.rs' - - 'crates/oxc_ast_visit/src/generated/utf8_to_utf16_converter.rs' - - 'crates/oxc_ast_visit/src/generated/visit.rs' - - 'crates/oxc_ast_visit/src/generated/visit_mut.rs' + - 'crates/oxc_ast_visit/src/**' + - 'crates/oxc_codegen/src/**' + - 'crates/oxc_data_structures/src/**' - 'crates/oxc_formatter/src/ast_nodes/generated/ast_nodes.rs' - 'crates/oxc_formatter/src/ast_nodes/generated/format.rs' - 'crates/oxc_linter/src/generated/assert_layouts.rs' - 'crates/oxc_linter/src/lib.rs' + - 'crates/oxc_minifier/src/**' + - 'crates/oxc_parser/src/**' - 'crates/oxc_regular_expression/src/ast.rs' - 'crates/oxc_regular_expression/src/generated/assert_layouts.rs' - 'crates/oxc_regular_expression/src/generated/derive_clone_in.rs' - 'crates/oxc_regular_expression/src/generated/derive_content_eq.rs' - 'crates/oxc_regular_expression/src/generated/derive_get_address.rs' - - 'crates/oxc_span/src/generated/assert_layouts.rs' - - 'crates/oxc_span/src/generated/derive_dummy.rs' - - 'crates/oxc_span/src/generated/derive_estree.rs' - - 'crates/oxc_span/src/source_type.rs' - - 'crates/oxc_span/src/span.rs' - - 'crates/oxc_syntax/src/comment_node.rs' - - 'crates/oxc_syntax/src/generated/assert_layouts.rs' - - 'crates/oxc_syntax/src/generated/derive_clone_in.rs' - - 'crates/oxc_syntax/src/generated/derive_content_eq.rs' - - 'crates/oxc_syntax/src/generated/derive_dummy.rs' - - 'crates/oxc_syntax/src/generated/derive_estree.rs' - - 'crates/oxc_syntax/src/lib.rs' - - 'crates/oxc_syntax/src/module_record.rs' - - 'crates/oxc_syntax/src/number.rs' - - 'crates/oxc_syntax/src/operator.rs' - - 'crates/oxc_syntax/src/reference.rs' - - 'crates/oxc_syntax/src/scope.rs' - - 'crates/oxc_syntax/src/serialize.rs' - - 'crates/oxc_syntax/src/symbol.rs' + - 'crates/oxc_span/src/**' + - 'crates/oxc_syntax/src/**' - 'crates/oxc_traverse/src/generated/scopes_collector.rs' - 'napi/parser/generated/constants.js' - 'napi/parser/generated/deserialize/js.js' diff --git a/.github/scripts/clone-parallel.sh b/.github/scripts/clone-parallel.sh index b42f2342fea70..6bf382a3ea9df 100755 --- a/.github/scripts/clone-parallel.sh +++ b/.github/scripts/clone-parallel.sh @@ -7,12 +7,12 @@ set -euo pipefail # Submodule commit SHAs - updated automatically by .github/workflows/update_submodules.yml -TEST262_SHA="d2940bdbb0e28fd002ec31b89f8182bbf63da092" -BABEL_SHA="4cc3d8888f3e3ac30d38ccfa4557baa7a4a52714" -TYPESCRIPT_SHA="8ea03f88d039759018673f229addb87f579f326c" -PRETTIER_SHA="011791fd6c8856fb92fafec95930e10383ac267b" -ACORN_TEST262_SHA="994d763f5327046485d985d5596186d3c82f507a" -NODE_COMPAT_TABLE_SHA="17ac85ca289a2d6fea14a991284e631468efe14c" +TEST262_SHA="fd594a077a0a018440f241fdd421a5862f1153f5" +BABEL_SHA="777ded79cd97e872ff607e1a4897036f30939188" +TYPESCRIPT_SHA="48244d89f8ccc803fef4a2f0930100de1c77668d" +PRETTIER_SHA="9e9f65e7b9277e7af12362628b42e003393731e6" +ACORN_TEST262_SHA="9cce4c914fac21da5827f80f269572619302a21c" +NODE_COMPAT_TABLE_SHA="6822522983aec284ad8add5a867847bbe2059a2e" # Default values for which submodules to clone TEST262=${1:-true} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c35843c66612..262b499cbbed3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -385,20 +385,12 @@ jobs: if: steps.filter.outputs.src == 'true' with: components: rustfmt - tools: dprint cache-key: ast_changes save-cache: ${{ github.ref_name == 'main' }} - uses: oxc-project/setup-node@fdbf0dfd334c4e6d56ceeb77d91c76339c2a0885 # v1.0.4 if: steps.filter.outputs.src == 'true' - - name: Restore dprint plugin cache - if: steps.filter.outputs.src == 'true' - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - key: dprint-${{ hashFiles('dprint.json') }} - path: ~/.cache/dprint - - name: Check AST Changes if: steps.filter.outputs.src == 'true' run: | diff --git a/.github/workflows/prepare_release_crates.yml b/.github/workflows/prepare_release_crates.yml index 5cbc4cb84af2e..b07d045676893 100644 --- a/.github/workflows/prepare_release_crates.yml +++ b/.github/workflows/prepare_release_crates.yml @@ -23,7 +23,7 @@ jobs: cache-key: warm tools: cargo-release-oxc - run: cargo ck - - run: cargo release-oxc publish --release crates --dry-run + - run: cargo release-oxc publish --release crates --dry-run # zizmor: ignore[use-trusted-publishing] prepare: name: Prepare Release diff --git a/.github/workflows/release_apps.yml b/.github/workflows/release_apps.yml index f8197e59cb7ed..850f4ba497484 100644 --- a/.github/workflows/release_apps.yml +++ b/.github/workflows/release_apps.yml @@ -403,16 +403,19 @@ jobs: { echo "## Table of Contents" echo "" - echo "- [Oxlint v${{ needs.check.outputs.oxlint_version }}](#oxlint-v${{ needs.check.outputs.oxlint_version }})" - echo "- [Oxfmt v${{ needs.check.outputs.oxfmt_version }}](#oxfmt-v${{ needs.check.outputs.oxfmt_version }})" + echo "- [Oxlint v${NEEDS_CHECK_OUTPUTS_OXLINT_VERSION}](#oxlint-v${NEEDS_CHECK_OUTPUTS_OXLINT_VERSION})" + echo "- [Oxfmt v${NEEDS_CHECK_OUTPUTS_OXFMT_VERSION}](#oxfmt-v${NEEDS_CHECK_OUTPUTS_OXFMT_VERSION})" echo "" - echo "## Oxlint v${{ needs.check.outputs.oxlint_version }}" + echo "## Oxlint v${NEEDS_CHECK_OUTPUTS_OXLINT_VERSION}" cat ./target/OXLINT_CHANGELOG echo "" echo "" - echo "## Oxfmt v${{ needs.check.outputs.oxfmt_version }}" + echo "## Oxfmt v${NEEDS_CHECK_OUTPUTS_OXFMT_VERSION}" cat ./target/OXFMT_CHANGELOG } > ./target/RELEASE_BODY.md + env: + NEEDS_CHECK_OUTPUTS_OXLINT_VERSION: ${{ needs.check.outputs.oxlint_version }} + NEEDS_CHECK_OUTPUTS_OXFMT_VERSION: ${{ needs.check.outputs.oxfmt_version }} - name: tag oxlint and oxfmt env: diff --git a/Cargo.lock b/Cargo.lock index ebb746d3144b4..1abafdd6a5066 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -335,15 +335,6 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136d3e02915a2cea4d74caa8681e2d44b1c3254bdbf17d11d41d587ff858832c" -[[package]] -name = "convert_case" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "convert_case" version = "0.9.0" @@ -700,7 +691,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1448,12 +1439,13 @@ dependencies = [ [[package]] name = "napi" -version = "3.5.0" +version = "3.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d00c1a7ffcf62e0889630f122f8920383f5a9ce4b54377b05c2833fb6123857" +checksum = "4e917a98ac74187a5d486604a269ed69cd7901dd4824453d5573fb051f69b1b3" dependencies = [ "bitflags 2.10.0", "ctor", + "futures", "napi-build", "napi-sys", "nohash-hasher", @@ -1471,11 +1463,11 @@ checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1" [[package]] name = "napi-derive" -version = "3.3.0" +version = "3.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78665d6bdf10e9a4e6b38123efb0f66962e6197c1aea2f07cff3f159a374696d" +checksum = "a258a6521951715e00568b258b8fb7a44c6087f588c371dc6b84a413f2728fdb" dependencies = [ - "convert_case 0.8.0", + "convert_case", "ctor", "napi-derive-backend", "proc-macro2", @@ -1485,11 +1477,11 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "3.0.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d55d01423e7264de3acc13b258fa48ca7cf38a4d25db848908ec3c1304a85a" +checksum = "77c36636292fe04366a1eec028adc25bc72f4fd7cce35bdcc310499ef74fb7de" dependencies = [ - "convert_case 0.8.0", + "convert_case", "proc-macro2", "quote", "semver", @@ -1498,9 +1490,9 @@ dependencies = [ [[package]] name = "napi-sys" -version = "3.1.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f200fd782433de18d46d496223be780837b2f3772e5816f4425e0520bff26c2" +checksum = "50ef9c1086f16aea2417c3788dbefed7591c3bccd800b827f4dfb271adff1149" dependencies = [ "libloading", ] @@ -1544,7 +1536,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1631,31 +1623,31 @@ checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" [[package]] name = "oxc" -version = "0.96.0" +version = "0.97.0" dependencies = [ - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_ast_visit 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", "oxc_cfg", - "oxc_codegen 0.96.0", - "oxc_diagnostics 0.96.0", + "oxc_codegen", + "oxc_diagnostics", "oxc_isolated_declarations", - "oxc_mangler 0.96.0", - "oxc_minifier 0.96.0", - "oxc_parser 0.96.0", - "oxc_regular_expression 0.96.0", - "oxc_semantic 0.96.0", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", + "oxc_mangler", + "oxc_minifier", + "oxc_parser", + "oxc_regular_expression", + "oxc_semantic", + "oxc_span", + "oxc_syntax", "oxc_transformer", "oxc_transformer_plugins", ] [[package]] name = "oxc-browserslist" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7612127f5fa1ef0f3e98905ddfc10a7fa1df98944b75d774bb3cb4b530663616" +checksum = "f978be538ca5e2a64326d24b7991dc658cc8495132833ae387212ab3b8abd70a" dependencies = [ "bincode 2.0.1", "flate2", @@ -1708,79 +1700,37 @@ dependencies = [ [[package]] name = "oxc_allocator" -version = "0.96.0" +version = "0.97.0" dependencies = [ "allocator-api2", "bumpalo", "hashbrown 0.16.0", - "oxc_ast_macros 0.96.0", - "oxc_data_structures 0.96.0", - "oxc_estree 0.96.0", + "oxc_ast_macros", + "oxc_data_structures", + "oxc_estree", "rustc-hash", "serde", "serde_json", ] -[[package]] -name = "oxc_allocator" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71ef2dba21be1ce515378b2b7143eaa2a912f9e6ffe162ae20639d56f53d60e3" -dependencies = [ - "allocator-api2", - "bumpalo", - "hashbrown 0.16.0", - "oxc_data_structures 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-hash", -] - [[package]] name = "oxc_ast" -version = "0.96.0" -dependencies = [ - "bitflags 2.10.0", - "oxc_allocator 0.96.0", - "oxc_ast_macros 0.96.0", - "oxc_data_structures 0.96.0", - "oxc_diagnostics 0.96.0", - "oxc_estree 0.96.0", - "oxc_regular_expression 0.96.0", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", -] - -[[package]] -name = "oxc_ast" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad9195311a1961bb6ef1de0ce6a52147bccea50b5a40423b7b44e8448ed4fc" +version = "0.97.0" dependencies = [ "bitflags 2.10.0", - "oxc_allocator 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast_macros 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_data_structures 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_diagnostics 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_estree 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_regular_expression 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_span 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_syntax 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "oxc_ast_macros" -version = "0.96.0" -dependencies = [ - "phf", - "proc-macro2", - "quote", - "syn", + "oxc_allocator", + "oxc_ast_macros", + "oxc_data_structures", + "oxc_diagnostics", + "oxc_estree", + "oxc_regular_expression", + "oxc_span", + "oxc_syntax", ] [[package]] name = "oxc_ast_macros" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f03da6fac191c0817a32ae1a7dde27fd27d98732c61fcaeb55a99a4d543ba49" +version = "0.97.0" dependencies = [ "phf", "proc-macro2", @@ -1794,21 +1744,21 @@ version = "0.0.0" dependencies = [ "bitflags 2.10.0", "bpaf", - "convert_case 0.9.0", + "convert_case", "cow-utils", "indexmap", "itertools", "lazy-regex", - "oxc_allocator 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast_visit 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_codegen 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_data_structures 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_codegen", + "oxc_data_structures", "oxc_index", - "oxc_minifier 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_parser 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_span 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_syntax 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", + "oxc_minifier", + "oxc_parser", + "oxc_span", + "oxc_syntax", "phf", "phf_codegen", "prettyplease", @@ -1819,28 +1769,17 @@ dependencies = [ "serde", "serde_json", "syn", + "toml", ] [[package]] name = "oxc_ast_visit" -version = "0.96.0" -dependencies = [ - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", -] - -[[package]] -name = "oxc_ast_visit" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42fcdb162d247a0e9c1aa985b388f000eba19fb1ee1845b2ec0ddc595f95131" +version = "0.97.0" dependencies = [ - "oxc_allocator 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_span 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_syntax 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", + "oxc_allocator", + "oxc_ast", + "oxc_span", + "oxc_syntax", ] [[package]] @@ -1849,18 +1788,18 @@ version = "0.0.0" dependencies = [ "cow-utils", "criterion2", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_ast_visit 0.96.0", - "oxc_codegen 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_codegen", "oxc_formatter", "oxc_isolated_declarations", "oxc_linter", - "oxc_mangler 0.96.0", - "oxc_minifier 0.96.0", - "oxc_parser 0.96.0", - "oxc_semantic 0.96.0", - "oxc_span 0.96.0", + "oxc_mangler", + "oxc_minifier", + "oxc_parser", + "oxc_semantic", + "oxc_span", "oxc_tasks_common", "oxc_transformer", "rustc-hash", @@ -1870,79 +1809,45 @@ dependencies = [ [[package]] name = "oxc_cfg" -version = "0.96.0" +version = "0.97.0" dependencies = [ "bitflags 2.10.0", "itertools", "oxc_index", - "oxc_syntax 0.96.0", + "oxc_syntax", "petgraph", "rustc-hash", ] [[package]] name = "oxc_codegen" -version = "0.96.0" +version = "0.97.0" dependencies = [ "bitflags 2.10.0", "cow-utils", "dragonbox_ecma", "insta", "itoa", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_data_structures 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_data_structures", "oxc_index", - "oxc_parser 0.96.0", - "oxc_semantic 0.96.0", + "oxc_parser", + "oxc_semantic", "oxc_sourcemap", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", + "oxc_span", + "oxc_syntax", "pico-args", "rustc-hash", ] -[[package]] -name = "oxc_codegen" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c9a53ca79c87846e4f2b4f3df514b0b2bf910a1ba76a9e8cb742570b8b47ce" -dependencies = [ - "bitflags 2.10.0", - "cow-utils", - "dragonbox_ecma", - "itoa", - "oxc_allocator 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_data_structures 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_index", - "oxc_semantic 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_sourcemap", - "oxc_span 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_syntax 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-hash", -] - -[[package]] -name = "oxc_compat" -version = "0.96.0" -dependencies = [ - "cow-utils", - "oxc-browserslist", - "oxc_syntax 0.96.0", - "rustc-hash", - "serde", -] - [[package]] name = "oxc_compat" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271a875e3e9e1f6d259c99e0557fcc83d96b025ddfca40e215d6bbb58bae9d45" +version = "0.97.0" dependencies = [ "cow-utils", "oxc-browserslist", - "oxc_syntax 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", + "oxc_syntax", "rustc-hash", "serde", ] @@ -1991,31 +1896,14 @@ dependencies = [ [[package]] name = "oxc_data_structures" -version = "0.96.0" +version = "0.97.0" dependencies = [ "ropey", ] -[[package]] -name = "oxc_data_structures" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f5171d7b8bc907a1b29e557d14f8478509a2154272d56db9ee8aed6bfe8dec" - -[[package]] -name = "oxc_diagnostics" -version = "0.96.0" -dependencies = [ - "cow-utils", - "oxc-miette", - "percent-encoding", -] - [[package]] name = "oxc_diagnostics" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ef2bf6a713fd27bc65812d695bdfde3f8fcef735f00b861258518346642721b" +version = "0.97.0" dependencies = [ "cow-utils", "oxc-miette", @@ -2024,50 +1912,24 @@ dependencies = [ [[package]] name = "oxc_ecmascript" -version = "0.96.0" -dependencies = [ - "cow-utils", - "num-bigint", - "num-traits", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", -] - -[[package]] -name = "oxc_ecmascript" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f908100cb2759dd2f42ca33d95ea158b8d78e2591b577757729fc9a4a4c63bc3" +version = "0.97.0" dependencies = [ "cow-utils", "num-bigint", "num-traits", - "oxc_allocator 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_span 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_syntax 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", + "oxc_allocator", + "oxc_ast", + "oxc_span", + "oxc_syntax", ] [[package]] name = "oxc_estree" -version = "0.96.0" -dependencies = [ - "dragonbox_ecma", - "itoa", - "oxc_data_structures 0.96.0", -] - -[[package]] -name = "oxc_estree" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5644d3399116ff3f0cfb81f9a790c4b8173b504ed52274ecc757b57f30098ad1" +version = "0.97.0" dependencies = [ "dragonbox_ecma", "itoa", - "oxc_data_structures 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", + "oxc_data_structures", ] [[package]] @@ -2078,13 +1940,13 @@ dependencies = [ "insta", "json-strip-comments", "oxc-schemars", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_data_structures 0.96.0", - "oxc_parser 0.96.0", - "oxc_semantic 0.96.0", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_data_structures", + "oxc_parser", + "oxc_semantic", + "oxc_span", + "oxc_syntax", "phf", "pico-args", "project-root", @@ -2106,19 +1968,19 @@ dependencies = [ [[package]] name = "oxc_isolated_declarations" -version = "0.96.0" +version = "0.97.0" dependencies = [ "bitflags 2.10.0", "insta", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_ast_visit 0.96.0", - "oxc_codegen 0.96.0", - "oxc_diagnostics 0.96.0", - "oxc_ecmascript 0.96.0", - "oxc_parser 0.96.0", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_codegen", + "oxc_diagnostics", + "oxc_ecmascript", + "oxc_parser", + "oxc_span", + "oxc_syntax", "rustc-hash", ] @@ -2131,12 +1993,12 @@ dependencies = [ "ignore", "insta", "log", - "oxc_allocator 0.96.0", - "oxc_data_structures 0.96.0", - "oxc_diagnostics 0.96.0", + "oxc_allocator", + "oxc_data_structures", + "oxc_diagnostics", "oxc_formatter", "oxc_linter", - "oxc_parser 0.96.0", + "oxc_parser", "papaya", "rustc-hash", "serde", @@ -2151,7 +2013,7 @@ version = "1.28.0" dependencies = [ "bitflags 2.10.0", "constcat", - "convert_case 0.9.0", + "convert_case", "cow-utils", "fast-glob", "icu_segmenter", @@ -2166,23 +2028,23 @@ dependencies = [ "markdown", "memchr", "oxc-schemars", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_ast_macros 0.96.0", - "oxc_ast_visit 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_ast_macros", + "oxc_ast_visit", "oxc_cfg", - "oxc_codegen 0.96.0", - "oxc_data_structures 0.96.0", - "oxc_diagnostics 0.96.0", - "oxc_ecmascript 0.96.0", + "oxc_codegen", + "oxc_data_structures", + "oxc_diagnostics", + "oxc_ecmascript", "oxc_index", "oxc_macros", - "oxc_parser 0.96.0", - "oxc_regular_expression 0.96.0", + "oxc_parser", + "oxc_regular_expression", "oxc_resolver", - "oxc_semantic 0.96.0", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", + "oxc_semantic", + "oxc_span", + "oxc_syntax", "papaya", "phf", "project-root", @@ -2200,7 +2062,7 @@ dependencies = [ name = "oxc_linter_codegen" version = "0.0.0" dependencies = [ - "convert_case 0.9.0", + "convert_case", "project-root", "rustc-hash", "syn", @@ -2210,7 +2072,7 @@ dependencies = [ name = "oxc_macros" version = "0.0.0" dependencies = [ - "convert_case 0.9.0", + "convert_case", "itertools", "proc-macro2", "quote", @@ -2219,106 +2081,64 @@ dependencies = [ [[package]] name = "oxc_mangler" -version = "0.96.0" -dependencies = [ - "itertools", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_data_structures 0.96.0", - "oxc_index", - "oxc_parser 0.96.0", - "oxc_semantic 0.96.0", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", - "rustc-hash", -] - -[[package]] -name = "oxc_mangler" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3695e3b0694093d24f0f7c8b77dc36b1a4b55020bf52166271010c8095bba5" +version = "0.97.0" dependencies = [ "itertools", - "oxc_allocator 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_data_structures 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", + "oxc_allocator", + "oxc_ast", + "oxc_data_structures", "oxc_index", - "oxc_semantic 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_span 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_syntax 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", + "oxc_parser", + "oxc_semantic", + "oxc_span", + "oxc_syntax", "rustc-hash", ] [[package]] name = "oxc_minifier" -version = "0.96.0" +version = "0.97.0" dependencies = [ "cow-utils", "insta", "javascript-globals", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_ast_visit 0.96.0", - "oxc_codegen 0.96.0", - "oxc_compat 0.96.0", - "oxc_data_structures 0.96.0", - "oxc_ecmascript 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_codegen", + "oxc_compat", + "oxc_data_structures", + "oxc_ecmascript", "oxc_index", - "oxc_mangler 0.96.0", - "oxc_parser 0.96.0", - "oxc_regular_expression 0.96.0", - "oxc_semantic 0.96.0", + "oxc_mangler", + "oxc_parser", + "oxc_regular_expression", + "oxc_semantic", "oxc_sourcemap", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", - "oxc_traverse 0.96.0", + "oxc_span", + "oxc_syntax", + "oxc_traverse", "pico-args", "rustc-hash", ] -[[package]] -name = "oxc_minifier" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "869d4f7493209202a6d2824a1d18a3bced22c7e6a32b9b0f41d718dc090fa4dc" -dependencies = [ - "cow-utils", - "oxc_allocator 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast_visit 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_codegen 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_compat 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_data_structures 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ecmascript 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_index", - "oxc_mangler 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_parser 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_regular_expression 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_semantic 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_span 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_syntax 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_traverse 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-hash", -] - [[package]] name = "oxc_minify_napi" -version = "0.96.0" +version = "0.97.0" dependencies = [ "mimalloc-safe", "napi", "napi-build", "napi-derive", - "oxc_allocator 0.96.0", - "oxc_codegen 0.96.0", - "oxc_compat 0.96.0", - "oxc_diagnostics 0.96.0", - "oxc_minifier 0.96.0", + "oxc_allocator", + "oxc_codegen", + "oxc_compat", + "oxc_diagnostics", + "oxc_minifier", "oxc_napi", - "oxc_parser 0.96.0", + "oxc_parser", "oxc_sourcemap", - "oxc_span 0.96.0", + "oxc_span", ] [[package]] @@ -2328,12 +2148,12 @@ dependencies = [ "cow-utils", "flate2", "humansize", - "oxc_allocator 0.96.0", - "oxc_codegen 0.96.0", - "oxc_minifier 0.96.0", - "oxc_parser 0.96.0", - "oxc_semantic 0.96.0", - "oxc_span 0.96.0", + "oxc_allocator", + "oxc_codegen", + "oxc_minifier", + "oxc_parser", + "oxc_semantic", + "oxc_span", "oxc_tasks_common", "oxc_transformer_plugins", "pico-args", @@ -2343,75 +2163,52 @@ dependencies = [ [[package]] name = "oxc_napi" -version = "0.96.0" +version = "0.97.0" dependencies = [ "napi", "napi-build", "napi-derive", - "oxc_ast 0.96.0", - "oxc_ast_visit 0.96.0", - "oxc_diagnostics 0.96.0", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", + "oxc_ast", + "oxc_ast_visit", + "oxc_diagnostics", + "oxc_span", + "oxc_syntax", ] [[package]] name = "oxc_parser" -version = "0.96.0" +version = "0.97.0" dependencies = [ "bitflags 2.10.0", "cow-utils", "memchr", "num-bigint", "num-traits", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_ast_visit 0.96.0", - "oxc_data_structures 0.96.0", - "oxc_diagnostics 0.96.0", - "oxc_ecmascript 0.96.0", - "oxc_regular_expression 0.96.0", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_data_structures", + "oxc_diagnostics", + "oxc_ecmascript", + "oxc_regular_expression", + "oxc_span", + "oxc_syntax", "pico-args", "rustc-hash", "seq-macro", ] -[[package]] -name = "oxc_parser" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e080498b7a4456a63111f9c65b4dd1b98147955347854b809b6ad4cc5d6a0c0a" -dependencies = [ - "bitflags 2.10.0", - "cow-utils", - "memchr", - "num-bigint", - "num-traits", - "oxc_allocator 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_data_structures 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_diagnostics 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ecmascript 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_regular_expression 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_span 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_syntax 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-hash", - "seq-macro", -] - [[package]] name = "oxc_parser_napi" -version = "0.96.0" +version = "0.97.0" dependencies = [ "mimalloc-safe", "napi", "napi-build", "napi-derive", "oxc", - "oxc_ast_macros 0.96.0", - "oxc_estree 0.96.0", + "oxc_ast_macros", + "oxc_estree", "oxc_napi", "rustc-hash", ] @@ -2438,12 +2235,12 @@ name = "oxc_prettier_conformance" version = "0.0.0" dependencies = [ "cow-utils", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_ast_visit 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", "oxc_formatter", - "oxc_parser 0.96.0", - "oxc_span 0.96.0", + "oxc_parser", + "oxc_span", "oxc_tasks_common", "pico-args", "rustc-hash", @@ -2453,29 +2250,13 @@ dependencies = [ [[package]] name = "oxc_regular_expression" -version = "0.96.0" -dependencies = [ - "bitflags 2.10.0", - "oxc_allocator 0.96.0", - "oxc_ast_macros 0.96.0", - "oxc_diagnostics 0.96.0", - "oxc_span 0.96.0", - "phf", - "rustc-hash", - "unicode-id-start", -] - -[[package]] -name = "oxc_regular_expression" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb87ab0b072e1e97d8101cb1678204bc3873d84f13255ae5aa088f1b85f7a8e1" +version = "0.97.0" dependencies = [ "bitflags 2.10.0", - "oxc_allocator 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast_macros 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_diagnostics 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_span 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", + "oxc_allocator", + "oxc_ast_macros", + "oxc_diagnostics", + "oxc_span", "phf", "rustc-hash", "unicode-id-start", @@ -2519,48 +2300,27 @@ dependencies = [ [[package]] name = "oxc_semantic" -version = "0.96.0" +version = "0.97.0" dependencies = [ "insta", "itertools", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_ast_visit 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", "oxc_cfg", - "oxc_data_structures 0.96.0", - "oxc_diagnostics 0.96.0", - "oxc_ecmascript 0.96.0", + "oxc_data_structures", + "oxc_diagnostics", + "oxc_ecmascript", "oxc_index", - "oxc_parser 0.96.0", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", + "oxc_parser", + "oxc_span", + "oxc_syntax", "phf", "rustc-hash", "self_cell", "serde_json", ] -[[package]] -name = "oxc_semantic" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56958658ca1f9f5f050dc4317821255d2ca132763b6fbee9227e45ef79ed173" -dependencies = [ - "itertools", - "oxc_allocator 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast_visit 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_data_structures 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_diagnostics 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ecmascript 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_index", - "oxc_span 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_syntax 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "phf", - "rustc-hash", - "self_cell", -] - [[package]] name = "oxc_sourcemap" version = "6.0.1" @@ -2579,66 +2339,31 @@ dependencies = [ [[package]] name = "oxc_span" -version = "0.96.0" +version = "0.97.0" dependencies = [ "compact_str", "oxc-miette", "oxc-schemars", - "oxc_allocator 0.96.0", - "oxc_ast_macros 0.96.0", - "oxc_estree 0.96.0", - "serde", -] - -[[package]] -name = "oxc_span" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41422232cfd9915d31dbb76ba2e5ae212884cad232e37203bdcb15bd1466951d" -dependencies = [ - "compact_str", - "oxc-miette", - "oxc_allocator 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast_macros 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_estree 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde", -] - -[[package]] -name = "oxc_syntax" -version = "0.96.0" -dependencies = [ - "bitflags 2.10.0", - "cow-utils", - "dragonbox_ecma", - "nonmax", - "oxc_allocator 0.96.0", - "oxc_ast_macros 0.96.0", - "oxc_data_structures 0.96.0", - "oxc_estree 0.96.0", - "oxc_index", - "oxc_span 0.96.0", - "phf", + "oxc_allocator", + "oxc_ast_macros", + "oxc_estree", "serde", - "unicode-id-start", ] [[package]] name = "oxc_syntax" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea81736f2343df141c7d8de78a91d155be4f712dfa6cd1bdd9a8b4f0676f01f" +version = "0.97.0" dependencies = [ "bitflags 2.10.0", "cow-utils", "dragonbox_ecma", "nonmax", - "oxc_allocator 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast_macros 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_data_structures 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_estree 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", + "oxc_allocator", + "oxc_ast_macros", + "oxc_data_structures", + "oxc_estree", "oxc_index", - "oxc_span 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", + "oxc_span", "phf", "serde", "unicode-id-start", @@ -2649,7 +2374,7 @@ name = "oxc_tasks_common" version = "0.0.0" dependencies = [ "console 0.16.1", - "oxc_span 0.96.0", + "oxc_span", "project-root", "similar", "ureq", @@ -2660,13 +2385,13 @@ name = "oxc_tasks_transform_checker" version = "0.0.0" dependencies = [ "indexmap", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_ast_visit 0.96.0", - "oxc_diagnostics 0.96.0", - "oxc_semantic 0.96.0", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_diagnostics", + "oxc_semantic", + "oxc_span", + "oxc_syntax", "rustc-hash", ] @@ -2676,10 +2401,10 @@ version = "0.0.0" dependencies = [ "humansize", "mimalloc-safe", - "oxc_allocator 0.96.0", - "oxc_minifier 0.96.0", - "oxc_parser 0.96.0", - "oxc_semantic 0.96.0", + "oxc_allocator", + "oxc_minifier", + "oxc_parser", + "oxc_semantic", "oxc_tasks_common", "oxc_transformer", ] @@ -2700,7 +2425,7 @@ dependencies = [ [[package]] name = "oxc_transform_napi" -version = "0.96.0" +version = "0.97.0" dependencies = [ "mimalloc-safe", "napi", @@ -2714,7 +2439,7 @@ dependencies = [ [[package]] name = "oxc_transformer" -version = "0.96.0" +version = "0.97.0" dependencies = [ "base64", "compact_str", @@ -2722,20 +2447,20 @@ dependencies = [ "insta", "itoa", "memchr", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_ast_visit 0.96.0", - "oxc_codegen 0.96.0", - "oxc_compat 0.96.0", - "oxc_data_structures 0.96.0", - "oxc_diagnostics 0.96.0", - "oxc_ecmascript 0.96.0", - "oxc_parser 0.96.0", - "oxc_regular_expression 0.96.0", - "oxc_semantic 0.96.0", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", - "oxc_traverse 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_codegen", + "oxc_compat", + "oxc_data_structures", + "oxc_diagnostics", + "oxc_ecmascript", + "oxc_parser", + "oxc_regular_expression", + "oxc_semantic", + "oxc_span", + "oxc_syntax", + "oxc_traverse", "pico-args", "rustc-hash", "serde", @@ -2745,26 +2470,26 @@ dependencies = [ [[package]] name = "oxc_transformer_plugins" -version = "0.96.0" +version = "0.97.0" dependencies = [ "cow-utils", "insta", "itoa", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_ast_visit 0.96.0", - "oxc_codegen 0.96.0", - "oxc_diagnostics 0.96.0", - "oxc_ecmascript 0.96.0", - "oxc_minifier 0.96.0", - "oxc_parser 0.96.0", - "oxc_semantic 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_codegen", + "oxc_diagnostics", + "oxc_ecmascript", + "oxc_minifier", + "oxc_parser", + "oxc_semantic", "oxc_sourcemap", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", + "oxc_span", + "oxc_syntax", "oxc_tasks_common", "oxc_transformer", - "oxc_traverse 0.96.0", + "oxc_traverse", "pico-args", "rustc-hash", "similar", @@ -2772,35 +2497,17 @@ dependencies = [ [[package]] name = "oxc_traverse" -version = "0.96.0" -dependencies = [ - "itoa", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_ast_visit 0.96.0", - "oxc_data_structures 0.96.0", - "oxc_ecmascript 0.96.0", - "oxc_semantic 0.96.0", - "oxc_span 0.96.0", - "oxc_syntax 0.96.0", - "rustc-hash", -] - -[[package]] -name = "oxc_traverse" -version = "0.96.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdcbcda1412b43a921856314e2984cb9282f0d23c1439ae21bd5879110e01681" +version = "0.97.0" dependencies = [ "itoa", - "oxc_allocator 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ast_visit 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_data_structures 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_ecmascript 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_semantic 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_span 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", - "oxc_syntax 0.96.0 (registry+https://github.com/rust-lang/crates.io-index)", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_data_structures", + "oxc_ecmascript", + "oxc_semantic", + "oxc_span", + "oxc_syntax", "rustc-hash", ] @@ -2816,11 +2523,11 @@ dependencies = [ "napi-build", "napi-derive", "oxc-miette", - "oxc_allocator 0.96.0", - "oxc_diagnostics 0.96.0", + "oxc_allocator", + "oxc_diagnostics", "oxc_formatter", - "oxc_parser 0.96.0", - "oxc_span 0.96.0", + "oxc_parser", + "oxc_span", "rayon", "tracing-subscriber", ] @@ -2839,10 +2546,10 @@ dependencies = [ "napi-build", "napi-derive", "oxc-miette", - "oxc_allocator 0.96.0", - "oxc_diagnostics 0.96.0", + "oxc_allocator", + "oxc_diagnostics", "oxc_linter", - "oxc_span 0.96.0", + "oxc_span", "rayon", "rustc-hash", "serde", @@ -3186,14 +2893,14 @@ dependencies = [ name = "rulegen" version = "0.0.0" dependencies = [ - "convert_case 0.9.0", + "convert_case", "handlebars", "lazy-regex", - "oxc_allocator 0.96.0", - "oxc_ast 0.96.0", - "oxc_ast_visit 0.96.0", - "oxc_parser 0.96.0", - "oxc_span 0.96.0", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_parser", + "oxc_span", "oxc_tasks_common", "rustc-hash", "serde", @@ -3224,7 +2931,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3319,7 +3026,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3406,6 +3113,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3562,9 +3278,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.109" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -3598,7 +3314,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3717,6 +3433,45 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + [[package]] name = "tower" version = "0.5.2" @@ -4072,11 +3827,11 @@ dependencies = [ "itertools", "markdown", "oxc-schemars", - "oxc_allocator 0.96.0", - "oxc_diagnostics 0.96.0", + "oxc_allocator", + "oxc_diagnostics", "oxc_linter", - "oxc_parser 0.96.0", - "oxc_span 0.96.0", + "oxc_parser", + "oxc_span", "oxlint", "pico-args", "project-root", @@ -4090,7 +3845,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4294,6 +4049,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" + [[package]] name = "wit-bindgen" version = "0.46.0" diff --git a/Cargo.toml b/Cargo.toml index 7ddfba46e3135..a9206002b96fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,33 +103,33 @@ multiple_crate_versions = "allow" [workspace.dependencies] # publish = true -oxc = { version = "0.96.0", path = "crates/oxc" } # Main entry point -oxc_allocator = { version = "0.96.0", path = "crates/oxc_allocator" } # Memory management -oxc_ast = { version = "0.96.0", path = "crates/oxc_ast" } # AST definitions -oxc_ast_macros = { version = "0.96.0", path = "crates/oxc_ast_macros" } # AST proc macros -oxc_ast_visit = { version = "0.96.0", path = "crates/oxc_ast_visit" } # AST visitor pattern -oxc_cfg = { version = "0.96.0", path = "crates/oxc_cfg" } # Control flow graph -oxc_codegen = { version = "0.96.0", path = "crates/oxc_codegen" } # Code generation -oxc_compat = { version = "0.96.0", path = "crates/oxc_compat" } # Browser compatibility -oxc_data_structures = { version = "0.96.0", path = "crates/oxc_data_structures" } # Shared data structures -oxc_diagnostics = { version = "0.96.0", path = "crates/oxc_diagnostics" } # Error reporting -oxc_ecmascript = { version = "0.96.0", path = "crates/oxc_ecmascript" } # ECMAScript operations -oxc_estree = { version = "0.96.0", path = "crates/oxc_estree" } # ESTree format -oxc_isolated_declarations = { version = "0.96.0", path = "crates/oxc_isolated_declarations" } # TS declaration generation -oxc_mangler = { version = "0.96.0", path = "crates/oxc_mangler" } # Name mangling -oxc_minifier = { version = "0.96.0", path = "crates/oxc_minifier" } # Code minification -oxc_minify_napi = { version = "0.96.0", path = "napi/minify" } # Node.js minifier binding -oxc_napi = { version = "0.96.0", path = "crates/oxc_napi" } # NAPI utilities -oxc_parser = { version = "0.96.0", path = "crates/oxc_parser", features = ["regular_expression"] } # JS/TS parser -oxc_parser_napi = { version = "0.96.0", path = "napi/parser" } # Node.js parser binding -oxc_regular_expression = { version = "0.96.0", path = "crates/oxc_regular_expression" } # Regex parser -oxc_semantic = { version = "0.96.0", path = "crates/oxc_semantic" } # Semantic analysis -oxc_span = { version = "0.96.0", path = "crates/oxc_span" } # Source positions -oxc_syntax = { version = "0.96.0", path = "crates/oxc_syntax" } # Syntax utilities -oxc_transform_napi = { version = "0.96.0", path = "napi/transform" } # Node.js transformer binding -oxc_transformer = { version = "0.96.0", path = "crates/oxc_transformer" } # Code transformation -oxc_transformer_plugins = { version = "0.96.0", path = "crates/oxc_transformer_plugins" } # Transformer plugins -oxc_traverse = { version = "0.96.0", path = "crates/oxc_traverse" } # AST traversal +oxc = { version = "0.97.0", path = "crates/oxc" } # Main entry point +oxc_allocator = { version = "0.97.0", path = "crates/oxc_allocator" } # Memory management +oxc_ast = { version = "0.97.0", path = "crates/oxc_ast" } # AST definitions +oxc_ast_macros = { version = "0.97.0", path = "crates/oxc_ast_macros" } # AST proc macros +oxc_ast_visit = { version = "0.97.0", path = "crates/oxc_ast_visit" } # AST visitor pattern +oxc_cfg = { version = "0.97.0", path = "crates/oxc_cfg" } # Control flow graph +oxc_codegen = { version = "0.97.0", path = "crates/oxc_codegen" } # Code generation +oxc_compat = { version = "0.97.0", path = "crates/oxc_compat" } # Browser compatibility +oxc_data_structures = { version = "0.97.0", path = "crates/oxc_data_structures" } # Shared data structures +oxc_diagnostics = { version = "0.97.0", path = "crates/oxc_diagnostics" } # Error reporting +oxc_ecmascript = { version = "0.97.0", path = "crates/oxc_ecmascript" } # ECMAScript operations +oxc_estree = { version = "0.97.0", path = "crates/oxc_estree" } # ESTree format +oxc_isolated_declarations = { version = "0.97.0", path = "crates/oxc_isolated_declarations" } # TS declaration generation +oxc_mangler = { version = "0.97.0", path = "crates/oxc_mangler" } # Name mangling +oxc_minifier = { version = "0.97.0", path = "crates/oxc_minifier" } # Code minification +oxc_minify_napi = { version = "0.97.0", path = "napi/minify" } # Node.js minifier binding +oxc_napi = { version = "0.97.0", path = "crates/oxc_napi" } # NAPI utilities +oxc_parser = { version = "0.97.0", path = "crates/oxc_parser", features = ["regular_expression"] } # JS/TS parser +oxc_parser_napi = { version = "0.97.0", path = "napi/parser" } # Node.js parser binding +oxc_regular_expression = { version = "0.97.0", path = "crates/oxc_regular_expression" } # Regex parser +oxc_semantic = { version = "0.97.0", path = "crates/oxc_semantic" } # Semantic analysis +oxc_span = { version = "0.97.0", path = "crates/oxc_span" } # Source positions +oxc_syntax = { version = "0.97.0", path = "crates/oxc_syntax" } # Syntax utilities +oxc_transform_napi = { version = "0.97.0", path = "napi/transform" } # Node.js transformer binding +oxc_transformer = { version = "0.97.0", path = "crates/oxc_transformer" } # Code transformation +oxc_transformer_plugins = { version = "0.97.0", path = "crates/oxc_transformer_plugins" } # Transformer plugins +oxc_traverse = { version = "0.97.0", path = "crates/oxc_traverse" } # AST traversal # publish = false oxc_formatter = { path = "crates/oxc_formatter" } # Code formatting @@ -225,6 +225,7 @@ similar-asserts = "1.7.0" # Test diff assertions smallvec = { version = "1.15.1", features = ["union"] } # Stack-allocated vectors tempfile = "3.23.0" # Temporary files tokio = { version = "1.48.0", default-features = false } # Async runtime +toml = { version = "0.9.8" } tower-lsp-server = "0.22.1" # LSP server framework tracing-subscriber = "0.3.20" # Tracing implementation ureq = { version = "3.1.4", default-features = false } # HTTP client diff --git a/MAINTENANCE.md b/MAINTENANCE.md index 484b36e63e468..434afaf5ee9dd 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -1,22 +1,20 @@ -## Oxlint +## Oxlint & Oxfmt -### Release Oxlint +### Release Oxlint & Oxfmt -- Run [Prepare Release Oxlint](https://github.com/oxc-project/oxc/actions/workflows/prepare_release_oxlint.yml) +- Run [Prepare Release Apps](https://github.com/oxc-project/oxc/actions/workflows/prepare_release_apps.yml) + - Releases both oxlint and oxfmt together as a single GitHub release (with different versions) + - Runs automatically every Monday at 9am UTC / 5pm Shanghai time ### E2E Testing -- Run [Oxc Ecosystem CI](https://github.com/oxc-project/oxc-ecosystem-ci/actions/workflows/ci.yml) - -## Oxfmt - -### Release Oxfmt - -- Run [Prepare Release Oxfmt](https://github.com/oxc-project/oxc/actions/workflows/prepare_release_oxfmt.yml) +- Run [Oxlint Ecosystem CI](https://github.com/oxc-project/oxc-ecosystem-ci/actions/workflows/ci.yml) +- Run [Oxfmt Ecosystem CI](https://github.com/oxc-project/oxc-ecosystem-ci/actions/workflows/oxfmt-ci.yml) ## Publish Crates - Run [Prepare Release Crates](https://github.com/oxc-project/oxc/actions/workflows/prepare_release_crates.yml) + - Runs automatically every Monday at 9am UTC / 5pm Shanghai time Note: [crates.io trusted publishing](https://crates.io/docs/trusted-publishing) is configured, a short lived token is used instead of a long-live token stored in github secrets. diff --git a/apps/oxfmt/package.json b/apps/oxfmt/package.json index 9307f057d6e3b..d2d998457b2c8 100644 --- a/apps/oxfmt/package.json +++ b/apps/oxfmt/package.json @@ -1,6 +1,6 @@ { "name": "oxfmt", - "version": "0.9.0", + "version": "0.13.0", "private": true, "type": "module", "main": "dist/index.js", diff --git a/apps/oxfmt/src-js/bindings.js b/apps/oxfmt/src-js/bindings.js index 11a8ca50ce562..cbc9a1c4e30c5 100644 --- a/apps/oxfmt/src-js/bindings.js +++ b/apps/oxfmt/src-js/bindings.js @@ -81,8 +81,8 @@ function requireNative() { try { const binding = require('@oxfmt/android-arm64') const bindingPackageVersion = require('@oxfmt/android-arm64/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -97,8 +97,8 @@ function requireNative() { try { const binding = require('@oxfmt/android-arm-eabi') const bindingPackageVersion = require('@oxfmt/android-arm-eabi/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -118,8 +118,8 @@ function requireNative() { try { const binding = require('@oxfmt/win32-x64-gnu') const bindingPackageVersion = require('@oxfmt/win32-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -134,8 +134,8 @@ function requireNative() { try { const binding = require('@oxfmt/win32-x64') const bindingPackageVersion = require('@oxfmt/win32-x64/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -151,8 +151,8 @@ function requireNative() { try { const binding = require('@oxfmt/win32-ia32') const bindingPackageVersion = require('@oxfmt/win32-ia32/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -167,8 +167,8 @@ function requireNative() { try { const binding = require('@oxfmt/win32-arm64') const bindingPackageVersion = require('@oxfmt/win32-arm64/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -186,8 +186,8 @@ function requireNative() { try { const binding = require('@oxfmt/darwin-universal') const bindingPackageVersion = require('@oxfmt/darwin-universal/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -202,8 +202,8 @@ function requireNative() { try { const binding = require('@oxfmt/darwin-x64') const bindingPackageVersion = require('@oxfmt/darwin-x64/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -218,8 +218,8 @@ function requireNative() { try { const binding = require('@oxfmt/darwin-arm64') const bindingPackageVersion = require('@oxfmt/darwin-arm64/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -238,8 +238,8 @@ function requireNative() { try { const binding = require('@oxfmt/freebsd-x64') const bindingPackageVersion = require('@oxfmt/freebsd-x64/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -254,8 +254,8 @@ function requireNative() { try { const binding = require('@oxfmt/freebsd-arm64') const bindingPackageVersion = require('@oxfmt/freebsd-arm64/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -275,8 +275,8 @@ function requireNative() { try { const binding = require('@oxfmt/linux-x64-musl') const bindingPackageVersion = require('@oxfmt/linux-x64-musl/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -291,8 +291,8 @@ function requireNative() { try { const binding = require('@oxfmt/linux-x64-gnu') const bindingPackageVersion = require('@oxfmt/linux-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -309,8 +309,8 @@ function requireNative() { try { const binding = require('@oxfmt/linux-arm64-musl') const bindingPackageVersion = require('@oxfmt/linux-arm64-musl/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -325,8 +325,8 @@ function requireNative() { try { const binding = require('@oxfmt/linux-arm64-gnu') const bindingPackageVersion = require('@oxfmt/linux-arm64-gnu/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -343,8 +343,8 @@ function requireNative() { try { const binding = require('@oxfmt/linux-arm-musleabihf') const bindingPackageVersion = require('@oxfmt/linux-arm-musleabihf/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -359,8 +359,8 @@ function requireNative() { try { const binding = require('@oxfmt/linux-arm-gnueabihf') const bindingPackageVersion = require('@oxfmt/linux-arm-gnueabihf/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -377,8 +377,8 @@ function requireNative() { try { const binding = require('@oxfmt/linux-loong64-musl') const bindingPackageVersion = require('@oxfmt/linux-loong64-musl/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -393,8 +393,8 @@ function requireNative() { try { const binding = require('@oxfmt/linux-loong64-gnu') const bindingPackageVersion = require('@oxfmt/linux-loong64-gnu/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -411,8 +411,8 @@ function requireNative() { try { const binding = require('@oxfmt/linux-riscv64-musl') const bindingPackageVersion = require('@oxfmt/linux-riscv64-musl/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -427,8 +427,8 @@ function requireNative() { try { const binding = require('@oxfmt/linux-riscv64-gnu') const bindingPackageVersion = require('@oxfmt/linux-riscv64-gnu/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -444,8 +444,8 @@ function requireNative() { try { const binding = require('@oxfmt/linux-ppc64-gnu') const bindingPackageVersion = require('@oxfmt/linux-ppc64-gnu/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -460,8 +460,8 @@ function requireNative() { try { const binding = require('@oxfmt/linux-s390x-gnu') const bindingPackageVersion = require('@oxfmt/linux-s390x-gnu/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -480,8 +480,8 @@ function requireNative() { try { const binding = require('@oxfmt/openharmony-arm64') const bindingPackageVersion = require('@oxfmt/openharmony-arm64/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -496,8 +496,8 @@ function requireNative() { try { const binding = require('@oxfmt/openharmony-x64') const bindingPackageVersion = require('@oxfmt/openharmony-x64/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -512,8 +512,8 @@ function requireNative() { try { const binding = require('@oxfmt/openharmony-arm') const bindingPackageVersion = require('@oxfmt/openharmony-arm/package.json').version - if (bindingPackageVersion !== '0.9.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.9.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.13.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.13.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { diff --git a/apps/oxfmt/src/command.rs b/apps/oxfmt/src/command.rs index dac32b171e31e..9614e45fbc34b 100644 --- a/apps/oxfmt/src/command.rs +++ b/apps/oxfmt/src/command.rs @@ -44,7 +44,7 @@ pub enum OutputOptions { /// Default - when no output option is specified, behaves like `--write` mode in Prettier #[bpaf(hide)] DefaultWrite, - /// Check mode - check if files are formatted + /// Check mode - check if files are formatted, also show statistics #[bpaf(long)] Check, /// List mode - list files that would be changed diff --git a/apps/oxfmt/src/format.rs b/apps/oxfmt/src/format.rs index f702381165203..21d2a782be586 100644 --- a/apps/oxfmt/src/format.rs +++ b/apps/oxfmt/src/format.rs @@ -188,10 +188,10 @@ impl FormatRunner { print_stats(stdout); CliRunResult::FormatMismatch } - // Default (write) also outputs friendly summary - (OutputOptions::DefaultWrite, formatted_count) => { - print_and_flush_stdout(stdout, &format!("Formatted {formatted_count} files.\n")); - print_stats(stdout); + // Default (write) does not output anything + (OutputOptions::DefaultWrite, warnings_count) => { + // Each changed file is also NOT printed by reporter + debug_assert_eq!(warnings_count, 0, "There should be no warnings in write mode"); CliRunResult::FormatSucceeded } } diff --git a/apps/oxfmt/src/service.rs b/apps/oxfmt/src/service.rs index 8a80568c04c2f..9e350ccfaf57a 100644 --- a/apps/oxfmt/src/service.rs +++ b/apps/oxfmt/src/service.rs @@ -124,24 +124,24 @@ impl FormatService { } // Notify if needed - let display_path = path - // Show path relative to `cwd` for cleaner output - .strip_prefix(&self.cwd) - .unwrap_or(path) - .to_string_lossy() - // Normalize path separators for consistent output across platforms - .cow_replace('\\', "/") - .to_string(); - let elapsed = elapsed.as_millis(); if let Some(diagnostic) = match (&self.output_options, is_changed) { - (OutputOptions::Check, true) => { - Some(OxcDiagnostic::warn(format!("{display_path} ({elapsed}ms)"))) + (OutputOptions::Check | OutputOptions::ListDifferent, true) => { + let display_path = path + // Show path relative to `cwd` for cleaner output + .strip_prefix(&self.cwd) + .unwrap_or(path) + .to_string_lossy() + // Normalize path separators for consistent output across platforms + .cow_replace('\\', "/") + .to_string(); + let elapsed = elapsed.as_millis(); + + if matches!(self.output_options, OutputOptions::Check) { + Some(OxcDiagnostic::warn(format!("{display_path} ({elapsed}ms)"))) + } else { + Some(OxcDiagnostic::warn(display_path)) + } } - (OutputOptions::ListDifferent, true) => Some(OxcDiagnostic::warn(display_path)), - (OutputOptions::DefaultWrite, _) => Some(OxcDiagnostic::warn(format!( - "{display_path} {elapsed}ms{}", - if is_changed { "" } else { " (unchanged)" } - ))), _ => None, } { tx_error.send(vec![diagnostic.into()]).unwrap(); diff --git a/apps/oxlint/src-js/index.ts b/apps/oxlint/src-js/index.ts index b2657acc89c56..5f3707c42b8aa 100644 --- a/apps/oxlint/src-js/index.ts +++ b/apps/oxlint/src-js/index.ts @@ -143,6 +143,8 @@ let cwd: string | null = null; // // Only `cwd` property and `extends` method are available in `createOnce`, so only those are implemented here. // All other getters/methods throw, same as they do in main implementation. +// +// See `FILE_CONTEXT` in `plugins/context.ts` for details of all the getters/methods. const FILE_CONTEXT: FileContext = freeze({ get filename(): string { throw new Error('Cannot access `context.filename` in `createOnce`'); @@ -152,9 +154,6 @@ const FILE_CONTEXT: FileContext = freeze({ throw new Error('Cannot access `context.physicalFilename` in `createOnce`'); }, - /** - * Current working directory. - */ get cwd(): string { // Note: We can allow accessing `cwd` in `createOnce`, as it's global if (cwd === null) cwd = process.cwd(); @@ -173,14 +172,8 @@ const FILE_CONTEXT: FileContext = freeze({ throw new Error('Cannot access `context.settings` in `createOnce`'); }, - /** - * Create a new object with the current object as the prototype and - * the specified properties as its own properties. - * @param extension - The properties to add to the new object. - * @returns A new object with the current object as the prototype - * and the specified properties as its own properties. - */ extend(this: FileContext, extension: Record): FileContext { + // Note: We can allow calling `extend` in `createOnce`, as it involves no file-specific state return freeze(ObjectAssign(ObjectCreate(this), extension)); }, diff --git a/apps/oxlint/src-js/plugins/context.ts b/apps/oxlint/src-js/plugins/context.ts index acc03e256b8a2..751f8241dcd63 100644 --- a/apps/oxlint/src-js/plugins/context.ts +++ b/apps/oxlint/src-js/plugins/context.ts @@ -227,6 +227,7 @@ const FILE_CONTEXT = freeze({ * and the specified properties as its own properties. */ extend(this: FileContext, extension: Record): FileContext { + // Note: We can allow calling `extend` in `createOnce`, as it involves no file-specific state return freeze(ObjectAssign(ObjectCreate(this), extension)); }, diff --git a/apps/oxlint/test/fixtures/comments/plugin.ts b/apps/oxlint/test/fixtures/comments/plugin.ts index 73e4b3893ad44..d8be3f2a0c848 100644 --- a/apps/oxlint/test/fixtures/comments/plugin.ts +++ b/apps/oxlint/test/fixtures/comments/plugin.ts @@ -29,14 +29,16 @@ const testCommentsRule: Rule = { assert(topLevelFunction.type === 'FunctionDeclaration'); context.report({ - message: 'commentsExistBetween(topLevelVariable2, topLevelFunction): ' + + message: + 'commentsExistBetween(topLevelVariable2, topLevelFunction): ' + sourceCode.commentsExistBetween(topLevelVariable2, topLevelFunction), node: topLevelVariable2, }); // Test `commentsExistBetween` returns `false` when start node is after end node context.report({ - message: 'commentsExistBetween(topLevelFunction, topLevelVariable2): ' + + message: + 'commentsExistBetween(topLevelFunction, topLevelVariable2): ' + sourceCode.commentsExistBetween(topLevelFunction, topLevelVariable2), node: topLevelFunction, }); @@ -50,7 +52,8 @@ const testCommentsRule: Rule = { assert(init !== null); context.report({ - message: `VariableDeclaration(${id.name}):\n` + + message: + `VariableDeclaration(${id.name}):\n` + `getCommentsBefore: ${formatComments(sourceCode.getCommentsBefore(node))}\n` + `getCommentsInside: ${formatComments(sourceCode.getCommentsInside(node))}\n` + `getCommentsAfter: ${formatComments(sourceCode.getCommentsAfter(node))}\n` + @@ -60,7 +63,8 @@ const testCommentsRule: Rule = { }, FunctionDeclaration(node) { context.report({ - message: `FunctionDeclaration(${node.id.name}):\n` + + message: + `FunctionDeclaration(${node.id.name}):\n` + `getCommentsBefore: ${formatComments(sourceCode.getCommentsBefore(node))}\n` + `getCommentsInside: ${formatComments(sourceCode.getCommentsInside(node))}\n` + `getCommentsAfter: ${formatComments(sourceCode.getCommentsAfter(node))}`, diff --git a/apps/oxlint/test/fixtures/custom_plugin_disable_directives/files/index.js b/apps/oxlint/test/fixtures/custom_plugin_disable_directives/files/index.js index beb6374b5e38c..e52042cc5700e 100644 --- a/apps/oxlint/test/fixtures/custom_plugin_disable_directives/files/index.js +++ b/apps/oxlint/test/fixtures/custom_plugin_disable_directives/files/index.js @@ -1,5 +1,3 @@ -// dprint-ignore-file - var shouldError = 1; // oxlint-disable-next-line test-plugin/no-var diff --git a/apps/oxlint/test/fixtures/custom_plugin_disable_directives/output.snap.md b/apps/oxlint/test/fixtures/custom_plugin_disable_directives/output.snap.md index 0c3ea4d714b80..f3b262a9fa047 100644 --- a/apps/oxlint/test/fixtures/custom_plugin_disable_directives/output.snap.md +++ b/apps/oxlint/test/fixtures/custom_plugin_disable_directives/output.snap.md @@ -4,42 +4,41 @@ # stdout ``` x test-plugin(no-var): Use let or const instead of var - ,-[files/index.js:3:1] - 2 | - 3 | var shouldError = 1; + ,-[files/index.js:1:1] + 1 | var shouldError = 1; : ^^^^^^^^^^^^^^^^^^^^ - 4 | + 2 | `---- x ]8;;https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger.html\eslint(no-debugger)]8;;\: `debugger` statement is not allowed - ,-[files/index.js:12:1] - 11 | // should trigger an error - 12 | debugger; + ,-[files/index.js:10:1] + 9 | // should trigger an error + 10 | debugger; : ^^^^^^^^^ - 13 | + 11 | `---- help: Remove the debugger statement x test-plugin(no-var): Use let or const instead of var - ,-[files/index.js:18:1] - 17 | /* oxlint-disable-next-line test-plugin */ // `test-plugin` should be `test-plugin/no-var` - 18 | var incorrectlyDisabled = 4; + ,-[files/index.js:16:1] + 15 | /* oxlint-disable-next-line test-plugin */ // `test-plugin` should be `test-plugin/no-var` + 16 | var incorrectlyDisabled = 4; : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 19 | + 17 | `---- x test-plugin(no-var): Use let or const instead of var - ,-[files/index.js:21:1] - 20 | /* oxlint-disable-next-line no-var */ // `no-var` should be `test-plugin/no-var` - 21 | var anotherIncorrectlyDisabled = 4; + ,-[files/index.js:19:1] + 18 | /* oxlint-disable-next-line no-var */ // `no-var` should be `test-plugin/no-var` + 19 | var anotherIncorrectlyDisabled = 4; : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 22 | + 20 | `---- x test-plugin(no-var): Use let or const instead of var - ,-[files/index.js:24:1] - 23 | // This var should trigger an error again - 24 | var shouldErrorAgain = 3; + ,-[files/index.js:22:1] + 21 | // This var should trigger an error again + 22 | var shouldErrorAgain = 3; : ^^^^^^^^^^^^^^^^^^^^^^^^^ `---- diff --git a/apps/oxlint/test/fixtures/estree/files/index.ts b/apps/oxlint/test/fixtures/estree/files/index.ts index 300c53ee8c7e2..c4d74b4c49b3d 100644 --- a/apps/oxlint/test/fixtures/estree/files/index.ts +++ b/apps/oxlint/test/fixtures/estree/files/index.ts @@ -1,14 +1,15 @@ // @ts-nocheck -// dprint-ignore-file // All `Identifier`s let a = { x: y }; // No `ParenthesizedExpression`s in AST +// prettier-ignore const b = (x * ((('str' + ((123)))))); // TS syntax type T = string; // No `TSParenthesizedType`s in AST +// prettier-ignore type U = (((((string)) | ((number))))); diff --git a/apps/oxlint/test/fixtures/estree/output.snap.md b/apps/oxlint/test/fixtures/estree/output.snap.md index f844a840b6ce9..1ac53bc9dad75 100644 --- a/apps/oxlint/test/fixtures/estree/output.snap.md +++ b/apps/oxlint/test/fixtures/estree/output.snap.md @@ -33,96 +33,100 @@ | * TSUnionType:exit: (types: TSStringKeyword, TSNumberKeyword) | * TSTypeAliasDeclaration:exit: (typeAnnotation: TSUnionType) | * Program:exit - ,-[files/index.ts:5:1] - 4 | // All `Identifier`s - 5 | ,-> let a = { x: y }; - 6 | | - 7 | | // No `ParenthesizedExpression`s in AST + ,-[files/index.ts:4:1] + 3 | // All `Identifier`s + 4 | ,-> let a = { x: y }; + 5 | | + 6 | | // No `ParenthesizedExpression`s in AST + 7 | | // prettier-ignore 8 | | const b = (x * ((('str' + ((123)))))); 9 | | 10 | | // TS syntax 11 | | type T = string; 12 | | 13 | | // No `TSParenthesizedType`s in AST - 14 | `-> type U = (((((string)) | ((number))))); + 14 | | // prettier-ignore + 15 | `-> type U = (((((string)) | ((number))))); `---- x estree-check(check): program: - | start/end: [59,265] - | range: [59,265] - | loc: [{"start":{"line":5,"column":0},"end":{"line":15,"column":0}}] - ,-[files/index.ts:5:1] - 4 | // All `Identifier`s - 5 | ,-> let a = { x: y }; - 6 | | - 7 | | // No `ParenthesizedExpression`s in AST + | start/end: [37,281] + | range: [37,281] + | loc: [{"start":{"line":4,"column":0},"end":{"line":16,"column":0}}] + ,-[files/index.ts:4:1] + 3 | // All `Identifier`s + 4 | ,-> let a = { x: y }; + 5 | | + 6 | | // No `ParenthesizedExpression`s in AST + 7 | | // prettier-ignore 8 | | const b = (x * ((('str' + ((123)))))); 9 | | 10 | | // TS syntax 11 | | type T = string; 12 | | 13 | | // No `TSParenthesizedType`s in AST - 14 | `-> type U = (((((string)) | ((number))))); + 14 | | // prettier-ignore + 15 | `-> type U = (((((string)) | ((number))))); `---- x estree-check(check): ident "a": - | start/end: [63,64] - | range: [63,64] - | loc: [{"start":{"line":5,"column":4},"end":{"line":5,"column":5}}] - ,-[files/index.ts:5:5] - 4 | // All `Identifier`s - 5 | let a = { x: y }; + | start/end: [41,42] + | range: [41,42] + | loc: [{"start":{"line":4,"column":4},"end":{"line":4,"column":5}}] + ,-[files/index.ts:4:5] + 3 | // All `Identifier`s + 4 | let a = { x: y }; : ^ - 6 | + 5 | `---- x estree-check(check): ident "x": - | start/end: [69,70] - | range: [69,70] - | loc: [{"start":{"line":5,"column":10},"end":{"line":5,"column":11}}] - ,-[files/index.ts:5:11] - 4 | // All `Identifier`s - 5 | let a = { x: y }; + | start/end: [47,48] + | range: [47,48] + | loc: [{"start":{"line":4,"column":10},"end":{"line":4,"column":11}}] + ,-[files/index.ts:4:11] + 3 | // All `Identifier`s + 4 | let a = { x: y }; : ^ - 6 | + 5 | `---- x estree-check(check): ident "y": - | start/end: [72,73] - | range: [72,73] - | loc: [{"start":{"line":5,"column":13},"end":{"line":5,"column":14}}] - ,-[files/index.ts:5:14] - 4 | // All `Identifier`s - 5 | let a = { x: y }; + | start/end: [50,51] + | range: [50,51] + | loc: [{"start":{"line":4,"column":13},"end":{"line":4,"column":14}}] + ,-[files/index.ts:4:14] + 3 | // All `Identifier`s + 4 | let a = { x: y }; : ^ - 6 | + 5 | `---- x estree-check(check): ident "b": - | start/end: [124,125] - | range: [124,125] + | start/end: [121,122] + | range: [121,122] | loc: [{"start":{"line":8,"column":6},"end":{"line":8,"column":7}}] ,-[files/index.ts:8:7] - 7 | // No `ParenthesizedExpression`s in AST + 7 | // prettier-ignore 8 | const b = (x * ((('str' + ((123)))))); : ^ 9 | `---- x estree-check(check): ident "x": - | start/end: [129,130] - | range: [129,130] + | start/end: [126,127] + | range: [126,127] | loc: [{"start":{"line":8,"column":11},"end":{"line":8,"column":12}}] ,-[files/index.ts:8:12] - 7 | // No `ParenthesizedExpression`s in AST + 7 | // prettier-ignore 8 | const b = (x * ((('str' + ((123)))))); : ^ 9 | `---- x estree-check(check): ident "T": - | start/end: [176,177] - | range: [176,177] + | start/end: [173,174] + | range: [173,174] | loc: [{"start":{"line":11,"column":5},"end":{"line":11,"column":6}}] ,-[files/index.ts:11:6] 10 | // TS syntax @@ -132,12 +136,12 @@ `---- x estree-check(check): ident "U": - | start/end: [230,231] - | range: [230,231] - | loc: [{"start":{"line":14,"column":5},"end":{"line":14,"column":6}}] - ,-[files/index.ts:14:6] - 13 | // No `TSParenthesizedType`s in AST - 14 | type U = (((((string)) | ((number))))); + | start/end: [246,247] + | range: [246,247] + | loc: [{"start":{"line":15,"column":5},"end":{"line":15,"column":6}}] + ,-[files/index.ts:15:6] + 14 | // prettier-ignore + 15 | type U = (((((string)) | ((number))))); : ^ `---- diff --git a/apps/oxlint/test/fixtures/estree/plugin.ts b/apps/oxlint/test/fixtures/estree/plugin.ts index 8a01ae14c218d..88483114d7078 100644 --- a/apps/oxlint/test/fixtures/estree/plugin.ts +++ b/apps/oxlint/test/fixtures/estree/plugin.ts @@ -16,7 +16,8 @@ const plugin: Plugin = { return { Program(program) { context.report({ - message: 'program:\n' + + message: + 'program:\n' + `start/end: [${program.start},${program.end}]\n` + `range: [${program.range}]\n` + `loc: [${JSON.stringify(program.loc)}]`, @@ -41,7 +42,8 @@ const plugin: Plugin = { assert(loc2 === loc); context.report({ - message: `ident "${ident.name}":\n` + + message: + `ident "${ident.name}":\n` + `start/end: [${ident.start},${ident.end}]\n` + `range: [${ident.range}]\n` + `loc: [${JSON.stringify(loc)}]`, @@ -80,11 +82,11 @@ const plugin: Plugin = { }, TSUnionType(union) { // `types` should not be `TSParenthesizedType` - visits.push(`${union.type}: (types: ${union.types.map(t => t.type).join(', ')})`); + visits.push(`${union.type}: (types: ${union.types.map((t) => t.type).join(', ')})`); }, 'TSUnionType:exit'(union) { // `types` should not be `TSParenthesizedType` - visits.push(`${union.type}:exit: (types: ${union.types.map(t => t.type).join(', ')})`); + visits.push(`${union.type}:exit: (types: ${union.types.map((t) => t.type).join(', ')})`); }, TSNumberKeyword(keyword) { visits.push(keyword.type); diff --git a/apps/oxlint/test/fixtures/fixes/plugin.ts b/apps/oxlint/test/fixtures/fixes/plugin.ts index 733e31cf39184..e58d3274f7d3c 100644 --- a/apps/oxlint/test/fixtures/fixes/plugin.ts +++ b/apps/oxlint/test/fixtures/fixes/plugin.ts @@ -5,7 +5,7 @@ const plugin: Plugin = { name: 'fixes-plugin', }, rules: { - 'fixes': { + fixes: { meta: { fixable: 'code', }, diff --git a/apps/oxlint/test/fixtures/isSpaceBetween/files/index.js b/apps/oxlint/test/fixtures/isSpaceBetween/files/index.js index 8f4c0ad6359ec..294ffff5c41fe 100644 --- a/apps/oxlint/test/fixtures/isSpaceBetween/files/index.js +++ b/apps/oxlint/test/fixtures/isSpaceBetween/files/index.js @@ -1,18 +1,26 @@ +// prettier-ignore noSpace=1; +// prettier-ignore singleSpaceBefore =2; +// prettier-ignore singleSpaceAfter= 3; +// prettier-ignore multipleSpaces = 4; +// prettier-ignore newlineBefore= 5; +// prettier-ignore newlineAfter =6; +// prettier-ignore nested = 7 + 8; // We should return `false` for `isSpaceBetween(beforeString, afterString)`, but we currently return `true` +// prettier-ignore beforeString," ",afterString; diff --git a/apps/oxlint/test/fixtures/isSpaceBetween/output.snap.md b/apps/oxlint/test/fixtures/isSpaceBetween/output.snap.md index b5b1b41d8bc1e..addf50f807e94 100644 --- a/apps/oxlint/test/fixtures/isSpaceBetween/output.snap.md +++ b/apps/oxlint/test/fixtures/isSpaceBetween/output.snap.md @@ -10,19 +10,21 @@ | isSpaceBetween(node, left): false | isSpaceBetween(right, node): false | isSpaceBetween(node, right): false - ,-[files/index.js:1:1] - 1 | noSpace=1; + ,-[files/index.js:2:1] + 1 | // prettier-ignore + 2 | noSpace=1; : ^^^^^^^^^ - 2 | + 3 | `---- x test-plugin(is-space-between): | isSpaceBetween(leftExtended, right): false | isSpaceBetween(right, leftExtended): false - ,-[files/index.js:1:1] - 1 | noSpace=1; + ,-[files/index.js:2:1] + 1 | // prettier-ignore + 2 | noSpace=1; : ^^^^^^^^^ - 2 | + 3 | `---- x test-plugin(is-space-between): @@ -32,11 +34,11 @@ | isSpaceBetween(node, left): false | isSpaceBetween(right, node): false | isSpaceBetween(node, right): false - ,-[files/index.js:3:1] - 2 | - 3 | singleSpaceBefore =2; + ,-[files/index.js:5:1] + 4 | // prettier-ignore + 5 | singleSpaceBefore =2; : ^^^^^^^^^^^^^^^^^^^^ - 4 | + 6 | `---- x test-plugin(is-space-between): @@ -46,11 +48,11 @@ | isSpaceBetween(node, left): false | isSpaceBetween(right, node): false | isSpaceBetween(node, right): false - ,-[files/index.js:5:1] - 4 | - 5 | singleSpaceAfter= 3; + ,-[files/index.js:8:1] + 7 | // prettier-ignore + 8 | singleSpaceAfter= 3; : ^^^^^^^^^^^^^^^^^^^ - 6 | + 9 | `---- x test-plugin(is-space-between): @@ -60,12 +62,12 @@ | isSpaceBetween(node, left): false | isSpaceBetween(right, node): false | isSpaceBetween(node, right): false - ,-[files/index.js:7:1] - 6 | - 7 | multipleSpaces = 4; - : ^^^^^^^^^^^^^^^^^^^^^^ - 8 | - `---- + ,-[files/index.js:11:1] + 10 | // prettier-ignore + 11 | multipleSpaces = 4; + : ^^^^^^^^^^^^^^^^^^^^^^ + 12 | + `---- x test-plugin(is-space-between): | isSpaceBetween(left, right): true @@ -74,11 +76,11 @@ | isSpaceBetween(node, left): false | isSpaceBetween(right, node): false | isSpaceBetween(node, right): false - ,-[files/index.js:9:1] - 8 | - 9 | ,-> newlineBefore= - 10 | `-> 5; - 11 | + ,-[files/index.js:14:1] + 13 | // prettier-ignore + 14 | ,-> newlineBefore= + 15 | `-> 5; + 16 | `---- x test-plugin(is-space-between): @@ -88,21 +90,21 @@ | isSpaceBetween(node, left): false | isSpaceBetween(right, node): false | isSpaceBetween(node, right): false - ,-[files/index.js:12:1] - 11 | - 12 | ,-> newlineAfter - 13 | `-> =6; - 14 | + ,-[files/index.js:18:1] + 17 | // prettier-ignore + 18 | ,-> newlineAfter + 19 | `-> =6; + 20 | `---- x test-plugin(is-space-between): | isSpaceBetween(node, binaryLeft): false | isSpaceBetween(binaryLeft, node): false - ,-[files/index.js:15:1] - 14 | - 15 | nested = 7 + 8; + ,-[files/index.js:22:1] + 21 | // prettier-ignore + 22 | nested = 7 + 8; : ^^^^^^^^^^^^^^ - 16 | + 23 | `---- x test-plugin(is-space-between): @@ -112,19 +114,19 @@ | isSpaceBetween(node, left): false | isSpaceBetween(right, node): false | isSpaceBetween(node, right): false - ,-[files/index.js:15:1] - 14 | - 15 | nested = 7 + 8; + ,-[files/index.js:22:1] + 21 | // prettier-ignore + 22 | nested = 7 + 8; : ^^^^^^^^^^^^^^ - 16 | + 23 | `---- x test-plugin(is-space-between): | isSpaceBetween(beforeString, afterString): true | isSpaceBetween(afterString, beforeString): true - ,-[files/index.js:18:1] - 17 | // We should return `false` for `isSpaceBetween(beforeString, afterString)`, but we currently return `true` - 18 | beforeString," ",afterString; + ,-[files/index.js:26:1] + 25 | // prettier-ignore + 26 | beforeString," ",afterString; : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `---- diff --git a/apps/oxlint/test/fixtures/isSpaceBetween/plugin.ts b/apps/oxlint/test/fixtures/isSpaceBetween/plugin.ts index 9e211c718c27f..4a8c8063b4afd 100644 --- a/apps/oxlint/test/fixtures/isSpaceBetween/plugin.ts +++ b/apps/oxlint/test/fixtures/isSpaceBetween/plugin.ts @@ -59,12 +59,12 @@ const testRule: Rule = { // We get this wrong. Should be `false`, but we get `true`. context.report({ - message: - '\n' + - `isSpaceBetween(beforeString, afterString): ${isSpaceBetween(beforeString, afterString)}\n` + - `isSpaceBetween(afterString, beforeString): ${isSpaceBetween(afterString, beforeString)}`, - node, - }); + message: + '\n' + + `isSpaceBetween(beforeString, afterString): ${isSpaceBetween(beforeString, afterString)}\n` + + `isSpaceBetween(afterString, beforeString): ${isSpaceBetween(afterString, beforeString)}`, + node, + }); }, }; }, diff --git a/apps/oxlint/test/fixtures/parent/plugin.ts b/apps/oxlint/test/fixtures/parent/plugin.ts index 07e864dcd85ae..2f71df1c09a84 100644 --- a/apps/oxlint/test/fixtures/parent/plugin.ts +++ b/apps/oxlint/test/fixtures/parent/plugin.ts @@ -9,10 +9,14 @@ const plugin: Plugin = { create(context) { function reportAncestry(node: any) { context.report({ - message: `${node.type}:\n` + + message: + `${node.type}:\n` + `parent: ${node.parent?.type}\n` + - // @ts-ignore - `ancestors: [ ${context.sourceCode.getAncestors(node).map(node => node.type).join(', ')} ]`, + `ancestors: [ ${context.sourceCode + .getAncestors(node) + // @ts-expect-error - Shouldn't be an error. We need to fix our types. + .map((node) => node.type) + .join(', ')} ]`, node, }); } diff --git a/apps/oxlint/test/fixtures/scope_manager/plugin.ts b/apps/oxlint/test/fixtures/scope_manager/plugin.ts index af947e980a52a..307d0098fbecb 100644 --- a/apps/oxlint/test/fixtures/scope_manager/plugin.ts +++ b/apps/oxlint/test/fixtures/scope_manager/plugin.ts @@ -24,9 +24,9 @@ const rule: Rule = { assert.equal(moduleScope.upper, scopeManager.globalScope); context.report({ - message: `File has ${scopeManager.scopes.length} scopes: ${ - scopeManager.scopes.map((s: any) => s.block?.id?.name ?? '<' + s.constructor.name + '>').join(', ') - }`, + message: `File has ${scopeManager.scopes.length} scopes: ${scopeManager.scopes + .map((s: any) => s.block?.id?.name ?? '<' + s.constructor.name + '>') + .join(', ')}`, node: SPAN, }); @@ -37,9 +37,9 @@ const rule: Rule = { if (node.declarations[0].id.type === 'ObjectPattern') { const variables = context.sourceCode.scopeManager.getDeclaredVariables(node); context.report({ - message: `VariableDeclaration declares ${variables.length} variables: ${ - variables.map(v => v.name).join(', ') - }.`, + message: `VariableDeclaration declares ${variables.length} variables: ${variables + .map((v) => v.name) + .join(', ')}.`, node: node, }); } @@ -49,9 +49,9 @@ const rule: Rule = { const topLevelFunctionScope = context.sourceCode.scopeManager.acquire(node)!; assert.equal(topLevelFunctionScope.upper, moduleScope); context.report({ - message: `topLevelFunction has ${topLevelFunctionScope.variables.length} local variables: ${ - topLevelFunctionScope?.variables.map(v => v.name).join(', ') - }. Child scopes: ${topLevelFunctionScope.childScopes.length}.`, + message: `topLevelFunction has ${topLevelFunctionScope.variables.length} local variables: ${topLevelFunctionScope?.variables + .map((v) => v.name) + .join(', ')}. Child scopes: ${topLevelFunctionScope.childScopes.length}.`, node: topLevelFunctionScope.block, }); } @@ -61,9 +61,9 @@ const rule: Rule = { const topLevelModuleScope = context.sourceCode.scopeManager.acquire(node)!; assert.equal(topLevelModuleScope.upper, moduleScope); context.report({ - message: `TopLevelModule has ${topLevelModuleScope.variables.length} local variables: ${ - topLevelModuleScope?.variables.map(v => v.name).join(', ') - }. Child scopes: ${topLevelModuleScope.childScopes.length}.`, + message: `TopLevelModule has ${topLevelModuleScope.variables.length} local variables: ${topLevelModuleScope?.variables + .map((v) => v.name) + .join(', ')}. Child scopes: ${topLevelModuleScope.childScopes.length}.`, node: topLevelModuleScope.block, }); } @@ -78,9 +78,9 @@ const rule: Rule = { assert('name' in upperBlock.id); assert.equal(upperBlock.id.name, 'TestClass'); context.report({ - message: `TestClass static block has ${staticBlockScope.variables.length} local variables: ${ - staticBlockScope?.variables.map(v => v.name).join(', ') - }. Child scopes: ${staticBlockScope.childScopes.length}.`, + message: `TestClass static block has ${staticBlockScope.variables.length} local variables: ${staticBlockScope?.variables + .map((v) => v.name) + .join(', ')}. Child scopes: ${staticBlockScope.childScopes.length}.`, node: node, }); }, @@ -88,9 +88,9 @@ const rule: Rule = { const labeledStatementScope = context.sourceCode.scopeManager.acquire(node.body)!; assert.equal(labeledStatementScope.upper, moduleScope); context.report({ - message: `LabeledStatement's block has ${labeledStatementScope.variables.length} local variables: ${ - labeledStatementScope?.variables.map(v => v.name).join(', ') - }. Child scopes: ${labeledStatementScope.childScopes.length}.`, + message: `LabeledStatement's block has ${labeledStatementScope.variables.length} local variables: ${labeledStatementScope?.variables + .map((v) => v.name) + .join(', ')}. Child scopes: ${labeledStatementScope.childScopes.length}.`, node: node, }); }, diff --git a/apps/oxlint/test/fixtures/selector/files/index.js b/apps/oxlint/test/fixtures/selector/files/index.js index 968299a0fc070..04a40909fec39 100644 --- a/apps/oxlint/test/fixtures/selector/files/index.js +++ b/apps/oxlint/test/fixtures/selector/files/index.js @@ -3,4 +3,4 @@ const obj = { a: [b, c], ...d }; function foo() {} function bar() {} -(() => {}); +() => {}; diff --git a/apps/oxlint/test/fixtures/selector/output.snap.md b/apps/oxlint/test/fixtures/selector/output.snap.md index 4f0855463737a..3371162e1ea35 100644 --- a/apps/oxlint/test/fixtures/selector/output.snap.md +++ b/apps/oxlint/test/fixtures/selector/output.snap.md @@ -117,7 +117,7 @@ 3 | | function foo() {} 4 | | function bar() {} 5 | | - 6 | `-> (() => {}); + 6 | `-> () => {}; `---- Found 0 warnings and 1 error. diff --git a/apps/oxlint/test/fixtures/selector/plugin.ts b/apps/oxlint/test/fixtures/selector/plugin.ts index 310a5d6331ad5..b1e64e92af31c 100644 --- a/apps/oxlint/test/fixtures/selector/plugin.ts +++ b/apps/oxlint/test/fixtures/selector/plugin.ts @@ -59,16 +59,18 @@ const plugin: Plugin = { } visitor['Program:exit'] = (program) => { - const visitLog = visits.map(({ key, node }) => { - const { type } = node; - let nodeDescription = type; - if (type === 'Identifier') { - nodeDescription += `(${node.name})`; - } else if (type === 'FunctionDeclaration') { - nodeDescription += `(${node.id.name})`; - } - return `${key}: ${nodeDescription}`; - }).join('\n'); + const visitLog = visits + .map(({ key, node }) => { + const { type } = node; + let nodeDescription = type; + if (type === 'Identifier') { + nodeDescription += `(${node.name})`; + } else if (type === 'FunctionDeclaration') { + nodeDescription += `(${node.id.name})`; + } + return `${key}: ${nodeDescription}`; + }) + .join('\n'); context.report({ message: `\n${visitLog}`, diff --git a/apps/oxlint/test/fixtures/settings/plugin.ts b/apps/oxlint/test/fixtures/settings/plugin.ts index bbb601b3c975b..e5638bde95088 100644 --- a/apps/oxlint/test/fixtures/settings/plugin.ts +++ b/apps/oxlint/test/fixtures/settings/plugin.ts @@ -15,7 +15,7 @@ const rule: Rule = { const settings = context.settings; // Report each setting key and value - Object.keys(settings).forEach(key => { + Object.keys(settings).forEach((key) => { const value = settings[key]; context.report({ message: `setting ${key}: ${JSON.stringify(value)}`, diff --git a/apps/oxlint/test/fixtures/sourceCode/plugin.ts b/apps/oxlint/test/fixtures/sourceCode/plugin.ts index 48634df6d4ff5..fe45204857650 100644 --- a/apps/oxlint/test/fixtures/sourceCode/plugin.ts +++ b/apps/oxlint/test/fixtures/sourceCode/plugin.ts @@ -22,12 +22,14 @@ const createRule: Rule = { for (let offset = 0; offset <= text.length; offset++) { const loc = context.sourceCode.getLocFromIndex(offset); assert(context.sourceCode.getIndexFromLoc(loc) === offset); - locs += `\n ${offset} => { line: ${loc.line}, column: ${loc.column} }` + + locs += + `\n ${offset} => { line: ${loc.line}, column: ${loc.column} }` + `(${JSON.stringify(text[offset] || '')})`; } context.report({ - message: 'create:\n' + + message: + 'create:\n' + `text: ${JSON.stringify(text)}\n` + `getText(): ${JSON.stringify(context.sourceCode.getText())}\n` + `lines: ${JSON.stringify(lines)}\n` + @@ -55,7 +57,8 @@ const createRule: Rule = { assert(context.sourceCode.getIndexFromLoc(endLoc) === node.end); context.report({ - message: `ident "${node.name}":\n` + + message: + `ident "${node.name}":\n` + `source: "${context.sourceCode.getText(node)}"\n` + `source with before: "${context.sourceCode.getText(node, 2)}"\n` + `source with after: "${context.sourceCode.getText(node, null, 1)}"\n` + @@ -82,12 +85,14 @@ const createOnceRule: Rule = { for (let offset = 0; offset <= text.length; offset++) { const loc = context.sourceCode.getLocFromIndex(offset); assert(context.sourceCode.getIndexFromLoc(loc) === offset); - locs += `\n ${offset} => { line: ${loc.line}, column: ${loc.column} }` + + locs += + `\n ${offset} => { line: ${loc.line}, column: ${loc.column} }` + `(${JSON.stringify(text[offset] || '')})`; } context.report({ - message: 'before:\n' + + message: + 'before:\n' + `text: ${JSON.stringify(text)}\n` + `getText(): ${JSON.stringify(context.sourceCode.getText())}\n` + `lines: ${JSON.stringify(lines)}\n` + @@ -114,7 +119,8 @@ const createOnceRule: Rule = { assert(context.sourceCode.getIndexFromLoc(endLoc) === node.end); context.report({ - message: `ident "${node.name}":\n` + + message: + `ident "${node.name}":\n` + `source: "${context.sourceCode.getText(node)}"\n` + `source with before: "${context.sourceCode.getText(node, 2)}"\n` + `source with after: "${context.sourceCode.getText(node, null, 1)}"\n` + @@ -129,8 +135,7 @@ const createOnceRule: Rule = { ast = null; context.report({ - message: 'after:\n' + - `source: ${JSON.stringify(context.sourceCode.text)}`, + message: 'after:\n' + `source: ${JSON.stringify(context.sourceCode.text)}`, node: SPAN, }); }, diff --git a/apps/oxlint/test/fixtures/sourceCode_late_access/plugin.ts b/apps/oxlint/test/fixtures/sourceCode_late_access/plugin.ts index ce7cdaa0518b6..b3bea50914e4f 100644 --- a/apps/oxlint/test/fixtures/sourceCode_late_access/plugin.ts +++ b/apps/oxlint/test/fixtures/sourceCode_late_access/plugin.ts @@ -31,7 +31,8 @@ const createRule: Rule = { assert(ast === node); context.report({ - message: 'program:\n' + + message: + 'program:\n' + `text: ${JSON.stringify(context.sourceCode.text)}\n` + `getText(): ${JSON.stringify(context.sourceCode.getText())}`, node: SPAN, @@ -49,7 +50,8 @@ const createRule: Rule = { assert(context.sourceCode.ast === ast); context.report({ - message: `ident "${node.name}":\n` + + message: + `ident "${node.name}":\n` + `source: "${context.sourceCode.getText(node)}"\n` + `source with before: "${context.sourceCode.getText(node, 2)}"\n` + `source with after: "${context.sourceCode.getText(node, null, 1)}"\n` + @@ -71,7 +73,8 @@ const createOnceRule: Rule = { assert(ast === node); context.report({ - message: 'program:\n' + + message: + 'program:\n' + `text: ${JSON.stringify(context.sourceCode.text)}\n` + `getText(): ${JSON.stringify(context.sourceCode.getText())}`, node: SPAN, @@ -89,7 +92,8 @@ const createOnceRule: Rule = { assert(context.sourceCode.ast === ast); context.report({ - message: `ident "${node.name}":\n` + + message: + `ident "${node.name}":\n` + `source: "${context.sourceCode.getText(node)}"\n` + `source with before: "${context.sourceCode.getText(node, 2)}"\n` + `source with after: "${context.sourceCode.getText(node, null, 1)}"\n` + @@ -102,8 +106,7 @@ const createOnceRule: Rule = { ast = null; context.report({ - message: 'after:\n' + - `source: ${JSON.stringify(context.sourceCode.text)}`, + message: 'after:\n' + `source: ${JSON.stringify(context.sourceCode.text)}`, node: SPAN, }); }, diff --git a/apps/oxlint/test/fixtures/sourceCode_late_access_after_only/plugin.ts b/apps/oxlint/test/fixtures/sourceCode_late_access_after_only/plugin.ts index b7a50e94d6813..e70d3556982fc 100644 --- a/apps/oxlint/test/fixtures/sourceCode_late_access_after_only/plugin.ts +++ b/apps/oxlint/test/fixtures/sourceCode_late_access_after_only/plugin.ts @@ -19,7 +19,8 @@ const createOnceRule: Rule = { Program(_program) {}, after() { context.report({ - message: 'after:\n' + + message: + 'after:\n' + `text: ${JSON.stringify(context.sourceCode.text)}\n` + `getText(): ${JSON.stringify(context.sourceCode.getText())}\n` + // @ts-ignore diff --git a/apps/oxlint/test/fixtures/sourceCode_scope_methods/plugin.ts b/apps/oxlint/test/fixtures/sourceCode_scope_methods/plugin.ts index 2345f85ea8ec3..93fa0cb4e0fa4 100644 --- a/apps/oxlint/test/fixtures/sourceCode_scope_methods/plugin.ts +++ b/apps/oxlint/test/fixtures/sourceCode_scope_methods/plugin.ts @@ -28,7 +28,7 @@ const rule: Rule = { VariableDeclaration(node) { const variables = sourceCode.getDeclaredVariables(node); context.report({ - message: `getDeclaredVariables(): ${variables.map(v => v.name).join(', ')}`, + message: `getDeclaredVariables(): ${variables.map((v) => v.name).join(', ')}`, node, }); }, @@ -45,8 +45,8 @@ const rule: Rule = { let text = ''; text += `type: ${scope.type}\n`; text += `isStrict: ${scope.isStrict}\n`; - text += `vars: [${scope.variables.map(v => v.name).join(', ')}]\n`; - text += `through: [${scope.through.map(r => (r.identifier as any).name).join(', ')}]\n`; + text += `vars: [${scope.variables.map((v) => v.name).join(', ')}]\n`; + text += `through: [${scope.through.map((r) => (r.identifier as any).name).join(', ')}]\n`; if (scope.upper) text += `upper: ${scope.upper.type}\n`; context.report({ diff --git a/apps/oxlint/test/fixtures/utf16_offsets/plugin.ts b/apps/oxlint/test/fixtures/utf16_offsets/plugin.ts index ce4244439b88f..e452236858ed6 100644 --- a/apps/oxlint/test/fixtures/utf16_offsets/plugin.ts +++ b/apps/oxlint/test/fixtures/utf16_offsets/plugin.ts @@ -10,7 +10,8 @@ const plugin: Plugin = { return { Program(program) { context.report({ - message: 'program:\n' + + message: + 'program:\n' + `start/end: [${program.start},${program.end}]\n` + `range: [${program.range}]\n` + `loc: [${JSON.stringify(program.loc)}]`, @@ -19,7 +20,8 @@ const plugin: Plugin = { }, DebuggerStatement(debuggerStatement) { context.report({ - message: 'debugger:\n' + + message: + 'debugger:\n' + `start/end: [${debuggerStatement.start},${debuggerStatement.end}]\n` + `range: [${debuggerStatement.range}]\n` + `loc: [${JSON.stringify(debuggerStatement.loc)}]`, diff --git a/crates/oxc/Cargo.toml b/crates/oxc/Cargo.toml index 1f07028eeb0e2..1cd2ccea5c885 100644 --- a/crates/oxc/Cargo.toml +++ b/crates/oxc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_allocator/CHANGELOG.md b/crates/oxc_allocator/CHANGELOG.md index d364396a73162..860f3f7b10fb0 100644 --- a/crates/oxc_allocator/CHANGELOG.md +++ b/crates/oxc_allocator/CHANGELOG.md @@ -4,6 +4,27 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 🚀 Features + +- b401708 allocator: Add `Box::as_non_null` method (#15321) (overlookmotel) +- 8d69661 allocator: Add `Address::from_ref` method (#15318) (overlookmotel) + +### 🐛 Bug Fixes + +- 55b533c allocator: Avoid dereferencing `Box` when obtaining its `Address` (#15322) (overlookmotel) + +### ⚡ Performance + +- b6f3424 allocator: Add `#[inline(always)]` to trivial `RawVec` methods (#15470) (overlookmotel) +- b5d6360 allocator: `#[inline(always)]` all `Address` methods (#15324) (overlookmotel) +- bfd17fd allocator/address: Add `#[repr(transparent)]` to `Address` (#15312) (overlookmotel) + +### 📚 Documentation + +- ed0e023 allocator/address: Improve docs for `Address::from_ptr` (#15311) (overlookmotel) + ## [0.94.0] - 2025-10-06 diff --git a/crates/oxc_allocator/Cargo.toml b/crates/oxc_allocator/Cargo.toml index 2adfa0cb17e9d..9d5c4f7a3476e 100644 --- a/crates/oxc_allocator/Cargo.toml +++ b/crates/oxc_allocator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_allocator" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_ast/CHANGELOG.md b/crates/oxc_ast/CHANGELOG.md index c7d427de44ca8..2c871df3fe42a 100644 --- a/crates/oxc_ast/CHANGELOG.md +++ b/crates/oxc_ast/CHANGELOG.md @@ -4,6 +4,25 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 🚀 Features + +- 8d69661 allocator: Add `Address::from_ref` method (#15318) (overlookmotel) +- 977a6a0 ast: Implement `GetAddress` for `ModuleDeclarationKind` and `PropertyKeyKind` (#15313) (overlookmotel) +- 682dca2 parser: Add more helps to parser errors (#15186) (sapphi-red) + +### 🐛 Bug Fixes + +- 40231a6 linter/plugins, napi/parser: Add `parent` field to `FormalParameterRest` and `TSParameterProperty` in TS type defs (#15337) (overlookmotel) +- 7f079ab ast/estree: Fix raw transfer deserializer for `AssignmentTargetPropertyIdentifier` (#15304) (overlookmotel) +- d92451e ast/estree: Correct raw transfer deserializer for `FormalParameter` (#15302) (overlookmotel) +- 75c9164 ast: Treat `TSEmptyBodyFunctionExpression` as expression in Function::is_expression (#14945) (Liang Mi) + +### 📚 Documentation + +- 3dc24b5 linter,minifier: Always refer as "ES Modules" instead of "ES6 Modules" (#15409) (sapphi-red) + ## [0.96.0] - 2025-10-30 ### 🚀 Features diff --git a/crates/oxc_ast/Cargo.toml b/crates/oxc_ast/Cargo.toml index 6e20276099171..761983eed5bb8 100644 --- a/crates/oxc_ast/Cargo.toml +++ b/crates/oxc_ast/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_ast" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs index 48e5d6d0ce352..b98cd035dd5b1 100644 --- a/crates/oxc_ast/src/ast_impl/js.rs +++ b/crates/oxc_ast/src/ast_impl/js.rs @@ -430,7 +430,7 @@ impl<'a> From> for ArrayExpressionElement<'a> { fn from(argument: Argument<'a>) -> Self { match argument { Argument::SpreadElement(spread) => Self::SpreadElement(spread), - match_expression!(Argument) => Self::from(argument.into_expression()), + _ => Self::from(argument.into_expression()), } } } diff --git a/crates/oxc_ast/src/ast_kind_impl.rs b/crates/oxc_ast/src/ast_kind_impl.rs index d56ef32ec254f..9601ee7211a78 100644 --- a/crates/oxc_ast/src/ast_kind_impl.rs +++ b/crates/oxc_ast/src/ast_kind_impl.rs @@ -122,6 +122,54 @@ impl<'a> AstKind<'a> { matches!(self, Self::Function(_) | Self::ArrowFunctionExpression(_)) } + /// Check if this CallExpression or NewExpression has an argument with the given span + /// + /// This is useful for determining if a node is an argument to a call expression + /// when traversing the AST, particularly after the removal of `AstKind::Argument`. + /// + /// # Examples + /// + /// ```rust,ignore + /// // Check if a node is an argument to its parent call expression + /// if parent.has_argument_with_span(node.span()) { + /// // This node is an argument + /// } + /// ``` + #[inline] + pub fn has_argument_with_span(&self, span: oxc_span::Span) -> bool { + match self { + Self::CallExpression(call) => call.arguments.iter().any(|arg| arg.span() == span), + Self::NewExpression(new_expr) => { + new_expr.arguments.iter().any(|arg| arg.span() == span) + } + _ => false, + } + } + + /// Check if this CallExpression or NewExpression has the given span as its callee + /// + /// This is useful for determining if a node is the callee of a call expression + /// when traversing the AST. + /// + /// # Examples + /// + /// ```rust,ignore + /// // Detect eval() calls + /// if let AstKind::IdentifierReference(ident) = node.kind() { + /// if parent.is_callee_with_span(ident.span) && ident.name == "eval" { + /// // This is an eval() call + /// } + /// } + /// ``` + #[inline] + pub fn is_callee_with_span(&self, span: oxc_span::Span) -> bool { + match self { + Self::CallExpression(call) => call.callee.span() == span, + Self::NewExpression(new_expr) => new_expr.callee.span() == span, + _ => false, + } + } + /// Get the name of an identifier node /// /// Returns the identifier name if this is any kind of identifier node, @@ -415,7 +463,6 @@ impl AstKind<'_> { Self::ObjectProperty(p) => { format!("ObjectProperty({})", p.key.name().unwrap_or(COMPUTED)).into() } - Self::Argument(_) => "Argument".into(), Self::ArrayAssignmentTarget(_) => "ArrayAssignmentTarget".into(), Self::ObjectAssignmentTarget(_) => "ObjectAssignmentTarget".into(), Self::AssignmentTargetWithDefault(_) => "AssignmentTargetWithDefault".into(), @@ -789,3 +836,49 @@ impl GetAddress for PropertyKeyKind<'_> { } } } +#[cfg(test)] +mod tests { + use super::*; + use oxc_span::Span; + + // Note: These tests verify the logic of the methods. + // Integration tests using real parsed AST are in the linter crate. + + #[test] + fn test_has_argument_with_span_returns_false_for_non_call_expressions() { + // Test that non-CallExpression/NewExpression AstKinds always return false + let test_span = Span::new(0, 5); + + let num_lit = NumericLiteral { + span: test_span, + value: 42.0, + raw: None, + base: oxc_syntax::number::NumberBase::Decimal, + }; + let num_kind = AstKind::NumericLiteral(&num_lit); + assert!(!num_kind.has_argument_with_span(test_span)); + + let bool_lit = BooleanLiteral { span: test_span, value: true }; + let bool_kind = AstKind::BooleanLiteral(&bool_lit); + assert!(!bool_kind.has_argument_with_span(test_span)); + } + + #[test] + fn test_is_callee_with_span_returns_false_for_non_call_expressions() { + // Test that non-CallExpression/NewExpression AstKinds always return false + let test_span = Span::new(0, 5); + + let num_lit = NumericLiteral { + span: test_span, + value: 42.0, + raw: None, + base: oxc_syntax::number::NumberBase::Decimal, + }; + let num_kind = AstKind::NumericLiteral(&num_lit); + assert!(!num_kind.is_callee_with_span(test_span)); + + let bool_lit = BooleanLiteral { span: test_span, value: true }; + let bool_kind = AstKind::BooleanLiteral(&bool_lit); + assert!(!bool_kind.is_callee_with_span(test_span)); + } +} diff --git a/crates/oxc_ast/src/generated/ast_kind.rs b/crates/oxc_ast/src/generated/ast_kind.rs index 748a081e36e96..2c66b27c07574 100644 --- a/crates/oxc_ast/src/generated/ast_kind.rs +++ b/crates/oxc_ast/src/generated/ast_kind.rs @@ -12,7 +12,7 @@ use oxc_span::{GetSpan, Span}; use crate::ast::*; /// The largest integer value that can be mapped to an `AstType`/`AstKind` enum variant. -pub const AST_TYPE_MAX: u8 = 186; +pub const AST_TYPE_MAX: u8 = 185; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u8)] @@ -37,173 +37,172 @@ pub enum AstType { NewExpression = 17, MetaProperty = 18, SpreadElement = 19, - Argument = 20, - UpdateExpression = 21, - UnaryExpression = 22, - BinaryExpression = 23, - PrivateInExpression = 24, - LogicalExpression = 25, - ConditionalExpression = 26, - AssignmentExpression = 27, - ArrayAssignmentTarget = 28, - ObjectAssignmentTarget = 29, - AssignmentTargetRest = 30, - AssignmentTargetWithDefault = 31, - AssignmentTargetPropertyIdentifier = 32, - AssignmentTargetPropertyProperty = 33, - SequenceExpression = 34, - Super = 35, - AwaitExpression = 36, - ChainExpression = 37, - ParenthesizedExpression = 38, - Directive = 39, - Hashbang = 40, - BlockStatement = 41, - VariableDeclaration = 42, - VariableDeclarator = 43, - EmptyStatement = 44, - ExpressionStatement = 45, - IfStatement = 46, - DoWhileStatement = 47, - WhileStatement = 48, - ForStatement = 49, - ForInStatement = 50, - ForOfStatement = 51, - ContinueStatement = 52, - BreakStatement = 53, - ReturnStatement = 54, - WithStatement = 55, - SwitchStatement = 56, - SwitchCase = 57, - LabeledStatement = 58, - ThrowStatement = 59, - TryStatement = 60, - CatchClause = 61, - CatchParameter = 62, - DebuggerStatement = 63, - AssignmentPattern = 64, - ObjectPattern = 65, - BindingProperty = 66, - ArrayPattern = 67, - BindingRestElement = 68, - Function = 69, - FormalParameters = 70, - FormalParameter = 71, - FunctionBody = 72, - ArrowFunctionExpression = 73, - YieldExpression = 74, - Class = 75, - ClassBody = 76, - MethodDefinition = 77, - PropertyDefinition = 78, - PrivateIdentifier = 79, - StaticBlock = 80, - AccessorProperty = 81, - ImportExpression = 82, - ImportDeclaration = 83, - ImportSpecifier = 84, - ImportDefaultSpecifier = 85, - ImportNamespaceSpecifier = 86, - WithClause = 87, - ImportAttribute = 88, - ExportNamedDeclaration = 89, - ExportDefaultDeclaration = 90, - ExportAllDeclaration = 91, - ExportSpecifier = 92, - V8IntrinsicExpression = 93, - BooleanLiteral = 94, - NullLiteral = 95, - NumericLiteral = 96, - StringLiteral = 97, - BigIntLiteral = 98, - RegExpLiteral = 99, - JSXElement = 100, - JSXOpeningElement = 101, - JSXClosingElement = 102, - JSXFragment = 103, - JSXOpeningFragment = 104, - JSXClosingFragment = 105, - JSXNamespacedName = 106, - JSXMemberExpression = 107, - JSXExpressionContainer = 108, - JSXEmptyExpression = 109, - JSXAttribute = 110, - JSXSpreadAttribute = 111, - JSXIdentifier = 112, - JSXSpreadChild = 113, - JSXText = 114, - TSThisParameter = 115, - TSEnumDeclaration = 116, - TSEnumBody = 117, - TSEnumMember = 118, - TSTypeAnnotation = 119, - TSLiteralType = 120, - TSConditionalType = 121, - TSUnionType = 122, - TSIntersectionType = 123, - TSParenthesizedType = 124, - TSTypeOperator = 125, - TSArrayType = 126, - TSIndexedAccessType = 127, - TSTupleType = 128, - TSNamedTupleMember = 129, - TSOptionalType = 130, - TSRestType = 131, - TSAnyKeyword = 132, - TSStringKeyword = 133, - TSBooleanKeyword = 134, - TSNumberKeyword = 135, - TSNeverKeyword = 136, - TSIntrinsicKeyword = 137, - TSUnknownKeyword = 138, - TSNullKeyword = 139, - TSUndefinedKeyword = 140, - TSVoidKeyword = 141, - TSSymbolKeyword = 142, - TSThisType = 143, - TSObjectKeyword = 144, - TSBigIntKeyword = 145, - TSTypeReference = 146, - TSQualifiedName = 147, - TSTypeParameterInstantiation = 148, - TSTypeParameter = 149, - TSTypeParameterDeclaration = 150, - TSTypeAliasDeclaration = 151, - TSClassImplements = 152, - TSInterfaceDeclaration = 153, - TSInterfaceBody = 154, - TSPropertySignature = 155, - TSIndexSignature = 156, - TSCallSignatureDeclaration = 157, - TSMethodSignature = 158, - TSConstructSignatureDeclaration = 159, - TSIndexSignatureName = 160, - TSInterfaceHeritage = 161, - TSTypePredicate = 162, - TSModuleDeclaration = 163, - TSModuleBlock = 164, - TSTypeLiteral = 165, - TSInferType = 166, - TSTypeQuery = 167, - TSImportType = 168, - TSImportTypeQualifiedName = 169, - TSFunctionType = 170, - TSConstructorType = 171, - TSMappedType = 172, - TSTemplateLiteralType = 173, - TSAsExpression = 174, - TSSatisfiesExpression = 175, - TSTypeAssertion = 176, - TSImportEqualsDeclaration = 177, - TSExternalModuleReference = 178, - TSNonNullExpression = 179, - Decorator = 180, - TSExportAssignment = 181, - TSNamespaceExportDeclaration = 182, - TSInstantiationExpression = 183, - JSDocNullableType = 184, - JSDocNonNullableType = 185, - JSDocUnknownType = 186, + UpdateExpression = 20, + UnaryExpression = 21, + BinaryExpression = 22, + PrivateInExpression = 23, + LogicalExpression = 24, + ConditionalExpression = 25, + AssignmentExpression = 26, + ArrayAssignmentTarget = 27, + ObjectAssignmentTarget = 28, + AssignmentTargetRest = 29, + AssignmentTargetWithDefault = 30, + AssignmentTargetPropertyIdentifier = 31, + AssignmentTargetPropertyProperty = 32, + SequenceExpression = 33, + Super = 34, + AwaitExpression = 35, + ChainExpression = 36, + ParenthesizedExpression = 37, + Directive = 38, + Hashbang = 39, + BlockStatement = 40, + VariableDeclaration = 41, + VariableDeclarator = 42, + EmptyStatement = 43, + ExpressionStatement = 44, + IfStatement = 45, + DoWhileStatement = 46, + WhileStatement = 47, + ForStatement = 48, + ForInStatement = 49, + ForOfStatement = 50, + ContinueStatement = 51, + BreakStatement = 52, + ReturnStatement = 53, + WithStatement = 54, + SwitchStatement = 55, + SwitchCase = 56, + LabeledStatement = 57, + ThrowStatement = 58, + TryStatement = 59, + CatchClause = 60, + CatchParameter = 61, + DebuggerStatement = 62, + AssignmentPattern = 63, + ObjectPattern = 64, + BindingProperty = 65, + ArrayPattern = 66, + BindingRestElement = 67, + Function = 68, + FormalParameters = 69, + FormalParameter = 70, + FunctionBody = 71, + ArrowFunctionExpression = 72, + YieldExpression = 73, + Class = 74, + ClassBody = 75, + MethodDefinition = 76, + PropertyDefinition = 77, + PrivateIdentifier = 78, + StaticBlock = 79, + AccessorProperty = 80, + ImportExpression = 81, + ImportDeclaration = 82, + ImportSpecifier = 83, + ImportDefaultSpecifier = 84, + ImportNamespaceSpecifier = 85, + WithClause = 86, + ImportAttribute = 87, + ExportNamedDeclaration = 88, + ExportDefaultDeclaration = 89, + ExportAllDeclaration = 90, + ExportSpecifier = 91, + V8IntrinsicExpression = 92, + BooleanLiteral = 93, + NullLiteral = 94, + NumericLiteral = 95, + StringLiteral = 96, + BigIntLiteral = 97, + RegExpLiteral = 98, + JSXElement = 99, + JSXOpeningElement = 100, + JSXClosingElement = 101, + JSXFragment = 102, + JSXOpeningFragment = 103, + JSXClosingFragment = 104, + JSXNamespacedName = 105, + JSXMemberExpression = 106, + JSXExpressionContainer = 107, + JSXEmptyExpression = 108, + JSXAttribute = 109, + JSXSpreadAttribute = 110, + JSXIdentifier = 111, + JSXSpreadChild = 112, + JSXText = 113, + TSThisParameter = 114, + TSEnumDeclaration = 115, + TSEnumBody = 116, + TSEnumMember = 117, + TSTypeAnnotation = 118, + TSLiteralType = 119, + TSConditionalType = 120, + TSUnionType = 121, + TSIntersectionType = 122, + TSParenthesizedType = 123, + TSTypeOperator = 124, + TSArrayType = 125, + TSIndexedAccessType = 126, + TSTupleType = 127, + TSNamedTupleMember = 128, + TSOptionalType = 129, + TSRestType = 130, + TSAnyKeyword = 131, + TSStringKeyword = 132, + TSBooleanKeyword = 133, + TSNumberKeyword = 134, + TSNeverKeyword = 135, + TSIntrinsicKeyword = 136, + TSUnknownKeyword = 137, + TSNullKeyword = 138, + TSUndefinedKeyword = 139, + TSVoidKeyword = 140, + TSSymbolKeyword = 141, + TSThisType = 142, + TSObjectKeyword = 143, + TSBigIntKeyword = 144, + TSTypeReference = 145, + TSQualifiedName = 146, + TSTypeParameterInstantiation = 147, + TSTypeParameter = 148, + TSTypeParameterDeclaration = 149, + TSTypeAliasDeclaration = 150, + TSClassImplements = 151, + TSInterfaceDeclaration = 152, + TSInterfaceBody = 153, + TSPropertySignature = 154, + TSIndexSignature = 155, + TSCallSignatureDeclaration = 156, + TSMethodSignature = 157, + TSConstructSignatureDeclaration = 158, + TSIndexSignatureName = 159, + TSInterfaceHeritage = 160, + TSTypePredicate = 161, + TSModuleDeclaration = 162, + TSModuleBlock = 163, + TSTypeLiteral = 164, + TSInferType = 165, + TSTypeQuery = 166, + TSImportType = 167, + TSImportTypeQualifiedName = 168, + TSFunctionType = 169, + TSConstructorType = 170, + TSMappedType = 171, + TSTemplateLiteralType = 172, + TSAsExpression = 173, + TSSatisfiesExpression = 174, + TSTypeAssertion = 175, + TSImportEqualsDeclaration = 176, + TSExternalModuleReference = 177, + TSNonNullExpression = 178, + Decorator = 179, + TSExportAssignment = 180, + TSNamespaceExportDeclaration = 181, + TSInstantiationExpression = 182, + JSDocNullableType = 183, + JSDocNonNullableType = 184, + JSDocUnknownType = 185, } /// Untyped AST Node Kind @@ -232,7 +231,6 @@ pub enum AstKind<'a> { NewExpression(&'a NewExpression<'a>) = AstType::NewExpression as u8, MetaProperty(&'a MetaProperty<'a>) = AstType::MetaProperty as u8, SpreadElement(&'a SpreadElement<'a>) = AstType::SpreadElement as u8, - Argument(&'a Argument<'a>) = AstType::Argument as u8, UpdateExpression(&'a UpdateExpression<'a>) = AstType::UpdateExpression as u8, UnaryExpression(&'a UnaryExpression<'a>) = AstType::UnaryExpression as u8, BinaryExpression(&'a BinaryExpression<'a>) = AstType::BinaryExpression as u8, @@ -452,7 +450,6 @@ impl GetSpan for AstKind<'_> { Self::NewExpression(it) => it.span(), Self::MetaProperty(it) => it.span(), Self::SpreadElement(it) => it.span(), - Self::Argument(it) => it.span(), Self::UpdateExpression(it) => it.span(), Self::UnaryExpression(it) => it.span(), Self::BinaryExpression(it) => it.span(), @@ -646,7 +643,6 @@ impl GetAddress for AstKind<'_> { Self::NewExpression(it) => Address::from_ref(it), Self::MetaProperty(it) => Address::from_ref(it), Self::SpreadElement(it) => Address::from_ref(it), - Self::Argument(it) => it.address(), Self::UpdateExpression(it) => Address::from_ref(it), Self::UnaryExpression(it) => Address::from_ref(it), Self::BinaryExpression(it) => Address::from_ref(it), @@ -918,11 +914,6 @@ impl<'a> AstKind<'a> { if let Self::SpreadElement(v) = self { Some(v) } else { None } } - #[inline] - pub fn as_argument(self) -> Option<&'a Argument<'a>> { - if let Self::Argument(v) = self { Some(v) } else { None } - } - #[inline] pub fn as_update_expression(self) -> Option<&'a UpdateExpression<'a>> { if let Self::UpdateExpression(v) = self { Some(v) } else { None } diff --git a/crates/oxc_ast_macros/Cargo.toml b/crates/oxc_ast_macros/Cargo.toml index 24d52ce4f1d2b..c1091bfeedde1 100644 --- a/crates/oxc_ast_macros/Cargo.toml +++ b/crates/oxc_ast_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_ast_macros" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_ast_visit/Cargo.toml b/crates/oxc_ast_visit/Cargo.toml index d88453dd1b412..38c1b3948972c 100644 --- a/crates/oxc_ast_visit/Cargo.toml +++ b/crates/oxc_ast_visit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_ast_visit" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_ast_visit/src/generated/visit.rs b/crates/oxc_ast_visit/src/generated/visit.rs index c85076294c15a..505ac2f601c19 100644 --- a/crates/oxc_ast_visit/src/generated/visit.rs +++ b/crates/oxc_ast_visit/src/generated/visit.rs @@ -1678,13 +1678,11 @@ pub mod walk { #[inline] pub fn walk_argument<'a, V: Visit<'a>>(visitor: &mut V, it: &Argument<'a>) { - let kind = AstKind::Argument(visitor.alloc(it)); - visitor.enter_node(kind); + // No `AstKind` for this type match it { Argument::SpreadElement(it) => visitor.visit_spread_element(it), match_expression!(Argument) => visitor.visit_expression(it.to_expression()), } - visitor.leave_node(kind); } #[inline] @@ -4259,7 +4257,14 @@ pub mod walk { #[inline] pub fn walk_arguments<'a, V: Visit<'a>>(visitor: &mut V, it: &Vec<'a, Argument<'a>>) { for el in it { - visitor.visit_argument(el); + match el { + oxc_ast::ast::Argument::SpreadElement(spread) => { + visitor.visit_spread_element(spread); + } + _ => { + visitor.visit_expression(el.to_expression()); + } + } } } diff --git a/crates/oxc_ast_visit/src/generated/visit_mut.rs b/crates/oxc_ast_visit/src/generated/visit_mut.rs index 4ca253155e2c4..601bb5b619c03 100644 --- a/crates/oxc_ast_visit/src/generated/visit_mut.rs +++ b/crates/oxc_ast_visit/src/generated/visit_mut.rs @@ -1694,13 +1694,11 @@ pub mod walk_mut { #[inline] pub fn walk_argument<'a, V: VisitMut<'a>>(visitor: &mut V, it: &mut Argument<'a>) { - let kind = AstType::Argument; - visitor.enter_node(kind); + // No `AstType` for this type match it { Argument::SpreadElement(it) => visitor.visit_spread_element(it), match_expression!(Argument) => visitor.visit_expression(it.to_expression_mut()), } - visitor.leave_node(kind); } #[inline] @@ -4489,7 +4487,14 @@ pub mod walk_mut { #[inline] pub fn walk_arguments<'a, V: VisitMut<'a>>(visitor: &mut V, it: &mut Vec<'a, Argument<'a>>) { for el in it { - visitor.visit_argument(el); + match el { + oxc_ast::ast::Argument::SpreadElement(spread) => { + visitor.visit_spread_element(spread); + } + _ => { + visitor.visit_expression(el.to_expression_mut()); + } + } } } diff --git a/crates/oxc_cfg/Cargo.toml b/crates/oxc_cfg/Cargo.toml index b846d69a24e43..f69dcd364a722 100644 --- a/crates/oxc_cfg/Cargo.toml +++ b/crates/oxc_cfg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_cfg" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_codegen/CHANGELOG.md b/crates/oxc_codegen/CHANGELOG.md index 820953654835a..08694e3146514 100644 --- a/crates/oxc_codegen/CHANGELOG.md +++ b/crates/oxc_codegen/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 🐛 Bug Fixes + +- 020aa4f codegen: Print space before `BindingRestElement` in `ObjectPattern` (#15315) (overlookmotel) + +### ⚡ Performance + +- ab4b12b codegen: Reduce branches printing `ObjectPattern` (#15316) (overlookmotel) + ## [0.96.0] - 2025-10-30 ### 🐛 Bug Fixes diff --git a/crates/oxc_codegen/Cargo.toml b/crates/oxc_codegen/Cargo.toml index 7fa8062f3ad43..edc8ce08ad41e 100644 --- a/crates/oxc_codegen/Cargo.toml +++ b/crates/oxc_codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_codegen" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_compat/CHANGELOG.md b/crates/oxc_compat/CHANGELOG.md index d2087d9fcb926..dfaa764af31a0 100644 --- a/crates/oxc_compat/CHANGELOG.md +++ b/crates/oxc_compat/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 🚀 Features + +- 2c15353 transformer: Warn top level await usage if not supported (#14276) (Copilot) +- aee6310 transformer: Add warning for arbitrary module namespace identifier names (#15035) (Copilot) + ## [0.95.0] - 2025-10-15 diff --git a/crates/oxc_compat/Cargo.toml b/crates/oxc_compat/Cargo.toml index fdf8010f5114b..36d2c2c054c4c 100644 --- a/crates/oxc_compat/Cargo.toml +++ b/crates/oxc_compat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_compat" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_data_structures/Cargo.toml b/crates/oxc_data_structures/Cargo.toml index 718a637941b3a..f9b53ab6c9469 100644 --- a/crates/oxc_data_structures/Cargo.toml +++ b/crates/oxc_data_structures/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_data_structures" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_diagnostics/Cargo.toml b/crates/oxc_diagnostics/Cargo.toml index a2254d8a354ee..9bc7d98f9cb34 100644 --- a/crates/oxc_diagnostics/Cargo.toml +++ b/crates/oxc_diagnostics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_diagnostics" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_ecmascript/CHANGELOG.md b/crates/oxc_ecmascript/CHANGELOG.md index 38dcf921a5603..f600786a26d4d 100644 --- a/crates/oxc_ecmascript/CHANGELOG.md +++ b/crates/oxc_ecmascript/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 🐛 Bug Fixes + +- 8b14ec9 minifier: Handle `{ __proto__: null } instanceof Object` correctly (#15217) (sapphi-red) + diff --git a/crates/oxc_ecmascript/Cargo.toml b/crates/oxc_ecmascript/Cargo.toml index 956b1f15d4512..e37889f303964 100644 --- a/crates/oxc_ecmascript/Cargo.toml +++ b/crates/oxc_ecmascript/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_ecmascript" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_estree/Cargo.toml b/crates/oxc_estree/Cargo.toml index 67140a79bd961..8dc6aa7f43dd2 100644 --- a/crates/oxc_estree/Cargo.toml +++ b/crates/oxc_estree/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_estree" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_formatter/examples/sort_imports.rs b/crates/oxc_formatter/examples/sort_imports.rs index 2dd502dc47bcf..fe4c27ef7ccb7 100644 --- a/crates/oxc_formatter/examples/sort_imports.rs +++ b/crates/oxc_formatter/examples/sort_imports.rs @@ -28,6 +28,7 @@ fn main() -> Result<(), String> { sort_side_effects, ignore_case, newlines_between, + groups: SortImports::default_groups(), }; // Read source file @@ -50,7 +51,7 @@ fn main() -> Result<(), String> { // Format the parsed code let options = FormatOptions { - experimental_sort_imports: Some(sort_imports_options), + experimental_sort_imports: Some(sort_imports_options.clone()), ..Default::default() }; diff --git a/crates/oxc_formatter/src/ast_nodes/generated/ast_nodes.rs b/crates/oxc_formatter/src/ast_nodes/generated/ast_nodes.rs index fb442d9150605..a1ee5e3462526 100644 --- a/crates/oxc_formatter/src/ast_nodes/generated/ast_nodes.rs +++ b/crates/oxc_formatter/src/ast_nodes/generated/ast_nodes.rs @@ -47,7 +47,6 @@ pub enum AstNodes<'a> { NewExpression(&'a AstNode<'a, NewExpression<'a>>), MetaProperty(&'a AstNode<'a, MetaProperty<'a>>), SpreadElement(&'a AstNode<'a, SpreadElement<'a>>), - Argument(&'a AstNode<'a, Argument<'a>>), UpdateExpression(&'a AstNode<'a, UpdateExpression<'a>>), UnaryExpression(&'a AstNode<'a, UnaryExpression<'a>>), BinaryExpression(&'a AstNode<'a, BinaryExpression<'a>>), @@ -240,7 +239,6 @@ impl<'a> AstNodes<'a> { Self::NewExpression(n) => n.span(), Self::MetaProperty(n) => n.span(), Self::SpreadElement(n) => n.span(), - Self::Argument(n) => n.parent.span(), Self::UpdateExpression(n) => n.span(), Self::UnaryExpression(n) => n.span(), Self::BinaryExpression(n) => n.span(), @@ -433,7 +431,6 @@ impl<'a> AstNodes<'a> { Self::NewExpression(n) => n.parent, Self::MetaProperty(n) => n.parent, Self::SpreadElement(n) => n.parent, - Self::Argument(n) => n.parent, Self::UpdateExpression(n) => n.parent, Self::UnaryExpression(n) => n.parent, Self::BinaryExpression(n) => n.parent, @@ -626,7 +623,6 @@ impl<'a> AstNodes<'a> { Self::NewExpression(_) => "NewExpression", Self::MetaProperty(_) => "MetaProperty", Self::SpreadElement(_) => "SpreadElement", - Self::Argument(_) => "Argument", Self::UpdateExpression(_) => "UpdateExpression", Self::UnaryExpression(_) => "UnaryExpression", Self::BinaryExpression(_) => "BinaryExpression", @@ -1895,7 +1891,7 @@ impl<'a> AstNode<'a, SpreadElement<'a>> { impl<'a> AstNode<'a, Argument<'a>> { #[inline] pub fn as_ast_nodes(&self) -> &AstNodes<'a> { - let parent = self.allocator.alloc(AstNodes::Argument(transmute_self(self))); + let parent = self.parent; let node = match self.inner { Argument::SpreadElement(s) => AstNodes::SpreadElement(self.allocator.alloc(AstNode { inner: s.as_ref(), diff --git a/crates/oxc_formatter/src/ast_nodes/generated/format.rs b/crates/oxc_formatter/src/ast_nodes/generated/format.rs index 65a91d048e2d6..9518bccf6f6b3 100644 --- a/crates/oxc_formatter/src/ast_nodes/generated/format.rs +++ b/crates/oxc_formatter/src/ast_nodes/generated/format.rs @@ -830,7 +830,7 @@ impl<'a> Format<'a> for AstNode<'a, Argument<'a>> { #[inline] fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { let allocator = self.allocator; - let parent = allocator.alloc(AstNodes::Argument(transmute_self(self))); + let parent = self.parent; match self.inner { Argument::SpreadElement(inner) => allocator .alloc(AstNode:: { @@ -2853,14 +2853,21 @@ impl<'a> Format<'a> for AstNode<'a, RegExpLiteral<'a>> { impl<'a> Format<'a> for AstNode<'a, JSXElement<'a>> { fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - if format_type_cast_comment_node(self, false, f)? { + let is_suppressed = f.comments().is_suppressed(self.span().start); + if !is_suppressed && format_type_cast_comment_node(self, false, f)? { return Ok(()); } - let needs_parentheses = self.needs_parentheses(f); + let needs_parentheses = !is_suppressed && self.needs_parentheses(f); if needs_parentheses { "(".fmt(f)?; } - let result = self.write(f); + let result = if is_suppressed { + self.format_leading_comments(f)?; + FormatSuppressedNode(self.span()).fmt(f)?; + self.format_trailing_comments(f) + } else { + self.write(f) + }; if needs_parentheses { ")".fmt(f)?; } @@ -2892,14 +2899,21 @@ impl<'a> Format<'a> for AstNode<'a, JSXClosingElement<'a>> { impl<'a> Format<'a> for AstNode<'a, JSXFragment<'a>> { fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - if format_type_cast_comment_node(self, false, f)? { + let is_suppressed = f.comments().is_suppressed(self.span().start); + if !is_suppressed && format_type_cast_comment_node(self, false, f)? { return Ok(()); } - let needs_parentheses = self.needs_parentheses(f); + let needs_parentheses = !is_suppressed && self.needs_parentheses(f); if needs_parentheses { "(".fmt(f)?; } - let result = self.write(f); + let result = if is_suppressed { + self.format_leading_comments(f)?; + FormatSuppressedNode(self.span()).fmt(f)?; + self.format_trailing_comments(f) + } else { + self.write(f) + }; if needs_parentheses { ")".fmt(f)?; } @@ -4202,11 +4216,13 @@ impl<'a> Format<'a> for AstNode<'a, TSTypeAliasDeclaration<'a>> { impl<'a> Format<'a> for AstNode<'a, TSClassImplements<'a>> { fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { let is_suppressed = f.comments().is_suppressed(self.span().start); - self.format_leading_comments(f)?; - let result = - if is_suppressed { FormatSuppressedNode(self.span()).fmt(f) } else { self.write(f) }; - self.format_trailing_comments(f)?; - result + if is_suppressed { + self.format_leading_comments(f)?; + FormatSuppressedNode(self.span()).fmt(f)?; + self.format_trailing_comments(f) + } else { + self.write(f) + } } } @@ -4802,11 +4818,13 @@ impl<'a> Format<'a> for AstNode<'a, TSNonNullExpression<'a>> { impl<'a> Format<'a> for AstNode<'a, Decorator<'a>> { fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { let is_suppressed = f.comments().is_suppressed(self.span().start); - self.format_leading_comments(f)?; - let result = - if is_suppressed { FormatSuppressedNode(self.span()).fmt(f) } else { self.write(f) }; - self.format_trailing_comments(f)?; - result + if is_suppressed { + self.format_leading_comments(f)?; + FormatSuppressedNode(self.span()).fmt(f)?; + self.format_trailing_comments(f) + } else { + self.write(f) + } } } diff --git a/crates/oxc_formatter/src/ir_transform/sort_imports/import_unit.rs b/crates/oxc_formatter/src/ir_transform/sort_imports/import_unit.rs index 690b8df752e43..e96ac3b0b91e1 100644 --- a/crates/oxc_formatter/src/ir_transform/sort_imports/import_unit.rs +++ b/crates/oxc_formatter/src/ir_transform/sort_imports/import_unit.rs @@ -1,240 +1,360 @@ -use std::path::Path; +use std::{borrow::Cow, path::Path}; use cow_utils::CowUtils; use phf::phf_set; use crate::{formatter::format_element::FormatElement, options}; -use super::source_line::{ImportLine, SourceLine}; +use super::source_line::{ImportLineMetadata, SourceLine}; -#[derive(Debug)] -pub struct ImportUnits(pub Vec); +#[derive(Debug, Clone)] +pub struct SortableImport<'a> { + pub leading_lines: Vec, + pub import_line: SourceLine, + pub group_idx: usize, + pub normalized_source: Cow<'a, str>, + pub is_ignored: bool, +} + +impl<'a> SortableImport<'a> { + pub fn new(leading_lines: Vec, import_line: SourceLine) -> Self { + Self { + leading_lines, + import_line, + // These will be computed by `collect_sort_keys()` + group_idx: 0, + normalized_source: Cow::Borrowed(""), + is_ignored: false, + } + } -impl IntoIterator for ImportUnits { - type Item = SortableImport; - type IntoIter = std::vec::IntoIter; + /// Pre-compute keys needed for sorting. + #[must_use] + pub fn collect_sort_keys( + mut self, + elements: &'a [FormatElement], + options: &options::SortImports, + ) -> Self { + let SourceLine::Import( + _, + ImportLineMetadata { + source_idx, + is_side_effect, + is_type_import, + has_default_specifier, + has_namespace_specifier, + has_named_specifier, + }, + ) = &self.import_line + else { + unreachable!("`import_line` must be of type `SourceLine::Import`."); + }; - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() + let source = extract_source_text(elements, *source_idx); + + // Pre-compute normalized source for case-insensitive comparison + self.normalized_source = + if options.ignore_case { source.cow_to_lowercase() } else { Cow::Borrowed(source) }; + + // Create group matcher from import characteristics + let matcher = ImportGroupMatcher { + is_side_effect: *is_side_effect, + is_type_import: *is_type_import, + is_style_import: is_style(source), + has_default_specifier: *has_default_specifier, + has_namespace_specifier: *has_namespace_specifier, + has_named_specifier: *has_named_specifier, + path_kind: to_path_kind(source), + }; + self.group_idx = matcher.match_group(&options.groups); + + // TODO: Check ignore comments? + self.is_ignored = !options.sort_side_effects && *is_side_effect; + + self } } -impl ImportUnits { - pub fn sort_imports(&mut self, elements: &[FormatElement], options: options::SortImports) { - let imports_len = self.0.len(); +// --- - // Perform sorting only if needed - if imports_len < 2 { - return; - } +/// Helper for matching imports to configured groups. +/// +/// Contains all characteristics of an import needed to determine which group it belongs to, +/// such as whether it's a type import, side-effect import, style import, and what kind of path it uses. +#[derive(Debug, Clone)] +struct ImportGroupMatcher { + is_side_effect: bool, + is_type_import: bool, + is_style_import: bool, + has_default_specifier: bool, + has_namespace_specifier: bool, + has_named_specifier: bool, + path_kind: ImportPathKind, +} - // Separate imports into: - // - sortable: indices of imports that should be sorted - // - fixed: indices of imports that should be ignored - // - e.g. side-effect imports when `sort_side_effects: false`, with ignore comments, etc... - let mut sortable_indices = vec![]; - let mut fixed_indices = vec![]; - for (idx, si) in self.0.iter().enumerate() { - if si.is_ignored(options) { - fixed_indices.push(idx); - } else { - sortable_indices.push(idx); +impl ImportGroupMatcher { + /// Match this import against the configured groups and return the group index. + /// Returns the index of the first matching group, or the index of "unknown" group if present, + /// or the last index + 1 if no match found. + /// + /// Matching prioritizes more specific group names (e.g., "type-external" over "type-import"). + pub fn match_group(&self, groups: &[Vec]) -> usize { + let possible_names = self.generate_group_names(); + let mut unknown_index = None; + + // Try each possible name in order (most specific first) + for possible_name in &possible_names { + for (group_idx, group) in groups.iter().enumerate() { + for group_name in group { + // Check if this is the "unknown" group + if group_name == "unknown" { + unknown_index = Some(group_idx); + } + + // Check if this possible name matches this group + if possible_name == group_name { + return group_idx; + } + } } } - // Sort indices by comparing their corresponding import groups, then sources - sortable_indices.sort_by(|&a, &b| { - let metadata_a = self.0[a].get_metadata(elements); - let metadata_b = self.0[b].get_metadata(elements); + // No match found - use "unknown" group if present, otherwise return last + 1 + unknown_index.unwrap_or(groups.len()) + } - // First, compare by group - let group_ord = metadata_a.group().cmp(&metadata_b.group()); - if group_ord != std::cmp::Ordering::Equal { - return if options.order.is_desc() { group_ord.reverse() } else { group_ord }; + /// Generate all possible group names for this import, ordered by specificity. + /// Returns group names in the format used by perfectionist. + /// + /// Perfectionist format examples: + /// - `type-external` - type modifier + path selector + /// - `value-internal` - value modifier + path selector + /// - `type-import` - type modifier + import selector + /// - `external` - path selector only + fn generate_group_names(&self) -> Vec { + let selectors = self.selectors(); + let modifiers = self.modifiers(); + + let mut group_names = Vec::new(); + + // Most specific: type/value modifier combined with path selectors + // e.g., "type-external", "value-internal", "type-parent" + let type_or_value_modifier = if self.is_type_import { "type" } else { "value" }; + + for selector in &selectors { + // Skip the generic "type" selector since it's already in the modifier + if matches!(selector, ImportSelector::Type) { + continue; } - // Within the same group, compare by source - let source_ord = if options.ignore_case { - metadata_a.source.cow_to_lowercase().cmp(&metadata_b.source.cow_to_lowercase()) - } else { - metadata_a.source.cmp(metadata_b.source) - }; - - if options.order.is_desc() { source_ord.reverse() } else { source_ord } - }); - - // Create a permutation map - let mut permutation = vec![0; imports_len]; - let mut sortable_iter = sortable_indices.into_iter(); - for (idx, perm) in permutation.iter_mut().enumerate() { - // NOTE: This is O(n), but side-effect imports are usually few - if fixed_indices.contains(&idx) { - *perm = idx; - } else if let Some(sorted_idx) = sortable_iter.next() { - *perm = sorted_idx; + // For path-based selectors, combine with type/value modifier + if matches!( + selector, + ImportSelector::Builtin + | ImportSelector::External + | ImportSelector::Internal + | ImportSelector::Parent + | ImportSelector::Sibling + | ImportSelector::Index + ) { + let name = format!("{}-{}", type_or_value_modifier, selector.as_str()); + group_names.push(name); } } - debug_assert!( - permutation.iter().copied().collect::>().len() == imports_len, - "`permutation` must be a valid permutation, all indices must be unique." - ); - - // Apply permutation in-place using cycle decomposition - let mut visited = vec![false; imports_len]; - for idx in 0..imports_len { - // Already visited or already in the correct position - if visited[idx] || permutation[idx] == idx { + + // Add other modifier combinations for special selectors + for selector in &selectors { + // Skip path-based selectors (already handled above) and "import" selector + if matches!( + selector, + ImportSelector::Builtin + | ImportSelector::External + | ImportSelector::Internal + | ImportSelector::Parent + | ImportSelector::Sibling + | ImportSelector::Index + | ImportSelector::Import + | ImportSelector::Type + ) { continue; } - // Follow the cycle - let mut current = idx; - loop { - let next = permutation[current]; - visited[current] = true; - if next == idx { - break; - } - self.0.swap(current, next); - current = next; + + // For special selectors like side-effect, side-effect-style, style + // combine with relevant modifiers + for modifier in &modifiers { + let name = format!("{}-{}", modifier.as_str(), selector.as_str()); + group_names.push(name); } + + // Selector-only name + group_names.push(selector.as_str().to_string()); } - debug_assert!(self.0.len() == imports_len, "Length must remain the same after sorting."); - } -} -// --- + // Add "type-import" or "value-import" or just "import" + if self.is_type_import { + group_names.push("type-import".to_string()); + } -#[derive(Debug, Clone)] -pub struct SortableImport { - pub leading_lines: Vec, - pub import_line: SourceLine, -} + group_names.push("import".to_string()); -impl SortableImport { - pub fn new(leading_lines: Vec, import_line: SourceLine) -> Self { - Self { leading_lines, import_line } + group_names } - /// Get all import metadata in one place. - pub fn get_metadata<'a>(&self, elements: &'a [FormatElement]) -> ImportMetadata<'a> { - let SourceLine::Import(ImportLine { - source_idx, - is_side_effect, - is_type_import, - has_default_specifier, - has_namespace_specifier, - has_named_specifier, - .. - }) = &self.import_line - else { - unreachable!("`import_line` must be of type `SourceLine::Import`."); - }; + /// Compute all selectors for this import, ordered from most to least specific. + fn selectors(&self) -> Vec { + let mut selectors = Vec::new(); - // Strip quotes and params - let source = match &elements[*source_idx] { - FormatElement::Text { text, .. } => *text, - _ => unreachable!( - "`source_idx` must point to either `LocatedTokenText` or `Text` in the `elements`." - ), - }; - let source = source.trim_matches('"').trim_matches('\''); - let source = source.split('?').next().unwrap_or(source); - - ImportMetadata { - source, - is_side_effect: *is_side_effect, - is_type_import: *is_type_import, - is_style_import: is_style(source), - has_default_specifier: *has_default_specifier, - has_namespace_specifier: *has_namespace_specifier, - has_named_specifier: *has_named_specifier, - path_kind: ImportPathKind::new(source), + // Most specific selectors first + if self.is_side_effect && self.is_style_import { + selectors.push(ImportSelector::SideEffectStyle); + } + if self.is_side_effect { + selectors.push(ImportSelector::SideEffect); + } + if self.is_style_import { + selectors.push(ImportSelector::Style); } + // Type selector + if self.is_type_import { + selectors.push(ImportSelector::Type); + } + // Path-based selectors + match self.path_kind { + ImportPathKind::Index => selectors.push(ImportSelector::Index), + ImportPathKind::Sibling => selectors.push(ImportSelector::Sibling), + ImportPathKind::Parent => selectors.push(ImportSelector::Parent), + ImportPathKind::Internal => selectors.push(ImportSelector::Internal), + ImportPathKind::Builtin => selectors.push(ImportSelector::Builtin), + ImportPathKind::External => selectors.push(ImportSelector::External), + ImportPathKind::Unknown => {} + } + // Catch-all selector + selectors.push(ImportSelector::Import); + + selectors } - /// Check if this import should be ignored (not sorted). - pub fn is_ignored(&self, options: options::SortImports) -> bool { - match self.import_line { - SourceLine::Import(ImportLine { is_side_effect, .. }) => { - // TODO: Check ignore comments? - !options.sort_side_effects && is_side_effect - } - _ => unreachable!("`import_line` must be of type `SourceLine::Import`."), + /// Compute all modifiers for this import. + fn modifiers(&self) -> Vec { + let mut modifiers = Vec::new(); + + if self.is_side_effect { + modifiers.push(ImportModifier::SideEffect); + } + if self.is_type_import { + modifiers.push(ImportModifier::Type); + } else { + modifiers.push(ImportModifier::Value); } + if self.has_default_specifier { + modifiers.push(ImportModifier::Default); + } + if self.has_namespace_specifier { + modifiers.push(ImportModifier::Wildcard); + } + if self.has_named_specifier { + modifiers.push(ImportModifier::Named); + } + + modifiers } } -/// Import group classification for sorting. -/// -/// NOTE: The order of variants in this enum determines the sort order when comparing groups. -/// Groups are sorted in the order they appear here (TypeImport first, Unknown last). -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum ImportGroup { - /// Type-only imports from builtin or external packages - /// e.g., `import type { Foo } from 'react'` - TypeImport, - /// Value imports from Node.js builtin modules or external packages - /// Corresponds to `['value-builtin', 'value-external']` in perfectionist - /// e.g., `import fs from 'node:fs'`, `import React from 'react'` - ValueBuiltinOrExternal, - /// Type-only imports from internal modules - /// e.g., `import type { Config } from '~/types'`, `import type { User } from '@/models'` - TypeInternal, - /// Value imports from internal modules - /// e.g., `import { config } from '~/config'`, `import { utils } from '@/utils'` - ValueInternal, - /// Type-only imports from relative paths (parent, sibling, or index) - /// Corresponds to `['type-parent', 'type-sibling', 'type-index']` in perfectionist - /// e.g., `import type { Props } from '../types'`, `import type { State } from './types'` - TypeRelative, - /// Value imports from relative paths (parent, sibling, or index) - /// Corresponds to `['value-parent', 'value-sibling', 'value-index']` in perfectionist - /// e.g., `import { helper } from '../parent'`, `import { Component } from './sibling'` - ValueRelative, - /// Unclassified imports (fallback) - Unknown, +/// Selector types for import categorization. +/// Selectors identify the type or location of an import. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ImportSelector { + /// Type-only imports (`import type { ... }`) + Type, + /// Side-effect style imports (CSS, SCSS, etc. without bindings) + SideEffectStyle, + /// Side-effect imports (imports without bindings) + SideEffect, + /// Style file imports (CSS, SCSS, etc.) + Style, + /// Index file imports (`./`, `../`) + Index, + /// Sibling module imports (`./foo`) + Sibling, + /// Parent module imports (`../foo`) + Parent, + /// Internal module imports (matching internal patterns like `~/`, `@/`) + Internal, + /// Built-in module imports (`node:fs`, `fs`) + Builtin, + /// External module imports (from node_modules) + External, + /// Catch-all selector + Import, } -/// Metadata about an import for sorting purposes. -#[derive(Debug, Clone)] -pub struct ImportMetadata<'a> { - pub source: &'a str, - pub is_side_effect: bool, - pub is_type_import: bool, - pub is_style_import: bool, - pub has_default_specifier: bool, - pub has_namespace_specifier: bool, - pub has_named_specifier: bool, - pub path_kind: ImportPathKind, +impl ImportSelector { + /// Returns the string representation used in group names. + const fn as_str(self) -> &'static str { + match self { + Self::Type => "type", + Self::SideEffectStyle => "side-effect-style", + Self::SideEffect => "side-effect", + Self::Style => "style", + Self::Index => "index", + Self::Sibling => "sibling", + Self::Parent => "parent", + Self::Internal => "internal", + Self::Builtin => "builtin", + Self::External => "external", + Self::Import => "import", + } + } } -impl ImportMetadata<'_> { - /// Determine the import group based on metadata. - pub fn group(&self) -> ImportGroup { - if self.is_type_import { - return match self.path_kind { - ImportPathKind::Builtin | ImportPathKind::External => ImportGroup::TypeImport, - ImportPathKind::Internal => ImportGroup::TypeInternal, - ImportPathKind::Parent | ImportPathKind::Sibling | ImportPathKind::Index => { - ImportGroup::TypeRelative - } - ImportPathKind::Unknown => ImportGroup::Unknown, - }; - } +/// Modifier types for import categorization. +/// Modifiers describe characteristics of how an import is declared. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ImportModifier { + /// Side-effect imports + SideEffect, + /// Type-only imports + Type, + /// Value imports (non-type) + Value, + /// Default specifier present + Default, + /// Namespace/wildcard specifier present (`* as`) + Wildcard, + /// Named specifiers present + Named, +} - match self.path_kind { - ImportPathKind::Builtin | ImportPathKind::External => { - ImportGroup::ValueBuiltinOrExternal - } - ImportPathKind::Internal => ImportGroup::ValueInternal, - ImportPathKind::Parent | ImportPathKind::Sibling | ImportPathKind::Index => { - ImportGroup::ValueRelative - } - ImportPathKind::Unknown => ImportGroup::Unknown, +impl ImportModifier { + /// Returns the string representation used in group names. + const fn as_str(self) -> &'static str { + match self { + Self::SideEffect => "side-effect", + Self::Type => "type", + Self::Value => "value", + Self::Default => "default", + Self::Wildcard => "wildcard", + Self::Named => "named", } } } +// --- + +/// Extract the import source text from format elements. +/// +/// This removes quotes and query parameters from the source string. +/// For example, `"./foo?bar"` becomes `./foo`. +fn extract_source_text<'a>(elements: &'a [FormatElement], source_idx: usize) -> &'a str { + let source = match &elements[source_idx] { + FormatElement::Text { text, .. } => *text, + _ => unreachable!("`source_idx` must point to the `Text` in the `elements`."), + }; + + let source = source.trim_matches('"').trim_matches('\''); + source.split('?').next().unwrap_or(source) +} + // spellchecker:off static STYLE_EXTENSIONS: phf::Set<&'static str> = phf_set! { "css", @@ -247,6 +367,7 @@ static STYLE_EXTENSIONS: phf::Set<&'static str> = phf_set! { }; // spellchecker:on +/// Check if an import source is a style file based on its extension. fn is_style(source: &str) -> bool { Path::new(source) .extension() @@ -264,8 +385,12 @@ static NODE_BUILTINS: phf::Set<&'static str> = phf_set! { "zlib", }; -/// Classification of import path types for grouping. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Check if an import source is a Node.js or Bun builtin module. +fn is_builtin(source: &str) -> bool { + source.starts_with("node:") || source.starts_with("bun:") || NODE_BUILTINS.contains(source) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum ImportPathKind { /// Node.js builtin module (e.g., `node:fs`, `fs`) Builtin, @@ -280,33 +405,30 @@ pub enum ImportPathKind { /// Index file import (e.g., `./`, `../`) Index, /// Unknown or unclassified + #[default] Unknown, } -impl ImportPathKind { - fn new(source: &str) -> Self { - if source.starts_with("node:") - || source.starts_with("bun:") - || NODE_BUILTINS.contains(source) - { - return Self::Builtin; - } +/// Determine the path kind for an import source. +fn to_path_kind(source: &str) -> ImportPathKind { + if is_builtin(source) { + return ImportPathKind::Builtin; + } - if source.starts_with('.') { - if source == "." || source == ".." || source.ends_with('/') { - return Self::Index; - } - if source.starts_with("../") { - return Self::Parent; - } - return Self::Sibling; + if source.starts_with('.') { + if source == "." || source == ".." || source.ends_with('/') { + return ImportPathKind::Index; } - - // TODO: This can be changed via `options.internalPattern` - if source.starts_with('~') || source.starts_with('@') { - return Self::Internal; + if source.starts_with("../") { + return ImportPathKind::Parent; } + return ImportPathKind::Sibling; + } - Self::External + // TODO: This can be changed via `options.internalPattern` + if source.starts_with('~') || source.starts_with('@') { + return ImportPathKind::Internal; } + + ImportPathKind::External } diff --git a/crates/oxc_formatter/src/ir_transform/sort_imports/mod.rs b/crates/oxc_formatter/src/ir_transform/sort_imports/mod.rs index b0ab6b2e60f18..342914a9ff898 100644 --- a/crates/oxc_formatter/src/ir_transform/sort_imports/mod.rs +++ b/crates/oxc_formatter/src/ir_transform/sort_imports/mod.rs @@ -136,7 +136,7 @@ impl SortImportsTransform { // Finally, sort import lines within each chunk. // After sorting, flatten everything back to `FormatElement`s. - let mut next_elements = vec![]; + let mut next_elements = Vec::with_capacity(prev_elements.len()); let mut chunks_iter = chunks.into_iter().enumerate().peekable(); while let Some((idx, chunk)) = chunks_iter.next() { @@ -166,29 +166,31 @@ impl SortImportsTransform { // // const YET_ANOTHER_BOUNDARY = true; // ``` - let (mut import_units, trailing_lines) = chunk.into_import_units(prev_elements); - import_units.sort_imports(prev_elements, self.options); + let (mut sortable_imports, trailing_lines) = + chunk.into_import_units(prev_elements, &self.options); + + sort_imports(&mut sortable_imports, &self.options); // Output sorted import units let preserve_empty_line = self.options.partition_by_newline; - let mut prev_group = None; - for sortable_import in import_units { + let mut prev_group_idx = None; + for sorted_import in sortable_imports { // Insert blank line between different groups if enabled if self.options.newlines_between { - let current_group = sortable_import.get_metadata(prev_elements).group(); - if let Some(prev) = prev_group - && prev != current_group + let current_group_idx = sorted_import.group_idx; + if let Some(prev_idx) = prev_group_idx + && prev_idx != current_group_idx { next_elements.push(FormatElement::Line(LineMode::Empty)); } - prev_group = Some(current_group); + prev_group_idx = Some(current_group_idx); } // Output leading lines and import line - for line in sortable_import.leading_lines { + for line in sorted_import.leading_lines { line.write(prev_elements, &mut next_elements, preserve_empty_line); } - sortable_import.import_line.write(prev_elements, &mut next_elements, false); + sorted_import.import_line.write(prev_elements, &mut next_elements, false); } // And output trailing lines // @@ -227,3 +229,77 @@ impl SortImportsTransform { Document::from(next_elements) } } + +/// Sort a list of imports in-place according to the given options. +fn sort_imports(imports: &mut [SortableImport], options: &options::SortImports) { + let imports_len = imports.len(); + + // Perform sorting only if needed + if imports_len < 2 { + return; + } + + // Separate imports into: + // - sortable: indices of imports that should be sorted + // - fixed: indices of imports that should be ignored + // - e.g. side-effect imports when `sort_side_effects: false`, with ignore comments, etc... + let mut sortable_indices = vec![]; + let mut fixed_indices = vec![]; + for (idx, si) in imports.iter().enumerate() { + if si.is_ignored { + fixed_indices.push(idx); + } else { + sortable_indices.push(idx); + } + } + + // Sort indices by comparing their corresponding import groups, then sources. + sortable_indices.sort_by(|&a, &b| { + // Always sort by groups array order first + let group_ord = imports[a].group_idx.cmp(&imports[b].group_idx); + if group_ord != std::cmp::Ordering::Equal { + return group_ord; + } + + // Within the same group, sort by source respecting the order option + let source_ord = imports[a].normalized_source.cmp(&imports[b].normalized_source); + if options.order.is_desc() { source_ord.reverse() } else { source_ord } + }); + + // Create a permutation map + let mut permutation = vec![0; imports_len]; + let mut sortable_iter = sortable_indices.into_iter(); + for (idx, perm) in permutation.iter_mut().enumerate() { + // NOTE: This is O(n), but side-effect imports are usually few + if fixed_indices.contains(&idx) { + *perm = idx; + } else if let Some(sorted_idx) = sortable_iter.next() { + *perm = sorted_idx; + } + } + debug_assert!( + permutation.iter().copied().collect::>().len() == imports_len, + "`permutation` must be a valid permutation, all indices must be unique." + ); + + // Apply permutation in-place using cycle decomposition + let mut visited = vec![false; imports_len]; + for idx in 0..imports_len { + // Already visited or already in the correct position + if visited[idx] || permutation[idx] == idx { + continue; + } + // Follow the cycle + let mut current = idx; + loop { + let next = permutation[current]; + visited[current] = true; + if next == idx { + break; + } + imports.swap(current, next); + current = next; + } + } + debug_assert!(imports.len() == imports_len, "Length must remain the same after sorting."); +} diff --git a/crates/oxc_formatter/src/ir_transform/sort_imports/partitioned_chunk.rs b/crates/oxc_formatter/src/ir_transform/sort_imports/partitioned_chunk.rs index 049f3e5c7a778..5b1191c920e9a 100644 --- a/crates/oxc_formatter/src/ir_transform/sort_imports/partitioned_chunk.rs +++ b/crates/oxc_formatter/src/ir_transform/sort_imports/partitioned_chunk.rs @@ -1,9 +1,6 @@ -use crate::formatter::format_element::FormatElement; +use crate::{formatter::format_element::FormatElement, options}; -use super::{ - import_unit::{ImportUnits, SortableImport}, - source_line::SourceLine, -}; +use super::{import_unit::SortableImport, source_line::SourceLine}; #[derive(Debug, Clone)] pub enum PartitionedChunk { @@ -41,24 +38,29 @@ impl PartitionedChunk { matches!(self, Self::Imports(lines) if lines.is_empty()) } + /// Convert this import chunk into a list of sortable import units and trailing lines. + /// Returns a tuple of `(sortable_imports, trailing_lines)`. #[must_use] - pub fn into_import_units(self, _elements: &[FormatElement]) -> (ImportUnits, Vec) { + pub fn into_import_units<'a>( + self, + elements: &'a [FormatElement], + options: &options::SortImports, + ) -> (Vec>, Vec) { let Self::Imports(lines) = self else { unreachable!( "`into_import_units()` must be called on `PartitionedChunk::Imports` only." ); }; - let mut units = vec![]; - + let mut sortable_imports = vec![]; let mut current_leading_lines = vec![]; for line in lines { match line { SourceLine::Import(..) => { - units.push(SortableImport::new( - std::mem::take(&mut current_leading_lines), - line, - )); + sortable_imports.push( + SortableImport::new(std::mem::take(&mut current_leading_lines), line) + .collect_sort_keys(elements, options), + ); } SourceLine::CommentOnly(..) | SourceLine::Empty => { current_leading_lines.push(line); @@ -74,6 +76,6 @@ impl PartitionedChunk { // Any remaining comments/lines are trailing let trailing_lines = current_leading_lines; - (ImportUnits(units), trailing_lines) + (sortable_imports, trailing_lines) } } diff --git a/crates/oxc_formatter/src/ir_transform/sort_imports/source_line.rs b/crates/oxc_formatter/src/ir_transform/sort_imports/source_line.rs index d3a057d684f7c..02d5fcd1566fa 100644 --- a/crates/oxc_formatter/src/ir_transform/sort_imports/source_line.rs +++ b/crates/oxc_formatter/src/ir_transform/sort_imports/source_line.rs @@ -8,25 +8,6 @@ use crate::{ }, }; -/// Import line information. -#[derive(Debug, Clone)] -pub struct ImportLine { - /// Range of elements in the original `elements` slice. - pub elements_range: Range, - /// Index of the import source in the original `elements` slice. - pub source_idx: usize, - /// Whether this is a side-effect-only import (e.g., `import "foo"`). - pub is_side_effect: bool, - /// Whether this is a type-only import (e.g., `import type { Foo } from "foo"`). - pub is_type_import: bool, - /// Whether this import has a default specifier (e.g., `import Foo from "foo"`). - pub has_default_specifier: bool, - /// Whether this import has a namespace specifier (e.g., `import * as Foo from "foo"`). - pub has_namespace_specifier: bool, - /// Whether this import has named specifiers (e.g., `import { foo } from "foo"`). - pub has_named_specifier: bool, -} - #[derive(Debug, Clone)] pub enum SourceLine { /// Line that contains an import statement. @@ -34,7 +15,7 @@ pub enum SourceLine { /// And also may have trailing comments like `import ...; // ...`. /// /// Never be a boundary. - Import(ImportLine), + Import(Range, ImportLineMetadata), /// Empty line. /// May be used as a boundary if `options.partition_by_newline` is true. Empty, @@ -148,15 +129,17 @@ impl SourceLine { if has_import && let Some(source_idx) = source_idx { // TODO: Check line has trailing ignore comment? - return SourceLine::Import(ImportLine { - elements_range: range, - source_idx, - is_side_effect, - is_type_import, - has_default_specifier, - has_namespace_specifier, - has_named_specifier, - }); + return SourceLine::Import( + range, + ImportLineMetadata { + source_idx, + is_side_effect, + is_type_import, + has_default_specifier, + has_namespace_specifier, + has_named_specifier, + }, + ); } // Otherwise, this line is neither of: @@ -180,8 +163,8 @@ impl SourceLine { next_elements.push(FormatElement::Line(LineMode::Empty)); } } - SourceLine::Import(ImportLine { elements_range, .. }) => { - for idx in elements_range.clone() { + SourceLine::Import(range, _) => { + for idx in range.clone() { next_elements.push(prev_elements[idx].clone()); } // Always use hard line break after import statement. @@ -196,3 +179,20 @@ impl SourceLine { } } } + +/// Import line metadata extracted during parsing. +#[derive(Debug, Clone)] +pub struct ImportLineMetadata { + /// Index of the import source in the original `elements` slice. + pub source_idx: usize, + /// Whether this is a side-effect-only import (e.g., `import "foo"`). + pub is_side_effect: bool, + /// Whether this is a type-only import (e.g., `import type { Foo } from "foo"`). + pub is_type_import: bool, + /// Whether this import has a default specifier (e.g., `import Foo from "foo"`). + pub has_default_specifier: bool, + /// Whether this import has a namespace specifier (e.g., `import * as Foo from "foo"`). + pub has_namespace_specifier: bool, + /// Whether this import has named specifiers (e.g., `import { foo } from "foo"`). + pub has_named_specifier: bool, +} diff --git a/crates/oxc_formatter/src/lib.rs b/crates/oxc_formatter/src/lib.rs index 010271c97a8e1..bbe3d01654754 100644 --- a/crates/oxc_formatter/src/lib.rs +++ b/crates/oxc_formatter/src/lib.rs @@ -80,7 +80,7 @@ impl<'a> Formatter<'a> { let source_text = program.source_text; self.source_text = source_text; - let experimental_sort_imports = self.options.experimental_sort_imports; + let experimental_sort_imports = self.options.experimental_sort_imports.clone(); let mut context = FormatContext::new( program.source_text, diff --git a/crates/oxc_formatter/src/options.rs b/crates/oxc_formatter/src/options.rs index f6273dcdff038..7f7a9149b0f4d 100644 --- a/crates/oxc_formatter/src/options.rs +++ b/crates/oxc_formatter/src/options.rs @@ -963,7 +963,7 @@ impl fmt::Display for EmbeddedLanguageFormatting { // --- -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct SortImports { /// Partition imports by newlines. /// Default is `false`. @@ -986,6 +986,9 @@ pub struct SortImports { /// /// NOTE: Cannot be used together with `partition_by_newline: true`. pub newlines_between: bool, + /// Groups configuration for organizing imports. + /// Each inner `Vec` represents a group, and multiple group names in the same `Vec` are treated as one. + pub groups: Vec>, } impl Default for SortImports { @@ -997,10 +1000,30 @@ impl Default for SortImports { order: SortOrder::default(), ignore_case: true, newlines_between: true, + groups: Self::default_groups(), } } } +impl SortImports { + pub fn default_groups() -> Vec> { + vec![ + vec!["type-import".to_string()], + vec!["value-builtin".to_string(), "value-external".to_string()], + vec!["type-internal".to_string()], + vec!["value-internal".to_string()], + vec!["type-parent".to_string(), "type-sibling".to_string(), "type-index".to_string()], + vec![ + "value-parent".to_string(), + "value-sibling".to_string(), + "value-index".to_string(), + ], + // vec!["ts-equals-import".to_string()], + vec!["unknown".to_string()], + ] + } +} + #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] pub enum SortOrder { /// Sort in ascending order (A-Z). diff --git a/crates/oxc_formatter/src/parentheses/expression.rs b/crates/oxc_formatter/src/parentheses/expression.rs index a760ee81a3646..8696fa263fb26 100644 --- a/crates/oxc_formatter/src/parentheses/expression.rs +++ b/crates/oxc_formatter/src/parentheses/expression.rs @@ -14,14 +14,56 @@ use crate::{ Format, ast_nodes::{AstNode, AstNodes}, formatter::Formatter, - utils::expression::ExpressionLeftSide, + utils::{expression::ExpressionLeftSide, is_expression_used_as_call_argument}, write::{BinaryLikeExpression, should_flatten}, }; use super::NeedsParentheses; -impl NeedsParentheses<'_> for AstNode<'_, Expression<'_>> { - fn needs_parentheses(&self, f: &Formatter<'_, '_>) -> bool { +// Helper function to check if a MemberExpression has a CallExpression in its object chain +fn member_has_call_object(member: &MemberExpression) -> bool { + match member { + MemberExpression::ComputedMemberExpression(m) => expression_is_or_contains_call(&m.object), + MemberExpression::StaticMemberExpression(m) => expression_is_or_contains_call(&m.object), + MemberExpression::PrivateFieldExpression(m) => expression_is_or_contains_call(&m.object), + } +} + +// Helper function to check if an Expression is or contains a CallExpression +fn expression_is_or_contains_call(expr: &Expression) -> bool { + match expr { + Expression::CallExpression(_) => true, + Expression::TaggedTemplateExpression(t) => { + // Tagged templates like x()`` where the tag is a call expression + expression_is_or_contains_call(&t.tag) + } + Expression::ComputedMemberExpression(m) => expression_is_or_contains_call(&m.object), + Expression::StaticMemberExpression(m) => expression_is_or_contains_call(&m.object), + Expression::PrivateFieldExpression(m) => expression_is_or_contains_call(&m.object), + _ => false, + } +} + +// Helper function to check if an expression can be used unparenthesized in a decorator +// Based on Prettier's isDecoratorMemberExpression +fn is_decorator_member_expression(expr: &Expression) -> bool { + match expr { + Expression::Identifier(_) => true, + Expression::StaticMemberExpression(m) if !m.optional => { + // Non-optional static member access like a.b.c + is_decorator_member_expression(&m.object) + } + Expression::ComputedMemberExpression(m) if !m.optional => { + // Non-optional computed member access like a[0] or a["prop"] + // Note: Prettier allows this without parentheses + is_decorator_member_expression(&m.object) + } + _ => false, + } +} + +impl<'a> NeedsParentheses<'a> for AstNode<'a, Expression<'a>> { + fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { match self.as_ast_nodes() { AstNodes::BooleanLiteral(it) => it.needs_parentheses(f), AstNodes::NullLiteral(it) => it.needs_parentheses(f), @@ -66,10 +108,7 @@ impl NeedsParentheses<'_> for AstNode<'_, Expression<'_>> { AstNodes::StaticMemberExpression(it) => it.needs_parentheses(f), AstNodes::ComputedMemberExpression(it) => it.needs_parentheses(f), AstNodes::PrivateFieldExpression(it) => it.needs_parentheses(f), - _ => { - // TODO: incomplete - false - } + _ => false, } } } @@ -85,19 +124,78 @@ impl NeedsParentheses<'_> for AstNode<'_, IdentifierReference<'_>> { matches!(self.parent, AstNodes::ForOfStatement(stmt) if !stmt.r#await && stmt.left.span().contains_inclusive(self.span)) } "let" => { - // Walk up ancestors to find the relevant context for `let` keyword - for parent in self.ancestors() { + // Special handling for 'let' identifier to match Prettier's behavior + + // Check for-statement contexts first + let mut parent = self.parent; + loop { match parent { - AstNodes::ExpressionStatement(_) => return false, + AstNodes::Program(_) | AstNodes::ExpressionStatement(_) => return false, AstNodes::ForOfStatement(stmt) => { return stmt.left.span().contains_inclusive(self.span); } AstNodes::TSSatisfiesExpression(expr) => { return expr.expression.span() == self.span(); } - _ => {} + AstNodes::ForInStatement(stmt) => { + return stmt.left.span().contains_inclusive(self.span); + } + AstNodes::ForStatement(stmt) => { + return stmt + .init + .as_ref() + .is_some_and(|init| init.span().contains_inclusive(self.span)); + } + _ => parent = parent.parent(), } } + + // Check if 'let' is used as the object of a computed member expression + if let AstNodes::ComputedMemberExpression(member) = self.parent + && member.object.span() == self.span() + { + // Check if this is used as a call argument - if so, no parentheses needed + let mut check_parent = member.parent; + loop { + match check_parent { + // Direct argument to call/new - no parens needed + AstNodes::CallExpression(call) => { + // Check if member is directly an argument + if call + .arguments + .iter() + .any(|arg| arg.span().contains_inclusive(member.span())) + { + return false; + } + } + AstNodes::NewExpression(new_expr) => { + // Check if member or its parent assignment is an argument + if new_expr.arguments.iter().any(|arg| { + // Check if the argument contains our member expression + arg.span().contains_inclusive(member.span()) + }) { + return false; + } + } + // If we hit an assignment that's an argument, check further + AstNodes::AssignmentExpression(_) => { + check_parent = check_parent.parent(); + continue; + } + _ => break, + } + break; + } + + // Need parentheses when at the start of a statement + return is_first_in_statement( + member.span(), + member.parent, + FirstInStatementMode::ExpressionStatementOrArrow, + ); + } + false } name => { @@ -229,19 +327,37 @@ impl NeedsParentheses<'_> for AstNode<'_, ArrayExpression<'_>> { } } -impl NeedsParentheses<'_> for AstNode<'_, ObjectExpression<'_>> { - fn needs_parentheses(&self, f: &Formatter<'_, '_>) -> bool { - if f.comments().is_type_cast_node(self) { +impl<'a> NeedsParentheses<'a> for AstNode<'a, ObjectExpression<'a>> { + fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { + let span = self.span(); + let parent = self.parent; + + // Object expressions inside template literals don't need parentheses + // They are unambiguously in expression context and cannot be confused with block statements + if matches!(parent, AstNodes::TemplateLiteral(_)) { return false; } - let parent = self.parent; - is_class_extends(self.span, parent) - || is_first_in_statement( - self.span, - parent, - FirstInStatementMode::ExpressionStatementOrArrow, - ) + // Object expressions don't need parentheses when used as function arguments + if is_expression_used_as_call_argument(span, parent) { + return false; + } + + // Object expressions don't need parentheses when used as the expression of a cast + // that is itself used as an argument + if let AstNodes::TSAsExpression(as_expr) = parent + && is_expression_used_as_call_argument(as_expr.span, as_expr.parent) + { + return false; + } + if let AstNodes::TSSatisfiesExpression(satisfies_expr) = parent + && is_expression_used_as_call_argument(satisfies_expr.span, satisfies_expr.parent) + { + return false; + } + + is_class_extends(parent, span) + || is_first_in_statement(span, parent, FirstInStatementMode::ExpressionStatementOrArrow) } } @@ -252,17 +368,41 @@ impl NeedsParentheses<'_> for AstNode<'_, TaggedTemplateExpression<'_>> { } } -impl NeedsParentheses<'_> for AstNode<'_, MemberExpression<'_>> { +impl<'a> NeedsParentheses<'a> for AstNode<'a, MemberExpression<'a>> { #[inline] - fn needs_parentheses(&self, f: &Formatter<'_, '_>) -> bool { + fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { + // Member expressions with call expression or another member expression with call as object + // need parentheses when used as the callee of a new expression: new (a().b)() + if let AstNodes::NewExpression(new_expr) = self.parent { + let span = self.span(); + if new_expr.callee.span() == span { + // Check if the object of this member expression needs parens + return member_has_call_object(self); + } + } false } } -impl NeedsParentheses<'_> for AstNode<'_, ComputedMemberExpression<'_>> { - fn needs_parentheses(&self, f: &Formatter<'_, '_>) -> bool { - matches!(self.parent, AstNodes::NewExpression(_)) - && (self.optional || member_chain_callee_needs_parens(&self.object)) +impl<'a> NeedsParentheses<'a> for AstNode<'a, ComputedMemberExpression<'a>> { + fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { + // Computed member expressions with call expression objects need parentheses + // when used as the callee of a new expression: new (a()[0])() + if let AstNodes::NewExpression(new_expr) = self.parent { + let span = self.span(); + if new_expr.callee.span() == span { + // Check if the object is or contains a call expression + return expression_is_or_contains_call(&self.object); + } + } + + // Computed member expressions need parentheses in decorators + // Example: @(decorators[0]) and @(decorators?.[0]) + if let AstNodes::Decorator(_) = self.parent { + return true; + } + + false } } @@ -296,7 +436,11 @@ impl NeedsParentheses<'_> for AstNode<'_, CallExpression<'_>> { } match self.parent { - AstNodes::NewExpression(_) => true, + AstNodes::NewExpression(_) => { + // Call expressions don't need parentheses when used as new expression arguments + !is_expression_used_as_call_argument(self.span(), self.parent) + } + AstNodes::Decorator(_) => !is_decorator_member_expression(&self.callee), AstNodes::ExportDefaultDeclaration(_) => { let callee = &self.callee(); let callee_span = callee.span(); @@ -314,13 +458,22 @@ impl NeedsParentheses<'_> for AstNode<'_, CallExpression<'_>> { } } -impl NeedsParentheses<'_> for AstNode<'_, NewExpression<'_>> { - fn needs_parentheses(&self, f: &Formatter<'_, '_>) -> bool { - if f.comments().is_type_cast_node(self) { - return false; +impl<'a> NeedsParentheses<'a> for AstNode<'a, NewExpression<'a>> { + fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { + let span = self.span(); + let parent = self.parent; + + // New expressions with call expressions as callees need parentheses when being called + if let AstNodes::CallExpression(call) = parent + && call.callee.span() == span + { + // Only need parens if the new expression's callee is a call expression + if let Expression::CallExpression(_) = self.callee { + return true; + } } - is_class_extends(self.span, self.parent) + is_class_extends(parent, span) } } @@ -360,9 +513,9 @@ impl NeedsParentheses<'_> for AstNode<'_, UnaryExpression<'_>> { && parent_operator == operator } // A user typing `!foo instanceof Bar` probably intended `!(foo instanceof Bar)`, - // so format to `(!foo) instance Bar` to what is really happening - // A user typing `!foo in bar` probably intended `!(foo instanceof Bar)`, - // so format to `(!foo) in bar` to what is really happening + // so format to `(!foo) instanceof Bar` to show what is really happening + // A user typing `!foo in bar` probably intended `!(foo in bar)`, + // so format to `(!foo) in bar` to show what is really happening AstNodes::BinaryExpression(e) if e.operator().is_relational() => true, _ => unary_like_expression_needs_parens(UnaryLike::UnaryExpression(self)), } @@ -405,6 +558,8 @@ fn is_in_for_initializer(expr: &AstNode<'_, BinaryExpression<'_>>) -> bool { .is_some_and(|init| init.span().contains_inclusive(expr.span)); } AstNodes::ForInStatement(stmt) => { + // Only add parentheses for in expressions on the LEFT side of for-in + // The right side doesn't need parentheses for disambiguation return stmt.left.span().contains_inclusive(expr.span); } AstNodes::Program(_) => { @@ -417,15 +572,18 @@ fn is_in_for_initializer(expr: &AstNode<'_, BinaryExpression<'_>>) -> bool { false } -impl NeedsParentheses<'_> for AstNode<'_, PrivateInExpression<'_>> { +impl<'a> NeedsParentheses<'a> for AstNode<'a, PrivateInExpression<'a>> { #[inline] - fn needs_parentheses(&self, f: &Formatter<'_, '_>) -> bool { - if f.comments().is_type_cast_node(self) { - return false; + fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { + // Need parentheses in class extends: class A extends (#x in obj) {} + if is_class_extends(self.parent, self.span) { + return true; } - is_class_extends(self.span, self.parent) - || matches!(self.parent, AstNodes::UnaryExpression(_)) + // Need parentheses when used as argument to unary operators + // !(#target in this) - parentheses required + // The 'in' operator has lower precedence than unary operators + matches!(self.parent, AstNodes::UnaryExpression(_)) } } @@ -469,6 +627,23 @@ impl NeedsParentheses<'_> for AstNode<'_, ConditionalExpression<'_>> { ) { return true; } + + // Conditional expressions need parentheses when used as the object of a member expression + // BUT only when there's a property access that follows + if let AstNodes::StaticMemberExpression(member) = parent + && member.object.span() == self.span() + { + // Check if there's actually a property access or if it's just grouping + // Only add parentheses if the member expression has properties being accessed + return true; + } + if let AstNodes::ComputedMemberExpression(member) = parent + && member.object.span() == self.span() + { + // Same logic for computed member expressions + return true; + } + if let AstNodes::ConditionalExpression(e) = parent { e.test.span() == self.span() } else { @@ -488,16 +663,20 @@ impl NeedsParentheses<'_> for AstNode<'_, Function<'_>> { } let parent = self.parent; - matches!( - parent, - AstNodes::CallExpression(_) - | AstNodes::NewExpression(_) - | AstNodes::TaggedTemplateExpression(_) - ) || is_first_in_statement( - self.span, - parent, - FirstInStatementMode::ExpressionOrExportDefault, - ) + + // Function expressions in call/new arguments don't need parentheses + // unless they are the callee + if let AstNodes::CallExpression(call) = parent { + // Only add parentheses if this function is the callee, not an argument + return call.callee.span() == self.span; + } + + matches!(parent, AstNodes::NewExpression(_) | AstNodes::TaggedTemplateExpression(_)) + || is_first_in_statement( + self.span, + parent, + FirstInStatementMode::ExpressionOrExportDefault, + ) } } @@ -550,15 +729,14 @@ impl NeedsParentheses<'_> for AstNode<'_, AssignmentExpression<'_>> { true } - // `interface A { [a = 1]; }` not need parens - AstNodes::TSPropertySignature(_) | // Never need parentheses in these contexts: + // - `interface { [a = 1]; }` and `class { [a = 1]; }` (TS property signatures) // - `a = (b = c)` = nested assignments don't need extra parens - AstNodes::AssignmentExpression(_) => false, + AstNodes::TSPropertySignature(_) | AstNodes::AssignmentExpression(_) => false, // Computed member expressions: need parens when assignment is the object - // - `obj[(a = b)]` parens needed for explicitness + // - `obj[a = b]` = no parens needed for property // - `(a = b)[obj]` = parens needed for object - AstNodes::ComputedMemberExpression(member) => true, + AstNodes::ComputedMemberExpression(member) => member.object.span() == self.span(), // For statements, no parens needed in initializer or update sections: // - `for (a = 1; ...; a = 2) {}` = both assignments don't need parens AstNodes::ForStatement(stmt) => { @@ -585,13 +763,23 @@ impl NeedsParentheses<'_> for AstNode<'_, SequenceExpression<'_>> { return false; } + // Special handling for prettier-ignore comments + if f.comments().has_leading_own_line_comment(self.span().start) { + return false; + } + match self.parent { - AstNodes::ReturnStatement(_) - | AstNodes::ThrowStatement(_) + // Return and throw statements handle their own parentheses + // Only add parentheses if there are comments in the sequence + AstNodes::ReturnStatement(_) | AstNodes::ThrowStatement(_) => { + f.comments().has_comment_in_span(self.span()) + } // There's a precedence for writing `x++, y++` - | AstNodes::ForStatement(_) - | AstNodes::SequenceExpression(_) => false, - AstNodes::ExpressionStatement(stmt) => !stmt.is_arrow_function_body(), + AstNodes::ForStatement(_) | AstNodes::SequenceExpression(_) => false, + // Arrow function concise bodies don't need parens (unless there are comments) + AstNodes::ExpressionStatement(stmt) => { + !stmt.is_arrow_function_body() || f.comments().has_comment_in_span(self.span()) + } _ => true, } } @@ -607,19 +795,22 @@ impl NeedsParentheses<'_> for AstNode<'_, AwaitExpression<'_>> { } } -impl NeedsParentheses<'_> for AstNode<'_, ChainExpression<'_>> { - fn needs_parentheses(&self, f: &Formatter<'_, '_>) -> bool { - if f.comments().is_type_cast_node(self) { +impl<'a> NeedsParentheses<'a> for AstNode<'a, ChainExpression<'a>> { + fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { + let span = self.span(); + + // Chain expressions don't need parentheses when used as call arguments + if is_expression_used_as_call_argument(span, self.parent) { return false; } match self.parent { - AstNodes::NewExpression(_) => true, AstNodes::CallExpression(call) => !call.optional, AstNodes::StaticMemberExpression(member) => !member.optional, AstNodes::ComputedMemberExpression(member) => { !member.optional && member.object.span() == self.span() } + AstNodes::NewExpression(_) | AstNodes::TSNonNullExpression(_) => true, _ => false, } } @@ -630,24 +821,35 @@ impl NeedsParentheses<'_> for AstNode<'_, Class<'_>> { if self.r#type() != ClassType::ClassExpression { return false; } + let span = self.span(); + let parent = self.parent; + + // Check if this class is the left side of a division operator + // This prevents ASI ambiguity when formatting: (class{}) / foo + // Without parentheses, a line break before the division could cause: + // x = class{} + // / foo <- Parser might try to interpret / as start of regex or statement + if let AstNodes::BinaryExpression(binary) = parent + && matches!(binary.operator, BinaryOperator::Division) + && binary.left.span() == span + { + return true; + } - if f.comments().is_type_cast_node(self) { + // Decorated class expressions need parentheses when used in extends clause + if !self.decorators.is_empty() && is_class_extends(parent, span) { + return true; + } + + // Class expressions don't need parentheses when used as function arguments + if is_expression_used_as_call_argument(span, parent) { return false; } - let parent = self.parent; match parent { - AstNodes::CallExpression(_) - | AstNodes::NewExpression(_) - | AstNodes::ExportDefaultDeclaration(_) => true, - + AstNodes::ExportDefaultDeclaration(_) => true, _ => { - (is_class_extends(self.span, self.parent) && !self.decorators.is_empty()) - || is_first_in_statement( - self.span, - parent, - FirstInStatementMode::ExpressionOrExportDefault, - ) + is_first_in_statement(span, parent, FirstInStatementMode::ExpressionOrExportDefault) } } } @@ -659,12 +861,9 @@ impl NeedsParentheses<'_> for AstNode<'_, ParenthesizedExpression<'_>> { } } -impl NeedsParentheses<'_> for AstNode<'_, ArrowFunctionExpression<'_>> { - fn needs_parentheses(&self, f: &Formatter<'_, '_>) -> bool { - if f.comments().is_type_cast_node(self) { - return false; - } - +impl<'a> NeedsParentheses<'a> for AstNode<'a, ArrowFunctionExpression<'a>> { + fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { + let span = self.span(); let parent = self.parent; if matches!( parent, @@ -679,9 +878,15 @@ impl NeedsParentheses<'_> for AstNode<'_, ArrowFunctionExpression<'_>> { return true; } if let AstNodes::ConditionalExpression(e) = parent { - e.test.span() == self.span() + e.test.without_parentheses().span() == span + } else if let AstNodes::CallExpression(call) = parent { + // Only add parentheses if this arrow function is the callee, not an argument + call.callee.span() == span + } else if let AstNodes::NewExpression(new_expr) = parent { + // Only add parentheses if this arrow function is the callee, not an argument + new_expr.callee.span() == span } else { - update_or_lower_expression_needs_parens(self.span(), parent) + update_or_lower_expression_needs_parens(span, parent) } } } @@ -755,7 +960,14 @@ impl NeedsParentheses<'_> for AstNode<'_, TSTypeAssertion<'_>> { AstNodes::BinaryExpression(binary) => { matches!(binary.operator, BinaryOperator::ShiftLeft) } - _ => type_cast_like_needs_parens(self.span(), self.parent), + _ => { + // Type assertions don't need parentheses when used as function arguments + // This allows for "argument hugging" behavior like `func(obj)` + if is_expression_used_as_call_argument(self.span(), self.parent) { + return false; + } + type_cast_like_needs_parens(self.span(), self.parent) + } } } } @@ -787,14 +999,15 @@ fn type_cast_like_needs_parens(span: Span, parent: &AstNodes<'_>) -> bool { AstNodes::AssignmentExpression(assignment) => { assignment.left.span() == span } - _ => is_class_extends(span, parent), + _ => is_class_extends(parent, span), } } -impl NeedsParentheses<'_> for AstNode<'_, TSNonNullExpression<'_>> { - fn needs_parentheses(&self, f: &Formatter<'_, '_>) -> bool { +impl<'a> NeedsParentheses<'a> for AstNode<'a, TSNonNullExpression<'a>> { + fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { + let span = self.span(); let parent = self.parent; - is_class_extends(self.span, parent) + is_class_extends(parent, span) || (matches!(parent, AstNodes::NewExpression(_)) && member_chain_callee_needs_parens(&self.expression)) } @@ -814,7 +1027,10 @@ impl NeedsParentheses<'_> for AstNode<'_, TSInstantiationExpression<'_>> { } fn binary_like_needs_parens(binary_like: BinaryLikeExpression<'_, '_>) -> bool { - let parent = match binary_like.parent() { + let span = binary_like.span(); + let parent_nodes = binary_like.parent(); + + let parent = match parent_nodes { AstNodes::TSAsExpression(_) | AstNodes::TSSatisfiesExpression(_) | AstNodes::TSTypeAssertion(_) @@ -823,11 +1039,23 @@ fn binary_like_needs_parens(binary_like: BinaryLikeExpression<'_, '_>) -> bool { | AstNodes::TSNonNullExpression(_) | AstNodes::SpreadElement(_) | AstNodes::JSXSpreadAttribute(_) - | AstNodes::CallExpression(_) - | AstNodes::NewExpression(_) | AstNodes::ChainExpression(_) | AstNodes::StaticMemberExpression(_) | AstNodes::TaggedTemplateExpression(_) => return true, + AstNodes::CallExpression(_) => { + // Binary expressions don't need parentheses when used as function arguments + if is_expression_used_as_call_argument(span, parent_nodes) { + return false; + } + return true; + } + AstNodes::NewExpression(_) => { + // Binary expressions don't need parentheses when used as constructor arguments + if is_expression_used_as_call_argument(span, parent_nodes) { + return false; + } + return true; + } AstNodes::ComputedMemberExpression(computed) => { return computed.object.span() == binary_like.span(); } @@ -843,6 +1071,9 @@ fn binary_like_needs_parens(binary_like: BinaryLikeExpression<'_, '_>) -> bool { let parent_operator = parent.operator(); let operator = binary_like.operator(); + + // Only cache span calculation for multiple uses + let parent_precedence = parent_operator.precedence(); let precedence = operator.precedence(); @@ -852,7 +1083,9 @@ fn binary_like_needs_parens(binary_like: BinaryLikeExpression<'_, '_>) -> bool { return true; } - let is_right = parent.right().span() == binary_like.span(); + // Cache span for multiple comparisons to avoid recalculation + let binary_span = binary_like.span(); + let is_right = parent.right().span() == binary_span; // `a ** b ** c` if is_right && parent_precedence == precedence { @@ -924,17 +1157,27 @@ fn update_or_lower_expression_needs_parens(span: Span, parent: &AstNodes<'_>) -> if matches!( parent, AstNodes::TSNonNullExpression(_) - | AstNodes::CallExpression(_) - | AstNodes::NewExpression(_) | AstNodes::StaticMemberExpression(_) | AstNodes::TaggedTemplateExpression(_) - ) || is_class_extends(span, parent) + ) || is_class_extends(parent, span) { return true; } - if let AstNodes::ComputedMemberExpression(computed_member_expr) = parent { - return computed_member_expr.object.span() == span; + // Special handling for computed member expressions + if let AstNodes::ComputedMemberExpression(computed) = parent { + // Only need parentheses if this expression is the object, not the property + // For a?.[++x], ++x is the property and doesn't need parentheses + return computed.object.span() == span; + } + + // Check call expressions and new expressions, but allow expressions used as arguments + if matches!(parent, AstNodes::CallExpression(_) | AstNodes::NewExpression(_)) { + // Don't add parentheses when used as function/constructor arguments + if is_expression_used_as_call_argument(span, parent) { + return false; + } + return true; } false @@ -983,13 +1226,22 @@ fn is_first_in_statement( return true; } AstNodes::StaticMemberExpression(_) + | AstNodes::TemplateLiteral(_) | AstNodes::TaggedTemplateExpression(_) | AstNodes::ChainExpression(_) - | AstNodes::CallExpression(_) - | AstNodes::NewExpression(_) | AstNodes::TSAsExpression(_) | AstNodes::TSSatisfiesExpression(_) | AstNodes::TSNonNullExpression(_) => {} + AstNodes::CallExpression(call) => { + if call.callee.span() != current_span { + break; + } + } + AstNodes::NewExpression(new_expr) => { + if new_expr.callee.span() != current_span { + break; + } + } AstNodes::SequenceExpression(sequence) => { if sequence.expressions.first().unwrap().span() != current_span { break; @@ -1067,12 +1319,17 @@ fn ts_as_or_satisfies_needs_parens( AstNodes::ExportDefaultDeclaration(_) => matches!(inner, Expression::FunctionExpression(_) | Expression::ClassExpression(_)), _ => { + // Type assertions don't need parentheses when used as function arguments + // This allows for "argument hugging" behavior like `func(obj as Type)` + if is_expression_used_as_call_argument(span, parent) { + return false; + } type_cast_like_needs_parens(span, parent) } } } -fn is_class_extends(span: Span, parent: &AstNodes<'_>) -> bool { +fn is_class_extends(parent: &AstNodes<'_>, span: Span) -> bool { if let AstNodes::Class(c) = parent { return c.super_class.as_ref().is_some_and(|c| c.span() == span); } @@ -1080,7 +1337,7 @@ fn is_class_extends(span: Span, parent: &AstNodes<'_>) -> bool { } fn jsx_element_or_fragment_needs_paren(span: Span, parent: &AstNodes<'_>) -> bool { - if is_class_extends(span, parent) { + if is_class_extends(parent, span) { return true; } @@ -1098,11 +1355,13 @@ fn jsx_element_or_fragment_needs_paren(span: Span, parent: &AstNodes<'_>) -> boo | AstNodes::UnaryExpression(_) | AstNodes::TSNonNullExpression(_) | AstNodes::SpreadElement(_) - | AstNodes::CallExpression(_) - | AstNodes::NewExpression(_) | AstNodes::TaggedTemplateExpression(_) | AstNodes::JSXSpreadAttribute(_) | AstNodes::JSXSpreadChild(_) => true, + AstNodes::CallExpression(_) | AstNodes::NewExpression(_) => { + // JSX elements don't need parentheses when used as function/constructor arguments + !is_expression_used_as_call_argument(span, parent) + } _ => false, } } diff --git a/crates/oxc_formatter/src/service/oxfmtrc.rs b/crates/oxc_formatter/src/service/oxfmtrc.rs index a409a519b6f83..31f0714b2acdd 100644 --- a/crates/oxc_formatter/src/service/oxfmtrc.rs +++ b/crates/oxc_formatter/src/service/oxfmtrc.rs @@ -332,6 +332,8 @@ impl Oxfmtrc { }), ignore_case: sort_imports_config.ignore_case, newlines_between: sort_imports_config.newlines_between, + // TODO: Make this configurable later + groups: SortImports::default_groups(), }); } diff --git a/crates/oxc_formatter/src/utils/assignment_like.rs b/crates/oxc_formatter/src/utils/assignment_like.rs index bd03fa00ca102..62ab0ed2e20e9 100644 --- a/crates/oxc_formatter/src/utils/assignment_like.rs +++ b/crates/oxc_formatter/src/utils/assignment_like.rs @@ -811,7 +811,33 @@ impl<'a> Format<'a> for AssignmentLike<'a, '_> { } }); - write!(f, [format_content]) + // Check if the right-hand side contains ASI-risky division patterns + // If so, use RemoveSoftLinesBuffer to prevent line breaks that could cause + // non-idempotent formatting due to ASI ambiguity + let should_force_inline = match self { + AssignmentLike::AssignmentExpression(assignment) => { + crate::write::has_asi_risky_division_pattern(&assignment.right) + } + AssignmentLike::VariableDeclarator(declarator) => declarator + .init + .as_ref() + .is_some_and(|init| crate::write::has_asi_risky_division_pattern(init)), + _ => false, + }; + + if should_force_inline { + write!( + f, + [format_once(|f| { + use crate::formatter::buffer::RemoveSoftLinesBuffer; + let mut buffer = RemoveSoftLinesBuffer::new(f); + let mut formatter = Formatter::new(&mut buffer); + write!(formatter, [format_content]) + })] + ) + } else { + write!(f, [format_content]) + } } } diff --git a/crates/oxc_formatter/src/utils/call_expression.rs b/crates/oxc_formatter/src/utils/call_expression.rs index 4bb43c2dafd94..a1abbabe4bb9b 100644 --- a/crates/oxc_formatter/src/utils/call_expression.rs +++ b/crates/oxc_formatter/src/utils/call_expression.rs @@ -36,8 +36,11 @@ pub fn is_test_call_expression(call: &AstNode>) -> bool { match (args.next(), args.next(), args.next()) { (Some(argument), None, None) if arguments.len() == 1 => { if is_angular_test_wrapper(call) && { - if let AstNodes::CallExpression(call) = call.grand_parent() { - is_test_call_expression(call) + // After removal of AstKind::Argument, the parent of a CallExpression + // that's used as an argument is now directly the parent CallExpression, + // not parent.parent(). + if let AstNodes::CallExpression(parent_call) = call.parent { + is_test_call_expression(parent_call) } else { false } diff --git a/crates/oxc_formatter/src/utils/jsx.rs b/crates/oxc_formatter/src/utils/jsx.rs index 4fd8c89437323..f0a6323c32527 100644 --- a/crates/oxc_formatter/src/utils/jsx.rs +++ b/crates/oxc_formatter/src/utils/jsx.rs @@ -79,6 +79,8 @@ pub fn get_wrap_state(parent: &AstNodes<'_>) -> WrapState { match parent { AstNodes::ArrayExpression(_) + | AstNodes::CallExpression(_) + | AstNodes::NewExpression(_) | AstNodes::JSXAttribute(_) | AstNodes::JSXExpressionContainer(_) | AstNodes::ConditionalExpression(_) => WrapState::NoWrap, @@ -89,9 +91,6 @@ pub fn get_wrap_state(parent: &AstNodes<'_>) -> WrapState { WrapState::WrapOnBreak } } - AstNodes::Argument(argument) if matches!(argument.parent, AstNodes::CallExpression(_)) => { - WrapState::NoWrap - } AstNodes::ExpressionStatement(stmt) => { // `() =>
` // ^^^^^^^^^^^ diff --git a/crates/oxc_formatter/src/utils/member_chain/chain_member.rs b/crates/oxc_formatter/src/utils/member_chain/chain_member.rs index a3d19a2aceaa0..1e01e465b07af 100644 --- a/crates/oxc_formatter/src/utils/member_chain/chain_member.rs +++ b/crates/oxc_formatter/src/utils/member_chain/chain_member.rs @@ -59,6 +59,37 @@ impl ChainMember<'_, '_> { pub const fn is_computed_expression(&self) -> bool { matches!(self, Self::ComputedMember { .. }) } + + /// Check if this member has a trailing line comment on the same line + pub fn has_same_line_trailing_comment(&self, f: &Formatter<'_, '_>) -> bool { + // Only check for trailing comments on static members for now + // to avoid breaking up other constructs + match self { + Self::StaticMember(e) => { + let member_end = e.property().span.end; + let source = f.source_text(); + + // Look for the rest of the line after this member + let rest_of_line = &source[member_end as usize..]; + + // Find the end of the current line + let line_end_pos = rest_of_line.find('\n').unwrap_or(rest_of_line.len()); + let line_content = &rest_of_line[..line_end_pos]; + + // Check if there's a line comment on this line + // Also check that it's not just whitespace before the comment + if let Some(comment_pos) = line_content.find("//") { + let before_comment = &line_content[..comment_pos]; + + before_comment.chars().all(char::is_whitespace) && !before_comment.is_empty() + } else { + false + } + } + // Don't break groups for other member types for now + _ => false, + } + } } impl<'a> Format<'a> for ChainMember<'a, '_> { diff --git a/crates/oxc_formatter/src/utils/member_chain/mod.rs b/crates/oxc_formatter/src/utils/member_chain/mod.rs index 1808ef9b1af84..a4b8786cc773b 100644 --- a/crates/oxc_formatter/src/utils/member_chain/mod.rs +++ b/crates/oxc_formatter/src/utils/member_chain/mod.rs @@ -46,7 +46,7 @@ impl<'a, 'b> MemberChain<'a, 'b> { // `flattened_items` now contains only the nodes that should have a sequence of // `[ StaticMemberExpression -> AnyNode + CallExpression ]` let tail_groups = - compute_remaining_groups(chain_members.drain(remaining_members_start_index..)); + compute_remaining_groups(chain_members.drain(remaining_members_start_index..), f); let head_group = MemberChainGroup::from(chain_members); let mut member_chain = Self { head: head_group, tail: tail_groups, root: call_expression }; @@ -301,39 +301,55 @@ fn get_split_index_of_head_and_tail_groups(members: &[ChainMember<'_, '_>]) -> u /// computes groups coming after the first group fn compute_remaining_groups<'a, 'b>( members: impl IntoIterator>, + f: &Formatter<'_, 'a>, ) -> TailChainGroups<'a, 'b> { let mut has_seen_call_expression = false; + let mut has_trailing_comment = false; let mut groups_builder = MemberChainGroupsBuilder::default(); for member in members { + // Check if previous member had a trailing comment + // If so, we should start a new group + let should_break_group = has_seen_call_expression || has_trailing_comment; + match member { // [0] should be appended at the end of the group instead of the // beginning of the next one ChainMember::ComputedMember(_) if is_computed_array_member_access(&member) => { - groups_builder.start_or_continue_group(member); + // Always append to the current group, don't start a new one + groups_builder.start_or_continue_group(member.clone()); + // Don't reset has_seen_call_expression since we're continuing the group } ChainMember::StaticMember(_) | ChainMember::ComputedMember(_) => { // if we have seen a CallExpression, we want to close the group. // The resultant group will be something like: [ . , then, () ]; // `.` and `then` belong to the previous StaticMemberExpression, // and `()` belong to the call expression we just encountered - if has_seen_call_expression { + if should_break_group { groups_builder.close_group(); - groups_builder.start_group(member); + groups_builder.start_group(member.clone()); has_seen_call_expression = false; } else { - groups_builder.start_or_continue_group(member); + groups_builder.start_or_continue_group(member.clone()); } } ChainMember::CallExpression { .. } => { - groups_builder.start_or_continue_group(member); + if has_trailing_comment { + groups_builder.close_group(); + groups_builder.start_group(member.clone()); + } else { + groups_builder.start_or_continue_group(member.clone()); + } has_seen_call_expression = true; } ChainMember::TSNonNullExpression(_) => { - groups_builder.start_or_continue_group(member); + groups_builder.start_or_continue_group(member.clone()); } ChainMember::Node(_) => unreachable!("Remaining members never have a `Node` variant"), } + + // Check if this member has a trailing comment for the next iteration + has_trailing_comment = member.has_same_line_trailing_comment(f); } groups_builder.finish() @@ -345,14 +361,45 @@ fn is_computed_array_member_access(member: &ChainMember<'_, '_>) -> bool { ) } +/// Combined analysis of call arguments to avoid multiple iterations +#[derive(Debug, Default)] +struct ArgumentAnalysis { + has_arrow_or_function: bool, + all_simple: bool, +} + +fn analyze_call_arguments<'a>(call: &AstNode<'a, CallExpression<'a>>) -> ArgumentAnalysis { + let mut analysis = ArgumentAnalysis { has_arrow_or_function: false, all_simple: true }; + + for argument in call.arguments() { + // Check for arrow or function expressions + if matches!( + &**argument, + Argument::ArrowFunctionExpression(_) | Argument::FunctionExpression(_) + ) { + analysis.has_arrow_or_function = true; + } + + // Check if argument is simple + if analysis.all_simple && !SimpleArgument::new(argument).is_simple() { + analysis.all_simple = false; + } + + // Early exit if we've determined both conditions + if analysis.has_arrow_or_function && !analysis.all_simple { + break; + } + } + + analysis +} + fn has_arrow_or_function_expression_arg(call: &AstNode<'_, CallExpression<'_>>) -> bool { - call.as_ref().arguments.iter().any(|argument| { - matches!(&argument, Argument::ArrowFunctionExpression(_) | Argument::FunctionExpression(_)) - }) + analyze_call_arguments(call).has_arrow_or_function } fn has_simple_arguments<'a>(call: &AstNode<'a, CallExpression<'a>>) -> bool { - call.arguments().iter().all(|argument| SimpleArgument::new(argument).is_simple()) + analyze_call_arguments(call).all_simple } /// In order to detect those cases, we use an heuristic: if the first diff --git a/crates/oxc_formatter/src/utils/mod.rs b/crates/oxc_formatter/src/utils/mod.rs index bdcdac7282870..5c6aff10d3161 100644 --- a/crates/oxc_formatter/src/utils/mod.rs +++ b/crates/oxc_formatter/src/utils/mod.rs @@ -14,7 +14,11 @@ pub mod typecast; pub mod typescript; use oxc_allocator::Address; -use oxc_ast::{AstKind, ast::CallExpression}; +use oxc_ast::{ + AstKind, + ast::{Argument, CallExpression}, +}; +use oxc_span::{GetSpan, Span}; use crate::{ Format, FormatResult, FormatTrailingCommas, @@ -30,9 +34,109 @@ use crate::{ /// ``` pub fn is_long_curried_call(call: &AstNode<'_, CallExpression<'_>>) -> bool { if let AstNodes::CallExpression(parent_call) = call.parent { - return call.arguments().len() > parent_call.arguments().len() - && !parent_call.arguments().is_empty(); + let parent_args_len = parent_call.arguments().len(); + // Fast path: if parent has no arguments, it's not a curried call + if parent_args_len == 0 { + return false; + } + return call.arguments().len() > parent_args_len; } false } + +/// Check if an expression is used as a call argument by examining the parent node. +#[inline(always)] +pub fn is_expression_used_as_call_argument(span: Span, parent: &AstNodes) -> bool { + match parent { + AstNodes::CallExpression(call) => { + if call.arguments.is_empty() { + return false; + } + if call.callee.span().eq(&span) { + return false; + } + + // Unrolled loop optimization for common argument counts (95% of cases) + match call.arguments.len() { + 1 => { + // Single argument: most common case after empty + let arg_span = call.arguments[0].span(); + arg_span.eq(&span) || arg_span.contains_inclusive(span) + } + 2 => { + // Two arguments: second most common + let arg0_span = call.arguments[0].span(); + let arg1_span = call.arguments[1].span(); + arg0_span.eq(&span) + || arg0_span.contains_inclusive(span) + || arg1_span.eq(&span) + || arg1_span.contains_inclusive(span) + } + 3 => { + // Three arguments: unroll for cache efficiency + let spans = [ + call.arguments[0].span(), + call.arguments[1].span(), + call.arguments[2].span(), + ]; + spans + .iter() + .any(|&arg_span| arg_span.eq(&span) || arg_span.contains_inclusive(span)) + } + _ => { + // Rare case (>3 arguments): use cold path + check_many_arguments_cold(span, &call.arguments) + } + } + } + AstNodes::NewExpression(new_expr) => { + // Branch prediction: Most expressions are not arguments + if new_expr.arguments.is_empty() { + return false; + } + if new_expr.callee.span().eq(&span) { + return false; + } + + // Same unrolled optimization as CallExpression + match new_expr.arguments.len() { + 1 => { + let arg_span = new_expr.arguments[0].span(); + arg_span.eq(&span) || arg_span.contains_inclusive(span) + } + 2 => { + let arg0_span = new_expr.arguments[0].span(); + let arg1_span = new_expr.arguments[1].span(); + arg0_span.eq(&span) + || arg0_span.contains_inclusive(span) + || arg1_span.eq(&span) + || arg1_span.contains_inclusive(span) + } + 3 => { + let spans = [ + new_expr.arguments[0].span(), + new_expr.arguments[1].span(), + new_expr.arguments[2].span(), + ]; + spans + .iter() + .any(|&arg_span| arg_span.eq(&span) || arg_span.contains_inclusive(span)) + } + _ => check_many_arguments_cold(span, &new_expr.arguments), + } + } + _ => false, + } +} + +/// Cold path for checking many arguments - rarely called, optimized for code size not speed +#[cold] +#[inline(never)] +fn check_many_arguments_cold(span: Span, arguments: &[Argument]) -> bool { + // Iterator for rare complex cases (>3 arguments) + arguments.iter().any(|arg| { + let arg_span = arg.span(); + arg_span.eq(&span) || arg_span.contains_inclusive(span) + }) +} diff --git a/crates/oxc_formatter/src/write/arrow_function_expression.rs b/crates/oxc_formatter/src/write/arrow_function_expression.rs index 8f5266e743f67..9eee0a1db3a6d 100644 --- a/crates/oxc_formatter/src/write/arrow_function_expression.rs +++ b/crates/oxc_formatter/src/write/arrow_function_expression.rs @@ -10,10 +10,7 @@ use crate::{ Buffer, Comments, Format, FormatError, FormatResult, Formatter, SourceText, buffer::RemoveSoftLinesBuffer, prelude::*, - trivia::{ - DanglingIndentMode, FormatDanglingComments, FormatLeadingComments, - FormatTrailingComments, format_trailing_comments, - }, + trivia::{FormatLeadingComments, format_trailing_comments}, }, options::FormatTrailingCommas, utils::{assignment_like::AssignmentLikeLayout, expression::ExpressionLeftSide}, @@ -99,7 +96,7 @@ impl<'a> Format<'a> for FormatJsArrowFunctionExpression<'a, '_> { arrow, self.options.call_arg_layout.is_some(), true, - self.options.cache_mode + self.options.cache_mode, ), space(), "=>" @@ -162,19 +159,25 @@ impl<'a> Format<'a> for FormatJsArrowFunctionExpression<'a, '_> { }; } - let body_has_soft_line_break = - arrow_expression.is_none_or(|expression| match expression { + #[expect(clippy::match_same_arms)] + let body_has_soft_line_break = arrow_expression.is_none_or(|expression| { + match expression { Expression::ArrowFunctionExpression(_) | Expression::ArrayExpression(_) | Expression::ObjectExpression(_) => { - !f.comments().has_leading_own_line_comment(body.span().start) + // TODO: It seems no difference whether check there is a leading comment or not. + // !f.comments().has_leading_own_line_comment(body.span().start) + true } Expression::JSXElement(_) | Expression::JSXFragment(_) => true, _ => { is_multiline_template_starting_on_same_line(expression, f.source_text()) } - }); + } + }); + let body_is_condition_type = + matches!(arrow_expression, Some(Expression::ConditionalExpression(_))); if body_has_soft_line_break { write!(f, [formatted_signature, space(), format_body]) } else { @@ -185,13 +188,11 @@ impl<'a> Format<'a> for FormatJsArrowFunctionExpression<'a, '_> { Some(GroupedCallArgumentLayout::GroupedLastArgument) ); - let should_add_soft_line = is_last_call_arg + let should_add_soft_line = (is_last_call_arg // if it's inside a JSXExpression (e.g. an attribute) we should align the expression's closing } with the line with the opening {. - || (matches!(self.arrow.parent, AstNodes::JSXExpressionContainer(container) - if !f.context().comments().has_comment_in_range(arrow.span.end, container.span.end))); - - let body_is_condition_type = - matches!(arrow_expression, Some(Expression::ConditionalExpression(_))); + || matches!(self.arrow.parent, AstNodes::JSXExpressionContainer(_))); + // TODO: it seems no difference whether check there is a comment or not. + //&& !f.context().comments().has_comments(node.syntax()); if body_is_condition_type { write!( @@ -285,20 +286,30 @@ impl<'a, 'b> ArrowFunctionLayout<'a, 'b> { let mut current = arrow; let mut should_break = false; + // Three-way context handling for chain building: + // 1. Assignments: always build chains + // 2. Grouped call arguments: always build chains + // 3. Unspecified call arguments: build only if complex (3+ arrows, type params, etc.) + let should_build_chain = options.assignment_layout.is_some() + || options.call_arg_layout.is_some() + || (options.call_arg_layout.is_none() + && options.assignment_layout.is_none() + && Self::should_build_unspecified_chain(arrow.as_ref())); + loop { if current.expression() && let Some(AstNodes::ExpressionStatement(expr_stmt)) = current.body().statements().first().map(AstNode::::as_ast_nodes) && let AstNodes::ArrowFunctionExpression(next) = &expr_stmt.expression().as_ast_nodes() - && matches!( - options.call_arg_layout, - None | Some(GroupedCallArgumentLayout::GroupedLastArgument) - ) + && should_build_chain { - should_break = should_break || Self::should_break_chain(current); + // Build chains based on context + let should_break_current = Self::should_break_chain(current); + let should_break_next = Self::should_break_chain(next); - should_break = should_break || Self::should_break_chain(next); + should_break = should_break || should_break_current; + should_break = should_break || should_break_next; if head.is_none() { head = Some(current); @@ -313,9 +324,10 @@ impl<'a, 'b> ArrowFunctionLayout<'a, 'b> { None => ArrowFunctionLayout::Single(current), Some(head) => ArrowFunctionLayout::Chain(ArrowChain { head, - middle, tail: current, expand_signatures: should_break, + is_short_chain: middle.is_empty(), // Only 2 arrows if middle is empty + middle, options, }), }; @@ -350,6 +362,34 @@ impl<'a, 'b> ArrowFunctionLayout<'a, 'b> { let has_type_and_parameters = arrow.return_type.is_some() && has_parameters; has_type_and_parameters || has_rest_object_or_array_parameter(parameters) } + + /// Checks if we should build a chain for an unspecified (non-grouped) call argument. + /// Returns true for complex chains that need breaking, false for simple 2-arrow chains. + /// + /// This is used when `call_arg_layout` is `None` (not grouped) to decide if chains + /// should be built based on their complexity. + fn should_build_unspecified_chain(arrow: &ArrowFunctionExpression<'a>) -> bool { + // Count chain length by traversing expression-bodied arrows + let mut current = arrow; + let mut chain_length = 1; + + while current.expression + && let Some(body_stmt) = current.body.statements.first() + && let Statement::ExpressionStatement(expr_stmt) = body_stmt + && let Expression::ArrowFunctionExpression(next) = &expr_stmt.expression + { + chain_length += 1; + current = next; + } + + // Build chains for: + // - 3+ arrows (long chains need breaking) + // - Chains with type parameters (complex) + // - Chains with complex parameters + chain_length >= 3 + || arrow.type_parameters.is_some() + || !has_only_simple_parameters(&arrow.params, true) + } } /// Returns `true` for a template that starts on the same line as the previous token and contains a line break. @@ -407,6 +447,10 @@ struct ArrowChain<'a, 'b> { /// Whether the group wrapping the signatures should be expanded or not. expand_signatures: bool, + + /// Whether this is a short chain (only 2 arrows: head + tail, no middle). + /// Short chains in call arguments are formatted more compactly. + is_short_chain: bool, } impl<'a, 'b> ArrowChain<'a, 'b> { @@ -426,6 +470,10 @@ impl<'a> Format<'a> for ArrowChain<'a, '_> { let is_assignment_rhs = self.options.assignment_layout.is_some(); let is_grouped_call_arg_layout = self.options.call_arg_layout.is_some(); + // Check if this arrow function is a call argument (even if not grouped) + let is_call_argument = is_grouped_call_arg_layout + || crate::utils::is_expression_used_as_call_argument(self.head.span, head_parent); + // If this chain is the callee in a parent call expression, then we // want it to break onto a new line to clearly show that the arrow // chain is distinct and the _result_ is what's being called. @@ -436,8 +484,16 @@ impl<'a> Format<'a> for ArrowChain<'a, '_> { // () => () => // a // )(); - let is_callee = - matches!(head_parent, AstNodes::CallExpression(_) | AstNodes::NewExpression(_)); + // + // We need to verify the arrow is actually the CALLEE (the thing being called), + // not just an argument to a call. Check if arrow's span is within the callee's span. + let is_callee = match head_parent { + AstNodes::CallExpression(call) => call.callee.span().contains_inclusive(self.head.span), + AstNodes::NewExpression(new_expr) => { + new_expr.callee.span().contains_inclusive(self.head.span) + } + _ => false, + }; // With arrays, objects, sequence expressions, and block function bodies, // the opening brace gives a convenient boundary to insert a line break, @@ -454,20 +510,45 @@ impl<'a> Format<'a> for ArrowChain<'a, '_> { // If the body is _not_ one of those kinds, then we'll want to insert a // soft line break before the body so that it prints on a separate line // in its entirety. - let body_on_separate_line = !tail.get_expression().is_none_or(|expression| { + // For call arguments with simple literals, keep them inline to match Prettier + let body_on_separate_line = if self.is_short_chain + && is_call_argument + && !self.arrows().any(|arrow| arrow.type_parameters.is_some()) + && self.arrows().all(|arrow| has_only_simple_parameters(&arrow.params, true)) + { + // Short chains (2 arrows) in call arguments with simple parameters: + // only break if body naturally breaks + // This keeps `(a) => (b) => dispatch(c)` inline + // But breaks `() => () => 1` because type parameters add complexity + // And breaks `(c=default, d=default) => (e) => 0` because complex params add complexity matches!( - expression, - Expression::ObjectExpression(_) - | Expression::ArrayExpression(_) - | Expression::SequenceExpression(_) - | Expression::JSXElement(_) - | Expression::JSXFragment(_) + tail.get_expression(), + Some( + Expression::ObjectExpression(_) + | Expression::ArrayExpression(_) + | Expression::SequenceExpression(_) + | Expression::JSXElement(_) + | Expression::JSXFragment(_) + ) ) - }); + } else { + // Long chains or assignments: use full breaking logic + !tail.get_expression().is_none_or(|expression| { + matches!( + expression, + Expression::ObjectExpression(_) + | Expression::ArrayExpression(_) + | Expression::SequenceExpression(_) + | Expression::JSXElement(_) + | Expression::JSXFragment(_) + ) + }) + }; // If the arrow chain will break onto multiple lines, either because // it's a callee or because the body is printed on its own line, then // the signatures should be expanded first. + // For call arguments, break signatures but don't expand them (controlled by expand_signatures) let break_signatures = (is_callee && body_on_separate_line) || matches!( self.options.assignment_layout, @@ -505,46 +586,34 @@ impl<'a> Format<'a> for ArrowChain<'a, '_> { let is_first = is_first_in_chain; let formatted_signature = format_with(|f| { - let format_leading_comments = format_once(|f| { - if should_format_comments { - // A grouped layout implies that the arrow chain is trying to be rendered - // in a condensed, single-line format (at least the signatures, not - // necessarily the body). In that case, we _need_ to prevent the leading - // comments from inserting line breaks. But if it's _not_ a grouped layout, - // then we want to _force_ the line break so that the leading comments - // don't inadvertently end up on the previous line after the fat arrow. - if is_grouped_call_arg_layout { - write!(f, [space(), format_leading_comments(arrow.span())]) - } else { - write!( - f, - [ - soft_line_break_or_space(), - format_leading_comments(arrow.span()) - ] - ) - } + if should_format_comments { + // A grouped layout implies that the arrow chain is trying to be rendered + // in a condensed, single-line format (at least the signatures, not + // necessarily the body). In that case, we _need_ to prevent the leading + // comments from inserting line breaks. But if it's _not_ a grouped layout, + // then we want to _force_ the line break so that the leading comments + // don't inadvertently end up on the previous line after the fat arrow. + if is_grouped_call_arg_layout { + write!(f, [space(), format_leading_comments(arrow.span())])?; } else { - Ok(()) + write!( + f, + [ + soft_line_break_or_space(), + format_leading_comments(arrow.span()) + ] + )?; } - }); + } - let start = arrow.span().start; write!( f, - [ - FormatContentWithCacheMode::new( - Span::new(start, start), - format_leading_comments, - self.options.cache_mode, - ), - format_signature( - arrow, - is_grouped_call_arg_layout, - is_first, - self.options.cache_mode - ) - ] + [format_signature( + arrow, + is_grouped_call_arg_layout, + is_first, + self.options.cache_mode, + )] ) }); @@ -557,6 +626,9 @@ impl<'a> Format<'a> for ArrowChain<'a, '_> { // its own indention. This ensures that the first item keeps the same // level as the surrounding content, and then each subsequent item has // one additional level, as shown above. + // EXCEPTION: When has_initial_indent is true (assignments, callees), the + // whole chain is already wrapped in indent(), so we don't add extra indent + // to middle arrows to avoid double-indenting. if is_first_in_chain || has_initial_indent { is_first_in_chain = false; write!(f, [formatted_signature])?; @@ -574,7 +646,7 @@ impl<'a> Format<'a> for ArrowChain<'a, '_> { Ok(()) }); - group(&join_signatures).should_expand(*expand_signatures).fmt(f) + write!(f, [group(&join_signatures).should_expand(*expand_signatures)]) }); let format_tail_body_inner = format_with(|f| { @@ -622,6 +694,14 @@ impl<'a> Format<'a> for ArrowChain<'a, '_> { write!(f, [format_tail_body])?; } } + + // Format the trailing comments of all arrow function EXCEPT the first one because + // the comments of the head get formatted as part of the `FormatJsArrowFunctionExpression` call. + // TODO: It seems unneeded in the current oxc implementation? + // for arrow in self.arrows().skip(1) { + // write!(f, format_trailing_comments(arrow.span().end))?; + // } + Ok(()) }); @@ -630,6 +710,7 @@ impl<'a> Format<'a> for ArrowChain<'a, '_> { let should_add_soft_line = matches!(head_parent, AstNodes::JSXExpressionContainer(_)); if body_on_separate_line { + // Use normal indent for arrow chains to match Prettier write!( f, [ @@ -663,11 +744,7 @@ impl<'a> Format<'a> for ArrowChain<'a, '_> { write!(f, [space(), "=>"])?; - if is_grouped_call_arg_layout { - write!(f, [group(&format_tail_body)])?; - } else { - write!(f, [indent_if_group_breaks(&format_tail_body, group_id)])?; - } + write!(f, [indent_if_group_breaks(&format_tail_body, group_id)])?; if is_callee { write!(f, [if_group_breaks(&soft_line_break()).with_group_id(Some(group_id))])?; @@ -742,7 +819,9 @@ fn format_signature<'a, 'b>( }); let format_head = FormatContentWithCacheMode::new(arrow.params.span, content, cache_mode); - if is_first_or_last_call_argument { + if is_first_or_last_call_argument && is_first_in_chain { + // Only enforce strict no-break policy for the first signature in grouped arguments + // Chains need to be able to break for proper formatting let mut buffer = RemoveSoftLinesBuffer::new(f); let mut recording = buffer.start_recording(); @@ -765,12 +844,12 @@ fn format_signature<'a, 'b>( )?; } - // Print comments before the fat arrow (`=>`) - let comments_before_fat_arrow = - f.context().comments().comments_before_character(arrow.params.span().end, b'='); - let content = - format_once(|f| FormatTrailingComments::Comments(comments_before_fat_arrow).fmt(f)); - write!(f, [FormatContentWithCacheMode::new(arrow.span, content, cache_mode)]) + // TODO: for case `a = (x: any): x is string /* comment */ => {}` + // if f.comments().has_dangling_comments(arrow.span()) { + // write!(f, [space(), format_dangling_comments(arrow.span())])?; + // } + + Ok(()) }) } diff --git a/crates/oxc_formatter/src/write/as_or_satisfies_expression.rs b/crates/oxc_formatter/src/write/as_or_satisfies_expression.rs index 9f5bf2de7ae15..5f74e59bb3163 100644 --- a/crates/oxc_formatter/src/write/as_or_satisfies_expression.rs +++ b/crates/oxc_formatter/src/write/as_or_satisfies_expression.rs @@ -48,7 +48,16 @@ fn format_as_or_satisfies_expression<'a>( }); if is_callee_or_object { - write!(f, [group(&soft_block_indent(&format_inner))]) + // When as/satisfies is the object of a member expression or callee of a call, + // wrap with indent and soft breaks to allow proper line breaking. + // This matches Prettier's formatting: group([indent([softline, ...parts]), softline]) + write!( + f, + [group(&format_args!( + indent(&format_args!(soft_line_break(), format_inner)), + soft_line_break() + ))] + ) } else { write!(f, [format_inner]) } @@ -56,13 +65,14 @@ fn format_as_or_satisfies_expression<'a>( fn is_callee_or_object_context(span: Span, parent: &AstNodes<'_>) -> bool { match parent { - // Callee - AstNodes::CallExpression(_) | AstNodes::NewExpression(_) - // Static member - | AstNodes::StaticMemberExpression(_) => true, - AstNodes::ComputedMemberExpression(member) => { - member.object.span() == span - } + // Only the callee of a call expression needs special formatting + AstNodes::CallExpression(call) => call.callee.span() == span, + // Only the callee of a new expression needs special formatting + AstNodes::NewExpression(new_expr) => new_expr.callee.span() == span, + // All static member expressions need special formatting (as expression is always the object) + AstNodes::StaticMemberExpression(_) => true, + // Only when the as expression is the object of a computed member expression + AstNodes::ComputedMemberExpression(member) => member.object.span() == span, _ => false, } } diff --git a/crates/oxc_formatter/src/write/binary_like_expression.rs b/crates/oxc_formatter/src/write/binary_like_expression.rs index 3228c9ec3629f..2b54c61ca05d0 100644 --- a/crates/oxc_formatter/src/write/binary_like_expression.rs +++ b/crates/oxc_formatter/src/write/binary_like_expression.rs @@ -17,6 +17,39 @@ use crate::{ use crate::{format_args, formatter::prelude::*, write}; +/// Checks if an expression contains class expressions followed by division operators +/// that could create ASI (Automatic Semicolon Insertion) ambiguity when formatted +/// with line breaks. +/// +/// This specifically detects patterns like `class{} / foo` where a line break +/// between the class expression and division operator could cause the parser to +/// insert a semicolon, changing the AST structure on subsequent format passes. +pub fn has_asi_risky_division_pattern(expr: &Expression) -> bool { + match expr { + Expression::BinaryExpression(bin) if matches!(bin.operator, BinaryOperator::Division) => { + // Check if left side is or contains a class expression + has_class_expression(&bin.left) + } + Expression::CallExpression(call) => { + // Check if the callee is a risky division + has_asi_risky_division_pattern(&call.callee) + } + _ => false, + } +} + +/// Helper: Check if expression is or contains a class expression +fn has_class_expression(expr: &Expression) -> bool { + match expr { + Expression::ClassExpression(_) => true, + Expression::ParenthesizedExpression(paren) => has_class_expression(&paren.expression), + Expression::BinaryExpression(bin) if matches!(bin.operator, BinaryOperator::Division) => { + has_class_expression(&bin.left) + } + _ => false, + } +} + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum BinaryLikeOperator { BinaryOperator(BinaryOperator), @@ -158,6 +191,13 @@ impl<'a, 'b> BinaryLikeExpression<'a, 'b> { matches!(container.parent, AstNodes::JSXAttribute(_)) } AstNodes::ExpressionStatement(statement) => statement.is_arrow_function_body(), + AstNodes::CallExpression(call) => { + // https://github.com/prettier/prettier/issues/18057#issuecomment-3472912112 + // Special case: Boolean(expr) with single argument should not indent + !call.optional + && call.arguments.len() == 1 + && matches!(&call.callee, Expression::Identifier(ident) if ident.name == "Boolean") + } AstNodes::ConditionalExpression(conditional) => { !matches!( parent.parent(), @@ -165,16 +205,17 @@ impl<'a, 'b> BinaryLikeExpression<'a, 'b> { | AstNodes::ThrowStatement(_) | AstNodes::CallExpression(_) | AstNodes::ImportExpression(_) - | AstNodes::MetaProperty(_) - ) && - // TODO(prettier): Why not include `NewExpression` ??? - !matches!(parent.parent(), AstNodes::Argument(argument) if matches!(argument.parent, AstNodes::CallExpression(_))) - } - AstNodes::Argument(argument) => { - // https://github.com/prettier/prettier/issues/18057#issuecomment-3472912112 - matches!(argument.parent, AstNodes::CallExpression(call) if call.arguments.len() == 1 && - matches!(&call.callee, Expression::Identifier(ident) if ident.name == "Boolean")) + | AstNodes::MetaProperty(_) // TODO(prettier): Why not include `NewExpression` ??? + ) } + AstNodes::ConditionalExpression(conditional) => !matches!( + conditional.parent, + AstNodes::ReturnStatement(_) + | AstNodes::ThrowStatement(_) + | AstNodes::CallExpression(_) + | AstNodes::ImportExpression(_) + | AstNodes::MetaProperty(_) + ), _ => false, } } diff --git a/crates/oxc_formatter/src/write/call_arguments.rs b/crates/oxc_formatter/src/write/call_arguments.rs index a9727a74fd67a..fe1efd8592fb2 100644 --- a/crates/oxc_formatter/src/write/call_arguments.rs +++ b/crates/oxc_formatter/src/write/call_arguments.rs @@ -44,8 +44,6 @@ impl<'a> Format<'a> for AstNode<'a, ArenaVec<'a, Argument<'a>>> { let l_paren_token = "("; let r_paren_token = ")"; - let arguments = self.as_ref(); - if self.is_empty() { return write!( f, @@ -67,12 +65,36 @@ impl<'a> Format<'a> for AstNode<'a, ArenaVec<'a, Argument<'a>>> { None }; + let new_expression = + if let AstNodes::NewExpression(new_expr) = self.parent { Some(new_expr) } else { None }; + + let (is_commonjs_or_amd_call, is_test_call) = call_expression + .map(|call| (is_commonjs_or_amd_call(self, call, f), is_test_call_expression(call))) + .unwrap_or_default(); + + // For test calls: use compact formatting (space-separated, no breaks) + // EXCEPT when there are exactly 2 args and the first is NOT a string/template. + // This handles patterns like: + // - it("name", () => {}) - compact + // - it("name", async(() => {})) - compact + // - beforeEach(async(() => {})) - compact + // - it(() => {}, () => {}) - NOT compact (2 args, first not string) + let should_use_compact_test_formatting = is_test_call + && (self.len() != 2 + || matches!( + self.as_ref().first(), + Some( + Argument::StringLiteral(_) + | Argument::TemplateLiteral(_) + | Argument::TaggedTemplateExpression(_) + ) + )); + if is_simple_module_import - || call_expression.is_some_and(|call| { - is_commonjs_or_amd_call(self, call, f) || is_test_call_expression(call) - }) + || is_commonjs_or_amd_call || is_multiline_template_only_args(self, f.source_text()) || is_react_hook_with_deps_array(self, f.comments()) + || should_use_compact_test_formatting { return write!( f, @@ -92,28 +114,27 @@ impl<'a> Format<'a> for AstNode<'a, ArenaVec<'a, Argument<'a>>> { ); } - // Check if there's an empty line (2+ newlines) between any consecutive arguments. - // This is used to preserve intentional blank lines in the original source. - let has_empty_line = arguments.windows(2).any(|window| { - let (cur_arg, next_arg) = (&window[0], &window[1]); - - // Count newlines between arguments, short-circuiting at 2 for performance - // Check if there are at least two newlines between arguments - f.source_text() - .bytes_range(cur_arg.span().end, next_arg.span().start) - .iter() - .filter(|&&b| b == b'\n') - .nth(1) - .is_some() - }); - + let has_empty_line = + self.iter().any(|arg| f.source_text().get_lines_before(arg.span(), f.comments()) > 1); if has_empty_line - || (!matches!(self.grand_parent(), AstNodes::Decorator(_)) + || (!matches!(self.parent.parent(), AstNodes::Decorator(_)) && is_function_composition_args(self)) + || has_long_arrow_chain_arg(self) { return format_all_args_broken_out(self, true, f); } + // Check if this is a simple nested call that should stay compact + if call_expression.is_some_and(|call| should_keep_nested_call_compact(call, self, f)) { + return format_compact_call_arguments(self, f); + } + + // Check if this is a simple nested new expression that should stay compact + if new_expression.is_some_and(|new_expr| should_keep_nested_new_compact(new_expr, self, f)) + { + return format_compact_call_arguments(self, f); + } + if let Some(group_layout) = arguments_grouped_layout(self, f) { write_grouped_arguments(self, group_layout, f) } else if call_expression.is_some_and(|call| is_long_curried_call(call)) { @@ -136,6 +157,198 @@ impl<'a> Format<'a> for AstNode<'a, ArenaVec<'a, Argument<'a>>> { } } +/// Checks if this call should keep its arguments compact because it's a +/// simple nested call used as an argument. +/// +/// Returns `true` for patterns like: +/// - `require(path.join(arg1, arg2))` +/// - `logger.error(pipe(call1(), call2()))` +/// +/// After removing AstKind::Argument, these calls are direct children of +/// their parent CallExpression, so we check if our span is within the +/// parent's argument spans. +fn should_keep_nested_call_compact( + call: &AstNode<'_, CallExpression<'_>>, + args: &[Argument<'_>], + f: &Formatter<'_, '_>, +) -> bool { + // Only applies to calls with 2-4 simple arguments + if args.len() < 2 || args.len() > 4 { + return false; + } + + // Don't use compact formatting if there are any comments in the arguments + // Check before first argument and between all arguments + let call_span = call.span; + for (i, arg) in args.iter().enumerate() { + let previous_end = if i == 0 { + call.callee.span().end // Check from after callee to first arg + } else { + args[i - 1].span().end // Check between args + }; + + let current_span = arg.span(); + if f.comments().has_comment_in_range(previous_end, current_span.start) { + return false; + } + } + + // Also check after last argument (trailing comments) + if let Some(last_arg) = args.last() + && f.comments().has_comment_in_range(last_arg.span().end, call_span.end) + { + return false; + } + + // Estimate total argument length to avoid keeping very long calls compact + let total_length: u32 = args.iter().map(|arg| arg.span().size()).sum(); + // If arguments are too long (> 60 chars), let the normal formatting handle it + if total_length > 60 { + return false; + } + + // Check if arguments are relatively simple + // Allow some complex expressions like conditionals and simple calls if they're short + let all_args_acceptable = args.iter().all(|arg| { + match arg.as_expression() { + Some(expr) => match expr { + // Exclude objects and arrays - they often break across lines during formatting + // even when marked as "simple", which defeats the purpose of compact formatting + Expression::ObjectExpression(_) | Expression::ArrayExpression(_) => false, + // Allow conditionals, binary/logical expressions, and simple calls + Expression::ConditionalExpression(_) + | Expression::LogicalExpression(_) + | Expression::BinaryExpression(_) + | Expression::CallExpression(_) => true, + // For other expressions, use the simple check + _ => is_relatively_short_argument(expr), + }, + None => false, // SpreadElement is not acceptable + } + }); + + if !all_args_acceptable { + return false; + } + + // Check if this call is itself an argument to another call + // After AstKind::Argument removal, parent is directly the CallExpression + match call.parent { + AstNodes::CallExpression(parent_call) => { + // Verify our span is within parent's arguments (not the callee) + if parent_call.callee.span().contains_inclusive(call.span) { + return false; // We're the callee, not an argument + } + + // Check if our span is within any of parent's arguments + parent_call.arguments.iter().any(|arg| arg.span().contains_inclusive(call.span)) + } + AstNodes::NewExpression(parent_new) => { + // Same check for new expressions + if parent_new.callee.span().contains_inclusive(call.span) { + return false; + } + + parent_new.arguments.iter().any(|arg| arg.span().contains_inclusive(call.span)) + } + _ => false, + } +} + +/// Checks if this new expression should keep its arguments compact because it's a +/// simple nested new expression used as an argument. +/// +/// Returns `true` for patterns like: +/// - `assert.sameValue(Temporal.Instant.compare(new Temporal.Instant(-1000n), new Temporal.Instant(1000n)), -1)` +/// - `compareArray(new TA([0, 1, 2, 3]).copyWithin(0, 1, -1), [1, 2, 2, 3])` +/// +/// Similar to `should_keep_nested_call_compact` but for NewExpression. +fn should_keep_nested_new_compact( + new_expr: &AstNode<'_, NewExpression<'_>>, + args: &[Argument<'_>], + f: &Formatter<'_, '_>, +) -> bool { + // Only applies to new expressions with 1-4 simple arguments + if args.is_empty() || args.len() > 4 { + return false; + } + + // Don't use compact formatting if there are any comments in the arguments + let new_span = new_expr.span; + for (i, arg) in args.iter().enumerate() { + let previous_end = if i == 0 { + new_expr.callee.span().end // Check from after callee to first arg + } else { + args[i - 1].span().end // Check between args + }; + + let current_span = arg.span(); + if f.comments().has_comment_in_range(previous_end, current_span.start) { + return false; + } + } + + // Also check after last argument (trailing comments) + if let Some(last_arg) = args.last() + && f.comments().has_comment_in_range(last_arg.span().end, new_span.end) + { + return false; + } + + // Estimate total argument length to avoid keeping very long calls compact + let total_length: u32 = args.iter().map(|arg| arg.span().size()).sum(); + // If arguments are too long (> 60 chars), let the normal formatting handle it + if total_length > 60 { + return false; + } + + // Check if arguments are relatively simple + let all_args_acceptable = args.iter().all(|arg| { + match arg.as_expression() { + Some(expr) => match expr { + // Exclude objects and arrays - they often break across lines during formatting + // even when marked as "simple", which defeats the purpose of compact formatting + Expression::ObjectExpression(_) | Expression::ArrayExpression(_) => false, + // Allow conditionals, binary/logical expressions, and simple calls/new + Expression::ConditionalExpression(_) + | Expression::LogicalExpression(_) + | Expression::BinaryExpression(_) + | Expression::CallExpression(_) + | Expression::NewExpression(_) => true, + // For other expressions, use the simple check + _ => is_relatively_short_argument(expr), + }, + None => false, // SpreadElement is not acceptable + } + }); + + if !all_args_acceptable { + return false; + } + + // Check if this new expression is itself an argument to another call/new + match new_expr.parent { + AstNodes::CallExpression(parent_call) => { + // Verify our span is within parent's arguments (not the callee) + if parent_call.callee.span().contains_inclusive(new_expr.span) { + return false; // We're the callee, not an argument + } + + // Check if our span is within any of parent's arguments + parent_call.arguments.iter().any(|arg| arg.span().contains_inclusive(new_expr.span)) + } + AstNodes::NewExpression(parent_new) => { + // Same check for parent new expressions + if parent_new.callee.span().contains_inclusive(new_expr.span) { + return false; + } + + parent_new.arguments.iter().any(|arg| arg.span().contains_inclusive(new_expr.span)) + } + _ => false, + } +} + /// Tests if a call has multiple anonymous function like (arrow or function expression) arguments. /// /// ## Examples @@ -182,6 +395,55 @@ pub fn is_function_composition_args(args: &[Argument<'_>]) -> bool { false } +/// Tests if a call has any long arrow chain argument (3+ arrows) that should force expansion. +/// +/// Expands chains with: +/// - Block bodies: `head => body => { console.log(); }` +/// - Object/array expression bodies: `a => b => ({ foo: bar })` +/// +/// Does NOT expand chains with simple expression bodies: +/// - Literals: `a => b => c => 1` +/// - Sequence expressions: `a => b => c => (1, 2, 3)` +/// - Conditionals: `a => b => c => (x ? y : z)` +fn has_long_arrow_chain_arg(args: &[Argument<'_>]) -> bool { + args.iter().any(|arg| { + if let Argument::ArrowFunctionExpression(arrow) = arg { + let mut current = arrow; + let mut chain_length = 1; + + while current.expression + && let Some(Statement::ExpressionStatement(expr_stmt)) = + current.body.statements.first() + && let Expression::ArrowFunctionExpression(next) = &expr_stmt.expression + { + chain_length += 1; + current = next; + } + + if chain_length < 3 { + return false; + } + + // Check if tail should force expansion + if !current.expression { + // Block body always forces expansion + return true; + } + + // For expression bodies, only expand for object/array expressions + if let Some(Expression::ObjectExpression(_) | Expression::ArrayExpression(_)) = + current.get_expression() + { + return true; + } + + false + } else { + false + } + }) +} + fn format_all_elements_broken_out<'a, 'b>( node: &'b AstNode<'a, ArenaVec<'a, Argument<'a>>>, elements: impl Iterator>>, usize)>, @@ -252,6 +514,33 @@ fn format_all_args_broken_out<'a, 'b>( ) } +/// Formats call arguments in a compact style that resists breaking. +/// Used for simple nested calls that should stay on one line when possible. +/// +/// Uses a simple group without soft_block_indent to avoid breaking when +/// the outer call adds indentation. +fn format_compact_call_arguments<'a>( + node: &AstNode<'a, ArenaVec<'a, Argument<'a>>>, + f: &mut Formatter<'_, 'a>, +) -> FormatResult<()> { + write!( + f, + [group(&format_args!( + "(", + format_with(|f| { + for (index, argument) in node.iter().enumerate() { + if index > 0 { + write!(f, [",", space()])?; + } + write!(f, [argument])?; + } + Ok(()) + }), + ")", + ))] + ) +} + pub fn arguments_grouped_layout( args: &[Argument], f: &Formatter<'_, '_>, diff --git a/crates/oxc_formatter/src/write/class.rs b/crates/oxc_formatter/src/write/class.rs index 86fa73dc06865..233ef7aec7281 100644 --- a/crates/oxc_formatter/src/write/class.rs +++ b/crates/oxc_formatter/src/write/class.rs @@ -224,29 +224,82 @@ impl<'a> Format<'a> for AstNode<'a, Vec<'a, TSClassImplements<'a>>> { f, [ "implements", - group(&soft_line_indent_or_space(&format_once(|f| { - let last_index = self.len().saturating_sub(1); - let mut joiner = f.join_with(soft_line_break_or_space()); - - for (i, heritage) in FormatSeparatedIter::new(self.into_iter(), ",") - .with_trailing_separator(TrailingSeparator::Disallowed) - .enumerate() - { - if i == last_index { - // The trailing comments of the last heritage should be printed inside the class declaration - joiner.entry(&FormatNodeWithoutTrailingComments(&heritage)); + group(&indent(&format_args!( + soft_line_break_or_space(), + format_once(|f| { + let last_index = self.len().saturating_sub(1); + + // Check if any heritage items have inline (block) comments + let has_inline_comments = self.iter().any(|heritage| { + f.comments() + .comments_after(heritage.span().end) + .iter() + .any(|c| !c.is_line()) + }); + + // Use appropriate separator based on whether inline comments are present + if has_inline_comments { + // Keep inline comments on the same line + let mut joiner = f.join_with(space()); + + for (i, heritage) in FormatSeparatedIter::new(self.into_iter(), ",") + .with_trailing_separator(TrailingSeparator::Disallowed) + .enumerate() + { + if i == last_index { + // The trailing comments of the last heritage should be printed inside the class declaration + joiner.entry(&FormatNodeWithoutTrailingComments(&heritage)); + } else { + joiner.entry(&heritage); + } + } + + joiner.finish() } else { - joiner.entry(&heritage); - } - } + // Use soft_line_break_or_space() for normal formatting + let mut joiner = f.join_with(soft_line_break_or_space()); + + for (i, heritage) in self.iter().enumerate() { + joiner.entry(&format_heritage_with_trailing_comments( + heritage, + i == last_index, + )); + } - joiner.finish() - }))) + joiner.finish() + } + }) + ))) ] ) } } +/// Formats a heritage item in an implements clause with explicit trailing comment handling. +/// +/// For non-last items, trailing comments are explicitly formatted to ensure they stay +/// on the same line as the interface name (e.g., `implements z, // comment`). +/// For the last item, trailing comments are suppressed here and printed in the class body. +/// +/// This explicit handling is necessary because the automatic comment attachment mechanism +/// does not correctly associate trailing line comments with heritage items. +fn format_heritage_with_trailing_comments<'a>( + heritage: &'a AstNode<'a, TSClassImplements<'a>>, + is_last: bool, +) -> impl Format<'a> + 'a { + format_with(move |f: &mut Formatter<'_, 'a>| { + if is_last { + // Last item: no trailing comments (printed in class body), no comma + FormatNodeWithoutTrailingComments(heritage).fmt(f) + } else { + // Non-last items: explicit trailing comments + comma + heritage.write(f)?; + heritage.format_trailing_comments(f)?; + write!(f, ",") + } + }) +} + impl<'a> FormatWrite<'a> for AstNode<'a, TSClassImplements<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { write!(f, [self.expression(), self.type_arguments()]) diff --git a/crates/oxc_formatter/src/write/decorators.rs b/crates/oxc_formatter/src/write/decorators.rs index 7de4ee2c165fa..a1ab89bbda830 100644 --- a/crates/oxc_formatter/src/write/decorators.rs +++ b/crates/oxc_formatter/src/write/decorators.rs @@ -22,37 +22,42 @@ impl<'a> Format<'a> for AstNode<'a, Vec<'a, Decorator<'a>>> { return Ok(()); }; - // Check parent to determine formatting context - match self.parent { - AstNodes::PropertyDefinition(_) - | AstNodes::MethodDefinition(_) - | AstNodes::AccessorProperty(_) => { - return write!( - f, - [group(&format_args!( - format_once(|f| { - f.join_nodes_with_soft_line().entries(self.iter()).finish() - }), - soft_line_break_or_space() - )) - .should_expand(should_expand_decorators(self, f))] - ); + let format_decorators = format_once(|f| { + // Check parent to determine formatting context + match self.parent { + AstNodes::PropertyDefinition(_) + | AstNodes::MethodDefinition(_) + | AstNodes::AccessorProperty(_) => { + return write!( + f, + [group(&format_args!( + format_once(|f| { + f.join_nodes_with_soft_line().entries(self.iter()).finish() + }), + soft_line_break_or_space() + )) + .should_expand(should_expand_decorators(self, f))] + ); + } + // Parameter decorators + AstNodes::FormalParameter(_) => { + write!(f, should_expand_decorators(self, f).then_some(expand_parent()))?; + } + AstNodes::ExportNamedDeclaration(_) | AstNodes::ExportDefaultDeclaration(_) => { + write!(f, [hard_line_break()])?; + } + _ => { + write!(f, [expand_parent()])?; + } } - // Parameter decorators - AstNodes::FormalParameter(_) => { - write!(f, should_expand_decorators(self, f).then_some(expand_parent()))?; - } - AstNodes::ExportNamedDeclaration(_) | AstNodes::ExportDefaultDeclaration(_) => { - write!(f, [hard_line_break()])?; - } - _ => { - write!(f, [expand_parent()])?; - } - } - f.join_with(&soft_line_break_or_space()).entries(self.iter()).finish()?; + f.join_with(&soft_line_break_or_space()).entries(self.iter()).finish()?; + + write!(f, [soft_line_break_or_space()]) + }); - write!(f, [soft_line_break_or_space()]) + format_decorators.fmt(f)?; + format_trailing_comments_for_last_decorator(last.span.end, f) } } @@ -73,27 +78,29 @@ fn is_identifier_or_static_member_only(callee: &Expression) -> bool { impl<'a> FormatWrite<'a> for AstNode<'a, Decorator<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - write!(f, ["@"]); - - // Determine if parentheses are required around decorator expressions - let needs_parentheses = match &self.expression { - // Identifiers: `@decorator` needs no parens - Expression::Identifier(_) => false, - // Call expressions: `@obj.method()` needs no parens, `@(complex().method)()` needs parens - Expression::CallExpression(call) => !is_identifier_or_static_member_only(&call.callee), - // Static member expressions: `@obj.prop` needs no parens, `@(complex[key])` needs parens - Expression::StaticMemberExpression(static_member) => { - !is_identifier_or_static_member_only(&static_member.object) - } - // All other expressions need parentheses: `@(a + b)`, `@(condition ? a : b)` + self.format_leading_comments(f)?; + write!(f, ["@"])?; + + // Check if we need to manually add parentheses for cases not handled by NeedsParentheses + let needs_manual_parentheses = match &self.expression { + // These expressions don't need manual parentheses: + // - ParenthesizedExpression already has parens + // - CallExpression, ComputedMemberExpression, StaticMemberExpression handled by NeedsParentheses + // - Identifiers don't need parens + Expression::ParenthesizedExpression(_) + | Expression::CallExpression(_) + | Expression::ComputedMemberExpression(_) + | Expression::StaticMemberExpression(_) + | Expression::Identifier(_) => false, + // All other complex expressions need parentheses _ => true, }; - if needs_parentheses { + if needs_manual_parentheses { write!(f, "(")?; } write!(f, [self.expression()])?; - if needs_parentheses { + if needs_manual_parentheses { write!(f, ")")?; } Ok(()) @@ -108,3 +115,32 @@ fn should_expand_decorators<'a>( ) -> bool { decorators.iter().any(|decorator| f.source_text().lines_after(decorator.span().end) > 0) } + +pub fn format_trailing_comments_for_last_decorator( + mut start: u32, + f: &mut Formatter<'_, '_>, +) -> FormatResult<()> { + let mut comments = f.context().comments().unprinted_comments(); + + for (i, comment) in comments.iter().enumerate() { + if !f.source_text().all_bytes_match(start, comment.span.start, |b| b.is_ascii_whitespace()) + { + comments = &comments[..i]; + break; + } + + start = comment.span.end; + } + + if !comments.is_empty() { + write!( + f, + [group(&format_args!( + FormatTrailingComments::Comments(comments), + soft_line_break_or_space() + ))] + )?; + } + + Ok(()) +} diff --git a/crates/oxc_formatter/src/write/jsx/element.rs b/crates/oxc_formatter/src/write/jsx/element.rs index 43700109fd6b2..eef9cb436c31e 100644 --- a/crates/oxc_formatter/src/write/jsx/element.rs +++ b/crates/oxc_formatter/src/write/jsx/element.rs @@ -187,17 +187,25 @@ pub fn should_expand(mut parent: &AstNodes<'_>) -> bool { parent = stmt.grand_parent(); } let maybe_jsx_expression_child = match parent { - AstNodes::ArrowFunctionExpression(arrow) if arrow.expression => match arrow.parent { - // Argument - AstNodes::Argument(argument) - if matches!(argument.parent, AstNodes::CallExpression(_)) => - { - argument.grand_parent() + AstNodes::ArrowFunctionExpression(arrow) if arrow.expression => { + // Check if this arrow function is used as a call argument + if crate::utils::is_expression_used_as_call_argument(arrow.span, arrow.parent) { + // Get the call expression's parent + if let AstNodes::CallExpression(call) = arrow.parent { + call.parent + } else if let AstNodes::NewExpression(new_expr) = arrow.parent { + new_expr.parent + } else { + return false; + } + } else { + // If it's the callee + match arrow.parent { + AstNodes::CallExpression(call) => call.parent, + _ => return false, + } } - // Callee - AstNodes::CallExpression(call) => call.parent, - _ => return false, - }, + } _ => return false, }; matches!( diff --git a/crates/oxc_formatter/src/write/jsx/mod.rs b/crates/oxc_formatter/src/write/jsx/mod.rs index f4d99f1696d73..e71cb58995312 100644 --- a/crates/oxc_formatter/src/write/jsx/mod.rs +++ b/crates/oxc_formatter/src/write/jsx/mod.rs @@ -183,11 +183,53 @@ impl<'a> FormatWrite<'a> for AstNode<'a, JSXExpressionContainer<'a>> { | JSXExpression::BinaryExpression(_) ); + // Check if this is an arrow function that needs special handling + let is_arrow_with_non_jsx_body = + if let JSXExpression::ArrowFunctionExpression(arrow) = &self.expression { + // Only apply special handling for expression arrows (not block arrows) + if arrow.expression { + // Check if the body is a JSX element + if let Some(Statement::ExpressionStatement(expr_stmt)) = + arrow.body.statements.first() + { + // Unwrap parenthesized expressions + let mut expr = &expr_stmt.expression; + while let Expression::ParenthesizedExpression(paren) = expr { + expr = &paren.expression; + } + // If body is JSX, treat like normal inline (no special handling) + // If body is NOT JSX, we need to add line break + !matches!( + expr, + Expression::JSXElement(_) | Expression::JSXFragment(_) + ) + } else { + false + } + } else { + false + } + } else { + false + }; + let should_inline = !has_comment(f) && (is_conditional_or_binary || should_inline_jsx_expression(self, f.comments())); - if should_inline { + if is_arrow_with_non_jsx_body { + // Arrow with non-JSX body: add line break before closing brace + write!( + f, + [group(&format_args!( + "{", + self.expression(), + line_suffix_boundary(), + soft_line_break(), + "}" + ))] + ) + } else if should_inline { write!(f, ["{", self.expression(), line_suffix_boundary(), "}"]) } else { write!( diff --git a/crates/oxc_formatter/src/write/member_expression.rs b/crates/oxc_formatter/src/write/member_expression.rs index ff0b3b8617b6f..a50f0ef033cba 100644 --- a/crates/oxc_formatter/src/write/member_expression.rs +++ b/crates/oxc_formatter/src/write/member_expression.rs @@ -150,9 +150,7 @@ fn layout<'a>( } match first_non_static_member_ancestor { - AstNodes::Argument(argument) if matches!(argument.parent, AstNodes::NewExpression(_)) => { - StaticMemberLayout::NoBreak - } + AstNodes::NewExpression(_) => StaticMemberLayout::NoBreak, AstNodes::AssignmentExpression(assignment) => { if matches!(assignment.left, AssignmentTarget::AssignmentTargetIdentifier(_)) { StaticMemberLayout::BreakAfterObject diff --git a/crates/oxc_formatter/src/write/mod.rs b/crates/oxc_formatter/src/write/mod.rs index 3d54c6bebd6bb..9c5d09bae49be 100644 --- a/crates/oxc_formatter/src/write/mod.rs +++ b/crates/oxc_formatter/src/write/mod.rs @@ -37,7 +37,9 @@ mod variable_declaration; pub use arrow_function_expression::{ FormatJsArrowFunctionExpression, FormatJsArrowFunctionExpressionOptions, }; -pub use binary_like_expression::{BinaryLikeExpression, BinaryLikeOperator, should_flatten}; +pub use binary_like_expression::{ + BinaryLikeExpression, BinaryLikeOperator, has_asi_risky_division_pattern, should_flatten, +}; pub use function::FormatFunctionOptions; use call_arguments::is_function_composition_args; diff --git a/crates/oxc_formatter/src/write/parameters.rs b/crates/oxc_formatter/src/write/parameters.rs index a080cff161f8f..1dbb41e432823 100644 --- a/crates/oxc_formatter/src/write/parameters.rs +++ b/crates/oxc_formatter/src/write/parameters.rs @@ -51,14 +51,28 @@ impl<'a> FormatWrite<'a> for AstNode<'a, FormalParameters<'a>> { let layout = if !self.has_parameter() && this_param.is_none() { ParameterLayout::NoParameters } else if can_hug || { - // `self`: Function - // `self.ancestors().nth(1)`: Argument - // `self.ancestors().nth(2)`: CallExpression - if let Some(AstNodes::CallExpression(call)) = self.ancestors().nth(2) { - is_test_call_expression(call) - } else { - false + // Check if these parameters are part of a test call expression + // by walking up the parent chain + let mut current_parent = Some(self.parent); + let mut is_in_test_call = false; + + while let Some(parent) = current_parent { + // Stop at root (Dummy node provides natural termination) + if matches!(parent, AstNodes::Dummy()) { + break; + } + + if let AstNodes::CallExpression(call) = parent + && is_test_call_expression(call) + { + is_in_test_call = true; + break; + } + + current_parent = Some(parent.parent()); } + + is_in_test_call } { ParameterLayout::Hug } else { diff --git a/crates/oxc_formatter/src/write/return_or_throw_statement.rs b/crates/oxc_formatter/src/write/return_or_throw_statement.rs index 2ade133df735b..afc6055bb24fd 100644 --- a/crates/oxc_formatter/src/write/return_or_throw_statement.rs +++ b/crates/oxc_formatter/src/write/return_or_throw_statement.rs @@ -91,10 +91,12 @@ impl<'a> Format<'a> for FormatAdjacentArgument<'a, '_> { fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { let argument = self.0; - if !matches!(argument.as_ref(), Expression::JSXElement(_) | Expression::JSXFragment(_)) - && has_argument_leading_comments(argument, f) - { - write!(f, [token("("), &block_indent(&argument), token(")")]) + let is_jsx = + matches!(argument.as_ref(), Expression::JSXElement(_) | Expression::JSXFragment(_)); + let is_jsx_suppressed = is_jsx && f.comments().is_suppressed(argument.span().start); + + if has_argument_leading_comments(argument, f) && (!is_jsx || is_jsx_suppressed) { + write!(f, [text("("), &block_indent(&argument), text(")")]) } else if argument.is_binaryish() { write!( f, diff --git a/crates/oxc_formatter/src/write/type_parameters.rs b/crates/oxc_formatter/src/write/type_parameters.rs index a3fb02d073e0e..4a6d3bfb3ebeb 100644 --- a/crates/oxc_formatter/src/write/type_parameters.rs +++ b/crates/oxc_formatter/src/write/type_parameters.rs @@ -69,7 +69,8 @@ impl<'a> Format<'a> for AstNode<'a, Vec<'a, TSTypeParameter<'a>>> { let trailing_separator = if self.len() == 1 // This only concern sources that allow JSX or a restricted standard variant. && f.context().source_type().is_jsx() - && matches!(self.grand_parent(), AstNodes::ArrowFunctionExpression(_)) + && !matches!(self.parent, AstNodes::Dummy()) + && matches!(self.parent.parent(), AstNodes::ArrowFunctionExpression(_)) // Ignore Type parameter with an `extends` clause or a default type. && !self.first().is_some_and(|t| t.constraint().is_some() || t.default().is_some()) { @@ -108,23 +109,63 @@ impl<'a> Format<'a> for FormatTSTypeParameters<'a, '_> { fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { let params = self.decl.params(); if params.is_empty() && self.options.is_type_or_interface_decl { - write!(f, "<>") - } else { - write!( - f, - [group(&format_args!("<", format_once(|f| { - if matches!(self.decl.ancestors().nth(2), Some(AstNodes::CallExpression(call)) if is_test_call_expression(call)) - { - f.join_nodes_with_space().entries_with_trailing_separator(params, ",", TrailingSeparator::Omit).finish() + // Handle dangling comments inside empty type parameters + let comments = f.context().comments().comments_before(self.decl.span.end); + let indent = if comments.iter().any(|c| c.is_line()) { + DanglingIndentMode::Soft + } else { + DanglingIndentMode::None + }; + return write!(f, ["<", FormatDanglingComments::Comments { comments, indent }, ">"]); + } + if params.is_empty() { + // Handle dangling comments inside empty type parameters + let comments = f.context().comments().comments_before(self.decl.span.end); + let indent = if comments.iter().any(|c| c.is_line()) { + DanglingIndentMode::Soft + } else { + DanglingIndentMode::None + }; + return write!(f, ["<", FormatDanglingComments::Comments { comments, indent }, ">"]); + } + write!( + f, + [group(&format_args!( + "<", + format_once(|f| { + // Check if this type parameter declaration is inside a test call expression + // by walking up the parent chain + let mut current_parent = Some(self.decl.parent); + let mut is_test_call = false; + + while let Some(parent) = current_parent { + // Stop at root (Dummy node provides natural termination) + if matches!(parent, AstNodes::Dummy()) { + break; + } + + if let AstNodes::CallExpression(call) = parent + && is_test_call_expression(call) + { + is_test_call = true; + break; + } + + current_parent = Some(parent.parent()); + } + + if is_test_call { + f.join_nodes_with_space() + .entries_with_trailing_separator(params, ",", TrailingSeparator::Omit) + .finish() } else { soft_block_indent(¶ms).fmt(f) - }?; - - format_dangling_comments(self.decl.span).with_soft_block_indent().fmt(f) - }), ">")) - .with_group_id(self.options.group_id)] - ) - } + } + }), + ">" + )) + .with_group_id(self.options.group_id)] + ) } } diff --git a/crates/oxc_formatter/tests/fixtures/js/arguments/empty-lines.js.snap b/crates/oxc_formatter/tests/fixtures/js/arguments/empty-lines.js.snap index 8ae11d18029f3..60434b1eb9bae 100644 --- a/crates/oxc_formatter/tests/fixtures/js/arguments/empty-lines.js.snap +++ b/crates/oxc_formatter/tests/fixtures/js/arguments/empty-lines.js.snap @@ -53,20 +53,13 @@ call(() => { // ... }, "good"); -call( - () => { - // ... - }, - "good", -); - -call( - () => { - // ... - }, +call(() => { + // ... +}, "good"); - "good", -); +call(() => { + // ... +}, "good"); call( () => { diff --git a/crates/oxc_formatter/tests/fixtures/js/assignments/parenthesis.js.snap b/crates/oxc_formatter/tests/fixtures/js/assignments/parenthesis.js.snap index 80d67f1f7c7bc..bb3e9e0cc1b92 100644 --- a/crates/oxc_formatter/tests/fixtures/js/assignments/parenthesis.js.snap +++ b/crates/oxc_formatter/tests/fixtures/js/assignments/parenthesis.js.snap @@ -5,6 +5,6 @@ source: crates/oxc_formatter/tests/fixtures/mod.rs object[key = "a" + "b"]; ==================== Output ==================== -object[(key = "a" + "b")]; +object[key = "a" + "b"]; ===================== End ===================== diff --git a/crates/oxc_formatter/tests/fixtures/js/calls/blank-line.js.snap b/crates/oxc_formatter/tests/fixtures/js/calls/blank-line.js.snap index 8c2e4b58036fc..3f76f32a32225 100644 --- a/crates/oxc_formatter/tests/fixtures/js/calls/blank-line.js.snap +++ b/crates/oxc_formatter/tests/fixtures/js/calls/blank-line.js.snap @@ -12,8 +12,12 @@ gen "b"); ==================== Output ==================== -gen("a"); +gen( + "a", +); -gen("b"); +gen( + "b", +); ===================== End ===================== diff --git a/crates/oxc_formatter/tests/fixtures/js/comments/arrow.js.snap b/crates/oxc_formatter/tests/fixtures/js/comments/arrow.js.snap index b178b9f68e8a5..c846543ee6bce 100644 --- a/crates/oxc_formatter/tests/fixtures/js/comments/arrow.js.snap +++ b/crates/oxc_formatter/tests/fixtures/js/comments/arrow.js.snap @@ -31,32 +31,32 @@ call( ); ==================== Output ==================== -() => - // comment - []; +() => // comment +[]; -() => - // comment - ({}); +() => // comment +({}); -() /* comment1 */ => /* comment2 */ {}; +() => /* comment1 */ /* comment2 */ {}; -() /**/ => - // - () /**/ => - /**/ - () /**/ => /**/ { +() => + /**/ // + () => + /**/ /**/ + () => /**/ /**/ { // }; -call(() /* comment1 */ => /* comment2 */ {}); +call(() => /* comment1 */ /* comment2 */ {}); -call(() /**/ => - // - () /**/ => - /**/ - () /**/ => /**/ { - // -}); +call( + () => + /**/ // + () => + /**/ /**/ + () => /**/ /**/ { + // + }, +); ===================== End ===================== diff --git a/crates/oxc_formatter/tests/fixtures/js/comments/computed-member.js.snap b/crates/oxc_formatter/tests/fixtures/js/comments/computed-member.js.snap index 592c80b7edac6..878da46615931 100644 --- a/crates/oxc_formatter/tests/fixtures/js/comments/computed-member.js.snap +++ b/crates/oxc_formatter/tests/fixtures/js/comments/computed-member.js.snap @@ -18,7 +18,7 @@ prop[ ] = shouldCast; let handler = - props[(handlerName = toHandlerKey(event))] || // also try camelCase event handler (#2249) - props[(handlerName = toHandlerKey(camelize(event)))]; + props[handlerName = toHandlerKey(event)] || // also try camelCase event handler (#2249) + props[handlerName = toHandlerKey(camelize(event))]; ===================== End ===================== diff --git a/crates/oxc_formatter/tests/fixtures/js/jsx/arrow-expression.jsx.snap b/crates/oxc_formatter/tests/fixtures/js/jsx/arrow-expression.jsx.snap index 7cb339de26a8d..294de7ab29090 100644 --- a/crates/oxc_formatter/tests/fixtures/js/jsx/arrow-expression.jsx.snap +++ b/crates/oxc_formatter/tests/fixtures/js/jsx/arrow-expression.jsx.snap @@ -19,20 +19,19 @@ source: crates/oxc_formatter/tests/fixtures/mod.rs ==================== Output ==================== <>
- { - () => - function A() { - A(); - } /* comment */ + {() => + function A() { + A(); + } + /* comment */ }
- { - /* comment */ () => - function A() { - A(); - } + {/* comment */ () => + function A() { + A(); + } }
; diff --git a/crates/oxc_formatter/tests/ir_transform/sort_imports.rs b/crates/oxc_formatter/tests/ir_transform/sort_imports.rs index 2b79d06d3eb2d..a1689290ee11e 100644 --- a/crates/oxc_formatter/tests/ir_transform/sort_imports.rs +++ b/crates/oxc_formatter/tests/ir_transform/sort_imports.rs @@ -833,30 +833,28 @@ import { z } from "z"; fn should_groups_and_sorts_by_type_and_source() { assert_format( r#" -import type { T } from "t"; - import { c1, c2, c3, c4 } from "c"; -import { e1 } from "e/a"; import { e2 } from "e/b"; -import fs from "fs"; +import { e1 } from "e/a"; import path from "path"; -import type { I } from "~/i"; - import { b1, b2 } from "~/b"; +import type { I } from "~/i"; +import type { D } from "./d"; +import fs from "fs"; import { c1 } from "~/c"; import { i1, i2, i3 } from "~/i"; import type { A } from "."; import type { F } from "../f"; -import type { D } from "./d"; +import h from "../../h"; import type { H } from "./index.d.ts"; import a from "."; -import h from "../../h"; +import type { T } from "t"; +import "./style.css"; import { j } from "../j"; import { K, L, M } from "../k"; -import "./style.css"; "#, r#"{ "experimentalSortImports": {} }"#, r#" @@ -881,32 +879,35 @@ import type { H } from "./index.d.ts"; import a from "."; import h from "../../h"; +import "./style.css"; import { j } from "../j"; import { K, L, M } from "../k"; -import "./style.css"; "#, ); + // Input is already in the correct order, should remain unchanged assert_format( r#" +import type { T } from "t"; + import { c1, c2, c3, c4 } from "c"; -import { e2 } from "e/b"; import { e1 } from "e/a"; +import { e2 } from "e/b"; +import fs from "fs"; import path from "path"; -import { b1, b2 } from "~/b"; import type { I } from "~/i"; -import type { D } from "./d"; -import fs from "fs"; + +import { b1, b2 } from "~/b"; import { c1 } from "~/c"; import { i1, i2, i3 } from "~/i"; import type { A } from "."; import type { F } from "../f"; -import h from "../../h"; +import type { D } from "./d"; import type { H } from "./index.d.ts"; import a from "."; -import type { T } from "t"; +import h from "../../h"; import "./style.css"; import { j } from "../j"; import { K, L, M } from "../k"; @@ -939,7 +940,6 @@ import { j } from "../j"; import { K, L, M } from "../k"; "#, ); - // Ignore comments assert_format( r#" diff --git a/crates/oxc_isolated_declarations/CHANGELOG.md b/crates/oxc_isolated_declarations/CHANGELOG.md index a1551bb732670..c44e6b48567e8 100644 --- a/crates/oxc_isolated_declarations/CHANGELOG.md +++ b/crates/oxc_isolated_declarations/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 🐛 Bug Fixes + +- 2c23e15 isolated-declarations: Incorrectly dropping namespace even if it is being referenced (#15123) (Copilot) + diff --git a/crates/oxc_isolated_declarations/Cargo.toml b/crates/oxc_isolated_declarations/Cargo.toml index dded9017a9479..cb93ed60c10fc 100644 --- a/crates/oxc_isolated_declarations/Cargo.toml +++ b/crates/oxc_isolated_declarations/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_isolated_declarations" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_language_server/src/backend.rs b/crates/oxc_language_server/src/backend.rs index b5249a0f8135e..d1d3c00c33937 100644 --- a/crates/oxc_language_server/src/backend.rs +++ b/crates/oxc_language_server/src/backend.rs @@ -151,7 +151,7 @@ impl LanguageServer for Backend { version: Some(server_version.to_string()), }), offset_encoding: None, - capabilities: capabilities.into(), + capabilities: capabilities.server_capabilities(&self.tool_builders), }) } diff --git a/crates/oxc_language_server/src/capabilities.rs b/crates/oxc_language_server/src/capabilities.rs index ea4fa8c29fd9d..0503b1016173d 100644 --- a/crates/oxc_language_server/src/capabilities.rs +++ b/crates/oxc_language_server/src/capabilities.rs @@ -5,7 +5,7 @@ use tower_lsp_server::lsp_types::{ WorkDoneProgressOptions, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, }; -use crate::linter::{CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC, FIX_ALL_COMMAND_ID}; +use crate::ToolBuilder; #[derive(Clone, Default)] pub struct Capabilities { @@ -57,9 +57,15 @@ impl From for Capabilities { } } -impl From for ServerCapabilities { - fn from(value: Capabilities) -> Self { - Self { +impl Capabilities { + pub fn server_capabilities(&self, tools: &[Box]) -> ServerCapabilities { + let code_action_kinds: Vec = + tools.iter().flat_map(|tool| tool.provided_code_action_kinds()).collect(); + + let commands: Vec = + tools.iter().flat_map(|tool| tool.provided_commands()).collect(); + + ServerCapabilities { text_document_sync: Some(TextDocumentSyncCapability::Options( TextDocumentSyncOptions { change: Some(TextDocumentSyncKind::FULL), @@ -77,12 +83,9 @@ impl From for ServerCapabilities { }), file_operations: None, }), - code_action_provider: if value.code_action_provider { + code_action_provider: if self.code_action_provider && !code_action_kinds.is_empty() { Some(CodeActionProviderCapability::Options(CodeActionOptions { - code_action_kinds: Some(vec![ - CodeActionKind::QUICKFIX, - CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC, - ]), + code_action_kinds: Some(code_action_kinds), work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None, }, @@ -91,11 +94,8 @@ impl From for ServerCapabilities { } else { None }, - execute_command_provider: if value.workspace_execute_command { - Some(ExecuteCommandOptions { - commands: vec![FIX_ALL_COMMAND_ID.to_string()], - ..Default::default() - }) + execute_command_provider: if self.workspace_execute_command && !commands.is_empty() { + Some(ExecuteCommandOptions { commands, ..Default::default() }) } else { None }, diff --git a/crates/oxc_language_server/src/linter/mod.rs b/crates/oxc_language_server/src/linter/mod.rs index 9d787eb3019f5..c8c05c4ea6587 100644 --- a/crates/oxc_language_server/src/linter/mod.rs +++ b/crates/oxc_language_server/src/linter/mod.rs @@ -8,8 +8,6 @@ mod server_linter; #[cfg(test)] mod tester; -pub use code_actions::CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC; -pub use commands::FIX_ALL_COMMAND_ID; pub use server_linter::ServerLinterBuilder; const LINT_CONFIG_FILE: &str = ".oxlintrc.json"; diff --git a/crates/oxc_language_server/src/linter/server_linter.rs b/crates/oxc_language_server/src/linter/server_linter.rs index d7493d5292c02..b1b0ea6703401 100644 --- a/crates/oxc_language_server/src/linter/server_linter.rs +++ b/crates/oxc_language_server/src/linter/server_linter.rs @@ -21,8 +21,11 @@ use oxc_linter::{ use crate::{ ConcurrentHashMap, linter::{ - CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC, LINT_CONFIG_FILE, - code_actions::{apply_all_fix_code_action, apply_fix_code_actions, fix_all_text_edit}, + LINT_CONFIG_FILE, + code_actions::{ + CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC, apply_all_fix_code_action, apply_fix_code_actions, + fix_all_text_edit, + }, commands::{FIX_ALL_COMMAND_ID, FixAllCommandArgs}, config_walker::ConfigWalker, error_with_position::DiagnosticReport, @@ -140,6 +143,12 @@ impl ServerLinterBuilder { } impl ToolBuilder for ServerLinterBuilder { + fn provided_code_action_kinds(&self) -> Vec { + vec![CodeActionKind::QUICKFIX, CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC] + } + fn provided_commands(&self) -> Vec { + vec![FIX_ALL_COMMAND_ID.to_string()] + } fn build_boxed(&self, root_uri: &Uri, options: serde_json::Value) -> Box { Box::new(ServerLinterBuilder::build(root_uri, options)) } diff --git a/crates/oxc_language_server/src/tool.rs b/crates/oxc_language_server/src/tool.rs index 5b9fb1455202e..4026f21d27c03 100644 --- a/crates/oxc_language_server/src/tool.rs +++ b/crates/oxc_language_server/src/tool.rs @@ -7,6 +7,19 @@ use tower_lsp_server::{ }; pub trait ToolBuilder: Send + Sync { + /// Get the commands provided by this tool. + /// This will be used to register the commands with the LSP Client. + fn provided_commands(&self) -> Vec { + Vec::new() + } + + /// Get the code action kinds provided by this tool. + /// This will be used to register the code action kinds with the LSP Client. + fn provided_code_action_kinds(&self) -> Vec { + Vec::new() + } + + /// Build a boxed instance of the tool for the given root URI and options. fn build_boxed(&self, root_uri: &Uri, options: serde_json::Value) -> Box; } diff --git a/crates/oxc_linter/src/ast_util.rs b/crates/oxc_linter/src/ast_util.rs index a27263ac5d8df..b536cb411ccc3 100644 --- a/crates/oxc_linter/src/ast_util.rs +++ b/crates/oxc_linter/src/ast_util.rs @@ -205,12 +205,6 @@ pub fn get_enclosing_function<'a, 'b>( } } -/// Returns if `arg` is the `n`th (0-indexed) argument of `call`. -pub fn is_nth_argument<'a>(call: &CallExpression<'a>, arg: &Argument<'a>, n: usize) -> bool { - let nth = &call.arguments[n]; - nth.span() == arg.span() -} - /// Jump to the outer most of chained parentheses if any pub fn outermost_paren<'a, 'b>( node: &'b AstNode<'a>, @@ -939,3 +933,44 @@ pub fn get_outer_member_expression<'a, 'b>( _ => None, } } +/// Check if a node's span is exactly equal to any argument span in a call or new expression +#[inline] +pub fn is_node_exact_call_argument<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool { + let parent = ctx.nodes().parent_node(node.id()); + + match parent.kind() { + AstKind::CallExpression(call) => { + if call.arguments.is_empty() { + return false; + } + let node_span = node.span(); // Only compute span when needed + call.arguments.iter().any(|arg| arg.span() == node_span) + } + AstKind::NewExpression(new_expr) => { + if new_expr.arguments.is_empty() { + return false; + } + let node_span = node.span(); // Only compute span when needed + new_expr.arguments.iter().any(|arg| arg.span() == node_span) + } + _ => false, + } +} + +/// Check if a node's span is contained within any argument span in a call expression +#[inline] +pub fn is_node_within_call_argument<'a>( + node: &AstNode<'a>, + call: &CallExpression<'a>, + target_arg_index: usize, +) -> bool { + // Early exit for out-of-bounds index + if target_arg_index >= call.arguments.len() { + return false; + } + + let target_arg = &call.arguments[target_arg_index]; // Direct indexing, no Option unwrap + let node_span = node.span(); + let arg_span = target_arg.span(); + node_span.start >= arg_span.start && node_span.end <= arg_span.end +} diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index ab2bcb554b5a8..408ba3aa21c92 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -767,7 +767,6 @@ impl RuleRunner for crate::rules::eslint::no_unsafe_negation::NoUnsafeNegation { impl RuleRunner for crate::rules::eslint::no_unsafe_optional_chaining::NoUnsafeOptionalChaining { const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[ - AstType::Argument, AstType::ArrayExpression, AstType::AssignmentExpression, AstType::AssignmentPattern, diff --git a/crates/oxc_linter/src/rules/eslint/array_callback_return/mod.rs b/crates/oxc_linter/src/rules/eslint/array_callback_return/mod.rs index 030a8c223da29..f1ce2beaa53af 100644 --- a/crates/oxc_linter/src/rules/eslint/array_callback_return/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/array_callback_return/mod.rs @@ -12,7 +12,7 @@ use serde_json::Value; use self::return_checker::{StatementReturnStatus, check_function_body}; use crate::{ AstNode, - ast_util::{get_enclosing_function, is_nth_argument, outermost_paren}, + ast_util::{get_enclosing_function, outermost_paren}, context::LintContext, rule::Rule, }; @@ -159,7 +159,6 @@ pub fn get_array_method_name<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> O // foo.every(nativeFoo || function foo() { ... }) AstKind::LogicalExpression(_) | AstKind::ConditionalExpression(_) - | AstKind::Argument(_) | AstKind::ParenthesizedExpression(_) | AstKind::ChainExpression(_) => { current_node = parent; @@ -189,10 +188,6 @@ pub fn get_array_method_name<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> O } AstKind::CallExpression(call) => { - let AstKind::Argument(current_node_arg) = current_node.kind() else { - return None; - }; - let callee = call.callee.get_inner_expression(); let callee = if let Some(member) = callee.as_member_expression() { member @@ -205,7 +200,10 @@ pub fn get_array_method_name<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> O // Array.from if callee.is_specific_member_access("Array", "from") { // Check that current node is parent's second argument - if call.arguments.len() == 2 && is_nth_argument(call, current_node_arg, 1) { + if call.arguments.len() == 2 + && let Some(call_arg) = call.arguments[1].as_expression() + && call_arg.span() == current_node.kind().span() + { return Some("from"); } } @@ -216,7 +214,10 @@ pub fn get_array_method_name<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> O if TARGET_METHODS.contains(&array_method) // Check that current node is parent's first argument && call.arguments.len() == 1 - && is_nth_argument(call, current_node_arg, 0) + && let Some(call_arg) = call.arguments.first() + && call_arg + .as_expression() + .is_some_and(|arg| arg.span() == current_node.kind().span()) { return Some(array_method); } diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index c31d889ea115c..b0bd13bd4a440 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -267,17 +267,22 @@ fn is_recursive_function(func: &Function, func_name: &str, ctx: &LintContext) -> if let Some(binding) = ctx.scoping().find_binding(func_scope_id, func_name) { return ctx.semantic().symbol_references(binding).any(|reference| { let parent = ctx.nodes().parent_node(reference.node_id()); - if matches!(parent.kind(), AstKind::CallExpression(_)) { - ctx.nodes().ancestors(reference.node_id()).any(|ancestor| { - if let AstKind::Function(f) = ancestor.kind() { - f.scope_id.get() == Some(func_scope_id) - } else { - false - } - }) - } else { - false + // Check if this reference is the callee of a call expression (direct recursive call) + if let AstKind::CallExpression(call_expr) = parent.kind() { + // Only consider it recursive if the reference is the callee, not an argument + if call_expr.callee.span() + == ctx.nodes().get_node(reference.node_id()).kind().span() + { + return ctx.nodes().ancestors(reference.node_id()).any(|ancestor| { + if let AstKind::Function(f) = ancestor.kind() { + f.scope_id.get() == Some(func_scope_id) + } else { + false + } + }); + } } + false }); } diff --git a/crates/oxc_linter/src/rules/eslint/getter_return.rs b/crates/oxc_linter/src/rules/eslint/getter_return.rs index 4d07d20604c9c..a91780ec76696 100644 --- a/crates/oxc_linter/src/rules/eslint/getter_return.rs +++ b/crates/oxc_linter/src/rules/eslint/getter_return.rs @@ -169,9 +169,8 @@ impl GetterReturn { let parent_2 = ctx.nodes().parent_node(parent.id()); let parent_3 = ctx.nodes().parent_node(parent_2.id()); - let parent_4 = ctx.nodes().parent_node(parent_3.id()); // handle (X()) - match parent_4.kind() { + match parent_3.kind() { AstKind::ParenthesizedExpression(p) => { if Self::handle_paren_expr(&p.expression) { return true; @@ -185,9 +184,9 @@ impl GetterReturn { _ => {} } + let parent_4 = ctx.nodes().parent_node(parent_3.id()); let parent_5 = ctx.nodes().parent_node(parent_4.id()); - let parent_6 = ctx.nodes().parent_node(parent_5.id()); - match parent_6.kind() { + match parent_5.kind() { AstKind::ParenthesizedExpression(p) => { if Self::handle_paren_expr(&p.expression) { return true; diff --git a/crates/oxc_linter/src/rules/eslint/max_nested_callbacks.rs b/crates/oxc_linter/src/rules/eslint/max_nested_callbacks.rs index 0394e12e3c994..bcd41ce2cd610 100644 --- a/crates/oxc_linter/src/rules/eslint/max_nested_callbacks.rs +++ b/crates/oxc_linter/src/rules/eslint/max_nested_callbacks.rs @@ -129,7 +129,7 @@ fn is_callback<'a>(node: &AstNode<'a>, semantic: &Semantic<'a>) -> bool { is_function_node(node) && matches!( iter_outer_expressions(semantic.nodes(), node.id()).next(), - Some(AstKind::Argument(_)) + Some(AstKind::CallExpression(_)) ) } diff --git a/crates/oxc_linter/src/rules/eslint/no_eval.rs b/crates/oxc_linter/src/rules/eslint/no_eval.rs index eea13e45c0661..1c0ca01937d3d 100644 --- a/crates/oxc_linter/src/rules/eslint/no_eval.rs +++ b/crates/oxc_linter/src/rules/eslint/no_eval.rs @@ -119,13 +119,12 @@ impl Rule for NoEval { for reference_id in references { let reference = ctx.scoping().get_reference(*reference_id); let node = ctx.nodes().get_node(reference.node_id()); - let mut parent = Self::outermost_mem_expr(node, ctx).unwrap(); if name == "eval" { - if !matches!(parent.kind(), AstKind::CallExpression(_)) { - ctx.diagnostic(no_eval_diagnostic(node.span())); - } + ctx.diagnostic(no_eval_diagnostic(node.span())); } else { + let mut parent = Self::outermost_mem_expr(node, ctx).unwrap(); + loop { match parent.kind() { AstKind::StaticMemberExpression(mem_expr) => { diff --git a/crates/oxc_linter/src/rules/eslint/no_extend_native.rs b/crates/oxc_linter/src/rules/eslint/no_extend_native.rs index 58110ba5b813e..7921daa8f577d 100644 --- a/crates/oxc_linter/src/rules/eslint/no_extend_native.rs +++ b/crates/oxc_linter/src/rules/eslint/no_extend_native.rs @@ -150,8 +150,12 @@ fn get_define_property_call<'a>( for parent in ctx.nodes().ancestors(node.id()) { if let AstKind::CallExpression(call_expr) = parent.kind() && is_define_property_call(call_expr) + && let Some(first_arg) = call_expr.arguments.first() { - return Some(parent); + let arg_span = first_arg.span(); + if arg_span.contains_inclusive(node.span()) { + return Some(parent); + } } } None @@ -217,7 +221,6 @@ fn get_prototype_property_accessed<'a>( return None; }; let parent = ctx.nodes().parent_node(node.id()); - let mut prototype_node = Some(parent); match parent.kind() { prop_access_expr if prop_access_expr.is_member_expression_kind() => { let prop_name = prop_access_expr @@ -226,14 +229,23 @@ fn get_prototype_property_accessed<'a>( if prop_name != "prototype" { return None; } + // Check if this member expression is wrapped in a ChainExpression let grandparent_node = ctx.nodes().parent_node(parent.id()); + let result_node = if let AstKind::ChainExpression(_) = grandparent_node.kind() { + // Return the ChainExpression + grandparent_node + } else { + // Return the MemberExpression + parent + }; - if let AstKind::ChainExpression(_) = grandparent_node.kind() { - let grandparent_parent = ctx.nodes().parent_node(grandparent_node.id()); - prototype_node = Some(grandparent_parent); + // Check if the result is wrapped in parentheses + let great_grandparent_node = ctx.nodes().parent_node(result_node.id()); + if let AstKind::ParenthesizedExpression(_) = great_grandparent_node.kind() { + Some(great_grandparent_node) + } else { + Some(result_node) } - - prototype_node } _ => None, } diff --git a/crates/oxc_linter/src/rules/eslint/no_extra_boolean_cast.rs b/crates/oxc_linter/src/rules/eslint/no_extra_boolean_cast.rs index 42daf1aac6ff3..56f203c5bc32e 100644 --- a/crates/oxc_linter/src/rules/eslint/no_extra_boolean_cast.rs +++ b/crates/oxc_linter/src/rules/eslint/no_extra_boolean_cast.rs @@ -211,12 +211,7 @@ fn is_unary_negation(node: &AstNode) -> bool { fn get_real_parent<'a, 'b>(node: &AstNode, ctx: &'a LintContext<'b>) -> Option<&'a AstNode<'b>> { ctx.nodes().ancestors(node.id()).find(|parent| { - !matches!( - parent.kind(), - AstKind::Argument(_) - | AstKind::ParenthesizedExpression(_) - | AstKind::ChainExpression(_) - ) + !matches!(parent.kind(), AstKind::ParenthesizedExpression(_) | AstKind::ChainExpression(_)) }) } diff --git a/crates/oxc_linter/src/rules/eslint/no_import_assign.rs b/crates/oxc_linter/src/rules/eslint/no_import_assign.rs index d614264c36cbe..544415b8881c6 100644 --- a/crates/oxc_linter/src/rules/eslint/no_import_assign.rs +++ b/crates/oxc_linter/src/rules/eslint/no_import_assign.rs @@ -159,7 +159,7 @@ impl Rule for NoImportAssign { /// - `Reflect.setPrototypeOf` fn is_argument_of_well_known_mutation_function(node_id: NodeId, ctx: &LintContext<'_>) -> bool { let current_node = ctx.nodes().get_node(node_id); - let call_expression_node = ctx.nodes().parent_kind(ctx.nodes().parent_id(node_id)); + let call_expression_node = ctx.nodes().parent_kind(node_id); let AstKind::CallExpression(expr) = call_expression_node else { return false; diff --git a/crates/oxc_linter/src/rules/eslint/no_magic_numbers.rs b/crates/oxc_linter/src/rules/eslint/no_magic_numbers.rs index 29bd46662d263..96e8c82fff535 100644 --- a/crates/oxc_linter/src/rules/eslint/no_magic_numbers.rs +++ b/crates/oxc_linter/src/rules/eslint/no_magic_numbers.rs @@ -355,8 +355,8 @@ fn is_detectable_object(parent_kind: &AstKind<'_>) -> bool { matches!(parent_kind, AstKind::ObjectExpression(_) | AstKind::ObjectProperty(_)) } -fn is_parse_int_radix(parent_parent_node: &AstNode<'_>) -> bool { - let AstKind::CallExpression(expression) = parent_parent_node.kind() else { +fn is_parse_int_radix(parent_kind: &AstKind<'_>) -> bool { + let AstKind::CallExpression(expression) = parent_kind else { return false; }; @@ -483,7 +483,7 @@ impl NoMagicNumbers { let parent_parent = nodes.parent_node(parent.id()); - if is_parse_int_radix(parent_parent) { + if is_parse_int_radix(&parent.kind()) { return true; } diff --git a/crates/oxc_linter/src/rules/eslint/no_new_func.rs b/crates/oxc_linter/src/rules/eslint/no_new_func.rs index da4e17e995166..a85d8271f1697 100644 --- a/crates/oxc_linter/src/rules/eslint/no_new_func.rs +++ b/crates/oxc_linter/src/rules/eslint/no_new_func.rs @@ -2,7 +2,7 @@ use oxc_ast::{AstKind, ast::IdentifierReference}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_semantic::IsGlobalReference; -use oxc_span::Span; +use oxc_span::{GetSpan, Span}; use crate::{AstNode, context::LintContext, rule::Rule}; @@ -78,6 +78,10 @@ impl Rule for NoNewFunc { return; }; + if !parent_call_expr.callee.span().contains_inclusive(node.span()) { + return; + } + let Some(static_property_name) = &member_expr.static_property_name().map(|s| s.as_str()) else { diff --git a/crates/oxc_linter/src/rules/eslint/no_unsafe_optional_chaining.rs b/crates/oxc_linter/src/rules/eslint/no_unsafe_optional_chaining.rs index af2ef54fd0909..13c5775cd468a 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unsafe_optional_chaining.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unsafe_optional_chaining.rs @@ -1,9 +1,6 @@ use oxc_ast::{ AstKind, - ast::{ - Argument, ArrayExpressionElement, AssignmentTarget, Expression, - match_assignment_target_pattern, - }, + ast::{ArrayExpressionElement, AssignmentTarget, Expression, match_assignment_target_pattern}, }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; @@ -125,9 +122,6 @@ impl Rule for NoUnsafeOptionalChaining { AstKind::AssignmentPattern(pat) if pat.left.kind.is_destructuring_pattern() => { Self::check_unsafe_usage(&pat.right, ctx); } - AstKind::Argument(Argument::SpreadElement(elem)) => { - Self::check_unsafe_usage(&elem.argument, ctx); - } AstKind::VariableDeclarator(decl) if decl.id.kind.is_destructuring_pattern() => { if let Some(expr) = &decl.init { Self::check_unsafe_usage(expr, ctx); diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_private_class_members.rs b/crates/oxc_linter/src/rules/eslint/no_unused_private_class_members.rs index fd888ec452861..ee6b37e87aa60 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_private_class_members.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_private_class_members.rs @@ -215,7 +215,6 @@ fn is_value_context(parent: &AstNode, child: &AstNode, semantic: &Semantic<'_>) | AstKind::ArrayExpression(_) | AstKind::ObjectProperty(_) | AstKind::JSXExpressionContainer(_) - | AstKind::Argument(_) | AstKind::ChainExpression(_) | AstKind::StaticMemberExpression(_) | AstKind::ComputedMemberExpression(_) diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs index fbfb6ba18566c..8ea896ffb9a03 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs @@ -416,11 +416,17 @@ impl<'a> Symbol<'_, 'a> { // used by others AstKind::VariableDeclarator(_) | AstKind::JSXExpressionContainer(_) - | AstKind::Argument(_) | AstKind::PropertyDefinition(_) => { // definitely used, short-circuit return false; } + AstKind::CallExpression(call_expr) + if call_expr.arguments_span().is_some_and(|span| { + span.contains_inclusive(self.nodes().get_node(reference.node_id()).span()) + }) => + { + return false; + } // When symbol is being assigned a new value, we flag the reference // as only affecting itself until proven otherwise. AstKind::UpdateExpression(UpdateExpression { argument, .. }) => { diff --git a/crates/oxc_linter/src/rules/eslint/prefer_object_spread.rs b/crates/oxc_linter/src/rules/eslint/prefer_object_spread.rs index e190503f75fcf..f65f2fd759289 100644 --- a/crates/oxc_linter/src/rules/eslint/prefer_object_spread.rs +++ b/crates/oxc_linter/src/rules/eslint/prefer_object_spread.rs @@ -143,12 +143,13 @@ impl Rule for PreferObjectSpread { let fixer = fixer.for_multifix(); let mut rule_fixes = fixer.new_fix_with_capacity(2 + call_expr.arguments.len() * 5); + let parent_kind = ctx.nodes().parent_kind(node.id()); let needs_paren = !matches!( - ctx.nodes().parent_kind(node.id()), + parent_kind, AstKind::VariableDeclarator(_) | AstKind::ArrayExpression(_) | AstKind::ReturnStatement(_) - | AstKind::Argument(_) + | AstKind::CallExpression(_) | AstKind::ObjectProperty(_) | AstKind::AssignmentExpression(_) ); diff --git a/crates/oxc_linter/src/rules/eslint/prefer_promise_reject_errors.rs b/crates/oxc_linter/src/rules/eslint/prefer_promise_reject_errors.rs index 479bb41d0e3ab..c7bedaf044f84 100644 --- a/crates/oxc_linter/src/rules/eslint/prefer_promise_reject_errors.rs +++ b/crates/oxc_linter/src/rules/eslint/prefer_promise_reject_errors.rs @@ -5,7 +5,7 @@ use oxc_ast::{ }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; +use oxc_span::{GetSpan, Span}; use schemars::JsonSchema; use crate::{ @@ -153,7 +153,11 @@ fn check_reject_in_function( ctx.symbol_references(reject_arg.symbol_id()).for_each(|reference| { if let AstKind::CallExpression(call_expr) = ctx.nodes().parent_kind(reference.node_id()) { - check_reject_call(call_expr, ctx, allow_empty_reject); + let ref_node = ctx.nodes().get_node(reference.node_id()); + + if call_expr.callee.span().contains_inclusive(ref_node.span()) { + check_reject_call(call_expr, ctx, allow_empty_reject); + } } }); return; diff --git a/crates/oxc_linter/src/rules/jest/no_conditional_expect.rs b/crates/oxc_linter/src/rules/jest/no_conditional_expect.rs index 20c0731cce1ac..d22a99e1b38eb 100644 --- a/crates/oxc_linter/src/rules/jest/no_conditional_expect.rs +++ b/crates/oxc_linter/src/rules/jest/no_conditional_expect.rs @@ -128,6 +128,29 @@ impl Rule for NoConditionalExpect { } } +fn is_in_test_context<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool { + let mut current = node; + loop { + current = ctx.nodes().parent_node(current.id()); + + if let AstKind::CallExpression(call_expr) = current.kind() { + let jest_node = PossibleJestNode { node: current, original: None }; + if is_type_of_jest_fn_call( + call_expr, + &jest_node, + ctx, + &[JestFnKind::General(JestGeneralFnKind::Test)], + ) { + return true; + } + } + + if matches!(current.kind(), AstKind::Program(_)) { + return false; + } + } +} + fn check_parents<'a>( node: &AstNode<'a>, visited: &mut FxHashSet, @@ -174,19 +197,29 @@ fn check_parents<'a>( let symbol_table = ctx.scoping(); let symbol_id = ident.symbol_id(); - // Consider cases like: - // ```javascript - // function foo() { - // foo() - // } - // ``` - // To avoid infinite loop, we need to check if the function is already visited when - // call `check_parents`. - let boolean = symbol_table.get_resolved_references(symbol_id).any(|reference| { - let parent = ctx.nodes().parent_node(reference.node_id()); - matches!(check_parents(parent, visited, in_conditional, ctx), InConditional(true)) - }); - return InConditional(boolean); + // Check if this function is used in a test context + let is_used_in_test = + symbol_table.get_resolved_references(symbol_id).any(|reference| { + let parent = ctx.nodes().parent_node(reference.node_id()); + + // Check if directly used as test callback + if let AstKind::CallExpression(call_expr) = parent.kind() { + let jest_node = PossibleJestNode { node: parent, original: None }; + if is_type_of_jest_fn_call( + call_expr, + &jest_node, + ctx, + &[JestFnKind::General(JestGeneralFnKind::Test)], + ) { + return true; + } + } + + // Check if called within a test context by traversing from the call site + is_in_test_context(parent, ctx) + }); + + return if is_used_in_test { in_conditional } else { InConditional(false) }; } AstKind::Program(_) => return InConditional(false), _ => {} diff --git a/crates/oxc_linter/src/rules/jest/no_standalone_expect/mod.rs b/crates/oxc_linter/src/rules/jest/no_standalone_expect/mod.rs index 360e2a97cfd9e..a8e0a1ac8757c 100644 --- a/crates/oxc_linter/src/rules/jest/no_standalone_expect/mod.rs +++ b/crates/oxc_linter/src/rules/jest/no_standalone_expect/mod.rs @@ -234,7 +234,7 @@ fn is_var_declarator_or_test_block<'a>( return true; } } - AstKind::Argument(_) | AstKind::ArrayExpression(_) | AstKind::ObjectExpression(_) => { + AstKind::ArrayExpression(_) | AstKind::ObjectExpression(_) => { let mut current = node; loop { let parent = ctx.nodes().parent_node(current.id()); @@ -247,9 +247,7 @@ fn is_var_declarator_or_test_block<'a>( ctx, ); } - AstKind::Argument(_) - | AstKind::ArrayExpression(_) - | AstKind::ObjectExpression(_) => { + AstKind::ArrayExpression(_) | AstKind::ObjectExpression(_) => { current = parent; } _ => break, diff --git a/crates/oxc_linter/src/rules/jest/valid_expect.rs b/crates/oxc_linter/src/rules/jest/valid_expect.rs index 4616349c492fd..8c9f78b8b1fa2 100644 --- a/crates/oxc_linter/src/rules/jest/valid_expect.rs +++ b/crates/oxc_linter/src/rules/jest/valid_expect.rs @@ -282,7 +282,6 @@ fn is_acceptable_return_node<'a, 'b>( match node.kind() { AstKind::ConditionalExpression(_) - | AstKind::Argument(_) | AstKind::ExpressionStatement(_) | AstKind::FunctionBody(_) => { node = ctx.nodes().parent_node(node.id()); @@ -296,6 +295,29 @@ fn is_acceptable_return_node<'a, 'b>( type ParentAndIsFirstItem<'a, 'b> = (&'b AstNode<'a>, bool); +/// Checks if a node should be skipped during parent traversal +fn should_skip_parent_node(node: &AstNode, parent: &AstNode) -> bool { + match parent.kind() { + AstKind::CallExpression(call) => { + // Don't skip arguments to Promise methods - they're semantically important for await detection + if let Some(member_expr) = call.callee.as_member_expression() + && let Expression::Identifier(ident) = member_expr.object() + && ident.name == "Promise" + { + return false; // Never skip Promise method arguments + } + + // For other call expressions, skip if this node is one of the arguments + call.arguments.iter().any(|arg| arg.span() == node.span()) + } + AstKind::NewExpression(new_expr) => { + // Skip if this node is one of the new expression arguments + new_expr.arguments.iter().any(|arg| arg.span() == node.span()) + } + _ => false, + } +} + // Returns the parent node of the given node, ignoring some nodes, // and return whether the first item if parent is an array. fn get_parent_with_ignore<'a, 'b>( @@ -305,7 +327,7 @@ fn get_parent_with_ignore<'a, 'b>( let mut node = node; loop { let parent = ctx.nodes().parent_node(node.id()); - if !matches!(parent.kind(), AstKind::Argument(_)) { + if !should_skip_parent_node(node, parent) { // we don't want to report `Promise.all([invalidExpectCall_1, invalidExpectCall_2])` twice. // so we need mark whether the node is the first item of an array. // if it not the first item, we ignore it in `find_promise_call_expression_node`. diff --git a/crates/oxc_linter/src/rules/jsdoc/require_returns.rs b/crates/oxc_linter/src/rules/jsdoc/require_returns.rs index af6e09f10a610..24e8102e658dc 100644 --- a/crates/oxc_linter/src/rules/jsdoc/require_returns.rs +++ b/crates/oxc_linter/src/rules/jsdoc/require_returns.rs @@ -280,18 +280,11 @@ fn is_promise_resolve_with_value(expr: &Expression, ctx: &LintContext) -> Option // IMO: This is a fault of the original rule design... for resolve_ref in ctx.scoping().get_resolved_references(ident.symbol_id()) { // Check if `resolve` is called with value - match ctx.nodes().parent_kind(resolve_ref.node_id()) { - // `resolve(foo)` - AstKind::CallExpression(call_expr) => { - if !call_expr.arguments.is_empty() { - return Some(true); - } - } - // `foo(resolve)` - AstKind::Argument(_) => { - return Some(true); - } - _ => {} + if let AstKind::CallExpression(call_expr) = + ctx.nodes().parent_kind(resolve_ref.node_id()) + && !call_expr.arguments.is_empty() + { + return Some(true); } } None diff --git a/crates/oxc_linter/src/rules/oxc/only_used_in_recursion.rs b/crates/oxc_linter/src/rules/oxc/only_used_in_recursion.rs index 3c53ba1560f05..71d1d32beab5e 100644 --- a/crates/oxc_linter/src/rules/oxc/only_used_in_recursion.rs +++ b/crates/oxc_linter/src/rules/oxc/only_used_in_recursion.rs @@ -1,8 +1,8 @@ use oxc_ast::{ AstKind, ast::{ - AssignmentTarget, BindingIdentifier, BindingPatternKind, BindingProperty, CallExpression, - Expression, FormalParameters, JSXAttributeItem, JSXElementName, + Argument, AssignmentTarget, BindingIdentifier, BindingPatternKind, BindingProperty, + CallExpression, Expression, FormalParameters, JSXAttributeItem, JSXElementName, }, }; use oxc_diagnostics::OxcDiagnostic; @@ -275,11 +275,7 @@ fn is_argument_only_used_in_recursion<'a>( let function_symbol_id = function_id.symbol_id(); for reference in references { - let AstKind::Argument(argument) = ctx.nodes().parent_kind(reference.node_id()) else { - return false; - }; - let AstKind::CallExpression(call_expr) = - ctx.nodes().parent_kind(ctx.nodes().parent_node(reference.node_id()).id()) + let AstKind::CallExpression(call_expr) = ctx.nodes().parent_kind(reference.node_id()) else { return false; }; @@ -288,7 +284,9 @@ fn is_argument_only_used_in_recursion<'a>( return false; }; - if argument.span() != call_arg.span() { + if let Argument::Identifier(ident) = call_arg + && ident.name != arg.name + { return false; } diff --git a/crates/oxc_linter/src/rules/promise/always_return.rs b/crates/oxc_linter/src/rules/promise/always_return.rs index 65740401efec7..062b28d64b6f5 100644 --- a/crates/oxc_linter/src/rules/promise/always_return.rs +++ b/crates/oxc_linter/src/rules/promise/always_return.rs @@ -199,15 +199,13 @@ impl Rule for AlwaysReturn { if !is_inline_then_function_expression(node, ctx) { return; } - // want Argument - let parent1 = ctx.nodes().parent_node(node.id()); - // want CallExpression - let parent2 = ctx.nodes().parent_node(parent1.id()); - if self.ignore_last_callback && is_last_callback(parent2, ctx) { + + let parent = ctx.nodes().parent_node(node.id()); + if self.ignore_last_callback && is_last_callback(parent, ctx) { return; } if !self.ignore_assignment_variable.is_empty() - && is_last_callback(parent2, ctx) + && is_last_callback(parent, ctx) && has_ignored_assignment(node, &self.ignore_assignment_variable) { return; @@ -240,14 +238,12 @@ fn is_first_argument(node: &AstNode, call_node: &AstNode) -> bool { } fn is_inline_then_function_expression(node: &AstNode, ctx: &LintContext) -> bool { - // want Argument - let parent1 = ctx.nodes().parent_node(node.id()); - // want CallExpression - let parent2 = ctx.nodes().parent_node(parent1.id()); + // in order to be a thenable, the parent must be a CallExpression + let parent = ctx.nodes().parent_node(node.id()); is_function_with_block_statement(node) - && is_member_call(parent2, "then") - && is_first_argument(node, parent2) + && is_member_call(parent, "then") + && is_first_argument(node, parent) } fn is_last_callback(node: &AstNode, ctx: &LintContext) -> bool { diff --git a/crates/oxc_linter/src/rules/promise/no_callback_in_promise.rs b/crates/oxc_linter/src/rules/promise/no_callback_in_promise.rs index 1d58456963dc3..d4d333f159918 100644 --- a/crates/oxc_linter/src/rules/promise/no_callback_in_promise.rs +++ b/crates/oxc_linter/src/rules/promise/no_callback_in_promise.rs @@ -6,6 +6,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{CompactStr, GetSpan, Span}; use schemars::JsonSchema; +use serde::Deserialize; use crate::{AstNode, context::LintContext, rule::Rule}; @@ -18,13 +19,15 @@ fn no_callback_in_promise_diagnostic(span: Span) -> OxcDiagnostic { #[derive(Debug, Default, Clone)] pub struct NoCallbackInPromise(Box); -#[derive(Debug, Clone, JsonSchema)] +#[derive(Debug, Clone, JsonSchema, Deserialize)] #[serde(rename_all = "camelCase", default)] pub struct NoCallbackInPromiseConfig { /// List of callback function names to check for within Promise `then` and `catch` methods. callbacks: Vec, /// List of callback function names to allow within Promise `then` and `catch` methods. exceptions: Vec, + /// Boolean as to whether callbacks in timeout functions like `setTimeout` will err. + timeouts_err: bool, } impl Default for NoCallbackInPromiseConfig { @@ -32,6 +35,7 @@ impl Default for NoCallbackInPromiseConfig { Self { callbacks: vec!["callback".into(), "cb".into(), "done".into(), "next".into()], exceptions: Vec::new(), + timeouts_err: false, } } } @@ -83,22 +87,18 @@ declare_oxc_lint!( config = NoCallbackInPromiseConfig, ); +const TIMEOUT_WHITELIST: [&str; 4] = + ["setImmediate", "setTimeout", "requestAnimationFrame", "nextTick"]; + impl Rule for NoCallbackInPromise { fn from_configuration(value: serde_json::Value) -> Self { - let mut default_config = NoCallbackInPromiseConfig::default(); - - let exceptions: Vec = value - .get(0) - .and_then(|v| v.get("exceptions")) - .and_then(serde_json::Value::as_array) - .map(|v| { - v.iter().filter_map(serde_json::Value::as_str).map(ToString::to_string).collect() - }) + let mut config: NoCallbackInPromiseConfig = value + .as_array() + .and_then(|arr| arr.first()) + .and_then(|value| serde_json::from_value(value.clone()).ok()) .unwrap_or_default(); - - default_config.callbacks.retain(|item| !exceptions.contains(&item.to_string())); - - Self(Box::new(default_config)) + config.callbacks.retain(|item| !config.exceptions.contains(item)); + Self(Box::new(config)) } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { @@ -112,35 +112,56 @@ impl Rule for NoCallbackInPromise { .is_none_or(|id| self.callbacks.binary_search(&id.name.as_str().into()).is_err()); if is_not_callback { + let Some(id) = call_expr + .arguments + .first() + .and_then(|arg| arg.as_expression().and_then(Expression::get_identifier_reference)) + else { + return; + }; + let name = id.name.as_str(); + if self.callbacks.binary_search(&name.into()).is_err() { + return; + } if Self::has_promise_callback(call_expr) { - let Some(id) = call_expr.arguments.first().and_then(|arg| { - arg.as_expression().and_then(Expression::get_identifier_reference) - }) else { - return; - }; - - let name = id.name.as_str(); - if self.callbacks.binary_search(&name.into()).is_ok() { - ctx.diagnostic(no_callback_in_promise_diagnostic(id.span)); - } + ctx.diagnostic(no_callback_in_promise_diagnostic(id.span)); + return; + } else if !self.timeouts_err && Self::is_inside_timeout(node) { + return; + } + } + let ancestors = ctx.nodes().ancestors(node.id()); + for ancestor in ancestors { + if !self.timeouts_err && Self::is_inside_timeout(ancestor) { + break; + } + if Self::is_inside_promise(ancestor, ctx) { + ctx.diagnostic(no_callback_in_promise_diagnostic(node.span())); + break; } - } else if ctx.nodes().ancestors(node.id()).any(|node| Self::is_inside_promise(node, ctx)) { - ctx.diagnostic(no_callback_in_promise_diagnostic(node.span())); } } } impl NoCallbackInPromise { fn is_inside_promise(node: &AstNode, ctx: &LintContext) -> bool { - if !matches!(node.kind(), AstKind::Function(_) | AstKind::ArrowFunctionExpression(_)) - || !matches!(ctx.nodes().parent_kind(node.id()), AstKind::Argument(_)) - { + if !matches!(node.kind(), AstKind::Function(_) | AstKind::ArrowFunctionExpression(_)) { return false; } - ctx.nodes().ancestors(node.id()).nth(1).is_some_and(|node| { - node.kind().as_call_expression().is_some_and(Self::has_promise_callback) - }) + // Check if the parent is a CallExpression with then/catch + let parent = ctx.nodes().parent_node(node.id()); + if let Some(call_expr) = parent.kind().as_call_expression() { + // Check if this function is one of the arguments + let is_argument = call_expr + .arguments + .iter() + .any(|arg| matches!(arg.as_expression(), Some(expr) if expr.span() == node.span())); + + return is_argument && Self::has_promise_callback(call_expr); + } + + false } fn has_promise_callback(call_expr: &CallExpression) -> bool { @@ -152,6 +173,19 @@ impl NoCallbackInPromise { Some("then" | "catch") ) } + + fn is_inside_timeout(node: &AstNode) -> bool { + let Some(call_expr) = node.kind().as_call_expression() else { + return false; + }; + match &call_expr.callee { + Expression::Identifier(ident) => TIMEOUT_WHITELIST.contains(&ident.name.as_str()), + Expression::StaticMemberExpression(static_member_expr) => { + TIMEOUT_WHITELIST.contains(&static_member_expr.property.name.as_str()) + } + _ => false, + } + } } #[test] @@ -163,6 +197,17 @@ fn test() { ("doSomething(function(err) { cb(err) })", None), ("function thing(callback) { callback() }", None), ("doSomething(function(err) { callback(err) })", None), + ("a.then(doSomething)", None), + ("a.then(() => doSomething())", None), + ("a.then(function(err) { doSomething(err) })", None), + ("a.then(function(data) { doSomething(data) }, function(err) { doSomething(err) })", None), + ("a.catch(function(err) { doSomething(err) })", None), + ("whatever.then((err) => { process.nextTick(() => cb()) })", None), + ("whatever.then((err) => { setImmediate(() => cb()) })", None), + ("whatever.then((err) => setImmediate(() => cb()))", None), + ("whatever.then((err) => process.nextTick(() => cb()))", None), + ("whatever.then((err) => process.nextTick(cb))", None), + ("whatever.then((err) => setImmediate(cb))", None), ("let thing = (cb) => cb()", None), ("doSomething(err => cb(err))", None), ("a.then(() => next())", Some(serde_json::json!([{ "exceptions": ["next"] }]))), @@ -185,6 +230,49 @@ fn test() { ("a.then(function(err) { callback(err) })", None), ("a.then(function(data) { callback(data) }, function(err) { callback(err) })", None), ("a.catch(function(err) { callback(err) })", None), + ("a.then(() => doSomething(cb))", None), + ( + "function wait (callback) { + return Promise.resolve() + .then(() => { + setTimeout(callback); + }); +}", + Some(serde_json::json!([{ "timeoutsErr": true }])), + ), + ( + "function wait (callback) { + return Promise.resolve() + .then(() => { + setTimeout(() => callback()); + }); +}", + Some(serde_json::json!([{ "timeoutsErr": true }])), + ), + ( + "whatever.then((err) => { process.nextTick(() => cb()) })", + Some(serde_json::json!([{ "timeoutsErr": true }])), + ), + ( + "whatever.then((err) => { setImmediate(() => cb()) })", + Some(serde_json::json!([{ "timeoutsErr": true }])), + ), + ( + "whatever.then((err) => setImmediate(() => cb()))", + Some(serde_json::json!([{ "timeoutsErr": true }])), + ), + ( + "whatever.then((err) => process.nextTick(() => cb()))", + Some(serde_json::json!([{ "timeoutsErr": true }])), + ), + ( + "whatever.then((err) => process.nextTick(cb))", + Some(serde_json::json!([{ "timeoutsErr": true }])), + ), + ( + "whatever.then((err) => setImmediate(cb))", + Some(serde_json::json!([{ "timeoutsErr": true }])), + ), ]; Tester::new(NoCallbackInPromise::NAME, NoCallbackInPromise::PLUGIN, pass, fail) diff --git a/crates/oxc_linter/src/rules/promise/no_nesting.rs b/crates/oxc_linter/src/rules/promise/no_nesting.rs index 468db8c7947e1..b761bce3bc7d0 100644 --- a/crates/oxc_linter/src/rules/promise/no_nesting.rs +++ b/crates/oxc_linter/src/rules/promise/no_nesting.rs @@ -70,17 +70,25 @@ declare_oxc_lint!( ); fn is_inside_promise(node: &AstNode, ctx: &LintContext) -> bool { - if !matches!(node.kind(), AstKind::Function(_) | AstKind::ArrowFunctionExpression(_)) - || !matches!(ctx.nodes().parent_kind(node.id()), AstKind::Argument(_)) - { + if !matches!(node.kind(), AstKind::Function(_) | AstKind::ArrowFunctionExpression(_)) { return false; } - ctx.nodes().ancestors(node.id()).nth(1).is_some_and(|node| { - node.kind().as_call_expression().is_some_and(|a| { - is_promise(a).is_some_and(|prop_name| prop_name == "then" || prop_name == "catch") - }) - }) + // Check if the parent is a CallExpression with then/catch + let parent = ctx.nodes().parent_node(node.id()); + if let Some(call_expr) = parent.kind().as_call_expression() { + // Check if this function is one of the arguments + let is_argument = call_expr + .arguments + .iter() + .any(|arg| matches!(arg.as_expression(), Some(expr) if expr.span() == node.span())); + + return is_argument + && is_promise(call_expr) + .is_some_and(|prop_name| prop_name == "then" || prop_name == "catch"); + } + + false } /// Gets the closest promise callback function of the nested promise. diff --git a/crates/oxc_linter/src/rules/promise/no_promise_in_callback.rs b/crates/oxc_linter/src/rules/promise/no_promise_in_callback.rs index b0d69e68a0f55..ea27257aff114 100644 --- a/crates/oxc_linter/src/rules/promise/no_promise_in_callback.rs +++ b/crates/oxc_linter/src/rules/promise/no_promise_in_callback.rs @@ -107,16 +107,19 @@ fn is_within_promise_handler<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> b return false; } + // Check if the parent is a CallExpression let parent = ctx.nodes().parent_node(node.id()); - if !matches!(ctx.nodes().kind(parent.id()), AstKind::Argument(_)) { - return false; - } - - let AstKind::CallExpression(call_expr) = ctx.nodes().parent_kind(parent.id()) else { + let AstKind::CallExpression(call_expr) = parent.kind() else { return false; }; - matches!(call_expr.callee_name(), Some("then" | "catch")) + // Check if this function is one of the arguments to a promise method + let is_argument = call_expr + .arguments + .iter() + .any(|arg| matches!(arg.as_expression(), Some(expr) if expr.span() == node.span())); + + is_argument && matches!(call_expr.callee_name(), Some("then" | "catch")) } #[test] diff --git a/crates/oxc_linter/src/rules/react/exhaustive_deps.rs b/crates/oxc_linter/src/rules/react/exhaustive_deps.rs index 31ece6e1016e5..f7b930615d135 100644 --- a/crates/oxc_linter/src/rules/react/exhaustive_deps.rs +++ b/crates/oxc_linter/src/rules/react/exhaustive_deps.rs @@ -1188,6 +1188,7 @@ struct ExhaustiveDepsVisitor<'a, 'b> { decl_stack: Vec<&'a VariableDeclarator<'a>>, skip_reporting_dependency: bool, set_state_call: bool, + is_callee_of_call_expr: bool, found_dependencies: FxHashSet>, refs_inside_cleanups: Vec<&'a StaticMemberExpression<'a>>, } @@ -1200,6 +1201,7 @@ impl<'a, 'b> ExhaustiveDepsVisitor<'a, 'b> { decl_stack: vec![], skip_reporting_dependency: false, set_state_call: false, + is_callee_of_call_expr: false, found_dependencies: FxHashSet::default(), refs_inside_cleanups: vec![], } @@ -1308,6 +1310,22 @@ impl<'a> Visit<'a> for ExhaustiveDepsVisitor<'a, '_> { self.stack.pop(); } + fn visit_call_expression(&mut self, it: &CallExpression<'a>) { + self.stack.push(AstType::CallExpression); + + // Mark that we're visiting a callee + self.is_callee_of_call_expr = true; + self.visit_expression(&it.callee); + self.is_callee_of_call_expr = false; + + // Visit arguments normally + for arg in &it.arguments { + self.visit_argument(arg); + } + + self.stack.pop(); + } + fn visit_static_member_expression(&mut self, it: &StaticMemberExpression<'a>) { if it.property.name == "current" && is_inside_effect_cleanup(&self.stack) { // Safety: this is safe @@ -1328,8 +1346,7 @@ impl<'a> Visit<'a> for ExhaustiveDepsVisitor<'a, '_> { return; } - let is_parent_call_expr = - self.stack.last().is_some_and(|&ty| ty == AstType::CallExpression); + let is_parent_call_expr = self.is_callee_of_call_expr; match analyze_property_chain(&it.object, self.semantic) { Ok(source) => { diff --git a/crates/oxc_linter/src/rules/react/jsx_filename_extension.rs b/crates/oxc_linter/src/rules/react/jsx_filename_extension.rs index a559f72d456f5..f15bc68961e04 100644 --- a/crates/oxc_linter/src/rules/react/jsx_filename_extension.rs +++ b/crates/oxc_linter/src/rules/react/jsx_filename_extension.rs @@ -1,25 +1,31 @@ use std::ffi::OsStr; +use itertools::Itertools; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; use serde_json::Value; use oxc_ast::AstKind; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{CompactStr, GetSpan, Span}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use crate::{context::LintContext, rule::Rule}; -fn no_jsx_with_filename_extension_diagnostic(ext: &str, span: Span) -> OxcDiagnostic { - // See for details +fn no_jsx_with_filename_extension_diagnostic( + ext: &str, + span: Span, + allowed_extensions: &[CompactStr], +) -> OxcDiagnostic { OxcDiagnostic::warn(format!("JSX not allowed in files with extension '.{ext}'")) - .with_help("Rename the file with a good extension.") + .with_help(format!( + "Rename the file to use an allowed extension: {}", + allowed_extensions.iter().map(|e| format!(".{e}")).join(", ") + )) .with_label(span) } fn extension_only_for_jsx_diagnostic(ext: &str) -> OxcDiagnostic { - // See for details OxcDiagnostic::warn(format!("Only files containing JSX may use the extension '.{ext}'")) .with_help("Rename the file with a good extension.") } @@ -51,6 +57,7 @@ pub struct JsxFilenameExtensionConfig { /// Set this to `as-needed` to only allow JSX file extensions in files that contain JSX syntax. allow: AllowType, /// The set of allowed file extensions. + /// Can include or exclude the leading dot (e.g., "jsx" and ".jsx" are both valid). extensions: Vec, /// If enabled, files that do not contain code (i.e. are empty, contain only whitespaces or comments) will not be rejected. ignore_files_without_code: bool, @@ -77,11 +84,12 @@ impl std::ops::Deref for JsxFilenameExtension { declare_oxc_lint!( /// ### What it does /// - /// Enforces consistent use of the JSX file extension. + /// Enforces consistent use of the `.jsx` file extension. /// /// ### Why is this bad? /// /// Some bundlers or parsers need to know by the file extension that it contains JSX + /// in order to properly handle the files. /// /// ### Examples /// @@ -127,11 +135,10 @@ impl Rule for JsxFilenameExtension { .and_then(Value::as_array) .map(|v| { v.iter() - .filter_map(serde_json::Value::as_str) - .filter(|&s| s.starts_with('.')) - .map(|s| &s[1..]) - .map(CompactStr::from) - .collect() + .filter_map(Value::as_str) + .map(|s| CompactStr::from(s.strip_prefix('.').unwrap_or(s))) + .unique() + .collect::>() }) .unwrap_or(vec![CompactStr::from("jsx")]); @@ -151,6 +158,7 @@ impl Rule for JsxFilenameExtension { ctx.diagnostic(no_jsx_with_filename_extension_diagnostic( file_extension, jsx_elt.span(), + &self.extensions, )); } return; @@ -184,13 +192,13 @@ fn test() { Some(PathBuf::from("foo.jsx")), ), ( - "export default function MyComponent() { return ;}", + "export default function MyComponent() { return ; }", None, None, Some(PathBuf::from("foo.jsx")), ), ( - "export function MyComponent() { return
;}", + "export function MyComponent() { return
; }", None, None, Some(PathBuf::from("foo.jsx")), @@ -202,7 +210,7 @@ fn test() { Some(PathBuf::from("foo.jsx")), ), ( - "export function MyComponent() { return
;}", + "export function MyComponent() { return
; }", Some(serde_json::json!([{ "allow": "as-needed" }])), None, Some(PathBuf::from("foo.jsx")), @@ -220,13 +228,13 @@ fn test() { Some(PathBuf::from("foo.jsx")), ), ( - "export function MyComponent() { return <>;}", + "export function MyComponent() { return <>; }", None, None, Some(PathBuf::from("foo.jsx")), ), ( - "export function MyComponent() { return <>;}", + "export function MyComponent() { return <>; }", Some(serde_json::json!([{ "allow": "as-needed" }])), None, Some(PathBuf::from("foo.jsx")), @@ -253,7 +261,7 @@ fn test() { Some(PathBuf::from("foo.js")), ), ( - "export function MyComponent() { return
;}", + "export function MyComponent() { return
; }", Some(serde_json::json!([{ "extensions": [".js", ".jsx"] }])), None, Some(PathBuf::from("foo.js")), @@ -265,7 +273,7 @@ fn test() { Some(PathBuf::from("foo.js")), ), ( - "export function MyComponent() { return <>;}", + "export function MyComponent() { return <>; }", Some(serde_json::json!([{ "extensions": [".js", ".jsx"] }])), None, Some(PathBuf::from("foo.js")), @@ -276,6 +284,25 @@ fn test() { None, Some(PathBuf::from("foo.js")), ), + // Test that a commented-out JSX code snippet does not count. + ( + "// export function MyComponent() { return <>;}\n", + Some(serde_json::json!([{ "allow": "as-needed", "ignoreFilesWithoutCode": true }])), + None, + Some(PathBuf::from("foo.js")), + ), + ( + "// export function MyComponent() { return <>;}\nconsole.log('code');", + Some(serde_json::json!([{ "allow": "as-needed" }])), + None, + Some(PathBuf::from("foo.js")), + ), + ( + "/* export function MyComponent() { return <>;} */\nconsole.log('code');", + Some(serde_json::json!([{ "allow": "as-needed" }])), + None, + Some(PathBuf::from("foo.js")), + ), ( "//test\n\n//comment", Some(serde_json::json!([{ "allow": "as-needed", "ignoreFilesWithoutCode": true }])), @@ -288,6 +315,33 @@ fn test() { None, Some(PathBuf::from("foo.jsx")), ), + // Test that extensions without leading dot work (e.g., "tsx" instead of ".tsx") + ( + "module.exports = function MyComponent() { return
jsx\n
\n
; }", + Some(serde_json::json!([{ "extensions": ["tsx", ".jsx"] }])), + None, + Some(PathBuf::from("foo.tsx")), + ), + ( + "export default function MyComponent() { return ; }", + Some(serde_json::json!([{ "extensions": ["tsx"] }])), + None, + Some(PathBuf::from("foo.tsx")), + ), + // Test that identical extensions are de-duplicated and still allowed + ( + "export default function MyComponent() { return ; }", + Some(serde_json::json!([{ "extensions": ["tsx", ".tsx"] }])), + None, + Some(PathBuf::from("foo.tsx")), + ), + // Test that mixing extensions with and without dots works + ( + "export function MyComponent() { return
; }", + Some(serde_json::json!([{ "extensions": [".jsx", "tsx"] }])), + None, + Some(PathBuf::from("baz.tsx")), + ), ]; let fail = vec![ @@ -298,13 +352,13 @@ fn test() { Some(PathBuf::from("foo.js")), ), ( - "export default function MyComponent() { return ;}", + "export default function MyComponent() { return ; }", None, None, Some(PathBuf::from("foo.js")), ), ( - "export function MyComponent() { return
;}", + "export function MyComponent() { return
; }", None, None, Some(PathBuf::from("foo.js")), @@ -340,7 +394,7 @@ fn test() { Some(PathBuf::from("foo.jsx")), ), ( - "export function MyComponent() { return <>;}", + "export function MyComponent() { return <>; }", None, None, Some(PathBuf::from("foo.js")), @@ -352,17 +406,44 @@ fn test() { Some(PathBuf::from("foo.js")), ), ( - "export function MyComponent() { return <>;}", + "export function MyComponent() { return <>; }", Some(serde_json::json!([{ "extensions": [".js"] }])), None, Some(PathBuf::from("foo.jsx")), ), + // Test that the help message prints fine with multiple allowed extensions. + ( + "export function MyComponent() { return <>; }", + Some(serde_json::json!([{ "extensions": [".js", ".tsx", ".ts"] }])), + None, + Some(PathBuf::from("foo.jsx")), + ), ( "module.exports = function MyComponent() { return <>; }", Some(serde_json::json!([{ "extensions": [".js"] }])), None, Some(PathBuf::from("foo.jsx")), ), + // Test that identical extensions are de-duplicated. + ( + "module.exports = function MyComponent() { return <>; }", + Some(serde_json::json!([{ "extensions": [".js", "js"] }])), + None, + Some(PathBuf::from("foo.jsx")), + ), + ( + "module.exports = function MyComponent() { return <>; }", + Some(serde_json::json!([{ "extensions": ["js", "js"] }])), + None, + Some(PathBuf::from("foo.jsx")), + ), + // Test that extensions without leading dot work for failing cases too + ( + "module.exports = function MyComponent() { return
\n
\n
; }", + Some(serde_json::json!([{ "extensions": ["tsx"] }])), + None, + Some(PathBuf::from("foo.jsx")), + ), ]; Tester::new(JsxFilenameExtension::NAME, JsxFilenameExtension::PLUGIN, pass, fail) diff --git a/crates/oxc_linter/src/rules/react/jsx_key.rs b/crates/oxc_linter/src/rules/react/jsx_key.rs index fac191bc526a6..a352de31e5995 100644 --- a/crates/oxc_linter/src/rules/react/jsx_key.rs +++ b/crates/oxc_linter/src/rules/react/jsx_key.rs @@ -2,8 +2,8 @@ use cow_utils::CowUtils; use oxc_ast::{ AstKind, ast::{ - Argument, CallExpression, Expression, JSXAttributeItem, JSXAttributeName, JSXElement, - JSXFragment, Statement, + CallExpression, Expression, JSXAttributeItem, JSXAttributeName, JSXElement, JSXFragment, + Statement, }, }; use oxc_diagnostics::OxcDiagnostic; @@ -12,6 +12,7 @@ use oxc_span::{GetSpan, Span}; use crate::{ AstNode, + ast_util::is_node_within_call_argument, context::{ContextHost, LintContext}, rule::Rule, }; @@ -155,11 +156,11 @@ fn is_in_array_or_iter<'a, 'b>( node: &'b AstNode<'a>, ctx: &'b LintContext<'a>, ) -> Option { + let jsx_node = node; let mut node = node; let mut is_outside_containing_function = false; let mut is_explicit_return = false; - let mut argument = None; while !matches!(node.kind(), AstKind::Program(_)) { let parent = ctx.nodes().parent_node(node.id()); @@ -179,6 +180,7 @@ fn is_in_array_or_iter<'a, 'b>( if is_outside_containing_function { return None; } + is_outside_containing_function = true; } AstKind::Function(_) => { @@ -188,6 +190,7 @@ fn is_in_array_or_iter<'a, 'b>( if is_outside_containing_function { return None; } + is_outside_containing_function = true; } AstKind::ArrayExpression(_) => { @@ -203,13 +206,17 @@ fn is_in_array_or_iter<'a, 'b>( if let Some(member_expr) = callee.as_member_expression() && let Some((span, ident)) = member_expr.static_property_info() && TARGET_METHODS.contains(&ident) - && argument.is_some_and(|argument: &Argument<'_>| { - v.arguments - .get(if ident == "from" { 1 } else { 0 }) - .is_some_and(|arg| arg.span() == argument.span()) - }) { - return Some(InsideArrayOrIterator::Iterator(span)); + // Early exit if no arguments to check + if v.arguments.is_empty() { + return None; + } + + // Array.from uses 2nd argument (index 1), others use 1st argument (index 0) + let target_arg_index = if ident == "from" { 1 } else { 0 }; + if is_node_within_call_argument(jsx_node, v, target_arg_index) { + return Some(InsideArrayOrIterator::Iterator(span)); + } } return None; @@ -221,9 +228,6 @@ fn is_in_array_or_iter<'a, 'b>( AstKind::ReturnStatement(_) => { is_explicit_return = true; } - AstKind::Argument(arg) => { - argument = Some(arg); - } _ => {} } node = parent; diff --git a/crates/oxc_linter/src/rules/react/no_is_mounted.rs b/crates/oxc_linter/src/rules/react/no_is_mounted.rs index 969ebf21ff763..be76412d62f6a 100644 --- a/crates/oxc_linter/src/rules/react/no_is_mounted.rs +++ b/crates/oxc_linter/src/rules/react/no_is_mounted.rs @@ -10,8 +10,8 @@ use crate::{ }; fn no_is_mounted_diagnostic(span: Span) -> OxcDiagnostic { - OxcDiagnostic::warn("Do not use isMounted") - .with_help("isMounted is on its way to being officially deprecated. You can use a _isMounted property to track the mounted status yourself.") + OxcDiagnostic::warn("Do not use `isMounted`") + .with_help("`isMounted` is on its way to being officially deprecated. You can use an `_isMounted` property to track the mounted status yourself.") .with_label(span) } @@ -21,16 +21,17 @@ pub struct NoIsMounted; declare_oxc_lint!( /// ### What it does /// - /// This rule prevents using isMounted in classes + /// This rule prevents using `isMounted` in classes. /// /// ### Why is this bad? /// - /// isMounted is an anti-pattern, is not available when using classes, - /// and it is on its way to being officially deprecated./// + /// `isMounted` is an anti-pattern, is not available when using classes, + /// and it is on its way to being officially deprecated. /// /// ### Examples /// /// Examples of **incorrect** code for this rule: + /// /// ```jsx /// class Hello extends React.Component { /// someMethod() { @@ -102,6 +103,18 @@ fn test() { ", None, ), + ( + " + class Hello extends React.Component { + notIsMounted() {} + render() { + this.notIsMounted(); + return
Hello
; + } + }; + ", + None, + ), ]; let fail = vec![ diff --git a/crates/oxc_linter/src/rules/react/require_render_return.rs b/crates/oxc_linter/src/rules/react/require_render_return.rs index ac4363dc60799..01d1c7c8e66b2 100644 --- a/crates/oxc_linter/src/rules/react/require_render_return.rs +++ b/crates/oxc_linter/src/rules/react/require_render_return.rs @@ -198,13 +198,8 @@ fn is_in_es5_component<'a, 'b>(node: &'b AstNode<'a>, ctx: &'b LintContext<'a>) } let ancestors_1 = ctx.nodes().parent_node(ancestors_0.id()); - if !matches!(ancestors_1.kind(), AstKind::Argument(_)) { - return false; - } - - let ancestors_2 = ctx.nodes().parent_node(ancestors_1.id()); - is_es5_component(ancestors_2) + is_es5_component(ancestors_1) } fn is_in_es6_component<'a, 'b>(node: &'b AstNode<'a>, ctx: &'b LintContext<'a>) -> bool { diff --git a/crates/oxc_linter/src/rules/react/rules_of_hooks.rs b/crates/oxc_linter/src/rules/react/rules_of_hooks.rs index 36c213fcf2ed2..84da4ac80c2e4 100644 --- a/crates/oxc_linter/src/rules/react/rules_of_hooks.rs +++ b/crates/oxc_linter/src/rules/react/rules_of_hooks.rs @@ -396,11 +396,8 @@ fn parent_func<'a>(nodes: &'a AstNodes<'a>, node: &AstNode) -> Option<&'a AstNod /// Otherwise it would return `false`. fn is_non_react_func_arg(nodes: &AstNodes, node_id: NodeId) -> bool { let parent = nodes.parent_node(node_id); - if !matches!(parent.kind(), AstKind::Argument(_)) { - return false; - } - let AstKind::CallExpression(call) = nodes.parent_kind(parent.id()) else { + let AstKind::CallExpression(call) = parent.kind() else { return false; }; diff --git a/crates/oxc_linter/src/rules/typescript/explicit_function_return_type.rs b/crates/oxc_linter/src/rules/typescript/explicit_function_return_type.rs index 092e676bcc3f7..0121e142c0da8 100644 --- a/crates/oxc_linter/src/rules/typescript/explicit_function_return_type.rs +++ b/crates/oxc_linter/src/rules/typescript/explicit_function_return_type.rs @@ -219,7 +219,7 @@ impl Rule for ExplicitFunctionReturnType { } } - if let Some(parent) = get_parent_node(node, ctx) { + if let Some(parent) = outermost_paren_parent(node, ctx) { match parent.kind() { AstKind::MethodDefinition(def) => { ctx.diagnostic(explicit_function_return_type_diagnostic(Span::new( @@ -288,7 +288,7 @@ impl Rule for ExplicitFunctionReturnType { return; } - if let Some(parent) = get_parent_node(node, ctx) { + if let Some(parent) = outermost_paren_parent(node, ctx) { match parent.kind() { AstKind::MethodDefinition(def) => { ctx.diagnostic(explicit_function_return_type_diagnostic(Span::new( @@ -382,7 +382,7 @@ impl ExplicitFunctionReturnType { node: &AstNode<'a>, ctx: &LintContext<'a>, ) -> bool { - let Some(parent) = get_parent_node(node, ctx) else { return false }; + let Some(parent) = outermost_paren_parent(node, ctx) else { return false }; match parent.kind() { AstKind::VariableDeclarator(decl) => { let BindingPatternKind::BindingIdentifier(id) = &decl.id.kind else { @@ -540,19 +540,8 @@ fn is_setter(node: &AstNode) -> bool { } } -fn get_parent_node<'a, 'b>( - node: &'b AstNode<'a>, - ctx: &'b LintContext<'a>, -) -> Option<&'b AstNode<'a>> { - let parent = outermost_paren_parent(node, ctx)?; - match parent.kind() { - AstKind::Argument(_) => outermost_paren_parent(parent, ctx), - _ => Some(parent), - } -} - fn check_typed_function_expression<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool { - let Some(parent) = get_parent_node(node, ctx) else { return false }; + let Some(parent) = outermost_paren_parent(node, ctx) else { return false }; is_typed_parent(parent, Some(node)) || is_property_of_object_with_type(parent, ctx) || is_constructor_argument(parent) @@ -636,7 +625,7 @@ fn is_function(expr: &Expression) -> bool { } fn ancestor_has_return_type<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool { - let Some(parent) = get_parent_node(node, ctx) else { return false }; + let Some(parent) = outermost_paren_parent(node, ctx) else { return false }; if let AstKind::ObjectProperty(prop) = parent.kind() && let Expression::ArrowFunctionExpression(func) = &prop.value @@ -741,7 +730,7 @@ fn is_property_of_object_with_type(node: &AstNode, ctx: &LintContext) -> bool { if !matches!(parent.kind(), AstKind::ObjectExpression(_)) { return false; } - let Some(obj_expr_parent) = get_parent_node(parent, ctx) else { + let Some(obj_expr_parent) = outermost_paren_parent(parent, ctx) else { return false; }; is_typed_parent(obj_expr_parent, None) || is_property_of_object_with_type(obj_expr_parent, ctx) diff --git a/crates/oxc_linter/src/rules/unicorn/consistent_function_scoping.rs b/crates/oxc_linter/src/rules/unicorn/consistent_function_scoping.rs index 2c1e4f459cc89..b1563ff03b090 100644 --- a/crates/oxc_linter/src/rules/unicorn/consistent_function_scoping.rs +++ b/crates/oxc_linter/src/rules/unicorn/consistent_function_scoping.rs @@ -10,7 +10,9 @@ use schemars::JsonSchema; use crate::{ AstNode, - ast_util::{get_function_like_declaration, nth_outermost_paren_parent, outermost_paren_parent}, + ast_util::{ + get_function_like_declaration, is_node_exact_call_argument, outermost_paren_parent, + }, context::LintContext, rule::Rule, utils::is_react_hook, @@ -250,8 +252,9 @@ impl Rule for ConsistentFunctionScoping { if matches!( outermost_paren_parent(node, ctx).map(AstNode::kind), - Some(AstKind::ReturnStatement(_) | AstKind::Argument(_)) - ) { + Some(AstKind::ReturnStatement(_)) + ) || is_node_exact_call_argument(node, ctx) + { return; } @@ -341,22 +344,55 @@ fn is_parent_scope_iife<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool { if let Some(parent_node) = outermost_paren_parent(node, ctx) && let Some(parent_node) = outermost_paren_parent(parent_node, ctx) && matches!(parent_node.kind(), AstKind::Function(_) | AstKind::ArrowFunctionExpression(_)) - && let Some(parent_node) = outermost_paren_parent(parent_node, ctx) + && let Some(call_node) = outermost_paren_parent(parent_node, ctx) + && let AstKind::CallExpression(call) = call_node.kind() { - return matches!(parent_node.kind(), AstKind::CallExpression(_)); + // Check if the function is the callee (true IIFE) + // Handle both direct calls and parenthesized calls + let callee = &call.callee.without_parentheses(); + return callee.span().start <= parent_node.span().start + && parent_node.span().end <= callee.span().end; } false } fn is_in_react_hook<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool { - // we want the 3rd outermost parent - // parents are: function body -> function -> argument -> call expression - if let Some(parent) = nth_outermost_paren_parent(node, ctx, 3) - && let AstKind::CallExpression(call_expr) = parent.kind() + // Check immediate parent first, then use scope-based lookup + // First check if the function is directly inside a React hook call + let parent = ctx.nodes().parent_node(node.id()); + if let AstKind::CallExpression(call_expr) = parent.kind() + && is_react_hook(&call_expr.callee) { - return is_react_hook(&call_expr.callee); + return true; } + + // If not directly inside, check if we're inside a function that's inside a React hook + let current_scope_id = match node.kind() { + AstKind::Function(func) => func.scope_id(), + AstKind::ArrowFunctionExpression(arrow) => arrow.scope_id(), + _ => return false, + }; + + let scoping = ctx.scoping(); + + // Check the parent scope's node (the function that contains us) + if let Some(parent_scope_id) = scoping.scope_parent_id(current_scope_id) { + let parent_scope_node_id = scoping.get_node_id(parent_scope_id); + let parent_scope_node = ctx.nodes().get_node(parent_scope_node_id); + + // If the parent scope is a function, check if that function is inside a React hook + if matches!( + parent_scope_node.kind(), + AstKind::Function(_) | AstKind::ArrowFunctionExpression(_) + ) { + let grandparent = ctx.nodes().parent_node(parent_scope_node_id); + if let AstKind::CallExpression(call_expr) = grandparent.kind() { + return is_react_hook(&call_expr.callee); + } + } + } + false } diff --git a/crates/oxc_linter/src/rules/unicorn/explicit_length_check.rs b/crates/oxc_linter/src/rules/unicorn/explicit_length_check.rs index 86438fffd525b..36603e3c97059 100644 --- a/crates/oxc_linter/src/rules/unicorn/explicit_length_check.rs +++ b/crates/oxc_linter/src/rules/unicorn/explicit_length_check.rs @@ -13,7 +13,7 @@ use crate::{ AstNode, context::LintContext, rule::Rule, - utils::{get_boolean_ancestor, is_boolean_node}, + utils::{get_boolean_ancestor, is_boolean_call, is_boolean_node}, }; fn non_zero(span: Span, prop_name: &str, op_and_rhs: &str, help: Option) -> OxcDiagnostic { @@ -206,7 +206,17 @@ impl ExplicitLengthCheck { } }; - let span = kind.span(); + let span = match node.kind() { + AstKind::CallExpression(call) if is_boolean_call(&node.kind()) => { + // Check if we should replace just the member expression or the whole call + call.arguments + .first() + .and_then(|arg| arg.as_expression()) + .filter(|expr| matches!(expr, Expression::LogicalExpression(_))) + .map_or_else(|| node.span(), |_| static_member_expr.span) + } + _ => node.span(), + }; let mut need_pad_start = false; let mut need_pad_end = false; let parent = ctx.nodes().parent_kind(node.id()); @@ -221,15 +231,28 @@ impl ExplicitLengthCheck { need_pad_end = end.is_ascii_alphabetic() || !end.is_ascii(); } - let fixed = format!( - "{}{}{} {}{}{}", - if need_pad_start { " " } else { "" }, - if need_paren { "(" } else { "" }, - static_member_expr.span.source_text(ctx.source_text()), - check_code, - if need_paren { ")" } else { "" }, - if need_pad_end { " " } else { "" }, - ); + // Pre-compute source text to avoid repeated calls + let source_text = static_member_expr.span.source_text(ctx.source_text()); + + // Use capacity hint to reduce allocations - estimate based on components + let estimated_capacity = source_text.len() + check_code.len() + 6; // +6 for spaces and parens + let mut fixed = String::with_capacity(estimated_capacity); + + if need_pad_start { + fixed.push(' '); + } + if need_paren { + fixed.push('('); + } + fixed.push_str(source_text); + fixed.push(' '); + fixed.push_str(check_code); + if need_paren { + fixed.push(')'); + } + if need_pad_end { + fixed.push(' '); + } let property = static_member_expr.property.name; let help = if auto_fix { None diff --git a/crates/oxc_linter/src/rules/unicorn/no_null.rs b/crates/oxc_linter/src/rules/unicorn/no_null.rs index 90c5dba2a59aa..1ebec01d4e845 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_null.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_null.rs @@ -196,7 +196,7 @@ impl Rule for NoNull { let grandparent_kind = parents.next(); match (parent_kind, grandparent_kind) { - (AstKind::Argument(_), Some(AstKind::CallExpression(call_expr))) + (AstKind::CallExpression(call_expr), _) if match_call_expression_pass_case(null_literal, call_expr) => { // no violation diff --git a/crates/oxc_linter/src/rules/unicorn/no_useless_promise_resolve_reject.rs b/crates/oxc_linter/src/rules/unicorn/no_useless_promise_resolve_reject.rs index fa143f64a43b3..acd5e82ddb320 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_useless_promise_resolve_reject.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_useless_promise_resolve_reject.rs @@ -208,9 +208,6 @@ fn is_promise_callback<'a, 'b>(node: &'a AstNode<'b>, ctx: &'a LintContext<'b>) let Some(parent) = outermost_paren_parent(function_node, ctx) else { return false; }; - let Some(parent) = outermost_paren_parent(parent, ctx) else { - return false; - }; let AstKind::CallExpression(call_expr) = parent.kind() else { return false; diff --git a/crates/oxc_linter/src/rules/unicorn/no_useless_spread/mod.rs b/crates/oxc_linter/src/rules/unicorn/no_useless_spread/mod.rs index c262bcdddc336..6861a8ba9598f 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_useless_spread/mod.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_useless_spread/mod.rs @@ -212,22 +212,31 @@ fn check_useless_spread_in_list<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) - true } // foo(...[ ]) - AstKind::Argument(_) => { - ctx.diagnostic_with_fix(spread_in_arguments(span), |fixer| { + AstKind::NewExpression(NewExpression { arguments, .. }) + | AstKind::CallExpression(CallExpression { arguments, .. }) => { + if let Some(call_expr_args_span) = arguments.first().map(|first| { + let last = arguments.last().unwrap(); + Span::new(first.span().start, last.span().end) + }) && call_expr_args_span.contains_inclusive(array_expr.span) + { + // compute replacer before the closure so we don't capture `ctx` by reference inside the fixer closure let replacer = if let Some(first) = array_expr.elements.first() { - let mut span = first.span(); + let mut snippet_span = first.span(); if array_expr.elements.len() != 1 { let last = array_expr.elements.last().unwrap(); - span = Span::new(first.span().start, last.span().end); + snippet_span = Span::new(first.span().start, last.span().end); } - ctx.source_range(span) + ctx.source_range(snippet_span).to_string() } else { - "" + String::new() }; - fixer.replace(spread_elem.span(), replacer.to_owned()) - }); - true + ctx.diagnostic_with_fix(spread_in_arguments(span), move |fixer| { + fixer.replace(spread_elem.span(), replacer) + }); + return true; + } + false } _ => false, }, @@ -310,15 +319,6 @@ fn check_useless_iterable_to_array<'a>( let span = Span::new(spread_elem.span.start, spread_elem.span.start + 3); - let parent = if let AstKind::Argument(_) = parent.kind() { - let Some(parent) = outermost_paren_parent(parent, ctx) else { - return false; - }; - parent - } else { - parent - }; - match parent.kind() { AstKind::ForOfStatement(for_of_stmt) => { if for_of_stmt.right.without_parentheses().span() == array_expr.span { diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_add_event_listener.rs b/crates/oxc_linter/src/rules/unicorn/prefer_add_event_listener.rs index dc4769a3e3a2d..18382d3a63348 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_add_event_listener.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_add_event_listener.rs @@ -71,6 +71,14 @@ impl Rule for PreferAddEventListener { } } +// Can refer to the following sources for the list of event handler names, compare +// this array against any new `onx` functions introduced in browsers: +// - https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes#list_of_global_event_handler_attributes +// - https://github.com/mdn/browser-compat-data/blob/d5d5f2e21ef3f798784d1f5f75bde7c7f10f250e/api/Element.json +// - https://github.com/microsoft/TypeScript-DOM-lib-generator/blob/f915ac0c987300d75af41bfe4a34bb29a0fb941f/baselines/dom.generated.d.ts +// +// Please avoid adding new events that are not implemented in at least two major browser engines! +// Last updated: Nov 2025 const DOM_EVENT_TYPE_NAMES: phf::Set<&'static str> = phf::phf_set![ "AnimationEnd", "AnimationIteration", @@ -104,9 +112,10 @@ const DOM_EVENT_TYPE_NAMES: phf::Set<&'static str> = phf::phf_set![ "activate", "afterblur", "afterprint", - "animationEnd", - "animationStart", + "animationcancel", + "animationend", "animationiteration", + "animationstart", "appinstalled", "auxclick", "beforeblur", @@ -114,8 +123,10 @@ const DOM_EVENT_TYPE_NAMES: phf::Set<&'static str> = phf::phf_set![ "beforecut", "beforeinput", "beforeinstallprompt", + "beforematch", "beforepaste", "beforeprint", + "beforetoggle", "beforeunload", "blur", "cancel", @@ -129,9 +140,12 @@ const DOM_EVENT_TYPE_NAMES: phf::Set<&'static str> = phf::phf_set![ "compositionupdate", "connect", "consolemessage", + "contextlost", "contextmenu", + "contextrestored", "controllerchange", "copy", + "cuechange", "cut", "dblclick", "deactivate", @@ -157,6 +171,7 @@ const DOM_EVENT_TYPE_NAMES: phf::Set<&'static str> = phf::phf_set![ "focusin", "focusout", "foreignfetch", + "formdata", "fullscreenchange", "gotpointercapture", "hashchange", @@ -208,6 +223,7 @@ const DOM_EVENT_TYPE_NAMES: phf::Set<&'static str> = phf::phf_set![ "pointermove", "pointerout", "pointerover", + "pointerrawupdate", "pointerup", "popstate", "progress", @@ -219,7 +235,9 @@ const DOM_EVENT_TYPE_NAMES: phf::Set<&'static str> = phf::phf_set![ "responsive", "rightclick", "scroll", + "scrollend", "search", + "securitypolicyviolation", "seeked", "seeking", "select", @@ -227,6 +245,7 @@ const DOM_EVENT_TYPE_NAMES: phf::Set<&'static str> = phf::phf_set![ "selectstart", "show", "sizechanged", + "slotchange", "sourceclosed", "sourceended", "sourceopen", @@ -236,15 +255,18 @@ const DOM_EVENT_TYPE_NAMES: phf::Set<&'static str> = phf::phf_set![ "submit", "suspend", "text", - "textInput", "textinput", + "textInput", "timeupdate", "toggle", "touchcancel", "touchend", "touchmove", "touchstart", + "transitioncancel", "transitionend", + "transitionrun", + "transitionstart", "unload", "unresponsive", "update", diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_native_coercion_functions.rs b/crates/oxc_linter/src/rules/unicorn/prefer_native_coercion_functions.rs index bd20339cbf362..9213c5b2ceecd 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_native_coercion_functions.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_native_coercion_functions.rs @@ -5,7 +5,7 @@ use oxc_ast::{ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_semantic::NodeId; -use oxc_span::Span; +use oxc_span::{GetSpan, Span}; use crate::{AstNode, context::LintContext, rule::Rule, utils::get_first_parameter_name}; @@ -193,15 +193,15 @@ fn check_array_callback_methods( ctx: &LintContext, ) -> bool { let parent = ctx.nodes().parent_node(node_id); - let AstKind::Argument(parent_call_expr_arg) = parent.kind() else { - return false; - }; - let grand_parent = ctx.nodes().parent_node(parent.id()); - let AstKind::CallExpression(call_expr) = grand_parent.kind() else { + + let AstKind::CallExpression(call_expr) = parent.kind() else { return false; }; - - if !std::ptr::eq(&raw const call_expr.arguments[0], parent_call_expr_arg) { + if call_expr + .arguments + .first() + .is_none_or(|arg| arg.span() != ctx.nodes().get_node(node_id).kind().span()) + { return false; } if call_expr.optional { diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_regexp_test.rs b/crates/oxc_linter/src/rules/unicorn/prefer_regexp_test.rs index f1ab6bf0857ee..c7d247d603e20 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_regexp_test.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_regexp_test.rs @@ -100,16 +100,7 @@ impl Rule for PreferRegexpTest { return; } } - - AstKind::Argument(_) => { - let Some(parent) = outermost_paren_parent(parent, ctx) else { - return; - }; - - let AstKind::CallExpression(call_expr) = parent.kind() else { - return; - }; - + AstKind::CallExpression(call_expr) => { let Expression::Identifier(ident) = &call_expr.callee else { return; }; diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_top_level_await.rs b/crates/oxc_linter/src/rules/unicorn/prefer_top_level_await.rs index c050d409cfb19..2e9db7a70d7cd 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_top_level_await.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_top_level_await.rs @@ -82,11 +82,7 @@ impl Rule for PreferTopLevelAwait { } let parent = ctx.nodes().parent_node(node.id()); - // TODO: remove this block once removing `AstKind::Argument` is complete - let grand_parent = { - let p = ctx.nodes().parent_node(parent.id()); - if let AstKind::Argument(_) = p.kind() { ctx.nodes().parent_node(p.id()) } else { p } - }; + let grand_parent = ctx.nodes().parent_node(parent.id()); if let AstKind::StaticMemberExpression(member_expr) = parent.kind() && member_expr.object.span() == call_expr.span diff --git a/crates/oxc_linter/src/rules/vue/define_props_destructuring.rs b/crates/oxc_linter/src/rules/vue/define_props_destructuring.rs index 4071f6b4187fe..f168aa8988ef7 100644 --- a/crates/oxc_linter/src/rules/vue/define_props_destructuring.rs +++ b/crates/oxc_linter/src/rules/vue/define_props_destructuring.rs @@ -119,7 +119,7 @@ impl Rule for DefinePropsDestructuring { } let parent = &ctx.nodes().parent_node(node.id()); - let with_defaults_span = get_parent_with_defaults_call_expression_span(parent, ctx); + let with_defaults_span = get_parent_with_defaults_call_expression_span(parent); let has_destructuring = is_parent_destructuring_variable(parent, ctx); if self.destructure == Destructure::Never { @@ -138,13 +138,8 @@ impl Rule for DefinePropsDestructuring { } } -fn get_parent_with_defaults_call_expression_span( - parent: &AstNode<'_>, - ctx: &LintContext<'_>, -) -> Option { - let AstKind::Argument(_) = parent.kind() else { return None }; - let parent = &ctx.nodes().parent_kind(parent.id()); - let AstKind::CallExpression(call_expr) = parent else { return None }; +fn get_parent_with_defaults_call_expression_span(parent: &AstNode<'_>) -> Option { + let AstKind::CallExpression(call_expr) = parent.kind() else { return None }; call_expr.callee.get_identifier_reference().and_then(|reference| { if reference.name == "withDefaults" { Some(reference.span) } else { None } diff --git a/crates/oxc_linter/src/snapshots/promise_no_callback_in_promise.snap b/crates/oxc_linter/src/snapshots/promise_no_callback_in_promise.snap index 6306f9b89fe0c..bc65c19fe4ec7 100644 --- a/crates/oxc_linter/src/snapshots/promise_no_callback_in_promise.snap +++ b/crates/oxc_linter/src/snapshots/promise_no_callback_in_promise.snap @@ -84,3 +84,70 @@ source: crates/oxc_linter/src/tester.rs · ───────────── ╰──── help: Use `then` and `catch` directly + + ⚠ eslint-plugin-promise(no-callback-in-promise): Avoid calling back inside of a promise + ╭─[no_callback_in_promise.tsx:1:14] + 1 │ a.then(() => doSomething(cb)) + · ─────────────── + ╰──── + help: Use `then` and `catch` directly + + ⚠ eslint-plugin-promise(no-callback-in-promise): Avoid calling back inside of a promise + ╭─[no_callback_in_promise.tsx:4:13] + 3 │ .then(() => { + 4 │ setTimeout(callback); + · ──────────────────── + 5 │ }); + ╰──── + help: Use `then` and `catch` directly + + ⚠ eslint-plugin-promise(no-callback-in-promise): Avoid calling back inside of a promise + ╭─[no_callback_in_promise.tsx:4:30] + 3 │ .then(() => { + 4 │ setTimeout(() => callback()); + · ────────── + 5 │ }); + ╰──── + help: Use `then` and `catch` directly + + ⚠ eslint-plugin-promise(no-callback-in-promise): Avoid calling back inside of a promise + ╭─[no_callback_in_promise.tsx:1:49] + 1 │ whatever.then((err) => { process.nextTick(() => cb()) }) + · ──── + ╰──── + help: Use `then` and `catch` directly + + ⚠ eslint-plugin-promise(no-callback-in-promise): Avoid calling back inside of a promise + ╭─[no_callback_in_promise.tsx:1:45] + 1 │ whatever.then((err) => { setImmediate(() => cb()) }) + · ──── + ╰──── + help: Use `then` and `catch` directly + + ⚠ eslint-plugin-promise(no-callback-in-promise): Avoid calling back inside of a promise + ╭─[no_callback_in_promise.tsx:1:43] + 1 │ whatever.then((err) => setImmediate(() => cb())) + · ──── + ╰──── + help: Use `then` and `catch` directly + + ⚠ eslint-plugin-promise(no-callback-in-promise): Avoid calling back inside of a promise + ╭─[no_callback_in_promise.tsx:1:47] + 1 │ whatever.then((err) => process.nextTick(() => cb())) + · ──── + ╰──── + help: Use `then` and `catch` directly + + ⚠ eslint-plugin-promise(no-callback-in-promise): Avoid calling back inside of a promise + ╭─[no_callback_in_promise.tsx:1:24] + 1 │ whatever.then((err) => process.nextTick(cb)) + · ──────────────────── + ╰──── + help: Use `then` and `catch` directly + + ⚠ eslint-plugin-promise(no-callback-in-promise): Avoid calling back inside of a promise + ╭─[no_callback_in_promise.tsx:1:24] + 1 │ whatever.then((err) => setImmediate(cb)) + · ──────────────── + ╰──── + help: Use `then` and `catch` directly diff --git a/crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap b/crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap index aa187c580431b..3758fcb587b20 100644 --- a/crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap +++ b/crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap @@ -393,13 +393,13 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: Either include it or remove the dependency array. - ⚠ eslint-plugin-react-hooks(exhaustive-deps): React Hook useEffect has missing dependencies: 'history.foo', and 'history.foo.bar' + ⚠ eslint-plugin-react-hooks(exhaustive-deps): React Hook useEffect has a missing dependency: 'history.foo' ╭─[exhaustive_deps.tsx:7:14] 3 │ return [ 4 │ history.foo.bar[2].dobedo.listen(), - · ─────────── + · ─────┬───── + · ╰── useEffect uses `history.foo` here 5 │ history.foo.bar().dobedo.listen[2] - · ─────────── 6 │ ]; 7 │ }, []); · ── diff --git a/crates/oxc_linter/src/snapshots/react_jsx_filename_extension.snap b/crates/oxc_linter/src/snapshots/react_jsx_filename_extension.snap index 7c94f014cf704..eb307a6f4118e 100644 --- a/crates/oxc_linter/src/snapshots/react_jsx_filename_extension.snap +++ b/crates/oxc_linter/src/snapshots/react_jsx_filename_extension.snap @@ -7,28 +7,28 @@ source: crates/oxc_linter/src/tester.rs 2 │ │
3 │ ╰─▶
; } ╰──── - help: Rename the file with a good extension. + help: Rename the file to use an allowed extension: .jsx ⚠ eslint-plugin-react(jsx-filename-extension): JSX not allowed in files with extension '.js' ╭─[jsx_filename_extension.tsx:1:48] - 1 │ export default function MyComponent() { return ;} + 1 │ export default function MyComponent() { return ; } · ──────── ╰──── - help: Rename the file with a good extension. + help: Rename the file to use an allowed extension: .jsx ⚠ eslint-plugin-react(jsx-filename-extension): JSX not allowed in files with extension '.js' ╭─[jsx_filename_extension.tsx:1:40] - 1 │ export function MyComponent() { return
;} + 1 │ export function MyComponent() { return
; } · ─────────────────── ╰──── - help: Rename the file with a good extension. + help: Rename the file to use an allowed extension: .jsx ⚠ eslint-plugin-react(jsx-filename-extension): JSX not allowed in files with extension '.js' ╭─[jsx_filename_extension.tsx:1:28] 1 │ const MyComponent = () => (
); export default MyComponent; · ─────────────────── ╰──── - help: Rename the file with a good extension. + help: Rename the file to use an allowed extension: .jsx ⚠ eslint-plugin-react(jsx-filename-extension): Only files containing JSX may use the extension '.jsx' help: Rename the file with a good extension. @@ -42,7 +42,7 @@ source: crates/oxc_linter/src/tester.rs 2 │ │
3 │ ╰─▶
; } ╰──── - help: Rename the file with a good extension. + help: Rename the file to use an allowed extension: .jsx ⚠ eslint-plugin-react(jsx-filename-extension): JSX not allowed in files with extension '.jsx' ╭─[jsx_filename_extension.tsx:1:50] @@ -50,32 +50,61 @@ source: crates/oxc_linter/src/tester.rs 2 │ │
3 │ ╰─▶
; } ╰──── - help: Rename the file with a good extension. + help: Rename the file to use an allowed extension: .js ⚠ eslint-plugin-react(jsx-filename-extension): JSX not allowed in files with extension '.js' ╭─[jsx_filename_extension.tsx:1:40] - 1 │ export function MyComponent() { return <>;} + 1 │ export function MyComponent() { return <>; } · ───────────────────── ╰──── - help: Rename the file with a good extension. + help: Rename the file to use an allowed extension: .jsx ⚠ eslint-plugin-react(jsx-filename-extension): JSX not allowed in files with extension '.js' ╭─[jsx_filename_extension.tsx:1:50] 1 │ module.exports = function MyComponent() { return <>; } · ───────────────────── ╰──── - help: Rename the file with a good extension. + help: Rename the file to use an allowed extension: .jsx ⚠ eslint-plugin-react(jsx-filename-extension): JSX not allowed in files with extension '.jsx' ╭─[jsx_filename_extension.tsx:1:40] - 1 │ export function MyComponent() { return <>;} + 1 │ export function MyComponent() { return <>; } · ───────────────────── ╰──── - help: Rename the file with a good extension. + help: Rename the file to use an allowed extension: .js + + ⚠ eslint-plugin-react(jsx-filename-extension): JSX not allowed in files with extension '.jsx' + ╭─[jsx_filename_extension.tsx:1:40] + 1 │ export function MyComponent() { return <>; } + · ───────────────────── + ╰──── + help: Rename the file to use an allowed extension: .js, .tsx, .ts ⚠ eslint-plugin-react(jsx-filename-extension): JSX not allowed in files with extension '.jsx' ╭─[jsx_filename_extension.tsx:1:50] 1 │ module.exports = function MyComponent() { return <>; } · ───────────────────── ╰──── - help: Rename the file with a good extension. + help: Rename the file to use an allowed extension: .js + + ⚠ eslint-plugin-react(jsx-filename-extension): JSX not allowed in files with extension '.jsx' + ╭─[jsx_filename_extension.tsx:1:50] + 1 │ module.exports = function MyComponent() { return <>; } + · ───────────────────── + ╰──── + help: Rename the file to use an allowed extension: .js + + ⚠ eslint-plugin-react(jsx-filename-extension): JSX not allowed in files with extension '.jsx' + ╭─[jsx_filename_extension.tsx:1:50] + 1 │ module.exports = function MyComponent() { return <>; } + · ───────────────────── + ╰──── + help: Rename the file to use an allowed extension: .js + + ⚠ eslint-plugin-react(jsx-filename-extension): JSX not allowed in files with extension '.jsx' + ╭─[jsx_filename_extension.tsx:1:50] + 1 │ ╭─▶ module.exports = function MyComponent() { return
+ 2 │ │
+ 3 │ ╰─▶
; } + ╰──── + help: Rename the file to use an allowed extension: .tsx diff --git a/crates/oxc_linter/src/snapshots/react_no_is_mounted.snap b/crates/oxc_linter/src/snapshots/react_no_is_mounted.snap index 5f0809fe305c9..e86995e9496da 100644 --- a/crates/oxc_linter/src/snapshots/react_no_is_mounted.snap +++ b/crates/oxc_linter/src/snapshots/react_no_is_mounted.snap @@ -1,29 +1,29 @@ --- source: crates/oxc_linter/src/tester.rs --- - ⚠ eslint-plugin-react(no-is-mounted): Do not use isMounted + ⚠ eslint-plugin-react(no-is-mounted): Do not use `isMounted` ╭─[no_is_mounted.tsx:4:24] 3 │ componentDidUpdate: function() { 4 │ if (!this.isMounted()) { · ──────────────── 5 │ return; ╰──── - help: isMounted is on its way to being officially deprecated. You can use a _isMounted property to track the mounted status yourself. + help: `isMounted` is on its way to being officially deprecated. You can use an `_isMounted` property to track the mounted status yourself. - ⚠ eslint-plugin-react(no-is-mounted): Do not use isMounted + ⚠ eslint-plugin-react(no-is-mounted): Do not use `isMounted` ╭─[no_is_mounted.tsx:4:24] 3 │ someMethod: function() { 4 │ if (!this.isMounted()) { · ──────────────── 5 │ return; ╰──── - help: isMounted is on its way to being officially deprecated. You can use a _isMounted property to track the mounted status yourself. + help: `isMounted` is on its way to being officially deprecated. You can use an `_isMounted` property to track the mounted status yourself. - ⚠ eslint-plugin-react(no-is-mounted): Do not use isMounted + ⚠ eslint-plugin-react(no-is-mounted): Do not use `isMounted` ╭─[no_is_mounted.tsx:4:24] 3 │ someMethod() { 4 │ if (!this.isMounted()) { · ──────────────── 5 │ return; ╰──── - help: isMounted is on its way to being officially deprecated. You can use a _isMounted property to track the mounted status yourself. + help: `isMounted` is on its way to being officially deprecated. You can use an `_isMounted` property to track the mounted status yourself. diff --git a/crates/oxc_linter/src/utils/unicorn/boolean.rs b/crates/oxc_linter/src/utils/unicorn/boolean.rs index 0515da0186b0d..220ae57d748f3 100644 --- a/crates/oxc_linter/src/utils/unicorn/boolean.rs +++ b/crates/oxc_linter/src/utils/unicorn/boolean.rs @@ -89,8 +89,7 @@ pub fn get_boolean_ancestor<'a, 'b>( cur = parent; continue; } - let parent = ctx.nodes().parent_node(parent.id()); - if is_boolean_call(&parent.kind()) { + if is_boolean_call(&kind) { cur = parent; continue; } diff --git a/crates/oxc_linter/tests/rule_configuration_documentation_test.rs b/crates/oxc_linter/tests/rule_configuration_documentation_test.rs new file mode 100644 index 0000000000000..ab9fbe6ad2f7c --- /dev/null +++ b/crates/oxc_linter/tests/rule_configuration_documentation_test.rs @@ -0,0 +1,179 @@ +//! Test to ensure rules with configuration options have proper documentation +//! +//! This test verifies that all linter rules with configuration options +//! have a schema value set in the declare_oxc_lint! macro. +//! +//! This helps ensure that users can understand how to configure rules properly. + +// NOTE: You will need to run the tests with `--features ruledocs` or +// `--all-features` for this test file to run. +#![cfg(feature = "ruledocs")] + +use lazy_regex::Regex; +use oxc_linter::{rules::RULES, table::RuleTable}; +use rustc_hash::FxHashSet; +use schemars::r#gen; + +/// Test to ensure that all rules with configuration options have proper documentation. +/// +/// This test gets the full rule list programmatically, identifies rules with +/// configuration schemas, and verifies that they are configured to generate +/// configuration documentation. +#[test] +fn test_rules_with_custom_configuration_have_schema() { + let mut failures = Vec::new(); + + // Rules that have from_configuration, but no proper schema documentation yet. + // These rules are temporarily allowed to not have schema docs. + // + // TODO: Remove rules from this list as they get fixed. Do NOT add new rules to this + // list - newly-created rules should always be documented before being merged! + let exceptions: &[&str] = &[ + // eslint + "eslint/arrow-body-style", + "eslint/default-case", + "eslint/func-names", + "eslint/new-cap", + "eslint/no-cond-assign", + "eslint/no-else-return", + "eslint/no-empty-function", + "eslint/no-fallthrough", + "eslint/no-restricted-globals", + "eslint/no-restricted-imports", + "eslint/no-warning-comments", + "eslint/yoda", + // jest + "jest/consistent-test-it", + "jest/valid-title", + // jsdoc + "jsdoc/require-param", + "jsdoc/require-returns", + // promise + "promise/param-names", + // react + "react/forbid-dom-props", + "react/forbid-elements", + "react/jsx-handler-names", + "react/prefer-es6-class", + "react/state-in-constructor", + // typescript + "typescript/ban-ts-comment", + "typescript/consistent-type-imports", + // unicorn + "unicorn/catch-error-name", + "unicorn/filename-case", + "unicorn/switch-case-braces", + // vue + "vue/define-emits-declaration", + "vue/define-props-declaration", + ]; + + let exception_set: FxHashSet<&str> = exceptions.iter().copied().collect(); + + // Get the full rule list programmatically + let mut generator = r#gen::SchemaGenerator::new(r#gen::SchemaSettings::default()); + let table = RuleTable::new(Some(&mut generator)); + + // Build a map from rule name to RuleTableRow for easy lookup, filters + // out rules that have no schema. + // This is used to check which rules have schemas defined. + let rules_with_schemas: FxHashSet = table + .sections + .iter() + .flat_map(|section| §ion.rows) + .filter(|row| row.schema.is_some()) + .map(|row| format!("{}/{}", row.plugin, row.name)) + .collect(); + + // Regex to detect if a rule has configuration options in its debug output. + // + // Matches: + // - Any usage of curly braces `{}` (struct configs) + // - Any usage of square brackets `[]` (array configs) + // - Nested content in parentheses like `PluginRuleName(RuleName(Foo))` (enum configs) + // + // It will NOT match simple wrapper patterns like `Foo(Bar)`, with no nested content. + // + // Examples of rules with config options: + // - `UnicornPreferAt(PreferAt(PreferAtConfig { check_all_index_access: false, get_last_element_functions: [] }))` + // - `PromiseNoReturnWrap(NoReturnWrap { allow_reject: false })` + // - `VueDefineEmitsDeclaration(DefineEmitsDeclaration(TypeBased))` + // + // A rule with no configuration options would look like this, with no nesting past the first level: + // - `UnicornPreferTopLevelAwait(PreferTopLevelAwait)` + let config_regex = Regex::new(r"[{}\[\]]|\(\w+\([^)]+\)\)").unwrap(); + + // Check each rule to see if it has configuration options but no schema + for rule in RULES.iter() { + let full_rule_name = format!("{}/{}", rule.plugin_name(), rule.name()); + + // Skip if in exceptions list + if exception_set.contains(full_rule_name.as_str()) { + // Error if it is listed as an exception but has a schema defined + if rules_with_schemas.contains(&full_rule_name) { + failures.push(format!( + "Rule '{full_rule_name}' is in the exceptions list but has a schema defined.\n\ + This rule has been fixed! Please remove it from the exceptions list." + )); + } + continue; + } + + // Check if this rule has configuration options by looking at the debug + // output of its default values. + // + // This should work in all normal cases, but there may be a better option if we + // can check which rules have `from_configuration` defined explicitly in their + // source. + let default_rule = rule.clone(); + let rule_debug = format!("{default_rule:?}"); + let rule_has_config_options = config_regex.is_match(&rule_debug); + + // Skip rules with config options that already have schemas. + if rules_with_schemas.contains(&full_rule_name) && rule_has_config_options { + continue; + } + + // If a rule has a schema but no config options, fail. Something has likely broken about the way + // we're checking for this. + if rules_with_schemas.contains(&full_rule_name) && !rule_has_config_options { + failures.push(format!( + "Rule '{full_rule_name}' has a schema defined but no configuration options.\n\ + Please add configuration options to this rule.", + )); + } + + // Fail here if the rule has config options but no schema. + if rule_has_config_options { + failures.push(format!( + "Rule '{full_rule_name}' accepts configuration options but has no schema defined.\n\ + Please see the oxc website for info on adding config option schemas and docs to this rule.\n\ + https://oxc.rs/docs/contribute/linter/adding-rules.html", + )); + } + } + + // Verify all exceptions actually exist in the rule table + let all_rules: FxHashSet = table + .sections + .iter() + .flat_map(|section| §ion.rows) + .map(|rule| format!("{}/{}", rule.plugin, rule.name)) + .collect(); + + for &exception_rule in exceptions { + if !all_rules.contains(exception_rule) { + failures.push(format!( + "Exception rule '{exception_rule}' is in the exceptions list but does not exist in the linter.\n\ + This rule may have been removed or renamed. Please remove it from the exceptions list." + )); + } + } + + assert!( + failures.is_empty(), + "Found {} configuration documentation issues:\n\n{}", + failures.len(), + failures.join("\n\n") + ); +} diff --git a/crates/oxc_mangler/Cargo.toml b/crates/oxc_mangler/Cargo.toml index d08d7268af20a..25d05c64152a6 100644 --- a/crates/oxc_mangler/Cargo.toml +++ b/crates/oxc_mangler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_mangler" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_minifier/CHANGELOG.md b/crates/oxc_minifier/CHANGELOG.md index 8f803c0e81b26..fd599bb2da33f 100644 --- a/crates/oxc_minifier/CHANGELOG.md +++ b/crates/oxc_minifier/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 🐛 Bug Fixes + +- 8b14ec9 minifier: Handle `{ __proto__: null } instanceof Object` correctly (#15217) (sapphi-red) + ## [0.96.0] - 2025-10-30 ### 🚀 Features diff --git a/crates/oxc_minifier/Cargo.toml b/crates/oxc_minifier/Cargo.toml index c4915fd9069e5..b878919a72427 100644 --- a/crates/oxc_minifier/Cargo.toml +++ b/crates/oxc_minifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_minifier" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_minifier/src/peephole/mod.rs b/crates/oxc_minifier/src/peephole/mod.rs index dd1b808f6a8bb..a5ba67298aacb 100644 --- a/crates/oxc_minifier/src/peephole/mod.rs +++ b/crates/oxc_minifier/src/peephole/mod.rs @@ -219,6 +219,7 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations { Self::minimize_binary(expr, ctx); Self::substitute_loose_equals_undefined(expr, ctx); Self::substitute_typeof_undefined(expr, ctx); + Self::substitute_rotate_binary_expression(expr, ctx); } Expression::UnaryExpression(_) => { Self::fold_unary_expr(expr, ctx); diff --git a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs index 19289ec02f389..0ca9d0a23f86f 100644 --- a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs @@ -8,6 +8,7 @@ use oxc_ecmascript::{ToJsString, ToNumber, side_effects::MayHaveSideEffects}; use oxc_semantic::ReferenceFlags; use oxc_span::GetSpan; use oxc_span::SPAN; +use oxc_syntax::precedence::GetPrecedence; use oxc_syntax::{ number::NumberBase, operator::{BinaryOperator, UnaryOperator}, @@ -310,6 +311,71 @@ impl<'a> PeepholeOptimizations { ctx.state.changed = true; } + /// Rotate associative binary operators: + /// - `a | (b | c)` -> `(a | b) | c` + /// + /// Rotate commutative operators to reduce parentheses: + /// - `a * (b % c)` -> `b % c * a` + /// - `a * (b / c)` -> `b / c * a` + pub fn substitute_rotate_binary_expression(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) { + let Expression::BinaryExpression(e) = expr else { return }; + + // Handle associative rotation + let is_associative = matches!( + e.operator, + BinaryOperator::BitwiseOR | BinaryOperator::BitwiseAnd | BinaryOperator::BitwiseXOR + ); + if is_associative + && let Expression::BinaryExpression(right) = &e.right + && right.operator == e.operator + && !right.right.may_have_side_effects(ctx) + { + let Expression::BinaryExpression(mut right) = e.right.take_in(ctx.ast) else { + return; + }; + let mut new_left = ctx.ast.expression_binary( + e.span, + e.left.take_in(ctx.ast), + e.operator, + right.left.take_in(ctx.ast), + ); + Self::substitute_rotate_binary_expression(&mut new_left, ctx); + *expr = ctx.ast.expression_binary( + e.span, + new_left, + e.operator, + right.right.take_in(ctx.ast), + ); + ctx.state.changed = true; + return; + } + + // Handle commutative rotation + if let Expression::BinaryExpression(right) = &e.right + && matches!(e.operator, BinaryOperator::Multiplication) + && e.operator.precedence() == right.operator.precedence() + { + // Don't swap if left does not need a parentheses + if let Expression::BinaryExpression(left) = &e.left + && e.operator.precedence() <= left.operator.precedence() + { + return; + } + + // Don't swap if any of the value may have side effects as they may update the other values + if !e.left.may_have_side_effects(ctx) + && !right.left.may_have_side_effects(ctx) + && !right.right.may_have_side_effects(ctx) + { + let left = e.left.take_in(ctx.ast); + let right = e.right.take_in(ctx.ast); + e.right = left; + e.left = right; + ctx.state.changed = true; + } + } + } + /// Compress `typeof foo === 'object' && foo !== null` into `typeof foo == 'object' && !!foo`. /// /// - `typeof foo === 'object' && foo !== null` => `typeof foo == 'object' && !!foo` @@ -1910,25 +1976,73 @@ mod test { test_same("(f.bind(a))(b)"); } - // FIXME: the cases commented out can be implemented #[test] fn test_rotate_associative_operators() { - test("a || (b || c)", "(a || b) || c"); - // float multiplication is not always associative - // - test_same("a * (b * c)"); - // test("a | (b | c)", "(a | b) | c"); - test_same("a % (b % c)"); - test_same("a / (b / c)"); - test_same("a - (b - c);"); - // test("a * (b % c);", "b % c * a"); - // test("a * (b / c);", "b / c * a"); - // cannot transform to `c / d * a * b` - test_same("a * b * (c / d)"); - // test("(a + b) * (c % d)", "c % d * (a + b)"); - test_same("(a / b) * (c % d)"); - test_same("(c = 5) * (c % d)"); - // test("!a * c * (d % e)", "d % e * c * !a"); + test( + "function f(a, b, c) { return a || (b || c) }", + "function f(a, b, c) { return (a || b) || c }", + ); + test( + "function f(a, b, c) { return a && (b && c) }", + "function f(a, b, c) { return (a && b) && c }", + ); + test( + "function f(a, b, c) { return a ?? (b ?? c) }", + "function f(a, b, c) { return (a ?? b) ?? c }", + ); + + test( + "function f(a, b, c) { return a | (b | c) }", + "function f(a, b, c) { return (a | b) | c }", + ); + test( + "function f(a, b, c) { return a() | (b | c) }", + "function f(a, b, c) { return (a() | b) | c }", + ); + test( + "function f(a, b, c) { return a | (b() | c) }", + "function f(a, b, c) { return (a | b()) | c }", + ); + // c() will not be executed when `a | b` throws an error + test_same("function f(a, b, c) { return a | (b | c()) }"); + test( + "function f(a, b, c) { return a & (b & c) }", + "function f(a, b, c) { return (a & b) & c }", + ); + test( + "function f(a, b, c) { return a ^ (b ^ c) }", + "function f(a, b, c) { return (a ^ b) ^ c }", + ); + + // avoid rotation to prevent precision loss + // also multiplication is not associative due to floating point precision + // https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-multiply + test_same("function f(a, b, c) { return a + (b + c) }"); + test_same("function f(a, b, c) { return a - (b - c) }"); + test_same("function f(a, b, c) { return a / (b / c) }"); + test_same("function f(a, b, c) { return a % (b % c) }"); + test_same("function f(a, b, c) { return a ** (b ** c) }"); + + test( + "function f(a, b, c) { return a * (b % c) }", + "function f(a, b, c) { return b % c * a }", + ); + test_same("function f(a, b, c) { return a() * (b % c) }"); // a may update b / c + test_same("function f(a, b, c) { return a * (b() % c) }"); // b may update b / c + test_same("function f(a, b, c) { return a * (b % c()) }"); // c may update b / c + test( + "function f(a, b, c) { return a * (b / c) }", + "function f(a, b, c) { return b / c * a }", + ); + test( + "function f(a, b, c) { return a * (b * c) }", + "function f(a, b, c) { return b * c * a }", + ); + + test_same("function f(a, b, c, d) { return a * b * (c / d) }"); + test_same("function f(a, b, c, d) { return (a + b) * (c % d) }"); + // Don't swap if left has division (already high precedence) + test_same("function f(a, b, c, d) { return a / b * (c % d) }"); } #[test] diff --git a/crates/oxc_napi/Cargo.toml b/crates/oxc_napi/Cargo.toml index aec7d8e28618e..19d9ef38b6653 100644 --- a/crates/oxc_napi/Cargo.toml +++ b/crates/oxc_napi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_napi" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_parser/CHANGELOG.md b/crates/oxc_parser/CHANGELOG.md index f1d878c12d45a..2f8112461ace6 100644 --- a/crates/oxc_parser/CHANGELOG.md +++ b/crates/oxc_parser/CHANGELOG.md @@ -4,6 +4,40 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 🚀 Features + +- 8951953 parser: Improve diagnostic messages for merge conflicts (#15443) (camc314) +- 73f9e29 parser: Show allowed modifiers in invalid modifier error messages (#15442) (sapphi-red) +- 5616ad5 parser,semantic: Add TS1274 error (#15441) (sapphi-red) +- 0fa484d parser: Improve error messages for missing closing parentheses (#15446) (sapphi-red) +- 4decc1d parser: Improve error message for missing block closing tokens (#15445) (sapphi-red) +- e1704a4 parser: Improve error message for missing closing tokens that may be followed by a rest element (#15444) (sapphi-red) +- b7e3849 parser: Add more help messages to diagnostics (#15440) (sapphi-red) +- d4f6545 parser: Improve error message for missing closing tokens (#15269) (sapphi-red) +- 2ef8f01 parser: Improve error message for missing conditional alternative (#15268) (sapphi-red) +- 5f203c6 parser: Improve trailing comma error messages (#15267) (sapphi-red) +- e62d14a parser: Improve error message for using declarations with `export` (#15266) (sapphi-red) +- 682dca2 parser: Add more helps to parser errors (#15186) (sapphi-red) + +### 🐛 Bug Fixes + +- 732205e parser: Reject `using` / `await using` in a switch `case` / `default` clause (#15225) (sapphi-red) +- 4668004 parser: Reject `using` / `await using` in single statement contexts (#15224) (sapphi-red) +- 0398d40 parser: Reject trailing commas after rest elements in object patterns (#15223) (sapphi-red) +- c28807b parser: Reject `async await => {}` in scripts (#15222) (sapphi-red) +- 837ef21 parser: Reject `yield` and `await` in object destructing shorthand property parameters (#15221) (sapphi-red) +- 2d6d3a8 parser: Reject `for (let.something of ...)` (#15220) (sapphi-red) +- 97ab60d parser: Reject invalid assignment operator in assignment targets (#15219) (sapphi-red) + +### ⚡ Performance + +- f1efc63 lexer: Skip single space in `read_next_token` (#15513) (overlookmotel) +- b310c28 lexer: Inline `handle_byte` into `read_next_token` (#15520) (overlookmotel) +- 2f0518d lexer: Hint to compiler that EOF only happens once (#15512) (overlookmotel) +- 5f08c69 lexer: Remove branches from unicode handling (#15328) (overlookmotel) + ## [0.96.0] - 2025-10-30 ### 🐛 Bug Fixes diff --git a/crates/oxc_parser/Cargo.toml b/crates/oxc_parser/Cargo.toml index 4e943bc5ca24e..55c29601fbc57 100644 --- a/crates/oxc_parser/Cargo.toml +++ b/crates/oxc_parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_parser" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_regular_expression/Cargo.toml b/crates/oxc_regular_expression/Cargo.toml index 6be8b2e2081d7..879d3109f37ce 100644 --- a/crates/oxc_regular_expression/Cargo.toml +++ b/crates/oxc_regular_expression/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_regular_expression" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_semantic/CHANGELOG.md b/crates/oxc_semantic/CHANGELOG.md index 2e644a717deab..327255128da2e 100644 --- a/crates/oxc_semantic/CHANGELOG.md +++ b/crates/oxc_semantic/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 🚀 Features + +- 5616ad5 parser,semantic: Add TS1274 error (#15441) (sapphi-red) +- 8d69661 allocator: Add `Address::from_ref` method (#15318) (overlookmotel) +- 682dca2 parser: Add more helps to parser errors (#15186) (sapphi-red) + +### 🐛 Bug Fixes + +- 4a54107 semantic: Allow `arguments` in the class field keys (#15227) (sapphi-red) + ## [0.96.0] - 2025-10-30 ### 🚀 Features diff --git a/crates/oxc_semantic/Cargo.toml b/crates/oxc_semantic/Cargo.toml index dd676d28db0dd..e5ecc4263a022 100644 --- a/crates/oxc_semantic/Cargo.toml +++ b/crates/oxc_semantic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_semantic" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_semantic/tests/fixtures/oxc/js/assignments/nested-assignment.snap b/crates/oxc_semantic/tests/fixtures/oxc/js/assignments/nested-assignment.snap index a6744ba8cdccd..828b4e4778803 100644 --- a/crates/oxc_semantic/tests/fixtures/oxc/js/assignments/nested-assignment.snap +++ b/crates/oxc_semantic/tests/fixtures/oxc/js/assignments/nested-assignment.snap @@ -37,7 +37,7 @@ input_file: crates/oxc_semantic/tests/fixtures/oxc/js/assignments/nested-assignm "flags": "ReferenceFlags(Read)", "id": 4, "name": "y", - "node_id": 38 + "node_id": 37 } ] } diff --git a/crates/oxc_semantic/tests/fixtures/oxc/js/try-catch/with-same-name-var.snap b/crates/oxc_semantic/tests/fixtures/oxc/js/try-catch/with-same-name-var.snap index bed7eea9bd32b..6ebaaab6472dc 100644 --- a/crates/oxc_semantic/tests/fixtures/oxc/js/try-catch/with-same-name-var.snap +++ b/crates/oxc_semantic/tests/fixtures/oxc/js/try-catch/with-same-name-var.snap @@ -50,7 +50,7 @@ input_file: crates/oxc_semantic/tests/fixtures/oxc/js/try-catch/with-same-name-v "flags": "ReferenceFlags(Read)", "id": 1, "name": "a", - "node_id": 23 + "node_id": 22 } ] } diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/call-expression/call-expression.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/call-expression/call-expression.snap index 3569953092a34..1fe41d1313426 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/call-expression/call-expression.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/call-expression/call-expression.snap @@ -33,7 +33,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/call-expression "flags": "ReferenceFlags(Read)", "id": 2, "name": "foo", - "node_id": 19 + "node_id": 18 } ] }, @@ -47,13 +47,13 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/call-expression "flags": "ReferenceFlags(Read)", "id": 1, "name": "a", - "node_id": 15 + "node_id": 14 }, { "flags": "ReferenceFlags(Read)", "id": 3, "name": "a", - "node_id": 21 + "node_id": 19 } ] } diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/class/declaration/method-param-default.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/class/declaration/method-param-default.snap index 98259835e4fe3..5e6f85d85a7c5 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/class/declaration/method-param-default.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/class/declaration/method-param-default.snap @@ -45,7 +45,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/class/declarati "flags": "ReferenceFlags(Read)", "id": 1, "name": "printerName", - "node_id": 36 + "node_id": 35 } ] } diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/new-expression/new-expression.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/new-expression/new-expression.snap index 1668230b3beba..f683174af5090 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/new-expression/new-expression.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/new-expression/new-expression.snap @@ -41,7 +41,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/new-expression/ "flags": "ReferenceFlags(Read)", "id": 1, "name": "a", - "node_id": 12 + "node_id": 11 } ] } diff --git a/crates/oxc_span/CHANGELOG.md b/crates/oxc_span/CHANGELOG.md index 7b990e4cb1e85..145f199dc9506 100644 --- a/crates/oxc_span/CHANGELOG.md +++ b/crates/oxc_span/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 📚 Documentation + +- 3dc24b5 linter,minifier: Always refer as "ES Modules" instead of "ES6 Modules" (#15409) (sapphi-red) + diff --git a/crates/oxc_span/Cargo.toml b/crates/oxc_span/Cargo.toml index 2e7c192cd1a79..000a734bd09dd 100644 --- a/crates/oxc_span/Cargo.toml +++ b/crates/oxc_span/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_span" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_syntax/Cargo.toml b/crates/oxc_syntax/Cargo.toml index 9ffffff19fe13..53a9091226049 100644 --- a/crates/oxc_syntax/Cargo.toml +++ b/crates/oxc_syntax/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_syntax" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_transformer/CHANGELOG.md b/crates/oxc_transformer/CHANGELOG.md index 8cbecb7776893..8922a420fedaa 100644 --- a/crates/oxc_transformer/CHANGELOG.md +++ b/crates/oxc_transformer/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 🚀 Features + +- 8d69661 allocator: Add `Address::from_ref` method (#15318) (overlookmotel) +- 2c15353 transformer: Warn top level await usage if not supported (#14276) (Copilot) +- aee6310 transformer: Add warning for arbitrary module namespace identifier names (#15035) (Copilot) + +### 🐛 Bug Fixes + +- 7a5c011 transformer: Convert enum numbers to strings in template literals (#15183) (Copilot) + +### 📚 Documentation + +- 4b904b1 transformer: Clarify `jsx.pure` option would affect JSX elements (#15376) (sapphi-red) + ## [0.96.0] - 2025-10-30 ### 🐛 Bug Fixes diff --git a/crates/oxc_transformer/Cargo.toml b/crates/oxc_transformer/Cargo.toml index 5b5ff50ec6405..88bbde703fdd9 100644 --- a/crates/oxc_transformer/Cargo.toml +++ b/crates/oxc_transformer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_transformer" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_transformer/src/typescript/annotations.rs b/crates/oxc_transformer/src/typescript/annotations.rs index 20c09753686a5..d4b06d3a6102f 100644 --- a/crates/oxc_transformer/src/typescript/annotations.rs +++ b/crates/oxc_transformer/src/typescript/annotations.rs @@ -216,7 +216,17 @@ impl<'a> Traverse<'a, TransformState<'a>> for TypeScriptAnnotations<'a, '_> { } } + fn enter_function(&mut self, func: &mut Function<'a>, _ctx: &mut TraverseCtx<'a>) { + // Remove TypeScript annotations from function declarations + // Note: declare flag is preserved for exit_statements to handle declaration removal + func.type_parameters = None; + func.return_type = None; + func.this_param = None; + } + fn enter_class(&mut self, class: &mut Class<'a>, _ctx: &mut TraverseCtx<'a>) { + // Remove TypeScript annotations from class declarations + // Note: declare flag is preserved for exit_statements to handle declaration removal class.type_parameters = None; class.super_type_arguments = None; class.implements.clear(); @@ -366,12 +376,18 @@ impl<'a> Traverse<'a, TransformState<'a>> for TypeScriptAnnotations<'a, '_> { stmts: &mut ArenaVec<'a, Statement<'a>>, _ctx: &mut TraverseCtx<'a>, ) { - // Remove declare declaration - stmts.retain( - |stmt| { - if let Some(decl) = stmt.as_declaration() { !decl.declare() } else { true } - }, - ); + // Remove TypeScript type-only declarations (interfaces, type aliases, etc.) + // but NOT declarations with `declare` keyword - those will be handled + // by their respective enter_* methods which will remove the `declare` flag + stmts.retain(|stmt| { + if let Some(decl) = stmt.as_declaration() { + // Only remove pure TypeScript type declarations + // Keep all other declarations including those with `declare` + !decl.is_type() + } else { + true + } + }); } fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { @@ -401,9 +417,28 @@ impl<'a> Traverse<'a, TransformState<'a>> for TypeScriptAnnotations<'a, '_> { _ctx: &mut TraverseCtx<'a>, ) { // Remove TS specific statements - stmts.retain(|stmt| match stmt { + stmts.retain_mut(|stmt| match stmt { Statement::ExpressionStatement(s) => !s.expression.is_typescript_syntax(), - match_declaration!(Statement) => !stmt.to_declaration().is_typescript_syntax(), + match_declaration!(Statement) => { + let decl = stmt.to_declaration_mut(); + match decl { + Declaration::VariableDeclaration(var_decl) => { + // Remove declare variable declarations entirely + !var_decl.declare + } + Declaration::FunctionDeclaration(func_decl) => { + // Remove declare function declarations and function overload signatures entirely + // Keep only function implementations (those with a body) + !func_decl.declare && func_decl.body.is_some() + } + Declaration::ClassDeclaration(class_decl) => { + // Remove declare class declarations entirely + !class_decl.declare + } + // Remove type-only declarations + _ => !decl.is_typescript_syntax(), + } + } // Ignore ModuleDeclaration as it's handled in the program _ => true, }); diff --git a/crates/oxc_transformer/src/typescript/mod.rs b/crates/oxc_transformer/src/typescript/mod.rs index a43f8c588e4ca..3f7a6d06e38cf 100644 --- a/crates/oxc_transformer/src/typescript/mod.rs +++ b/crates/oxc_transformer/src/typescript/mod.rs @@ -282,6 +282,18 @@ impl<'a> Traverse<'a, TransformState<'a>> for TypeScript<'a, '_> { self.annotations.enter_jsx_fragment(elem, ctx); } + fn enter_variable_declaration( + &mut self, + decl: &mut VariableDeclaration<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.annotations.enter_variable_declaration(decl, ctx); + } + + fn enter_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { + self.annotations.enter_function(func, ctx); + } + fn enter_declaration(&mut self, node: &mut Declaration<'a>, ctx: &mut TraverseCtx<'a>) { self.module.enter_declaration(node, ctx); } diff --git a/crates/oxc_transformer_plugins/CHANGELOG.md b/crates/oxc_transformer_plugins/CHANGELOG.md index 856423c3006f2..13579235b29e5 100644 --- a/crates/oxc_transformer_plugins/CHANGELOG.md +++ b/crates/oxc_transformer_plugins/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 🚀 Features + +- 8d69661 allocator: Add `Address::from_ref` method (#15318) (overlookmotel) +- 9d568eb transformer_plugins: Support import.meta injection in inject_global_variables (#15125) (Copilot) + ## [0.95.0] - 2025-10-15 diff --git a/crates/oxc_transformer_plugins/Cargo.toml b/crates/oxc_transformer_plugins/Cargo.toml index 88bb4922d3620..dde47d8ac5308 100644 --- a/crates/oxc_transformer_plugins/Cargo.toml +++ b/crates/oxc_transformer_plugins/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_transformer_plugins" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_traverse/Cargo.toml b/crates/oxc_traverse/Cargo.toml index 1514f04759beb..2265172dc892a 100644 --- a/crates/oxc_traverse/Cargo.toml +++ b/crates/oxc_traverse/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_traverse" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/editors/vscode/package.json b/editors/vscode/package.json index e9a33ad2f751e..6030f9ed79c22 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -257,7 +257,7 @@ "@vscode/vsce": "^3.0.0", "cross-env": "^10.0.0", "ovsx": "^0.10.0", - "rolldown": "1.0.0-beta.46", + "rolldown": "1.0.0-beta.49", "tinyglobby": "^0.2.15", "typescript": "catalog:" }, diff --git a/justfile b/justfile index bf5972b4f835d..33785da445289 100755 --- a/justfile +++ b/justfile @@ -123,10 +123,13 @@ codecov: # ==================== AST & CODEGEN ==================== -# Generate AST related boilerplate code +# Generate AST related boilerplate code. +# If fails first time, run with JS generators disabled first, and then again with JS generators enabled. +# This is necessary because JS generators use `oxc_*` crates (e.g. `oxc_minifier`), and those crates may not compile +# unless Rust code is generated first. +# See: https://github.com/oxc-project/oxc/issues/15564 ast: - cargo run -p oxc_ast_tools - just check + cargo run -p oxc_ast_tools || (cargo run -p oxc_ast_tools --no-default-features && cargo run -p oxc_ast_tools) # ==================== PARSER ==================== diff --git a/napi/minify/CHANGELOG.md b/napi/minify/CHANGELOG.md index 26eca2087b662..504c63ba87b99 100644 --- a/napi/minify/CHANGELOG.md +++ b/napi/minify/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 🚀 Features + +- 1c31cb1 napi/minify: Expose `treeshake` options (#15109) (copilot-swe-agent) + +### 📚 Documentation + +- 3dc24b5 linter,minifier: Always refer as "ES Modules" instead of "ES6 Modules" (#15409) (sapphi-red) + ## [0.96.0] - 2025-10-30 ### 🚀 Features diff --git a/napi/minify/Cargo.toml b/napi/minify/Cargo.toml index b7b277a16dce1..931a3f49d03e8 100644 --- a/napi/minify/Cargo.toml +++ b/napi/minify/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_minify_napi" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/napi/minify/index.js b/napi/minify/index.js index 6871d5bdb7e72..fa75889022bbf 100644 --- a/napi/minify/index.js +++ b/napi/minify/index.js @@ -81,8 +81,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-android-arm64') const bindingPackageVersion = require('@oxc-minify/binding-android-arm64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -97,8 +97,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-android-arm-eabi') const bindingPackageVersion = require('@oxc-minify/binding-android-arm-eabi/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -118,8 +118,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-win32-x64-gnu') const bindingPackageVersion = require('@oxc-minify/binding-win32-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -134,8 +134,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-win32-x64-msvc') const bindingPackageVersion = require('@oxc-minify/binding-win32-x64-msvc/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -151,8 +151,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-win32-ia32-msvc') const bindingPackageVersion = require('@oxc-minify/binding-win32-ia32-msvc/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -167,8 +167,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-win32-arm64-msvc') const bindingPackageVersion = require('@oxc-minify/binding-win32-arm64-msvc/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -186,8 +186,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-darwin-universal') const bindingPackageVersion = require('@oxc-minify/binding-darwin-universal/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -202,8 +202,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-darwin-x64') const bindingPackageVersion = require('@oxc-minify/binding-darwin-x64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -218,8 +218,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-darwin-arm64') const bindingPackageVersion = require('@oxc-minify/binding-darwin-arm64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -238,8 +238,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-freebsd-x64') const bindingPackageVersion = require('@oxc-minify/binding-freebsd-x64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -254,8 +254,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-freebsd-arm64') const bindingPackageVersion = require('@oxc-minify/binding-freebsd-arm64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -275,8 +275,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-linux-x64-musl') const bindingPackageVersion = require('@oxc-minify/binding-linux-x64-musl/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -291,8 +291,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-linux-x64-gnu') const bindingPackageVersion = require('@oxc-minify/binding-linux-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -309,8 +309,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-linux-arm64-musl') const bindingPackageVersion = require('@oxc-minify/binding-linux-arm64-musl/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -325,8 +325,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-linux-arm64-gnu') const bindingPackageVersion = require('@oxc-minify/binding-linux-arm64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -343,8 +343,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-linux-arm-musleabihf') const bindingPackageVersion = require('@oxc-minify/binding-linux-arm-musleabihf/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -359,8 +359,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-linux-arm-gnueabihf') const bindingPackageVersion = require('@oxc-minify/binding-linux-arm-gnueabihf/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -377,8 +377,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-linux-loong64-musl') const bindingPackageVersion = require('@oxc-minify/binding-linux-loong64-musl/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -393,8 +393,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-linux-loong64-gnu') const bindingPackageVersion = require('@oxc-minify/binding-linux-loong64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -411,8 +411,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-linux-riscv64-musl') const bindingPackageVersion = require('@oxc-minify/binding-linux-riscv64-musl/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -427,8 +427,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-linux-riscv64-gnu') const bindingPackageVersion = require('@oxc-minify/binding-linux-riscv64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -444,8 +444,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-linux-ppc64-gnu') const bindingPackageVersion = require('@oxc-minify/binding-linux-ppc64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -460,8 +460,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-linux-s390x-gnu') const bindingPackageVersion = require('@oxc-minify/binding-linux-s390x-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -480,8 +480,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-openharmony-arm64') const bindingPackageVersion = require('@oxc-minify/binding-openharmony-arm64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -496,8 +496,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-openharmony-x64') const bindingPackageVersion = require('@oxc-minify/binding-openharmony-x64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -512,8 +512,8 @@ function requireNative() { try { const binding = require('@oxc-minify/binding-openharmony-arm') const bindingPackageVersion = require('@oxc-minify/binding-openharmony-arm/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { diff --git a/napi/minify/package.json b/napi/minify/package.json index 6f412f525fffe..e187b001a5d43 100644 --- a/napi/minify/package.json +++ b/napi/minify/package.json @@ -1,6 +1,6 @@ { "name": "oxc-minify", - "version": "0.96.0", + "version": "0.97.0", "type": "module", "main": "index.js", "browser": "browser.js", diff --git a/napi/parser/CHANGELOG.md b/napi/parser/CHANGELOG.md index a9c2601732044..8b0d3f4e6823f 100644 --- a/napi/parser/CHANGELOG.md +++ b/napi/parser/CHANGELOG.md @@ -4,6 +4,20 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 🚀 Features + +- 682dca2 parser: Add more helps to parser errors (#15186) (sapphi-red) + +### 🐛 Bug Fixes + +- 7f079ab ast/estree: Fix raw transfer deserializer for `AssignmentTargetPropertyIdentifier` (#15304) (overlookmotel) + +### ⚡ Performance + +- c82fab0 ast/estree: Remove pointless assignments from raw transfer deserializers (#15305) (overlookmotel) + ## [0.96.0] - 2025-10-30 ### 🚀 Features diff --git a/napi/parser/Cargo.toml b/napi/parser/Cargo.toml index de637df184b58..db762d63945a3 100644 --- a/napi/parser/Cargo.toml +++ b/napi/parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_parser_napi" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/napi/parser/package.json b/napi/parser/package.json index fb6e3a0f848f7..58956d0edc8ac 100644 --- a/napi/parser/package.json +++ b/napi/parser/package.json @@ -1,6 +1,6 @@ { "name": "oxc-parser", - "version": "0.96.0", + "version": "0.97.0", "type": "module", "main": "src-js/index.js", "browser": "src-js/wasm.js", diff --git a/napi/parser/src-js/bindings.js b/napi/parser/src-js/bindings.js index 3693d87212e22..87a3a06d49f56 100644 --- a/napi/parser/src-js/bindings.js +++ b/napi/parser/src-js/bindings.js @@ -81,8 +81,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-android-arm64') const bindingPackageVersion = require('@oxc-parser/binding-android-arm64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -97,8 +97,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-android-arm-eabi') const bindingPackageVersion = require('@oxc-parser/binding-android-arm-eabi/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -118,8 +118,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-win32-x64-gnu') const bindingPackageVersion = require('@oxc-parser/binding-win32-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -134,8 +134,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-win32-x64-msvc') const bindingPackageVersion = require('@oxc-parser/binding-win32-x64-msvc/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -151,8 +151,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-win32-ia32-msvc') const bindingPackageVersion = require('@oxc-parser/binding-win32-ia32-msvc/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -167,8 +167,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-win32-arm64-msvc') const bindingPackageVersion = require('@oxc-parser/binding-win32-arm64-msvc/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -186,8 +186,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-darwin-universal') const bindingPackageVersion = require('@oxc-parser/binding-darwin-universal/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -202,8 +202,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-darwin-x64') const bindingPackageVersion = require('@oxc-parser/binding-darwin-x64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -218,8 +218,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-darwin-arm64') const bindingPackageVersion = require('@oxc-parser/binding-darwin-arm64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -238,8 +238,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-freebsd-x64') const bindingPackageVersion = require('@oxc-parser/binding-freebsd-x64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -254,8 +254,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-freebsd-arm64') const bindingPackageVersion = require('@oxc-parser/binding-freebsd-arm64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -275,8 +275,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-linux-x64-musl') const bindingPackageVersion = require('@oxc-parser/binding-linux-x64-musl/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -291,8 +291,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-linux-x64-gnu') const bindingPackageVersion = require('@oxc-parser/binding-linux-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -309,8 +309,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-linux-arm64-musl') const bindingPackageVersion = require('@oxc-parser/binding-linux-arm64-musl/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -325,8 +325,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-linux-arm64-gnu') const bindingPackageVersion = require('@oxc-parser/binding-linux-arm64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -343,8 +343,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-linux-arm-musleabihf') const bindingPackageVersion = require('@oxc-parser/binding-linux-arm-musleabihf/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -359,8 +359,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-linux-arm-gnueabihf') const bindingPackageVersion = require('@oxc-parser/binding-linux-arm-gnueabihf/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -377,8 +377,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-linux-loong64-musl') const bindingPackageVersion = require('@oxc-parser/binding-linux-loong64-musl/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -393,8 +393,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-linux-loong64-gnu') const bindingPackageVersion = require('@oxc-parser/binding-linux-loong64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -411,8 +411,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-linux-riscv64-musl') const bindingPackageVersion = require('@oxc-parser/binding-linux-riscv64-musl/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -427,8 +427,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-linux-riscv64-gnu') const bindingPackageVersion = require('@oxc-parser/binding-linux-riscv64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -444,8 +444,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-linux-ppc64-gnu') const bindingPackageVersion = require('@oxc-parser/binding-linux-ppc64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -460,8 +460,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-linux-s390x-gnu') const bindingPackageVersion = require('@oxc-parser/binding-linux-s390x-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -480,8 +480,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-openharmony-arm64') const bindingPackageVersion = require('@oxc-parser/binding-openharmony-arm64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -496,8 +496,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-openharmony-x64') const bindingPackageVersion = require('@oxc-parser/binding-openharmony-x64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -512,8 +512,8 @@ function requireNative() { try { const binding = require('@oxc-parser/binding-openharmony-arm') const bindingPackageVersion = require('@oxc-parser/binding-openharmony-arm/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { diff --git a/napi/playground/src/lib.rs b/napi/playground/src/lib.rs index 44852d2306cd1..a7dadcf9a589d 100644 --- a/napi/playground/src/lib.rs +++ b/napi/playground/src/lib.rs @@ -514,6 +514,8 @@ impl Oxc { .as_ref() .and_then(|o| o.parse::().ok()) .unwrap_or_default(); + // TODO: support from options + let groups = SortImports::default_groups(); format_options.experimental_sort_imports = Some(SortImports { partition_by_newline: sort_imports_config.partition_by_newline.unwrap_or(false), @@ -522,6 +524,7 @@ impl Oxc { order, ignore_case: sort_imports_config.ignore_case.unwrap_or(true), newlines_between: sort_imports_config.newlines_between.unwrap_or(true), + groups, }); } diff --git a/napi/playground/src/options.rs b/napi/playground/src/options.rs index 0a568ab85fe29..eb4d974d8797f 100644 --- a/napi/playground/src/options.rs +++ b/napi/playground/src/options.rs @@ -146,7 +146,7 @@ pub struct OxcFormatterOptions { pub single_attribute_per_line: Option, /// Operator position: "start" | "end" (default: "end") pub experimental_operator_position: Option, - /// Sort imports configuration + /// Sort imports configuration (default: None) pub experimental_sort_imports: Option, } @@ -165,4 +165,6 @@ pub struct OxcSortImportsOptions { pub ignore_case: Option, /// Add newlines between import groups (default: true) pub newlines_between: Option, + /// Custom groups of imports + pub groups: Option>>, } diff --git a/napi/transform/CHANGELOG.md b/napi/transform/CHANGELOG.md index abf59fd04b405..ca20c4a44d425 100644 --- a/napi/transform/CHANGELOG.md +++ b/napi/transform/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 📚 Documentation + +- 4b904b1 transformer: Clarify `jsx.pure` option would affect JSX elements (#15376) (sapphi-red) + ## [0.94.0] - 2025-10-06 diff --git a/napi/transform/Cargo.toml b/napi/transform/Cargo.toml index 9eb248f0d1602..12b266d476956 100644 --- a/napi/transform/Cargo.toml +++ b/napi/transform/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_transform_napi" -version = "0.96.0" +version = "0.97.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/napi/transform/index.js b/napi/transform/index.js index 1113d2d589e62..b37628b13b9ae 100644 --- a/napi/transform/index.js +++ b/napi/transform/index.js @@ -81,8 +81,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-android-arm64') const bindingPackageVersion = require('@oxc-transform/binding-android-arm64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -97,8 +97,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-android-arm-eabi') const bindingPackageVersion = require('@oxc-transform/binding-android-arm-eabi/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -118,8 +118,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-win32-x64-gnu') const bindingPackageVersion = require('@oxc-transform/binding-win32-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -134,8 +134,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-win32-x64-msvc') const bindingPackageVersion = require('@oxc-transform/binding-win32-x64-msvc/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -151,8 +151,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-win32-ia32-msvc') const bindingPackageVersion = require('@oxc-transform/binding-win32-ia32-msvc/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -167,8 +167,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-win32-arm64-msvc') const bindingPackageVersion = require('@oxc-transform/binding-win32-arm64-msvc/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -186,8 +186,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-darwin-universal') const bindingPackageVersion = require('@oxc-transform/binding-darwin-universal/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -202,8 +202,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-darwin-x64') const bindingPackageVersion = require('@oxc-transform/binding-darwin-x64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -218,8 +218,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-darwin-arm64') const bindingPackageVersion = require('@oxc-transform/binding-darwin-arm64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -238,8 +238,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-freebsd-x64') const bindingPackageVersion = require('@oxc-transform/binding-freebsd-x64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -254,8 +254,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-freebsd-arm64') const bindingPackageVersion = require('@oxc-transform/binding-freebsd-arm64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -275,8 +275,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-linux-x64-musl') const bindingPackageVersion = require('@oxc-transform/binding-linux-x64-musl/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -291,8 +291,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-linux-x64-gnu') const bindingPackageVersion = require('@oxc-transform/binding-linux-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -309,8 +309,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-linux-arm64-musl') const bindingPackageVersion = require('@oxc-transform/binding-linux-arm64-musl/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -325,8 +325,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-linux-arm64-gnu') const bindingPackageVersion = require('@oxc-transform/binding-linux-arm64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -343,8 +343,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-linux-arm-musleabihf') const bindingPackageVersion = require('@oxc-transform/binding-linux-arm-musleabihf/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -359,8 +359,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-linux-arm-gnueabihf') const bindingPackageVersion = require('@oxc-transform/binding-linux-arm-gnueabihf/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -377,8 +377,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-linux-loong64-musl') const bindingPackageVersion = require('@oxc-transform/binding-linux-loong64-musl/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -393,8 +393,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-linux-loong64-gnu') const bindingPackageVersion = require('@oxc-transform/binding-linux-loong64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -411,8 +411,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-linux-riscv64-musl') const bindingPackageVersion = require('@oxc-transform/binding-linux-riscv64-musl/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -427,8 +427,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-linux-riscv64-gnu') const bindingPackageVersion = require('@oxc-transform/binding-linux-riscv64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -444,8 +444,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-linux-ppc64-gnu') const bindingPackageVersion = require('@oxc-transform/binding-linux-ppc64-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -460,8 +460,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-linux-s390x-gnu') const bindingPackageVersion = require('@oxc-transform/binding-linux-s390x-gnu/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -480,8 +480,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-openharmony-arm64') const bindingPackageVersion = require('@oxc-transform/binding-openharmony-arm64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -496,8 +496,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-openharmony-x64') const bindingPackageVersion = require('@oxc-transform/binding-openharmony-x64/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -512,8 +512,8 @@ function requireNative() { try { const binding = require('@oxc-transform/binding-openharmony-arm') const bindingPackageVersion = require('@oxc-transform/binding-openharmony-arm/package.json').version - if (bindingPackageVersion !== '0.96.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.96.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.97.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.97.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { diff --git a/napi/transform/package.json b/napi/transform/package.json index 77694db55305b..0029269262d90 100644 --- a/napi/transform/package.json +++ b/napi/transform/package.json @@ -1,6 +1,6 @@ { "name": "oxc-transform", - "version": "0.96.0", + "version": "0.97.0", "type": "module", "main": "index.js", "browser": "browser.js", diff --git a/npm/oxc-types/CHANGELOG.md b/npm/oxc-types/CHANGELOG.md index 494572138c30c..f41c4da4b9048 100644 --- a/npm/oxc-types/CHANGELOG.md +++ b/npm/oxc-types/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0). +## [0.97.0] - 2025-11-11 + +### 🐛 Bug Fixes + +- 40231a6 linter/plugins, napi/parser: Add `parent` field to `FormalParameterRest` and `TSParameterProperty` in TS type defs (#15337) (overlookmotel) + ## [0.96.0] - 2025-10-30 ### 🐛 Bug Fixes diff --git a/npm/oxc-types/package.json b/npm/oxc-types/package.json index af6729c15d7a9..94f66c38f4ae1 100644 --- a/npm/oxc-types/package.json +++ b/npm/oxc-types/package.json @@ -1,6 +1,6 @@ { "name": "@oxc-project/types", - "version": "0.96.0", + "version": "0.97.0", "description": "Types for Oxc AST nodes", "type": "module", "keywords": [ diff --git a/npm/runtime/package.json b/npm/runtime/package.json index 9460753121108..14fc4868cf112 100644 --- a/npm/runtime/package.json +++ b/npm/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@oxc-project/runtime", - "version": "0.96.0", + "version": "0.97.0", "description": "Oxc's modular runtime helpers", "license": "MIT", "repository": { diff --git a/oxc_release.toml b/oxc_release.toml index 83e05616c0b6a..ab959ef14afd3 100644 --- a/oxc_release.toml +++ b/oxc_release.toml @@ -30,6 +30,7 @@ name = "oxfmt" scopes_for_breaking_change = ["format"] versioned_files = [ "apps/oxfmt/Cargo.toml", + "apps/oxfmt/package.json", "crates/oxc_formatter/Cargo.toml", "npm/oxfmt/package.json", ] diff --git a/package.json b/package.json index 8dd05c3e2d7b6..bc68510910ba4 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "@arethetypeswrong/core": "0.18.2", "@napi-rs/cli": "catalog:", "emnapi": "1.6.0", - "oxfmt": "^0.9.0", + "oxfmt": "^0.13.0", "oxlint": "^1.25.0", "oxlint-tsgolint": "0.5.1", "publint": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e7697882dd30..c473c71fd64c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,8 +42,8 @@ importers: specifier: 1.6.0 version: 1.6.0 oxfmt: - specifier: ^0.9.0 - version: 0.9.0 + specifier: ^0.13.0 + version: 0.13.0 oxlint: specifier: ^1.25.0 version: 1.28.0(oxlint-tsgolint@0.5.1) @@ -1477,47 +1477,47 @@ packages: '@oxc-project/types@0.96.0': resolution: {integrity: sha512-r/xkmoXA0xEpU6UGtn18CNVjXH6erU3KCpCDbpLmbVxBFor1U9MqN5Z2uMmCHJuXjJzlnDR+hWY+yPoLo8oHDw==} - '@oxfmt/darwin-arm64@0.9.0': - resolution: {integrity: sha512-1D9z55GMMqceq3VT0rNVQjBGanXvwMoNUmkDyXxcigfu+qSVt+qmcJbZwttJfGx9GSlR+ilnAxHODLdcfSShMQ==} + '@oxfmt/darwin-arm64@0.13.0': + resolution: {integrity: sha512-WJKGJp9t8lMG3Vmsyz77qj4GIp2Z/z5KkS4Mpbn7nfiVZLVNdxf9k85vHnuGtBZcuxIAjJIRDgitePFPD+timA==} cpu: [arm64] os: [darwin] - '@oxfmt/darwin-x64@0.9.0': - resolution: {integrity: sha512-WI/2AsQz058vcpPCcLk3R5gFBlS3kWrzZGGlZq8UljB8Jzn8/OH6+UEZxiDsFJd/ilbBxPpgAgPxna3z8Is4bw==} + '@oxfmt/darwin-x64@0.13.0': + resolution: {integrity: sha512-b9r+uOrnsFIl8DEimw5G69/aXbY5XURzFz0j6Hr8GiAZIrp2GlMLz0B8zhylAXh882GalZGxtcVbLZt5SSe2jw==} cpu: [x64] os: [darwin] - '@oxfmt/linux-arm64-gnu@0.9.0': - resolution: {integrity: sha512-Sj/mbXJZ1vtCvGcmond5zfW69TA1xFLkDl99aje4JazyMsrEK1PdknJClFKfVIBVPNzxfWa/DixAjFzbJoNQ7A==} + '@oxfmt/linux-arm64-gnu@0.13.0': + resolution: {integrity: sha512-v9+rE/d38wBRli0iYvhgGWlgSAgFBJnnK6kefwQ6POu6n6y/tGiQXjWIyvkBqpQhxxavGnk3z3WXP+DAJSC2eA==} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/linux-arm64-musl@0.9.0': - resolution: {integrity: sha512-t1+MPQ6xkw6Xy0aNsviunHqnUWKrUgIskR+x4OLUSs+ZxzS2HPqTrYzQJWJp/2wq4lHpF4NsfNBkmXq751YA6A==} + '@oxfmt/linux-arm64-musl@0.13.0': + resolution: {integrity: sha512-g9A8dOoM/XwToz70aq8XodQZMWwWWPjuTUCI9cxkB1uvpQe4JN6VcHRLMY6Ft1LLh4MIARqq3mCbuXwMVseKiA==} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/linux-x64-gnu@0.9.0': - resolution: {integrity: sha512-/jn3JBBFZb3rw0L+ZazDi757a7/fsI7bxHKUeSLegt4Jlu3EvG1h+zMG5ETsVE0f8ULAuwjsYA/ujGOQxzHRNQ==} + '@oxfmt/linux-x64-gnu@0.13.0': + resolution: {integrity: sha512-CbMEtJ+0mVWnBHOF+Fx8CYApAs3Iywmo6E+buokXEli98167R2eJ/g7dqNiU6R8hBiO0n4KyoT4KaeYhmQp7KA==} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/linux-x64-musl@0.9.0': - resolution: {integrity: sha512-K10t6rNnogmytxbBmdcJnZnSH34kMvTbKI7Kyi0AKv9v7hKUm1kE9hq9VmNyo0KLuu+YYDIMxmQ3Mm5DY2wLyg==} + '@oxfmt/linux-x64-musl@0.13.0': + resolution: {integrity: sha512-KqE6qmwLqxbC2I1t65JNqbu87qL4my3Bi1nsmwXzJBW/xFAVNS4OgZnKQwOpW9dDXw8Ng/IoBO24GgOOECkd/w==} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/win32-arm64@0.9.0': - resolution: {integrity: sha512-IaVN68uowQThhSkbtfKo9Hm4I/y3WHom115tYUXeR6NMi/JDTrBpcrF7v0Tg6b6LbpT6LeXSYVr2cO2IVQwF5A==} + '@oxfmt/win32-arm64@0.13.0': + resolution: {integrity: sha512-jVvlnkgdKHT/l13zIG9511KoVwCKGAvQ4CUtiwiP4Nv4K1F586dV4IcOawcRnKpw9KHTV/Q0E8jB5m3tnz2yeQ==} cpu: [arm64] os: [win32] - '@oxfmt/win32-x64@0.9.0': - resolution: {integrity: sha512-77OiFJ9lpc7ICmHMSN+belxHPDMOu9U7N/LEp40YuC219QWClt6E5Ved6GwNV5bsDCTxTrpH1/3LhxBNKC66Xg==} + '@oxfmt/win32-x64@0.13.0': + resolution: {integrity: sha512-HgC7Efv1Eqv8Ag/3LP2WjSvzIFHsxBLBaYOgMhvq4WhZMM3xG9zsb/1A3/pVqdPhvw+Kh62WaYm1WQM9J4l0lQ==} cpu: [x64] os: [win32] @@ -3341,8 +3341,8 @@ packages: engines: {node: '>= 20'} hasBin: true - oxfmt@0.9.0: - resolution: {integrity: sha512-RVMw8kqZjCDCFxBZyDK4VW8DHxmSHV0pRky7LoLq9JL3ge4kelT0UB8GS0nVTZIteqOJ9rfwPxSZRUVXSX/n0w==} + oxfmt@0.13.0: + resolution: {integrity: sha512-WhWYL1nRxevnezPK3GsGlZ2uPnO+rPlJ1U44TEfET+UwDPhKDVFyqlblduAgu3PFwTMgY/GRbaZwlWugvpfbWQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -5325,28 +5325,28 @@ snapshots: '@oxc-project/types@0.96.0': {} - '@oxfmt/darwin-arm64@0.9.0': + '@oxfmt/darwin-arm64@0.13.0': optional: true - '@oxfmt/darwin-x64@0.9.0': + '@oxfmt/darwin-x64@0.13.0': optional: true - '@oxfmt/linux-arm64-gnu@0.9.0': + '@oxfmt/linux-arm64-gnu@0.13.0': optional: true - '@oxfmt/linux-arm64-musl@0.9.0': + '@oxfmt/linux-arm64-musl@0.13.0': optional: true - '@oxfmt/linux-x64-gnu@0.9.0': + '@oxfmt/linux-x64-gnu@0.13.0': optional: true - '@oxfmt/linux-x64-musl@0.9.0': + '@oxfmt/linux-x64-musl@0.13.0': optional: true - '@oxfmt/win32-arm64@0.9.0': + '@oxfmt/win32-arm64@0.13.0': optional: true - '@oxfmt/win32-x64@0.9.0': + '@oxfmt/win32-x64@0.13.0': optional: true '@oxlint-tsgolint/darwin-arm64@0.5.1': @@ -7125,16 +7125,16 @@ snapshots: - debug - supports-color - oxfmt@0.9.0: + oxfmt@0.13.0: optionalDependencies: - '@oxfmt/darwin-arm64': 0.9.0 - '@oxfmt/darwin-x64': 0.9.0 - '@oxfmt/linux-arm64-gnu': 0.9.0 - '@oxfmt/linux-arm64-musl': 0.9.0 - '@oxfmt/linux-x64-gnu': 0.9.0 - '@oxfmt/linux-x64-musl': 0.9.0 - '@oxfmt/win32-arm64': 0.9.0 - '@oxfmt/win32-x64': 0.9.0 + '@oxfmt/darwin-arm64': 0.13.0 + '@oxfmt/darwin-x64': 0.13.0 + '@oxfmt/linux-arm64-gnu': 0.13.0 + '@oxfmt/linux-arm64-musl': 0.13.0 + '@oxfmt/linux-x64-gnu': 0.13.0 + '@oxfmt/linux-x64-musl': 0.13.0 + '@oxfmt/win32-arm64': 0.13.0 + '@oxfmt/win32-x64': 0.13.0 oxlint-tsgolint@0.5.1: optionalDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ff6d4e5bc9b93..3605bf7f7c8f6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -13,7 +13,7 @@ catalog: '@napi-rs/wasm-runtime': 1.0.7 '@types/node': ^24.0.0 publint: 0.3.15 - tsdown: 0.15.12 + tsdown: 0.16.2 typescript: 5.9.3 vitest: 4.0.6 diff --git a/tasks/ast_tools/Cargo.toml b/tasks/ast_tools/Cargo.toml index a62e3fbb608c6..500fab36a865d 100644 --- a/tasks/ast_tools/Cargo.toml +++ b/tasks/ast_tools/Cargo.toml @@ -14,18 +14,19 @@ test = true doctest = false [dependencies] -# NOT `workspace = true`. -# If AST is updated, the local versions of these crates may not compile until after the codegen has run. -# So the codegen itself (this crate) can't use local versions. +# These dependencies are only used by JS generators. +# So make them optional, and only enable them if `generate-js` feature is enabled. +# See comment on `js-generators` feature below. +# # `features = ["serialize"]` on `oxc_span` and `oxc_syntax` is needed to work around a bug in `oxc_index`. -oxc_allocator = { version = "0.96.0" } -oxc_ast = { version = "0.96.0" } -oxc_ast_visit = { version = "0.96.0" } -oxc_codegen = { version = "0.96.0" } -oxc_minifier = { version = "0.96.0" } -oxc_parser = { version = "0.96.0" } -oxc_span = { version = "0.96.0", features = ["serialize"] } -oxc_syntax = { version = "0.96.0", features = ["serialize"] } +oxc_allocator = { workspace = true, optional = true } +oxc_ast = { workspace = true, optional = true } +oxc_ast_visit = { workspace = true, optional = true } +oxc_codegen = { workspace = true, optional = true } +oxc_minifier = { workspace = true, optional = true } +oxc_parser = { workspace = true, optional = true } +oxc_span = { workspace = true, features = ["serialize"], optional = true } +oxc_syntax = { workspace = true, features = ["serialize"], optional = true } oxc_data_structures = { workspace = true, features = ["slice_iter"] } @@ -47,3 +48,21 @@ rustc-hash = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } syn = { workspace = true, features = ["clone-impls", "derive", "extra-traits", "full", "parsing", "printing", "proc-macro"] } +toml = { workspace = true } + +[features] +default = ["generate-js"] +# Feature to enable JS generators that require `oxc_*` crates. +# Enabled by default, but can be disabled to break the circular dependency +# when AST changes prevent local crates from compiling. +# See: https://github.com/oxc-project/oxc/issues/15564 +generate-js = [ + "dep:oxc_allocator", + "dep:oxc_ast", + "dep:oxc_ast_visit", + "dep:oxc_codegen", + "dep:oxc_minifier", + "dep:oxc_parser", + "dep:oxc_span", + "dep:oxc_syntax", +] diff --git a/tasks/ast_tools/src/generators/ast_kind.rs b/tasks/ast_tools/src/generators/ast_kind.rs index d0e05dd6bab66..b0991e586ff36 100644 --- a/tasks/ast_tools/src/generators/ast_kind.rs +++ b/tasks/ast_tools/src/generators/ast_kind.rs @@ -10,7 +10,6 @@ //! Variants of `AstKind` and `AstType` are created for: //! //! * All structs which are visited, and are not listed in `STRUCTS_BLACK_LIST` below. -//! * Enums listed in `ENUMS_WHITE_LIST` below. use quote::{format_ident, quote}; @@ -41,15 +40,6 @@ use super::define_generator; /// See also: const STRUCTS_BLACK_LIST: &[&str] = &["BindingPattern", "Span"]; -/// Enums to create an `AstKind` for. -/// -/// Apart from this list, enums don't have `AstKind`s. -/// -/// Ideally we don't want any enums to have `AstKind`s. -/// We are working towards removing all the items from this list. -/// -const ENUMS_WHITE_LIST: &[&str] = &["Argument"]; - /// Generator for `AstKind`, `AstType`, and related code. pub struct AstKindGenerator; @@ -81,20 +71,6 @@ impl Generator for AstKindGenerator { ); struct_def.kind.has_kind = false; } - - // Set `has_kind = true` for enums in white list - for &type_name in ENUMS_WHITE_LIST { - let type_def = schema.type_by_name_mut(type_name); - let TypeDef::Enum(enum_def) = type_def else { - panic!("Type which isn't an enum `{}` in `ENUMS_WHITE_LIST`", type_def.name()); - }; - assert!( - enum_def.visit.has_visitor(), - "Enum `{}` is not visited, cannot have an `AstKind`", - enum_def.name() - ); - enum_def.kind.has_kind = true; - } } /// Generate `AstKind` etc definitions. diff --git a/tasks/ast_tools/src/generators/formatter/format.rs b/tasks/ast_tools/src/generators/formatter/format.rs index 7cfe6c1d5653e..407ab7313165b 100644 --- a/tasks/ast_tools/src/generators/formatter/format.rs +++ b/tasks/ast_tools/src/generators/formatter/format.rs @@ -21,9 +21,11 @@ const AST_NODE_WITHOUT_PRINTING_COMMENTS_LIST: &[&str] = &[ "ClassBody", "CatchParameter", "CatchClause", + "Decorator", // Manually prints it because class's decorators can be appears before `export class Cls {}`. "ExportNamedDeclaration", "ExportDefaultDeclaration", + "TSClassImplements", // "JSXElement", "JSXFragment", @@ -134,10 +136,19 @@ fn generate_struct_implementation( let needs_parentheses = parenthesis_type_ids.contains(&struct_def.id); let needs_parentheses_before = if needs_parentheses { - quote! { - let needs_parentheses = self.needs_parentheses(f); - if needs_parentheses { - "(".fmt(f)?; + if matches!(struct_name, "JSXElement" | "JSXFragment") { + quote! { + let needs_parentheses = !is_suppressed && self.needs_parentheses(f); + if needs_parentheses { + "(".fmt(f)?; + } + } + } else { + quote! { + let needs_parentheses = self.needs_parentheses(f); + if needs_parentheses { + "(".fmt(f)?; + } } } } else { @@ -166,13 +177,12 @@ fn generate_struct_implementation( }; // `Program` can't be suppressed. - // `JSXElement` and `JSXFragment` implement suppression formatting in their formatting logic - let suppressed_check = (!matches!(struct_name, "Program" | "JSXElement" | "JSXFragment")) - .then(|| { - quote! { - let is_suppressed = f.comments().is_suppressed(self.span().start); - } - }); + // `JSXElement` and `JSXFragment` need special suppression handling before parentheses + let suppressed_check = (!matches!(struct_name, "Program")).then(|| { + quote! { + let is_suppressed = f.comments().is_suppressed(self.span().start); + } + }); let write_implementation = if suppressed_check.is_none() { write_call diff --git a/tasks/ast_tools/src/generators/mod.rs b/tasks/ast_tools/src/generators/mod.rs index 9165d6e09a2c1..f87e8e769b053 100644 --- a/tasks/ast_tools/src/generators/mod.rs +++ b/tasks/ast_tools/src/generators/mod.rs @@ -7,12 +7,16 @@ use crate::{ mod assert_layouts; mod ast_builder; mod ast_kind; +#[cfg(feature = "generate-js")] mod estree_visit; mod formatter; mod get_id; +#[cfg(feature = "generate-js")] mod raw_transfer; +#[cfg(feature = "generate-js")] mod raw_transfer_lazy; mod scopes_collector; +#[cfg(feature = "generate-js")] mod typescript; mod utf8_to_utf16; mod visit; @@ -20,12 +24,16 @@ mod visit; pub use assert_layouts::AssertLayouts; pub use ast_builder::AstBuilderGenerator; pub use ast_kind::AstKindGenerator; +#[cfg(feature = "generate-js")] pub use estree_visit::ESTreeVisitGenerator; pub use formatter::{FormatterAstNodesGenerator, FormatterFormatGenerator}; pub use get_id::GetIdGenerator; +#[cfg(feature = "generate-js")] pub use raw_transfer::RawTransferGenerator; +#[cfg(feature = "generate-js")] pub use raw_transfer_lazy::RawTransferLazyGenerator; pub use scopes_collector::ScopesCollectorGenerator; +#[cfg(feature = "generate-js")] pub use typescript::TypescriptGenerator; pub use utf8_to_utf16::Utf8ToUtf16ConverterGenerator; pub use visit::VisitGenerator; diff --git a/tasks/ast_tools/src/generators/visit.rs b/tasks/ast_tools/src/generators/visit.rs index 88d9588271c54..445c0eb034318 100644 --- a/tasks/ast_tools/src/generators/visit.rs +++ b/tasks/ast_tools/src/generators/visit.rs @@ -587,6 +587,8 @@ impl VisitBuilder<'_> { let match_ident = format_ident!("match_{inherits_snake_name}"); let to_fn_ident = format_ident!("to_{inherits_snake_name}"); + // For Argument, we call visitor.visit_expression to maintain enter/leave node bookkeeping + // This provides recursion protection for deeply nested argument lists let match_arm = quote! { #match_ident!(#enum_ident) => visitor.#inner_visit_fn_ident(it.#to_fn_ident()), }; @@ -673,15 +675,67 @@ impl VisitBuilder<'_> { _ => unreachable!(), }; + // Special optimization for Vec to reduce stack usage: + // Since Argument inherits from Expression and most arguments are expressions, + // we can directly dispatch to the expression visitor for non-spread elements, + // avoiding the intermediate walk_argument call and its stack frame. + let inner_type_name = match inner_type { + TypeDef::Enum(enum_def) => &enum_def.name, + _ => "", + }; + let is_arguments_vec = inner_type_name == "Argument"; + let gen_walk = |visit_trait_name, reference| { let visit_trait_ident = create_safe_ident(visit_trait_name); + let is_mut = visit_trait_name == "VisitMut"; + + let loop_body = if is_arguments_vec { + // Optimize for Vec by directly dispatching to expression visitor + if is_mut { + quote! { + for el in it { + // For Argument types, directly dispatch to avoid intermediate stack frame + match el { + oxc_ast::ast::Argument::SpreadElement(spread) => { + visitor.visit_spread_element(spread); + } + _ => { + // All other Argument variants are expressions + visitor.visit_expression(el.to_expression_mut()); + } + } + } + } + } else { + quote! { + for el in it { + // For Argument types, directly dispatch to avoid intermediate stack frame + match el { + oxc_ast::ast::Argument::SpreadElement(spread) => { + visitor.visit_spread_element(spread); + } + _ => { + // All other Argument variants are expressions + visitor.visit_expression(el.to_expression()); + } + } + } + } + } + } else { + // Normal case for other Vec types + quote! { + for el in it { + visitor.#inner_visit_fn_ident(el); + } + } + }; + quote! { ///@@line_break #[inline] pub fn #walk_fn_ident<'a, V: #visit_trait_ident<'a>>(visitor: &mut V, it: #reference #vec_ty) { - for el in it { - visitor.#inner_visit_fn_ident(el); - } + #loop_body } } }; diff --git a/tasks/ast_tools/src/main.rs b/tasks/ast_tools/src/main.rs index 7fe39c1813963..c678dde94868c 100644 --- a/tasks/ast_tools/src/main.rs +++ b/tasks/ast_tools/src/main.rs @@ -23,7 +23,7 @@ //! Code generation can be triggered by running this crate: //! //! ```sh -//! cargo run -p oxc_ast_tools +//! just ast //! ``` //! //! The generated code is checked into git. @@ -181,6 +181,9 @@ //! [`AttrLocation`]: parse::attr::AttrLocation //! [`AttrPart`]: parse::attr::AttrPart +// Prevent lint errors when JS generators are disabled +#![cfg_attr(not(feature = "generate-js"), allow(dead_code, unused_imports, unused_macros))] + use std::fs; use bpaf::{Bpaf, Parser}; @@ -283,9 +286,13 @@ const GENERATORS: &[&(dyn Generator + Sync)] = &[ &generators::VisitGenerator, &generators::ScopesCollectorGenerator, &generators::Utf8ToUtf16ConverterGenerator, + #[cfg(feature = "generate-js")] &generators::ESTreeVisitGenerator, + #[cfg(feature = "generate-js")] &generators::RawTransferGenerator, + #[cfg(feature = "generate-js")] &generators::RawTransferLazyGenerator, + #[cfg(feature = "generate-js")] &generators::TypescriptGenerator, &generators::FormatterFormatGenerator, &generators::FormatterAstNodesGenerator, @@ -347,13 +354,20 @@ fn main() { logln!("All Derives and Generators... Done!"); - // Edit `lib.rs` in `oxc_ast_macros` crate + // Generate `derived_traits.rs` in `oxc_ast_macros` crate outputs.push(generate_proc_macro()); + + // Edit `lib.rs` in `oxc_ast_macros` crate. + // Skip this step if JS generators are disabled, because those generators may define attributes. + #[cfg(feature = "generate-js")] outputs.push(generate_updated_proc_macro(&codegen)); - // Add CI filter file to outputs outputs.sort_unstable_by(|o1, o2| o1.path.cmp(&o2.path)); - outputs.push(generate_ci_filter(&outputs)); + + // Add CI filter file to outputs. + // Skip this step if JS generators are disabled, because not all files are generated. + #[cfg(feature = "generate-js")] + outputs.push(generate_ci_filter(&outputs, &codegen)); // Write outputs to disk if !options.dry_run { @@ -369,12 +383,12 @@ fn main() { /// unless relevant files have changed. /// /// List includes source files, generated files, and all files in `oxc_ast_tools` itself. -fn generate_ci_filter(outputs: &[RawOutput]) -> RawOutput { +fn generate_ci_filter(outputs: &[RawOutput], codegen: &Codegen) -> RawOutput { log!("Generate CI filter... "); let paths = SOURCE_PATHS.iter().copied().chain(outputs.iter().map(|output| output.path.as_str())); - let output = Output::yaml_watch_list(AST_CHANGES_WATCH_LIST_PATH, paths); + let output = Output::yaml_watch_list(AST_CHANGES_WATCH_LIST_PATH, paths, codegen); log_success!(); diff --git a/tasks/ast_tools/src/output/mod.rs b/tasks/ast_tools/src/output/mod.rs index cf4bea2fa289d..a0475f1955ca2 100644 --- a/tasks/ast_tools/src/output/mod.rs +++ b/tasks/ast_tools/src/output/mod.rs @@ -5,9 +5,11 @@ use proc_macro2::TokenStream; use crate::{log, log_result}; +#[cfg(feature = "generate-js")] pub mod javascript; mod rust; mod yaml; +#[cfg(feature = "generate-js")] use javascript::print_javascript; use rust::{print_rust, rust_fmt}; use yaml::print_yaml; @@ -31,11 +33,27 @@ fn add_header(code: &str, generator_path: &str, comment_start: &str) -> String { /// Can be Rust, Javascript, or other formats. #[expect(dead_code)] pub enum Output { - Rust { path: String, tokens: TokenStream }, - RustString { path: String, code: String }, - Javascript { path: String, code: String }, - Yaml { path: String, code: String }, - Raw { path: String, code: String }, + Rust { + path: String, + tokens: TokenStream, + }, + RustString { + path: String, + code: String, + }, + #[cfg(feature = "generate-js")] + Javascript { + path: String, + code: String, + }, + Yaml { + path: String, + code: String, + }, + Raw { + path: String, + code: String, + }, } impl Output { @@ -54,6 +72,7 @@ impl Output { let code = rust_fmt(&code); (path, code) } + #[cfg(feature = "generate-js")] Self::Javascript { path, code } => { let code = print_javascript(&code, &generator_path); (path, code) diff --git a/tasks/ast_tools/src/output/yaml.rs b/tasks/ast_tools/src/output/yaml.rs index f361bc52dd0ac..446719377a1f0 100644 --- a/tasks/ast_tools/src/output/yaml.rs +++ b/tasks/ast_tools/src/output/yaml.rs @@ -1,4 +1,8 @@ -use std::fmt::Write; +use std::{fmt::Write, fs}; + +use rustc_hash::FxHashSet; + +use crate::Codegen; use super::{Output, add_header}; @@ -10,17 +14,68 @@ pub fn print_yaml(code: &str, generator_path: &str) -> String { impl Output { /// Generate a watch list YAML file. /// - /// The path of the watch list itself and `tasks/ast_tools/src/**` are added to the list. + /// The following are added to the list: + /// * The watch list file itself + /// * `ast_tools` crate + /// * `oxc_*` dependencies of `ast_tools` + /// * CI workflow file pub fn yaml_watch_list<'s>( watch_list_path: &'s str, paths: impl IntoIterator, + codegen: &Codegen, ) -> Self { - let mut paths = paths + let mut paths = paths.into_iter().collect::>(); + + // Get `oxc_*` dependencies of `ast_tools`. + // `ast_tools` uses these crates, so generated code may change if these crates are changed. + let cargo_toml = parse_toml("tasks/ast_tools/Cargo.toml", codegen); + let dependency_crates = cargo_toml + .get("dependencies") + .and_then(|v| v.as_table()) + .unwrap() + .keys() + .map(String::as_str) + .filter(|krate| { + // Exclude crates which are not in this monorepo e.g. `oxc_index` + krate.starts_with("oxc_") && codegen.root_path().join("crates").join(krate).is_dir() + }) + .collect::>(); + + // Remove paths from `paths` which are in `src` dir of a dependency crate + // (as `crate/{krate}/src/**` pattern will cover them anyway) + paths.retain(|path| { + if let Some(path) = path.strip_prefix("crates/") + && let Some((krate, path)) = path.split_once('/') + && path.starts_with("src/") + { + return !dependency_crates.contains(krate); + } + true + }); + + // Get paths for dependency crates + let dependency_crate_paths = dependency_crates .into_iter() - .chain([watch_list_path, "tasks/ast_tools/src/**"]) + .map(|krate| format!("crates/{krate}/src/**")) .collect::>(); + + // Additional paths + let additional_paths = [ + // This watch list file + watch_list_path, + // All code in `ast_tools` + "tasks/ast_tools/src/**", + // Workflow which runs `ast_tools` + ".github/workflows/ci.yml", + ]; + + // Add additional paths and dependency crate paths to `paths`, and sort + paths.extend( + additional_paths.into_iter().chain(dependency_crate_paths.iter().map(String::as_str)), + ); paths.sort_unstable(); + // Generate YAML let mut code = "src:\n".to_string(); for path in paths { writeln!(code, " - '{path}'").unwrap(); @@ -29,3 +84,9 @@ impl Output { Self::Yaml { path: watch_list_path.to_string(), code } } } + +/// Parse TOML file. +fn parse_toml(path: &str, codegen: &Codegen) -> toml::Table { + let toml_content = fs::read_to_string(codegen.root_path().join(path)).unwrap(); + toml::from_str(&toml_content).unwrap() +} diff --git a/tasks/benchmark/benches/formatter.rs b/tasks/benchmark/benches/formatter.rs index ca5d09fd778cb..fdd6c2dff0bac 100644 --- a/tasks/benchmark/benches/formatter.rs +++ b/tasks/benchmark/benches/formatter.rs @@ -1,6 +1,6 @@ use oxc_allocator::Allocator; use oxc_benchmark::{BenchmarkId, Criterion, criterion_group, criterion_main}; -use oxc_formatter::{FormatOptions, Formatter, get_parse_options}; +use oxc_formatter::{FormatOptions, Formatter, SortImports, get_parse_options}; use oxc_parser::Parser; use oxc_tasks_common::TestFiles; @@ -19,7 +19,10 @@ fn bench_formatter(criterion: &mut Criterion) { .with_options(get_parse_options()) .parse() .program; - let format_options = FormatOptions::default(); + let format_options = FormatOptions { + experimental_sort_imports: Some(SortImports::default()), + ..Default::default() + }; runner.run(|| { Formatter::new(&allocator, format_options).build(&program); }); diff --git a/tasks/coverage/snapshots/codegen_babel.snap b/tasks/coverage/snapshots/codegen_babel.snap index 144e46a24bbe2..1a81a0d3331f5 100644 --- a/tasks/coverage/snapshots/codegen_babel.snap +++ b/tasks/coverage/snapshots/codegen_babel.snap @@ -1,4 +1,4 @@ -commit: 4cc3d888 +commit: 777ded79 codegen_babel Summary: AST Parsed : 2440/2440 (100.00%) diff --git a/tasks/coverage/snapshots/codegen_test262.snap b/tasks/coverage/snapshots/codegen_test262.snap index dbfc43bc40e38..9a13a4585e586 100644 --- a/tasks/coverage/snapshots/codegen_test262.snap +++ b/tasks/coverage/snapshots/codegen_test262.snap @@ -1,5 +1,5 @@ -commit: d2940bdb +commit: fd594a07 codegen_test262 Summary: -AST Parsed : 44651/44651 (100.00%) -Positive Passed: 44651/44651 (100.00%) +AST Parsed : 44758/44758 (100.00%) +Positive Passed: 44758/44758 (100.00%) diff --git a/tasks/coverage/snapshots/codegen_typescript.snap b/tasks/coverage/snapshots/codegen_typescript.snap index a5df5a4ff7211..f68430db43dd6 100644 --- a/tasks/coverage/snapshots/codegen_typescript.snap +++ b/tasks/coverage/snapshots/codegen_typescript.snap @@ -1,5 +1,5 @@ -commit: 8ea03f88 +commit: 48244d89 codegen_typescript Summary: -AST Parsed : 9812/9812 (100.00%) -Positive Passed: 9812/9812 (100.00%) +AST Parsed : 9817/9817 (100.00%) +Positive Passed: 9817/9817 (100.00%) diff --git a/tasks/coverage/snapshots/estree_acorn_jsx.snap b/tasks/coverage/snapshots/estree_acorn_jsx.snap index 6faeabe556701..f517d02e8760e 100644 --- a/tasks/coverage/snapshots/estree_acorn_jsx.snap +++ b/tasks/coverage/snapshots/estree_acorn_jsx.snap @@ -1,4 +1,4 @@ -commit: 994d763f +commit: 9cce4c91 estree_acorn_jsx Summary: AST Parsed : 39/39 (100.00%) diff --git a/tasks/coverage/snapshots/estree_test262.snap b/tasks/coverage/snapshots/estree_test262.snap index 1e78edb4aa71a..adba65e802176 100644 --- a/tasks/coverage/snapshots/estree_test262.snap +++ b/tasks/coverage/snapshots/estree_test262.snap @@ -1,8 +1,8 @@ -commit: d2940bdb +commit: fd594a07 estree_test262 Summary: -AST Parsed : 44425/44425 (100.00%) -Positive Passed: 44416/44425 (99.98%) +AST Parsed : 44531/44531 (100.00%) +Positive Passed: 44522/44531 (99.98%) Mismatch: tasks/coverage/test262/test/language/expressions/assignment/fn-name-lhs-cover.js Mismatch: tasks/coverage/test262/test/language/expressions/assignment/target-cover-id.js diff --git a/tasks/coverage/snapshots/estree_typescript.snap b/tasks/coverage/snapshots/estree_typescript.snap index 04cacb22fda73..4240dd6573386 100644 --- a/tasks/coverage/snapshots/estree_typescript.snap +++ b/tasks/coverage/snapshots/estree_typescript.snap @@ -1,8 +1,8 @@ -commit: 8ea03f88 +commit: 48244d89 estree_typescript Summary: -AST Parsed : 9743/9743 (100.00%) -Positive Passed: 9735/9743 (99.92%) +AST Parsed : 9748/9748 (100.00%) +Positive Passed: 9740/9748 (99.92%) Expect to Parse: tasks/coverage/typescript/tests/cases/compiler/modulePreserveTopLevelAwait1.ts `await` is only allowed within async functions and at the top levels of modules diff --git a/tasks/coverage/snapshots/formatter_babel.snap b/tasks/coverage/snapshots/formatter_babel.snap index 05a50093a9674..3a1c4ea3ee098 100644 --- a/tasks/coverage/snapshots/formatter_babel.snap +++ b/tasks/coverage/snapshots/formatter_babel.snap @@ -1,4 +1,4 @@ -commit: 4cc3d888 +commit: 777ded79 formatter_babel Summary: AST Parsed : 2440/2440 (100.00%) diff --git a/tasks/coverage/snapshots/formatter_test262.snap b/tasks/coverage/snapshots/formatter_test262.snap index c3951710ab692..6863291370f4c 100644 --- a/tasks/coverage/snapshots/formatter_test262.snap +++ b/tasks/coverage/snapshots/formatter_test262.snap @@ -1,8 +1,8 @@ -commit: d2940bdb +commit: fd594a07 formatter_test262 Summary: -AST Parsed : 44651/44651 (100.00%) -Positive Passed: 44641/44651 (99.98%) +AST Parsed : 44758/44758 (100.00%) +Positive Passed: 44747/44758 (99.98%) Expect to Parse: tasks/coverage/test262/test/annexB/language/expressions/assignmenttargettype/callexpression-as-for-in-lhs.js Cannot assign to this expression Expect to Parse: tasks/coverage/test262/test/annexB/language/expressions/assignmenttargettype/callexpression-as-for-of-lhs.js @@ -23,3 +23,5 @@ Mismatch: tasks/coverage/test262/test/built-ins/Function/prototype/toString/line Mismatch: tasks/coverage/test262/test/built-ins/Function/prototype/toString/line-terminator-normalisation-LF.js +Expect to Parse: tasks/coverage/test262/test/language/statements/using/syntax/using-for-statement.js +Unexpected token diff --git a/tasks/coverage/snapshots/formatter_typescript.snap b/tasks/coverage/snapshots/formatter_typescript.snap index 0d55998fcfee0..30d5cf9ce2b69 100644 --- a/tasks/coverage/snapshots/formatter_typescript.snap +++ b/tasks/coverage/snapshots/formatter_typescript.snap @@ -1,8 +1,8 @@ -commit: 8ea03f88 +commit: 48244d89 formatter_typescript Summary: -AST Parsed : 9812/9812 (100.00%) -Positive Passed: 9800/9812 (99.88%) +AST Parsed : 9817/9817 (100.00%) +Positive Passed: 9805/9817 (99.88%) Mismatch: tasks/coverage/typescript/tests/cases/compiler/amdLikeInputDeclarationEmit.ts Expect to Parse: tasks/coverage/typescript/tests/cases/compiler/arrayFromAsync.ts diff --git a/tasks/coverage/snapshots/minifier_babel.snap b/tasks/coverage/snapshots/minifier_babel.snap index 09cb3968531d3..38f87bd005258 100644 --- a/tasks/coverage/snapshots/minifier_babel.snap +++ b/tasks/coverage/snapshots/minifier_babel.snap @@ -1,4 +1,4 @@ -commit: 4cc3d888 +commit: 777ded79 minifier_babel Summary: AST Parsed : 1823/1823 (100.00%) diff --git a/tasks/coverage/snapshots/minifier_node_compat.snap b/tasks/coverage/snapshots/minifier_node_compat.snap index ba07f6386ccf6..5c435596cafdb 100644 --- a/tasks/coverage/snapshots/minifier_node_compat.snap +++ b/tasks/coverage/snapshots/minifier_node_compat.snap @@ -1,4 +1,4 @@ -commit: 17ac85ca +commit: 68225229 minifier_node_compat Summary: AST Parsed : 938/938 (100.00%) diff --git a/tasks/coverage/snapshots/minifier_test262.snap b/tasks/coverage/snapshots/minifier_test262.snap index 08acafc5047c7..9b859e6b6e9b2 100644 --- a/tasks/coverage/snapshots/minifier_test262.snap +++ b/tasks/coverage/snapshots/minifier_test262.snap @@ -1,8 +1,8 @@ -commit: d2940bdb +commit: fd594a07 minifier_test262 Summary: -AST Parsed : 42235/42235 (100.00%) -Positive Passed: 42232/42235 (99.99%) +AST Parsed : 42340/42340 (100.00%) +Positive Passed: 42337/42340 (99.99%) Compress: tasks/coverage/test262/test/intl402/Temporal/PlainDate/prototype/toLocaleString/lone-options-accepted.js Compress: tasks/coverage/test262/test/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/lone-options-accepted.js diff --git a/tasks/coverage/snapshots/parser_babel.snap b/tasks/coverage/snapshots/parser_babel.snap index 3bd6bc7c1d306..538ea47e5ae25 100644 --- a/tasks/coverage/snapshots/parser_babel.snap +++ b/tasks/coverage/snapshots/parser_babel.snap @@ -1,4 +1,4 @@ -commit: 4cc3d888 +commit: 777ded79 parser_babel Summary: AST Parsed : 2422/2440 (99.26%) diff --git a/tasks/coverage/snapshots/parser_test262.snap b/tasks/coverage/snapshots/parser_test262.snap index ce83a4666bca4..cff0044cf9689 100644 --- a/tasks/coverage/snapshots/parser_test262.snap +++ b/tasks/coverage/snapshots/parser_test262.snap @@ -1,9 +1,13 @@ -commit: d2940bdb +commit: fd594a07 parser_test262 Summary: -AST Parsed : 44644/44651 (99.98%) -Positive Passed: 44644/44651 (99.98%) -Negative Passed: 4535/4535 (100.00%) +AST Parsed : 44750/44758 (99.98%) +Positive Passed: 44750/44758 (99.98%) +Negative Passed: 4579/4581 (99.96%) +Expect Syntax Error: tasks/coverage/test262/test/language/statements/await-using/syntax/await-using-not-allowed-at-top-level-of-script.js + +Expect Syntax Error: tasks/coverage/test262/test/language/statements/using/syntax/using-not-allowed-at-top-level-of-script.js + Expect to Parse: tasks/coverage/test262/test/annexB/language/expressions/assignmenttargettype/callexpression-as-for-in-lhs.js × Cannot assign to this expression @@ -74,6 +78,15 @@ Expect to Parse: tasks/coverage/test262/test/annexB/language/expressions/assignm 21 │ }); ╰──── +Expect to Parse: tasks/coverage/test262/test/language/statements/using/syntax/using-for-statement.js + + × Unexpected token + ╭─[test262/test/language/statements/using/syntax/using-for-statement.js:14:15] + 13 │ // handled similar to `for (let of = null;;)`: + 14 │ for (using of = null;;) break; + · ─ + ╰──── + × '0'-prefixed octal literals and octal escape sequences are deprecated ╭─[test262/test/annexB/language/expressions/template-literal/legacy-octal-escape-sequence-strict.js:19:4] @@ -26315,6 +26328,255 @@ Expect to Parse: tasks/coverage/test262/test/annexB/language/expressions/assignm 30 │ throw new Test262Error(); ╰──── + × Using declarations may not have binding patterns. + ╭─[test262/test/language/statements/await-using/syntax/await-using-invalid-arraybindingpattern-after-bindingidentifier.js:18:25] + 17 │ async function f() { + 18 │ await using x = null, [] = null; + · ── + 19 │ } + ╰──── + + × Expected function name + ╭─[test262/test/language/statements/await-using/syntax/await-using-invalid-arraybindingpattern.js:16:15] + 15 │ $DONOTEVALUATE(); + 16 │ async function() { + · ─ + 17 │ await using [] = null; + ╰──── + help: Function name is required in function declaration or named export + + × Unexpected token + ╭─[test262/test/language/statements/await-using/syntax/await-using-invalid-arraybindingpattern.js:17:16] + 16 │ async function() { + 17 │ await using [] = null; + · ─ + 18 │ } + ╰──── + + × The left-hand side of a for...in statement cannot be an await using declaration. + ╭─[test262/test/language/statements/await-using/syntax/await-using-invalid-for-in.js:16:8] + 15 │ async function f() { + 16 │ for (await using x in [1, 2, 3]) { } + · ───────────── + 17 │ } + ╰──── + help: Did you mean to use a for...of statement? + + × Using declarations may not have binding patterns. + ╭─[test262/test/language/statements/await-using/syntax/await-using-invalid-objectbindingpattern-after-bindingidentifier.js:17:25] + 16 │ async function f() { + 17 │ await using x = null, {} = null; + · ── + 18 │ } + ╰──── + + × Expected a semicolon or an implicit semicolon after a statement, but found none + ╭─[test262/test/language/statements/await-using/syntax/await-using-invalid-objectbindingpattern.js:17:14] + 16 │ async function f() { + 17 │ await using {} = null; + · ▲ + 18 │ } + ╰──── + help: Try inserting a semicolon here + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/await-using/syntax/block-scope-syntax-await-using-declarations-mixed-with-without-initializer.js:30:25] + 29 │ async function f() { + 30 │ await using x = null, y; + · ─ + 31 │ } + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/await-using/syntax/block-scope-syntax-await-using-declarations-mixed-without-with-initializer.js:30:15] + 29 │ async function f() { + 30 │ await using x, y = null; + · ─ + 31 │ } + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/await-using/syntax/block-scope-syntax-await-using-declarations-without-initializer.js:30:15] + 29 │ async function f() { + 30 │ await using x; + · ─ + 31 │ } + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Using declaration cannot appear in the bare case statement. + ╭─[test262/test/language/statements/await-using/syntax/with-initializer-case-expression-statement-list.js:24:32] + 23 │ async function f() { + 24 │ switch (true) { case true: await using x = null; } + · ───────────────────── + 25 │ } + ╰──── + help: Wrap this declaration in a block statement + + × Using declaration cannot appear in the bare case statement. + ╭─[test262/test/language/statements/await-using/syntax/with-initializer-default-statement-list.js:24:30] + 23 │ async function f() { + 24 │ switch (true) { default: await using x = null; } + · ───────────────────── + 25 │ } + ╰──── + help: Wrap this declaration in a block statement + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/await-using/syntax/with-initializer-do-statement-while-expression.js:18:6] + 17 │ async function f() { + 18 │ do await using x = 1; while (false) + · ────────────────── + 19 │ } + ╰──── + help: Wrap this declaration in a block statement + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/await-using/syntax/with-initializer-for-statement.js:18:17] + 17 │ async function f() { + 18 │ for (;false;) await using x = null; + · ───────────────────── + 19 │ } + ╰──── + help: Wrap this declaration in a block statement + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/await-using/syntax/with-initializer-if-expression-statement-else-statement.js:18:21] + 17 │ async function f() { + 18 │ if (true) {} else await using x = null; + · ───────────────────── + 19 │ } + ╰──── + help: Wrap this declaration in a block statement + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/await-using/syntax/with-initializer-if-expression-statement.js:18:13] + 17 │ async function f() { + 18 │ if (true) await using x = null; + · ───────────────────── + 19 │ } + ╰──── + help: Wrap this declaration in a block statement + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/await-using/syntax/with-initializer-label-statement.js:18:10] + 17 │ async function f() { + 18 │ label: await using x = null; + · ───────────────────── + 19 │ } + ╰──── + help: Wrap this declaration in a block statement + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/await-using/syntax/with-initializer-while-expression-statement.js:18:17] + 17 │ async function f() { + 18 │ while (false) await using x = null; + · ───────────────────── + 19 │ } + ╰──── + help: Wrap this declaration in a block statement + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/await-using/syntax/without-initializer-do-statement-while-expression.js:18:18] + 17 │ async function f() { + 18 │ do await using x; while (false) + · ─ + 19 │ } + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/await-using/syntax/without-initializer-do-statement-while-expression.js:18:6] + 17 │ async function f() { + 18 │ do await using x; while (false) + · ────────────── + 19 │ } + ╰──── + help: Wrap this declaration in a block statement + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/await-using/syntax/without-initializer-for-statement.js:18:17] + 17 │ async function f() { + 18 │ for (;false;) await using x; + · ────────────── + 19 │ } + ╰──── + help: Wrap this declaration in a block statement + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/await-using/syntax/without-initializer-if-expression-statement-else-statement.js:18:33] + 17 │ async function f() { + 18 │ if (true) {} else await using x; + · ─ + 19 │ } + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/await-using/syntax/without-initializer-if-expression-statement-else-statement.js:18:21] + 17 │ async function f() { + 18 │ if (true) {} else await using x; + · ────────────── + 19 │ } + ╰──── + help: Wrap this declaration in a block statement + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/await-using/syntax/without-initializer-if-expression-statement.js:18:25] + 17 │ async function f() { + 18 │ if (true) await using x; + · ─ + 19 │ } + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/await-using/syntax/without-initializer-if-expression-statement.js:18:13] + 17 │ async function f() { + 18 │ if (true) await using x; + · ────────────── + 19 │ } + ╰──── + help: Wrap this declaration in a block statement + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/await-using/syntax/without-initializer-label-statement.js:18:22] + 17 │ async function f() { + 18 │ label: await using x; + · ─ + 19 │ } + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/await-using/syntax/without-initializer-label-statement.js:18:10] + 17 │ async function f() { + 18 │ label: await using x; + · ────────────── + 19 │ } + ╰──── + help: Wrap this declaration in a block statement + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/await-using/syntax/without-initializer-while-expression-statement.js:18:29] + 17 │ async function f() { + 18 │ while (false) await using x; + · ─ + 19 │ } + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/await-using/syntax/without-initializer-while-expression-statement.js:18:17] + 17 │ async function f() { + 18 │ while (false) await using x; + · ────────────── + 19 │ } + ╰──── + help: Wrap this declaration in a block statement + × Missing catch or finally clause ╭─[test262/test/language/statements/block/12.1-1.js:15:6] 14 │ @@ -38674,6 +38936,225 @@ Expect to Parse: tasks/coverage/test262/test/annexB/language/expressions/assignm 26 │ } ╰──── + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/using/syntax/block-scope-syntax-using-declarations-mixed-with-without-initializer.js:30:19] + 29 │ { + 30 │ using x = null, y; + · ─ + 31 │ } + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/using/syntax/block-scope-syntax-using-declarations-mixed-without-with-initializer.js:30:9] + 29 │ { + 30 │ using x, y = null; + · ─ + 31 │ } + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/using/syntax/block-scope-syntax-using-declarations-without-initializer.js:30:9] + 29 │ { + 30 │ using x; + · ─ + 31 │ } + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Using declarations may not have binding patterns. + ╭─[test262/test/language/statements/using/syntax/using-invalid-arraybindingpattern-after-bindingidentifier.js:18:19] + 17 │ { + 18 │ using x = null, [] = null; + · ── + 19 │ } + ╰──── + + × Unexpected token + ╭─[test262/test/language/statements/using/syntax/using-invalid-arraybindingpattern.js:18:10] + 17 │ { + 18 │ using [] = null; + · ─ + 19 │ } + ╰──── + + × The left-hand side of a for...in statement cannot be an using declaration. + ╭─[test262/test/language/statements/using/syntax/using-invalid-for-in.js:15:6] + 14 │ $DONOTEVALUATE(); + 15 │ for (using x in [1, 2, 3]) { } + · ─────── + ╰──── + help: Did you mean to use a for...of statement? + + × Using declarations may not have binding patterns. + ╭─[test262/test/language/statements/using/syntax/using-invalid-objectbindingpattern-after-bindingidentifier.js:18:19] + 17 │ { + 18 │ using x = null, {} = null; + · ── + 19 │ } + ╰──── + + × Using declarations may not have binding patterns. + ╭─[test262/test/language/statements/using/syntax/using-invalid-objectbindingpattern.js:18:9] + 17 │ { + 18 │ using {} = null; + · ── + 19 │ } + ╰──── + + × Using declaration cannot appear in the bare case statement. + ╭─[test262/test/language/statements/using/syntax/with-initializer-case-expression-statement-list.js:20:28] + 19 │ $DONOTEVALUATE(); + 20 │ switch (true) { case true: using x = null; } + · ─────────────── + ╰──── + help: Wrap this declaration in a block statement + + × Using declaration cannot appear in the bare case statement. + ╭─[test262/test/language/statements/using/syntax/with-initializer-default-statement-list.js:21:26] + 20 │ $DONOTEVALUATE(); + 21 │ switch (true) { default: using x = null; } + · ─────────────── + ╰──── + help: Wrap this declaration in a block statement + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/using/syntax/with-initializer-do-statement-while-expression.js:17:4] + 16 │ $DONOTEVALUATE(); + 17 │ do using x = 1; while (false) + · ──────────── + ╰──── + help: Wrap this declaration in a block statement + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/using/syntax/with-initializer-for-statement.js:17:15] + 16 │ $DONOTEVALUATE(); + 17 │ for (;false;) using x = null; + · ─────────────── + ╰──── + help: Wrap this declaration in a block statement + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/using/syntax/with-initializer-if-expression-statement-else-statement.js:17:19] + 16 │ $DONOTEVALUATE(); + 17 │ if (true) {} else using x = null; + · ─────────────── + ╰──── + help: Wrap this declaration in a block statement + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/using/syntax/with-initializer-if-expression-statement.js:17:11] + 16 │ $DONOTEVALUATE(); + 17 │ if (true) using x = null; + · ─────────────── + ╰──── + help: Wrap this declaration in a block statement + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/using/syntax/with-initializer-label-statement.js:17:8] + 16 │ $DONOTEVALUATE(); + 17 │ label: using x = null; + · ─────────────── + ╰──── + help: Wrap this declaration in a block statement + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/using/syntax/with-initializer-while-expression-statement.js:17:15] + 16 │ $DONOTEVALUATE(); + 17 │ while (false) using x = null; + · ─────────────── + ╰──── + help: Wrap this declaration in a block statement + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/using/syntax/without-initializer-do-statement-while-expression.js:17:10] + 16 │ $DONOTEVALUATE(); + 17 │ do using x; while (false) + · ─ + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/using/syntax/without-initializer-do-statement-while-expression.js:17:4] + 16 │ $DONOTEVALUATE(); + 17 │ do using x; while (false) + · ──────── + ╰──── + help: Wrap this declaration in a block statement + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/using/syntax/without-initializer-for-statement.js:17:15] + 16 │ $DONOTEVALUATE(); + 17 │ for (;false;) using x; + · ──────── + ╰──── + help: Wrap this declaration in a block statement + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/using/syntax/without-initializer-if-expression-statement-else-statement.js:17:25] + 16 │ $DONOTEVALUATE(); + 17 │ if (true) {} else using x; + · ─ + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/using/syntax/without-initializer-if-expression-statement-else-statement.js:17:19] + 16 │ $DONOTEVALUATE(); + 17 │ if (true) {} else using x; + · ──────── + ╰──── + help: Wrap this declaration in a block statement + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/using/syntax/without-initializer-if-expression-statement.js:17:17] + 16 │ $DONOTEVALUATE(); + 17 │ if (true) using x; + · ─ + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/using/syntax/without-initializer-if-expression-statement.js:17:11] + 16 │ $DONOTEVALUATE(); + 17 │ if (true) using x; + · ──────── + ╰──── + help: Wrap this declaration in a block statement + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/using/syntax/without-initializer-label-statement.js:17:14] + 16 │ $DONOTEVALUATE(); + 17 │ label: using x; + · ─ + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/using/syntax/without-initializer-label-statement.js:17:8] + 16 │ $DONOTEVALUATE(); + 17 │ label: using x; + · ──────── + ╰──── + help: Wrap this declaration in a block statement + + × Using declarations must have an initializer. + ╭─[test262/test/language/statements/using/syntax/without-initializer-while-expression-statement.js:17:21] + 16 │ $DONOTEVALUATE(); + 17 │ while (false) using x; + · ─ + ╰──── + help: Add an initializer (e.g. ` = undefined`) here + + × Lexical declaration cannot appear in a single-statement context + ╭─[test262/test/language/statements/using/syntax/without-initializer-while-expression-statement.js:17:15] + 16 │ $DONOTEVALUATE(); + 17 │ while (false) using x; + · ──────── + ╰──── + help: Wrap this declaration in a block statement + × Cannot assign to 'eval' in strict mode ╭─[test262/test/language/statements/variable/12.2.1-1gs.js:18:10] 17 │ diff --git a/tasks/coverage/snapshots/parser_typescript.snap b/tasks/coverage/snapshots/parser_typescript.snap index 7288e0ea384d8..ec5380b12c327 100644 --- a/tasks/coverage/snapshots/parser_typescript.snap +++ b/tasks/coverage/snapshots/parser_typescript.snap @@ -1,8 +1,8 @@ -commit: 8ea03f88 +commit: 48244d89 parser_typescript Summary: -AST Parsed : 9811/9812 (99.99%) -Positive Passed: 9799/9812 (99.87%) +AST Parsed : 9816/9817 (99.99%) +Positive Passed: 9804/9817 (99.87%) Negative Passed: 1455/2545 (57.17%) Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ExportAssignment7.ts diff --git a/tasks/coverage/snapshots/semantic_babel.snap b/tasks/coverage/snapshots/semantic_babel.snap index cfde07cbe599e..42fe76fcdde48 100644 --- a/tasks/coverage/snapshots/semantic_babel.snap +++ b/tasks/coverage/snapshots/semantic_babel.snap @@ -1,4 +1,4 @@ -commit: 4cc3d888 +commit: 777ded79 semantic_babel Summary: AST Parsed : 2440/2440 (100.00%) diff --git a/tasks/coverage/snapshots/semantic_test262.snap b/tasks/coverage/snapshots/semantic_test262.snap index d66849bdfd08c..f67679ae5ecf5 100644 --- a/tasks/coverage/snapshots/semantic_test262.snap +++ b/tasks/coverage/snapshots/semantic_test262.snap @@ -1,8 +1,8 @@ -commit: d2940bdb +commit: fd594a07 semantic_test262 Summary: -AST Parsed : 44651/44651 (100.00%) -Positive Passed: 43010/44651 (96.32%) +AST Parsed : 44758/44758 (100.00%) +Positive Passed: 43114/44758 (96.33%) semantic Error: tasks/coverage/test262/test/annexB/language/expressions/assignmenttargettype/callexpression-as-for-in-lhs.js Cannot assign to this expression @@ -7306,6 +7306,31 @@ Scope flags mismatch: after transform: ScopeId(4): ScopeFlags(Function) rebuilt : ScopeId(2): ScopeFlags(Function | DirectEval) +semantic Error: tasks/coverage/test262/test/language/statements/await-using/syntax/await-using-allows-bindingidentifier.js +Bindings mismatch: +after transform: ScopeId(1): ["_usingCtx", "x"] +rebuilt : ScopeId(1): ["_usingCtx"] +Bindings mismatch: +after transform: ScopeId(2): [] +rebuilt : ScopeId(2): ["x"] +Symbol scope ID mismatch for "x": +after transform: SymbolId(0): ScopeId(1) +rebuilt : SymbolId(3): ScopeId(2) + +semantic Error: tasks/coverage/test262/test/language/statements/await-using/syntax/await-using-allows-multiple-bindings.js +Bindings mismatch: +after transform: ScopeId(1): ["_usingCtx", "x", "y"] +rebuilt : ScopeId(3): ["_usingCtx"] +Bindings mismatch: +after transform: ScopeId(2): [] +rebuilt : ScopeId(4): ["x", "y"] +Symbol scope ID mismatch for "x": +after transform: SymbolId(1): ScopeId(1) +rebuilt : SymbolId(5): ScopeId(4) +Symbol scope ID mismatch for "y": +after transform: SymbolId(2): ScopeId(1) +rebuilt : SymbolId(6): ScopeId(4) + semantic Error: tasks/coverage/test262/test/language/statements/await-using/throws-if-initializer-not-object.js Bindings mismatch: after transform: ScopeId(2): ["_usingCtx", "x"] @@ -14630,6 +14655,9 @@ Reference symbol mismatch for "x": after transform: SymbolId(1) "x" rebuilt : SymbolId(1) "x" +semantic Error: tasks/coverage/test262/test/language/statements/using/syntax/using-for-statement.js +Unexpected token + semantic Error: tasks/coverage/test262/test/language/statements/using/throws-if-initializer-not-object.js Bindings mismatch: after transform: ScopeId(1): ["_usingCtx", "x"] diff --git a/tasks/coverage/snapshots/semantic_typescript.snap b/tasks/coverage/snapshots/semantic_typescript.snap index 7ba84affa7856..f5138edc86597 100644 --- a/tasks/coverage/snapshots/semantic_typescript.snap +++ b/tasks/coverage/snapshots/semantic_typescript.snap @@ -1,8 +1,8 @@ -commit: 8ea03f88 +commit: 48244d89 semantic_typescript Summary: -AST Parsed : 6142/6142 (100.00%) -Positive Passed: 2660/6142 (43.31%) +AST Parsed : 6146/6146 (100.00%) +Positive Passed: 2660/6146 (43.28%) semantic Error: tasks/coverage/typescript/tests/cases/compiler/2dArrays.ts Symbol reference IDs mismatch for "Cell": after transform: SymbolId(0): [ReferenceId(1)] @@ -13442,6 +13442,38 @@ Symbol reference IDs mismatch for "A": after transform: SymbolId(0): [ReferenceId(0), ReferenceId(2)] rebuilt : SymbolId(0): [ReferenceId(1)] +semantic Error: tasks/coverage/typescript/tests/cases/compiler/forAwaitForIntersection1.ts +Scope children mismatch: +after transform: ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3), ScopeId(7), ScopeId(8), ScopeId(9), ScopeId(13), ScopeId(14), ScopeId(15), ScopeId(21), ScopeId(22), ScopeId(23), ScopeId(38), ScopeId(39), ScopeId(40), ScopeId(41), ScopeId(51), ScopeId(52), ScopeId(62), ScopeId(63)] +rebuilt : ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3), ScopeId(4), ScopeId(16), ScopeId(17), ScopeId(18), ScopeId(19), ScopeId(23), ScopeId(24), ScopeId(25), ScopeId(26), ScopeId(40), ScopeId(41), ScopeId(42), ScopeId(43)] +Symbol reference IDs mismatch for "A1": +after transform: SymbolId(3): [ReferenceId(5)] +rebuilt : SymbolId(2): [] +Symbol reference IDs mismatch for "B1": +after transform: SymbolId(4): [ReferenceId(6)] +rebuilt : SymbolId(3): [] +Symbol reference IDs mismatch for "A2": +after transform: SymbolId(11): [ReferenceId(13)] +rebuilt : SymbolId(15): [] +Symbol reference IDs mismatch for "B2": +after transform: SymbolId(12): [ReferenceId(14)] +rebuilt : SymbolId(16): [] +Symbol reference IDs mismatch for "A3": +after transform: SymbolId(19): [ReferenceId(21)] +rebuilt : SymbolId(22): [] +Symbol reference IDs mismatch for "B3": +after transform: SymbolId(20): [ReferenceId(22)] +rebuilt : SymbolId(23): [] +Symbol reference IDs mismatch for "A4": +after transform: SymbolId(28): [ReferenceId(30)] +rebuilt : SymbolId(36): [] +Symbol reference IDs mismatch for "B4": +after transform: SymbolId(29): [ReferenceId(31)] +rebuilt : SymbolId(37): [] +Unresolved references mismatch: +after transform: ["AsyncIterable", "Iterable", "arguments", "require"] +rebuilt : ["arguments", "require"] + semantic Error: tasks/coverage/typescript/tests/cases/compiler/forAwaitForUnion.ts Unresolved references mismatch: after transform: ["AsyncIterable", "Iterable", "arguments", "require"] @@ -30801,6 +30833,20 @@ Symbol reference IDs mismatch for "Type": after transform: SymbolId(23): [ReferenceId(51), ReferenceId(60)] rebuilt : SymbolId(9): [] +semantic Error: tasks/coverage/typescript/tests/cases/compiler/variancePropagation.ts +Bindings mismatch: +after transform: ScopeId(0): ["destination", "source"] +rebuilt : ScopeId(0): ["destination"] +Scope children mismatch: +after transform: ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3)] +rebuilt : ScopeId(0): [] +Reference symbol mismatch for "source": +after transform: SymbolId(4) "source" +rebuilt : +Unresolved references mismatch: +after transform: ["Readonly"] +rebuilt : ["source"] + semantic Error: tasks/coverage/typescript/tests/cases/compiler/varianceRepeatedlyPropegatesWithUnreliableFlag.ts Scope children mismatch: after transform: ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3), ScopeId(5), ScopeId(6), ScopeId(7)] @@ -49822,6 +49868,143 @@ Scope children mismatch: after transform: ScopeId(0): [ScopeId(1), ScopeId(3), ScopeId(4), ScopeId(5)] rebuilt : ScopeId(0): [ScopeId(1), ScopeId(3)] +semantic Error: tasks/coverage/typescript/tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterConstModifiersReturnsAndYields.ts +Bindings mismatch: +after transform: ScopeId(0): ["E", "FileRouter", "UploadThingServerHelper", "_asyncToGenerator", "_wrapAsyncGenerator", "nestedResult1", "nestedResult2", "nestedResult3", "outer1", "outer2", "outer3", "overloadA", "overloadB", "overloadC", "overloadD", "overloadE", "overloadF", "overloadG", "overloadH", "overloadI", "overloadJ", "overloaded1", "overloaded2", "overloaded3", "overloaded4", "overloaded5", "result1", "result10", "result11", "result12", "result13", "result14", "result15", "result16", "result17", "result18", "result19", "result2", "result20", "result21", "result22", "result3", "result4", "result5", "result6", "result7", "result8", "result9", "test1", "test2", "test3", "test4"] +rebuilt : ScopeId(0): ["E", "FileRouter", "UploadThingServerHelper", "_asyncToGenerator", "_wrapAsyncGenerator", "nestedResult1", "nestedResult2", "nestedResult3", "outer1", "outer2", "outer3", "overloadA", "overloadB", "overloadC", "overloadD", "overloadE", "overloadF", "overloadG", "overloadH", "overloadI", "overloadJ", "result1", "result10", "result11", "result12", "result13", "result14", "result15", "result16", "result17", "result18", "result19", "result2", "result20", "result21", "result22", "result3", "result4", "result5", "result6", "result7", "result8", "result9"] +Scope children mismatch: +after transform: ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(4), ScopeId(5), ScopeId(6), ScopeId(7), ScopeId(8), ScopeId(9), ScopeId(10), ScopeId(11), ScopeId(12), ScopeId(13), ScopeId(14), ScopeId(15), ScopeId(16), ScopeId(17), ScopeId(18), ScopeId(19), ScopeId(20), ScopeId(21), ScopeId(22), ScopeId(24), ScopeId(25), ScopeId(26), ScopeId(28), ScopeId(29), ScopeId(31), ScopeId(32), ScopeId(36), ScopeId(37), ScopeId(38), ScopeId(42), ScopeId(43), ScopeId(44), ScopeId(48), ScopeId(49), ScopeId(50), ScopeId(54), ScopeId(55), ScopeId(56), ScopeId(58), ScopeId(61), ScopeId(62), ScopeId(63), ScopeId(64), ScopeId(66), ScopeId(69), ScopeId(70), ScopeId(71), ScopeId(72), ScopeId(74), ScopeId(77), ScopeId(78), ScopeId(79), ScopeId(80), ScopeId(82), ScopeId(85), ScopeId(86), ScopeId(87), ScopeId(88), ScopeId(90), ScopeId(93), ScopeId(94), ScopeId(95)] +rebuilt : ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3), ScopeId(4), ScopeId(5), ScopeId(6), ScopeId(7), ScopeId(8), ScopeId(9), ScopeId(10), ScopeId(11), ScopeId(12), ScopeId(13), ScopeId(14), ScopeId(15), ScopeId(16), ScopeId(17), ScopeId(18), ScopeId(19), ScopeId(20), ScopeId(21), ScopeId(22), ScopeId(23), ScopeId(24), ScopeId(25), ScopeId(26), ScopeId(27), ScopeId(29), ScopeId(30), ScopeId(31), ScopeId(33), ScopeId(34), ScopeId(35), ScopeId(37), ScopeId(38), ScopeId(39), ScopeId(40), ScopeId(41), ScopeId(42), ScopeId(43), ScopeId(44), ScopeId(45), ScopeId(46), ScopeId(47), ScopeId(48), ScopeId(49), ScopeId(50), ScopeId(51), ScopeId(52), ScopeId(53)] +Bindings mismatch: +after transform: ScopeId(1): ["E", "Val", "Val2"] +rebuilt : ScopeId(1): ["E"] +Scope flags mismatch: +after transform: ScopeId(1): ScopeFlags(0x0) +rebuilt : ScopeId(1): ScopeFlags(Function) +Scope children mismatch: +after transform: ScopeId(32): [ScopeId(33)] +rebuilt : ScopeId(24): [] +Scope children mismatch: +after transform: ScopeId(38): [ScopeId(39), ScopeId(40)] +rebuilt : ScopeId(27): [ScopeId(28)] +Scope children mismatch: +after transform: ScopeId(40): [ScopeId(41)] +rebuilt : ScopeId(28): [] +Scope children mismatch: +after transform: ScopeId(44): [ScopeId(45), ScopeId(46)] +rebuilt : ScopeId(31): [ScopeId(32)] +Scope children mismatch: +after transform: ScopeId(46): [ScopeId(47)] +rebuilt : ScopeId(32): [] +Scope children mismatch: +after transform: ScopeId(50): [ScopeId(51), ScopeId(52)] +rebuilt : ScopeId(35): [ScopeId(36)] +Scope children mismatch: +after transform: ScopeId(52): [ScopeId(53)] +rebuilt : ScopeId(36): [] +Symbol flags mismatch for "E": +after transform: SymbolId(0): SymbolFlags(RegularEnum) +rebuilt : SymbolId(2): SymbolFlags(FunctionScopedVariable) +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test1": +after transform: SymbolId(3) "test1" +rebuilt : +Reference symbol mismatch for "test2": +after transform: SymbolId(24) "test2" +rebuilt : +Reference symbol mismatch for "test2": +after transform: SymbolId(24) "test2" +rebuilt : +Reference symbol mismatch for "test3": +after transform: SymbolId(29) "test3" +rebuilt : +Reference symbol mismatch for "test4": +after transform: SymbolId(34) "test4" +rebuilt : +Reference symbol mismatch for "overloaded1": +after transform: SymbolId(68) "overloaded1" +rebuilt : +Reference symbol mismatch for "overloaded1": +after transform: SymbolId(68) "overloaded1" +rebuilt : +Reference symbol mismatch for "overloaded2": +after transform: SymbolId(77) "overloaded2" +rebuilt : +Reference symbol mismatch for "overloaded2": +after transform: SymbolId(77) "overloaded2" +rebuilt : +Reference symbol mismatch for "overloaded3": +after transform: SymbolId(86) "overloaded3" +rebuilt : +Reference symbol mismatch for "overloaded3": +after transform: SymbolId(86) "overloaded3" +rebuilt : +Reference symbol mismatch for "overloaded4": +after transform: SymbolId(95) "overloaded4" +rebuilt : +Reference symbol mismatch for "overloaded4": +after transform: SymbolId(95) "overloaded4" +rebuilt : +Reference symbol mismatch for "overloaded5": +after transform: SymbolId(104) "overloaded5" +rebuilt : +Reference symbol mismatch for "overloaded5": +after transform: SymbolId(104) "overloaded5" +rebuilt : +Unresolved references mismatch: +after transform: ["AsyncGenerator", "Generator", "Math", "Promise", "require"] +rebuilt : ["Math", "overloaded1", "overloaded2", "overloaded3", "overloaded4", "overloaded5", "require", "test1", "test2", "test3", "test4"] + semantic Error: tasks/coverage/typescript/tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterConstModifiersReverseMappedTypes.ts Bindings mismatch: after transform: ScopeId(0): ["result1", "result2", "result3", "result4", "result5", "test1", "test2", "test3", "test4", "test5"] @@ -56896,6 +57079,23 @@ Scope children mismatch: after transform: ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3), ScopeId(4)] rebuilt : ScopeId(0): [ScopeId(1)] +semantic Error: tasks/coverage/typescript/tests/cases/conformance/types/union/discriminatedUnionTypes4.ts +Scope children mismatch: +after transform: ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3), ScopeId(4), ScopeId(8), ScopeId(10)] +rebuilt : ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3), ScopeId(7), ScopeId(9)] +Bindings mismatch: +after transform: ScopeId(1): ["AnimalType", "cat", "dog"] +rebuilt : ScopeId(1): ["AnimalType"] +Scope flags mismatch: +after transform: ScopeId(1): ScopeFlags(0x0) +rebuilt : ScopeId(1): ScopeFlags(Function) +Symbol flags mismatch for "AnimalType": +after transform: SymbolId(0): SymbolFlags(RegularEnum) +rebuilt : SymbolId(0): SymbolFlags(FunctionScopedVariable) +Symbol reference IDs mismatch for "AnimalType": +after transform: SymbolId(0): [ReferenceId(0), ReferenceId(1), ReferenceId(5), ReferenceId(9), ReferenceId(16), ReferenceId(19), ReferenceId(26), ReferenceId(29), ReferenceId(37)] +rebuilt : SymbolId(0): [ReferenceId(3), ReferenceId(6), ReferenceId(10), ReferenceId(16), ReferenceId(19), ReferenceId(25), ReferenceId(28)] + semantic Error: tasks/coverage/typescript/tests/cases/conformance/types/union/unionTypeCallSignatures2.ts Scope children mismatch: after transform: ScopeId(0): [ScopeId(1), ScopeId(6), ScopeId(11)] diff --git a/tasks/coverage/snapshots/transformer_babel.snap b/tasks/coverage/snapshots/transformer_babel.snap index 7f044f3a7aa8f..489b6a15a61ae 100644 --- a/tasks/coverage/snapshots/transformer_babel.snap +++ b/tasks/coverage/snapshots/transformer_babel.snap @@ -1,4 +1,4 @@ -commit: 4cc3d888 +commit: 777ded79 transformer_babel Summary: AST Parsed : 2440/2440 (100.00%) diff --git a/tasks/coverage/snapshots/transformer_test262.snap b/tasks/coverage/snapshots/transformer_test262.snap index 7ab2ab937cb2f..81720e4dd34ec 100644 --- a/tasks/coverage/snapshots/transformer_test262.snap +++ b/tasks/coverage/snapshots/transformer_test262.snap @@ -1,5 +1,5 @@ -commit: d2940bdb +commit: fd594a07 transformer_test262 Summary: -AST Parsed : 44651/44651 (100.00%) -Positive Passed: 44651/44651 (100.00%) +AST Parsed : 44758/44758 (100.00%) +Positive Passed: 44758/44758 (100.00%) diff --git a/tasks/coverage/snapshots/transformer_typescript.snap b/tasks/coverage/snapshots/transformer_typescript.snap index c9452b018a17f..560814d761192 100644 --- a/tasks/coverage/snapshots/transformer_typescript.snap +++ b/tasks/coverage/snapshots/transformer_typescript.snap @@ -1,8 +1,8 @@ -commit: 8ea03f88 +commit: 48244d89 transformer_typescript Summary: -AST Parsed : 9812/9812 (100.00%) -Positive Passed: 9810/9812 (99.98%) +AST Parsed : 9817/9817 (100.00%) +Positive Passed: 9815/9817 (99.98%) Mismatch: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/autoAccessor2.ts Mismatch: tasks/coverage/typescript/tests/cases/conformance/esDecorators/classDeclaration/fields/esDecorators-classDeclaration-fields-staticPrivateAccessor.ts diff --git a/tasks/coverage/snapshots/transpile.snap b/tasks/coverage/snapshots/transpile.snap index bf0f03190adc7..1caaa17f3cb72 100644 --- a/tasks/coverage/snapshots/transpile.snap +++ b/tasks/coverage/snapshots/transpile.snap @@ -1,4 +1,4 @@ -commit: 8ea03f88 +commit: 48244d89 transpile Summary: AST Parsed : 20/20 (100.00%) diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index d636dcd19013a..17b9c8a1d9147 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -11,13 +11,13 @@ Original | minified | minified | gzip | gzip | Iterations | Fi 544.10 kB | 71.04 kB | 72.48 kB | 25.78 kB | 26.20 kB | 2 | lodash.js -555.77 kB | 267.39 kB | 270.13 kB | 88.01 kB | 90.80 kB | 2 | d3.js +555.77 kB | 267.39 kB | 270.13 kB | 88.00 kB | 90.80 kB | 2 | d3.js 1.01 MB | 439.40 kB | 458.89 kB | 122.06 kB | 126.71 kB | 2 | bundle.min.js -1.25 MB | 642.65 kB | 646.76 kB | 159.39 kB | 163.73 kB | 2 | three.js +1.25 MB | 642.64 kB | 646.76 kB | 159.40 kB | 163.73 kB | 2 | three.js -2.14 MB | 711.15 kB | 724.14 kB | 160.43 kB | 181.07 kB | 2 | victory.js +2.14 MB | 711.13 kB | 724.14 kB | 160.42 kB | 181.07 kB | 2 | victory.js 3.20 MB | 1.00 MB | 1.01 MB | 322.59 kB | 331.56 kB | 3 | echarts.js diff --git a/tasks/prettier_conformance/snapshots/prettier.js.snap.md b/tasks/prettier_conformance/snapshots/prettier.js.snap.md index 453e389488638..da0135255ba3a 100644 --- a/tasks/prettier_conformance/snapshots/prettier.js.snap.md +++ b/tasks/prettier_conformance/snapshots/prettier.js.snap.md @@ -1,4 +1,4 @@ -js compatibility: 700/749 (93.46%) +js compatibility: 704/754 (93.37%) # Failed @@ -6,8 +6,7 @@ js compatibility: 700/749 (93.46%) | :-------- | :--------------: | :---------: | | js/arrows/comment.js | 💥💥 | 88.89% | | js/call/boolean/boolean.js | 💥 | 97.12% | -| js/class-comment/misc.js | 💥 | 72.73% | -| js/class-comment/superclass.js | 💥 | 95.35% | +| js/class-comment/superclass.js | 💥 | 95.65% | | js/comments/15661.js | 💥💥 | 55.17% | | js/comments/dangling_for.js | 💥💥 | 22.22% | | js/comments/empty-statements.js | 💥💥 | 90.91% | @@ -25,12 +24,13 @@ js compatibility: 700/749 (93.46%) | js/for/for-in-with-initializer.js | 💥 | 37.50% | | js/for/parentheses.js | 💥 | 96.00% | | js/identifier/for-of/let.js | 💥 | 92.31% | -| js/identifier/parentheses/let.js | 💥💥 | 82.27% | +| js/identifier/parentheses/let.js | 💥💥 | 84.09% | | js/if/comment-between-condition-and-body.js | 💥 | 65.79% | | js/if/expr_and_same_line_comments.js | 💥 | 97.73% | | js/if/if_comments.js | 💥 | 76.00% | | js/if/trailing_comment.js | 💥 | 91.43% | | js/last-argument-expansion/dangling-comment-in-arrow-function.js | 💥 | 22.22% | +| js/logical-assignment/inside-call/18171.js | 💥 | 90.20% | | js/object-multiline/multiline.js | 💥✨ | 22.22% | | js/quote-props/classes.js | 💥💥✨✨ | 47.06% | | js/quote-props/objects.js | 💥💥✨✨ | 45.10% | @@ -50,6 +50,7 @@ js compatibility: 700/749 (93.46%) | js/test-declarations/angularjs_inject.js | 💥💥 | 91.53% | | js/test-declarations/test_declarations.js | 💥💥 | 95.88% | | jsx/fbt/test.js | 💥 | 84.06% | +| jsx/ignore/spread.js | 💥 | 83.33% | | jsx/jsx/quotes.js | 💥💥💥💥 | 79.41% | | jsx/single-attribute-per-line/single-attribute-per-line.js | 💥✨ | 43.37% | | jsx/text-wrap/test.js | 💥 | 99.56% | diff --git a/tasks/prettier_conformance/snapshots/prettier.ts.snap.md b/tasks/prettier_conformance/snapshots/prettier.ts.snap.md index 471de0efddf58..b47ca74c1df67 100644 --- a/tasks/prettier_conformance/snapshots/prettier.ts.snap.md +++ b/tasks/prettier_conformance/snapshots/prettier.ts.snap.md @@ -1,10 +1,11 @@ -ts compatibility: 547/598 (91.47%) +ts compatibility: 545/603 (90.38%) # Failed | Spec path | Failed or Passed | Match ratio | | :-------- | :--------------: | :---------: | | jsx/fbt/test.js | 💥 | 84.06% | +| jsx/ignore/spread.js | 💥 | 83.33% | | jsx/jsx/quotes.js | 💥💥💥💥 | 79.41% | | jsx/single-attribute-per-line/single-attribute-per-line.js | 💥✨ | 43.37% | | jsx/text-wrap/test.js | 💥 | 99.56% | @@ -12,11 +13,15 @@ ts compatibility: 547/598 (91.47%) | typescript/angular-component-examples/15934.component.ts | 💥💥 | 53.85% | | typescript/angular-component-examples/test.component.ts | 💥💥 | 41.18% | | typescript/arrow/comments.ts | 💥✨ | 44.44% | -| typescript/chain-expression/call-expression.ts | 💥 | 68.75% | -| typescript/chain-expression/member-expression.ts | 💥 | 65.67% | -| typescript/chain-expression/test.ts | 💥 | 0.00% | +| typescript/as/as-const/as-const.ts | 💥 | 90.91% | +| typescript/as/break-after-keyword/18148.ts | 💥 | 82.22% | +| typescript/as/comments/18160.ts | 💥 | 71.58% | +| typescript/chain-expression/call-expression.ts | 💥 | 82.81% | +| typescript/chain-expression/member-expression.ts | 💥 | 82.09% | +| typescript/chain-expression/test.ts | 💥 | 50.00% | | typescript/class/empty-method-body.ts | 💥 | 80.00% | -| typescript/class/extends_implements.ts | 💥 | 90.12% | +| typescript/class/extends_implements.ts | 💥 | 84.27% | +| typescript/class/issue-16723.ts | 💥 | 82.19% | | typescript/class/quoted-property.ts | 💥 | 66.67% | | typescript/class-and-interface/long-type-parameters/long-type-parameters.ts | 💥 | 63.64% | | typescript/class-comment/class-implements.ts | 💥 | 98.89% | @@ -33,17 +38,18 @@ ts compatibility: 547/598 (91.47%) | typescript/decorators-ts/angular.ts | 💥 | 87.50% | | typescript/instantiation-expression/17714.ts | 💥 | 0.00% | | typescript/interface/comments-generic.ts | 💥💥 | 41.94% | -| typescript/interface/long-extends.ts | 💥💥 | 83.64% | +| typescript/interface/generic.ts | 💥💥 | 76.92% | +| typescript/interface/long-extends.ts | 💥💥 | 80.00% | | typescript/interface/long-type-parameters/long-type-parameters.ts | 💥💥 | 87.33% | | typescript/interface/no-semi/14040.ts | 💥 | 84.81% | -| typescript/interface2/break/break.ts | 💥💥💥 | 82.30% | +| typescript/interface2/break/break.ts | 💥💥💥 | 80.15% | | typescript/intersection/intersection-parens.ts | 💥💥 | 86.17% | | typescript/intersection/consistent-with-flow/intersection-parens.ts | 💥 | 69.77% | | typescript/last-argument-expansion/decorated-function.tsx | 💥 | 29.06% | | typescript/mapped-type/issue-11098.ts | 💥 | 97.03% | | typescript/mapped-type/break-mode/break-mode.ts | 💥 | 68.75% | | typescript/multiparser-css/issue-6259.ts | 💥 | 57.14% | -| typescript/non-null/optional-chain.ts | 💥 | 72.22% | +| typescript/non-null/optional-chain.ts | 💥 | 88.89% | | typescript/object-multiline/multiline.ts | 💥✨ | 23.21% | | typescript/prettier-ignore/mapped-types.ts | 💥 | 96.61% | | typescript/property-signature/consistent-with-flow/comments.ts | 💥 | 80.00% | @@ -53,5 +59,6 @@ ts compatibility: 547/598 (91.47%) | typescript/type-params/constraints-and-default.ts | 💥 | 87.32% | | typescript/union/inlining.ts | 💥 | 97.78% | | typescript/union/union-parens.ts | 💥 | 92.59% | -| typescript/union/consistent-with-flow/comment.ts | 💥 | 78.26% | +| typescript/union/comments/18106.ts | 💥 | 88.10% | +| typescript/union/consistent-with-flow/comment.ts | 💥 | 68.09% | | typescript/union/single-type/single-type.ts | 💥 | 0.00% | diff --git a/tasks/track_memory_allocations/allocs_minifier.snap b/tasks/track_memory_allocations/allocs_minifier.snap index b076ed1d1f319..88673ea63f056 100644 --- a/tasks/track_memory_allocations/allocs_minifier.snap +++ b/tasks/track_memory_allocations/allocs_minifier.snap @@ -2,13 +2,13 @@ File | File size || Sys allocs | Sys reallocs | ------------------------------------------------------------------------------------------------------------------------------------------- checker.ts | 2.92 MB || 83503 | 14179 || 152592 | 28237 -cal.com.tsx | 1.06 MB || 40452 | 3033 || 37146 | 4583 +cal.com.tsx | 1.06 MB || 40453 | 3033 || 37145 | 4586 RadixUIAdoptionSection.jsx | 2.52 kB || 85 | 9 || 30 | 6 pdf.mjs | 567.30 kB || 20494 | 2899 || 47442 | 7725 -antd.js | 6.69 MB || 99524 | 13523 || 331573 | 69338 +antd.js | 6.69 MB || 99524 | 13523 || 331583 | 69338 binder.ts | 193.08 kB || 4760 | 974 || 7075 | 824 diff --git a/tasks/transform_conformance/snapshots/babel.snap.md b/tasks/transform_conformance/snapshots/babel.snap.md index b870e546a19fc..3164323389a8e 100644 --- a/tasks/transform_conformance/snapshots/babel.snap.md +++ b/tasks/transform_conformance/snapshots/babel.snap.md @@ -1,6 +1,6 @@ -commit: 4cc3d888 +commit: 777ded79 -Passed: 711/1217 +Passed: 713/1219 # All Passed: * babel-plugin-transform-logical-assignment-operators @@ -2824,7 +2824,7 @@ x Output mismatch x Output mismatch -# babel-plugin-transform-react-jsx (132/144) +# babel-plugin-transform-react-jsx (134/146) * autoImport/after-polyfills-compiled-to-cjs/input.mjs x Output mismatch diff --git a/tasks/transform_conformance/snapshots/babel_exec.snap.md b/tasks/transform_conformance/snapshots/babel_exec.snap.md index be857b49928b5..f54a21ab0ed14 100644 --- a/tasks/transform_conformance/snapshots/babel_exec.snap.md +++ b/tasks/transform_conformance/snapshots/babel_exec.snap.md @@ -1,4 +1,4 @@ -commit: 4cc3d888 +commit: 777ded79 node: v24.10.0 diff --git a/tasks/transform_conformance/snapshots/oxc.snap.md b/tasks/transform_conformance/snapshots/oxc.snap.md index 96a636d0de315..2122469de6fb3 100644 --- a/tasks/transform_conformance/snapshots/oxc.snap.md +++ b/tasks/transform_conformance/snapshots/oxc.snap.md @@ -1,4 +1,4 @@ -commit: 4cc3d888 +commit: 777ded79 Passed: 188/319 diff --git a/tasks/transform_conformance/snapshots/oxc_exec.snap.md b/tasks/transform_conformance/snapshots/oxc_exec.snap.md index a26083ddaa221..08b4c237058a6 100644 --- a/tasks/transform_conformance/snapshots/oxc_exec.snap.md +++ b/tasks/transform_conformance/snapshots/oxc_exec.snap.md @@ -1,4 +1,4 @@ -commit: 4cc3d888 +commit: 777ded79 node: v24.10.0 diff --git a/tasks/website/src/linter/json_schema.rs b/tasks/website/src/linter/json_schema.rs index 96ad8fb986aeb..5eca145eacfad 100644 --- a/tasks/website/src/linter/json_schema.rs +++ b/tasks/website/src/linter/json_schema.rs @@ -199,6 +199,36 @@ impl Renderer { }) .collect::>(); } + if let Some(schemas) = &subschemas.any_of { + // For anyOf, render as a union type description + let type_descriptions: Vec = schemas + .iter() + .filter_map(|subschema| { + let subschema = Self::get_schema_object(subschema); + let subschema = self.get_referenced_schema(subschema); + subschema + .instance_type + .as_ref() + .map(|t| serde_json::to_string(t).unwrap().replace('"', "")) + }) + .collect(); + + if !type_descriptions.is_empty() { + let union_type = type_descriptions.join(" | "); + return vec![Section { + level: "#".repeat(depth), + title: key.into(), + instance_type: Some(union_type), + default: Self::render_default(schema), + description: schema + .metadata + .as_ref() + .and_then(|m| m.description.clone()) + .unwrap_or_default(), + sections: vec![], + }]; + } + } vec![] } @@ -212,6 +242,14 @@ impl Renderer { let ref_schema = schema; let schema = self.get_referenced_schema(schema); + // Handle subschemas (anyOf, allOf, etc.) at the top level + if let Some(subschemas) = &schema.subschemas { + let subsections = self.render_sub_schema(depth, key, subschemas, schema); + if !subsections.is_empty() { + return subsections.into_iter().next().unwrap(); + } + } + let (instance_type, sections) = if let Some(item) = as_primitive_array(schema) { // e.g. "string[]" instead of "array" let instance_type = serde_json::to_string_pretty(item.instance_type.as_ref().unwrap()) diff --git a/tasks/website/src/linter/snapshots/schema_markdown.snap b/tasks/website/src/linter/snapshots/schema_markdown.snap index 9482e4fff6690..d87ab057e27a7 100644 --- a/tasks/website/src/linter/snapshots/schema_markdown.snap +++ b/tasks/website/src/linter/snapshots/schema_markdown.snap @@ -223,6 +223,14 @@ type: `object` +### overrides[n].env + +type: `object | null` + + +Environments enable and disable collections of global variables. + + #### overrides[n].files type: `string[]` @@ -231,6 +239,14 @@ type: `string[]` A set of glob patterns. +### overrides[n].globals + +type: `object | null` + + +Enabled or disabled specific global variables. + + #### overrides[n].jsPlugins type: `string[]` @@ -242,6 +258,16 @@ Note: JS plugins are experimental and not subject to semver. They are not supported in language server at present. +### overrides[n].plugins + +type: `array | null` + +default: `null` + +Optionally change what plugins are enabled for this override. When +omitted, the base config's plugins are used. + + #### overrides[n].rules type: `object` @@ -250,6 +276,20 @@ type: `object` See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html) +# plugins + +type: `array | null` + +default: `null` + +Enabled built-in plugins for Oxlint. +You can view the list of available plugins on +[the website](https://oxc.rs/docs/guide/usage/linter/plugins.html#supported-plugins). + +NOTE: Setting the `plugins` field will overwrite the base set of plugins. +The `plugins` array should reflect all of the plugins you want to use. + + ## rules type: `object` @@ -465,6 +505,7 @@ Configure Next.js plugin rules. #### settings.next.rootDir +type: `string | array` @@ -509,6 +550,7 @@ Example: ##### settings.react.formComponents[n] +type: `string | object | object` @@ -544,6 +586,7 @@ Example: ##### settings.react.linkComponents[n] +type: `string | object | object`