diff --git a/.clippy.toml b/.clippy.toml index 9da0cf0e84dcc..c76422cd094e8 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,4 +1,4 @@ -ignore-interior-mutability = ["oxc_linter::rule::RuleWithSeverity"] +ignore-interior-mutability = ["oxc_linter::rules::RuleEnum"] disallowed-methods = [ { path = "str::to_ascii_lowercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_ascii_lowercase` instead." }, diff --git a/.github/generated/ast_changes_watch_list.yml b/.github/generated/ast_changes_watch_list.yml index b671c40bc73f7..96738fe0fefb0 100644 --- a/.github/generated/ast_changes_watch_list.yml +++ b/.github/generated/ast_changes_watch_list.yml @@ -20,8 +20,14 @@ src: - '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.rs' - - 'crates/oxc_ast_macros/src/generated/mod.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_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' @@ -35,7 +41,7 @@ src: - '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/mod.rs' + - 'crates/oxc_span/src/source_type.rs' - 'crates/oxc_span/src/span.rs' - 'crates/oxc_syntax/src/generated/assert_layouts.rs' - 'crates/oxc_syntax/src/generated/derive_clone_in.rs' diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index e742fe4ab2714..7b26216d345a1 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -148,7 +148,7 @@ jobs: chmod +x ./target/codspeed/instrumentation/oxc_benchmark/* - name: Install codspeed - uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2.50.7 + uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2.50.10 with: tool: cargo-codspeed diff --git a/.github/workflows/cargo_llvm_lines.yml b/.github/workflows/cargo_llvm_lines.yml index d218ea9a05baf..aedb6a28d8c38 100644 --- a/.github/workflows/cargo_llvm_lines.yml +++ b/.github/workflows/cargo_llvm_lines.yml @@ -22,7 +22,7 @@ jobs: - uses: oxc-project/setup-rust@cd82e1efec7fef815e2c23d296756f31c7cdc03d # v1.0.0 - name: Install cargo-llvm-lines - uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2.50.7 + uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2.50.10 with: tool: cargo-llvm-lines diff --git a/.github/workflows/ci_security.yml b/.github/workflows/ci_security.yml index 62077e93462d4..ac69bf785a3c9 100644 --- a/.github/workflows/ci_security.yml +++ b/.github/workflows/ci_security.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: taiki-e/checkout-action@b13d20b7cda4e2f325ef19895128f7ff735c0b3d # v1.3.1 - - uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2.50.7 + - uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2.50.10 with: tool: zizmor diff --git a/.github/workflows/dprint.yml b/.github/workflows/dprint.yml index d6385b1db16da..0514655e93ebb 100644 --- a/.github/workflows/dprint.yml +++ b/.github/workflows/dprint.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: taiki-e/checkout-action@b13d20b7cda4e2f325ef19895128f7ff735c0b3d # v1.3.1 - - uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2.50.7 + - uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2.50.10 with: tool: dprint diff --git a/.github/workflows/link_check.yml b/.github/workflows/link_check.yml index 3dd5713df84f9..0e2e21f35a4b2 100644 --- a/.github/workflows/link_check.yml +++ b/.github/workflows/link_check.yml @@ -31,7 +31,7 @@ jobs: uses: taiki-e/checkout-action@b13d20b7cda4e2f325ef19895128f7ff735c0b3d # v1.3.1 - name: Check Links - uses: lycheeverse/lychee-action@1d97d84f0bc547f7b25f4c2170d87d810dc2fb2c # v2.4.0 + uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1 with: # For parameter description, see https://github.com/lycheeverse/lychee#commandline-parameters # Accept 429 for now due to GitHub rate limit. diff --git a/.github/workflows/release_oxlint.yml b/.github/workflows/release_oxlint.yml index 77c1ab73416a4..c4164ff2de02b 100644 --- a/.github/workflows/release_oxlint.yml +++ b/.github/workflows/release_oxlint.yml @@ -88,7 +88,7 @@ jobs: - uses: taiki-e/checkout-action@b13d20b7cda4e2f325ef19895128f7ff735c0b3d # v1.3.1 - name: Install cross - uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2.50.7 + uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2.50.10 with: tool: cross diff --git a/.github/workflows/release_vscode.yml b/.github/workflows/release_vscode.yml index 00b1e681b5fba..3ddb3c594b791 100644 --- a/.github/workflows/release_vscode.yml +++ b/.github/workflows/release_vscode.yml @@ -81,7 +81,7 @@ jobs: run: pnpm run compile - name: Install cross - uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2.50.7 + uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2.50.10 with: tool: cross diff --git a/.github/workflows/reusable_release_napi.yml b/.github/workflows/reusable_release_napi.yml index 960fb0fa12e0f..350411b462251 100644 --- a/.github/workflows/reusable_release_napi.yml +++ b/.github/workflows/reusable_release_napi.yml @@ -86,6 +86,11 @@ jobs: build: | pnpm build --target armv7-unknown-linux-gnueabihf --use-napi-cross + - os: ubuntu-latest + target: armv7-unknown-linux-musleabihf + build: | + pnpm build --target armv7-unknown-linux-musleabihf -x + - os: macos-latest target: x86_64-apple-darwin build: | diff --git a/.gitignore b/.gitignore index 2313c7e481136..e8224a0b34300 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,8 @@ target/ /editors/vscode/out/ /editors/vscode/*.vsix /editors/vscode/test_workspace/ +/editors/vscode/test_workspace_second/ +/editors/vscode/*.test.code-workspace # Cloned conformance repos tasks/coverage/babel/ diff --git a/Cargo.lock b/Cargo.lock index f2068566de9ed..aa57190d0cfff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,9 +58,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -68,7 +68,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -176,9 +176,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.21" +version = "1.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" dependencies = [ "shlex", ] @@ -808,9 +808,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", @@ -917,21 +917,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -940,31 +941,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -972,67 +953,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -1052,9 +1020,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -1124,18 +1092,18 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "javascript-globals" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1801f93e5ec6ff0967c1efb67824146b8c5750c316ef3b5ff9d3754e933dfbfa" +checksum = "0d354e7ed074ff87cc305457421b6fd6388ea445f5a4e8b300e0f3a2cd96df8b" dependencies = [ "phf", ] [[package]] name = "jiff" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07d8d955d798e7a4d6f9c58cd1f1916e790b42b092758a9ef6e16fef9f1b3fd" +checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" dependencies = [ "jiff-static", "log", @@ -1146,9 +1114,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f244cfe006d98d26f859c7abd1318d85327e1882dc9cef80f62daeeb0adcf300" +checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" dependencies = [ "proc-macro2", "quote", @@ -1207,19 +1175,19 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", - "windows-targets", + "windows-targets 0.53.0", ] [[package]] name = "libm" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25169bd5913a4b437588a7e3d127cd6e90127b60e0ffbd834a38f1599e016b8" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libmimalloc-sys2" @@ -1240,9 +1208,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" @@ -1490,7 +1458,7 @@ checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" [[package]] name = "oxc" -version = "0.68.1" +version = "0.70.0" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1512,9 +1480,9 @@ dependencies = [ [[package]] name = "oxc-browserslist" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf11cf76199836fa65eb981c7cee15baaaa3812ddec4210e2f38f6b96d413e34" +checksum = "b847912b3807a563f0e0e122bd4086dfca503662fc16f8558b253bdf83cfa71f" dependencies = [ "nom", "rustc-hash", @@ -1551,7 +1519,7 @@ dependencies = [ [[package]] name = "oxc_allocator" -version = "0.68.1" +version = "0.70.0" dependencies = [ "allocator-api2", "bumpalo", @@ -1566,7 +1534,7 @@ dependencies = [ [[package]] name = "oxc_ast" -version = "0.68.1" +version = "0.70.0" dependencies = [ "bitflags 2.9.0", "cow-utils", @@ -1581,8 +1549,9 @@ dependencies = [ [[package]] name = "oxc_ast_macros" -version = "0.68.1" +version = "0.70.0" dependencies = [ + "phf", "proc-macro2", "quote", "syn", @@ -1601,6 +1570,7 @@ dependencies = [ "lazy-regex", "oxc_index", "phf", + "phf_codegen", "prettyplease", "proc-macro2", "quote", @@ -1612,7 +1582,7 @@ dependencies = [ [[package]] name = "oxc_ast_visit" -version = "0.68.1" +version = "0.70.0" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1646,7 +1616,7 @@ dependencies = [ [[package]] name = "oxc_cfg" -version = "0.68.1" +version = "0.70.0" dependencies = [ "bitflags 2.9.0", "itertools", @@ -1659,7 +1629,7 @@ dependencies = [ [[package]] name = "oxc_codegen" -version = "0.68.1" +version = "0.70.0" dependencies = [ "base64", "bitflags 2.9.0", @@ -1722,14 +1692,14 @@ dependencies = [ [[package]] name = "oxc_data_structures" -version = "0.68.1" +version = "0.70.0" dependencies = [ "ropey", ] [[package]] name = "oxc_diagnostics" -version = "0.68.1" +version = "0.70.0" dependencies = [ "cow-utils", "oxc-miette", @@ -1737,7 +1707,7 @@ dependencies = [ [[package]] name = "oxc_ecmascript" -version = "0.68.1" +version = "0.70.0" dependencies = [ "cow-utils", "num-bigint", @@ -1749,7 +1719,7 @@ dependencies = [ [[package]] name = "oxc_estree" -version = "0.68.1" +version = "0.70.0" dependencies = [ "itoa", "oxc_data_structures", @@ -1783,7 +1753,7 @@ dependencies = [ [[package]] name = "oxc_isolated_declarations" -version = "0.68.1" +version = "0.70.0" dependencies = [ "bitflags 2.9.0", "insta", @@ -1801,7 +1771,7 @@ dependencies = [ [[package]] name = "oxc_language_server" -version = "0.16.10" +version = "0.16.11" dependencies = [ "env_logger", "futures", @@ -1822,7 +1792,7 @@ dependencies = [ [[package]] name = "oxc_linter" -version = "0.16.10" +version = "0.16.11" dependencies = [ "bitflags 2.9.0", "constcat", @@ -1830,7 +1800,6 @@ dependencies = [ "cow-utils", "fast-glob", "globset", - "ignore", "indexmap", "insta", "itertools", @@ -1884,7 +1853,7 @@ dependencies = [ [[package]] name = "oxc_mangler" -version = "0.68.1" +version = "0.70.0" dependencies = [ "fixedbitset", "itertools", @@ -1900,7 +1869,7 @@ dependencies = [ [[package]] name = "oxc_minifier" -version = "0.68.1" +version = "0.70.0" dependencies = [ "cow-utils", "insta", @@ -1958,7 +1927,7 @@ dependencies = [ [[package]] name = "oxc_napi" -version = "0.68.1" +version = "0.70.0" dependencies = [ "napi", "napi-build", @@ -1966,12 +1935,13 @@ dependencies = [ "oxc_ast", "oxc_ast_visit", "oxc_diagnostics", + "oxc_span", "oxc_syntax", ] [[package]] name = "oxc_parser" -version = "0.68.1" +version = "0.70.0" dependencies = [ "bitflags 2.9.0", "cow-utils", @@ -1994,7 +1964,7 @@ dependencies = [ [[package]] name = "oxc_parser_napi" -version = "0.68.1" +version = "0.70.0" dependencies = [ "mimalloc-safe", "napi", @@ -2044,7 +2014,7 @@ dependencies = [ [[package]] name = "oxc_regular_expression" -version = "0.68.1" +version = "0.70.0" dependencies = [ "oxc_allocator", "oxc_ast_macros", @@ -2057,9 +2027,9 @@ dependencies = [ [[package]] name = "oxc_resolver" -version = "8.0.0" +version = "9.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5c7d43ec2c0dcd506adb52f94cc2903e754f413cb548c4733b03c3103eb908" +checksum = "fa108b1939b68ef316a674ac1d81a3f3a9012e5373d914b9e549ae69b6f2b729" dependencies = [ "cfg-if", "indexmap", @@ -2076,7 +2046,7 @@ dependencies = [ [[package]] name = "oxc_semantic" -version = "0.68.1" +version = "0.70.0" dependencies = [ "insta", "itertools", @@ -2099,9 +2069,9 @@ dependencies = [ [[package]] name = "oxc_sourcemap" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6665c417b2aa1c426a7b142bcc0d2f47d9fee9e6f88610f054cfa7ce6623001e" +checksum = "9cd7bb37974a2684a080d05b9c28460e1610c5ac5ef13f481a45179f458239cb" dependencies = [ "base64-simd", "cfg-if", @@ -2117,7 +2087,7 @@ dependencies = [ [[package]] name = "oxc_span" -version = "0.68.1" +version = "0.70.0" dependencies = [ "compact_str", "oxc-miette", @@ -2130,7 +2100,7 @@ dependencies = [ [[package]] name = "oxc_syntax" -version = "0.68.1" +version = "0.70.0" dependencies = [ "bitflags 2.9.0", "cow-utils", @@ -2189,7 +2159,7 @@ dependencies = [ [[package]] name = "oxc_transform_napi" -version = "0.68.1" +version = "0.70.0" dependencies = [ "mimalloc-safe", "napi", @@ -2203,7 +2173,7 @@ dependencies = [ [[package]] name = "oxc_transformer" -version = "0.68.1" +version = "0.70.0" dependencies = [ "base64", "compact_str", @@ -2211,6 +2181,7 @@ dependencies = [ "indexmap", "insta", "itoa", + "memchr", "oxc-browserslist", "oxc_allocator", "oxc_ast", @@ -2234,7 +2205,7 @@ dependencies = [ [[package]] name = "oxc_transformer_plugins" -version = "0.68.1" +version = "0.70.0" dependencies = [ "cow-utils", "insta", @@ -2260,7 +2231,7 @@ dependencies = [ [[package]] name = "oxc_traverse" -version = "0.68.1" +version = "0.70.0" dependencies = [ "itoa", "oxc_allocator", @@ -2276,7 +2247,7 @@ dependencies = [ [[package]] name = "oxlint" -version = "0.16.10" +version = "0.16.11" dependencies = [ "bpaf", "cow-utils", @@ -2316,7 +2287,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2392,6 +2363,16 @@ dependencies = [ "phf_shared", ] +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + [[package]] name = "phf_generator" version = "0.11.3" @@ -2457,6 +2438,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2653,9 +2643,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.26" +version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "log", "once_cell", @@ -2677,15 +2667,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "ring", "rustls-pki-types", @@ -3015,12 +3008,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.19.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "rustix", "windows-sys 0.59.0", @@ -3100,9 +3093,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -3110,9 +3103,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" dependencies = [ "backtrace", "pin-project-lite", @@ -3317,7 +3310,7 @@ dependencies = [ "serde_json", "ureq-proto", "utf-8", - "webpki-roots", + "webpki-roots 0.26.11", ] [[package]] @@ -3349,12 +3342,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -3367,7 +3354,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] @@ -3415,9 +3402,18 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.10" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37493cadf42a2a939ed404698ded7fb378bf301b5011f973361779a3a74f8c93" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.0", +] + +[[package]] +name = "webpki-roots" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" dependencies = [ "rustls-pki-types", ] @@ -3480,7 +3476,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3489,7 +3485,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3498,14 +3494,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -3514,48 +3526,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -3565,23 +3625,17 @@ dependencies = [ "bitflags 2.9.0", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -3591,9 +3645,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -3628,11 +3682,22 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -3641,9 +3706,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 8a43c7ad5ca90..1306a2851b596 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,31 +104,31 @@ doc_lazy_continuation = "allow" # FIXME [workspace.dependencies] # publish = true -oxc = { version = "0.68.1", path = "crates/oxc" } -oxc_allocator = { version = "0.68.1", path = "crates/oxc_allocator" } -oxc_ast = { version = "0.68.1", path = "crates/oxc_ast" } -oxc_ast_macros = { version = "0.68.1", path = "crates/oxc_ast_macros" } -oxc_ast_visit = { version = "0.68.1", path = "crates/oxc_ast_visit" } -oxc_cfg = { version = "0.68.1", path = "crates/oxc_cfg" } -oxc_codegen = { version = "0.68.1", path = "crates/oxc_codegen" } -oxc_data_structures = { version = "0.68.1", path = "crates/oxc_data_structures" } -oxc_diagnostics = { version = "0.68.1", path = "crates/oxc_diagnostics" } -oxc_ecmascript = { version = "0.68.1", path = "crates/oxc_ecmascript" } -oxc_estree = { version = "0.68.1", path = "crates/oxc_estree" } -oxc_isolated_declarations = { version = "0.68.1", path = "crates/oxc_isolated_declarations" } -oxc_mangler = { version = "0.68.1", path = "crates/oxc_mangler" } -oxc_minifier = { version = "0.68.1", path = "crates/oxc_minifier" } -oxc_napi = { version = "0.68.1", path = "crates/oxc_napi" } -oxc_parser = { version = "0.68.1", path = "crates/oxc_parser", features = ["regular_expression"] } -oxc_parser_napi = { version = "0.68.1", path = "napi/parser" } -oxc_regular_expression = { version = "0.68.1", path = "crates/oxc_regular_expression" } -oxc_semantic = { version = "0.68.1", path = "crates/oxc_semantic" } -oxc_span = { version = "0.68.1", path = "crates/oxc_span" } -oxc_syntax = { version = "0.68.1", path = "crates/oxc_syntax" } -oxc_transform_napi = { version = "0.68.1", path = "napi/transform" } -oxc_transformer = { version = "0.68.1", path = "crates/oxc_transformer" } -oxc_transformer_plugins = { version = "0.68.1", path = "crates/oxc_transformer_plugins" } -oxc_traverse = { version = "0.68.1", path = "crates/oxc_traverse" } +oxc = { version = "0.70.0", path = "crates/oxc" } +oxc_allocator = { version = "0.70.0", path = "crates/oxc_allocator" } +oxc_ast = { version = "0.70.0", path = "crates/oxc_ast" } +oxc_ast_macros = { version = "0.70.0", path = "crates/oxc_ast_macros" } +oxc_ast_visit = { version = "0.70.0", path = "crates/oxc_ast_visit" } +oxc_cfg = { version = "0.70.0", path = "crates/oxc_cfg" } +oxc_codegen = { version = "0.70.0", path = "crates/oxc_codegen" } +oxc_data_structures = { version = "0.70.0", path = "crates/oxc_data_structures" } +oxc_diagnostics = { version = "0.70.0", path = "crates/oxc_diagnostics" } +oxc_ecmascript = { version = "0.70.0", path = "crates/oxc_ecmascript" } +oxc_estree = { version = "0.70.0", path = "crates/oxc_estree" } +oxc_isolated_declarations = { version = "0.70.0", path = "crates/oxc_isolated_declarations" } +oxc_mangler = { version = "0.70.0", path = "crates/oxc_mangler" } +oxc_minifier = { version = "0.70.0", path = "crates/oxc_minifier" } +oxc_napi = { version = "0.70.0", path = "crates/oxc_napi" } +oxc_parser = { version = "0.70.0", path = "crates/oxc_parser", features = ["regular_expression"] } +oxc_parser_napi = { version = "0.70.0", path = "napi/parser" } +oxc_regular_expression = { version = "0.70.0", path = "crates/oxc_regular_expression" } +oxc_semantic = { version = "0.70.0", path = "crates/oxc_semantic" } +oxc_span = { version = "0.70.0", path = "crates/oxc_span" } +oxc_syntax = { version = "0.70.0", path = "crates/oxc_syntax" } +oxc_transform_napi = { version = "0.70.0", path = "napi/transform" } +oxc_transformer = { version = "0.70.0", path = "crates/oxc_transformer" } +oxc_transformer_plugins = { version = "0.70.0", path = "crates/oxc_transformer_plugins" } +oxc_traverse = { version = "0.70.0", path = "crates/oxc_traverse" } # publish = false oxc_formatter = { path = "crates/oxc_formatter" } @@ -155,11 +155,11 @@ unicode-id-start = "1" # oxc-browserslist = "2" oxc_index = "3" -oxc_resolver = "8" +oxc_resolver = "9" oxc_sourcemap = "3" # -allocator-api2 = "0.2.21" +allocator-api2 = "=0.2.21" base64 = "0.22.1" bitflags = "2.9.0" bpaf = "0.9.19" @@ -183,28 +183,29 @@ flate2 = "1.1.1" futures = "0.3.31" globset = "0.4.16" handlebars = "6.3.2" -hashbrown = { version = "0.15.2", default-features = false } +hashbrown = { version = "0.15.3", default-features = false } humansize = "2.1.3" ignore = "0.4.23" -insta = "1.42.2" +insta = "1.43.1" itertools = "0.14.0" itoa = "1.0.15" -javascript-globals = "1.0.0" +javascript-globals = "1.1.0" json-strip-comments = "1.0.4" language-tags = "0.3.2" lazy-regex = "3.4.1" lazy_static = "1.5.0" log = "0.4.27" -markdown = "1.0.0-alpha.23" +markdown = "1.0.0" memchr = "2.7.4" miette = { package = "oxc-miette", version = "2.2.1", features = ["fancy-no-syscall"] } -mimalloc-safe = "0.1.50" +mimalloc-safe = "0.1.52" nonmax = "0.5.5" num-bigint = "0.4.6" num-traits = "0.2.19" papaya = "0.2.1" -petgraph = { version = "0.8.0", default-features = false } +petgraph = { version = "0.8.1", default-features = false } phf = "0.11.3" +phf_codegen = "0.11.3" pico-args = "0.5.0" prettyplease = "0.2.32" project-root = "0.2.2" @@ -214,18 +215,18 @@ rust-lapper = "1.1.0" ryu-js = "1.0.2" saphyr = "0.0.4" schemars = "0.8.22" -self_cell = "1.1.0" +self_cell = "1.2.0" seq-macro = "0.3.6" sha1 = "0.10.6" simdutf8 = { version = "0.1.5", features = ["aarch64_neon"] } similar = "2.7.0" similar-asserts = "1.7.0" smallvec = "1.15.0" -tempfile = "3.19.1" -tokio = { version = "1.44.2", default-features = false } +tempfile = "3.20.0" +tokio = { version = "1.45.0", default-features = false } tower-lsp-server = "0.21.1" tracing-subscriber = "0.3.19" -ureq = { version = "3.0.10", default-features = false } +ureq = { version = "3.0.11", default-features = false } walkdir = "2.5.0" [workspace.metadata.cargo-shear] diff --git a/apps/oxlint/CHANGELOG.md b/apps/oxlint/CHANGELOG.md index 788358aefe5fd..a128c3cf52038 100644 --- a/apps/oxlint/CHANGELOG.md +++ b/apps/oxlint/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/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.16.11] - 2025-05-16 + +- 4e5c73b span: [**BREAKING**] `SourceType::from_path(".js")` return js instead of jsx (#11038) (Boshen) + +### Features + +- 466c24a linter: Add gitlab reporter output format (#10927) (Connor Pearson) + +### Bug Fixes + +- c52a9ba linter: Fix plugins inside overrides not being applied (#11057) (camc314) +- b12bd48 linter: Fix rule config not being correctly applied (#11055) (camc314) +- 0961296 linter: Add `gitlab` to linter `--help` docs (#10932) (camc314) +- 584d8b9 napi: Enable mimalloc `no_opt_arch` feature on linux aarch64 (#11053) (Boshen) + +### Refactor + +- bb999a3 language_server: Avoid cloning linter by taking reference in LintService (#10907) (Ulrich Stark) + ## [0.16.10] - 2025-05-09 ### Features diff --git a/apps/oxlint/Cargo.toml b/apps/oxlint/Cargo.toml index 91f2a2db9d13e..4870e61bb6c0e 100644 --- a/apps/oxlint/Cargo.toml +++ b/apps/oxlint/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxlint" -version = "0.16.10" +version = "0.16.11" authors.workspace = true categories.workspace = true edition.workspace = true @@ -40,12 +40,15 @@ serde_json = { workspace = true } tempfile = { workspace = true } tracing-subscriber = { workspace = true, features = [] } # Omit the `regex` feature -[target.'cfg(all(not(target_os = "linux"), not(target_os = "freebsd"), not(target_family = "wasm")))'.dependencies] +[target.'cfg(not(any(target_os = "linux", target_os = "freebsd", target_arch = "arm", target_family = "wasm")))'.dependencies] mimalloc-safe = { workspace = true, optional = true, features = ["skip_collect_on_exit"] } -[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies] +[target.'cfg(all(target_os = "linux", not(target_arch = "arm"), not(target_arch = "aarch64")))'.dependencies] mimalloc-safe = { workspace = true, optional = true, features = ["skip_collect_on_exit", "local_dynamic_tls"] } +[target.'cfg(all(target_os = "linux", target_arch = "aarch64"))'.dependencies] +mimalloc-safe = { workspace = true, optional = true, features = ["skip_collect_on_exit", "local_dynamic_tls", "no_opt_arch"] } + [dev-dependencies] insta = { workspace = true } lazy-regex = { workspace = true } diff --git a/apps/oxlint/fixtures/issue_11054/.oxlintrc.json b/apps/oxlint/fixtures/issue_11054/.oxlintrc.json new file mode 100644 index 0000000000000..933355aaa4b99 --- /dev/null +++ b/apps/oxlint/fixtures/issue_11054/.oxlintrc.json @@ -0,0 +1,12 @@ +{ + "rules": { + "no-unused-vars": [ + 2, + { + "args": "none", + "caughtErrors": "none", + "varsIgnorePattern": "^h|React|createElement|Fragment$" + } + ] + } +} diff --git a/apps/oxlint/fixtures/issue_11054/index.js b/apps/oxlint/fixtures/issue_11054/index.js new file mode 100644 index 0000000000000..24624c8ef0d1d --- /dev/null +++ b/apps/oxlint/fixtures/issue_11054/index.js @@ -0,0 +1 @@ +import { createElement } from "preact"; diff --git a/apps/oxlint/fixtures/linter/js_as_jsx.js b/apps/oxlint/fixtures/linter/js_as_jsx.js new file mode 100644 index 0000000000000..e130e46d3f36f --- /dev/null +++ b/apps/oxlint/fixtures/linter/js_as_jsx.js @@ -0,0 +1,2 @@ +debugger; +
// Should `.js` file pass as `.jsx`. diff --git a/apps/oxlint/fixtures/overrides_with_plugin/.oxlintrc.json b/apps/oxlint/fixtures/overrides_with_plugin/.oxlintrc.json new file mode 100644 index 0000000000000..febf7a7f2bbe2 --- /dev/null +++ b/apps/oxlint/fixtures/overrides_with_plugin/.oxlintrc.json @@ -0,0 +1,13 @@ +{ + "overrides": [ + { + "files": ["*.test.ts"], + "plugins": ["jest", "vitest"], + "rules": { + "jest/valid-title": "deny", + "no-unused-vars": "off" + } + } + ] + } + \ No newline at end of file diff --git a/apps/oxlint/fixtures/overrides_with_plugin/index.test.ts b/apps/oxlint/fixtures/overrides_with_plugin/index.test.ts new file mode 100644 index 0000000000000..b8a63407093bd --- /dev/null +++ b/apps/oxlint/fixtures/overrides_with_plugin/index.test.ts @@ -0,0 +1,7 @@ +describe("", () => { + // + + it("", () => {}); +}); + +const foo = 123; diff --git a/apps/oxlint/fixtures/overrides_with_plugin/index.ts b/apps/oxlint/fixtures/overrides_with_plugin/index.ts new file mode 100644 index 0000000000000..472e4584238a4 --- /dev/null +++ b/apps/oxlint/fixtures/overrides_with_plugin/index.ts @@ -0,0 +1 @@ +const foo = 123; diff --git a/apps/oxlint/src/command/lint.rs b/apps/oxlint/src/command/lint.rs index fcb9c285fc3c2..180f051a851be 100644 --- a/apps/oxlint/src/command/lint.rs +++ b/apps/oxlint/src/command/lint.rs @@ -194,7 +194,7 @@ pub struct WarningOptions { #[derive(Debug, Clone, Bpaf)] pub struct OutputOptions { /// Use a specific output format. Possible values: - /// `checkstyle`, `default`, `github`, `json`, `junit`, `stylish`, `unix` + /// `checkstyle`, `default`, `github`, `gitlab`, `json`, `junit`, `stylish`, `unix` #[bpaf(long, short, fallback(OutputFormat::Default), hide_usage)] pub format: OutputFormat, } diff --git a/apps/oxlint/src/lint.rs b/apps/oxlint/src/lint.rs index 3fea36e452f3e..827b55bfd9709 100644 --- a/apps/oxlint/src/lint.rs +++ b/apps/oxlint/src/lint.rs @@ -293,18 +293,16 @@ impl Runner for LintRunner { } } - let mut lint_service = LintService::new(linter, options); let mut diagnostic_service = Self::get_diagnostic_service(&output_formatter, &warning_options, &misc_options); + let tx_error = diagnostic_service.sender().clone(); - let number_of_rules = lint_service.linter().number_of_rules(); + let number_of_rules = linter.number_of_rules(); // Spawn linting in another thread so diagnostics can be printed immediately from diagnostic_service.run. - rayon::spawn({ - let tx_error = diagnostic_service.sender().clone(); - move || { - lint_service.run(&tx_error); - } + rayon::spawn(move || { + let mut lint_service = LintService::new(&linter, options); + lint_service.run(&tx_error); }); let diagnostic_result = diagnostic_service.run(stdout); @@ -780,6 +778,12 @@ mod test { Tester::new().test_and_snapshot(args); } + #[test] + fn js_and_jsx() { + let args = &["fixtures/linter/js_as_jsx.js"]; + Tester::new().test_and_snapshot(args); + } + #[test] fn lint_vue_file() { let args = &["fixtures/vue/debugger.vue"]; @@ -1155,4 +1159,16 @@ mod test { let args = &["--import-plugin", "-D", "import/no-cycle"]; Tester::new().with_cwd("fixtures/import-cycle".into()).test_and_snapshot(args); } + + #[test] + fn test_rule_config_being_enabled_correctly() { + let args = &["-c", ".oxlintrc.json"]; + Tester::new().with_cwd("fixtures/issue_11054".into()).test_and_snapshot(args); + } + + #[test] + fn test_plugins_in_overrides_enabled_correctly() { + let args = &["-c", ".oxlintrc.json"]; + Tester::new().with_cwd("fixtures/overrides_with_plugin".into()).test_and_snapshot(args); + } } diff --git a/apps/oxlint/src/output_formatter/gitlab.rs b/apps/oxlint/src/output_formatter/gitlab.rs new file mode 100644 index 0000000000000..aba91dc3783a2 --- /dev/null +++ b/apps/oxlint/src/output_formatter/gitlab.rs @@ -0,0 +1,129 @@ +use oxc_diagnostics::{ + Error, Severity, + reporter::{DiagnosticReporter, DiagnosticResult, Info}, +}; + +use std::hash::{DefaultHasher, Hash, Hasher}; + +use crate::output_formatter::InternalFormatter; + +#[derive(Debug, Default)] +pub struct GitlabOutputFormatter; + +#[derive(Debug, serde::Serialize)] +struct GitlabErrorLocationLinesJson { + begin: usize, + end: usize, +} + +#[derive(Debug, serde::Serialize)] +struct GitlabErrorLocationJson { + path: String, + lines: GitlabErrorLocationLinesJson, +} + +#[derive(Debug, serde::Serialize)] +struct GitlabErrorJson { + description: String, + check_name: String, + fingerprint: String, + severity: String, + location: GitlabErrorLocationJson, +} + +impl InternalFormatter for GitlabOutputFormatter { + fn get_diagnostic_reporter(&self) -> Box { + Box::new(GitlabReporter::default()) + } +} + +/// Renders reports as a Gitlab Code Quality Report +/// +/// +/// +/// Note that, due to syntactic restrictions of JSON arrays, this reporter waits until all +/// diagnostics have been reported before writing them to the output stream. +#[derive(Default)] +struct GitlabReporter { + diagnostics: Vec, +} + +impl DiagnosticReporter for GitlabReporter { + fn finish(&mut self, _: &DiagnosticResult) -> Option { + Some(format_gitlab(&mut self.diagnostics)) + } + + fn render_error(&mut self, error: Error) -> Option { + self.diagnostics.push(error); + None + } +} + +fn format_gitlab(diagnostics: &mut Vec) -> String { + let errors = diagnostics.drain(..).map(|error| { + let Info { start, end, filename, message, severity, rule_id } = Info::new(&error); + let severity = match severity { + Severity::Error => "critical".to_string(), + Severity::Warning => "major".to_string(), + Severity::Advice => "minor".to_string(), + }; + + let fingerprint = { + let mut hasher = DefaultHasher::new(); + start.line.hash(&mut hasher); + end.line.hash(&mut hasher); + filename.hash(&mut hasher); + message.hash(&mut hasher); + severity.hash(&mut hasher); + + format!("{:x}", hasher.finish()) + }; + + GitlabErrorJson { + description: message, + check_name: rule_id.unwrap_or_default(), + location: GitlabErrorLocationJson { + path: filename, + lines: GitlabErrorLocationLinesJson { begin: start.line, end: end.line }, + }, + fingerprint, + severity, + } + }); + + serde_json::to_string_pretty(&errors.collect::>()).expect("Failed to serialize") +} + +#[cfg(test)] +mod test { + use oxc_diagnostics::{ + NamedSource, OxcDiagnostic, + reporter::{DiagnosticReporter, DiagnosticResult}, + }; + use oxc_span::Span; + + use super::GitlabReporter; + + #[test] + fn reporter() { + let mut reporter = GitlabReporter::default(); + + let error = OxcDiagnostic::warn("error message") + .with_label(Span::new(0, 8)) + .with_source_code(NamedSource::new("file://test.ts", "debugger;")); + + let first_result = reporter.render_error(error); + + // reporter keeps it in memory + assert!(first_result.is_none()); + + // reporter gives results when finishing + let second_result = reporter.finish(&DiagnosticResult::default()); + + assert!(second_result.is_some()); + assert_eq!( + second_result.unwrap(), + "[\n {\n \"description\": \"error message\",\n \"check_name\": \"\",\n \"fingerprint\": \"8b23bd85b148d3\",\n \"severity\": \"major\",\n \"location\": {\n \"path\": \"file://test.ts\",\n \"lines\": {\n \"begin\": 1,\n \"end\": 1\n }\n }\n }\n]" + ); + } +} diff --git a/apps/oxlint/src/output_formatter/mod.rs b/apps/oxlint/src/output_formatter/mod.rs index a502f45efac6a..39a922fae62ea 100644 --- a/apps/oxlint/src/output_formatter/mod.rs +++ b/apps/oxlint/src/output_formatter/mod.rs @@ -1,6 +1,7 @@ mod checkstyle; mod default; mod github; +mod gitlab; mod json; mod junit; mod stylish; @@ -12,6 +13,7 @@ use std::time::Duration; use checkstyle::CheckStyleOutputFormatter; use github::GithubOutputFormatter; +use gitlab::GitlabOutputFormatter; use junit::JUnitOutputFormatter; use stylish::StylishOutputFormatter; use unix::UnixOutputFormatter; @@ -26,6 +28,7 @@ pub enum OutputFormat { /// GitHub Check Annotation /// Github, + Gitlab, Json, Unix, Checkstyle, @@ -43,6 +46,7 @@ impl FromStr for OutputFormat { "unix" => Ok(Self::Unix), "checkstyle" => Ok(Self::Checkstyle), "github" => Ok(Self::Github), + "gitlab" => Ok(Self::Gitlab), "stylish" => Ok(Self::Stylish), "junit" => Ok(Self::JUnit), _ => Err(format!("'{s}' is not a known format")), @@ -96,6 +100,7 @@ impl OutputFormatter { OutputFormat::Json => Box::::default(), OutputFormat::Checkstyle => Box::::default(), OutputFormat::Github => Box::new(GithubOutputFormatter), + OutputFormat::Gitlab => Box::::default(), OutputFormat::Unix => Box::::default(), OutputFormat::Default => Box::new(DefaultOutputFormatter), OutputFormat::Stylish => Box::::default(), diff --git a/apps/oxlint/src/snapshots/_-A all fixtures__linter@oxlint.snap b/apps/oxlint/src/snapshots/_-A all fixtures__linter@oxlint.snap index fe6cd3391cc4d..5b9d90b549c4c 100644 --- a/apps/oxlint/src/snapshots/_-A all fixtures__linter@oxlint.snap +++ b/apps/oxlint/src/snapshots/_-A all fixtures__linter@oxlint.snap @@ -6,7 +6,7 @@ arguments: -A all fixtures/linter working directory: ---------- Found 0 warnings and 0 errors. -Finished in ms on 3 files with 0 rules using 1 threads. +Finished in ms on 4 files with 0 rules using 1 threads. ---------- CLI result: LintSucceeded ---------- diff --git a/apps/oxlint/src/snapshots/_fixtures__linter@oxlint.snap b/apps/oxlint/src/snapshots/_fixtures__linter@oxlint.snap index ba8feb6dd0a38..45cae04933d7d 100644 --- a/apps/oxlint/src/snapshots/_fixtures__linter@oxlint.snap +++ b/apps/oxlint/src/snapshots/_fixtures__linter@oxlint.snap @@ -20,6 +20,14 @@ working directory: `---- help: Remove the debugger statement + ! ]8;;https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger.html\eslint(no-debugger)]8;;\: `debugger` statement is not allowed + ,-[fixtures/linter/js_as_jsx.js:1:1] + 1 | debugger; + : ^^^^^^^^^ + 2 |
// Should `.js` file pass as `.jsx`. + `---- + help: Remove the debugger statement + ! ]8;;https://oxc.rs/docs/guide/usage/linter/rules/eslint/use-isnan.html\eslint(use-isnan)]8;;\: Requires calls to isNaN() when checking for NaN ,-[fixtures/linter/nan.js:1:8] 1 | 123 == NaN; @@ -27,8 +35,8 @@ working directory: `---- help: Use the isNaN function to compare with NaN. -Found 3 warnings and 0 errors. -Finished in ms on 3 files with 101 rules using 1 threads. +Found 4 warnings and 0 errors. +Finished in ms on 4 files with 101 rules using 1 threads. ---------- CLI result: LintSucceeded ---------- diff --git a/apps/oxlint/src/snapshots/_fixtures__linter__js_and_jsx.js@oxlint.snap b/apps/oxlint/src/snapshots/_fixtures__linter__js_and_jsx.js@oxlint.snap new file mode 100644 index 0000000000000..dab8f41f46802 --- /dev/null +++ b/apps/oxlint/src/snapshots/_fixtures__linter__js_and_jsx.js@oxlint.snap @@ -0,0 +1,12 @@ +--- +source: apps/oxlint/src/tester.rs +--- +########## +arguments: fixtures/linter/js_and_jsx.js +working directory: +---------- +Found 0 warnings and 0 errors. +Finished in ms on 0 files with 101 rules using 1 threads. +---------- +CLI result: LintSucceeded +---------- diff --git a/apps/oxlint/src/snapshots/_fixtures__linter__js_as_jsx.js@oxlint.snap b/apps/oxlint/src/snapshots/_fixtures__linter__js_as_jsx.js@oxlint.snap new file mode 100644 index 0000000000000..185506f6ae6da --- /dev/null +++ b/apps/oxlint/src/snapshots/_fixtures__linter__js_as_jsx.js@oxlint.snap @@ -0,0 +1,21 @@ +--- +source: apps/oxlint/src/tester.rs +--- +########## +arguments: fixtures/linter/js_as_jsx.js +working directory: +---------- + + ! ]8;;https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger.html\eslint(no-debugger)]8;;\: `debugger` statement is not allowed + ,-[fixtures/linter/js_as_jsx.js:1:1] + 1 | debugger; + : ^^^^^^^^^ + 2 |
// Should `.js` file pass as `.jsx`. + `---- + help: Remove the debugger statement + +Found 1 warning and 0 errors. +Finished in ms on 1 file with 101 rules using 1 threads. +---------- +CLI result: LintSucceeded +---------- diff --git a/apps/oxlint/src/snapshots/fixtures__issue_11054_-c .oxlintrc.json@oxlint.snap b/apps/oxlint/src/snapshots/fixtures__issue_11054_-c .oxlintrc.json@oxlint.snap new file mode 100644 index 0000000000000..3621a4b8c0e1f --- /dev/null +++ b/apps/oxlint/src/snapshots/fixtures__issue_11054_-c .oxlintrc.json@oxlint.snap @@ -0,0 +1,12 @@ +--- +source: apps/oxlint/src/tester.rs +--- +########## +arguments: -c .oxlintrc.json +working directory: fixtures/issue_11054 +---------- +Found 0 warnings and 0 errors. +Finished in ms on 1 file with 101 rules using 1 threads. +---------- +CLI result: LintSucceeded +---------- diff --git a/apps/oxlint/src/snapshots/fixtures__overrides_with_plugin_-c .oxlintrc.json@oxlint.snap b/apps/oxlint/src/snapshots/fixtures__overrides_with_plugin_-c .oxlintrc.json@oxlint.snap new file mode 100644 index 0000000000000..fb6df87b9015f --- /dev/null +++ b/apps/oxlint/src/snapshots/fixtures__overrides_with_plugin_-c .oxlintrc.json@oxlint.snap @@ -0,0 +1,38 @@ +--- +source: apps/oxlint/src/tester.rs +--- +########## +arguments: -c .oxlintrc.json +working directory: fixtures/overrides_with_plugin +---------- + + x ]8;;https://oxc.rs/docs/guide/usage/linter/rules/jest/valid-title.html\eslint-plugin-jest(valid-title)]8;;\: "Should not have an empty title" + ,-[index.test.ts:1:10] + 1 | describe("", () => { + : ^^ + 2 | // + `---- + help: "Write a meaningful title for your test" + + ! ]8;;https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unused-vars.html\eslint(no-unused-vars)]8;;\: Variable 'foo' is declared but never used. Unused variables should start with a '_'. + ,-[index.ts:1:7] + 1 | const foo = 123; + : ^|^ + : `-- 'foo' is declared here + `---- + help: Consider removing this declaration. + + x ]8;;https://oxc.rs/docs/guide/usage/linter/rules/jest/valid-title.html\eslint-plugin-jest(valid-title)]8;;\: "Should not have an empty title" + ,-[index.test.ts:4:6] + 3 | + 4 | it("", () => {}); + : ^^ + 5 | }); + `---- + help: "Write a meaningful title for your test" + +Found 1 warning and 2 errors. +Finished in ms on 2 files with 101 rules using 1 threads. +---------- +CLI result: LintFoundErrors +---------- diff --git a/crates/oxc/CHANGELOG.md b/crates/oxc/CHANGELOG.md index 74b77299cb505..1f228a7d00089 100644 --- a/crates/oxc/CHANGELOG.md +++ b/crates/oxc/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/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.70.0] - 2025-05-15 + +### Testing + +- a05361e ast/estree: Check span offsets are converted in ascending order in ESTree conformance tests (#10887) (overlookmotel) + ## [0.68.0] - 2025-05-03 - 315143a codegen: [**BREAKING**] Remove useless `CodeGenerator` type alias (#10702) (Boshen) diff --git a/crates/oxc/Cargo.toml b/crates/oxc/Cargo.toml index 18f2c30cb0d70..699a6c316adef 100644 --- a/crates/oxc/Cargo.toml +++ b/crates/oxc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc" -version = "0.68.1" +version = "0.70.0" authors.workspace = true categories.workspace = true edition.workspace = true @@ -77,5 +77,8 @@ serialize = [ "oxc_syntax/serialize", ] +# Only for conformance tests +conformance = ["oxc_ast_visit/conformance"] + [package.metadata.docs.rs] all-features = true diff --git a/crates/oxc_allocator/CHANGELOG.md b/crates/oxc_allocator/CHANGELOG.md index 2f9e7473bc4b7..57198a89dc74b 100644 --- a/crates/oxc_allocator/CHANGELOG.md +++ b/crates/oxc_allocator/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/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.69.0] - 2025-05-09 + +### Bug Fixes + +- 446d9b3 allocator: `Allocator::from_raw_parts` do not use const assertion for endianness test (#10888) (overlookmotel) + +### Refactor + +- b16331e ast/estree: Generalize concatenating fields with `Concat2` (#10848) (overlookmotel) + ## [0.68.0] - 2025-05-03 ### Features diff --git a/crates/oxc_allocator/Cargo.toml b/crates/oxc_allocator/Cargo.toml index b7ad7c65020fc..3da1d6daea3a7 100644 --- a/crates/oxc_allocator/Cargo.toml +++ b/crates/oxc_allocator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_allocator" -version = "0.68.1" +version = "0.70.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_allocator/src/string.rs b/crates/oxc_allocator/src/string.rs index 336b3ae62c1b9..e3c728cba8ebf 100644 --- a/crates/oxc_allocator/src/string.rs +++ b/crates/oxc_allocator/src/string.rs @@ -63,7 +63,6 @@ impl<'alloc> String<'alloc> { /// If the given capacity is `0`, no allocation will occur, and this method /// is identical to the [`new_in`] method. /// - /// [`capacity`]: String::capacity /// [`new_in`]: String::new_in #[inline(always)] pub fn with_capacity_in(capacity: usize, allocator: &'alloc Allocator) -> String<'alloc> { diff --git a/crates/oxc_allocator/src/vec.rs b/crates/oxc_allocator/src/vec.rs index 00d05057dd315..e2a47b3caffa5 100644 --- a/crates/oxc_allocator/src/vec.rs +++ b/crates/oxc_allocator/src/vec.rs @@ -78,7 +78,7 @@ impl<'alloc, T> Vec<'alloc, T> { /// minimum *capacity* specified, the vector will have a zero *length*. /// /// For `Vec` where `T` is a zero-sized type, there will be no allocation - /// and the capacity will always be `usize::MAX`. + /// and the capacity will always be `u32::MAX`. /// /// # Panics /// diff --git a/crates/oxc_allocator/src/vec2/mod.rs b/crates/oxc_allocator/src/vec2/mod.rs index 254c9291a549a..f8faedbe0fbef 100644 --- a/crates/oxc_allocator/src/vec2/mod.rs +++ b/crates/oxc_allocator/src/vec2/mod.rs @@ -94,7 +94,6 @@ clippy::semicolon_if_nothing_returned, clippy::needless_pass_by_ref_mut, clippy::needless_for_each, - clippy::needless_lifetimes, clippy::cloned_instead_of_copied, clippy::checked_conversions, clippy::legacy_numeric_constants, @@ -541,9 +540,9 @@ macro_rules! vec { /// [`insert`]: struct.Vec.html#method.insert /// [`reserve`]: struct.Vec.html#method.reserve /// [owned slice]: https://doc.rust-lang.org/std/boxed/struct.Box.html +#[repr(transparent)] pub struct Vec<'bump, T: 'bump> { buf: RawVec<'bump, T>, - len: usize, } //////////////////////////////////////////////////////////////////////////////// @@ -566,7 +565,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// ``` #[inline] pub fn new_in(bump: &'bump Bump) -> Vec<'bump, T> { - Vec { buf: RawVec::new_in(bump), len: 0 } + Vec { buf: RawVec::new_in(bump) } } /// Constructs a new, empty `Vec<'bump, T>` with the specified capacity. @@ -603,7 +602,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// ``` #[inline] pub fn with_capacity_in(capacity: usize, bump: &'bump Bump) -> Vec<'bump, T> { - Vec { buf: RawVec::with_capacity_in(capacity, bump), len: 0 } + Vec { buf: RawVec::with_capacity_in(capacity, bump) } } /// Construct a new `Vec` from the given iterator's items. @@ -687,7 +686,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { capacity: usize, bump: &'bump Bump, ) -> Vec<'bump, T> { - Vec { buf: RawVec::from_raw_parts_in(ptr, capacity, bump), len: length } + Vec { buf: RawVec::from_raw_parts_in(ptr, bump, capacity, length) } } /// Returns a shared reference to the allocator backing this `Vec`. @@ -724,7 +723,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// ``` #[inline] pub fn capacity(&self) -> usize { - self.buf.cap() + self.buf.cap_usize() } /// Reserves capacity for at least `additional` more elements to be inserted @@ -735,7 +734,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// /// # Panics /// - /// Panics if the new capacity overflows `usize`. + /// Panics if the new capacity overflows `u32`. /// /// # Examples /// @@ -748,7 +747,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// assert!(vec.capacity() >= 11); /// ``` pub fn reserve(&mut self, additional: usize) { - self.buf.reserve(self.len, additional); + self.buf.reserve(self.buf.len, additional); } /// Reserves the minimum capacity for exactly `additional` more elements to @@ -762,7 +761,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// /// # Panics /// - /// Panics if the new capacity overflows `usize`. + /// Panics if the new capacity overflows `u32`. /// /// # Examples /// @@ -775,7 +774,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// assert!(vec.capacity() >= 11); /// ``` pub fn reserve_exact(&mut self, additional: usize) { - self.buf.reserve_exact(self.len, additional); + self.buf.reserve_exact(self.buf.len, additional); } /// Attempts to reserve capacity for at least `additional` more elements to be inserted @@ -786,7 +785,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// /// # Panics /// - /// Panics if the new capacity overflows `usize`. + /// Panics if the new capacity overflows `u32`. /// /// # Examples /// @@ -799,7 +798,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// assert!(vec.capacity() >= 11); /// ``` pub fn try_reserve(&mut self, additional: usize) -> Result<(), CollectionAllocErr> { - self.buf.try_reserve(self.len, additional) + self.buf.try_reserve(self.buf.len, additional) } /// Attempts to reserve the minimum capacity for exactly `additional` more elements to @@ -813,7 +812,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// /// # Panics /// - /// Panics if the new capacity overflows `usize`. + /// Panics if the new capacity overflows `u32`. /// /// # Examples /// @@ -826,7 +825,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// assert!(vec.capacity() >= 11); /// ``` pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), CollectionAllocErr> { - self.buf.try_reserve_exact(self.len, additional) + self.buf.try_reserve_exact(self.buf.len, additional) } /// Shrinks the capacity of the vector as much as possible. @@ -848,8 +847,8 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// assert!(vec.capacity() >= 3); /// ``` pub fn shrink_to_fit(&mut self) { - if self.capacity() != self.len { - self.buf.shrink_to_fit(self.len); + if self.buf.cap() != self.buf.len { + self.buf.shrink_to_fit(self.buf.len); } } @@ -955,14 +954,14 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// [`clear`]: #method.clear /// [`drain`]: #method.drain pub fn truncate(&mut self, len: usize) { - let current_len = self.len; + let current_len = self.buf.len_usize(); unsafe { - let mut ptr = self.as_mut_ptr().add(self.len); + let mut ptr = self.as_mut_ptr().add(current_len); // Set the final length at the end, keeping in mind that // dropping an element might panic. Works around a missed // optimization, as seen in the following issue: // https://github.com/rust-lang/rust/issues/51802 - let mut local_len = SetLenOnDrop::new(&mut self.len); + let mut local_len = SetLenOnDrop::new(&mut self.buf.len); // drop any extra elements for _ in len..current_len { @@ -1105,6 +1104,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// /// # Safety /// + /// - `new_len` must be less than or equal to `u32::MAX`. /// - `new_len` must be less than or equal to [`capacity()`]. /// - The elements at `old_len..new_len` must be initialized. /// @@ -1169,8 +1169,12 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// assert_eq!(b"aaaa", &*vec); /// ``` #[inline] + #[expect(clippy::cast_possible_truncation)] pub unsafe fn set_len(&mut self, new_len: usize) { - self.len = new_len; + // `new_len as u32` is safe because caller must ensure that + // `new_len` is less than or equal to `u32::MAX`, otherwise + // it is UB. + self.buf.len = new_len as u32; } /// Removes an element from the vector and returns it. @@ -1205,8 +1209,8 @@ impl<'bump, T: 'bump> Vec<'bump, T> { // bounds check on hole succeeds there must be a last element (which // can be self[index] itself). let hole: *mut T = &mut self[index]; - let last = ptr::read(self.get_unchecked(self.len - 1)); - self.len -= 1; + let last = ptr::read(self.get_unchecked(self.buf.len_usize() - 1)); + self.buf.len -= 1; ptr::replace(hole, last) } } @@ -1232,11 +1236,11 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// assert_eq!(vec, [1, 4, 2, 3, 5]); /// ``` pub fn insert(&mut self, index: usize, element: T) { - let len = self.len(); + let len = self.buf.len_usize(); assert!(index <= len); // space for the new element - if len == self.buf.cap() { + if self.buf.len == self.buf.cap() { self.buf.grow_one(); } @@ -1252,7 +1256,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { // element. ptr::write(p, element); } - self.set_len(len + 1); + self.buf.len += 1; } } @@ -1547,7 +1551,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// /// # Panics /// - /// Panics if the number of elements in the vector overflows a `usize`. + /// Panics if the number of elements in the vector overflows a `u32`. /// /// # Examples /// @@ -1564,13 +1568,13 @@ impl<'bump, T: 'bump> Vec<'bump, T> { pub fn push(&mut self, value: T) { // This will panic or abort if we would allocate > isize::MAX bytes // or if the length increment would overflow for zero-sized types. - if self.len == self.buf.cap() { + if self.buf.len == self.buf.cap() { self.buf.grow_one(); } unsafe { - let end = self.buf.ptr().add(self.len); + let end = self.buf.ptr().add(self.buf.len_usize()); ptr::write(end, value); - self.len += 1; + self.buf.len += 1; } } @@ -1592,11 +1596,11 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// ``` #[inline] pub fn pop(&mut self) -> Option { - if self.len == 0 { + if self.buf.len == 0 { None } else { unsafe { - self.len -= 1; + self.buf.len -= 1; Some(ptr::read(self.as_ptr().add(self.len()))) } } @@ -1606,7 +1610,8 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// /// # Panics /// - /// Panics if the number of elements in the vector overflows a `usize`. + /// Panics if the number of elements in the vector overflows a `u32`. + /// Panics if the length of `Self` and `other` add up to more than `u32::MAX`. /// /// # Examples /// @@ -1630,13 +1635,24 @@ impl<'bump, T: 'bump> Vec<'bump, T> { } /// Appends elements to `Self` from other buffer. + /// + /// # SAFETY + /// + /// The caller must ensure that the length of buffer passed in is less than or + /// equal to `u32::MAX`. + /// The length of the `Self` and `other` buffer add up to must be less than or + /// equal to `u32::MAX`. #[inline] + #[expect(clippy::cast_possible_truncation)] unsafe fn append_elements(&mut self, other: *const [T]) { let count = (*other).len(); self.reserve(count); let len = self.len(); ptr::copy_nonoverlapping(other as *const T, self.as_mut_ptr().add(len), count); - self.len += count; + // `unwrap_unchecked()` is okay because that caller needs to ensure that + // the length of the buffer passed in is less than or equal to `u32::MAX`, + // otherwise it is UB. + self.buf.len = self.buf.len.checked_add(count as u32).unwrap_unchecked() } /// Creates a draining iterator that removes the specified range in the vector @@ -1753,7 +1769,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// ``` #[inline] pub fn len(&self) -> usize { - self.len + self.buf.len_usize() } /// Returns `true` if the vector contains no elements. @@ -1802,7 +1818,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { pub fn split_off(&mut self, at: usize) -> Self { assert!(at <= self.len(), "`at` out of bounds"); - let other_len = self.len - at; + let other_len = self.buf.len_usize() - at; let mut other = Vec::with_capacity_in(other_len, self.buf.bump()); // Unsafely `set_len` and copy items to `other`. @@ -2069,7 +2085,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { // Use SetLenOnDrop to work around bug where compiler // may not realize the store through `ptr` through self.set_len() // don't alias. - let mut local_len = SetLenOnDrop::new(&mut self.len); + let mut local_len = SetLenOnDrop::new(&mut self.buf.len); // Write all elements except the last one for _ in 1..n { @@ -2096,28 +2112,28 @@ impl<'bump, T: 'bump> Vec<'bump, T> { // that the optimizer will see does not alias with any stores through the Vec's data // pointer. This is a workaround for alias analysis issue #32155 struct SetLenOnDrop<'a> { - len: &'a mut usize, - local_len: usize, + len: &'a mut u32, + local_len: u32, } impl<'a> SetLenOnDrop<'a> { #[inline] - fn new(len: &'a mut usize) -> Self { + fn new(len: &'a mut u32) -> Self { SetLenOnDrop { local_len: *len, len } } #[inline] - fn increment_len(&mut self, increment: usize) { + fn increment_len(&mut self, increment: u32) { self.local_len += increment; } #[inline] - fn decrement_len(&mut self, decrement: usize) { + fn decrement_len(&mut self, decrement: u32) { self.local_len -= decrement; } } -impl<'a> Drop for SetLenOnDrop<'a> { +impl Drop for SetLenOnDrop<'_> { #[inline] fn drop(&mut self) { *self.len = self.local_len; @@ -2180,7 +2196,7 @@ impl<'bump, T: 'bump + Hash> Hash for Vec<'bump, T> { } } -impl<'bump, T, I> Index for Vec<'bump, T> +impl Index for Vec<'_, T> where I: ::core::slice::SliceIndex<[T]>, { @@ -2192,7 +2208,7 @@ where } } -impl<'bump, T, I> IndexMut for Vec<'bump, T> +impl IndexMut for Vec<'_, T> where I: ::core::slice::SliceIndex<[T]>, { @@ -2209,7 +2225,7 @@ impl<'bump, T: 'bump> ops::Deref for Vec<'bump, T> { unsafe { let p = self.buf.ptr(); // assume(!p.is_null()); - slice::from_raw_parts(p, self.len) + slice::from_raw_parts(p, self.buf.len_usize()) } } } @@ -2219,7 +2235,7 @@ impl<'bump, T: 'bump> ops::DerefMut for Vec<'bump, T> { unsafe { let ptr = self.buf.ptr(); // assume(!ptr.is_null()); - slice::from_raw_parts_mut(ptr, self.len) + slice::from_raw_parts_mut(ptr, self.buf.len_usize()) } } } @@ -2260,7 +2276,7 @@ impl<'bump, T: 'bump> IntoIterator for Vec<'bump, T> { } } -impl<'a, 'bump, T> IntoIterator for &'a Vec<'bump, T> { +impl<'a, T> IntoIterator for &'a Vec<'_, T> { type Item = &'a T; type IntoIter = slice::Iter<'a, T>; @@ -2269,7 +2285,7 @@ impl<'a, 'bump, T> IntoIterator for &'a Vec<'bump, T> { } } -impl<'a, 'bump, T> IntoIterator for &'a mut Vec<'bump, T> { +impl<'a, T> IntoIterator for &'a mut Vec<'_, T> { type Item = &'a mut T; type IntoIter = slice::IterMut<'a, T>; @@ -2381,7 +2397,7 @@ impl<'bump, T: 'bump> Vec<'bump, T> { /// append the entire slice at once. /// /// [`copy_from_slice`]: https://doc.rust-lang.org/std/primitive.slice.html#method.copy_from_slice -impl<'a, 'bump, T: 'a + Copy> Extend<&'a T> for Vec<'bump, T> { +impl<'a, T: 'a + Copy> Extend<&'a T> for Vec<'_, T> { fn extend>(&mut self, iter: I) { self.extend(iter.into_iter().cloned()) } @@ -2530,7 +2546,7 @@ pub struct IntoIter<'bump, T> { end: *const T, } -impl<'bump, T: fmt::Debug> fmt::Debug for IntoIter<'bump, T> { +impl fmt::Debug for IntoIter<'_, T> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("IntoIter").field(&self.as_slice()).finish() } @@ -2578,8 +2594,8 @@ impl<'bump, T: 'bump> IntoIter<'bump, T> { } } -unsafe impl<'bump, T: Send> Send for IntoIter<'bump, T> {} -unsafe impl<'bump, T: Sync> Sync for IntoIter<'bump, T> {} +unsafe impl Send for IntoIter<'_, T> {} +unsafe impl Sync for IntoIter<'_, T> {} impl<'bump, T: 'bump> Iterator for IntoIter<'bump, T> { type Item = T; @@ -2587,7 +2603,7 @@ impl<'bump, T: 'bump> Iterator for IntoIter<'bump, T> { #[inline] fn next(&mut self) -> Option { unsafe { - if self.ptr as *const _ == self.end { + if std::ptr::eq(self.ptr, self.end) { None } else if mem::size_of::() == 0 { // purposefully don't use 'ptr.offset' because for @@ -2647,7 +2663,7 @@ impl<'bump, T: 'bump> ExactSizeIterator for IntoIter<'bump, T> {} impl<'bump, T: 'bump> FusedIterator for IntoIter<'bump, T> {} -impl<'bump, T> Drop for IntoIter<'bump, T> { +impl Drop for IntoIter<'_, T> { fn drop(&mut self) { // drop all remaining elements self.for_each(drop); @@ -2673,10 +2689,10 @@ impl<'a, 'bump, T: 'a + 'bump + fmt::Debug> fmt::Debug for Drain<'a, 'bump, T> { } } -unsafe impl<'a, 'bump, T: Sync> Sync for Drain<'a, 'bump, T> {} -unsafe impl<'a, 'bump, T: Send> Send for Drain<'a, 'bump, T> {} +unsafe impl Sync for Drain<'_, '_, T> {} +unsafe impl Send for Drain<'_, '_, T> {} -impl<'a, 'bump, T> Iterator for Drain<'a, 'bump, T> { +impl Iterator for Drain<'_, '_, T> { type Item = T; #[inline] @@ -2689,14 +2705,14 @@ impl<'a, 'bump, T> Iterator for Drain<'a, 'bump, T> { } } -impl<'a, 'bump, T> DoubleEndedIterator for Drain<'a, 'bump, T> { +impl DoubleEndedIterator for Drain<'_, '_, T> { #[inline] fn next_back(&mut self) -> Option { self.iter.next_back().map(|elt| unsafe { ptr::read(elt as *const _) }) } } -impl<'a, 'bump, T> Drop for Drain<'a, 'bump, T> { +impl Drop for Drain<'_, '_, T> { fn drop(&mut self) { // exhaust self first self.for_each(drop); @@ -2718,9 +2734,9 @@ impl<'a, 'bump, T> Drop for Drain<'a, 'bump, T> { } } -impl<'a, 'bump, T> ExactSizeIterator for Drain<'a, 'bump, T> {} +impl ExactSizeIterator for Drain<'_, '_, T> {} -impl<'a, 'bump, T> FusedIterator for Drain<'a, 'bump, T> {} +impl FusedIterator for Drain<'_, '_, T> {} /// A splicing iterator for `Vec`. /// @@ -2732,7 +2748,7 @@ pub struct Splice<'a, 'bump, I: Iterator + 'a + 'bump> { replace_with: I, } -impl<'a, 'bump, I: Iterator> Iterator for Splice<'a, 'bump, I> { +impl Iterator for Splice<'_, '_, I> { type Item = I::Item; fn next(&mut self) -> Option { @@ -2744,15 +2760,15 @@ impl<'a, 'bump, I: Iterator> Iterator for Splice<'a, 'bump, I> { } } -impl<'a, 'bump, I: Iterator> DoubleEndedIterator for Splice<'a, 'bump, I> { +impl DoubleEndedIterator for Splice<'_, '_, I> { fn next_back(&mut self) -> Option { self.drain.next_back() } } -impl<'a, 'bump, I: Iterator> ExactSizeIterator for Splice<'a, 'bump, I> {} +impl ExactSizeIterator for Splice<'_, '_, I> {} -impl<'a, 'bump, I: Iterator> Drop for Splice<'a, 'bump, I> { +impl Drop for Splice<'_, '_, I> { fn drop(&mut self) { self.drain.by_ref().for_each(drop); @@ -2795,14 +2811,14 @@ impl<'a, 'bump, I: Iterator> Drop for Splice<'a, 'bump, I> { } /// Private helper methods for `Splice::drop` -impl<'a, 'bump, T> Drain<'a, 'bump, T> { +impl Drain<'_, '_, T> { /// The range from `self.vec.len` to `self.tail_start` contains elements /// that have been moved out. /// Fill that range as much as possible with new elements from the `replace_with` iterator. /// Return whether we filled the entire range. (`replace_with.next()` didn’t return `None`.) unsafe fn fill>(&mut self, replace_with: &mut I) -> bool { let vec = self.vec.as_mut(); - let range_start = vec.len; + let range_start = vec.buf.len_usize(); let range_end = self.tail_start; let range_slice = slice::from_raw_parts_mut(vec.as_mut_ptr().add(range_start), range_end - range_start); @@ -2810,7 +2826,7 @@ impl<'a, 'bump, T> Drain<'a, 'bump, T> { for place in range_slice { if let Some(new_item) = replace_with.next() { ptr::write(place, new_item); - vec.len += 1; + vec.buf.len += 1; } else { return false; } @@ -2819,10 +2835,14 @@ impl<'a, 'bump, T> Drain<'a, 'bump, T> { } /// Make room for inserting more elements before the tail. + #[expect(clippy::cast_possible_truncation)] unsafe fn move_tail(&mut self, extra_capacity: usize) { let vec = self.vec.as_mut(); let used_capacity = self.tail_start + self.tail_len; - vec.buf.reserve(used_capacity, extra_capacity); + // `used_capacity as u32` is safe because the [`Vec::drain`] method has ensured that + // `self.tail_start` is less than or equal to `self.len()`, and `self.tail_len` calculated + // from `self.len() - self.tail_start`, and `self.len()` is `u32`. + vec.buf.reserve(used_capacity as u32, extra_capacity); let new_tail_start = self.tail_start + extra_capacity; let src = vec.as_ptr().add(self.tail_start); @@ -2845,7 +2865,7 @@ where pred: F, } -impl<'a, 'bump, T, F> Iterator for DrainFilter<'a, 'bump, T, F> +impl Iterator for DrainFilter<'_, '_, T, F> where F: FnMut(&mut T) -> bool, { @@ -2879,7 +2899,7 @@ where } } -impl<'a, 'bump, T, F> Drop for DrainFilter<'a, 'bump, T, F> +impl Drop for DrainFilter<'_, '_, T, F> where F: FnMut(&mut T) -> bool, { diff --git a/crates/oxc_allocator/src/vec2/raw_vec.rs b/crates/oxc_allocator/src/vec2/raw_vec.rs index 871c8e5bd6660..8bc83dbee416b 100644 --- a/crates/oxc_allocator/src/vec2/raw_vec.rs +++ b/crates/oxc_allocator/src/vec2/raw_vec.rs @@ -53,7 +53,7 @@ use bumpalo::collections::CollectionAllocErr::{self, AllocErr, CapacityOverflow} /// free its memory, but it *won't* try to Drop its contents. It is up to the user of RawVec /// to handle the actual things *stored* inside of a RawVec. /// -/// Note that a RawVec always forces its capacity to be usize::MAX for zero-sized types. +/// Note that a RawVec always forces its capacity to be u32::MAX for zero-sized types. /// This enables you to use capacity growing logic catch the overflows in your length /// that might occur with zero-sized types. /// @@ -63,9 +63,11 @@ use bumpalo::collections::CollectionAllocErr::{self, AllocErr, CapacityOverflow} /// field. This allows zero-sized types to not be special-cased by consumers of /// this type. #[allow(missing_debug_implementations)] +#[repr(C)] pub struct RawVec<'a, T> { ptr: NonNull, - cap: usize, + pub(super) len: u32, + cap: u32, a: &'a Bump, } @@ -74,7 +76,7 @@ impl<'a, T> RawVec<'a, T> { /// the returned RawVec. pub fn new_in(a: &'a Bump) -> Self { // `cap: 0` means "unallocated". zero-sized types are ignored. - RawVec { ptr: NonNull::dangling(), cap: 0, a } + RawVec { ptr: NonNull::dangling(), a, cap: 0, len: 0 } } /// Like `with_capacity` but parameterized over the choice of @@ -91,6 +93,7 @@ impl<'a, T> RawVec<'a, T> { RawVec::allocate_in(cap, true, a) } + #[expect(clippy::cast_possible_truncation)] fn allocate_in(cap: usize, zeroed: bool, mut a: &'a Bump) -> Self { unsafe { let elem_size = mem::size_of::(); @@ -111,7 +114,9 @@ impl<'a, T> RawVec<'a, T> { } }; - RawVec { ptr, cap, a } + // `cap as u32` is safe because `alloc_guard` ensures that `cap` + // cannot exceed `u32::MAX`. + RawVec { ptr, a, cap: cap as u32, len: 0 } } } } @@ -122,10 +127,15 @@ impl<'a, T> RawVec<'a, T> { /// # Undefined Behavior /// /// The ptr must be allocated (via the given allocator `a`), and with the given capacity. The - /// capacity cannot exceed `isize::MAX` (only a concern on 32-bit systems). + /// capacity cannot exceed `isize::MAX` (only a concern on 32-bit systems) and also + /// cannot exceed `u32::MAX` as capacity is stored as `u32`. /// If the ptr and capacity come from a RawVec created via `a`, then this is guaranteed. - pub unsafe fn from_raw_parts_in(ptr: *mut T, cap: usize, a: &'a Bump) -> Self { - RawVec { ptr: NonNull::new_unchecked(ptr), cap, a } + #[expect(clippy::cast_possible_truncation)] + pub unsafe fn from_raw_parts_in(ptr: *mut T, a: &'a Bump, cap: usize, len: usize) -> Self { + alloc_guard(cap).unwrap_or_else(|_| capacity_overflow()); + // `cap as u32` and `len as u32` are safe because `alloc_guard` ensures that + // `cap` and `len` cannot exceed `u32::MAX`. + RawVec { ptr: NonNull::new_unchecked(ptr), a, cap: cap as u32, len: len as u32 } } } @@ -139,12 +149,29 @@ impl<'a, T> RawVec<'a, T> { /// Gets the capacity of the allocation. /// - /// This will always be `usize::MAX` if `T` is zero-sized. + /// This will always be `u32::MAX` if `T` is zero-sized. #[inline(always)] - pub fn cap(&self) -> usize { + pub fn cap(&self) -> u32 { if mem::size_of::() == 0 { !0 } else { self.cap } } + /// Gets the usize capacity of the allocation. + /// + /// This will always be `usize::MAX` if `T` is zero-sized. + #[inline(always)] + pub fn cap_usize(&self) -> usize { + // `self.cap as usize` is safe because it's is `u32` so it must be + // less than `usize::MAX`. + if mem::size_of::() == 0 { !0 } else { self.cap as usize } + } + + /// Gets the usize number of elements. + pub fn len_usize(&self) -> usize { + // `self.len as usize` is safe because it's is `u32` so it must be + // less than `usize::MAX`. + self.len as usize + } + /// Returns a shared reference to the allocator backing this RawVec. pub fn bump(&self) -> &'a Bump { self.a @@ -158,7 +185,9 @@ impl<'a, T> RawVec<'a, T> { // checks to get our current layout. unsafe { let align = mem::align_of::(); - let size = mem::size_of::() * self.cap; + // `self.cap as usize` is safe because it's is `u32` + // so it must be less than `usize::MAX`. + let size = mem::size_of::() * self.cap as usize; Some(Layout::from_size_align_unchecked(size, align)) } } @@ -177,7 +206,7 @@ impl<'a, T> RawVec<'a, T> { /// # Panics /// /// * Panics if T is zero-sized on the assumption that you managed to exhaust - /// all `usize::MAX` slots in your imaginary buffer. + /// all `u32::MAX` slots in your imaginary buffer. /// * Panics on 32-bit platforms if the requested capacity exceeds /// `isize::MAX` bytes. /// @@ -273,7 +302,7 @@ impl<'a, T> RawVec<'a, T> { /// # Panics /// /// * Panics if T is zero-sized on the assumption that you managed to exhaust - /// all `usize::MAX` slots in your imaginary buffer. + /// all `u32::MAX` slots in your imaginary buffer. /// * Panics on 32-bit platforms if the requested capacity exceeds /// `isize::MAX` bytes. #[inline(never)] @@ -315,7 +344,7 @@ impl<'a, T> RawVec<'a, T> { /// The same as `reserve_exact`, but returns on errors instead of panicking or aborting. pub fn try_reserve_exact( &mut self, - len: usize, + len: u32, additional: usize, ) -> Result<(), CollectionAllocErr> { if self.needs_to_grow(len, additional) { @@ -338,21 +367,22 @@ impl<'a, T> RawVec<'a, T> { /// /// # Panics /// - /// * Panics if the requested capacity exceeds `usize::MAX` bytes. + /// * Panics if the requested capacity exceeds `u32::MAX` bytes. /// * Panics on 32-bit platforms if the requested capacity exceeds /// `isize::MAX` bytes. + /// * Panics if the new number of elements would overflow `u32`. /// /// # Aborts /// /// Aborts on OOM - pub fn reserve_exact(&mut self, len: usize, additional: usize) { + pub fn reserve_exact(&mut self, len: u32, additional: usize) { if let Err(err) = self.try_reserve_exact(len, additional) { handle_error(err) } } /// The same as `reserve`, but returns on errors instead of panicking or aborting. - pub fn try_reserve(&mut self, len: usize, additional: usize) -> Result<(), CollectionAllocErr> { + pub fn try_reserve(&mut self, len: u32, additional: usize) -> Result<(), CollectionAllocErr> { if self.needs_to_grow(len, additional) { self.grow_amortized(len, additional)?; } @@ -374,7 +404,7 @@ impl<'a, T> RawVec<'a, T> { /// /// # Panics /// - /// * Panics if the requested capacity exceeds `usize::MAX` bytes. + /// * Panics if the requested capacity exceeds `u32::MAX` bytes. /// * Panics on 32-bit platforms if the requested capacity exceeds /// `isize::MAX` bytes. /// @@ -413,13 +443,13 @@ impl<'a, T> RawVec<'a, T> { /// # } /// ``` #[inline] - pub fn reserve(&mut self, len: usize, additional: usize) { + pub fn reserve(&mut self, len: u32, additional: usize) { // Callers expect this function to be very cheap when there is already sufficient capacity. // Therefore, we move all the resizing and error-handling logic from grow_amortized and // handle_reserve behind a call, while making sure that this function is likely to be // inlined as just a comparison and a call if the comparison fails. #[cold] - fn do_reserve_and_handle(slf: &mut RawVec, len: usize, additional: usize) { + fn do_reserve_and_handle(slf: &mut RawVec, len: u32, additional: usize) { if let Err(err) = slf.grow_amortized(len, additional) { handle_error(err); } @@ -457,7 +487,7 @@ impl<'a, T> RawVec<'a, T> { /// /// # Panics /// - /// * Panics if the requested capacity exceeds `usize::MAX` bytes. + /// * Panics if the requested capacity exceeds `u32::MAX` bytes. /// * Panics on 32-bit platforms if the requested capacity exceeds /// `isize::MAX` bytes. pub fn reserve_in_place(&mut self, len: usize, additional: usize) -> bool { @@ -510,7 +540,7 @@ impl<'a, T> RawVec<'a, T> { /// # Aborts /// /// Aborts on OOM. - pub fn shrink_to_fit(&mut self, amount: usize) { + pub fn shrink_to_fit(&mut self, amount: u32) { let elem_size = mem::size_of::(); // Set the `cap` because they might be about to promote to a `Box<[T]>` @@ -544,8 +574,11 @@ impl<'a, T> RawVec<'a, T> { // We also know that `self.cap` is greater than `amount`, and // consequently we don't need runtime checks for creating either // layout - let old_size = elem_size * self.cap; - let new_size = elem_size * amount; + // + // `self.cap as usize` and `amount as usize` are safe because + // they are `u32` so they must be less than `usize::MAX`. + let old_size = elem_size * self.cap as usize; + let new_size = elem_size * amount as usize; let align = mem::align_of::(); let old_layout = Layout::from_size_align_unchecked(old_size, align); let new_layout = Layout::from_size_align_unchecked(new_size, align); @@ -584,27 +617,35 @@ impl<'a, T> RawVec<'a, T> { } */ -impl<'a, T> RawVec<'a, T> { +impl RawVec<'_, T> { #[inline] - fn needs_to_grow(&self, len: usize, additional: usize) -> bool { - additional > self.cap().wrapping_sub(len) + fn needs_to_grow(&self, len: u32, additional: usize) -> bool { + // `self.cap().wrapping_sub(len) as usize` is safe because + // `self.cap()` is `u32` and `len` is `u32`, so the result + // is guaranteed to be less than `usize::MAX`. + additional > self.cap().wrapping_sub(len) as usize } /// Helper method to reserve additional space, reallocating the backing memory. /// The caller is responsible for confirming that there is not already enough space available. - fn grow_exact(&mut self, len: usize, additional: usize) -> Result<(), CollectionAllocErr> { + #[expect(clippy::cast_possible_truncation)] + fn grow_exact(&mut self, len: u32, additional: usize) -> Result<(), CollectionAllocErr> { unsafe { // NOTE: we don't early branch on ZSTs here because we want this - // to actually catch "asking for more than usize::MAX" in that case. + // to actually catch "asking for more than u32::MAX" in that case. // If we make it past the first branch then we are guaranteed to // panic. - let new_cap = len.checked_add(additional).ok_or(CapacityOverflow)?; + // `len as usize` is safe because `len` is `u32`, so it must be + // less than `usize::MAX`. + let new_cap = (len as usize).checked_add(additional).ok_or(CapacityOverflow)?; let new_layout = Layout::array::(new_cap).map_err(|_| CapacityOverflow)?; self.ptr = self.finish_grow(new_layout)?.cast(); - self.cap = new_cap; + // `cap as u32` is safe because `finish_grow` called `alloc_guard`, and + // `alloc_guard` ensures that `cap` cannot exceed `u32::MAX`. + self.cap = new_cap as u32; Ok(()) } @@ -612,19 +653,22 @@ impl<'a, T> RawVec<'a, T> { /// Helper method to reserve additional space, reallocating the backing memory. /// The caller is responsible for confirming that there is not already enough space available. - fn grow_amortized(&mut self, len: usize, additional: usize) -> Result<(), CollectionAllocErr> { + #[expect(clippy::cast_possible_truncation)] + fn grow_amortized(&mut self, len: u32, additional: usize) -> Result<(), CollectionAllocErr> { unsafe { // NOTE: we don't early branch on ZSTs here because we want this - // to actually catch "asking for more than usize::MAX" in that case. + // to actually catch "asking for more than u32::MAX" in that case. // If we make it past the first branch then we are guaranteed to // panic. // Nothing we can really do about these checks, sadly. - let required_cap = len.checked_add(additional).ok_or(CapacityOverflow)?; + // `len as usize` is safe because `len` is `u32`, so it must be + // less than `usize::MAX`. + let required_cap = (len as usize).checked_add(additional).ok_or(CapacityOverflow)?; // This guarantees exponential growth. The doubling cannot overflow - // because `cap <= isize::MAX` and the type of `cap` is `usize`. - let cap = cmp::max(self.cap() * 2, required_cap); + // because `cap <= isize::MAX` and the type of `cap` is `u32`. + let cap = cmp::max((self.cap() as usize) * 2, required_cap); // The following commented-out code is copied from the standard library. // We don't use it because this would cause notable performance regression @@ -658,7 +702,9 @@ impl<'a, T> RawVec<'a, T> { self.ptr = self.finish_grow(new_layout)?.cast(); - self.cap = cap; + // `cap as u32` is safe because `finish_grow` called `alloc_guard`, and + // `alloc_guard` ensures that `cap` cannot exceed `u32::MAX`. + self.cap = cap as u32; Ok(()) } @@ -693,7 +739,7 @@ impl<'a, T> RawVec<'a, T> { } } -impl<'a, T> RawVec<'a, T> { +impl RawVec<'_, T> { /// Frees the memory owned by the RawVec *without* trying to Drop its contents. pub unsafe fn dealloc_buffer(&mut self) { let elem_size = mem::size_of::(); @@ -707,20 +753,23 @@ impl<'a, T> RawVec<'a, T> { // We need to guarantee the following: // * We don't ever allocate `> isize::MAX` byte-size objects -// * We don't overflow `usize::MAX` and actually allocate too little +// * We don't overflow `u32::MAX` and actually allocate too little // -// On 64-bit we just need to check for overflow since trying to allocate -// `> isize::MAX` bytes will surely fail. On 32-bit and 16-bit we need to add -// an extra guard for this in case we're running on a platform which can use -// all 4GB in user-space. e.g. PAE or x32 +// On 64-bit we need to check for overflow since trying to allocate `> u32::MAX` +// bytes is overflow because `cap` and `len` are both `u32`s. +// On 32-bit and 16-bit we need to add an extra guard for this in case we're +// running on a platform which can use all 4GB in user-space. e.g. PAE or x32 #[inline] fn alloc_guard(alloc_size: usize) -> Result<(), CollectionAllocErr> { - if mem::size_of::() < 8 && alloc_size > ::core::isize::MAX as usize { - Err(CapacityOverflow) - } else { - Ok(()) + if mem::size_of::() < 8 { + if alloc_size > ::core::isize::MAX as usize { + return Err(CapacityOverflow); + } + } else if alloc_size > u32::MAX as usize { + return Err(CapacityOverflow); } + Ok(()) } // One central function responsible for reporting capacity overflows. This'll diff --git a/crates/oxc_ast/CHANGELOG.md b/crates/oxc_ast/CHANGELOG.md index ee704eb2e0fa6..2f12aa186e0d7 100644 --- a/crates/oxc_ast/CHANGELOG.md +++ b/crates/oxc_ast/CHANGELOG.md @@ -4,6 +4,91 @@ 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/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.70.0] - 2025-05-15 + +### Features + +- 1673ffb codegen: Rework printing normal / legal / annotation comments (#10997) (Boshen) + +### Bug Fixes + +- 6f3f9d7 ast/estree: Fix `raw_deser` for `TSMappedTypeOptional` serializer (#10971) (overlookmotel) +- 53329f8 ast/estree: Fix field order for `FormalParameter` (#10962) (overlookmotel) +- 8b8f78f ast/estree: Fix field order and type def for `RestElement` in `FormalParameters` (#10961) (overlookmotel) +- 2b76ab5 ast/estree: Fix `TSModuleDeclaration` raw deserializer (#10924) (overlookmotel) +- d036cf5 estree: Ensure the same key order for `AssignmentPattern` (#10953) (Yuji Sugiura) + +### Performance + +- a4b5716 ast/estree: Streamline raw deserializer for `WithClause` (#10974) (overlookmotel) + +### Documentation + +- 206e07d ast: Fix docs for `ExportSpecifier` (#10891) (overlookmotel) +- b23fef7 ast/estree: Standardize doc comments for ESTree serializers (#10968) (overlookmotel) + +### Refactor + +- 6cc74ff ast/estree: Add semi-colon (#10973) (overlookmotel) +- 2958ff7 ast/estree: Rename serializers (#10969) (overlookmotel) +- b8018b9 ast/estree: Split custom serializers into multiple files (#10967) (overlookmotel) +- 08f1f3f ast/estree: Remove custom serializer for `Elision` (#10965) (overlookmotel) +- d01a47b ast/estree: Simplify custom serializers for `FormalParameters` (#10964) (overlookmotel) +- bfc6b9a ast/estree: Rename custom serializers for fields containing `FormalParameters` (#10963) (overlookmotel) + +### Styling + +- 6d4936f ast/estree: Reformat `raw_deser` code (#10972) (overlookmotel) + +## [0.69.0] - 2025-05-09 + +- 2b5d826 ast: [**BREAKING**] Fix field order for `TSTypeAssertion` (#10906) (overlookmotel) + +- 1f35910 ast: [**BREAKING**] Fix field order for `TSNamedTupleMember` (#10905) (overlookmotel) + +- 8a3bba8 ast: [**BREAKING**] Fix field order for `PropertyDefinition` (#10902) (overlookmotel) + +- 5746d36 ast: [**BREAKING**] Fix field order for `NewExpression` (#10893) (overlookmotel) + +- 0139793 ast: [**BREAKING**] Re-order fields of `TaggedTemplateExpression` (#10889) (overlookmotel) + +- 6646b6b ast: [**BREAKING**] Fix field order for `JSXOpeningElement` (#10882) (overlookmotel) + +- cc2ed21 ast: [**BREAKING**] Fix field order for `JSXElement` and `JSXFragment` (#10881) (overlookmotel) + +- ad4fbf4 ast: [**BREAKING**] Simplify `RegExpPattern` (#10834) (overlookmotel) + +### Features + +- d066516 ast_tools: Support `#[estree(prepend_to)]` (#10849) (overlookmotel) + +### Bug Fixes + +- 2c09243 ast: Fix field order for `AccessorProperty` (#10878) (overlookmotel) +- 581d068 ast/estree: Fix TS type def for `RegExpLiteral` (#10876) (overlookmotel) +- e7228fa ast/estree: Fix `optional` field of `TSMappedType` in TS-ESTree AST (#10874) (overlookmotel) +- 6f0638a ast/estree: Remove `TSImportTypeOptions` custom serializer (#10873) (overlookmotel) +- e6657ae ast/estree: Reorder fields for TS `Identifier` types in TS-ESTree AST (#10864) (overlookmotel) + +### Performance + +- 49a6f97 napi/parser: Faster fixup of `BigInt`s and `RegExp`s (#10820) (overlookmotel) + +### Documentation + +- 4863eef ast: Improve docs for `TSImportType` (#10903) (overlookmotel) + +### Refactor + +- 805735b ast: Remove quotes from `#[estree(append_to)]` attributes (#10863) (overlookmotel) +- 0f7e755 ast/estree: Use `#[estree(prepend_to)]` (#10862) (overlookmotel) +- b16331e ast/estree: Generalize concatenating fields with `Concat2` (#10848) (overlookmotel) +- daba0a7 estree: Remove regular expression types from ESTree AST (#10855) (overlookmotel) + +### Styling + +- 62c3a4a ast_tools: Add full stop to end of generated comments (#10809) (overlookmotel) + ## [0.68.1] - 2025-05-04 ### Bug Fixes diff --git a/crates/oxc_ast/Cargo.toml b/crates/oxc_ast/Cargo.toml index ad9d07f5f99e9..7d1b433ac95b8 100644 --- a/crates/oxc_ast/Cargo.toml +++ b/crates/oxc_ast/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_ast" -version = "0.68.1" +version = "0.70.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_ast/src/ast/comment.rs b/crates/oxc_ast/src/ast/comment.rs index e142fec8e98ff..8c0ff61971d50 100644 --- a/crates/oxc_ast/src/ast/comment.rs +++ b/crates/oxc_ast/src/ast/comment.rs @@ -49,15 +49,14 @@ pub enum CommentAnnotation { #[default] None = 0, - /// `/** jsdoc */` - /// - Jsdoc = 1, - /// Legal Comment /// e.g. `/* @license */`, `/* @preserve */`, or starts with `//!` or `/*!`. - /// /// - Legal = 2, + Legal = 1, + + /// `/** jsdoc */` + /// + Jsdoc = 2, /// `/* #__PURE__ */` /// @@ -82,6 +81,22 @@ pub enum CommentAnnotation { CoverageIgnore = 7, } +/// State of newlines around a comment. +#[ast] +#[generate_derive(CloneIn, ContentEq)] +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)] +pub enum CommentNewlines { + /// No newlines before or after + #[default] + None = 0, + /// Preceded by a newline + Leading = 1, + /// Followed by a newline + Trailing = 2, + /// Preceded and followed by a newline + LeadingAndTrailing = 3, +} + /// A comment in source code. #[ast] #[generate_derive(CloneIn, ContentEq, ESTree)] @@ -106,14 +121,10 @@ pub struct Comment { #[estree(skip)] pub position: CommentPosition, - /// Whether this comment has a preceding newline. + /// Whether this comment has newlines around it. /// Used to avoid becoming a trailing comment in codegen. #[estree(skip)] - pub preceded_by_newline: bool, - - /// Whether this comment has a tailing newline. - #[estree(skip)] - pub followed_by_newline: bool, + pub newlines: CommentNewlines, /// Comment Annotation #[estree(skip)] @@ -130,8 +141,7 @@ impl Comment { attached_to: 0, kind, position: CommentPosition::Trailing, - preceded_by_newline: false, - followed_by_newline: false, + newlines: CommentNewlines::None, annotation: CommentAnnotation::None, } } @@ -220,4 +230,50 @@ impl Comment { pub fn is_coverage_ignore(self) -> bool { self.annotation == CommentAnnotation::CoverageIgnore && self.is_leading() } + + /// Sets the state of `newlines` to include/exclude a newline after the comment. + pub fn set_followed_by_newline(&mut self, followed_by_newline: bool) { + if followed_by_newline { + self.newlines = match self.newlines { + CommentNewlines::None => CommentNewlines::Trailing, + CommentNewlines::Leading => CommentNewlines::LeadingAndTrailing, + _ => self.newlines, + } + } else { + self.newlines = match self.newlines { + CommentNewlines::Trailing => CommentNewlines::None, + CommentNewlines::LeadingAndTrailing => CommentNewlines::Leading, + _ => self.newlines, + } + } + } + + /// Sets the state of `newlines` to include/exclude a newline before the comment. + pub fn set_preceded_by_newline(&mut self, preceded_by_newline: bool) { + if preceded_by_newline { + self.newlines = match self.newlines { + CommentNewlines::None => CommentNewlines::Leading, + CommentNewlines::Trailing => CommentNewlines::LeadingAndTrailing, + _ => self.newlines, + } + } else { + self.newlines = match self.newlines { + CommentNewlines::Leading => CommentNewlines::None, + CommentNewlines::LeadingAndTrailing => CommentNewlines::Trailing, + _ => self.newlines, + } + } + } + + /// Returns `true` if this comment is preceded by a newline. + #[inline] + pub fn preceded_by_newline(self) -> bool { + matches!(self.newlines, CommentNewlines::Leading | CommentNewlines::LeadingAndTrailing) + } + + /// Returns `true` if this comment is followed by a newline. + #[inline] + pub fn followed_by_newline(self) -> bool { + matches!(self.newlines, CommentNewlines::Trailing | CommentNewlines::LeadingAndTrailing) + } } diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 23876a7ae2a29..1ba0166423951 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -334,11 +334,10 @@ pub enum ArrayExpressionElement<'a> { /// empty slot in `const array = [1, , 2];` /// /// Array Expression Elision Element -/// Serialized as `null` in JSON AST. See `serialize.rs`. #[ast(visit)] #[derive(Debug, Clone)] #[generate_derive(CloneIn, Dummy, TakeIn, GetSpan, GetSpanMut, ContentEq, ESTree)] -#[estree(via = ElisionConverter)] +#[estree(via = Null)] pub struct Elision { pub span: Span, } @@ -963,7 +962,7 @@ pub struct AssignmentTargetPropertyIdentifier<'a> { pub span: Span, #[estree(rename = "key")] pub binding: IdentifierReference<'a>, - #[estree(rename = "value", via = AssignmentTargetPropertyIdentifierValue)] + #[estree(rename = "value", via = AssignmentTargetPropertyIdentifierInit)] pub init: Option>, } @@ -1727,7 +1726,7 @@ pub struct Function<'a> { /// Function parameters. /// /// Does not include `this` parameters used by some TypeScript functions. - #[estree(via = FunctionFormalParameters)] + #[estree(via = FunctionParams)] pub params: Box<'a, FormalParameters<'a>>, /// The TypeScript return type annotation. #[ts] @@ -1774,8 +1773,10 @@ pub enum FunctionType { interface FormalParameterRest extends Span { type: 'RestElement'; argument: BindingPatternKind; - typeAnnotation: TSTypeAnnotation | null; - optional: boolean; + decorators?: [], + optional?: boolean; + typeAnnotation?: TSTypeAnnotation | null; + value?: null; } " )] @@ -2490,10 +2491,13 @@ pub struct ImportNamespaceSpecifier<'a> { #[ast(visit)] #[derive(Debug)] #[generate_derive(CloneIn, Dummy, TakeIn, GetSpan, GetSpanMut, ContentEq, ESTree)] -#[estree(no_ts_def)] +#[estree(no_type, no_ts_def)] pub struct WithClause<'a> { + #[estree(skip)] pub span: Span, + #[estree(skip)] pub attributes_keyword: IdentifierName<'a>, // `with` or `assert` + #[estree(rename = "attributes")] pub with_entries: Vec<'a, ImportAttribute<'a>>, } @@ -2589,12 +2593,16 @@ pub struct ExportAllDeclaration<'a> { /// /// Each [`ExportSpecifier`] is one of the named exports in an [`ExportNamedDeclaration`]. /// +/// Note: `export_kind` relates to whether this specific `ExportSpecifier` is preceded by `type` keyword. +/// If the whole `ExportNamedDeclaration` has a `type` prefix, its `ExportSpecifier`s will still have +/// `export_kind: ImportOrExportKind::Value`. e.g. in this case: `export type { Foo, Bar }`. +/// /// ## Example /// /// ```ts /// // ____ export_kind -/// import { type Foo as Bar } from './foo'; -/// // exported ^^^ ^^^ local +/// export { type Foo as Bar }; +/// // local ^^^ ^^^ exported /// ``` #[ast(visit)] #[derive(Debug)] @@ -2604,7 +2612,7 @@ pub struct ExportSpecifier<'a> { pub local: ModuleExportName<'a>, pub exported: ModuleExportName<'a>, #[ts] - pub export_kind: ImportOrExportKind, // `export type *` + pub export_kind: ImportOrExportKind, // `export { type Foo as Bar };` } inherit_variants! { diff --git a/crates/oxc_ast/src/ast/jsx.rs b/crates/oxc_ast/src/ast/jsx.rs index 2039ecdf5ff20..b78477dea718e 100644 --- a/crates/oxc_ast/src/ast/jsx.rs +++ b/crates/oxc_ast/src/ast/jsx.rs @@ -38,7 +38,7 @@ pub struct JSXElement<'a> { /// Node location in source code pub span: Span, /// Opening tag of the element. - #[estree(via = JSXElementOpening)] + #[estree(via = JSXElementOpeningElement)] pub opening_element: Box<'a, JSXOpeningElement<'a>>, /// Children of the element. /// This can be text, other elements, or expressions. diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index 0df61554d6361..c61c8805c23d2 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -1020,7 +1020,7 @@ pub struct TSCallSignatureDeclaration<'a> { pub type_parameters: Option>>, #[estree(skip)] pub this_param: Option>>, - #[estree(via = TSCallSignatureDeclarationFormalParameters)] + #[estree(via = TSCallSignatureDeclarationParams)] pub params: Box<'a, FormalParameters<'a>>, pub return_type: Option>>, } @@ -1059,7 +1059,7 @@ pub struct TSMethodSignature<'a> { pub type_parameters: Option>>, #[estree(skip)] pub this_param: Option>>, - #[estree(via = TSMethodSignatureFormalParameters)] + #[estree(via = TSMethodSignatureParams)] pub params: Box<'a, FormalParameters<'a>>, pub return_type: Option>>, pub scope_id: Cell>, @@ -1388,7 +1388,7 @@ pub struct TSFunctionType<'a> { #[estree(skip)] pub this_param: Option>>, /// Function parameters. Akin to [`Function::params`]. - #[estree(via = TSFunctionTypeFormalParameters)] + #[estree(via = TSFunctionTypeParams)] pub params: Box<'a, FormalParameters<'a>>, /// Return type of the function. /// ```ts diff --git a/crates/oxc_ast/src/generated/assert_layouts.rs b/crates/oxc_ast/src/generated/assert_layouts.rs index 6d84993d05eed..a1f095f74b418 100644 --- a/crates/oxc_ast/src/generated/assert_layouts.rs +++ b/crates/oxc_ast/src/generated/assert_layouts.rs @@ -9,47 +9,54 @@ use crate::ast::*; #[cfg(target_pointer_width = "64")] const _: () = { - assert!(size_of::() == 160); + // Padding: 1 bytes + assert!(size_of::() == 128); assert!(align_of::() == 8); assert!(offset_of!(Program, span) == 0); - assert!(offset_of!(Program, source_type) == 8); - assert!(offset_of!(Program, source_text) == 16); - assert!(offset_of!(Program, comments) == 32); - assert!(offset_of!(Program, hashbang) == 64); - assert!(offset_of!(Program, directives) == 88); - assert!(offset_of!(Program, body) == 120); - assert!(offset_of!(Program, scope_id) == 152); + assert!(offset_of!(Program, source_type) == 124); + assert!(offset_of!(Program, source_text) == 8); + assert!(offset_of!(Program, comments) == 24); + assert!(offset_of!(Program, hashbang) == 48); + assert!(offset_of!(Program, directives) == 72); + assert!(offset_of!(Program, body) == 96); + assert!(offset_of!(Program, scope_id) == 120); assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(IdentifierName, span) == 0); assert!(offset_of!(IdentifierName, name) == 8); + // Padding: 4 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(IdentifierReference, span) == 0); assert!(offset_of!(IdentifierReference, name) == 8); assert!(offset_of!(IdentifierReference, reference_id) == 24); + // Padding: 4 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(BindingIdentifier, span) == 0); assert!(offset_of!(BindingIdentifier, name) == 8); assert!(offset_of!(BindingIdentifier, symbol_id) == 24); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(LabelIdentifier, span) == 0); assert!(offset_of!(LabelIdentifier, name) == 8); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(ThisExpression, span) == 0); - assert!(size_of::() == 40); + // Padding: 0 bytes + assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(ArrayExpression, span) == 0); assert!(offset_of!(ArrayExpression, elements) == 8); @@ -57,11 +64,13 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(Elision, span) == 0); - assert!(size_of::() == 40); + // Padding: 0 bytes + assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(ObjectExpression, span) == 0); assert!(offset_of!(ObjectExpression, properties) == 8); @@ -69,15 +78,16 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); - assert!(size_of::() == 56); + // Padding: 4 bytes + assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(ObjectProperty, span) == 0); - assert!(offset_of!(ObjectProperty, kind) == 8); - assert!(offset_of!(ObjectProperty, key) == 16); - assert!(offset_of!(ObjectProperty, value) == 32); - assert!(offset_of!(ObjectProperty, method) == 48); - assert!(offset_of!(ObjectProperty, shorthand) == 49); - assert!(offset_of!(ObjectProperty, computed) == 50); + assert!(offset_of!(ObjectProperty, kind) == 40); + assert!(offset_of!(ObjectProperty, key) == 8); + assert!(offset_of!(ObjectProperty, value) == 24); + assert!(offset_of!(ObjectProperty, method) == 41); + assert!(offset_of!(ObjectProperty, shorthand) == 42); + assert!(offset_of!(ObjectProperty, computed) == 43); assert!(size_of::() == 16); assert!(align_of::() == 8); @@ -85,19 +95,22 @@ const _: () = { assert!(size_of::() == 1); assert!(align_of::() == 1); - assert!(size_of::() == 72); + // Padding: 0 bytes + assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(TemplateLiteral, span) == 0); assert!(offset_of!(TemplateLiteral, quasis) == 8); - assert!(offset_of!(TemplateLiteral, expressions) == 40); + assert!(offset_of!(TemplateLiteral, expressions) == 32); - assert!(size_of::() == 104); + // Padding: 0 bytes + assert!(size_of::() == 88); assert!(align_of::() == 8); assert!(offset_of!(TaggedTemplateExpression, span) == 0); assert!(offset_of!(TaggedTemplateExpression, tag) == 8); assert!(offset_of!(TaggedTemplateExpression, type_arguments) == 24); assert!(offset_of!(TaggedTemplateExpression, quasi) == 32); + // Padding: 6 bytes assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(TemplateElement, span) == 0); @@ -105,6 +118,7 @@ const _: () = { assert!(offset_of!(TemplateElement, tail) == 40); assert!(offset_of!(TemplateElement, lone_surrogates) == 41); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TemplateElementValue, raw) == 0); @@ -113,6 +127,7 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 7 bytes assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(ComputedMemberExpression, span) == 0); @@ -120,6 +135,7 @@ const _: () = { assert!(offset_of!(ComputedMemberExpression, expression) == 24); assert!(offset_of!(ComputedMemberExpression, optional) == 40); + // Padding: 7 bytes assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(StaticMemberExpression, span) == 0); @@ -127,6 +143,7 @@ const _: () = { assert!(offset_of!(StaticMemberExpression, property) == 24); assert!(offset_of!(StaticMemberExpression, optional) == 48); + // Padding: 7 bytes assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(PrivateFieldExpression, span) == 0); @@ -134,29 +151,33 @@ const _: () = { assert!(offset_of!(PrivateFieldExpression, field) == 24); assert!(offset_of!(PrivateFieldExpression, optional) == 48); - assert!(size_of::() == 72); + // Padding: 6 bytes + assert!(size_of::() == 64); assert!(align_of::() == 8); assert!(offset_of!(CallExpression, span) == 0); assert!(offset_of!(CallExpression, callee) == 8); assert!(offset_of!(CallExpression, type_arguments) == 24); assert!(offset_of!(CallExpression, arguments) == 32); - assert!(offset_of!(CallExpression, optional) == 64); - assert!(offset_of!(CallExpression, pure) == 65); + assert!(offset_of!(CallExpression, optional) == 56); + assert!(offset_of!(CallExpression, pure) == 57); - assert!(size_of::() == 72); + // Padding: 7 bytes + assert!(size_of::() == 64); assert!(align_of::() == 8); assert!(offset_of!(NewExpression, span) == 0); assert!(offset_of!(NewExpression, callee) == 8); assert!(offset_of!(NewExpression, type_arguments) == 24); assert!(offset_of!(NewExpression, arguments) == 32); - assert!(offset_of!(NewExpression, pure) == 64); + assert!(offset_of!(NewExpression, pure) == 56); + // Padding: 0 bytes assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(MetaProperty, span) == 0); assert!(offset_of!(MetaProperty, meta) == 8); assert!(offset_of!(MetaProperty, property) == 32); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(SpreadElement, span) == 0); @@ -165,39 +186,45 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 6 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(UpdateExpression, span) == 0); - assert!(offset_of!(UpdateExpression, operator) == 8); - assert!(offset_of!(UpdateExpression, prefix) == 9); - assert!(offset_of!(UpdateExpression, argument) == 16); + assert!(offset_of!(UpdateExpression, operator) == 24); + assert!(offset_of!(UpdateExpression, prefix) == 25); + assert!(offset_of!(UpdateExpression, argument) == 8); + // Padding: 7 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(UnaryExpression, span) == 0); - assert!(offset_of!(UnaryExpression, operator) == 8); - assert!(offset_of!(UnaryExpression, argument) == 16); + assert!(offset_of!(UnaryExpression, operator) == 24); + assert!(offset_of!(UnaryExpression, argument) == 8); + // Padding: 7 bytes assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(BinaryExpression, span) == 0); assert!(offset_of!(BinaryExpression, left) == 8); - assert!(offset_of!(BinaryExpression, operator) == 24); - assert!(offset_of!(BinaryExpression, right) == 32); + assert!(offset_of!(BinaryExpression, operator) == 40); + assert!(offset_of!(BinaryExpression, right) == 24); + // Padding: 0 bytes assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(PrivateInExpression, span) == 0); assert!(offset_of!(PrivateInExpression, left) == 8); assert!(offset_of!(PrivateInExpression, right) == 32); + // Padding: 7 bytes assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(LogicalExpression, span) == 0); assert!(offset_of!(LogicalExpression, left) == 8); - assert!(offset_of!(LogicalExpression, operator) == 24); - assert!(offset_of!(LogicalExpression, right) == 32); + assert!(offset_of!(LogicalExpression, operator) == 40); + assert!(offset_of!(LogicalExpression, right) == 24); + // Padding: 0 bytes assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(ConditionalExpression, span) == 0); @@ -205,12 +232,13 @@ const _: () = { assert!(offset_of!(ConditionalExpression, consequent) == 24); assert!(offset_of!(ConditionalExpression, alternate) == 40); + // Padding: 7 bytes assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(AssignmentExpression, span) == 0); - assert!(offset_of!(AssignmentExpression, operator) == 8); - assert!(offset_of!(AssignmentExpression, left) == 16); - assert!(offset_of!(AssignmentExpression, right) == 32); + assert!(offset_of!(AssignmentExpression, operator) == 40); + assert!(offset_of!(AssignmentExpression, left) == 8); + assert!(offset_of!(AssignmentExpression, right) == 24); assert!(size_of::() == 16); assert!(align_of::() == 8); @@ -221,18 +249,21 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); - assert!(size_of::() == 64); + // Padding: 0 bytes + assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(ArrayAssignmentTarget, span) == 0); assert!(offset_of!(ArrayAssignmentTarget, elements) == 8); - assert!(offset_of!(ArrayAssignmentTarget, rest) == 40); + assert!(offset_of!(ArrayAssignmentTarget, rest) == 32); - assert!(size_of::() == 64); + // Padding: 0 bytes + assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(ObjectAssignmentTarget, span) == 0); assert!(offset_of!(ObjectAssignmentTarget, properties) == 8); - assert!(offset_of!(ObjectAssignmentTarget, rest) == 40); + assert!(offset_of!(ObjectAssignmentTarget, rest) == 32); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(AssignmentTargetRest, span) == 0); @@ -241,6 +272,7 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(AssignmentTargetWithDefault, span) == 0); @@ -250,12 +282,14 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(AssignmentTargetPropertyIdentifier, span) == 0); assert!(offset_of!(AssignmentTargetPropertyIdentifier, binding) == 8); assert!(offset_of!(AssignmentTargetPropertyIdentifier, init) == 40); + // Padding: 7 bytes assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(AssignmentTargetPropertyProperty, span) == 0); @@ -263,20 +297,24 @@ const _: () = { assert!(offset_of!(AssignmentTargetPropertyProperty, binding) == 24); assert!(offset_of!(AssignmentTargetPropertyProperty, computed) == 40); - assert!(size_of::() == 40); + // Padding: 0 bytes + assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(SequenceExpression, span) == 0); assert!(offset_of!(SequenceExpression, expressions) == 8); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(Super, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(AwaitExpression, span) == 0); assert!(offset_of!(AwaitExpression, argument) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(ChainExpression, span) == 0); @@ -285,6 +323,7 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(ParenthesizedExpression, span) == 0); @@ -293,53 +332,61 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 72); assert!(align_of::() == 8); assert!(offset_of!(Directive, span) == 0); assert!(offset_of!(Directive, expression) == 8); assert!(offset_of!(Directive, directive) == 56); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(Hashbang, span) == 0); assert!(offset_of!(Hashbang, value) == 8); - assert!(size_of::() == 48); + // Padding: 4 bytes + assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(BlockStatement, span) == 0); assert!(offset_of!(BlockStatement, body) == 8); - assert!(offset_of!(BlockStatement, scope_id) == 40); + assert!(offset_of!(BlockStatement, scope_id) == 32); assert!(size_of::() == 16); assert!(align_of::() == 8); - assert!(size_of::() == 56); + // Padding: 6 bytes + assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(VariableDeclaration, span) == 0); - assert!(offset_of!(VariableDeclaration, kind) == 8); - assert!(offset_of!(VariableDeclaration, declarations) == 16); - assert!(offset_of!(VariableDeclaration, declare) == 48); + assert!(offset_of!(VariableDeclaration, kind) == 32); + assert!(offset_of!(VariableDeclaration, declarations) == 8); + assert!(offset_of!(VariableDeclaration, declare) == 33); assert!(size_of::() == 1); assert!(align_of::() == 1); - assert!(size_of::() == 72); + // Padding: 6 bytes + assert!(size_of::() == 64); assert!(align_of::() == 8); assert!(offset_of!(VariableDeclarator, span) == 0); - assert!(offset_of!(VariableDeclarator, kind) == 8); - assert!(offset_of!(VariableDeclarator, id) == 16); - assert!(offset_of!(VariableDeclarator, init) == 48); - assert!(offset_of!(VariableDeclarator, definite) == 64); + assert!(offset_of!(VariableDeclarator, kind) == 56); + assert!(offset_of!(VariableDeclarator, id) == 8); + assert!(offset_of!(VariableDeclarator, init) == 40); + assert!(offset_of!(VariableDeclarator, definite) == 57); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(EmptyStatement, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(ExpressionStatement, span) == 0); assert!(offset_of!(ExpressionStatement, expression) == 8); + // Padding: 0 bytes assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(IfStatement, span) == 0); @@ -347,18 +394,21 @@ const _: () = { assert!(offset_of!(IfStatement, consequent) == 24); assert!(offset_of!(IfStatement, alternate) == 40); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(DoWhileStatement, span) == 0); assert!(offset_of!(DoWhileStatement, body) == 8); assert!(offset_of!(DoWhileStatement, test) == 24); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(WhileStatement, span) == 0); assert!(offset_of!(WhileStatement, test) == 8); assert!(offset_of!(WhileStatement, body) == 24); + // Padding: 4 bytes assert!(size_of::() == 80); assert!(align_of::() == 8); assert!(offset_of!(ForStatement, span) == 0); @@ -371,6 +421,7 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 4 bytes assert!(size_of::() == 64); assert!(align_of::() == 8); assert!(offset_of!(ForInStatement, span) == 0); @@ -382,60 +433,70 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); - assert!(size_of::() == 72); + // Padding: 3 bytes + assert!(size_of::() == 64); assert!(align_of::() == 8); assert!(offset_of!(ForOfStatement, span) == 0); - assert!(offset_of!(ForOfStatement, r#await) == 8); - assert!(offset_of!(ForOfStatement, left) == 16); - assert!(offset_of!(ForOfStatement, right) == 32); - assert!(offset_of!(ForOfStatement, body) == 48); - assert!(offset_of!(ForOfStatement, scope_id) == 64); + assert!(offset_of!(ForOfStatement, r#await) == 60); + assert!(offset_of!(ForOfStatement, left) == 8); + assert!(offset_of!(ForOfStatement, right) == 24); + assert!(offset_of!(ForOfStatement, body) == 40); + assert!(offset_of!(ForOfStatement, scope_id) == 56); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(ContinueStatement, span) == 0); assert!(offset_of!(ContinueStatement, label) == 8); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(BreakStatement, span) == 0); assert!(offset_of!(BreakStatement, label) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(ReturnStatement, span) == 0); assert!(offset_of!(ReturnStatement, argument) == 8); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(WithStatement, span) == 0); assert!(offset_of!(WithStatement, object) == 8); assert!(offset_of!(WithStatement, body) == 24); - assert!(size_of::() == 64); + // Padding: 4 bytes + assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(SwitchStatement, span) == 0); assert!(offset_of!(SwitchStatement, discriminant) == 8); assert!(offset_of!(SwitchStatement, cases) == 24); - assert!(offset_of!(SwitchStatement, scope_id) == 56); + assert!(offset_of!(SwitchStatement, scope_id) == 48); - assert!(size_of::() == 56); + // Padding: 0 bytes + assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(SwitchCase, span) == 0); assert!(offset_of!(SwitchCase, test) == 8); assert!(offset_of!(SwitchCase, consequent) == 24); + // Padding: 0 bytes assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(LabeledStatement, span) == 0); assert!(offset_of!(LabeledStatement, label) == 8); assert!(offset_of!(LabeledStatement, body) == 32); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(ThrowStatement, span) == 0); assert!(offset_of!(ThrowStatement, argument) == 8); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TryStatement, span) == 0); @@ -443,6 +504,7 @@ const _: () = { assert!(offset_of!(TryStatement, handler) == 16); assert!(offset_of!(TryStatement, finalizer) == 24); + // Padding: 4 bytes assert!(size_of::() == 64); assert!(align_of::() == 8); assert!(offset_of!(CatchClause, span) == 0); @@ -450,15 +512,18 @@ const _: () = { assert!(offset_of!(CatchClause, body) == 48); assert!(offset_of!(CatchClause, scope_id) == 56); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(CatchParameter, span) == 0); assert!(offset_of!(CatchParameter, pattern) == 8); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(DebuggerStatement, span) == 0); + // Padding: 7 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(BindingPattern, kind) == 0); @@ -468,18 +533,21 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(AssignmentPattern, span) == 0); assert!(offset_of!(AssignmentPattern, left) == 8); assert!(offset_of!(AssignmentPattern, right) == 40); - assert!(size_of::() == 48); + // Padding: 0 bytes + assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(ObjectPattern, span) == 0); assert!(offset_of!(ObjectPattern, properties) == 8); - assert!(offset_of!(ObjectPattern, rest) == 40); + assert!(offset_of!(ObjectPattern, rest) == 32); + // Padding: 6 bytes assert!(size_of::() == 64); assert!(align_of::() == 8); assert!(offset_of!(BindingProperty, span) == 0); @@ -488,98 +556,108 @@ const _: () = { assert!(offset_of!(BindingProperty, shorthand) == 56); assert!(offset_of!(BindingProperty, computed) == 57); - assert!(size_of::() == 48); + // Padding: 0 bytes + assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(ArrayPattern, span) == 0); assert!(offset_of!(ArrayPattern, elements) == 8); - assert!(offset_of!(ArrayPattern, rest) == 40); + assert!(offset_of!(ArrayPattern, rest) == 32); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(BindingRestElement, span) == 0); assert!(offset_of!(BindingRestElement, argument) == 8); - assert!(size_of::() == 104); + // Padding: 7 bytes + assert!(size_of::() == 96); assert!(align_of::() == 8); assert!(offset_of!(Function, span) == 0); - assert!(offset_of!(Function, r#type) == 8); - assert!(offset_of!(Function, id) == 16); - assert!(offset_of!(Function, generator) == 48); - assert!(offset_of!(Function, r#async) == 49); - assert!(offset_of!(Function, declare) == 50); - assert!(offset_of!(Function, type_parameters) == 56); - assert!(offset_of!(Function, this_param) == 64); - assert!(offset_of!(Function, params) == 72); - assert!(offset_of!(Function, return_type) == 80); - assert!(offset_of!(Function, body) == 88); - assert!(offset_of!(Function, scope_id) == 96); - assert!(offset_of!(Function, pure) == 100); + assert!(offset_of!(Function, r#type) == 84); + assert!(offset_of!(Function, id) == 8); + assert!(offset_of!(Function, generator) == 85); + assert!(offset_of!(Function, r#async) == 86); + assert!(offset_of!(Function, declare) == 87); + assert!(offset_of!(Function, type_parameters) == 40); + assert!(offset_of!(Function, this_param) == 48); + assert!(offset_of!(Function, params) == 56); + assert!(offset_of!(Function, return_type) == 64); + assert!(offset_of!(Function, body) == 72); + assert!(offset_of!(Function, scope_id) == 80); + assert!(offset_of!(Function, pure) == 88); assert!(size_of::() == 1); assert!(align_of::() == 1); - assert!(size_of::() == 56); + // Padding: 7 bytes + assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(FormalParameters, span) == 0); - assert!(offset_of!(FormalParameters, kind) == 8); - assert!(offset_of!(FormalParameters, items) == 16); - assert!(offset_of!(FormalParameters, rest) == 48); + assert!(offset_of!(FormalParameters, kind) == 40); + assert!(offset_of!(FormalParameters, items) == 8); + assert!(offset_of!(FormalParameters, rest) == 32); - assert!(size_of::() == 80); + // Padding: 5 bytes + assert!(size_of::() == 72); assert!(align_of::() == 8); assert!(offset_of!(FormalParameter, span) == 0); assert!(offset_of!(FormalParameter, decorators) == 8); - assert!(offset_of!(FormalParameter, pattern) == 40); - assert!(offset_of!(FormalParameter, accessibility) == 72); - assert!(offset_of!(FormalParameter, readonly) == 73); - assert!(offset_of!(FormalParameter, r#override) == 74); + assert!(offset_of!(FormalParameter, pattern) == 32); + assert!(offset_of!(FormalParameter, accessibility) == 64); + assert!(offset_of!(FormalParameter, readonly) == 65); + assert!(offset_of!(FormalParameter, r#override) == 66); assert!(size_of::() == 1); assert!(align_of::() == 1); - assert!(size_of::() == 72); + // Padding: 0 bytes + assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(FunctionBody, span) == 0); assert!(offset_of!(FunctionBody, directives) == 8); - assert!(offset_of!(FunctionBody, statements) == 40); + assert!(offset_of!(FunctionBody, statements) == 32); - assert!(size_of::() == 56); + // Padding: 1 bytes + assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(ArrowFunctionExpression, span) == 0); - assert!(offset_of!(ArrowFunctionExpression, expression) == 8); - assert!(offset_of!(ArrowFunctionExpression, r#async) == 9); - assert!(offset_of!(ArrowFunctionExpression, type_parameters) == 16); - assert!(offset_of!(ArrowFunctionExpression, params) == 24); - assert!(offset_of!(ArrowFunctionExpression, return_type) == 32); - assert!(offset_of!(ArrowFunctionExpression, body) == 40); - assert!(offset_of!(ArrowFunctionExpression, scope_id) == 48); - assert!(offset_of!(ArrowFunctionExpression, pure) == 52); + assert!(offset_of!(ArrowFunctionExpression, expression) == 44); + assert!(offset_of!(ArrowFunctionExpression, r#async) == 45); + assert!(offset_of!(ArrowFunctionExpression, type_parameters) == 8); + assert!(offset_of!(ArrowFunctionExpression, params) == 16); + assert!(offset_of!(ArrowFunctionExpression, return_type) == 24); + assert!(offset_of!(ArrowFunctionExpression, body) == 32); + assert!(offset_of!(ArrowFunctionExpression, scope_id) == 40); + assert!(offset_of!(ArrowFunctionExpression, pure) == 46); + // Padding: 7 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(YieldExpression, span) == 0); - assert!(offset_of!(YieldExpression, delegate) == 8); - assert!(offset_of!(YieldExpression, argument) == 16); + assert!(offset_of!(YieldExpression, delegate) == 24); + assert!(offset_of!(YieldExpression, argument) == 8); - assert!(size_of::() == 160); + // Padding: 1 bytes + assert!(size_of::() == 136); assert!(align_of::() == 8); assert!(offset_of!(Class, span) == 0); - assert!(offset_of!(Class, r#type) == 8); - assert!(offset_of!(Class, decorators) == 16); - assert!(offset_of!(Class, id) == 48); - assert!(offset_of!(Class, type_parameters) == 80); - assert!(offset_of!(Class, super_class) == 88); - assert!(offset_of!(Class, super_type_arguments) == 104); - assert!(offset_of!(Class, implements) == 112); - assert!(offset_of!(Class, body) == 144); - assert!(offset_of!(Class, r#abstract) == 152); - assert!(offset_of!(Class, declare) == 153); - assert!(offset_of!(Class, scope_id) == 156); + assert!(offset_of!(Class, r#type) == 132); + assert!(offset_of!(Class, decorators) == 8); + assert!(offset_of!(Class, id) == 32); + assert!(offset_of!(Class, type_parameters) == 64); + assert!(offset_of!(Class, super_class) == 72); + assert!(offset_of!(Class, super_type_arguments) == 88); + assert!(offset_of!(Class, implements) == 96); + assert!(offset_of!(Class, body) == 120); + assert!(offset_of!(Class, r#abstract) == 133); + assert!(offset_of!(Class, declare) == 134); + assert!(offset_of!(Class, scope_id) == 128); assert!(size_of::() == 1); assert!(align_of::() == 1); - assert!(size_of::() == 40); + // Padding: 0 bytes + assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(ClassBody, span) == 0); assert!(offset_of!(ClassBody, body) == 8); @@ -587,39 +665,41 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); - assert!(size_of::() == 80); + // Padding: 1 bytes + assert!(size_of::() == 64); assert!(align_of::() == 8); assert!(offset_of!(MethodDefinition, span) == 0); - assert!(offset_of!(MethodDefinition, r#type) == 8); - assert!(offset_of!(MethodDefinition, decorators) == 16); - assert!(offset_of!(MethodDefinition, key) == 48); - assert!(offset_of!(MethodDefinition, value) == 64); - assert!(offset_of!(MethodDefinition, kind) == 72); - assert!(offset_of!(MethodDefinition, computed) == 73); - assert!(offset_of!(MethodDefinition, r#static) == 74); - assert!(offset_of!(MethodDefinition, r#override) == 75); - assert!(offset_of!(MethodDefinition, optional) == 76); - assert!(offset_of!(MethodDefinition, accessibility) == 77); + assert!(offset_of!(MethodDefinition, r#type) == 56); + assert!(offset_of!(MethodDefinition, decorators) == 8); + assert!(offset_of!(MethodDefinition, key) == 32); + assert!(offset_of!(MethodDefinition, value) == 48); + assert!(offset_of!(MethodDefinition, kind) == 57); + assert!(offset_of!(MethodDefinition, computed) == 58); + assert!(offset_of!(MethodDefinition, r#static) == 59); + assert!(offset_of!(MethodDefinition, r#override) == 60); + assert!(offset_of!(MethodDefinition, optional) == 61); + assert!(offset_of!(MethodDefinition, accessibility) == 62); assert!(size_of::() == 1); assert!(align_of::() == 1); - assert!(size_of::() == 96); + // Padding: 7 bytes + assert!(size_of::() == 88); assert!(align_of::() == 8); assert!(offset_of!(PropertyDefinition, span) == 0); - assert!(offset_of!(PropertyDefinition, r#type) == 8); - assert!(offset_of!(PropertyDefinition, decorators) == 16); - assert!(offset_of!(PropertyDefinition, key) == 48); - assert!(offset_of!(PropertyDefinition, type_annotation) == 64); - assert!(offset_of!(PropertyDefinition, value) == 72); - assert!(offset_of!(PropertyDefinition, computed) == 88); - assert!(offset_of!(PropertyDefinition, r#static) == 89); - assert!(offset_of!(PropertyDefinition, declare) == 90); - assert!(offset_of!(PropertyDefinition, r#override) == 91); - assert!(offset_of!(PropertyDefinition, optional) == 92); - assert!(offset_of!(PropertyDefinition, definite) == 93); - assert!(offset_of!(PropertyDefinition, readonly) == 94); - assert!(offset_of!(PropertyDefinition, accessibility) == 95); + assert!(offset_of!(PropertyDefinition, r#type) == 72); + assert!(offset_of!(PropertyDefinition, decorators) == 8); + assert!(offset_of!(PropertyDefinition, key) == 32); + assert!(offset_of!(PropertyDefinition, type_annotation) == 48); + assert!(offset_of!(PropertyDefinition, value) == 56); + assert!(offset_of!(PropertyDefinition, computed) == 73); + assert!(offset_of!(PropertyDefinition, r#static) == 74); + assert!(offset_of!(PropertyDefinition, declare) == 75); + assert!(offset_of!(PropertyDefinition, r#override) == 76); + assert!(offset_of!(PropertyDefinition, optional) == 77); + assert!(offset_of!(PropertyDefinition, definite) == 78); + assert!(offset_of!(PropertyDefinition, readonly) == 79); + assert!(offset_of!(PropertyDefinition, accessibility) == 80); assert!(size_of::() == 1); assert!(align_of::() == 1); @@ -627,16 +707,18 @@ const _: () = { assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(PrivateIdentifier, span) == 0); assert!(offset_of!(PrivateIdentifier, name) == 8); - assert!(size_of::() == 48); + // Padding: 4 bytes + assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(StaticBlock, span) == 0); assert!(offset_of!(StaticBlock, body) == 8); - assert!(offset_of!(StaticBlock, scope_id) == 40); + assert!(offset_of!(StaticBlock, scope_id) == 32); assert!(size_of::() == 16); assert!(align_of::() == 8); @@ -644,20 +726,22 @@ const _: () = { assert!(size_of::() == 1); assert!(align_of::() == 1); - assert!(size_of::() == 96); + // Padding: 2 bytes + assert!(size_of::() == 80); assert!(align_of::() == 8); assert!(offset_of!(AccessorProperty, span) == 0); - assert!(offset_of!(AccessorProperty, r#type) == 8); - assert!(offset_of!(AccessorProperty, decorators) == 16); - assert!(offset_of!(AccessorProperty, key) == 48); - assert!(offset_of!(AccessorProperty, type_annotation) == 64); - assert!(offset_of!(AccessorProperty, value) == 72); - assert!(offset_of!(AccessorProperty, computed) == 88); - assert!(offset_of!(AccessorProperty, r#static) == 89); - assert!(offset_of!(AccessorProperty, r#override) == 90); - assert!(offset_of!(AccessorProperty, definite) == 91); - assert!(offset_of!(AccessorProperty, accessibility) == 92); - + assert!(offset_of!(AccessorProperty, r#type) == 72); + assert!(offset_of!(AccessorProperty, decorators) == 8); + assert!(offset_of!(AccessorProperty, key) == 32); + assert!(offset_of!(AccessorProperty, type_annotation) == 48); + assert!(offset_of!(AccessorProperty, value) == 56); + assert!(offset_of!(AccessorProperty, computed) == 73); + assert!(offset_of!(AccessorProperty, r#static) == 74); + assert!(offset_of!(AccessorProperty, r#override) == 75); + assert!(offset_of!(AccessorProperty, definite) == 76); + assert!(offset_of!(AccessorProperty, accessibility) == 77); + + // Padding: 7 bytes assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(ImportExpression, span) == 0); @@ -665,14 +749,15 @@ const _: () = { assert!(offset_of!(ImportExpression, options) == 24); assert!(offset_of!(ImportExpression, phase) == 40); - assert!(size_of::() == 112); + // Padding: 6 bytes + assert!(size_of::() == 96); assert!(align_of::() == 8); assert!(offset_of!(ImportDeclaration, span) == 0); assert!(offset_of!(ImportDeclaration, specifiers) == 8); - assert!(offset_of!(ImportDeclaration, source) == 40); + assert!(offset_of!(ImportDeclaration, source) == 32); assert!(offset_of!(ImportDeclaration, phase) == 88); - assert!(offset_of!(ImportDeclaration, with_clause) == 96); - assert!(offset_of!(ImportDeclaration, import_kind) == 104); + assert!(offset_of!(ImportDeclaration, with_clause) == 80); + assert!(offset_of!(ImportDeclaration, import_kind) == 89); assert!(size_of::() == 1); assert!(align_of::() == 1); @@ -680,6 +765,7 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 7 bytes assert!(size_of::() == 104); assert!(align_of::() == 8); assert!(offset_of!(ImportSpecifier, span) == 0); @@ -687,22 +773,26 @@ const _: () = { assert!(offset_of!(ImportSpecifier, local) == 64); assert!(offset_of!(ImportSpecifier, import_kind) == 96); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(ImportDefaultSpecifier, span) == 0); assert!(offset_of!(ImportDefaultSpecifier, local) == 8); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(ImportNamespaceSpecifier, span) == 0); assert!(offset_of!(ImportNamespaceSpecifier, local) == 8); - assert!(size_of::() == 64); + // Padding: 0 bytes + assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(WithClause, span) == 0); assert!(offset_of!(WithClause, attributes_keyword) == 8); assert!(offset_of!(WithClause, with_entries) == 32); + // Padding: 0 bytes assert!(size_of::() == 112); assert!(align_of::() == 8); assert!(offset_of!(ImportAttribute, span) == 0); @@ -712,21 +802,24 @@ const _: () = { assert!(size_of::() == 56); assert!(align_of::() == 8); - assert!(size_of::() == 120); + // Padding: 7 bytes + assert!(size_of::() == 112); assert!(align_of::() == 8); assert!(offset_of!(ExportNamedDeclaration, span) == 0); assert!(offset_of!(ExportNamedDeclaration, declaration) == 8); assert!(offset_of!(ExportNamedDeclaration, specifiers) == 24); - assert!(offset_of!(ExportNamedDeclaration, source) == 56); + assert!(offset_of!(ExportNamedDeclaration, source) == 48); assert!(offset_of!(ExportNamedDeclaration, export_kind) == 104); - assert!(offset_of!(ExportNamedDeclaration, with_clause) == 112); + assert!(offset_of!(ExportNamedDeclaration, with_clause) == 96); + // Padding: 0 bytes assert!(size_of::() == 80); assert!(align_of::() == 8); assert!(offset_of!(ExportDefaultDeclaration, span) == 0); assert!(offset_of!(ExportDefaultDeclaration, exported) == 8); assert!(offset_of!(ExportDefaultDeclaration, declaration) == 64); + // Padding: 7 bytes assert!(size_of::() == 128); assert!(align_of::() == 8); assert!(offset_of!(ExportAllDeclaration, span) == 0); @@ -735,6 +828,7 @@ const _: () = { assert!(offset_of!(ExportAllDeclaration, with_clause) == 112); assert!(offset_of!(ExportAllDeclaration, export_kind) == 120); + // Padding: 7 bytes assert!(size_of::() == 128); assert!(align_of::() == 8); assert!(offset_of!(ExportSpecifier, span) == 0); @@ -748,21 +842,25 @@ const _: () = { assert!(size_of::() == 56); assert!(align_of::() == 8); - assert!(size_of::() == 64); + // Padding: 0 bytes + assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(V8IntrinsicExpression, span) == 0); assert!(offset_of!(V8IntrinsicExpression, name) == 8); assert!(offset_of!(V8IntrinsicExpression, arguments) == 32); + // Padding: 7 bytes assert!(size_of::() == 16); assert!(align_of::() == 8); assert!(offset_of!(BooleanLiteral, span) == 0); assert!(offset_of!(BooleanLiteral, value) == 8); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(NullLiteral, span) == 0); + // Padding: 7 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(NumericLiteral, span) == 0); @@ -770,6 +868,7 @@ const _: () = { assert!(offset_of!(NumericLiteral, raw) == 16); assert!(offset_of!(NumericLiteral, base) == 32); + // Padding: 7 bytes assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(StringLiteral, span) == 0); @@ -777,61 +876,72 @@ const _: () = { assert!(offset_of!(StringLiteral, raw) == 24); assert!(offset_of!(StringLiteral, lone_surrogates) == 40); + // Padding: 7 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(BigIntLiteral, span) == 0); assert!(offset_of!(BigIntLiteral, raw) == 8); assert!(offset_of!(BigIntLiteral, base) == 24); + // Padding: 0 bytes assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(RegExpLiteral, span) == 0); assert!(offset_of!(RegExpLiteral, regex) == 8); assert!(offset_of!(RegExpLiteral, raw) == 40); + // Padding: 7 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(RegExp, pattern) == 0); assert!(offset_of!(RegExp, flags) == 24); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(RegExpPattern, text) == 0); assert!(offset_of!(RegExpPattern, pattern) == 16); + // Padding: 0 bytes assert!(size_of::() == 1); assert!(align_of::() == 1); - assert!(size_of::() == 56); + // Padding: 0 bytes + assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(JSXElement, span) == 0); assert!(offset_of!(JSXElement, opening_element) == 8); assert!(offset_of!(JSXElement, children) == 16); - assert!(offset_of!(JSXElement, closing_element) == 48); + assert!(offset_of!(JSXElement, closing_element) == 40); - assert!(size_of::() == 64); + // Padding: 0 bytes + assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(JSXOpeningElement, span) == 0); assert!(offset_of!(JSXOpeningElement, name) == 8); assert!(offset_of!(JSXOpeningElement, type_arguments) == 24); assert!(offset_of!(JSXOpeningElement, attributes) == 32); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(JSXClosingElement, span) == 0); assert!(offset_of!(JSXClosingElement, name) == 8); - assert!(size_of::() == 56); + // Padding: 0 bytes + assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(JSXFragment, span) == 0); assert!(offset_of!(JSXFragment, opening_fragment) == 8); assert!(offset_of!(JSXFragment, children) == 16); - assert!(offset_of!(JSXFragment, closing_fragment) == 48); + assert!(offset_of!(JSXFragment, closing_fragment) == 40); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(JSXOpeningFragment, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(JSXClosingFragment, span) == 0); @@ -839,12 +949,14 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(JSXNamespacedName, span) == 0); assert!(offset_of!(JSXNamespacedName, namespace) == 8); assert!(offset_of!(JSXNamespacedName, name) == 32); + // Padding: 0 bytes assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(JSXMemberExpression, span) == 0); @@ -854,6 +966,7 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(JSXExpressionContainer, span) == 0); @@ -862,6 +975,7 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(JSXEmptyExpression, span) == 0); @@ -869,12 +983,14 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(JSXAttribute, span) == 0); assert!(offset_of!(JSXAttribute, name) == 8); assert!(offset_of!(JSXAttribute, value) == 24); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(JSXSpreadAttribute, span) == 0); @@ -886,6 +1002,7 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(JSXIdentifier, span) == 0); @@ -894,37 +1011,43 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(JSXSpreadChild, span) == 0); assert!(offset_of!(JSXSpreadChild, expression) == 8); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(JSXText, span) == 0); assert!(offset_of!(JSXText, value) == 8); assert!(offset_of!(JSXText, raw) == 24); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(TSThisParameter, span) == 0); assert!(offset_of!(TSThisParameter, this_span) == 8); assert!(offset_of!(TSThisParameter, type_annotation) == 16); - assert!(size_of::() == 88); + // Padding: 2 bytes + assert!(size_of::() == 80); assert!(align_of::() == 8); assert!(offset_of!(TSEnumDeclaration, span) == 0); assert!(offset_of!(TSEnumDeclaration, id) == 8); assert!(offset_of!(TSEnumDeclaration, body) == 40); - assert!(offset_of!(TSEnumDeclaration, r#const) == 80); - assert!(offset_of!(TSEnumDeclaration, declare) == 81); - assert!(offset_of!(TSEnumDeclaration, scope_id) == 84); + assert!(offset_of!(TSEnumDeclaration, r#const) == 76); + assert!(offset_of!(TSEnumDeclaration, declare) == 77); + assert!(offset_of!(TSEnumDeclaration, scope_id) == 72); - assert!(size_of::() == 40); + // Padding: 0 bytes + assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSEnumBody, span) == 0); assert!(offset_of!(TSEnumBody, members) == 8); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(TSEnumMember, span) == 0); @@ -934,11 +1057,13 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(TSTypeAnnotation, span) == 0); assert!(offset_of!(TSTypeAnnotation, type_annotation) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(TSLiteralType, span) == 0); @@ -950,6 +1075,7 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 4 bytes assert!(size_of::() == 80); assert!(align_of::() == 8); assert!(offset_of!(TSConditionalType, span) == 0); @@ -959,46 +1085,54 @@ const _: () = { assert!(offset_of!(TSConditionalType, false_type) == 56); assert!(offset_of!(TSConditionalType, scope_id) == 72); - assert!(size_of::() == 40); + // Padding: 0 bytes + assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSUnionType, span) == 0); assert!(offset_of!(TSUnionType, types) == 8); - assert!(size_of::() == 40); + // Padding: 0 bytes + assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSIntersectionType, span) == 0); assert!(offset_of!(TSIntersectionType, types) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(TSParenthesizedType, span) == 0); assert!(offset_of!(TSParenthesizedType, type_annotation) == 8); + // Padding: 7 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSTypeOperator, span) == 0); - assert!(offset_of!(TSTypeOperator, operator) == 8); - assert!(offset_of!(TSTypeOperator, type_annotation) == 16); + assert!(offset_of!(TSTypeOperator, operator) == 24); + assert!(offset_of!(TSTypeOperator, type_annotation) == 8); assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(TSArrayType, span) == 0); assert!(offset_of!(TSArrayType, element_type) == 8); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(TSIndexedAccessType, span) == 0); assert!(offset_of!(TSIndexedAccessType, object_type) == 8); assert!(offset_of!(TSIndexedAccessType, index_type) == 24); - assert!(size_of::() == 40); + // Padding: 0 bytes + assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSTupleType, span) == 0); assert!(offset_of!(TSTupleType, element_types) == 8); + // Padding: 7 bytes assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(TSNamedTupleMember, span) == 0); @@ -1006,11 +1140,13 @@ const _: () = { assert!(offset_of!(TSNamedTupleMember, element_type) == 32); assert!(offset_of!(TSNamedTupleMember, optional) == 48); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(TSOptionalType, span) == 0); assert!(offset_of!(TSOptionalType, type_annotation) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(TSRestType, span) == 0); @@ -1019,62 +1155,77 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(TSAnyKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(TSStringKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(TSBooleanKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(TSNumberKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(TSNeverKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(TSIntrinsicKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(TSUnknownKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(TSNullKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(TSUndefinedKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(TSVoidKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(TSSymbolKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(TSThisType, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(TSObjectKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(TSBigIntKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSTypeReference, span) == 0); @@ -1084,17 +1235,20 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(TSQualifiedName, span) == 0); assert!(offset_of!(TSQualifiedName, left) == 8); assert!(offset_of!(TSQualifiedName, right) == 24); - assert!(size_of::() == 40); + // Padding: 0 bytes + assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSTypeParameterInstantiation, span) == 0); assert!(offset_of!(TSTypeParameterInstantiation, params) == 8); + // Padding: 5 bytes assert!(size_of::() == 80); assert!(align_of::() == 8); assert!(offset_of!(TSTypeParameter, span) == 0); @@ -1105,64 +1259,72 @@ const _: () = { assert!(offset_of!(TSTypeParameter, out) == 73); assert!(offset_of!(TSTypeParameter, r#const) == 74); - assert!(size_of::() == 40); + // Padding: 0 bytes + assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSTypeParameterDeclaration, span) == 0); assert!(offset_of!(TSTypeParameterDeclaration, params) == 8); + // Padding: 3 bytes assert!(size_of::() == 72); assert!(align_of::() == 8); assert!(offset_of!(TSTypeAliasDeclaration, span) == 0); assert!(offset_of!(TSTypeAliasDeclaration, id) == 8); assert!(offset_of!(TSTypeAliasDeclaration, type_parameters) == 40); assert!(offset_of!(TSTypeAliasDeclaration, type_annotation) == 48); - assert!(offset_of!(TSTypeAliasDeclaration, declare) == 64); - assert!(offset_of!(TSTypeAliasDeclaration, scope_id) == 68); + assert!(offset_of!(TSTypeAliasDeclaration, declare) == 68); + assert!(offset_of!(TSTypeAliasDeclaration, scope_id) == 64); assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSClassImplements, span) == 0); assert!(offset_of!(TSClassImplements, expression) == 8); assert!(offset_of!(TSClassImplements, type_arguments) == 24); - assert!(size_of::() == 96); + // Padding: 3 bytes + assert!(size_of::() == 88); assert!(align_of::() == 8); assert!(offset_of!(TSInterfaceDeclaration, span) == 0); assert!(offset_of!(TSInterfaceDeclaration, id) == 8); assert!(offset_of!(TSInterfaceDeclaration, type_parameters) == 40); assert!(offset_of!(TSInterfaceDeclaration, extends) == 48); - assert!(offset_of!(TSInterfaceDeclaration, body) == 80); - assert!(offset_of!(TSInterfaceDeclaration, declare) == 88); - assert!(offset_of!(TSInterfaceDeclaration, scope_id) == 92); + assert!(offset_of!(TSInterfaceDeclaration, body) == 72); + assert!(offset_of!(TSInterfaceDeclaration, declare) == 84); + assert!(offset_of!(TSInterfaceDeclaration, scope_id) == 80); - assert!(size_of::() == 40); + // Padding: 0 bytes + assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSInterfaceBody, span) == 0); assert!(offset_of!(TSInterfaceBody, body) == 8); + // Padding: 5 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(TSPropertySignature, span) == 0); - assert!(offset_of!(TSPropertySignature, computed) == 8); - assert!(offset_of!(TSPropertySignature, optional) == 9); - assert!(offset_of!(TSPropertySignature, readonly) == 10); - assert!(offset_of!(TSPropertySignature, key) == 16); - assert!(offset_of!(TSPropertySignature, type_annotation) == 32); + assert!(offset_of!(TSPropertySignature, computed) == 32); + assert!(offset_of!(TSPropertySignature, optional) == 33); + assert!(offset_of!(TSPropertySignature, readonly) == 34); + assert!(offset_of!(TSPropertySignature, key) == 8); + assert!(offset_of!(TSPropertySignature, type_annotation) == 24); assert!(size_of::() == 16); assert!(align_of::() == 8); - assert!(size_of::() == 56); + // Padding: 6 bytes + assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(TSIndexSignature, span) == 0); assert!(offset_of!(TSIndexSignature, parameters) == 8); - assert!(offset_of!(TSIndexSignature, type_annotation) == 40); - assert!(offset_of!(TSIndexSignature, readonly) == 48); - assert!(offset_of!(TSIndexSignature, r#static) == 49); + assert!(offset_of!(TSIndexSignature, type_annotation) == 32); + assert!(offset_of!(TSIndexSignature, readonly) == 40); + assert!(offset_of!(TSIndexSignature, r#static) == 41); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(TSCallSignatureDeclaration, span) == 0); @@ -1174,19 +1336,21 @@ const _: () = { assert!(size_of::() == 1); assert!(align_of::() == 1); - assert!(size_of::() == 72); + // Padding: 1 bytes + assert!(size_of::() == 64); assert!(align_of::() == 8); assert!(offset_of!(TSMethodSignature, span) == 0); assert!(offset_of!(TSMethodSignature, key) == 8); - assert!(offset_of!(TSMethodSignature, computed) == 24); - assert!(offset_of!(TSMethodSignature, optional) == 25); - assert!(offset_of!(TSMethodSignature, kind) == 26); - assert!(offset_of!(TSMethodSignature, type_parameters) == 32); - assert!(offset_of!(TSMethodSignature, this_param) == 40); - assert!(offset_of!(TSMethodSignature, params) == 48); - assert!(offset_of!(TSMethodSignature, return_type) == 56); - assert!(offset_of!(TSMethodSignature, scope_id) == 64); - + assert!(offset_of!(TSMethodSignature, computed) == 60); + assert!(offset_of!(TSMethodSignature, optional) == 61); + assert!(offset_of!(TSMethodSignature, kind) == 62); + assert!(offset_of!(TSMethodSignature, type_parameters) == 24); + assert!(offset_of!(TSMethodSignature, this_param) == 32); + assert!(offset_of!(TSMethodSignature, params) == 40); + assert!(offset_of!(TSMethodSignature, return_type) == 48); + assert!(offset_of!(TSMethodSignature, scope_id) == 56); + + // Padding: 4 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(TSConstructSignatureDeclaration, span) == 0); @@ -1195,36 +1359,40 @@ const _: () = { assert!(offset_of!(TSConstructSignatureDeclaration, return_type) == 24); assert!(offset_of!(TSConstructSignatureDeclaration, scope_id) == 32); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSIndexSignatureName, span) == 0); assert!(offset_of!(TSIndexSignatureName, name) == 8); assert!(offset_of!(TSIndexSignatureName, type_annotation) == 24); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSInterfaceHeritage, span) == 0); assert!(offset_of!(TSInterfaceHeritage, expression) == 8); assert!(offset_of!(TSInterfaceHeritage, type_arguments) == 24); + // Padding: 7 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(TSTypePredicate, span) == 0); assert!(offset_of!(TSTypePredicate, parameter_name) == 8); - assert!(offset_of!(TSTypePredicate, asserts) == 24); - assert!(offset_of!(TSTypePredicate, type_annotation) == 32); + assert!(offset_of!(TSTypePredicate, asserts) == 32); + assert!(offset_of!(TSTypePredicate, type_annotation) == 24); assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 2 bytes assert!(size_of::() == 88); assert!(align_of::() == 8); assert!(offset_of!(TSModuleDeclaration, span) == 0); assert!(offset_of!(TSModuleDeclaration, id) == 8); assert!(offset_of!(TSModuleDeclaration, body) == 64); - assert!(offset_of!(TSModuleDeclaration, kind) == 80); - assert!(offset_of!(TSModuleDeclaration, declare) == 81); - assert!(offset_of!(TSModuleDeclaration, scope_id) == 84); + assert!(offset_of!(TSModuleDeclaration, kind) == 84); + assert!(offset_of!(TSModuleDeclaration, declare) == 85); + assert!(offset_of!(TSModuleDeclaration, scope_id) == 80); assert!(size_of::() == 1); assert!(align_of::() == 1); @@ -1235,22 +1403,26 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); - assert!(size_of::() == 72); + // Padding: 0 bytes + assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(TSModuleBlock, span) == 0); assert!(offset_of!(TSModuleBlock, directives) == 8); - assert!(offset_of!(TSModuleBlock, body) == 40); + assert!(offset_of!(TSModuleBlock, body) == 32); - assert!(size_of::() == 40); + // Padding: 0 bytes + assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSTypeLiteral, span) == 0); assert!(offset_of!(TSTypeLiteral, members) == 8); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 8); assert!(offset_of!(TSInferType, span) == 0); assert!(offset_of!(TSInferType, type_parameter) == 8); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSTypeQuery, span) == 0); @@ -1260,6 +1432,7 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(TSImportType, span) == 0); @@ -1268,6 +1441,7 @@ const _: () = { assert!(offset_of!(TSImportType, qualifier) == 32); assert!(offset_of!(TSImportType, type_arguments) == 48); + // Padding: 4 bytes assert!(size_of::() == 48); assert!(align_of::() == 8); assert!(offset_of!(TSFunctionType, span) == 0); @@ -1277,51 +1451,58 @@ const _: () = { assert!(offset_of!(TSFunctionType, return_type) == 32); assert!(offset_of!(TSFunctionType, scope_id) == 40); + // Padding: 7 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(TSConstructorType, span) == 0); - assert!(offset_of!(TSConstructorType, r#abstract) == 8); - assert!(offset_of!(TSConstructorType, type_parameters) == 16); - assert!(offset_of!(TSConstructorType, params) == 24); - assert!(offset_of!(TSConstructorType, return_type) == 32); + assert!(offset_of!(TSConstructorType, r#abstract) == 32); + assert!(offset_of!(TSConstructorType, type_parameters) == 8); + assert!(offset_of!(TSConstructorType, params) == 16); + assert!(offset_of!(TSConstructorType, return_type) == 24); + // Padding: 2 bytes assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(TSMappedType, span) == 0); assert!(offset_of!(TSMappedType, type_parameter) == 8); assert!(offset_of!(TSMappedType, name_type) == 16); assert!(offset_of!(TSMappedType, type_annotation) == 32); - assert!(offset_of!(TSMappedType, optional) == 48); - assert!(offset_of!(TSMappedType, readonly) == 49); - assert!(offset_of!(TSMappedType, scope_id) == 52); + assert!(offset_of!(TSMappedType, optional) == 52); + assert!(offset_of!(TSMappedType, readonly) == 53); + assert!(offset_of!(TSMappedType, scope_id) == 48); assert!(size_of::() == 1); assert!(align_of::() == 1); - assert!(size_of::() == 72); + // Padding: 0 bytes + assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(TSTemplateLiteralType, span) == 0); assert!(offset_of!(TSTemplateLiteralType, quasis) == 8); - assert!(offset_of!(TSTemplateLiteralType, types) == 40); + assert!(offset_of!(TSTemplateLiteralType, types) == 32); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(TSAsExpression, span) == 0); assert!(offset_of!(TSAsExpression, expression) == 8); assert!(offset_of!(TSAsExpression, type_annotation) == 24); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(TSSatisfiesExpression, span) == 0); assert!(offset_of!(TSSatisfiesExpression, expression) == 8); assert!(offset_of!(TSSatisfiesExpression, type_annotation) == 24); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 8); assert!(offset_of!(TSTypeAssertion, span) == 0); assert!(offset_of!(TSTypeAssertion, type_annotation) == 8); assert!(offset_of!(TSTypeAssertion, expression) == 24); + // Padding: 7 bytes assert!(size_of::() == 64); assert!(align_of::() == 8); assert!(offset_of!(TSImportEqualsDeclaration, span) == 0); @@ -1332,31 +1513,37 @@ const _: () = { assert!(size_of::() == 16); assert!(align_of::() == 8); + // Padding: 0 bytes assert!(size_of::() == 56); assert!(align_of::() == 8); assert!(offset_of!(TSExternalModuleReference, span) == 0); assert!(offset_of!(TSExternalModuleReference, expression) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(TSNonNullExpression, span) == 0); assert!(offset_of!(TSNonNullExpression, expression) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(Decorator, span) == 0); assert!(offset_of!(Decorator, expression) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 8); assert!(offset_of!(TSExportAssignment, span) == 0); assert!(offset_of!(TSExportAssignment, expression) == 8); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSNamespaceExportDeclaration, span) == 0); assert!(offset_of!(TSNamespaceExportDeclaration, id) == 8); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(TSInstantiationExpression, span) == 0); @@ -1366,18 +1553,21 @@ const _: () = { assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 7 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(JSDocNullableType, span) == 0); assert!(offset_of!(JSDocNullableType, type_annotation) == 8); assert!(offset_of!(JSDocNullableType, postfix) == 24); + // Padding: 7 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(JSDocNonNullableType, span) == 0); assert!(offset_of!(JSDocNonNullableType, type_annotation) == 8); assert!(offset_of!(JSDocNonNullableType, postfix) == 24); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 8); assert!(offset_of!(JSDocUnknownType, span) == 0); @@ -1391,59 +1581,69 @@ const _: () = { assert!(size_of::() == 1); assert!(align_of::() == 1); - assert!(size_of::() == 24); + assert!(size_of::() == 1); + assert!(align_of::() == 1); + + // Padding: 0 bytes + assert!(size_of::() == 16); assert!(align_of::() == 8); assert!(offset_of!(Comment, span) == 0); assert!(offset_of!(Comment, attached_to) == 8); assert!(offset_of!(Comment, kind) == 12); assert!(offset_of!(Comment, position) == 13); - assert!(offset_of!(Comment, preceded_by_newline) == 14); - assert!(offset_of!(Comment, followed_by_newline) == 15); - assert!(offset_of!(Comment, annotation) == 16); + assert!(offset_of!(Comment, newlines) == 14); + assert!(offset_of!(Comment, annotation) == 15); }; #[cfg(target_pointer_width = "32")] const _: () = { + // Padding: 1 bytes assert!(size_of::() == 88); assert!(align_of::() == 4); assert!(offset_of!(Program, span) == 0); - assert!(offset_of!(Program, source_type) == 8); - assert!(offset_of!(Program, source_text) == 12); - assert!(offset_of!(Program, comments) == 20); - assert!(offset_of!(Program, hashbang) == 36); - assert!(offset_of!(Program, directives) == 52); - assert!(offset_of!(Program, body) == 68); - assert!(offset_of!(Program, scope_id) == 84); + assert!(offset_of!(Program, source_type) == 84); + assert!(offset_of!(Program, source_text) == 8); + assert!(offset_of!(Program, comments) == 16); + assert!(offset_of!(Program, hashbang) == 32); + assert!(offset_of!(Program, directives) == 48); + assert!(offset_of!(Program, body) == 64); + assert!(offset_of!(Program, scope_id) == 80); assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(IdentifierName, span) == 0); assert!(offset_of!(IdentifierName, name) == 8); + // Padding: 0 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(IdentifierReference, span) == 0); assert!(offset_of!(IdentifierReference, name) == 8); assert!(offset_of!(IdentifierReference, reference_id) == 16); + // Padding: 0 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(BindingIdentifier, span) == 0); assert!(offset_of!(BindingIdentifier, name) == 8); assert!(offset_of!(BindingIdentifier, symbol_id) == 16); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(LabelIdentifier, span) == 0); assert!(offset_of!(LabelIdentifier, name) == 8); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(ThisExpression, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(ArrayExpression, span) == 0); @@ -1452,10 +1652,12 @@ const _: () = { assert!(size_of::() == 12); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(Elision, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(ObjectExpression, span) == 0); @@ -1464,15 +1666,16 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); - assert!(size_of::() == 32); + // Padding: 0 bytes + assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(ObjectProperty, span) == 0); - assert!(offset_of!(ObjectProperty, kind) == 8); - assert!(offset_of!(ObjectProperty, key) == 12); - assert!(offset_of!(ObjectProperty, value) == 20); - assert!(offset_of!(ObjectProperty, method) == 28); - assert!(offset_of!(ObjectProperty, shorthand) == 29); - assert!(offset_of!(ObjectProperty, computed) == 30); + assert!(offset_of!(ObjectProperty, kind) == 24); + assert!(offset_of!(ObjectProperty, key) == 8); + assert!(offset_of!(ObjectProperty, value) == 16); + assert!(offset_of!(ObjectProperty, method) == 25); + assert!(offset_of!(ObjectProperty, shorthand) == 26); + assert!(offset_of!(ObjectProperty, computed) == 27); assert!(size_of::() == 8); assert!(align_of::() == 4); @@ -1480,12 +1683,14 @@ const _: () = { assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(TemplateLiteral, span) == 0); assert!(offset_of!(TemplateLiteral, quasis) == 8); assert!(offset_of!(TemplateLiteral, expressions) == 24); + // Padding: 0 bytes assert!(size_of::() == 60); assert!(align_of::() == 4); assert!(offset_of!(TaggedTemplateExpression, span) == 0); @@ -1493,6 +1698,7 @@ const _: () = { assert!(offset_of!(TaggedTemplateExpression, type_arguments) == 16); assert!(offset_of!(TaggedTemplateExpression, quasi) == 20); + // Padding: 2 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(TemplateElement, span) == 0); @@ -1500,6 +1706,7 @@ const _: () = { assert!(offset_of!(TemplateElement, tail) == 24); assert!(offset_of!(TemplateElement, lone_surrogates) == 25); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(TemplateElementValue, raw) == 0); @@ -1508,6 +1715,7 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 3 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(ComputedMemberExpression, span) == 0); @@ -1515,6 +1723,7 @@ const _: () = { assert!(offset_of!(ComputedMemberExpression, expression) == 16); assert!(offset_of!(ComputedMemberExpression, optional) == 24); + // Padding: 3 bytes assert!(size_of::() == 36); assert!(align_of::() == 4); assert!(offset_of!(StaticMemberExpression, span) == 0); @@ -1522,6 +1731,7 @@ const _: () = { assert!(offset_of!(StaticMemberExpression, property) == 16); assert!(offset_of!(StaticMemberExpression, optional) == 32); + // Padding: 3 bytes assert!(size_of::() == 36); assert!(align_of::() == 4); assert!(offset_of!(PrivateFieldExpression, span) == 0); @@ -1529,6 +1739,7 @@ const _: () = { assert!(offset_of!(PrivateFieldExpression, field) == 16); assert!(offset_of!(PrivateFieldExpression, optional) == 32); + // Padding: 2 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(CallExpression, span) == 0); @@ -1538,6 +1749,7 @@ const _: () = { assert!(offset_of!(CallExpression, optional) == 36); assert!(offset_of!(CallExpression, pure) == 37); + // Padding: 3 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(NewExpression, span) == 0); @@ -1546,12 +1758,14 @@ const _: () = { assert!(offset_of!(NewExpression, arguments) == 20); assert!(offset_of!(NewExpression, pure) == 36); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(MetaProperty, span) == 0); assert!(offset_of!(MetaProperty, meta) == 8); assert!(offset_of!(MetaProperty, property) == 24); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(SpreadElement, span) == 0); @@ -1560,39 +1774,45 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 2 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(UpdateExpression, span) == 0); - assert!(offset_of!(UpdateExpression, operator) == 8); - assert!(offset_of!(UpdateExpression, prefix) == 9); - assert!(offset_of!(UpdateExpression, argument) == 12); + assert!(offset_of!(UpdateExpression, operator) == 16); + assert!(offset_of!(UpdateExpression, prefix) == 17); + assert!(offset_of!(UpdateExpression, argument) == 8); + // Padding: 3 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(UnaryExpression, span) == 0); - assert!(offset_of!(UnaryExpression, operator) == 8); - assert!(offset_of!(UnaryExpression, argument) == 12); + assert!(offset_of!(UnaryExpression, operator) == 16); + assert!(offset_of!(UnaryExpression, argument) == 8); + // Padding: 3 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(BinaryExpression, span) == 0); assert!(offset_of!(BinaryExpression, left) == 8); - assert!(offset_of!(BinaryExpression, operator) == 16); - assert!(offset_of!(BinaryExpression, right) == 20); + assert!(offset_of!(BinaryExpression, operator) == 24); + assert!(offset_of!(BinaryExpression, right) == 16); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 4); assert!(offset_of!(PrivateInExpression, span) == 0); assert!(offset_of!(PrivateInExpression, left) == 8); assert!(offset_of!(PrivateInExpression, right) == 24); + // Padding: 3 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(LogicalExpression, span) == 0); assert!(offset_of!(LogicalExpression, left) == 8); - assert!(offset_of!(LogicalExpression, operator) == 16); - assert!(offset_of!(LogicalExpression, right) == 20); + assert!(offset_of!(LogicalExpression, operator) == 24); + assert!(offset_of!(LogicalExpression, right) == 16); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 4); assert!(offset_of!(ConditionalExpression, span) == 0); @@ -1600,12 +1820,13 @@ const _: () = { assert!(offset_of!(ConditionalExpression, consequent) == 16); assert!(offset_of!(ConditionalExpression, alternate) == 24); + // Padding: 3 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(AssignmentExpression, span) == 0); - assert!(offset_of!(AssignmentExpression, operator) == 8); - assert!(offset_of!(AssignmentExpression, left) == 12); - assert!(offset_of!(AssignmentExpression, right) == 20); + assert!(offset_of!(AssignmentExpression, operator) == 24); + assert!(offset_of!(AssignmentExpression, left) == 8); + assert!(offset_of!(AssignmentExpression, right) == 16); assert!(size_of::() == 8); assert!(align_of::() == 4); @@ -1616,18 +1837,21 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(ArrayAssignmentTarget, span) == 0); assert!(offset_of!(ArrayAssignmentTarget, elements) == 8); assert!(offset_of!(ArrayAssignmentTarget, rest) == 24); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(ObjectAssignmentTarget, span) == 0); assert!(offset_of!(ObjectAssignmentTarget, properties) == 8); assert!(offset_of!(ObjectAssignmentTarget, rest) == 24); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(AssignmentTargetRest, span) == 0); @@ -1636,6 +1860,7 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(AssignmentTargetWithDefault, span) == 0); @@ -1645,12 +1870,14 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 36); assert!(align_of::() == 4); assert!(offset_of!(AssignmentTargetPropertyIdentifier, span) == 0); assert!(offset_of!(AssignmentTargetPropertyIdentifier, binding) == 8); assert!(offset_of!(AssignmentTargetPropertyIdentifier, init) == 28); + // Padding: 3 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(AssignmentTargetPropertyProperty, span) == 0); @@ -1658,20 +1885,24 @@ const _: () = { assert!(offset_of!(AssignmentTargetPropertyProperty, binding) == 16); assert!(offset_of!(AssignmentTargetPropertyProperty, computed) == 24); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(SequenceExpression, span) == 0); assert!(offset_of!(SequenceExpression, expressions) == 8); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(Super, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(AwaitExpression, span) == 0); assert!(offset_of!(AwaitExpression, argument) == 8); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(ChainExpression, span) == 0); @@ -1680,6 +1911,7 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(ParenthesizedExpression, span) == 0); @@ -1688,17 +1920,20 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 44); assert!(align_of::() == 4); assert!(offset_of!(Directive, span) == 0); assert!(offset_of!(Directive, expression) == 8); assert!(offset_of!(Directive, directive) == 36); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(Hashbang, span) == 0); assert!(offset_of!(Hashbang, value) == 8); + // Padding: 0 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(BlockStatement, span) == 0); @@ -1708,33 +1943,38 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); - assert!(size_of::() == 32); + // Padding: 2 bytes + assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(VariableDeclaration, span) == 0); - assert!(offset_of!(VariableDeclaration, kind) == 8); - assert!(offset_of!(VariableDeclaration, declarations) == 12); - assert!(offset_of!(VariableDeclaration, declare) == 28); + assert!(offset_of!(VariableDeclaration, kind) == 24); + assert!(offset_of!(VariableDeclaration, declarations) == 8); + assert!(offset_of!(VariableDeclaration, declare) == 25); assert!(size_of::() == 1); assert!(align_of::() == 1); - assert!(size_of::() == 40); + // Padding: 2 bytes + assert!(size_of::() == 36); assert!(align_of::() == 4); assert!(offset_of!(VariableDeclarator, span) == 0); - assert!(offset_of!(VariableDeclarator, kind) == 8); - assert!(offset_of!(VariableDeclarator, id) == 12); - assert!(offset_of!(VariableDeclarator, init) == 28); - assert!(offset_of!(VariableDeclarator, definite) == 36); + assert!(offset_of!(VariableDeclarator, kind) == 32); + assert!(offset_of!(VariableDeclarator, id) == 8); + assert!(offset_of!(VariableDeclarator, init) == 24); + assert!(offset_of!(VariableDeclarator, definite) == 33); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(EmptyStatement, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(ExpressionStatement, span) == 0); assert!(offset_of!(ExpressionStatement, expression) == 8); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 4); assert!(offset_of!(IfStatement, span) == 0); @@ -1742,18 +1982,21 @@ const _: () = { assert!(offset_of!(IfStatement, consequent) == 16); assert!(offset_of!(IfStatement, alternate) == 24); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(DoWhileStatement, span) == 0); assert!(offset_of!(DoWhileStatement, body) == 8); assert!(offset_of!(DoWhileStatement, test) == 16); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(WhileStatement, span) == 0); assert!(offset_of!(WhileStatement, test) == 8); assert!(offset_of!(WhileStatement, body) == 16); + // Padding: 0 bytes assert!(size_of::() == 44); assert!(align_of::() == 4); assert!(offset_of!(ForStatement, span) == 0); @@ -1766,6 +2009,7 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 36); assert!(align_of::() == 4); assert!(offset_of!(ForInStatement, span) == 0); @@ -1777,36 +2021,42 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 3 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(ForOfStatement, span) == 0); - assert!(offset_of!(ForOfStatement, r#await) == 8); - assert!(offset_of!(ForOfStatement, left) == 12); - assert!(offset_of!(ForOfStatement, right) == 20); - assert!(offset_of!(ForOfStatement, body) == 28); - assert!(offset_of!(ForOfStatement, scope_id) == 36); + assert!(offset_of!(ForOfStatement, r#await) == 36); + assert!(offset_of!(ForOfStatement, left) == 8); + assert!(offset_of!(ForOfStatement, right) == 16); + assert!(offset_of!(ForOfStatement, body) == 24); + assert!(offset_of!(ForOfStatement, scope_id) == 32); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(ContinueStatement, span) == 0); assert!(offset_of!(ContinueStatement, label) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(BreakStatement, span) == 0); assert!(offset_of!(BreakStatement, label) == 8); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(ReturnStatement, span) == 0); assert!(offset_of!(ReturnStatement, argument) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(WithStatement, span) == 0); assert!(offset_of!(WithStatement, object) == 8); assert!(offset_of!(WithStatement, body) == 16); + // Padding: 0 bytes assert!(size_of::() == 36); assert!(align_of::() == 4); assert!(offset_of!(SwitchStatement, span) == 0); @@ -1814,23 +2064,27 @@ const _: () = { assert!(offset_of!(SwitchStatement, cases) == 16); assert!(offset_of!(SwitchStatement, scope_id) == 32); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 4); assert!(offset_of!(SwitchCase, span) == 0); assert!(offset_of!(SwitchCase, test) == 8); assert!(offset_of!(SwitchCase, consequent) == 16); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 4); assert!(offset_of!(LabeledStatement, span) == 0); assert!(offset_of!(LabeledStatement, label) == 8); assert!(offset_of!(LabeledStatement, body) == 24); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(ThrowStatement, span) == 0); assert!(offset_of!(ThrowStatement, argument) == 8); + // Padding: 0 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(TryStatement, span) == 0); @@ -1838,6 +2092,7 @@ const _: () = { assert!(offset_of!(TryStatement, handler) == 12); assert!(offset_of!(TryStatement, finalizer) == 16); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(CatchClause, span) == 0); @@ -1845,15 +2100,18 @@ const _: () = { assert!(offset_of!(CatchClause, body) == 32); assert!(offset_of!(CatchClause, scope_id) == 36); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(CatchParameter, span) == 0); assert!(offset_of!(CatchParameter, pattern) == 8); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(DebuggerStatement, span) == 0); + // Padding: 3 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(BindingPattern, kind) == 0); @@ -1863,18 +2121,21 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 4); assert!(offset_of!(AssignmentPattern, span) == 0); assert!(offset_of!(AssignmentPattern, left) == 8); assert!(offset_of!(AssignmentPattern, right) == 24); + // Padding: 0 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(ObjectPattern, span) == 0); assert!(offset_of!(ObjectPattern, properties) == 8); assert!(offset_of!(ObjectPattern, rest) == 24); + // Padding: 2 bytes assert!(size_of::() == 36); assert!(align_of::() == 4); assert!(offset_of!(BindingProperty, span) == 0); @@ -1883,43 +2144,48 @@ const _: () = { assert!(offset_of!(BindingProperty, shorthand) == 32); assert!(offset_of!(BindingProperty, computed) == 33); + // Padding: 0 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(ArrayPattern, span) == 0); assert!(offset_of!(ArrayPattern, elements) == 8); assert!(offset_of!(ArrayPattern, rest) == 24); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(BindingRestElement, span) == 0); assert!(offset_of!(BindingRestElement, argument) == 8); - assert!(size_of::() == 64); + // Padding: 3 bytes + assert!(size_of::() == 60); assert!(align_of::() == 4); assert!(offset_of!(Function, span) == 0); - assert!(offset_of!(Function, r#type) == 8); - assert!(offset_of!(Function, id) == 12); - assert!(offset_of!(Function, generator) == 32); - assert!(offset_of!(Function, r#async) == 33); - assert!(offset_of!(Function, declare) == 34); - assert!(offset_of!(Function, type_parameters) == 36); - assert!(offset_of!(Function, this_param) == 40); - assert!(offset_of!(Function, params) == 44); - assert!(offset_of!(Function, return_type) == 48); - assert!(offset_of!(Function, body) == 52); - assert!(offset_of!(Function, scope_id) == 56); - assert!(offset_of!(Function, pure) == 60); + assert!(offset_of!(Function, r#type) == 52); + assert!(offset_of!(Function, id) == 8); + assert!(offset_of!(Function, generator) == 53); + assert!(offset_of!(Function, r#async) == 54); + assert!(offset_of!(Function, declare) == 55); + assert!(offset_of!(Function, type_parameters) == 28); + assert!(offset_of!(Function, this_param) == 32); + assert!(offset_of!(Function, params) == 36); + assert!(offset_of!(Function, return_type) == 40); + assert!(offset_of!(Function, body) == 44); + assert!(offset_of!(Function, scope_id) == 48); + assert!(offset_of!(Function, pure) == 56); assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 3 bytes assert!(size_of::() == 32); assert!(align_of::() == 4); assert!(offset_of!(FormalParameters, span) == 0); - assert!(offset_of!(FormalParameters, kind) == 8); - assert!(offset_of!(FormalParameters, items) == 12); - assert!(offset_of!(FormalParameters, rest) == 28); + assert!(offset_of!(FormalParameters, kind) == 28); + assert!(offset_of!(FormalParameters, items) == 8); + assert!(offset_of!(FormalParameters, rest) == 24); + // Padding: 1 bytes assert!(size_of::() == 44); assert!(align_of::() == 4); assert!(offset_of!(FormalParameter, span) == 0); @@ -1932,48 +2198,53 @@ const _: () = { assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(FunctionBody, span) == 0); assert!(offset_of!(FunctionBody, directives) == 8); assert!(offset_of!(FunctionBody, statements) == 24); - assert!(size_of::() == 36); + // Padding: 1 bytes + assert!(size_of::() == 32); assert!(align_of::() == 4); assert!(offset_of!(ArrowFunctionExpression, span) == 0); - assert!(offset_of!(ArrowFunctionExpression, expression) == 8); - assert!(offset_of!(ArrowFunctionExpression, r#async) == 9); - assert!(offset_of!(ArrowFunctionExpression, type_parameters) == 12); - assert!(offset_of!(ArrowFunctionExpression, params) == 16); - assert!(offset_of!(ArrowFunctionExpression, return_type) == 20); - assert!(offset_of!(ArrowFunctionExpression, body) == 24); - assert!(offset_of!(ArrowFunctionExpression, scope_id) == 28); - assert!(offset_of!(ArrowFunctionExpression, pure) == 32); - + assert!(offset_of!(ArrowFunctionExpression, expression) == 28); + assert!(offset_of!(ArrowFunctionExpression, r#async) == 29); + assert!(offset_of!(ArrowFunctionExpression, type_parameters) == 8); + assert!(offset_of!(ArrowFunctionExpression, params) == 12); + assert!(offset_of!(ArrowFunctionExpression, return_type) == 16); + assert!(offset_of!(ArrowFunctionExpression, body) == 20); + assert!(offset_of!(ArrowFunctionExpression, scope_id) == 24); + assert!(offset_of!(ArrowFunctionExpression, pure) == 30); + + // Padding: 3 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(YieldExpression, span) == 0); - assert!(offset_of!(YieldExpression, delegate) == 8); - assert!(offset_of!(YieldExpression, argument) == 12); + assert!(offset_of!(YieldExpression, delegate) == 16); + assert!(offset_of!(YieldExpression, argument) == 8); - assert!(size_of::() == 92); + // Padding: 1 bytes + assert!(size_of::() == 88); assert!(align_of::() == 4); assert!(offset_of!(Class, span) == 0); - assert!(offset_of!(Class, r#type) == 8); - assert!(offset_of!(Class, decorators) == 12); - assert!(offset_of!(Class, id) == 28); - assert!(offset_of!(Class, type_parameters) == 48); - assert!(offset_of!(Class, super_class) == 52); - assert!(offset_of!(Class, super_type_arguments) == 60); - assert!(offset_of!(Class, implements) == 64); - assert!(offset_of!(Class, body) == 80); - assert!(offset_of!(Class, r#abstract) == 84); - assert!(offset_of!(Class, declare) == 85); - assert!(offset_of!(Class, scope_id) == 88); + assert!(offset_of!(Class, r#type) == 84); + assert!(offset_of!(Class, decorators) == 8); + assert!(offset_of!(Class, id) == 24); + assert!(offset_of!(Class, type_parameters) == 44); + assert!(offset_of!(Class, super_class) == 48); + assert!(offset_of!(Class, super_type_arguments) == 56); + assert!(offset_of!(Class, implements) == 60); + assert!(offset_of!(Class, body) == 76); + assert!(offset_of!(Class, r#abstract) == 85); + assert!(offset_of!(Class, declare) == 86); + assert!(offset_of!(Class, scope_id) == 80); assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(ClassBody, span) == 0); @@ -1982,39 +2253,41 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); - assert!(size_of::() == 48); + // Padding: 1 bytes + assert!(size_of::() == 44); assert!(align_of::() == 4); assert!(offset_of!(MethodDefinition, span) == 0); - assert!(offset_of!(MethodDefinition, r#type) == 8); - assert!(offset_of!(MethodDefinition, decorators) == 12); - assert!(offset_of!(MethodDefinition, key) == 28); - assert!(offset_of!(MethodDefinition, value) == 36); - assert!(offset_of!(MethodDefinition, kind) == 40); - assert!(offset_of!(MethodDefinition, computed) == 41); - assert!(offset_of!(MethodDefinition, r#static) == 42); - assert!(offset_of!(MethodDefinition, r#override) == 43); - assert!(offset_of!(MethodDefinition, optional) == 44); - assert!(offset_of!(MethodDefinition, accessibility) == 45); + assert!(offset_of!(MethodDefinition, r#type) == 36); + assert!(offset_of!(MethodDefinition, decorators) == 8); + assert!(offset_of!(MethodDefinition, key) == 24); + assert!(offset_of!(MethodDefinition, value) == 32); + assert!(offset_of!(MethodDefinition, kind) == 37); + assert!(offset_of!(MethodDefinition, computed) == 38); + assert!(offset_of!(MethodDefinition, r#static) == 39); + assert!(offset_of!(MethodDefinition, r#override) == 40); + assert!(offset_of!(MethodDefinition, optional) == 41); + assert!(offset_of!(MethodDefinition, accessibility) == 42); assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 3 bytes assert!(size_of::() == 56); assert!(align_of::() == 4); assert!(offset_of!(PropertyDefinition, span) == 0); - assert!(offset_of!(PropertyDefinition, r#type) == 8); - assert!(offset_of!(PropertyDefinition, decorators) == 12); - assert!(offset_of!(PropertyDefinition, key) == 28); - assert!(offset_of!(PropertyDefinition, type_annotation) == 36); - assert!(offset_of!(PropertyDefinition, value) == 40); - assert!(offset_of!(PropertyDefinition, computed) == 48); - assert!(offset_of!(PropertyDefinition, r#static) == 49); - assert!(offset_of!(PropertyDefinition, declare) == 50); - assert!(offset_of!(PropertyDefinition, r#override) == 51); - assert!(offset_of!(PropertyDefinition, optional) == 52); - assert!(offset_of!(PropertyDefinition, definite) == 53); - assert!(offset_of!(PropertyDefinition, readonly) == 54); - assert!(offset_of!(PropertyDefinition, accessibility) == 55); + assert!(offset_of!(PropertyDefinition, r#type) == 44); + assert!(offset_of!(PropertyDefinition, decorators) == 8); + assert!(offset_of!(PropertyDefinition, key) == 24); + assert!(offset_of!(PropertyDefinition, type_annotation) == 32); + assert!(offset_of!(PropertyDefinition, value) == 36); + assert!(offset_of!(PropertyDefinition, computed) == 45); + assert!(offset_of!(PropertyDefinition, r#static) == 46); + assert!(offset_of!(PropertyDefinition, declare) == 47); + assert!(offset_of!(PropertyDefinition, r#override) == 48); + assert!(offset_of!(PropertyDefinition, optional) == 49); + assert!(offset_of!(PropertyDefinition, definite) == 50); + assert!(offset_of!(PropertyDefinition, readonly) == 51); + assert!(offset_of!(PropertyDefinition, accessibility) == 52); assert!(size_of::() == 1); assert!(align_of::() == 1); @@ -2022,11 +2295,13 @@ const _: () = { assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(PrivateIdentifier, span) == 0); assert!(offset_of!(PrivateIdentifier, name) == 8); + // Padding: 0 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(StaticBlock, span) == 0); @@ -2039,20 +2314,22 @@ const _: () = { assert!(size_of::() == 1); assert!(align_of::() == 1); - assert!(size_of::() == 56); + // Padding: 2 bytes + assert!(size_of::() == 52); assert!(align_of::() == 4); assert!(offset_of!(AccessorProperty, span) == 0); - assert!(offset_of!(AccessorProperty, r#type) == 8); - assert!(offset_of!(AccessorProperty, decorators) == 12); - assert!(offset_of!(AccessorProperty, key) == 28); - assert!(offset_of!(AccessorProperty, type_annotation) == 36); - assert!(offset_of!(AccessorProperty, value) == 40); - assert!(offset_of!(AccessorProperty, computed) == 48); - assert!(offset_of!(AccessorProperty, r#static) == 49); - assert!(offset_of!(AccessorProperty, r#override) == 50); - assert!(offset_of!(AccessorProperty, definite) == 51); - assert!(offset_of!(AccessorProperty, accessibility) == 52); - + assert!(offset_of!(AccessorProperty, r#type) == 44); + assert!(offset_of!(AccessorProperty, decorators) == 8); + assert!(offset_of!(AccessorProperty, key) == 24); + assert!(offset_of!(AccessorProperty, type_annotation) == 32); + assert!(offset_of!(AccessorProperty, value) == 36); + assert!(offset_of!(AccessorProperty, computed) == 45); + assert!(offset_of!(AccessorProperty, r#static) == 46); + assert!(offset_of!(AccessorProperty, r#override) == 47); + assert!(offset_of!(AccessorProperty, definite) == 48); + assert!(offset_of!(AccessorProperty, accessibility) == 49); + + // Padding: 3 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(ImportExpression, span) == 0); @@ -2060,14 +2337,15 @@ const _: () = { assert!(offset_of!(ImportExpression, options) == 16); assert!(offset_of!(ImportExpression, phase) == 24); - assert!(size_of::() == 64); + // Padding: 2 bytes + assert!(size_of::() == 60); assert!(align_of::() == 4); assert!(offset_of!(ImportDeclaration, span) == 0); assert!(offset_of!(ImportDeclaration, specifiers) == 8); assert!(offset_of!(ImportDeclaration, source) == 24); - assert!(offset_of!(ImportDeclaration, phase) == 52); - assert!(offset_of!(ImportDeclaration, with_clause) == 56); - assert!(offset_of!(ImportDeclaration, import_kind) == 60); + assert!(offset_of!(ImportDeclaration, phase) == 56); + assert!(offset_of!(ImportDeclaration, with_clause) == 52); + assert!(offset_of!(ImportDeclaration, import_kind) == 57); assert!(size_of::() == 1); assert!(align_of::() == 1); @@ -2075,6 +2353,7 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 3 bytes assert!(size_of::() == 64); assert!(align_of::() == 4); assert!(offset_of!(ImportSpecifier, span) == 0); @@ -2082,22 +2361,26 @@ const _: () = { assert!(offset_of!(ImportSpecifier, local) == 40); assert!(offset_of!(ImportSpecifier, import_kind) == 60); + // Padding: 0 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(ImportDefaultSpecifier, span) == 0); assert!(offset_of!(ImportDefaultSpecifier, local) == 8); + // Padding: 0 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(ImportNamespaceSpecifier, span) == 0); assert!(offset_of!(ImportNamespaceSpecifier, local) == 8); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(WithClause, span) == 0); assert!(offset_of!(WithClause, attributes_keyword) == 8); assert!(offset_of!(WithClause, with_entries) == 24); + // Padding: 0 bytes assert!(size_of::() == 68); assert!(align_of::() == 4); assert!(offset_of!(ImportAttribute, span) == 0); @@ -2107,21 +2390,24 @@ const _: () = { assert!(size_of::() == 32); assert!(align_of::() == 4); + // Padding: 3 bytes assert!(size_of::() == 68); assert!(align_of::() == 4); assert!(offset_of!(ExportNamedDeclaration, span) == 0); assert!(offset_of!(ExportNamedDeclaration, declaration) == 8); assert!(offset_of!(ExportNamedDeclaration, specifiers) == 16); assert!(offset_of!(ExportNamedDeclaration, source) == 32); - assert!(offset_of!(ExportNamedDeclaration, export_kind) == 60); - assert!(offset_of!(ExportNamedDeclaration, with_clause) == 64); + assert!(offset_of!(ExportNamedDeclaration, export_kind) == 64); + assert!(offset_of!(ExportNamedDeclaration, with_clause) == 60); + // Padding: 0 bytes assert!(size_of::() == 48); assert!(align_of::() == 4); assert!(offset_of!(ExportDefaultDeclaration, span) == 0); assert!(offset_of!(ExportDefaultDeclaration, exported) == 8); assert!(offset_of!(ExportDefaultDeclaration, declaration) == 40); + // Padding: 3 bytes assert!(size_of::() == 76); assert!(align_of::() == 4); assert!(offset_of!(ExportAllDeclaration, span) == 0); @@ -2130,6 +2416,7 @@ const _: () = { assert!(offset_of!(ExportAllDeclaration, with_clause) == 68); assert!(offset_of!(ExportAllDeclaration, export_kind) == 72); + // Padding: 3 bytes assert!(size_of::() == 76); assert!(align_of::() == 4); assert!(offset_of!(ExportSpecifier, span) == 0); @@ -2143,21 +2430,25 @@ const _: () = { assert!(size_of::() == 32); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(V8IntrinsicExpression, span) == 0); assert!(offset_of!(V8IntrinsicExpression, name) == 8); assert!(offset_of!(V8IntrinsicExpression, arguments) == 24); + // Padding: 3 bytes assert!(size_of::() == 12); assert!(align_of::() == 4); assert!(offset_of!(BooleanLiteral, span) == 0); assert!(offset_of!(BooleanLiteral, value) == 8); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(NullLiteral, span) == 0); + // Padding: 7 bytes assert!(size_of::() == 32); assert!(align_of::() == 8); assert!(offset_of!(NumericLiteral, span) == 0); @@ -2165,6 +2456,7 @@ const _: () = { assert!(offset_of!(NumericLiteral, raw) == 16); assert!(offset_of!(NumericLiteral, base) == 24); + // Padding: 3 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(StringLiteral, span) == 0); @@ -2172,31 +2464,37 @@ const _: () = { assert!(offset_of!(StringLiteral, raw) == 16); assert!(offset_of!(StringLiteral, lone_surrogates) == 24); + // Padding: 3 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(BigIntLiteral, span) == 0); assert!(offset_of!(BigIntLiteral, raw) == 8); assert!(offset_of!(BigIntLiteral, base) == 16); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 4); assert!(offset_of!(RegExpLiteral, span) == 0); assert!(offset_of!(RegExpLiteral, regex) == 8); assert!(offset_of!(RegExpLiteral, raw) == 24); + // Padding: 3 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(RegExp, pattern) == 0); assert!(offset_of!(RegExp, flags) == 12); + // Padding: 0 bytes assert!(size_of::() == 12); assert!(align_of::() == 4); assert!(offset_of!(RegExpPattern, text) == 0); assert!(offset_of!(RegExpPattern, pattern) == 8); + // Padding: 0 bytes assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 4); assert!(offset_of!(JSXElement, span) == 0); @@ -2204,6 +2502,7 @@ const _: () = { assert!(offset_of!(JSXElement, children) == 12); assert!(offset_of!(JSXElement, closing_element) == 28); + // Padding: 0 bytes assert!(size_of::() == 36); assert!(align_of::() == 4); assert!(offset_of!(JSXOpeningElement, span) == 0); @@ -2211,11 +2510,13 @@ const _: () = { assert!(offset_of!(JSXOpeningElement, type_arguments) == 16); assert!(offset_of!(JSXOpeningElement, attributes) == 20); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(JSXClosingElement, span) == 0); assert!(offset_of!(JSXClosingElement, name) == 8); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(JSXFragment, span) == 0); @@ -2223,10 +2524,12 @@ const _: () = { assert!(offset_of!(JSXFragment, children) == 16); assert!(offset_of!(JSXFragment, closing_fragment) == 32); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(JSXOpeningFragment, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(JSXClosingFragment, span) == 0); @@ -2234,12 +2537,14 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(JSXNamespacedName, span) == 0); assert!(offset_of!(JSXNamespacedName, namespace) == 8); assert!(offset_of!(JSXNamespacedName, name) == 24); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 4); assert!(offset_of!(JSXMemberExpression, span) == 0); @@ -2249,6 +2554,7 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(JSXExpressionContainer, span) == 0); @@ -2257,6 +2563,7 @@ const _: () = { assert!(size_of::() == 12); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(JSXEmptyExpression, span) == 0); @@ -2264,12 +2571,14 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(JSXAttribute, span) == 0); assert!(offset_of!(JSXAttribute, name) == 8); assert!(offset_of!(JSXAttribute, value) == 16); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(JSXSpreadAttribute, span) == 0); @@ -2281,6 +2590,7 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(JSXIdentifier, span) == 0); @@ -2289,37 +2599,43 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(JSXSpreadChild, span) == 0); assert!(offset_of!(JSXSpreadChild, expression) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(JSXText, span) == 0); assert!(offset_of!(JSXText, value) == 8); assert!(offset_of!(JSXText, raw) == 16); + // Padding: 0 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(TSThisParameter, span) == 0); assert!(offset_of!(TSThisParameter, this_span) == 8); assert!(offset_of!(TSThisParameter, type_annotation) == 16); + // Padding: 2 bytes assert!(size_of::() == 60); assert!(align_of::() == 4); assert!(offset_of!(TSEnumDeclaration, span) == 0); assert!(offset_of!(TSEnumDeclaration, id) == 8); assert!(offset_of!(TSEnumDeclaration, body) == 28); - assert!(offset_of!(TSEnumDeclaration, r#const) == 52); - assert!(offset_of!(TSEnumDeclaration, declare) == 53); - assert!(offset_of!(TSEnumDeclaration, scope_id) == 56); + assert!(offset_of!(TSEnumDeclaration, r#const) == 56); + assert!(offset_of!(TSEnumDeclaration, declare) == 57); + assert!(offset_of!(TSEnumDeclaration, scope_id) == 52); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSEnumBody, span) == 0); assert!(offset_of!(TSEnumBody, members) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSEnumMember, span) == 0); @@ -2329,11 +2645,13 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(TSTypeAnnotation, span) == 0); assert!(offset_of!(TSTypeAnnotation, type_annotation) == 8); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(TSLiteralType, span) == 0); @@ -2345,6 +2663,7 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 44); assert!(align_of::() == 4); assert!(offset_of!(TSConditionalType, span) == 0); @@ -2354,46 +2673,54 @@ const _: () = { assert!(offset_of!(TSConditionalType, false_type) == 32); assert!(offset_of!(TSConditionalType, scope_id) == 40); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSUnionType, span) == 0); assert!(offset_of!(TSUnionType, types) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSIntersectionType, span) == 0); assert!(offset_of!(TSIntersectionType, types) == 8); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(TSParenthesizedType, span) == 0); assert!(offset_of!(TSParenthesizedType, type_annotation) == 8); + // Padding: 3 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(TSTypeOperator, span) == 0); - assert!(offset_of!(TSTypeOperator, operator) == 8); - assert!(offset_of!(TSTypeOperator, type_annotation) == 12); + assert!(offset_of!(TSTypeOperator, operator) == 16); + assert!(offset_of!(TSTypeOperator, type_annotation) == 8); assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(TSArrayType, span) == 0); assert!(offset_of!(TSArrayType, element_type) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSIndexedAccessType, span) == 0); assert!(offset_of!(TSIndexedAccessType, object_type) == 8); assert!(offset_of!(TSIndexedAccessType, index_type) == 16); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSTupleType, span) == 0); assert!(offset_of!(TSTupleType, element_types) == 8); + // Padding: 3 bytes assert!(size_of::() == 36); assert!(align_of::() == 4); assert!(offset_of!(TSNamedTupleMember, span) == 0); @@ -2401,11 +2728,13 @@ const _: () = { assert!(offset_of!(TSNamedTupleMember, element_type) == 24); assert!(offset_of!(TSNamedTupleMember, optional) == 32); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(TSOptionalType, span) == 0); assert!(offset_of!(TSOptionalType, type_annotation) == 8); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(TSRestType, span) == 0); @@ -2414,62 +2743,77 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(TSAnyKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(TSStringKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(TSBooleanKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(TSNumberKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(TSNeverKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(TSIntrinsicKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(TSUnknownKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(TSNullKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(TSUndefinedKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(TSVoidKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(TSSymbolKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(TSThisType, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(TSObjectKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(TSBigIntKeyword, span) == 0); + // Padding: 0 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(TSTypeReference, span) == 0); @@ -2479,17 +2823,20 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 4); assert!(offset_of!(TSQualifiedName, span) == 0); assert!(offset_of!(TSQualifiedName, left) == 8); assert!(offset_of!(TSQualifiedName, right) == 16); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSTypeParameterInstantiation, span) == 0); assert!(offset_of!(TSTypeParameterInstantiation, params) == 8); + // Padding: 1 bytes assert!(size_of::() == 48); assert!(align_of::() == 4); assert!(offset_of!(TSTypeParameter, span) == 0); @@ -2500,29 +2847,33 @@ const _: () = { assert!(offset_of!(TSTypeParameter, out) == 45); assert!(offset_of!(TSTypeParameter, r#const) == 46); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSTypeParameterDeclaration, span) == 0); assert!(offset_of!(TSTypeParameterDeclaration, params) == 8); + // Padding: 3 bytes assert!(size_of::() == 48); assert!(align_of::() == 4); assert!(offset_of!(TSTypeAliasDeclaration, span) == 0); assert!(offset_of!(TSTypeAliasDeclaration, id) == 8); assert!(offset_of!(TSTypeAliasDeclaration, type_parameters) == 28); assert!(offset_of!(TSTypeAliasDeclaration, type_annotation) == 32); - assert!(offset_of!(TSTypeAliasDeclaration, declare) == 40); - assert!(offset_of!(TSTypeAliasDeclaration, scope_id) == 44); + assert!(offset_of!(TSTypeAliasDeclaration, declare) == 44); + assert!(offset_of!(TSTypeAliasDeclaration, scope_id) == 40); assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 0 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(TSClassImplements, span) == 0); assert!(offset_of!(TSClassImplements, expression) == 8); assert!(offset_of!(TSClassImplements, type_arguments) == 16); + // Padding: 3 bytes assert!(size_of::() == 60); assert!(align_of::() == 4); assert!(offset_of!(TSInterfaceDeclaration, span) == 0); @@ -2530,26 +2881,29 @@ const _: () = { assert!(offset_of!(TSInterfaceDeclaration, type_parameters) == 28); assert!(offset_of!(TSInterfaceDeclaration, extends) == 32); assert!(offset_of!(TSInterfaceDeclaration, body) == 48); - assert!(offset_of!(TSInterfaceDeclaration, declare) == 52); - assert!(offset_of!(TSInterfaceDeclaration, scope_id) == 56); + assert!(offset_of!(TSInterfaceDeclaration, declare) == 56); + assert!(offset_of!(TSInterfaceDeclaration, scope_id) == 52); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSInterfaceBody, span) == 0); assert!(offset_of!(TSInterfaceBody, body) == 8); + // Padding: 1 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSPropertySignature, span) == 0); - assert!(offset_of!(TSPropertySignature, computed) == 8); - assert!(offset_of!(TSPropertySignature, optional) == 9); - assert!(offset_of!(TSPropertySignature, readonly) == 10); - assert!(offset_of!(TSPropertySignature, key) == 12); - assert!(offset_of!(TSPropertySignature, type_annotation) == 20); + assert!(offset_of!(TSPropertySignature, computed) == 20); + assert!(offset_of!(TSPropertySignature, optional) == 21); + assert!(offset_of!(TSPropertySignature, readonly) == 22); + assert!(offset_of!(TSPropertySignature, key) == 8); + assert!(offset_of!(TSPropertySignature, type_annotation) == 16); assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 2 bytes assert!(size_of::() == 32); assert!(align_of::() == 4); assert!(offset_of!(TSIndexSignature, span) == 0); @@ -2558,6 +2912,7 @@ const _: () = { assert!(offset_of!(TSIndexSignature, readonly) == 28); assert!(offset_of!(TSIndexSignature, r#static) == 29); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSCallSignatureDeclaration, span) == 0); @@ -2569,19 +2924,21 @@ const _: () = { assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 1 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(TSMethodSignature, span) == 0); assert!(offset_of!(TSMethodSignature, key) == 8); - assert!(offset_of!(TSMethodSignature, computed) == 16); - assert!(offset_of!(TSMethodSignature, optional) == 17); - assert!(offset_of!(TSMethodSignature, kind) == 18); - assert!(offset_of!(TSMethodSignature, type_parameters) == 20); - assert!(offset_of!(TSMethodSignature, this_param) == 24); - assert!(offset_of!(TSMethodSignature, params) == 28); - assert!(offset_of!(TSMethodSignature, return_type) == 32); - assert!(offset_of!(TSMethodSignature, scope_id) == 36); - + assert!(offset_of!(TSMethodSignature, computed) == 36); + assert!(offset_of!(TSMethodSignature, optional) == 37); + assert!(offset_of!(TSMethodSignature, kind) == 38); + assert!(offset_of!(TSMethodSignature, type_parameters) == 16); + assert!(offset_of!(TSMethodSignature, this_param) == 20); + assert!(offset_of!(TSMethodSignature, params) == 24); + assert!(offset_of!(TSMethodSignature, return_type) == 28); + assert!(offset_of!(TSMethodSignature, scope_id) == 32); + + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSConstructSignatureDeclaration, span) == 0); @@ -2590,36 +2947,40 @@ const _: () = { assert!(offset_of!(TSConstructSignatureDeclaration, return_type) == 16); assert!(offset_of!(TSConstructSignatureDeclaration, scope_id) == 20); + // Padding: 0 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(TSIndexSignatureName, span) == 0); assert!(offset_of!(TSIndexSignatureName, name) == 8); assert!(offset_of!(TSIndexSignatureName, type_annotation) == 16); + // Padding: 0 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(TSInterfaceHeritage, span) == 0); assert!(offset_of!(TSInterfaceHeritage, expression) == 8); assert!(offset_of!(TSInterfaceHeritage, type_arguments) == 16); + // Padding: 3 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(TSTypePredicate, span) == 0); assert!(offset_of!(TSTypePredicate, parameter_name) == 8); - assert!(offset_of!(TSTypePredicate, asserts) == 20); - assert!(offset_of!(TSTypePredicate, type_annotation) == 24); + assert!(offset_of!(TSTypePredicate, asserts) == 24); + assert!(offset_of!(TSTypePredicate, type_annotation) == 20); assert!(size_of::() == 12); assert!(align_of::() == 4); + // Padding: 2 bytes assert!(size_of::() == 56); assert!(align_of::() == 4); assert!(offset_of!(TSModuleDeclaration, span) == 0); assert!(offset_of!(TSModuleDeclaration, id) == 8); assert!(offset_of!(TSModuleDeclaration, body) == 40); - assert!(offset_of!(TSModuleDeclaration, kind) == 48); - assert!(offset_of!(TSModuleDeclaration, declare) == 49); - assert!(offset_of!(TSModuleDeclaration, scope_id) == 52); + assert!(offset_of!(TSModuleDeclaration, kind) == 52); + assert!(offset_of!(TSModuleDeclaration, declare) == 53); + assert!(offset_of!(TSModuleDeclaration, scope_id) == 48); assert!(size_of::() == 1); assert!(align_of::() == 1); @@ -2630,22 +2991,26 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(TSModuleBlock, span) == 0); assert!(offset_of!(TSModuleBlock, directives) == 8); assert!(offset_of!(TSModuleBlock, body) == 24); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSTypeLiteral, span) == 0); assert!(offset_of!(TSTypeLiteral, members) == 8); + // Padding: 0 bytes assert!(size_of::() == 12); assert!(align_of::() == 4); assert!(offset_of!(TSInferType, span) == 0); assert!(offset_of!(TSInferType, type_parameter) == 8); + // Padding: 0 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(TSTypeQuery, span) == 0); @@ -2655,6 +3020,7 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 32); assert!(align_of::() == 4); assert!(offset_of!(TSImportType, span) == 0); @@ -2663,6 +3029,7 @@ const _: () = { assert!(offset_of!(TSImportType, qualifier) == 20); assert!(offset_of!(TSImportType, type_arguments) == 28); + // Padding: 0 bytes assert!(size_of::() == 28); assert!(align_of::() == 4); assert!(offset_of!(TSFunctionType, span) == 0); @@ -2672,51 +3039,58 @@ const _: () = { assert!(offset_of!(TSFunctionType, return_type) == 20); assert!(offset_of!(TSFunctionType, scope_id) == 24); + // Padding: 3 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSConstructorType, span) == 0); - assert!(offset_of!(TSConstructorType, r#abstract) == 8); - assert!(offset_of!(TSConstructorType, type_parameters) == 12); - assert!(offset_of!(TSConstructorType, params) == 16); - assert!(offset_of!(TSConstructorType, return_type) == 20); + assert!(offset_of!(TSConstructorType, r#abstract) == 20); + assert!(offset_of!(TSConstructorType, type_parameters) == 8); + assert!(offset_of!(TSConstructorType, params) == 12); + assert!(offset_of!(TSConstructorType, return_type) == 16); + // Padding: 2 bytes assert!(size_of::() == 36); assert!(align_of::() == 4); assert!(offset_of!(TSMappedType, span) == 0); assert!(offset_of!(TSMappedType, type_parameter) == 8); assert!(offset_of!(TSMappedType, name_type) == 12); assert!(offset_of!(TSMappedType, type_annotation) == 20); - assert!(offset_of!(TSMappedType, optional) == 28); - assert!(offset_of!(TSMappedType, readonly) == 29); - assert!(offset_of!(TSMappedType, scope_id) == 32); + assert!(offset_of!(TSMappedType, optional) == 32); + assert!(offset_of!(TSMappedType, readonly) == 33); + assert!(offset_of!(TSMappedType, scope_id) == 28); assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 0 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(TSTemplateLiteralType, span) == 0); assert!(offset_of!(TSTemplateLiteralType, quasis) == 8); assert!(offset_of!(TSTemplateLiteralType, types) == 24); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSAsExpression, span) == 0); assert!(offset_of!(TSAsExpression, expression) == 8); assert!(offset_of!(TSAsExpression, type_annotation) == 16); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSSatisfiesExpression, span) == 0); assert!(offset_of!(TSSatisfiesExpression, expression) == 8); assert!(offset_of!(TSSatisfiesExpression, type_annotation) == 16); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSTypeAssertion, span) == 0); assert!(offset_of!(TSTypeAssertion, type_annotation) == 8); assert!(offset_of!(TSTypeAssertion, expression) == 16); + // Padding: 3 bytes assert!(size_of::() == 40); assert!(align_of::() == 4); assert!(offset_of!(TSImportEqualsDeclaration, span) == 0); @@ -2727,31 +3101,37 @@ const _: () = { assert!(size_of::() == 8); assert!(align_of::() == 4); + // Padding: 0 bytes assert!(size_of::() == 36); assert!(align_of::() == 4); assert!(offset_of!(TSExternalModuleReference, span) == 0); assert!(offset_of!(TSExternalModuleReference, expression) == 8); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(TSNonNullExpression, span) == 0); assert!(offset_of!(TSNonNullExpression, expression) == 8); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(Decorator, span) == 0); assert!(offset_of!(Decorator, expression) == 8); + // Padding: 0 bytes assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(TSExportAssignment, span) == 0); assert!(offset_of!(TSExportAssignment, expression) == 8); + // Padding: 0 bytes assert!(size_of::() == 24); assert!(align_of::() == 4); assert!(offset_of!(TSNamespaceExportDeclaration, span) == 0); assert!(offset_of!(TSNamespaceExportDeclaration, id) == 8); + // Padding: 0 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(TSInstantiationExpression, span) == 0); @@ -2761,18 +3141,21 @@ const _: () = { assert!(size_of::() == 1); assert!(align_of::() == 1); + // Padding: 3 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(JSDocNullableType, span) == 0); assert!(offset_of!(JSDocNullableType, type_annotation) == 8); assert!(offset_of!(JSDocNullableType, postfix) == 16); + // Padding: 3 bytes assert!(size_of::() == 20); assert!(align_of::() == 4); assert!(offset_of!(JSDocNonNullableType, span) == 0); assert!(offset_of!(JSDocNonNullableType, type_annotation) == 8); assert!(offset_of!(JSDocNonNullableType, postfix) == 16); + // Padding: 0 bytes assert!(size_of::() == 8); assert!(align_of::() == 4); assert!(offset_of!(JSDocUnknownType, span) == 0); @@ -2786,15 +3169,18 @@ const _: () = { assert!(size_of::() == 1); assert!(align_of::() == 1); - assert!(size_of::() == 20); + assert!(size_of::() == 1); + assert!(align_of::() == 1); + + // Padding: 0 bytes + assert!(size_of::() == 16); assert!(align_of::() == 4); assert!(offset_of!(Comment, span) == 0); assert!(offset_of!(Comment, attached_to) == 8); assert!(offset_of!(Comment, kind) == 12); assert!(offset_of!(Comment, position) == 13); - assert!(offset_of!(Comment, preceded_by_newline) == 14); - assert!(offset_of!(Comment, followed_by_newline) == 15); - assert!(offset_of!(Comment, annotation) == 16); + assert!(offset_of!(Comment, newlines) == 14); + assert!(offset_of!(Comment, annotation) == 15); }; #[cfg(not(any(target_pointer_width = "64", target_pointer_width = "32")))] diff --git a/crates/oxc_ast/src/generated/ast_builder.rs b/crates/oxc_ast/src/generated/ast_builder.rs index 258f4e11f66b6..34d7716fb6654 100644 --- a/crates/oxc_ast/src/generated/ast_builder.rs +++ b/crates/oxc_ast/src/generated/ast_builder.rs @@ -3,7 +3,7 @@ //! AST node factories -#![expect(clippy::default_trait_access)] +#![expect(clippy::default_trait_access, clippy::inconsistent_struct_constructor)] use std::cell::Cell; diff --git a/crates/oxc_ast/src/generated/derive_clone_in.rs b/crates/oxc_ast/src/generated/derive_clone_in.rs index 958dc30676737..457a709f1d978 100644 --- a/crates/oxc_ast/src/generated/derive_clone_in.rs +++ b/crates/oxc_ast/src/generated/derive_clone_in.rs @@ -7866,6 +7866,20 @@ impl<'new_alloc> CloneIn<'new_alloc> for CommentAnnotation { } } +impl<'new_alloc> CloneIn<'new_alloc> for CommentNewlines { + type Cloned = CommentNewlines; + + #[inline(always)] + fn clone_in(&self, allocator: &'new_alloc Allocator) -> Self::Cloned { + *self + } + + #[inline(always)] + fn clone_in_with_semantic_ids(&self, allocator: &'new_alloc Allocator) -> Self::Cloned { + *self + } +} + impl<'new_alloc> CloneIn<'new_alloc> for Comment { type Cloned = Comment; @@ -7875,8 +7889,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for Comment { attached_to: CloneIn::clone_in(&self.attached_to, allocator), kind: CloneIn::clone_in(&self.kind, allocator), position: CloneIn::clone_in(&self.position, allocator), - preceded_by_newline: CloneIn::clone_in(&self.preceded_by_newline, allocator), - followed_by_newline: CloneIn::clone_in(&self.followed_by_newline, allocator), + newlines: CloneIn::clone_in(&self.newlines, allocator), annotation: CloneIn::clone_in(&self.annotation, allocator), } } @@ -7887,14 +7900,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for Comment { attached_to: CloneIn::clone_in_with_semantic_ids(&self.attached_to, allocator), kind: CloneIn::clone_in_with_semantic_ids(&self.kind, allocator), position: CloneIn::clone_in_with_semantic_ids(&self.position, allocator), - preceded_by_newline: CloneIn::clone_in_with_semantic_ids( - &self.preceded_by_newline, - allocator, - ), - followed_by_newline: CloneIn::clone_in_with_semantic_ids( - &self.followed_by_newline, - allocator, - ), + newlines: CloneIn::clone_in_with_semantic_ids(&self.newlines, allocator), annotation: CloneIn::clone_in_with_semantic_ids(&self.annotation, allocator), } } diff --git a/crates/oxc_ast/src/generated/derive_content_eq.rs b/crates/oxc_ast/src/generated/derive_content_eq.rs index e433ecea7d801..38731cab2b576 100644 --- a/crates/oxc_ast/src/generated/derive_content_eq.rs +++ b/crates/oxc_ast/src/generated/derive_content_eq.rs @@ -2482,13 +2482,18 @@ impl ContentEq for CommentAnnotation { } } +impl ContentEq for CommentNewlines { + fn content_eq(&self, other: &Self) -> bool { + self == other + } +} + impl ContentEq for Comment { fn content_eq(&self, other: &Self) -> bool { ContentEq::content_eq(&self.attached_to, &other.attached_to) && ContentEq::content_eq(&self.kind, &other.kind) && ContentEq::content_eq(&self.position, &other.position) - && ContentEq::content_eq(&self.preceded_by_newline, &other.preceded_by_newline) - && ContentEq::content_eq(&self.followed_by_newline, &other.followed_by_newline) + && ContentEq::content_eq(&self.newlines, &other.newlines) && ContentEq::content_eq(&self.annotation, &other.annotation) } } diff --git a/crates/oxc_ast/src/generated/derive_dummy.rs b/crates/oxc_ast/src/generated/derive_dummy.rs index 18aee60bdba3f..0965306dc850d 100644 --- a/crates/oxc_ast/src/generated/derive_dummy.rs +++ b/crates/oxc_ast/src/generated/derive_dummy.rs @@ -451,7 +451,7 @@ impl<'a> Dummy<'a> for SimpleAssignmentTarget<'a> { impl<'a> Dummy<'a> for AssignmentTargetPattern<'a> { /// Create a dummy [`AssignmentTargetPattern`]. /// - /// Has cost of making 1 allocation (64 bytes). + /// Has cost of making 1 allocation (56 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self::ArrayAssignmentTarget(Dummy::dummy(allocator)) } @@ -651,7 +651,7 @@ impl<'a> Dummy<'a> for BlockStatement<'a> { impl<'a> Dummy<'a> for Declaration<'a> { /// Create a dummy [`Declaration`]. /// - /// Has cost of making 1 allocation (56 bytes). + /// Has cost of making 1 allocation (40 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self::VariableDeclaration(Dummy::dummy(allocator)) } @@ -911,7 +911,7 @@ impl<'a> Dummy<'a> for ThrowStatement<'a> { impl<'a> Dummy<'a> for TryStatement<'a> { /// Create a dummy [`TryStatement`]. /// - /// Has cost of making 1 allocation (48 bytes). + /// Has cost of making 1 allocation (40 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self { span: Dummy::dummy(allocator), @@ -925,7 +925,7 @@ impl<'a> Dummy<'a> for TryStatement<'a> { impl<'a> Dummy<'a> for CatchClause<'a> { /// Create a dummy [`CatchClause`]. /// - /// Has cost of making 1 allocation (48 bytes). + /// Has cost of making 1 allocation (40 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self { span: Dummy::dummy(allocator), @@ -1042,7 +1042,7 @@ impl<'a> Dummy<'a> for BindingRestElement<'a> { impl<'a> Dummy<'a> for Function<'a> { /// Create a dummy [`Function`]. /// - /// Has cost of making 1 allocation (56 bytes). + /// Has cost of making 1 allocation (48 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self { span: Dummy::dummy(allocator), @@ -1128,7 +1128,7 @@ impl<'a> Dummy<'a> for FunctionBody<'a> { impl<'a> Dummy<'a> for ArrowFunctionExpression<'a> { /// Create a dummy [`ArrowFunctionExpression`]. /// - /// Has cost of making 2 allocations (128 bytes). + /// Has cost of making 2 allocations (104 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self { span: Dummy::dummy(allocator), @@ -1160,7 +1160,7 @@ impl<'a> Dummy<'a> for YieldExpression<'a> { impl<'a> Dummy<'a> for Class<'a> { /// Create a dummy [`Class`]. /// - /// Has cost of making 1 allocation (40 bytes). + /// Has cost of making 1 allocation (32 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self { span: Dummy::dummy(allocator), @@ -1201,7 +1201,7 @@ impl<'a> Dummy<'a> for ClassBody<'a> { impl<'a> Dummy<'a> for ClassElement<'a> { /// Create a dummy [`ClassElement`]. /// - /// Has cost of making 1 allocation (48 bytes). + /// Has cost of making 1 allocation (40 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self::StaticBlock(Dummy::dummy(allocator)) } @@ -1210,7 +1210,7 @@ impl<'a> Dummy<'a> for ClassElement<'a> { impl<'a> Dummy<'a> for MethodDefinition<'a> { /// Create a dummy [`MethodDefinition`]. /// - /// Has cost of making 3 allocations (168 bytes). + /// Has cost of making 3 allocations (152 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self { span: Dummy::dummy(allocator), @@ -1642,7 +1642,7 @@ impl<'a> Dummy<'a> for RegExpPattern<'a> { impl<'a> Dummy<'a> for JSXElement<'a> { /// Create a dummy [`JSXElement`]. /// - /// Has cost of making 2 allocations (72 bytes). + /// Has cost of making 2 allocations (64 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self { span: Dummy::dummy(allocator), @@ -2340,7 +2340,7 @@ impl<'a> Dummy<'a> for TSClassImplements<'a> { impl<'a> Dummy<'a> for TSInterfaceDeclaration<'a> { /// Create a dummy [`TSInterfaceDeclaration`]. /// - /// Has cost of making 1 allocation (40 bytes). + /// Has cost of making 1 allocation (32 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self { span: Dummy::dummy(allocator), @@ -2406,7 +2406,7 @@ impl<'a> Dummy<'a> for TSIndexSignature<'a> { impl<'a> Dummy<'a> for TSCallSignatureDeclaration<'a> { /// Create a dummy [`TSCallSignatureDeclaration`]. /// - /// Has cost of making 1 allocation (56 bytes). + /// Has cost of making 1 allocation (48 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self { span: Dummy::dummy(allocator), @@ -2431,7 +2431,7 @@ impl<'a> Dummy<'a> for TSMethodSignatureKind { impl<'a> Dummy<'a> for TSMethodSignature<'a> { /// Create a dummy [`TSMethodSignature`]. /// - /// Has cost of making 2 allocations (64 bytes). + /// Has cost of making 2 allocations (56 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self { span: Dummy::dummy(allocator), @@ -2451,7 +2451,7 @@ impl<'a> Dummy<'a> for TSMethodSignature<'a> { impl<'a> Dummy<'a> for TSConstructSignatureDeclaration<'a> { /// Create a dummy [`TSConstructSignatureDeclaration`]. /// - /// Has cost of making 1 allocation (56 bytes). + /// Has cost of making 1 allocation (48 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self { span: Dummy::dummy(allocator), @@ -2550,7 +2550,7 @@ impl<'a> Dummy<'a> for TSModuleDeclarationName<'a> { impl<'a> Dummy<'a> for TSModuleDeclarationBody<'a> { /// Create a dummy [`TSModuleDeclarationBody`]. /// - /// Has cost of making 1 allocation (72 bytes). + /// Has cost of making 1 allocation (56 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self::TSModuleBlock(Dummy::dummy(allocator)) } @@ -2627,7 +2627,7 @@ impl<'a> Dummy<'a> for TSImportType<'a> { impl<'a> Dummy<'a> for TSFunctionType<'a> { /// Create a dummy [`TSFunctionType`]. /// - /// Has cost of making 3 allocations (88 bytes). + /// Has cost of making 3 allocations (80 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self { span: Dummy::dummy(allocator), @@ -2643,7 +2643,7 @@ impl<'a> Dummy<'a> for TSFunctionType<'a> { impl<'a> Dummy<'a> for TSConstructorType<'a> { /// Create a dummy [`TSConstructorType`]. /// - /// Has cost of making 3 allocations (88 bytes). + /// Has cost of making 3 allocations (80 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self { span: Dummy::dummy(allocator), @@ -2805,7 +2805,7 @@ impl<'a> Dummy<'a> for TSNamespaceExportDeclaration<'a> { impl<'a> Dummy<'a> for TSInstantiationExpression<'a> { /// Create a dummy [`TSInstantiationExpression`]. /// - /// Has cost of making 2 allocations (48 bytes). + /// Has cost of making 2 allocations (40 bytes). fn dummy(allocator: &'a Allocator) -> Self { Self { span: Dummy::dummy(allocator), diff --git a/crates/oxc_ast/src/generated/derive_estree.rs b/crates/oxc_ast/src/generated/derive_estree.rs index c7870ff45b83c..c2a8b82062fa3 100644 --- a/crates/oxc_ast/src/generated/derive_estree.rs +++ b/crates/oxc_ast/src/generated/derive_estree.rs @@ -76,9 +76,9 @@ impl ESTree for IdentifierName<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("name", &JsonSafeString(self.name.as_str())); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -90,9 +90,9 @@ impl ESTree for IdentifierReference<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("name", &JsonSafeString(self.name.as_str())); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -104,9 +104,9 @@ impl ESTree for BindingIdentifier<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("name", &JsonSafeString(self.name.as_str())); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -118,9 +118,9 @@ impl ESTree for LabelIdentifier<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("name", &JsonSafeString(self.name.as_str())); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -200,7 +200,7 @@ impl ESTree for ArrayExpressionElement<'_> { impl ESTree for Elision { fn serialize(&self, serializer: S) { - crate::serialize::ElisionConverter(self).serialize(serializer) + crate::serialize::basic::Null(self).serialize(serializer) } } @@ -236,7 +236,7 @@ impl ESTree for ObjectProperty<'_> { state.serialize_field("key", &self.key); state.serialize_field("value", &self.value); state.serialize_field("kind", &self.kind); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); state.end(); } } @@ -330,7 +330,7 @@ impl ESTree for TaggedTemplateExpression<'_> { impl ESTree for TemplateElement<'_> { fn serialize(&self, serializer: S) { - crate::serialize::TemplateElementConverter(self).serialize(serializer) + crate::serialize::literal::TemplateElementConverter(self).serialize(serializer) } } @@ -361,7 +361,7 @@ impl ESTree for ComputedMemberExpression<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("object", &self.object); state.serialize_field("property", &self.expression); - state.serialize_field("computed", &crate::serialize::True(self)); + state.serialize_field("computed", &crate::serialize::basic::True(self)); state.serialize_field("optional", &self.optional); state.end(); } @@ -375,7 +375,7 @@ impl ESTree for StaticMemberExpression<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("object", &self.object); state.serialize_field("property", &self.property); - state.serialize_field("computed", &crate::serialize::False(self)); + state.serialize_field("computed", &crate::serialize::basic::False(self)); state.serialize_field("optional", &self.optional); state.end(); } @@ -389,7 +389,7 @@ impl ESTree for PrivateFieldExpression<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("object", &self.object); state.serialize_field("property", &self.field); - state.serialize_field("computed", &crate::serialize::False(self)); + state.serialize_field("computed", &crate::serialize::basic::False(self)); state.serialize_field("optional", &self.optional); state.end(); } @@ -516,7 +516,7 @@ impl ESTree for UnaryExpression<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("operator", &self.operator); - state.serialize_field("prefix", &crate::serialize::True(self)); + state.serialize_field("prefix", &crate::serialize::basic::True(self)); state.serialize_field("argument", &self.argument); state.end(); } @@ -542,7 +542,7 @@ impl ESTree for PrivateInExpression<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("left", &self.left); - state.serialize_field("operator", &crate::serialize::In(self)); + state.serialize_field("operator", &crate::serialize::basic::In(self)); state.serialize_field("right", &self.right); state.end(); } @@ -635,9 +635,9 @@ impl ESTree for ArrayAssignmentTarget<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("elements", &Concat2(&self.elements, &self.rest)); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -649,9 +649,9 @@ impl ESTree for ObjectAssignmentTarget<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("properties", &Concat2(&self.properties, &self.rest)); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -663,10 +663,10 @@ impl ESTree for AssignmentTargetRest<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("argument", &self.target); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); - state.serialize_ts_field("value", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); + state.serialize_ts_field("value", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -697,9 +697,9 @@ impl ESTree for AssignmentTargetWithDefault<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("left", &self.binding); state.serialize_field("right", &self.init); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -719,16 +719,16 @@ impl ESTree for AssignmentTargetPropertyIdentifier<'_> { state.serialize_field("type", &JsonSafeString("Property")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("method", &crate::serialize::False(self)); - state.serialize_field("shorthand", &crate::serialize::True(self)); - state.serialize_field("computed", &crate::serialize::False(self)); + state.serialize_field("method", &crate::serialize::basic::False(self)); + state.serialize_field("shorthand", &crate::serialize::basic::True(self)); + state.serialize_field("computed", &crate::serialize::basic::False(self)); state.serialize_field("key", &self.binding); state.serialize_field( "value", - &crate::serialize::AssignmentTargetPropertyIdentifierValue(self), + &crate::serialize::js::AssignmentTargetPropertyIdentifierInit(self), ); - state.serialize_field("kind", &crate::serialize::Init(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); + state.serialize_field("kind", &crate::serialize::basic::Init(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); state.end(); } } @@ -739,13 +739,13 @@ impl ESTree for AssignmentTargetPropertyProperty<'_> { state.serialize_field("type", &JsonSafeString("Property")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("method", &crate::serialize::False(self)); - state.serialize_field("shorthand", &crate::serialize::False(self)); + state.serialize_field("method", &crate::serialize::basic::False(self)); + state.serialize_field("shorthand", &crate::serialize::basic::False(self)); state.serialize_field("computed", &self.computed); state.serialize_field("key", &self.name); state.serialize_field("value", &self.binding); - state.serialize_field("kind", &crate::serialize::Init(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); + state.serialize_field("kind", &crate::serialize::basic::Init(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); state.end(); } } @@ -959,8 +959,10 @@ impl ESTree for ExpressionStatement<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("expression", &self.expression); - state - .serialize_ts_field("directive", &crate::serialize::ExpressionStatementDirective(self)); + state.serialize_ts_field( + "directive", + &crate::serialize::ts::ExpressionStatementDirective(self), + ); state.end(); } } @@ -1278,9 +1280,9 @@ impl ESTree for AssignmentPattern<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("left", &self.left); state.serialize_field("right", &self.right); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -1292,9 +1294,9 @@ impl ESTree for ObjectPattern<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("properties", &Concat2(&self.properties, &self.rest)); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -1305,13 +1307,13 @@ impl ESTree for BindingProperty<'_> { state.serialize_field("type", &JsonSafeString("Property")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("method", &crate::serialize::False(self)); + state.serialize_field("method", &crate::serialize::basic::False(self)); state.serialize_field("shorthand", &self.shorthand); state.serialize_field("computed", &self.computed); state.serialize_field("key", &self.key); state.serialize_field("value", &self.value); - state.serialize_field("kind", &crate::serialize::Init(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); + state.serialize_field("kind", &crate::serialize::basic::Init(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); state.end(); } } @@ -1323,9 +1325,9 @@ impl ESTree for ArrayPattern<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("elements", &Concat2(&self.elements, &self.rest)); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -1337,10 +1339,10 @@ impl ESTree for BindingRestElement<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("argument", &self.argument); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); - state.serialize_ts_field("value", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); + state.serialize_ts_field("value", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -1352,10 +1354,10 @@ impl ESTree for Function<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("id", &self.id); - state.serialize_field("expression", &crate::serialize::False(self)); + state.serialize_field("expression", &crate::serialize::basic::False(self)); state.serialize_field("generator", &self.generator); state.serialize_field("async", &self.r#async); - state.serialize_field("params", &crate::serialize::FunctionFormalParameters(self)); + state.serialize_field("params", &crate::serialize::js::FunctionParams(self)); state.serialize_field("body", &self.body); state.serialize_ts_field("declare", &self.declare); state.serialize_ts_field("typeParameters", &self.type_parameters); @@ -1381,13 +1383,13 @@ impl ESTree for FunctionType { impl ESTree for FormalParameters<'_> { fn serialize(&self, serializer: S) { - crate::serialize::FormalParametersConverter(self).serialize(serializer) + crate::serialize::js::FormalParametersConverter(self).serialize(serializer) } } impl ESTree for FormalParameter<'_> { fn serialize(&self, serializer: S) { - crate::serialize::FormalParameterConverter(self).serialize(serializer) + crate::serialize::js::FormalParameterConverter(self).serialize(serializer) } } @@ -1423,12 +1425,12 @@ impl ESTree for ArrowFunctionExpression<'_> { state.serialize_field("type", &JsonSafeString("ArrowFunctionExpression")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("id", &crate::serialize::Null(self)); + state.serialize_field("id", &crate::serialize::basic::Null(self)); state.serialize_field("expression", &self.expression); - state.serialize_field("generator", &crate::serialize::False(self)); + state.serialize_field("generator", &crate::serialize::basic::False(self)); state.serialize_field("async", &self.r#async); state.serialize_field("params", &self.params); - state.serialize_field("body", &crate::serialize::ArrowFunctionExpressionBody(self)); + state.serialize_field("body", &crate::serialize::js::ArrowFunctionExpressionBody(self)); state.serialize_ts_field("typeParameters", &self.type_parameters); state.serialize_ts_field("returnType", &self.return_type); state.end(); @@ -1506,7 +1508,7 @@ impl ESTree for MethodDefinition<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("static", &self.r#static); state.serialize_field("computed", &self.computed); - state.serialize_field("key", &crate::serialize::MethodDefinitionKey(self)); + state.serialize_field("key", &crate::serialize::js::MethodDefinitionKey(self)); state.serialize_field("kind", &self.kind); state.serialize_field("value", &self.value); state.serialize_ts_field("decorators", &self.decorators); @@ -1632,10 +1634,10 @@ impl ESTree for AccessorProperty<'_> { state.serialize_ts_field("decorators", &self.decorators); state.serialize_ts_field("definite", &self.definite); state.serialize_ts_field("accessibility", &self.accessibility); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); state.serialize_ts_field("override", &self.r#override); - state.serialize_ts_field("readonly", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("declare", &crate::serialize::TsFalse(self)); + state.serialize_ts_field("readonly", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("declare", &crate::serialize::basic::TsFalse(self)); state.end(); } } @@ -1658,9 +1660,15 @@ impl ESTree for ImportDeclaration<'_> { state.serialize_field("type", &JsonSafeString("ImportDeclaration")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("specifiers", &crate::serialize::ImportDeclarationSpecifiers(self)); + state.serialize_field( + "specifiers", + &crate::serialize::js::ImportDeclarationSpecifiers(self), + ); state.serialize_field("source", &self.source); - state.serialize_field("attributes", &crate::serialize::ImportDeclarationWithClause(self)); + state.serialize_field( + "attributes", + &crate::serialize::js::ImportDeclarationWithClause(self), + ); state.serialize_ts_field("importKind", &self.import_kind); state.end(); } @@ -1723,11 +1731,7 @@ impl ESTree for ImportNamespaceSpecifier<'_> { impl ESTree for WithClause<'_> { fn serialize(&self, serializer: S) { let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("WithClause")); - state.serialize_field("start", &self.span.start); - state.serialize_field("end", &self.span.end); - state.serialize_field("attributesKeyword", &self.attributes_keyword); - state.serialize_field("withEntries", &self.with_entries); + state.serialize_field("attributes", &self.with_entries); state.end(); } } @@ -1764,7 +1768,7 @@ impl ESTree for ExportNamedDeclaration<'_> { state.serialize_field("source", &self.source); state.serialize_field( "attributes", - &crate::serialize::ExportNamedDeclarationWithClause(self), + &crate::serialize::js::ExportNamedDeclarationWithClause(self), ); state.serialize_ts_field("exportKind", &self.export_kind); state.end(); @@ -1778,7 +1782,7 @@ impl ESTree for ExportDefaultDeclaration<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("declaration", &self.declaration); - state.serialize_ts_field("exportKind", &crate::serialize::TsValue(self)); + state.serialize_ts_field("exportKind", &crate::serialize::basic::TsValue(self)); state.end(); } } @@ -1791,8 +1795,10 @@ impl ESTree for ExportAllDeclaration<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("exported", &self.exported); state.serialize_field("source", &self.source); - state - .serialize_field("attributes", &crate::serialize::ExportAllDeclarationWithClause(self)); + state.serialize_field( + "attributes", + &crate::serialize::js::ExportAllDeclarationWithClause(self), + ); state.serialize_ts_field("exportKind", &self.export_kind); state.end(); } @@ -1893,7 +1899,7 @@ impl ESTree for BooleanLiteral { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("value", &self.value); - state.serialize_field("raw", &crate::serialize::BooleanLiteralRaw(self)); + state.serialize_field("raw", &crate::serialize::literal::BooleanLiteralRaw(self)); state.end(); } } @@ -1904,8 +1910,8 @@ impl ESTree for NullLiteral { state.serialize_field("type", &JsonSafeString("Literal")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("value", &crate::serialize::Null(self)); - state.serialize_field("raw", &crate::serialize::NullLiteralRaw(self)); + state.serialize_field("value", &crate::serialize::basic::Null(self)); + state.serialize_field("raw", &crate::serialize::literal::NullLiteralRaw(self)); state.end(); } } @@ -1928,7 +1934,7 @@ impl ESTree for StringLiteral<'_> { state.serialize_field("type", &JsonSafeString("Literal")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("value", &crate::serialize::StringLiteralValue(self)); + state.serialize_field("value", &crate::serialize::literal::StringLiteralValue(self)); state.serialize_field("raw", &self.raw); state.end(); } @@ -1940,9 +1946,9 @@ impl ESTree for BigIntLiteral<'_> { state.serialize_field("type", &JsonSafeString("Literal")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("value", &crate::serialize::BigIntLiteralValue(self)); + state.serialize_field("value", &crate::serialize::literal::BigIntLiteralValue(self)); state.serialize_field("raw", &self.raw); - state.serialize_field("bigint", &crate::serialize::BigIntLiteralBigint(self)); + state.serialize_field("bigint", &crate::serialize::literal::BigIntLiteralBigint(self)); state.end(); } } @@ -1953,7 +1959,7 @@ impl ESTree for RegExpLiteral<'_> { state.serialize_field("type", &JsonSafeString("Literal")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("value", &crate::serialize::RegExpLiteralValue(self)); + state.serialize_field("value", &crate::serialize::literal::RegExpLiteralValue(self)); state.serialize_field("raw", &self.raw); state.serialize_field("regex", &self.regex); state.end(); @@ -1979,7 +1985,7 @@ impl ESTree for RegExpPattern<'_> { impl ESTree for RegExpFlags { fn serialize(&self, serializer: S) { - crate::serialize::RegExpFlagsConverter(self).serialize(serializer) + crate::serialize::literal::RegExpFlagsConverter(self).serialize(serializer) } } @@ -1989,7 +1995,10 @@ impl ESTree for JSXElement<'_> { state.serialize_field("type", &JsonSafeString("JSXElement")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("openingElement", &crate::serialize::JSXElementOpening(self)); + state.serialize_field( + "openingElement", + &crate::serialize::jsx::JSXElementOpeningElement(self), + ); state.serialize_field("closingElement", &self.closing_element); state.serialize_field("children", &self.children); state.end(); @@ -2004,7 +2013,10 @@ impl ESTree for JSXOpeningElement<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("attributes", &self.attributes); state.serialize_field("name", &self.name); - state.serialize_field("selfClosing", &crate::serialize::JSXOpeningElementSelfClosing(self)); + state.serialize_field( + "selfClosing", + &crate::serialize::jsx::JSXOpeningElementSelfClosing(self), + ); state.serialize_ts_field("typeArguments", &self.type_arguments); state.end(); } @@ -2036,7 +2048,7 @@ impl ESTree for JSXFragment<'_> { impl ESTree for JSXOpeningFragment { fn serialize(&self, serializer: S) { - crate::serialize::JSXOpeningFragmentConverter(self).serialize(serializer) + crate::serialize::jsx::JSXOpeningFragmentConverter(self).serialize(serializer) } } @@ -2055,12 +2067,12 @@ impl ESTree for JSXElementName<'_> { match self { Self::Identifier(it) => it.serialize(serializer), Self::IdentifierReference(it) => { - crate::serialize::JSXElementIdentifierReference(it).serialize(serializer) + crate::serialize::jsx::JSXElementIdentifierReference(it).serialize(serializer) } Self::NamespacedName(it) => it.serialize(serializer), Self::MemberExpression(it) => it.serialize(serializer), Self::ThisExpression(it) => { - crate::serialize::JSXElementThisExpression(it).serialize(serializer) + crate::serialize::jsx::JSXElementThisExpression(it).serialize(serializer) } } } @@ -2094,11 +2106,11 @@ impl ESTree for JSXMemberExpressionObject<'_> { fn serialize(&self, serializer: S) { match self { Self::IdentifierReference(it) => { - crate::serialize::JSXElementIdentifierReference(it).serialize(serializer) + crate::serialize::jsx::JSXElementIdentifierReference(it).serialize(serializer) } Self::MemberExpression(it) => it.serialize(serializer), Self::ThisExpression(it) => { - crate::serialize::JSXElementThisExpression(it).serialize(serializer) + crate::serialize::jsx::JSXElementThisExpression(it).serialize(serializer) } } } @@ -2280,9 +2292,9 @@ impl ESTree for TSThisParameter<'_> { state.serialize_field("type", &JsonSafeString("Identifier")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("name", &crate::serialize::This(self)); - state.serialize_field("decorators", &crate::serialize::EmptyArray(self)); - state.serialize_field("optional", &crate::serialize::False(self)); + state.serialize_field("name", &crate::serialize::basic::This(self)); + state.serialize_field("decorators", &crate::serialize::basic::EmptyArray(self)); + state.serialize_field("optional", &crate::serialize::basic::False(self)); state.serialize_field("typeAnnotation", &self.type_annotation); state.end(); } @@ -2320,7 +2332,7 @@ impl ESTree for TSEnumMember<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("id", &self.id); - state.serialize_field("computed", &crate::serialize::TSEnumMemberComputed(self)); + state.serialize_field("computed", &crate::serialize::ts::TSEnumMemberComputed(self)); state.serialize_field("initializer", &self.initializer); state.end(); } @@ -2756,7 +2768,7 @@ impl ESTree for TSTypeName<'_> { fn serialize(&self, serializer: S) { match self { Self::IdentifierReference(it) => { - crate::serialize::TSTypeNameIdentifierReference(it).serialize(serializer) + crate::serialize::ts::TSTypeNameIdentifierReference(it).serialize(serializer) } Self::QualifiedName(it) => it.serialize(serializer), } @@ -2843,7 +2855,10 @@ impl ESTree for TSClassImplements<'_> { state.serialize_field("type", &JsonSafeString("TSClassImplements")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("expression", &crate::serialize::TSClassImplementsExpression(self)); + state.serialize_field( + "expression", + &crate::serialize::ts::TSClassImplementsExpression(self), + ); state.serialize_field("typeArguments", &self.type_arguments); state.end(); } @@ -2886,8 +2901,8 @@ impl ESTree for TSPropertySignature<'_> { state.serialize_field("readonly", &self.readonly); state.serialize_field("key", &self.key); state.serialize_field("typeAnnotation", &self.type_annotation); - state.serialize_field("accessibility", &crate::serialize::Null(self)); - state.serialize_field("static", &crate::serialize::False(self)); + state.serialize_field("accessibility", &crate::serialize::basic::Null(self)); + state.serialize_field("static", &crate::serialize::basic::False(self)); state.end(); } } @@ -2914,7 +2929,7 @@ impl ESTree for TSIndexSignature<'_> { state.serialize_field("typeAnnotation", &self.type_annotation); state.serialize_field("readonly", &self.readonly); state.serialize_field("static", &self.r#static); - state.serialize_field("accessibility", &crate::serialize::Null(self)); + state.serialize_field("accessibility", &crate::serialize::basic::Null(self)); state.end(); } } @@ -2928,7 +2943,7 @@ impl ESTree for TSCallSignatureDeclaration<'_> { state.serialize_field("typeParameters", &self.type_parameters); state.serialize_field( "params", - &crate::serialize::TSCallSignatureDeclarationFormalParameters(self), + &crate::serialize::ts::TSCallSignatureDeclarationParams(self), ); state.serialize_field("returnType", &self.return_type); state.end(); @@ -2956,11 +2971,11 @@ impl ESTree for TSMethodSignature<'_> { state.serialize_field("optional", &self.optional); state.serialize_field("kind", &self.kind); state.serialize_field("typeParameters", &self.type_parameters); - state.serialize_field("params", &crate::serialize::TSMethodSignatureFormalParameters(self)); + state.serialize_field("params", &crate::serialize::ts::TSMethodSignatureParams(self)); state.serialize_field("returnType", &self.return_type); - state.serialize_field("accessibility", &crate::serialize::Null(self)); - state.serialize_field("readonly", &crate::serialize::False(self)); - state.serialize_field("static", &crate::serialize::False(self)); + state.serialize_field("accessibility", &crate::serialize::basic::Null(self)); + state.serialize_field("readonly", &crate::serialize::basic::False(self)); + state.serialize_field("static", &crate::serialize::basic::False(self)); state.end(); } } @@ -2985,8 +3000,8 @@ impl ESTree for TSIndexSignatureName<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("name", &JsonSafeString(self.name.as_str())); - state.serialize_field("decorators", &crate::serialize::EmptyArray(self)); - state.serialize_field("optional", &crate::serialize::False(self)); + state.serialize_field("decorators", &crate::serialize::basic::EmptyArray(self)); + state.serialize_field("optional", &crate::serialize::basic::False(self)); state.serialize_field("typeAnnotation", &self.type_annotation); state.end(); } @@ -3028,7 +3043,7 @@ impl ESTree for TSTypePredicateName<'_> { impl ESTree for TSModuleDeclaration<'_> { fn serialize(&self, serializer: S) { - crate::serialize::TSModuleDeclarationConverter(self).serialize(serializer) + crate::serialize::ts::TSModuleDeclarationConverter(self).serialize(serializer) } } @@ -3110,7 +3125,7 @@ impl ESTree for TSTypeQueryExprName<'_> { match self { Self::TSImportType(it) => it.serialize(serializer), Self::IdentifierReference(it) => { - crate::serialize::TSTypeNameIdentifierReference(it).serialize(serializer) + crate::serialize::ts::TSTypeNameIdentifierReference(it).serialize(serializer) } Self::QualifiedName(it) => it.serialize(serializer), } @@ -3138,7 +3153,7 @@ impl ESTree for TSFunctionType<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("typeParameters", &self.type_parameters); - state.serialize_field("params", &crate::serialize::TSFunctionTypeFormalParameters(self)); + state.serialize_field("params", &crate::serialize::ts::TSFunctionTypeParams(self)); state.serialize_field("returnType", &self.return_type); state.end(); } @@ -3166,10 +3181,10 @@ impl ESTree for TSMappedType<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("nameType", &self.name_type); state.serialize_field("typeAnnotation", &self.type_annotation); - state.serialize_field("optional", &crate::serialize::TSMappedTypeOptional(self)); + state.serialize_field("optional", &crate::serialize::ts::TSMappedTypeOptional(self)); state.serialize_field("readonly", &self.readonly); - state.serialize_field("key", &crate::serialize::TSMappedTypeKey(self)); - state.serialize_field("constraint", &crate::serialize::TSMappedTypeConstraint(self)); + state.serialize_field("key", &crate::serialize::ts::TSMappedTypeKey(self)); + state.serialize_field("constraint", &crate::serialize::ts::TSMappedTypeConstraint(self)); state.end(); } } @@ -3177,7 +3192,7 @@ impl ESTree for TSMappedType<'_> { impl ESTree for TSMappedTypeModifierOperator { fn serialize(&self, serializer: S) { match self { - Self::True => crate::serialize::True(()).serialize(serializer), + Self::True => crate::serialize::basic::True(()).serialize(serializer), Self::Plus => JsonSafeString("+").serialize(serializer), Self::Minus => JsonSafeString("-").serialize(serializer), } @@ -3250,7 +3265,7 @@ impl ESTree for TSModuleReference<'_> { match self { Self::ExternalModuleReference(it) => it.serialize(serializer), Self::IdentifierReference(it) => { - crate::serialize::TSTypeNameIdentifierReference(it).serialize(serializer) + crate::serialize::ts::TSTypeNameIdentifierReference(it).serialize(serializer) } Self::QualifiedName(it) => it.serialize(serializer), } diff --git a/crates/oxc_ast/src/generated/derive_take_in.rs b/crates/oxc_ast/src/generated/derive_take_in.rs index b08a1e5d83ec6..b593e63880233 100644 --- a/crates/oxc_ast/src/generated/derive_take_in.rs +++ b/crates/oxc_ast/src/generated/derive_take_in.rs @@ -1,7 +1,7 @@ // Auto-generated code, DO NOT EDIT DIRECTLY! // To edit this generated file you have to edit `tasks/ast_tools/src/derives/take_in.rs`. -#![allow(clippy::needless_lifetimes)] +#![expect(clippy::elidable_lifetime_names)] use oxc_allocator::TakeIn; diff --git a/crates/oxc_ast/src/serialize.rs b/crates/oxc_ast/src/serialize.rs deleted file mode 100644 index 16558efbe0027..0000000000000 --- a/crates/oxc_ast/src/serialize.rs +++ /dev/null @@ -1,1560 +0,0 @@ -use std::cmp; - -use cow_utils::CowUtils; - -use crate::ast::*; -use oxc_ast_macros::ast_meta; -use oxc_estree::{ - CompactFixesJSSerializer, CompactFixesTSSerializer, CompactJSSerializer, CompactTSSerializer, - Concat2, ESTree, FlatStructSerializer, JsonSafeString, LoneSurrogatesString, - PrettyFixesJSSerializer, PrettyFixesTSSerializer, PrettyJSSerializer, PrettyTSSerializer, - SequenceSerializer, Serializer, StructSerializer, -}; -use oxc_span::GetSpan; - -/// Main serialization methods for `Program`. -/// -/// Note: 8 separate methods for the different serialization options, rather than 1 method -/// with behavior controlled by flags -/// (e.g. `fn to_estree_json(&self, with_ts: bool, pretty: bool, fixes: bool)`) -/// to avoid bloating binary size. -/// -/// Most consumers (and Oxc crates) will use only 1 of these methods, so we don't want to needlessly -/// compile all 8 serializers when only 1 is used. -/// -/// Initial capacity for serializer's buffer is an estimate based on our benchmark fixtures -/// of ratio of source text size to JSON size. -/// -/// | File | Compact TS | Compact JS | Pretty TS | Pretty JS | -/// |----------------------------|------------|------------|-----------|-----------| -/// | antd.js | 10 | 9 | 76 | 72 | -/// | cal.com.tsx | 10 | 9 | 40 | 37 | -/// | checker.ts | 7 | 6 | 27 | 24 | -/// | pdf.mjs | 13 | 12 | 71 | 67 | -/// | RadixUIAdoptionSection.jsx | 10 | 9 | 45 | 44 | -/// |----------------------------|------------|------------|-----------|-----------| -/// | Maximum | 13 | 12 | 76 | 72 | -/// -/// It's better to over-estimate than under-estimate, as having to grow the buffer is expensive, -/// so have gone on the generous side. -const JSON_CAPACITY_RATIO_COMPACT: usize = 16; -const JSON_CAPACITY_RATIO_PRETTY: usize = 80; - -impl Program<'_> { - /// Serialize AST to ESTree JSON, including TypeScript fields. - pub fn to_estree_ts_json(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; - let mut serializer = CompactTSSerializer::with_capacity(capacity); - self.serialize(&mut serializer); - serializer.into_string() - } - - /// Serialize AST to ESTree JSON, without TypeScript fields. - pub fn to_estree_js_json(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; - let mut serializer = CompactJSSerializer::with_capacity(capacity); - self.serialize(&mut serializer); - serializer.into_string() - } - - /// Serialize AST to pretty-printed ESTree JSON, including TypeScript fields. - pub fn to_pretty_estree_ts_json(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; - let mut serializer = PrettyTSSerializer::with_capacity(capacity); - self.serialize(&mut serializer); - serializer.into_string() - } - - /// Serialize AST to pretty-printed ESTree JSON, without TypeScript fields. - pub fn to_pretty_estree_js_json(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; - let mut serializer = PrettyJSSerializer::with_capacity(capacity); - self.serialize(&mut serializer); - serializer.into_string() - } - - /// Serialize AST to ESTree JSON, including TypeScript fields, with list of fixes. - pub fn to_estree_ts_json_with_fixes(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; - let serializer = CompactFixesTSSerializer::with_capacity(capacity); - serializer.serialize_with_fixes(self) - } - - /// Serialize AST to ESTree JSON, without TypeScript fields, with list of fixes. - pub fn to_estree_js_json_with_fixes(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; - let serializer = CompactFixesJSSerializer::with_capacity(capacity); - serializer.serialize_with_fixes(self) - } - - /// Serialize AST to pretty-printed ESTree JSON, including TypeScript fields, with list of fixes. - pub fn to_pretty_estree_ts_json_with_fixes(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; - let serializer = PrettyFixesTSSerializer::with_capacity(capacity); - serializer.serialize_with_fixes(self) - } - - /// Serialize AST to pretty-printed ESTree JSON, without TypeScript fields, with list of fixes. - pub fn to_pretty_estree_js_json_with_fixes(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; - let serializer = PrettyFixesJSSerializer::with_capacity(capacity); - serializer.serialize_with_fixes(self) - } -} - -// -------------------- -// Program -// -------------------- - -/// Serializer for `Program`. -/// -/// In TS AST, set start span to start of first directive or statement. -/// This is required because unlike Acorn, TS-ESLint excludes whitespace and comments -/// from the `Program` start span. -/// See for more info. -/// -/// Special case where first statement is an `ExportNamedDeclaration` or `ExportDefaultDeclaration` -/// exporting a class with decorators, where one of the decorators is before `export`. -/// In these cases, the span of the statement starts after the span of the decorators. -/// e.g. `@dec export class C {}` - `ExportNamedDeclaration` span start is 5, `Decorator` span start is 0. -/// `Program` span start is 0 (not 5). -#[ast_meta] -#[estree(raw_deser = " - const body = DESER[Vec](POS_OFFSET.directives); - body.push(...DESER[Vec](POS_OFFSET.body)); - - /* IF_JS */ - const start = DESER[u32](POS_OFFSET.span.start); - /* END_IF_JS */ - - const end = DESER[u32](POS_OFFSET.span.end); - - /* IF_TS */ - let start; - if (body.length > 0) { - const first = body[0]; - start = first.start; - if (first.type === 'ExportNamedDeclaration' || first.type === 'ExportDefaultDeclaration') { - const {declaration} = first; - if ( - declaration !== null && declaration.type === 'ClassDeclaration' - && declaration.decorators.length > 0 - ) { - const decoratorStart = declaration.decorators[0].start; - if (decoratorStart < start) start = decoratorStart; - } - } - } else { - start = end; - } - /* END_IF_TS */ - - const program = { - type: 'Program', - start, - end, - body, - sourceType: DESER[ModuleKind](POS_OFFSET.source_type.module_kind), - hashbang: DESER[Option](POS_OFFSET.hashbang), - }; - program -")] -pub struct ProgramConverter<'a, 'b>(pub &'b Program<'a>); - -impl ESTree for ProgramConverter<'_, '_> { - fn serialize(&self, serializer: S) { - let program = self.0; - let span_start = - if S::INCLUDE_TS_FIELDS { get_ts_start_span(program) } else { program.span.start }; - - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("Program")); - state.serialize_field("start", &span_start); - state.serialize_field("end", &program.span.end); - state.serialize_field("body", &Concat2(&program.directives, &program.body)); - state.serialize_field("sourceType", &program.source_type.module_kind()); - state.serialize_field("hashbang", &program.hashbang); - state.end(); - } -} - -fn get_ts_start_span(program: &Program<'_>) -> u32 { - if let Some(first_directive) = program.directives.first() { - return first_directive.span.start; - } - - let Some(first_stmt) = program.body.first() else { - // Program contains no statements or directives. Span start = span end. - return program.span.end; - }; - - match first_stmt { - Statement::ExportNamedDeclaration(decl) => { - let start = decl.span.start; - if let Some(Declaration::ClassDeclaration(class)) = &decl.declaration { - if let Some(decorator) = class.decorators.first() { - return cmp::min(start, decorator.span.start); - } - } - start - } - Statement::ExportDefaultDeclaration(decl) => { - let start = decl.span.start; - if let ExportDefaultDeclarationKind::ClassDeclaration(class) = &decl.declaration { - if let Some(decorator) = class.decorators.first() { - return cmp::min(start, decorator.span.start); - } - } - start - } - _ => first_stmt.span().start, - } -} - -// -------------------- -// Basic types -// -------------------- - -/// Serialized as `null`. -#[ast_meta] -#[estree(ts_type = "null", raw_deser = "null")] -pub struct Null(pub T); - -impl ESTree for Null { - fn serialize(&self, serializer: S) { - ().serialize(serializer); - } -} - -#[ast_meta] -#[estree(ts_type = "null", raw_deser = "null")] -#[ts] -pub struct TsNull(pub T); - -impl ESTree for TsNull { - fn serialize(&self, serializer: S) { - Null(()).serialize(serializer); - } -} - -/// Serialized as `true`. -#[ast_meta] -#[estree(ts_type = "true", raw_deser = "true")] -pub struct True(pub T); - -impl ESTree for True { - fn serialize(&self, serializer: S) { - true.serialize(serializer); - } -} - -/// Serialized as `false`. -#[ast_meta] -#[estree(ts_type = "false", raw_deser = "false")] -pub struct False(pub T); - -impl ESTree for False { - fn serialize(&self, serializer: S) { - false.serialize(serializer); - } -} - -#[ast_meta] -#[estree(ts_type = "false", raw_deser = "false")] -#[ts] -pub struct TsFalse(pub T); - -impl ESTree for TsFalse { - fn serialize(&self, serializer: S) { - false.serialize(serializer); - } -} - -/// Serialized as `"value"`. -#[ast_meta] -#[estree(ts_type = "'value'", raw_deser = "'value'")] -#[ts] -pub struct TsValue(pub T); - -impl ESTree for TsValue { - fn serialize(&self, serializer: S) { - JsonSafeString("value").serialize(serializer); - } -} - -/// Serialized as `"in"`. -#[ast_meta] -#[estree(ts_type = "'in'", raw_deser = "'in'")] -pub struct In(pub T); - -impl ESTree for In { - fn serialize(&self, serializer: S) { - JsonSafeString("in").serialize(serializer); - } -} - -/// Serialized as `"init"`. -#[ast_meta] -#[estree(ts_type = "'init'", raw_deser = "'init'")] -pub struct Init(pub T); - -impl ESTree for Init { - fn serialize(&self, serializer: S) { - JsonSafeString("init").serialize(serializer); - } -} - -/// Serialized as `"this"`. -#[ast_meta] -#[estree(ts_type = "'this'", raw_deser = "'this'")] -pub struct This(pub T); - -impl ESTree for This { - fn serialize(&self, serializer: S) { - JsonSafeString("this").serialize(serializer); - } -} - -/// Serialized as `[]`. -#[ast_meta] -#[estree(ts_type = "[]", raw_deser = "[]")] -pub struct EmptyArray(pub T); - -impl ESTree for EmptyArray { - fn serialize(&self, serializer: S) { - [(); 0].serialize(serializer); - } -} - -#[ast_meta] -#[estree(ts_type = "[]", raw_deser = "[]")] -#[ts] -pub struct TsEmptyArray(pub T); - -impl ESTree for TsEmptyArray { - fn serialize(&self, serializer: S) { - EmptyArray(()).serialize(serializer); - } -} - -// -------------------- -// Literals -// -------------------- - -/// Serializer for `raw` field of `BooleanLiteral`. -#[ast_meta] -#[estree( - ts_type = "string | null", - raw_deser = "(THIS.start === 0 && THIS.end === 0) ? null : THIS.value + ''" -)] -pub struct BooleanLiteralRaw<'b>(pub &'b BooleanLiteral); - -impl ESTree for BooleanLiteralRaw<'_> { - fn serialize(&self, serializer: S) { - #[expect(clippy::collection_is_never_read)] // Clippy is wrong! - let raw = if self.0.span.is_unspanned() { - None - } else if self.0.value { - Some(JsonSafeString("true")) - } else { - Some(JsonSafeString("false")) - }; - raw.serialize(serializer); - } -} - -/// Serializer for `raw` field of `NullLiteral`. -#[ast_meta] -#[estree( - ts_type = "'null' | null", - raw_deser = "(THIS.start === 0 && THIS.end === 0) ? null : 'null'" -)] -pub struct NullLiteralRaw<'b>(pub &'b NullLiteral); - -impl ESTree for NullLiteralRaw<'_> { - fn serialize(&self, serializer: S) { - #[expect(clippy::collection_is_never_read)] // Clippy is wrong! - let raw = if self.0.span.is_unspanned() { None } else { Some(JsonSafeString("null")) }; - raw.serialize(serializer); - } -} - -/// Serializer for `value` field of `StringLiteral`. -/// -/// Handle when `lone_surrogates` flag is set, indicating the string contains lone surrogates. -#[ast_meta] -#[estree( - ts_type = "string", - raw_deser = r#" - let value = DESER[Atom](POS_OFFSET.value); - if (DESER[bool](POS_OFFSET.lone_surrogates)) { - value = value.replace(/\uFFFD(.{4})/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16))); - } - value - "# -)] -pub struct StringLiteralValue<'a, 'b>(pub &'b StringLiteral<'a>); - -impl ESTree for StringLiteralValue<'_, '_> { - fn serialize(&self, serializer: S) { - let lit = self.0; - #[expect(clippy::if_not_else)] - if !lit.lone_surrogates { - lit.value.serialize(serializer); - } else { - // String contains lone surrogates - self.serialize_lone_surrogates(serializer); - } - } -} - -impl StringLiteralValue<'_, '_> { - #[cold] - #[inline(never)] - fn serialize_lone_surrogates(&self, serializer: S) { - LoneSurrogatesString(self.0.value.as_str()).serialize(serializer); - } -} - -/// Serializer for `bigint` field of `BigIntLiteral`. -#[ast_meta] -#[estree(ts_type = "string", raw_deser = "THIS.raw.slice(0, -1).replace(/_/g, '')")] -pub struct BigIntLiteralBigint<'a, 'b>(pub &'b BigIntLiteral<'a>); - -impl ESTree for BigIntLiteralBigint<'_, '_> { - fn serialize(&self, serializer: S) { - let bigint = self.0.raw[..self.0.raw.len() - 1].cow_replace('_', ""); - JsonSafeString(bigint.as_ref()).serialize(serializer); - } -} - -/// Serializer for `value` field of `BigIntLiteral`. -/// -/// Serialized as `null` in JSON, but updated on JS side to contain a `BigInt`. -#[ast_meta] -#[estree(ts_type = "bigint", raw_deser = "BigInt(THIS.bigint)")] -pub struct BigIntLiteralValue<'a, 'b>(#[expect(dead_code)] pub &'b BigIntLiteral<'a>); - -impl ESTree for BigIntLiteralValue<'_, '_> { - fn serialize(&self, mut serializer: S) { - // Record that this node needs fixing on JS side - serializer.record_fix_path(); - Null(()).serialize(serializer); - } -} - -/// Serializer for `value` field of `RegExpLiteral`. -/// -/// Serialized as `null` in JSON, but updated on JS side to contain a `RegExp` if the regexp is valid. -#[ast_meta] -#[estree( - ts_type = "RegExp | null", - raw_deser = " - let value = null; - try { - value = new RegExp(THIS.regex.pattern, THIS.regex.flags); - } catch (e) {} - value - " -)] -pub struct RegExpLiteralValue<'a, 'b>(#[expect(dead_code)] pub &'b RegExpLiteral<'a>); - -impl ESTree for RegExpLiteralValue<'_, '_> { - fn serialize(&self, mut serializer: S) { - // Record that this node needs fixing on JS side - serializer.record_fix_path(); - Null(()).serialize(serializer); - } -} - -#[ast_meta] -#[estree( - ts_type = "string", - raw_deser = " - const flagBits = DESER[u8](POS); - let flags = ''; - // Alphabetical order - if (flagBits & 64) flags += 'd'; - if (flagBits & 1) flags += 'g'; - if (flagBits & 2) flags += 'i'; - if (flagBits & 4) flags += 'm'; - if (flagBits & 8) flags += 's'; - if (flagBits & 16) flags += 'u'; - if (flagBits & 128) flags += 'v'; - if (flagBits & 32) flags += 'y'; - flags - " -)] -pub struct RegExpFlagsConverter<'b>(pub &'b RegExpFlags); - -impl ESTree for RegExpFlagsConverter<'_> { - fn serialize(&self, serializer: S) { - JsonSafeString(self.0.to_inline_string().as_str()).serialize(serializer); - } -} - -/// Converter for `TemplateElement`. -/// -/// Decode `cooked` if it contains lone surrogates. -/// -/// Also adjust span in TS AST. -/// TS-ESLint produces a different span from Acorn: -/// ```js -/// const template = `abc${x}def${x}ghi`; -/// // Acorn: ^^^ ^^^ ^^^ -/// // TS-ESLint: ^^^^^^ ^^^^^^ ^^^^^ -/// ``` -// TODO: Raise an issue on TS-ESLint and see if they'll change span to match Acorn. -#[ast_meta] -#[estree(raw_deser = r#" - const tail = DESER[bool](POS_OFFSET.tail), - start = DESER[u32](POS_OFFSET.span.start) /* IF_TS */ - 1 /* END_IF_TS */, - end = DESER[u32](POS_OFFSET.span.end) /* IF_TS */ + 2 - tail /* END_IF_TS */, - value = DESER[TemplateElementValue](POS_OFFSET.value); - if (value.cooked !== null && DESER[bool](POS_OFFSET.lone_surrogates)) { - value.cooked = value.cooked - .replace(/\uFFFD(.{4})/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16))); - } - { type: 'TemplateElement', start, end, value, tail } -"#)] -pub struct TemplateElementConverter<'a, 'b>(pub &'b TemplateElement<'a>); - -impl ESTree for TemplateElementConverter<'_, '_> { - fn serialize(&self, serializer: S) { - let element = self.0; - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("TemplateElement")); - - let mut span = element.span; - if S::INCLUDE_TS_FIELDS { - span.start -= 1; - span.end += if element.tail { 1 } else { 2 }; - } - state.serialize_field("start", &span.start); - state.serialize_field("end", &span.end); - - state.serialize_field("value", &TemplateElementValue(element)); - state.serialize_field("tail", &element.tail); - state.end(); - } -} - -/// Serializer for `value` field of `TemplateElement`. -/// -/// Handle when `lone_surrogates` flag is set, indicating the cooked string contains lone surrogates. -/// -/// Implementation for `raw_deser` is included in `TemplateElementConverter` above. -#[ast_meta] -#[estree( - ts_type = "TemplateElementValue", - raw_deser = "(() => { throw new Error('Should not appear in deserializer code'); })()" -)] -pub struct TemplateElementValue<'a, 'b>(pub &'b TemplateElement<'a>); - -impl ESTree for TemplateElementValue<'_, '_> { - fn serialize(&self, serializer: S) { - let element = self.0; - #[expect(clippy::if_not_else)] - if !element.lone_surrogates { - element.value.serialize(serializer); - } else { - // String contains lone surrogates - self.serialize_lone_surrogates(serializer); - } - } -} - -impl TemplateElementValue<'_, '_> { - #[cold] - #[inline(never)] - fn serialize_lone_surrogates(&self, serializer: S) { - let value = &self.0.value; - - let mut state = serializer.serialize_struct(); - state.serialize_field("raw", &value.raw); - - let cooked = value.cooked.as_ref().map(|cooked| LoneSurrogatesString(cooked.as_str())); - state.serialize_field("cooked", &cooked); - - state.end(); - } -} - -// -------------------- -// Various -// -------------------- - -/// Serialize `ArrayExpressionElement::Elision` variant as `null`. -#[ast_meta] -#[estree(ts_type = "null", raw_deser = "null")] -pub struct ElisionConverter<'b>(#[expect(dead_code)] pub &'b Elision); - -impl ESTree for ElisionConverter<'_> { - fn serialize(&self, serializer: S) { - Null(()).serialize(serializer); - } -} - -/// Serialize `FormalParameters`, to be estree compatible, with `items` and `rest` fields combined -/// and `argument` field flattened. -#[ast_meta] -#[estree( - ts_type = "ParamPattern[]", - raw_deser = " - const params = DESER[Vec](POS_OFFSET.items); - if (uint32[(POS_OFFSET.rest) >> 2] !== 0 && uint32[(POS_OFFSET.rest + 4) >> 2] !== 0) { - pos = uint32[(POS_OFFSET.rest) >> 2]; - params.push({ - type: 'RestElement', - start: DESER[u32]( POS_OFFSET.span.start ), - end: DESER[u32]( POS_OFFSET.span.end ), - argument: DESER[BindingPatternKind]( POS_OFFSET.argument.kind ), - /* IF_TS */ - typeAnnotation: DESER[Option>]( - POS_OFFSET.argument.type_annotation - ), - optional: DESER[bool]( POS_OFFSET.argument.optional ), - decorators: [], - value: null, - /* END_IF_TS */ - }); - } - params - " -)] -pub struct FormalParametersConverter<'a, 'b>(pub &'b FormalParameters<'a>); - -impl ESTree for FormalParametersConverter<'_, '_> { - fn serialize(&self, serializer: S) { - let mut seq = serializer.serialize_sequence(); - for item in &self.0.items { - seq.serialize_element(item); - } - - if let Some(rest) = &self.0.rest { - seq.serialize_element(&FormalParametersRest(rest)); - } - - seq.end(); - } -} - -struct FormalParametersRest<'a, 'b>(&'b BindingRestElement<'a>); - -impl ESTree for FormalParametersRest<'_, '_> { - fn serialize(&self, serializer: S) { - let rest = self.0; - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("RestElement")); - state.serialize_field("start", &rest.span.start); - state.serialize_field("end", &rest.span.end); - state.serialize_field("argument", &rest.argument.kind); - state.serialize_ts_field("typeAnnotation", &rest.argument.type_annotation); - state.serialize_ts_field("optional", &rest.argument.optional); - state.serialize_ts_field("decorators", &EmptyArray(())); - state.serialize_ts_field("value", &Null(())); - state.end(); - } -} - -/// Converter for `FormalParameter`. -/// -/// In TS-ESTree AST, if `accessibility` is `Some`, or `readonly` or `override` is `true`, -/// is serialized as `TSParameterProperty` instead, which has a different object shape. -#[ast_meta] -#[estree( - ts_type = "FormalParameter | TSParameterProperty", - raw_deser = " - /* IF_JS */ - DESER[BindingPatternKind](POS_OFFSET.pattern.kind) - /* END_IF_JS */ - - /* IF_TS */ - const accessibility = DESER[Option](POS_OFFSET.accessibility), - readonly = DESER[bool](POS_OFFSET.readonly), - override = DESER[bool](POS_OFFSET.override); - let param; - if (accessibility === null && !readonly && !override) { - param = { - ...DESER[BindingPatternKind](POS_OFFSET.pattern.kind), - typeAnnotation: DESER[Option>](POS_OFFSET.pattern.type_annotation), - optional: DESER[bool](POS_OFFSET.pattern.optional), - decorators: DESER[Vec](POS_OFFSET.decorators), - }; - } else { - param = { - type: 'TSParameterProperty', - start: DESER[u32](POS_OFFSET.span.start), - end: DESER[u32](POS_OFFSET.span.end), - accessibility, - decorators: DESER[Vec](POS_OFFSET.decorators), - override, - parameter: DESER[BindingPattern](POS_OFFSET.pattern), - readonly, - static: false, - }; - } - param - /* END_IF_TS */ - " -)] -pub struct FormalParameterConverter<'a, 'b>(pub &'b FormalParameter<'a>); - -impl ESTree for FormalParameterConverter<'_, '_> { - fn serialize(&self, serializer: S) { - let param = self.0; - - if S::INCLUDE_TS_FIELDS { - if param.has_modifier() { - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("TSParameterProperty")); - state.serialize_field("start", ¶m.span.start); - state.serialize_field("end", ¶m.span.end); - state.serialize_field("accessibility", ¶m.accessibility); - state.serialize_field("decorators", ¶m.decorators); - state.serialize_field("override", ¶m.r#override); - state.serialize_field("parameter", ¶m.pattern); - state.serialize_field("readonly", ¶m.readonly); - state.serialize_field("static", &False(())); - state.end(); - } else { - let mut state = serializer.serialize_struct(); - param.pattern.kind.serialize(FlatStructSerializer(&mut state)); - state.serialize_field("typeAnnotation", ¶m.pattern.type_annotation); - state.serialize_field("optional", ¶m.pattern.optional); - state.serialize_field("decorators", ¶m.decorators); - state.end(); - } - } else { - param.pattern.kind.serialize(serializer); - } - } -} - -/// Serializer for `params` field of `Function`. -/// -/// In TS-ESTree, this adds `this_param` to start of the `params` array. -#[ast_meta] -#[estree( - ts_type = "ParamPattern[]", - raw_deser = " - const params = DESER[Box](POS_OFFSET.params); - /* IF_TS */ - const thisParam = DESER[Option>](POS_OFFSET.this_param); - if (thisParam !== null) params.unshift(thisParam); - /* END_IF_TS */ - params - " -)] -pub struct FunctionFormalParameters<'a, 'b>(pub &'b Function<'a>); - -impl ESTree for FunctionFormalParameters<'_, '_> { - fn serialize(&self, serializer: S) { - let mut seq = serializer.serialize_sequence(); - - if S::INCLUDE_TS_FIELDS { - if let Some(this_param) = &self.0.this_param { - seq.serialize_element(this_param); - } - } - - for item in &self.0.params.items { - seq.serialize_element(item); - } - - if let Some(rest) = &self.0.params.rest { - seq.serialize_element(&FormalParametersRest(rest)); - } - - seq.end(); - } -} - -/// Serializer for `key` field of `MethodDefinition`. -/// -/// In TS-ESTree `"constructor"` in `class C { "constructor"() {} }` -/// is represented as an `Identifier`. -/// In Acorn and Espree, it's a `Literal`. -/// -#[ast_meta] -#[estree( - ts_type = "PropertyKey", - raw_deser = " - /* IF_JS */ - DESER[PropertyKey](POS_OFFSET.key) - /* END_IF_JS */ - - /* IF_TS */ - let key = DESER[PropertyKey](POS_OFFSET.key); - if (THIS.kind === 'constructor') { - key = { - type: 'Identifier', - start: key.start, - end: key.end, - name: 'constructor', - decorators: [], - optional: false, - typeAnnotation: null, - }; - } - key - /* END_IF_TS */ - " -)] -pub struct MethodDefinitionKey<'a, 'b>(pub &'b MethodDefinition<'a>); - -impl ESTree for MethodDefinitionKey<'_, '_> { - fn serialize(&self, serializer: S) { - let method = self.0; - if S::INCLUDE_TS_FIELDS && method.kind == MethodDefinitionKind::Constructor { - // `key` can only be either an identifier `constructor`, or string `"constructor"` - let span = method.key.span(); - IdentifierName { span, name: Atom::from("constructor") }.serialize(serializer); - } else { - method.key.serialize(serializer); - } - } -} - -/// Serializer for `specifiers` field of `ImportDeclaration`. -/// -/// Serialize `specifiers` as an empty array if it's `None`. -#[ast_meta] -#[estree( - ts_type = "Array", - raw_deser = " - let specifiers = DESER[Option>](POS_OFFSET.specifiers); - if (specifiers === null) specifiers = []; - specifiers - " -)] -pub struct ImportDeclarationSpecifiers<'a, 'b>(pub &'b ImportDeclaration<'a>); - -impl ESTree for ImportDeclarationSpecifiers<'_, '_> { - fn serialize(&self, serializer: S) { - if let Some(specifiers) = &self.0.specifiers { - specifiers.serialize(serializer); - } else { - EmptyArray(()).serialize(serializer); - } - } -} - -/// Serializer for `ArrowFunctionExpression`'s `body` field. -/// -/// Serializes as either an expression (if `expression` property is set), -/// or a `BlockStatement` (if it's not). -#[ast_meta] -#[estree( - ts_type = "FunctionBody | Expression", - raw_deser = " - let body = DESER[Box](POS_OFFSET.body); - THIS.expression ? body.body[0].expression : body - " -)] -pub struct ArrowFunctionExpressionBody<'a>(pub &'a ArrowFunctionExpression<'a>); - -impl ESTree for ArrowFunctionExpressionBody<'_> { - fn serialize(&self, serializer: S) { - if let Some(expression) = self.0.get_expression() { - expression.serialize(serializer); - } else { - self.0.body.serialize(serializer); - } - } -} - -/// Serializer for `AssignmentTargetPropertyIdentifier`'s `init` field -/// (which is renamed to `value` in ESTree AST). -#[ast_meta] -#[estree( - ts_type = "IdentifierReference | AssignmentTargetWithDefault", - raw_deser = " - const init = DESER[Option](POS_OFFSET.init), - keyCopy = {...THIS.key}, - value = init === null - ? keyCopy - : { - type: 'AssignmentPattern', - start: THIS.start, - end: THIS.end, - left: keyCopy, - right: init, - /* IF_TS */ - typeAnnotation: null, - optional: false, - decorators: [], - /* END_IF_TS */ - }; - value - " -)] -pub struct AssignmentTargetPropertyIdentifierValue<'a>( - pub &'a AssignmentTargetPropertyIdentifier<'a>, -); - -impl ESTree for AssignmentTargetPropertyIdentifierValue<'_> { - fn serialize(&self, serializer: S) { - if let Some(init) = &self.0.init { - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("AssignmentPattern")); - state.serialize_field("start", &self.0.span.start); - state.serialize_field("end", &self.0.span.end); - state.serialize_field("left", &self.0.binding); - state.serialize_field("right", init); - state.serialize_ts_field("typeAnnotation", &Null(())); - state.serialize_ts_field("optional", &False(())); - state.serialize_ts_field("decorators", &EmptyArray(())); - state.end(); - } else { - self.0.binding.serialize(serializer); - } - } -} - -// Serializers for `with_clause` field of `ImportDeclaration`, `ExportNamedDeclaration`, -// and `ExportAllDeclaration` (which are renamed to `attributes` in ESTree AST). -// -// Serialize only the `with_entries` field of `WithClause`, and serialize `None` as empty array (`[]`). -// -// https://github.com/estree/estree/blob/master/es2025.md#importdeclaration -// https://github.com/estree/estree/blob/master/es2025.md#exportnameddeclaration - -#[ast_meta] -#[estree( - ts_type = "Array", - raw_deser = " - const withClause = DESER[Option>](POS_OFFSET.with_clause); - withClause === null ? [] : withClause.withEntries - " -)] -pub struct ImportDeclarationWithClause<'a, 'b>(pub &'b ImportDeclaration<'a>); - -impl ESTree for ImportDeclarationWithClause<'_, '_> { - fn serialize(&self, serializer: S) { - if let Some(with_clause) = &self.0.with_clause { - with_clause.with_entries.serialize(serializer); - } else { - EmptyArray(()).serialize(serializer); - } - } -} - -#[ast_meta] -#[estree( - ts_type = "Array", - raw_deser = " - const withClause = DESER[Option>](POS_OFFSET.with_clause); - withClause === null ? [] : withClause.withEntries - " -)] -pub struct ExportNamedDeclarationWithClause<'a, 'b>(pub &'b ExportNamedDeclaration<'a>); - -impl ESTree for ExportNamedDeclarationWithClause<'_, '_> { - fn serialize(&self, serializer: S) { - if let Some(with_clause) = &self.0.with_clause { - with_clause.with_entries.serialize(serializer); - } else { - EmptyArray(()).serialize(serializer); - } - } -} - -#[ast_meta] -#[estree( - ts_type = "Array", - raw_deser = " - const withClause = DESER[Option>](POS_OFFSET.with_clause); - withClause === null ? [] : withClause.withEntries - " -)] -pub struct ExportAllDeclarationWithClause<'a, 'b>(pub &'b ExportAllDeclaration<'a>); - -impl ESTree for ExportAllDeclarationWithClause<'_, '_> { - fn serialize(&self, serializer: S) { - if let Some(with_clause) = &self.0.with_clause { - with_clause.with_entries.serialize(serializer); - } else { - EmptyArray(()).serialize(serializer); - } - } -} - -// -------------------- -// JSX -// -------------------- - -/// Serializer for `opening_element` field of `JSXElement`. -/// -/// `selfClosing` field of `JSXOpeningElement` depends on whether `JSXElement` has a `closing_element`. -#[ast_meta] -#[estree( - ts_type = "JSXOpeningElement", - raw_deser = " - const openingElement = DESER[Box](POS_OFFSET.opening_element); - if (THIS.closingElement === null) openingElement.selfClosing = true; - openingElement - " -)] -pub struct JSXElementOpening<'a, 'b>(pub &'b JSXElement<'a>); - -impl ESTree for JSXElementOpening<'_, '_> { - fn serialize(&self, serializer: S) { - let element = self.0; - let opening_element = element.opening_element.as_ref(); - - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("JSXOpeningElement")); - state.serialize_field("start", &opening_element.span.start); - state.serialize_field("end", &opening_element.span.end); - state.serialize_field("attributes", &opening_element.attributes); - state.serialize_field("name", &opening_element.name); - state.serialize_field("selfClosing", &element.closing_element.is_none()); - state.serialize_ts_field("typeArguments", &opening_element.type_arguments); - state.end(); - } -} - -/// Converter for `selfClosing` field of `JSXOpeningElement`. -/// -/// This converter is not used for serialization - `JSXElementOpening` above handles serialization. -/// This type is only required to add `selfClosing: boolean` to TS type def, -/// and provide default value of `false` for raw transfer deserializer. -#[ast_meta] -#[estree(ts_type = "boolean", raw_deser = "false")] -pub struct JSXOpeningElementSelfClosing<'a, 'b>(#[expect(dead_code)] pub &'b JSXOpeningElement<'a>); - -impl ESTree for JSXOpeningElementSelfClosing<'_, '_> { - fn serialize(&self, _serializer: S) { - unreachable!() - } -} - -/// Serializer for `IdentifierReference` variant of `JSXElementName` and `JSXMemberExpressionObject`. -/// -/// Convert to `JSXIdentifier`. -#[ast_meta] -#[estree( - ts_type = "JSXIdentifier", - raw_deser = " - const ident = DESER[Box](POS); - {type: 'JSXIdentifier', start: ident.start, end: ident.end, name: ident.name} - " -)] -pub struct JSXElementIdentifierReference<'a, 'b>(pub &'b IdentifierReference<'a>); - -impl ESTree for JSXElementIdentifierReference<'_, '_> { - fn serialize(&self, serializer: S) { - JSXIdentifier { span: self.0.span, name: self.0.name }.serialize(serializer); - } -} - -/// Serializer for `ThisExpression` variant of `JSXElementName` and `JSXMemberExpressionObject`. -/// -/// Convert to `JSXIdentifier`. -#[ast_meta] -#[estree( - ts_type = "JSXIdentifier", - raw_deser = " - const thisExpr = DESER[Box](POS); - {type: 'JSXIdentifier', start: thisExpr.start, end: thisExpr.end, name: 'this'} - " -)] -pub struct JSXElementThisExpression<'b>(pub &'b ThisExpression); - -impl ESTree for JSXElementThisExpression<'_> { - fn serialize(&self, serializer: S) { - JSXIdentifier { span: self.0.span, name: Atom::from("this") }.serialize(serializer); - } -} - -/// Converter for `JSXOpeningFragment`. -/// -/// Add `attributes` and `selfClosing` fields in JS AST, but not in TS AST. -/// Acorn-JSX has these fields, but TS-ESLint parser does not. -/// -/// The extra fields are added to the type as `TsEmptyArray` and `TsFalse`, -/// which are incorrect, as these fields appear only in the *JS* AST, not the TS one. -/// But that results in the fields being optional in TS type definition. -// -// TODO: Find a better way to do this. -#[ast_meta] -#[estree(raw_deser = " - const node = { - type: 'JSXOpeningFragment', - start: DESER[u32](POS_OFFSET.span.start), - end: DESER[u32](POS_OFFSET.span.end), - /* IF_JS */ - attributes: [], - selfClosing: false, - /* END_IF_JS */ - }; - node -")] -pub struct JSXOpeningFragmentConverter<'b>(pub &'b JSXOpeningFragment); - -impl ESTree for JSXOpeningFragmentConverter<'_> { - fn serialize(&self, serializer: S) { - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("JSXOpeningFragment")); - state.serialize_field("start", &self.0.span.start); - state.serialize_field("end", &self.0.span.end); - if !S::INCLUDE_TS_FIELDS { - state.serialize_field("attributes", &EmptyArray(())); - state.serialize_field("selfClosing", &False(())); - } - state.end(); - } -} - -// -------------------- -// TS -// -------------------- - -/// Serializer for `computed` field of `TSEnumMember`. -/// -/// `true` if `id` field is one of the computed variants of `TSEnumMemberName`. -// -// TODO: Not ideal to have to include the enum discriminant's value here explicitly. -// Need a "macro" e.g. `ENUM_MATCHES(id, ComputedString | ComputedTemplateString)`. -#[ast_meta] -#[estree(ts_type = "boolean", raw_deser = "DESER[u8](POS_OFFSET.id) > 1")] -pub struct TSEnumMemberComputed<'a, 'b>(pub &'b TSEnumMember<'a>); - -impl ESTree for TSEnumMemberComputed<'_, '_> { - fn serialize(&self, serializer: S) { - matches!( - self.0.id, - TSEnumMemberName::ComputedString(_) | TSEnumMemberName::ComputedTemplateString(_) - ) - .serialize(serializer); - } -} - -/// Serializer for `directive` field of `ExpressionStatement`. -/// This field is always `null`, and only appears in the TS AST, not JS ESTree. -#[ast_meta] -#[estree(ts_type = "string | null", raw_deser = "null")] -#[ts] -pub struct ExpressionStatementDirective<'a, 'b>( - #[expect(dead_code)] pub &'b ExpressionStatement<'a>, -); - -impl ESTree for ExpressionStatementDirective<'_, '_> { - fn serialize(&self, serializer: S) { - Null(()).serialize(serializer); - } -} - -/// Converter for `TSModuleDeclaration`. -/// -/// Our AST represents `module X.Y.Z {}` as 3 x nested `TSModuleDeclaration`s. -/// TS-ESTree represents it as a single `TSModuleDeclaration`, -/// with a nested tree of `TSQualifiedName`s as `id`. -#[ast_meta] -#[estree(raw_deser = " - const kind = DESER[TSModuleDeclarationKind](POS_OFFSET.kind), - global = kind === 'global', - start = DESER[u32](POS_OFFSET.span.start), - end = DESER[u32](POS_OFFSET.span.end), - declare = DESER[bool](POS_OFFSET.declare); - let id = DESER[TSModuleDeclarationName](POS_OFFSET.id), - body = DESER[Option](POS_OFFSET.body); - - // Flatten `body`, and nest `id` - if (body !== null && body.type === 'TSModuleDeclaration') { - id = { - type: 'TSQualifiedName', - start: body.id.start, - end: id.end, - left: body.id, - right: id, - }; - body = Object.hasOwn(body, 'body') ? body.body : null; - } - - // Skip `body` field if `null` - const node = body === null - ? { type: 'TSModuleDeclaration', start, end, id, kind, declare, global } - : { type: 'TSModuleDeclaration', start, end, id, body, kind, declare, global }; - node -")] -pub struct TSModuleDeclarationConverter<'a, 'b>(pub &'b TSModuleDeclaration<'a>); - -impl ESTree for TSModuleDeclarationConverter<'_, '_> { - fn serialize(&self, serializer: S) { - let module = self.0; - - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("TSModuleDeclaration")); - state.serialize_field("start", &module.span.start); - state.serialize_field("end", &module.span.end); - - match &module.body { - Some(TSModuleDeclarationBody::TSModuleDeclaration(inner_module)) => { - // Nested modules e.g. `module X.Y.Z {}`. - // Collect all IDs in a `Vec`, in order they appear (i.e. [`X`, `Y`, `Z`]). - // Also get the inner `TSModuleBlock`. - let mut parts = Vec::with_capacity(4); - - let TSModuleDeclarationName::Identifier(id) = &module.id else { unreachable!() }; - parts.push(id); - - let mut body = None; - let mut inner_module = inner_module.as_ref(); - loop { - let TSModuleDeclarationName::Identifier(id) = &inner_module.id else { - unreachable!() - }; - parts.push(id); - - match &inner_module.body { - Some(TSModuleDeclarationBody::TSModuleDeclaration(inner_inner_module)) => { - inner_module = inner_inner_module.as_ref(); - } - Some(TSModuleDeclarationBody::TSModuleBlock(block)) => { - body = Some(block.as_ref()); - break; - } - None => break, - } - } - - // Serialize `parts` as a nested tree of `TSQualifiedName`s - state.serialize_field("id", &TSModuleDeclarationIdParts(&parts)); - - // Skip `body` field if it's `None` - if let Some(body) = body { - state.serialize_field("body", body); - } - } - Some(TSModuleDeclarationBody::TSModuleBlock(block)) => { - // No nested modules. - // Serialize as usual, with `id` being either a `BindingIdentifier` or `StringLiteral`. - state.serialize_field("id", &module.id); - state.serialize_field("body", block); - } - None => { - // No body. Skip `body` field. - state.serialize_field("id", &module.id); - } - } - - state.serialize_field("kind", &module.kind); - state.serialize_field("declare", &module.declare); - state.serialize_field("global", &crate::serialize::TSModuleDeclarationGlobal(module)); - state.end(); - } -} - -struct TSModuleDeclarationIdParts<'a, 'b>(&'b [&'b BindingIdentifier<'a>]); - -impl ESTree for TSModuleDeclarationIdParts<'_, '_> { - fn serialize(&self, serializer: S) { - let parts = self.0; - assert!(!parts.is_empty()); - - let span_start = parts[0].span.start; - let (&last, rest) = parts.split_last().unwrap(); - - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("TSQualifiedName")); - state.serialize_field("start", &span_start); - state.serialize_field("end", &last.span.end); - - if rest.len() == 1 { - // Only one part remaining (e.g. `X`). Serialize as `Identifier`. - state.serialize_field("left", &rest[0]); - } else { - // Multiple parts remaining (e.g. `X.Y`). Recurse to serialize as `TSQualifiedName`. - state.serialize_field("left", &TSModuleDeclarationIdParts(rest)); - } - - state.serialize_field("right", last); - state.end(); - } -} - -/// Serializer for `global` field of `TSModuleDeclaration`. -#[ast_meta] -#[estree(ts_type = "boolean", raw_deser = "THIS.kind === 'global'")] -pub struct TSModuleDeclarationGlobal<'a, 'b>(pub &'b TSModuleDeclaration<'a>); - -impl ESTree for TSModuleDeclarationGlobal<'_, '_> { - fn serialize(&self, serializer: S) { - self.0.kind.is_global().serialize(serializer); - } -} - -/// Serializer for `optional` field of `TSMappedType`. -/// -/// `None` is serialized as `false`. -#[ast_meta] -#[estree( - ts_type = "TSMappedTypeModifierOperator | false", - raw_deser = " - let optional = DESER[Option](POS_OFFSET.optional) || false; - if (optional === null) optional = false; - optional - " -)] -pub struct TSMappedTypeOptional<'a, 'b>(pub &'b TSMappedType<'a>); - -impl ESTree for TSMappedTypeOptional<'_, '_> { - fn serialize(&self, serializer: S) { - if let Some(optional) = self.0.optional { - optional.serialize(serializer); - } else { - False(()).serialize(serializer); - } - } -} - -/// Serializer for `key` and `constraint` field of `TSMappedType`. -#[ast_meta] -#[estree( - ts_type = "TSTypeParameter['name']", - raw_deser = " - const typeParameter = DESER[Box](POS_OFFSET.type_parameter); - typeParameter.name - " -)] -pub struct TSMappedTypeKey<'a, 'b>(pub &'b TSMappedType<'a>); - -impl ESTree for TSMappedTypeKey<'_, '_> { - fn serialize(&self, serializer: S) { - self.0.type_parameter.name.serialize(serializer); - } -} - -// NOTE: Variable `typeParameter` in `raw_deser` is shared between `key` and `constraint` serializers. -// They will be concatenated in the generated code. -#[ast_meta] -#[estree(ts_type = "TSTypeParameter['constraint']", raw_deser = "typeParameter.constraint")] -pub struct TSMappedTypeConstraint<'a, 'b>(pub &'b TSMappedType<'a>); - -impl ESTree for TSMappedTypeConstraint<'_, '_> { - fn serialize(&self, serializer: S) { - self.0.type_parameter.constraint.serialize(serializer); - } -} - -/// Serializer for `IdentifierReference` variant of `TSTypeName`. -/// -/// Where is an identifier called `this`, TS-ESTree presents it as a `ThisExpression`. -#[ast_meta] -#[estree( - ts_type = "IdentifierReference | ThisExpression", - raw_deser = " - let id = DESER[Box](POS); - if (id.name === 'this') id = { type: 'ThisExpression', start: id.start, end: id.end }; - id - " -)] -pub struct TSTypeNameIdentifierReference<'a, 'b>(pub &'b IdentifierReference<'a>); - -impl ESTree for TSTypeNameIdentifierReference<'_, '_> { - fn serialize(&self, serializer: S) { - let ident = self.0; - if ident.name == "this" { - ThisExpression { span: ident.span }.serialize(serializer); - } else { - ident.serialize(serializer); - } - } -} - -/// Serializer for `expression` field of `TSClassImplements`. -/// -/// Our AST represents `X.Y` in `class C implements X.Y {}` as a `TSQualifiedName`. -/// TS-ESTree represents `X.Y` as a `MemberExpression`. -/// -/// Where there are more parts e.g. `class C implements X.Y.Z {}`, the `TSQualifiedName`s (Oxc) -/// or `MemberExpression`s (TS-ESTree) are nested. -#[ast_meta] -#[estree( - ts_type = "IdentifierReference | ThisExpression | MemberExpression", - raw_deser = " - let expression = DESER[TSTypeName](POS_OFFSET.expression); - if (expression.type === 'TSQualifiedName') { - let parent = expression = { - type: 'MemberExpression', - start: expression.start, - end: expression.end, - object: expression.left, - property: expression.right, - computed: false, - optional: false, - }; - - while (parent.object.type === 'TSQualifiedName') { - const object = parent.object; - parent = parent.object = { - type: 'MemberExpression', - start: object.start, - end: object.end, - object: object.left, - property: object.right, - computed: false, - optional: false, - }; - } - } - expression - " -)] -pub struct TSClassImplementsExpression<'a, 'b>(pub &'b TSClassImplements<'a>); - -impl ESTree for TSClassImplementsExpression<'_, '_> { - #[inline] // Because it just delegates - fn serialize(&self, serializer: S) { - TSTypeNameAsMemberExpression(&self.0.expression).serialize(serializer); - } -} - -struct TSTypeNameAsMemberExpression<'a, 'b>(&'b TSTypeName<'a>); - -impl ESTree for TSTypeNameAsMemberExpression<'_, '_> { - fn serialize(&self, serializer: S) { - match self.0 { - TSTypeName::IdentifierReference(ident) => { - TSTypeNameIdentifierReference(ident).serialize(serializer); - } - TSTypeName::QualifiedName(name) => { - // Convert to `TSQualifiedName` to `MemberExpression`. - // Recursively convert `left` to `MemberExpression` too if it's a `TSQualifiedName`. - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("MemberExpression")); - state.serialize_field("start", &name.span.start); - state.serialize_field("end", &name.span.end); - state.serialize_field("object", &TSTypeNameAsMemberExpression(&name.left)); - state.serialize_field("property", &name.right); - state.serialize_field("computed", &false); - state.serialize_field("optional", &false); - state.end(); - } - } - } -} - -/// Serializer for `params` field of `TSCallSignatureDeclaration`. -/// -/// These add `this_param` to start of the `params` array. -#[ast_meta] -#[estree( - ts_type = "ParamPattern[]", - raw_deser = " - const params = DESER[Box](POS_OFFSET.params); - const thisParam = DESER[Option>](POS_OFFSET.this_param); - if (thisParam !== null) params.unshift(thisParam); - params - " -)] -pub struct TSCallSignatureDeclarationFormalParameters<'a, 'b>( - pub &'b TSCallSignatureDeclaration<'a>, -); - -impl ESTree for TSCallSignatureDeclarationFormalParameters<'_, '_> { - fn serialize(&self, serializer: S) { - let v = self.0; - serialize_formal_params_with_this_param(v.this_param.as_deref(), &v.params, serializer); - } -} - -/// Serializer for `params` field of `TSMethodSignature`. -/// -/// These add `this_param` to start of the `params` array. -#[ast_meta] -#[estree( - ts_type = "ParamPattern[]", - raw_deser = " - const params = DESER[Box](POS_OFFSET.params); - const thisParam = DESER[Option>](POS_OFFSET.this_param); - if (thisParam !== null) params.unshift(thisParam); - params - " -)] -pub struct TSMethodSignatureFormalParameters<'a, 'b>(pub &'b TSMethodSignature<'a>); - -impl ESTree for TSMethodSignatureFormalParameters<'_, '_> { - fn serialize(&self, serializer: S) { - let v = self.0; - serialize_formal_params_with_this_param(v.this_param.as_deref(), &v.params, serializer); - } -} - -/// Serializer for `params` field of `TSFunctionType`. -/// -/// These add `this_param` to start of the `params` array. -#[ast_meta] -#[estree( - ts_type = "ParamPattern[]", - raw_deser = " - const params = DESER[Box](POS_OFFSET.params); - const thisParam = DESER[Option>](POS_OFFSET.this_param); - if (thisParam !== null) params.unshift(thisParam); - params - " -)] -pub struct TSFunctionTypeFormalParameters<'a, 'b>(pub &'b TSFunctionType<'a>); - -impl ESTree for TSFunctionTypeFormalParameters<'_, '_> { - fn serialize(&self, serializer: S) { - let v = self.0; - serialize_formal_params_with_this_param(v.this_param.as_deref(), &v.params, serializer); - } -} - -/// Shared serialization logic used by: -/// - `TSCallSignatureDeclarationFormalParameters` -/// - `TSMethodSignatureFormalParameters` -/// - `TSFunctionTypeFormalParameters` -fn serialize_formal_params_with_this_param<'a, S: Serializer>( - this_param: Option<&TSThisParameter<'a>>, - params: &FormalParameters<'a>, - serializer: S, -) { - let mut seq = serializer.serialize_sequence(); - - if let Some(this_param) = this_param { - seq.serialize_element(this_param); - } - - for item in ¶ms.items { - seq.serialize_element(item); - } - - if let Some(rest) = ¶ms.rest { - seq.serialize_element(&FormalParametersRest(rest)); - } - - seq.end(); -} - -// -------------------- -// Comments -// -------------------- - -/// Serialize `value` field of `Comment`. -/// -/// This serializer does not work for JSON serializer, because there's no access to source text -/// in `fn serialize`. But in any case, comments often contain characters which need escaping in JSON, -/// which is slow, so it's probably faster to transfer comments as NAPI types (which we do). -/// -/// This meta type is only present for raw transfer, which can transfer faster. -#[ast_meta] -#[estree( - ts_type = "string", - raw_deser = " - const endCut = THIS.type === 'Line' ? 0 : 2; - SOURCE_TEXT.slice(THIS.start + 2, THIS.end - endCut) - " -)] -pub struct CommentValue<'b>(#[expect(dead_code)] pub &'b Comment); - -impl ESTree for CommentValue<'_> { - #[expect(clippy::unimplemented)] - fn serialize(&self, _serializer: S) { - unimplemented!(); - } -} diff --git a/crates/oxc_ast/src/serialize/basic.rs b/crates/oxc_ast/src/serialize/basic.rs new file mode 100644 index 0000000000000..a4d71c3fb2490 --- /dev/null +++ b/crates/oxc_ast/src/serialize/basic.rs @@ -0,0 +1,127 @@ +use oxc_ast_macros::ast_meta; +use oxc_estree::{ESTree, JsonSafeString, Serializer}; + +/// Serialized as `null`. +#[ast_meta] +#[estree(ts_type = "null", raw_deser = "null")] +pub struct Null(pub T); + +impl ESTree for Null { + fn serialize(&self, serializer: S) { + ().serialize(serializer); + } +} + +/// Serialized as `null`. Field only present in TS-ESTree AST. +#[ast_meta] +#[estree(ts_type = "null", raw_deser = "null")] +#[ts] +pub struct TsNull(pub T); + +impl ESTree for TsNull { + fn serialize(&self, serializer: S) { + Null(()).serialize(serializer); + } +} + +/// Serialized as `true`. +#[ast_meta] +#[estree(ts_type = "true", raw_deser = "true")] +pub struct True(pub T); + +impl ESTree for True { + fn serialize(&self, serializer: S) { + true.serialize(serializer); + } +} + +/// Serialized as `false`. +#[ast_meta] +#[estree(ts_type = "false", raw_deser = "false")] +pub struct False(pub T); + +impl ESTree for False { + fn serialize(&self, serializer: S) { + false.serialize(serializer); + } +} + +/// Serialized as `false`. Field only present in TS-ESTree AST. +#[ast_meta] +#[estree(ts_type = "false", raw_deser = "false")] +#[ts] +pub struct TsFalse(pub T); + +impl ESTree for TsFalse { + fn serialize(&self, serializer: S) { + false.serialize(serializer); + } +} + +/// Serialized as `"value"`. +#[ast_meta] +#[estree(ts_type = "'value'", raw_deser = "'value'")] +#[ts] +pub struct TsValue(pub T); + +impl ESTree for TsValue { + fn serialize(&self, serializer: S) { + JsonSafeString("value").serialize(serializer); + } +} + +/// Serialized as `"in"`. +#[ast_meta] +#[estree(ts_type = "'in'", raw_deser = "'in'")] +pub struct In(pub T); + +impl ESTree for In { + fn serialize(&self, serializer: S) { + JsonSafeString("in").serialize(serializer); + } +} + +/// Serialized as `"init"`. +#[ast_meta] +#[estree(ts_type = "'init'", raw_deser = "'init'")] +pub struct Init(pub T); + +impl ESTree for Init { + fn serialize(&self, serializer: S) { + JsonSafeString("init").serialize(serializer); + } +} + +/// Serialized as `"this"`. +#[ast_meta] +#[estree(ts_type = "'this'", raw_deser = "'this'")] +pub struct This(pub T); + +impl ESTree for This { + fn serialize(&self, serializer: S) { + JsonSafeString("this").serialize(serializer); + } +} + +/// Serialized as `[]`. +#[ast_meta] +#[estree(ts_type = "[]", raw_deser = "[]")] +pub struct EmptyArray(pub T); + +impl ESTree for EmptyArray { + fn serialize(&self, serializer: S) { + [(); 0].serialize(serializer); + } +} + +/// Serialized as `[]`. Field only present in TS-ESTree AST. +#[ast_meta] +#[estree(ts_type = "[]", raw_deser = "[]")] +#[ts] +pub struct TsEmptyArray(pub T); + +impl ESTree for TsEmptyArray { + fn serialize(&self, serializer: S) { + EmptyArray(()).serialize(serializer); + } +} diff --git a/crates/oxc_ast/src/serialize/js.rs b/crates/oxc_ast/src/serialize/js.rs new file mode 100644 index 0000000000000..d66b53a2136f7 --- /dev/null +++ b/crates/oxc_ast/src/serialize/js.rs @@ -0,0 +1,399 @@ +use oxc_ast_macros::ast_meta; +use oxc_estree::{ + Concat2, ConcatElement, ESTree, FlatStructSerializer, JsonSafeString, SequenceSerializer, + Serializer, StructSerializer, +}; +use oxc_span::GetSpan; + +use crate::ast::*; + +use super::{EmptyArray, Null}; + +// -------------------- +// Function params +// -------------------- + +/// Converter for `FormalParameters`. +/// +/// Combine `items` and `rest` fields. Convert `rest` field. +#[ast_meta] +#[estree( + ts_type = "ParamPattern[]", + raw_deser = " + const params = DESER[Vec](POS_OFFSET.items); + if (uint32[(POS_OFFSET.rest) >> 2] !== 0 && uint32[(POS_OFFSET.rest + 4) >> 2] !== 0) { + pos = uint32[(POS_OFFSET.rest) >> 2]; + params.push({ + type: 'RestElement', + start: DESER[u32]( POS_OFFSET.span.start ), + end: DESER[u32]( POS_OFFSET.span.end ), + argument: DESER[BindingPatternKind]( POS_OFFSET.argument.kind ), + /* IF_TS */ + decorators: [], + optional: DESER[bool]( POS_OFFSET.argument.optional ), + typeAnnotation: DESER[Option>]( + POS_OFFSET.argument.type_annotation + ), + value: null, + /* END_IF_TS */ + }); + } + params + " +)] +pub struct FormalParametersConverter<'a, 'b>(pub &'b FormalParameters<'a>); + +impl ESTree for FormalParametersConverter<'_, '_> { + fn serialize(&self, serializer: S) { + let mut seq = serializer.serialize_sequence(); + self.0.push_to_sequence(&mut seq); + seq.end(); + } +} + +impl ConcatElement for FormalParameters<'_> { + fn push_to_sequence(&self, seq: &mut S) { + self.items.push_to_sequence(seq); + if let Some(rest) = &self.rest { + seq.serialize_element(&FormalParametersRest(rest)); + } + } +} + +struct FormalParametersRest<'a, 'b>(&'b BindingRestElement<'a>); + +impl ESTree for FormalParametersRest<'_, '_> { + fn serialize(&self, serializer: S) { + let rest = self.0; + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("RestElement")); + state.serialize_field("start", &rest.span.start); + state.serialize_field("end", &rest.span.end); + state.serialize_field("argument", &rest.argument.kind); + state.serialize_ts_field("decorators", &EmptyArray(())); + state.serialize_ts_field("optional", &rest.argument.optional); + state.serialize_ts_field("typeAnnotation", &rest.argument.type_annotation); + state.serialize_ts_field("value", &Null(())); + state.end(); + } +} + +/// Converter for `FormalParameter`. +/// +/// In TS-ESTree AST, if `accessibility` is `Some`, or `readonly` or `override` is `true`, +/// is serialized as `TSParameterProperty` instead, which has a different object shape. +#[ast_meta] +#[estree( + ts_type = "FormalParameter | TSParameterProperty", + raw_deser = " + /* IF_JS */ + DESER[BindingPatternKind](POS_OFFSET.pattern.kind) + /* END_IF_JS */ + + /* IF_TS */ + const accessibility = DESER[Option](POS_OFFSET.accessibility), + readonly = DESER[bool](POS_OFFSET.readonly), + override = DESER[bool](POS_OFFSET.override); + let param; + if (accessibility === null && !readonly && !override) { + param = { + ...DESER[BindingPatternKind](POS_OFFSET.pattern.kind), + decorators: DESER[Vec](POS_OFFSET.decorators), + optional: DESER[bool](POS_OFFSET.pattern.optional), + typeAnnotation: DESER[Option>](POS_OFFSET.pattern.type_annotation), + }; + } else { + param = { + type: 'TSParameterProperty', + start: DESER[u32](POS_OFFSET.span.start), + end: DESER[u32](POS_OFFSET.span.end), + accessibility, + decorators: DESER[Vec](POS_OFFSET.decorators), + override, + parameter: DESER[BindingPattern](POS_OFFSET.pattern), + readonly, + static: false, + }; + } + param + /* END_IF_TS */ + " +)] +pub struct FormalParameterConverter<'a, 'b>(pub &'b FormalParameter<'a>); + +impl ESTree for FormalParameterConverter<'_, '_> { + fn serialize(&self, serializer: S) { + let param = self.0; + + if S::INCLUDE_TS_FIELDS { + if param.has_modifier() { + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("TSParameterProperty")); + state.serialize_field("start", ¶m.span.start); + state.serialize_field("end", ¶m.span.end); + state.serialize_field("accessibility", ¶m.accessibility); + state.serialize_field("decorators", ¶m.decorators); + state.serialize_field("override", ¶m.r#override); + state.serialize_field("parameter", ¶m.pattern); + state.serialize_field("readonly", ¶m.readonly); + state.serialize_field("static", &false); + state.end(); + } else { + let mut state = serializer.serialize_struct(); + param.pattern.kind.serialize(FlatStructSerializer(&mut state)); + state.serialize_field("decorators", ¶m.decorators); + state.serialize_field("optional", ¶m.pattern.optional); + state.serialize_field("typeAnnotation", ¶m.pattern.type_annotation); + state.end(); + } + } else { + param.pattern.kind.serialize(serializer); + } + } +} + +/// Serializer for `params` field of `Function`. +/// +/// In TS-ESTree, this adds `this_param` to start of the `params` array. +#[ast_meta] +#[estree( + ts_type = "ParamPattern[]", + raw_deser = " + const params = DESER[Box](POS_OFFSET.params); + /* IF_TS */ + const thisParam = DESER[Option>](POS_OFFSET.this_param); + if (thisParam !== null) params.unshift(thisParam); + /* END_IF_TS */ + params + " +)] +pub struct FunctionParams<'a, 'b>(pub &'b Function<'a>); + +impl ESTree for FunctionParams<'_, '_> { + fn serialize(&self, serializer: S) { + let func = self.0; + if S::INCLUDE_TS_FIELDS { + Concat2(&func.this_param, func.params.as_ref()).serialize(serializer); + } else { + func.params.serialize(serializer); + } + } +} + +// -------------------- +// Import / export +// -------------------- + +/// Serializer for `specifiers` field of `ImportDeclaration`. +/// +/// Serialize `specifiers` as an empty array if it's `None`. +#[ast_meta] +#[estree( + ts_type = "Array", + raw_deser = " + let specifiers = DESER[Option>](POS_OFFSET.specifiers); + if (specifiers === null) specifiers = []; + specifiers + " +)] +pub struct ImportDeclarationSpecifiers<'a, 'b>(pub &'b ImportDeclaration<'a>); + +impl ESTree for ImportDeclarationSpecifiers<'_, '_> { + fn serialize(&self, serializer: S) { + if let Some(specifiers) = &self.0.specifiers { + specifiers.serialize(serializer); + } else { + EmptyArray(()).serialize(serializer); + } + } +} + +// Serializers for `with_clause` field of `ImportDeclaration`, `ExportNamedDeclaration`, +// and `ExportAllDeclaration` (which are renamed to `attributes` in ESTree AST). +// +// Serialize only the `with_entries` field of `WithClause`, and serialize `None` as empty array (`[]`). +// +// https://github.com/estree/estree/blob/master/es2025.md#importdeclaration +// https://github.com/estree/estree/blob/master/es2025.md#exportnameddeclaration + +#[ast_meta] +#[estree( + ts_type = "Array", + raw_deser = " + const withClause = DESER[Option>](POS_OFFSET.with_clause); + withClause === null ? [] : withClause.attributes + " +)] +pub struct ImportDeclarationWithClause<'a, 'b>(pub &'b ImportDeclaration<'a>); + +impl ESTree for ImportDeclarationWithClause<'_, '_> { + fn serialize(&self, serializer: S) { + if let Some(with_clause) = &self.0.with_clause { + with_clause.with_entries.serialize(serializer); + } else { + EmptyArray(()).serialize(serializer); + } + } +} + +#[ast_meta] +#[estree( + ts_type = "Array", + raw_deser = " + const withClause = DESER[Option>](POS_OFFSET.with_clause); + withClause === null ? [] : withClause.attributes + " +)] +pub struct ExportNamedDeclarationWithClause<'a, 'b>(pub &'b ExportNamedDeclaration<'a>); + +impl ESTree for ExportNamedDeclarationWithClause<'_, '_> { + fn serialize(&self, serializer: S) { + if let Some(with_clause) = &self.0.with_clause { + with_clause.with_entries.serialize(serializer); + } else { + EmptyArray(()).serialize(serializer); + } + } +} + +#[ast_meta] +#[estree( + ts_type = "Array", + raw_deser = " + const withClause = DESER[Option>](POS_OFFSET.with_clause); + withClause === null ? [] : withClause.attributes + " +)] +pub struct ExportAllDeclarationWithClause<'a, 'b>(pub &'b ExportAllDeclaration<'a>); + +impl ESTree for ExportAllDeclarationWithClause<'_, '_> { + fn serialize(&self, serializer: S) { + if let Some(with_clause) = &self.0.with_clause { + with_clause.with_entries.serialize(serializer); + } else { + EmptyArray(()).serialize(serializer); + } + } +} + +// -------------------- +// Misc +// -------------------- + +/// Serializer for `key` field of `MethodDefinition`. +/// +/// In TS-ESTree `"constructor"` in `class C { "constructor"() {} }` +/// is represented as an `Identifier`. +/// In Acorn and Espree, it's a `Literal`. +/// +#[ast_meta] +#[estree( + ts_type = "PropertyKey", + raw_deser = " + /* IF_JS */ + DESER[PropertyKey](POS_OFFSET.key) + /* END_IF_JS */ + + /* IF_TS */ + let key = DESER[PropertyKey](POS_OFFSET.key); + if (THIS.kind === 'constructor') { + key = { + type: 'Identifier', + start: key.start, + end: key.end, + name: 'constructor', + decorators: [], + optional: false, + typeAnnotation: null, + }; + } + key + /* END_IF_TS */ + " +)] +pub struct MethodDefinitionKey<'a, 'b>(pub &'b MethodDefinition<'a>); + +impl ESTree for MethodDefinitionKey<'_, '_> { + fn serialize(&self, serializer: S) { + let method = self.0; + if S::INCLUDE_TS_FIELDS && method.kind == MethodDefinitionKind::Constructor { + // `key` can only be either an identifier `constructor`, or string `"constructor"` + let span = method.key.span(); + IdentifierName { span, name: Atom::from("constructor") }.serialize(serializer); + } else { + method.key.serialize(serializer); + } + } +} + +/// Serializer for `body` field of `ArrowFunctionExpression`. +/// +/// Serialize as either an expression (if `expression` property is set), +/// or a `BlockStatement` (if it's not). +#[ast_meta] +#[estree( + ts_type = "FunctionBody | Expression", + raw_deser = " + let body = DESER[Box](POS_OFFSET.body); + THIS.expression ? body.body[0].expression : body + " +)] +pub struct ArrowFunctionExpressionBody<'a>(pub &'a ArrowFunctionExpression<'a>); + +impl ESTree for ArrowFunctionExpressionBody<'_> { + fn serialize(&self, serializer: S) { + if let Some(expression) = self.0.get_expression() { + expression.serialize(serializer); + } else { + self.0.body.serialize(serializer); + } + } +} + +/// Serializer for `init` field of `AssignmentTargetPropertyIdentifier` +/// (which is renamed to `value` in ESTree AST). +#[ast_meta] +#[estree( + ts_type = "IdentifierReference | AssignmentTargetWithDefault", + raw_deser = " + const init = DESER[Option](POS_OFFSET.init), + keyCopy = { ...THIS.key }, + value = init === null + ? keyCopy + : { + type: 'AssignmentPattern', + start: THIS.start, + end: THIS.end, + left: keyCopy, + right: init, + /* IF_TS */ + decorators: [], + optional: false, + typeAnnotation: null, + /* END_IF_TS */ + }; + value + " +)] +pub struct AssignmentTargetPropertyIdentifierInit<'a>( + pub &'a AssignmentTargetPropertyIdentifier<'a>, +); + +impl ESTree for AssignmentTargetPropertyIdentifierInit<'_> { + fn serialize(&self, serializer: S) { + if let Some(init) = &self.0.init { + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("AssignmentPattern")); + state.serialize_field("start", &self.0.span.start); + state.serialize_field("end", &self.0.span.end); + state.serialize_field("left", &self.0.binding); + state.serialize_field("right", init); + state.serialize_ts_field("decorators", &EmptyArray(())); + state.serialize_ts_field("optional", &false); + state.serialize_ts_field("typeAnnotation", &Null(())); + state.end(); + } else { + self.0.binding.serialize(serializer); + } + } +} diff --git a/crates/oxc_ast/src/serialize/jsx.rs b/crates/oxc_ast/src/serialize/jsx.rs new file mode 100644 index 0000000000000..9e30c7a7619b3 --- /dev/null +++ b/crates/oxc_ast/src/serialize/jsx.rs @@ -0,0 +1,129 @@ +use oxc_ast_macros::ast_meta; +use oxc_estree::{ESTree, JsonSafeString, Serializer, StructSerializer}; + +use crate::ast::*; + +use super::EmptyArray; + +/// Serializer for `opening_element` field of `JSXElement`. +/// +/// `selfClosing` field of `JSXOpeningElement` depends on whether `JSXElement` has a `closing_element`. +#[ast_meta] +#[estree( + ts_type = "JSXOpeningElement", + raw_deser = " + const openingElement = DESER[Box](POS_OFFSET.opening_element); + if (THIS.closingElement === null) openingElement.selfClosing = true; + openingElement + " +)] +pub struct JSXElementOpeningElement<'a, 'b>(pub &'b JSXElement<'a>); + +impl ESTree for JSXElementOpeningElement<'_, '_> { + fn serialize(&self, serializer: S) { + let element = self.0; + let opening_element = element.opening_element.as_ref(); + + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("JSXOpeningElement")); + state.serialize_field("start", &opening_element.span.start); + state.serialize_field("end", &opening_element.span.end); + state.serialize_field("attributes", &opening_element.attributes); + state.serialize_field("name", &opening_element.name); + state.serialize_field("selfClosing", &element.closing_element.is_none()); + state.serialize_ts_field("typeArguments", &opening_element.type_arguments); + state.end(); + } +} + +/// Converter for `selfClosing` field of `JSXOpeningElement`. +/// +/// This converter is not used for serialization - `JSXElementOpening` above handles serialization. +/// This type is only required to add `selfClosing: boolean` to TS type def, +/// and provide default value of `false` for raw transfer deserializer. +#[ast_meta] +#[estree(ts_type = "boolean", raw_deser = "false")] +pub struct JSXOpeningElementSelfClosing<'a, 'b>(#[expect(dead_code)] pub &'b JSXOpeningElement<'a>); + +impl ESTree for JSXOpeningElementSelfClosing<'_, '_> { + fn serialize(&self, _serializer: S) { + unreachable!(); + } +} + +/// Serializer for `IdentifierReference` variant of `JSXElementName` and `JSXMemberExpressionObject`. +/// +/// Convert to `JSXIdentifier`. +#[ast_meta] +#[estree( + ts_type = "JSXIdentifier", + raw_deser = " + const ident = DESER[Box](POS); + { type: 'JSXIdentifier', start: ident.start, end: ident.end, name: ident.name } + " +)] +pub struct JSXElementIdentifierReference<'a, 'b>(pub &'b IdentifierReference<'a>); + +impl ESTree for JSXElementIdentifierReference<'_, '_> { + fn serialize(&self, serializer: S) { + JSXIdentifier { span: self.0.span, name: self.0.name }.serialize(serializer); + } +} + +/// Serializer for `ThisExpression` variant of `JSXElementName` and `JSXMemberExpressionObject`. +/// +/// Convert to `JSXIdentifier`. +#[ast_meta] +#[estree( + ts_type = "JSXIdentifier", + raw_deser = " + const thisExpr = DESER[Box](POS); + { type: 'JSXIdentifier', start: thisExpr.start, end: thisExpr.end, name: 'this' } + " +)] +pub struct JSXElementThisExpression<'b>(pub &'b ThisExpression); + +impl ESTree for JSXElementThisExpression<'_> { + fn serialize(&self, serializer: S) { + JSXIdentifier { span: self.0.span, name: Atom::from("this") }.serialize(serializer); + } +} + +/// Converter for `JSXOpeningFragment`. +/// +/// Add `attributes` and `selfClosing` fields in JS AST, but not in TS AST. +/// Acorn-JSX has these fields, but TS-ESLint parser does not. +/// +/// The extra fields are added to the type as `TsEmptyArray` and `TsFalse`, +/// which are incorrect, as these fields appear only in the *JS* AST, not the TS one. +/// But that results in the fields being optional in TS type definition. +// +// TODO: Find a better way to do this. +#[ast_meta] +#[estree(raw_deser = " + const node = { + type: 'JSXOpeningFragment', + start: DESER[u32](POS_OFFSET.span.start), + end: DESER[u32](POS_OFFSET.span.end), + /* IF_JS */ + attributes: [], + selfClosing: false, + /* END_IF_JS */ + }; + node +")] +pub struct JSXOpeningFragmentConverter<'b>(pub &'b JSXOpeningFragment); + +impl ESTree for JSXOpeningFragmentConverter<'_> { + fn serialize(&self, serializer: S) { + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("JSXOpeningFragment")); + state.serialize_field("start", &self.0.span.start); + state.serialize_field("end", &self.0.span.end); + if !S::INCLUDE_TS_FIELDS { + state.serialize_field("attributes", &EmptyArray(())); + state.serialize_field("selfClosing", &false); + } + state.end(); + } +} diff --git a/crates/oxc_ast/src/serialize/literal.rs b/crates/oxc_ast/src/serialize/literal.rs new file mode 100644 index 0000000000000..de31e4a16d262 --- /dev/null +++ b/crates/oxc_ast/src/serialize/literal.rs @@ -0,0 +1,257 @@ +use cow_utils::CowUtils; + +use oxc_ast_macros::ast_meta; +use oxc_estree::{ESTree, JsonSafeString, LoneSurrogatesString, Serializer, StructSerializer}; + +use crate::ast::*; + +use super::Null; + +/// Serializer for `raw` field of `BooleanLiteral`. +/// +/// If `BooleanLiteral` has a span, set `raw` field to `"true"` or `"false"`. +/// If no span, `null`. +#[ast_meta] +#[estree( + ts_type = "string | null", + raw_deser = "(THIS.start === 0 && THIS.end === 0) ? null : THIS.value + ''" +)] +pub struct BooleanLiteralRaw<'b>(pub &'b BooleanLiteral); + +impl ESTree for BooleanLiteralRaw<'_> { + fn serialize(&self, serializer: S) { + #[expect(clippy::collection_is_never_read)] // Clippy is wrong! + let raw = if self.0.span.is_unspanned() { + None + } else if self.0.value { + Some(JsonSafeString("true")) + } else { + Some(JsonSafeString("false")) + }; + raw.serialize(serializer); + } +} + +/// Serializer for `raw` field of `NullLiteral`. +/// +/// If `NullLiteral` has a span, set `raw` field to `"null"`. If no span, `null`. +#[ast_meta] +#[estree( + ts_type = "'null' | null", + raw_deser = "(THIS.start === 0 && THIS.end === 0) ? null : 'null'" +)] +pub struct NullLiteralRaw<'b>(pub &'b NullLiteral); + +impl ESTree for NullLiteralRaw<'_> { + fn serialize(&self, serializer: S) { + #[expect(clippy::collection_is_never_read)] // Clippy is wrong! + let raw = if self.0.span.is_unspanned() { None } else { Some(JsonSafeString("null")) }; + raw.serialize(serializer); + } +} + +/// Serializer for `value` field of `StringLiteral`. +/// +/// Handle when `lone_surrogates` flag is set, indicating the string contains lone surrogates. +#[ast_meta] +#[estree( + ts_type = "string", + raw_deser = r#" + let value = DESER[Atom](POS_OFFSET.value); + if (DESER[bool](POS_OFFSET.lone_surrogates)) { + value = value.replace(/\uFFFD(.{4})/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16))); + } + value + "# +)] +pub struct StringLiteralValue<'a, 'b>(pub &'b StringLiteral<'a>); + +impl ESTree for StringLiteralValue<'_, '_> { + fn serialize(&self, serializer: S) { + let lit = self.0; + #[expect(clippy::if_not_else)] + if !lit.lone_surrogates { + lit.value.serialize(serializer); + } else { + // String contains lone surrogates. Very uncommon, so cold path. + self.serialize_lone_surrogates(serializer); + } + } +} + +impl StringLiteralValue<'_, '_> { + #[cold] + #[inline(never)] + fn serialize_lone_surrogates(&self, serializer: S) { + LoneSurrogatesString(self.0.value.as_str()).serialize(serializer); + } +} + +/// Serializer for `bigint` field of `BigIntLiteral`. +/// +/// Serialize as `raw` value, with `n` postfix trimmed off, and underscores removed. +#[ast_meta] +#[estree(ts_type = "string", raw_deser = "THIS.raw.slice(0, -1).replace(/_/g, '')")] +pub struct BigIntLiteralBigint<'a, 'b>(pub &'b BigIntLiteral<'a>); + +impl ESTree for BigIntLiteralBigint<'_, '_> { + fn serialize(&self, serializer: S) { + let bigint = self.0.raw[..self.0.raw.len() - 1].cow_replace('_', ""); + JsonSafeString(bigint.as_ref()).serialize(serializer); + } +} + +/// Serializer for `value` field of `BigIntLiteral`. +/// +/// Serialized as `null` in JSON, but updated on JS side to contain a `BigInt`. +#[ast_meta] +#[estree(ts_type = "bigint", raw_deser = "BigInt(THIS.bigint)")] +pub struct BigIntLiteralValue<'a, 'b>(#[expect(dead_code)] pub &'b BigIntLiteral<'a>); + +impl ESTree for BigIntLiteralValue<'_, '_> { + fn serialize(&self, mut serializer: S) { + // Record that this node needs fixing on JS side + serializer.record_fix_path(); + Null(()).serialize(serializer); + } +} + +/// Serializer for `value` field of `RegExpLiteral`. +/// +/// Serialized as `null` in JSON, but updated on JS side to contain a `RegExp`, if the regexp is valid. +#[ast_meta] +#[estree( + ts_type = "RegExp | null", + raw_deser = " + let value = null; + try { + value = new RegExp(THIS.regex.pattern, THIS.regex.flags); + } catch (e) {} + value + " +)] +pub struct RegExpLiteralValue<'a, 'b>(#[expect(dead_code)] pub &'b RegExpLiteral<'a>); + +impl ESTree for RegExpLiteralValue<'_, '_> { + fn serialize(&self, mut serializer: S) { + // Record that this node needs fixing on JS side + serializer.record_fix_path(); + Null(()).serialize(serializer); + } +} + +/// Converter for `RegExpFlags`. +/// +/// Serialize as a string, with flags in alphabetical order. +#[ast_meta] +#[estree( + ts_type = "string", + raw_deser = " + const flagBits = DESER[u8](POS); + let flags = ''; + // Alphabetical order + if (flagBits & 64) flags += 'd'; + if (flagBits & 1) flags += 'g'; + if (flagBits & 2) flags += 'i'; + if (flagBits & 4) flags += 'm'; + if (flagBits & 8) flags += 's'; + if (flagBits & 16) flags += 'u'; + if (flagBits & 128) flags += 'v'; + if (flagBits & 32) flags += 'y'; + flags + " +)] +pub struct RegExpFlagsConverter<'b>(pub &'b RegExpFlags); + +impl ESTree for RegExpFlagsConverter<'_> { + fn serialize(&self, serializer: S) { + JsonSafeString(self.0.to_inline_string().as_str()).serialize(serializer); + } +} + +/// Converter for `TemplateElement`. +/// +/// Decode `cooked` if it contains lone surrogates. +/// +/// Also adjust span in TS AST. +/// TS-ESLint produces a different span from Acorn: +/// ```js +/// const template = `abc${x}def${x}ghi`; +/// // Acorn: ^^^ ^^^ ^^^ +/// // TS-ESLint: ^^^^^^ ^^^^^^ ^^^^^ +/// ``` +// TODO: Raise an issue on TS-ESLint and see if they'll change span to match Acorn. +#[ast_meta] +#[estree(raw_deser = r#" + const tail = DESER[bool](POS_OFFSET.tail), + start = DESER[u32](POS_OFFSET.span.start) /* IF_TS */ - 1 /* END_IF_TS */, + end = DESER[u32](POS_OFFSET.span.end) /* IF_TS */ + 2 - tail /* END_IF_TS */, + value = DESER[TemplateElementValue](POS_OFFSET.value); + if (value.cooked !== null && DESER[bool](POS_OFFSET.lone_surrogates)) { + value.cooked = value.cooked + .replace(/\uFFFD(.{4})/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16))); + } + { type: 'TemplateElement', start, end, value, tail } +"#)] +pub struct TemplateElementConverter<'a, 'b>(pub &'b TemplateElement<'a>); + +impl ESTree for TemplateElementConverter<'_, '_> { + fn serialize(&self, serializer: S) { + let element = self.0; + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("TemplateElement")); + + let mut span = element.span; + if S::INCLUDE_TS_FIELDS { + span.start -= 1; + span.end += if element.tail { 1 } else { 2 }; + } + state.serialize_field("start", &span.start); + state.serialize_field("end", &span.end); + + state.serialize_field("value", &TemplateElementValue(element)); + state.serialize_field("tail", &element.tail); + state.end(); + } +} + +/// Serializer for `value` field of `TemplateElement`. +/// +/// Handle when `lone_surrogates` flag is set, indicating the cooked string contains lone surrogates. +/// +/// Implementation for `raw_deser` is included in `TemplateElementConverter` above. +#[ast_meta] +#[estree( + ts_type = "TemplateElementValue", + raw_deser = "(() => { throw new Error('Should not appear in deserializer code'); })()" +)] +pub struct TemplateElementValue<'a, 'b>(pub &'b TemplateElement<'a>); + +impl ESTree for TemplateElementValue<'_, '_> { + fn serialize(&self, serializer: S) { + let element = self.0; + #[expect(clippy::if_not_else)] + if !element.lone_surrogates { + element.value.serialize(serializer); + } else { + // String contains lone surrogates. Very uncommon, so cold path. + self.serialize_lone_surrogates(serializer); + } + } +} + +impl TemplateElementValue<'_, '_> { + #[cold] + #[inline(never)] + fn serialize_lone_surrogates(&self, serializer: S) { + let value = &self.0.value; + + let mut state = serializer.serialize_struct(); + state.serialize_field("raw", &value.raw); + + let cooked = value.cooked.as_ref().map(|cooked| LoneSurrogatesString(cooked.as_str())); + state.serialize_field("cooked", &cooked); + + state.end(); + } +} diff --git a/crates/oxc_ast/src/serialize/mod.rs b/crates/oxc_ast/src/serialize/mod.rs new file mode 100644 index 0000000000000..5d8a8391f51c4 --- /dev/null +++ b/crates/oxc_ast/src/serialize/mod.rs @@ -0,0 +1,237 @@ +use std::cmp; + +use oxc_ast_macros::ast_meta; +use oxc_estree::{ + CompactFixesJSSerializer, CompactFixesTSSerializer, CompactJSSerializer, CompactTSSerializer, + Concat2, ESTree, JsonSafeString, PrettyFixesJSSerializer, PrettyFixesTSSerializer, + PrettyJSSerializer, PrettyTSSerializer, Serializer, StructSerializer, +}; +use oxc_span::GetSpan; + +use crate::ast::*; + +pub mod basic; +pub mod js; +pub mod jsx; +pub mod literal; +pub mod ts; +use basic::{EmptyArray, Null}; + +/// Main serialization methods for `Program`. +/// +/// Note: 8 separate methods for the different serialization options, rather than 1 method +/// with behavior controlled by flags +/// (e.g. `fn to_estree_json(&self, with_ts: bool, pretty: bool, fixes: bool)`) +/// to avoid bloating binary size. +/// +/// Most consumers (and Oxc crates) will use only 1 of these methods, so we don't want to needlessly +/// compile all 8 serializers when only 1 is used. +/// +/// Initial capacity for serializer's buffer is an estimate based on our benchmark fixtures +/// of ratio of source text size to JSON size. +/// +/// | File | Compact TS | Compact JS | Pretty TS | Pretty JS | +/// |----------------------------|------------|------------|-----------|-----------| +/// | antd.js | 10 | 9 | 76 | 72 | +/// | cal.com.tsx | 10 | 9 | 40 | 37 | +/// | checker.ts | 7 | 6 | 27 | 24 | +/// | pdf.mjs | 13 | 12 | 71 | 67 | +/// | RadixUIAdoptionSection.jsx | 10 | 9 | 45 | 44 | +/// |----------------------------|------------|------------|-----------|-----------| +/// | Maximum | 13 | 12 | 76 | 72 | +/// +/// It's better to over-estimate than under-estimate, as having to grow the buffer is expensive, +/// so have gone on the generous side. +const JSON_CAPACITY_RATIO_COMPACT: usize = 16; +const JSON_CAPACITY_RATIO_PRETTY: usize = 80; + +impl Program<'_> { + /// Serialize AST to ESTree JSON, including TypeScript fields. + pub fn to_estree_ts_json(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; + let mut serializer = CompactTSSerializer::with_capacity(capacity); + self.serialize(&mut serializer); + serializer.into_string() + } + + /// Serialize AST to ESTree JSON, without TypeScript fields. + pub fn to_estree_js_json(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; + let mut serializer = CompactJSSerializer::with_capacity(capacity); + self.serialize(&mut serializer); + serializer.into_string() + } + + /// Serialize AST to pretty-printed ESTree JSON, including TypeScript fields. + pub fn to_pretty_estree_ts_json(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; + let mut serializer = PrettyTSSerializer::with_capacity(capacity); + self.serialize(&mut serializer); + serializer.into_string() + } + + /// Serialize AST to pretty-printed ESTree JSON, without TypeScript fields. + pub fn to_pretty_estree_js_json(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; + let mut serializer = PrettyJSSerializer::with_capacity(capacity); + self.serialize(&mut serializer); + serializer.into_string() + } + + /// Serialize AST to ESTree JSON, including TypeScript fields, with list of fixes. + pub fn to_estree_ts_json_with_fixes(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; + let serializer = CompactFixesTSSerializer::with_capacity(capacity); + serializer.serialize_with_fixes(self) + } + + /// Serialize AST to ESTree JSON, without TypeScript fields, with list of fixes. + pub fn to_estree_js_json_with_fixes(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; + let serializer = CompactFixesJSSerializer::with_capacity(capacity); + serializer.serialize_with_fixes(self) + } + + /// Serialize AST to pretty-printed ESTree JSON, including TypeScript fields, with list of fixes. + pub fn to_pretty_estree_ts_json_with_fixes(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; + let serializer = PrettyFixesTSSerializer::with_capacity(capacity); + serializer.serialize_with_fixes(self) + } + + /// Serialize AST to pretty-printed ESTree JSON, without TypeScript fields, with list of fixes. + pub fn to_pretty_estree_js_json_with_fixes(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; + let serializer = PrettyFixesJSSerializer::with_capacity(capacity); + serializer.serialize_with_fixes(self) + } +} + +/// Serializer for `Program`. +/// +/// In TS AST, set start span to start of first directive or statement. +/// This is required because unlike Acorn, TS-ESLint excludes whitespace and comments +/// from the `Program` start span. +/// See for more info. +/// +/// Special case where first statement is an `ExportNamedDeclaration` or `ExportDefaultDeclaration` +/// exporting a class with decorators, where one of the decorators is before `export`. +/// In these cases, the span of the statement starts after the span of the decorators. +/// e.g. `@dec export class C {}` - `ExportNamedDeclaration` span start is 5, `Decorator` span start is 0. +/// `Program` span start is 0 (not 5). +#[ast_meta] +#[estree(raw_deser = " + const body = DESER[Vec](POS_OFFSET.directives); + body.push(...DESER[Vec](POS_OFFSET.body)); + + /* IF_JS */ + const start = DESER[u32](POS_OFFSET.span.start); + /* END_IF_JS */ + + const end = DESER[u32](POS_OFFSET.span.end); + + /* IF_TS */ + let start; + if (body.length > 0) { + const first = body[0]; + start = first.start; + if (first.type === 'ExportNamedDeclaration' || first.type === 'ExportDefaultDeclaration') { + const { declaration } = first; + if ( + declaration !== null && declaration.type === 'ClassDeclaration' + && declaration.decorators.length > 0 + ) { + const decoratorStart = declaration.decorators[0].start; + if (decoratorStart < start) start = decoratorStart; + } + } + } else { + start = end; + } + /* END_IF_TS */ + + const program = { + type: 'Program', + start, + end, + body, + sourceType: DESER[ModuleKind](POS_OFFSET.source_type.module_kind), + hashbang: DESER[Option](POS_OFFSET.hashbang), + }; + program +")] +pub struct ProgramConverter<'a, 'b>(pub &'b Program<'a>); + +impl ESTree for ProgramConverter<'_, '_> { + fn serialize(&self, serializer: S) { + let program = self.0; + let span_start = + if S::INCLUDE_TS_FIELDS { get_ts_start_span(program) } else { program.span.start }; + + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("Program")); + state.serialize_field("start", &span_start); + state.serialize_field("end", &program.span.end); + state.serialize_field("body", &Concat2(&program.directives, &program.body)); + state.serialize_field("sourceType", &program.source_type.module_kind()); + state.serialize_field("hashbang", &program.hashbang); + state.end(); + } +} + +fn get_ts_start_span(program: &Program<'_>) -> u32 { + if let Some(first_directive) = program.directives.first() { + return first_directive.span.start; + } + + let Some(first_stmt) = program.body.first() else { + // Program contains no statements or directives. Span start = span end. + return program.span.end; + }; + + match first_stmt { + Statement::ExportNamedDeclaration(decl) => { + let start = decl.span.start; + if let Some(Declaration::ClassDeclaration(class)) = &decl.declaration { + if let Some(decorator) = class.decorators.first() { + return cmp::min(start, decorator.span.start); + } + } + start + } + Statement::ExportDefaultDeclaration(decl) => { + let start = decl.span.start; + if let ExportDefaultDeclarationKind::ClassDeclaration(class) = &decl.declaration { + if let Some(decorator) = class.decorators.first() { + return cmp::min(start, decorator.span.start); + } + } + start + } + _ => first_stmt.span().start, + } +} + +/// Serialize `value` field of `Comment`. +/// +/// This serializer does not work for JSON serializer, because there's no access to source text +/// in `fn serialize`. But in any case, comments often contain characters which need escaping in JSON, +/// which is slow, so it's probably faster to transfer comments as NAPI types (which we do). +/// +/// This meta type is only present for raw transfer, which can transfer faster. +#[ast_meta] +#[estree( + ts_type = "string", + raw_deser = " + const endCut = THIS.type === 'Line' ? 0 : 2; + SOURCE_TEXT.slice(THIS.start + 2, THIS.end - endCut) + " +)] +pub struct CommentValue<'b>(#[expect(dead_code)] pub &'b Comment); + +impl ESTree for CommentValue<'_> { + #[expect(clippy::unimplemented)] + fn serialize(&self, _serializer: S) { + unimplemented!(); + } +} diff --git a/crates/oxc_ast/src/serialize/ts.rs b/crates/oxc_ast/src/serialize/ts.rs new file mode 100644 index 0000000000000..c0e9328436836 --- /dev/null +++ b/crates/oxc_ast/src/serialize/ts.rs @@ -0,0 +1,422 @@ +use oxc_ast_macros::ast_meta; +use oxc_estree::{Concat2, ESTree, JsonSafeString, Serializer, StructSerializer}; + +use crate::ast::*; + +use super::Null; + +/// Serializer for `computed` field of `TSEnumMember`. +/// +/// `true` if `id` field is one of the computed variants of `TSEnumMemberName`. +// +// TODO: Not ideal to have to include the enum discriminant's value here explicitly. +// Need a "macro" e.g. `ENUM_MATCHES(id, ComputedString | ComputedTemplateString)`. +#[ast_meta] +#[estree(ts_type = "boolean", raw_deser = "DESER[u8](POS_OFFSET.id) > 1")] +pub struct TSEnumMemberComputed<'a, 'b>(pub &'b TSEnumMember<'a>); + +impl ESTree for TSEnumMemberComputed<'_, '_> { + fn serialize(&self, serializer: S) { + matches!( + self.0.id, + TSEnumMemberName::ComputedString(_) | TSEnumMemberName::ComputedTemplateString(_) + ) + .serialize(serializer); + } +} + +/// Serializer for `directive` field of `ExpressionStatement`. +/// +/// This field is always `null`, and only appears in the TS-ESTree AST, not JS ESTree. +#[ast_meta] +#[estree(ts_type = "string | null", raw_deser = "null")] +#[ts] +pub struct ExpressionStatementDirective<'a, 'b>( + #[expect(dead_code)] pub &'b ExpressionStatement<'a>, +); + +impl ESTree for ExpressionStatementDirective<'_, '_> { + fn serialize(&self, serializer: S) { + Null(()).serialize(serializer); + } +} + +/// Converter for `TSModuleDeclaration`. +/// +/// Our AST represents `module X.Y.Z {}` as 3 x nested `TSModuleDeclaration`s. +/// TS-ESTree represents it as a single `TSModuleDeclaration`, +/// with a nested tree of `TSQualifiedName`s as `id`. +#[ast_meta] +#[estree(raw_deser = " + const kind = DESER[TSModuleDeclarationKind](POS_OFFSET.kind), + global = kind === 'global', + start = DESER[u32](POS_OFFSET.span.start), + end = DESER[u32](POS_OFFSET.span.end), + declare = DESER[bool](POS_OFFSET.declare); + let id = DESER[TSModuleDeclarationName](POS_OFFSET.id), + body = DESER[Option](POS_OFFSET.body); + + // Flatten `body`, and nest `id` + if (body !== null && body.type === 'TSModuleDeclaration') { + let innerId = body.id; + if (innerId.type === 'Identifier') { + id = { + type: 'TSQualifiedName', + start: id.start, + end: innerId.end, + left: id, + right: innerId, + }; + } else { + // Replace `left` of innermost `TSQualifiedName` with a nested `TSQualifiedName` with `id` of + // this module on left, and previous `left` of innermost `TSQualifiedName` on right + while (true) { + innerId.start = id.start; + if (innerId.left.type === 'Identifier') break; + innerId = innerId.left; + } + innerId.left = { + type: 'TSQualifiedName', + start: id.start, + end: innerId.left.end, + left: id, + right: innerId.left, + }; + id = body.id; + } + body = Object.hasOwn(body, 'body') ? body.body : null; + } + + // Skip `body` field if `null` + const node = body === null + ? { type: 'TSModuleDeclaration', start, end, id, kind, declare, global } + : { type: 'TSModuleDeclaration', start, end, id, body, kind, declare, global }; + node +")] +pub struct TSModuleDeclarationConverter<'a, 'b>(pub &'b TSModuleDeclaration<'a>); + +impl ESTree for TSModuleDeclarationConverter<'_, '_> { + fn serialize(&self, serializer: S) { + let module = self.0; + + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("TSModuleDeclaration")); + state.serialize_field("start", &module.span.start); + state.serialize_field("end", &module.span.end); + + match &module.body { + Some(TSModuleDeclarationBody::TSModuleDeclaration(inner_module)) => { + // Nested modules e.g. `module X.Y.Z {}`. + // Collect all IDs in a `Vec`, in order they appear (i.e. [`X`, `Y`, `Z`]). + // Also get the inner `TSModuleBlock`. + let mut parts = Vec::with_capacity(4); + + let TSModuleDeclarationName::Identifier(id) = &module.id else { unreachable!() }; + parts.push(id); + + let mut body = None; + let mut inner_module = inner_module.as_ref(); + loop { + let TSModuleDeclarationName::Identifier(id) = &inner_module.id else { + unreachable!() + }; + parts.push(id); + + match &inner_module.body { + Some(TSModuleDeclarationBody::TSModuleDeclaration(inner_inner_module)) => { + inner_module = inner_inner_module.as_ref(); + } + Some(TSModuleDeclarationBody::TSModuleBlock(block)) => { + body = Some(block.as_ref()); + break; + } + None => break, + } + } + + // Serialize `parts` as a nested tree of `TSQualifiedName`s + state.serialize_field("id", &TSModuleDeclarationIdParts(&parts)); + + // Skip `body` field if it's `None` + if let Some(body) = body { + state.serialize_field("body", body); + } + } + Some(TSModuleDeclarationBody::TSModuleBlock(block)) => { + // No nested modules. + // Serialize as usual, with `id` being either a `BindingIdentifier` or `StringLiteral`. + state.serialize_field("id", &module.id); + state.serialize_field("body", block); + } + None => { + // No body. Skip `body` field. + state.serialize_field("id", &module.id); + } + } + + state.serialize_field("kind", &module.kind); + state.serialize_field("declare", &module.declare); + state.serialize_field("global", &TSModuleDeclarationGlobal(module)); + state.end(); + } +} + +struct TSModuleDeclarationIdParts<'a, 'b>(&'b [&'b BindingIdentifier<'a>]); + +impl ESTree for TSModuleDeclarationIdParts<'_, '_> { + fn serialize(&self, serializer: S) { + let parts = self.0; + assert!(!parts.is_empty()); + + let span_start = parts[0].span.start; + let (&last, rest) = parts.split_last().unwrap(); + + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("TSQualifiedName")); + state.serialize_field("start", &span_start); + state.serialize_field("end", &last.span.end); + + if rest.len() == 1 { + // Only one part remaining (e.g. `X`). Serialize as `Identifier`. + state.serialize_field("left", &rest[0]); + } else { + // Multiple parts remaining (e.g. `X.Y`). Recurse to serialize as `TSQualifiedName`. + state.serialize_field("left", &TSModuleDeclarationIdParts(rest)); + } + + state.serialize_field("right", last); + state.end(); + } +} + +/// Serializer for `global` field of `TSModuleDeclaration`. +/// +/// `true` if `kind` is `TSModuleDeclarationKind::Global`. +#[ast_meta] +#[estree(ts_type = "boolean", raw_deser = "THIS.kind === 'global'")] +pub struct TSModuleDeclarationGlobal<'a, 'b>(pub &'b TSModuleDeclaration<'a>); + +impl ESTree for TSModuleDeclarationGlobal<'_, '_> { + fn serialize(&self, serializer: S) { + self.0.kind.is_global().serialize(serializer); + } +} + +/// Serializer for `optional` field of `TSMappedType`. +/// +/// `None` is serialized as `false`. +#[ast_meta] +#[estree( + ts_type = "TSMappedTypeModifierOperator | false", + raw_deser = " + let optional = DESER[Option](POS_OFFSET.optional); + if (optional === null) optional = false; + optional + " +)] +pub struct TSMappedTypeOptional<'a, 'b>(pub &'b TSMappedType<'a>); + +impl ESTree for TSMappedTypeOptional<'_, '_> { + fn serialize(&self, serializer: S) { + if let Some(optional) = self.0.optional { + optional.serialize(serializer); + } else { + false.serialize(serializer); + } + } +} + +/// Serializer for `key` field of `TSMappedType`. +#[ast_meta] +#[estree( + ts_type = "TSTypeParameter['name']", + raw_deser = " + const typeParameter = DESER[Box](POS_OFFSET.type_parameter); + typeParameter.name + " +)] +pub struct TSMappedTypeKey<'a, 'b>(pub &'b TSMappedType<'a>); + +impl ESTree for TSMappedTypeKey<'_, '_> { + fn serialize(&self, serializer: S) { + self.0.type_parameter.name.serialize(serializer); + } +} + +/// Serializer for `constraint` field of `TSMappedType`. +/// +/// NOTE: Variable `typeParameter` in `raw_deser` is shared between `key` and `constraint` serializers. +/// They will be concatenated in the generated code. +#[ast_meta] +#[estree(ts_type = "TSTypeParameter['constraint']", raw_deser = "typeParameter.constraint")] +pub struct TSMappedTypeConstraint<'a, 'b>(pub &'b TSMappedType<'a>); + +impl ESTree for TSMappedTypeConstraint<'_, '_> { + fn serialize(&self, serializer: S) { + self.0.type_parameter.constraint.serialize(serializer); + } +} + +/// Serializer for `IdentifierReference` variant of `TSTypeName`. +/// +/// Where is an identifier called `this`, TS-ESTree presents it as a `ThisExpression`. +#[ast_meta] +#[estree( + ts_type = "IdentifierReference | ThisExpression", + raw_deser = " + let id = DESER[Box](POS); + if (id.name === 'this') id = { type: 'ThisExpression', start: id.start, end: id.end }; + id + " +)] +pub struct TSTypeNameIdentifierReference<'a, 'b>(pub &'b IdentifierReference<'a>); + +impl ESTree for TSTypeNameIdentifierReference<'_, '_> { + fn serialize(&self, serializer: S) { + let ident = self.0; + if ident.name == "this" { + ThisExpression { span: ident.span }.serialize(serializer); + } else { + ident.serialize(serializer); + } + } +} + +/// Serializer for `expression` field of `TSClassImplements`. +/// +/// Our AST represents `X.Y` in `class C implements X.Y {}` as a `TSQualifiedName`. +/// TS-ESTree represents `X.Y` as a `MemberExpression`. +/// +/// Where there are more parts e.g. `class C implements X.Y.Z {}`, the `TSQualifiedName`s (Oxc) +/// or `MemberExpression`s (TS-ESTree) are nested. +#[ast_meta] +#[estree( + ts_type = "IdentifierReference | ThisExpression | MemberExpression", + raw_deser = " + let expression = DESER[TSTypeName](POS_OFFSET.expression); + if (expression.type === 'TSQualifiedName') { + let parent = expression = { + type: 'MemberExpression', + start: expression.start, + end: expression.end, + object: expression.left, + property: expression.right, + computed: false, + optional: false, + }; + + while (parent.object.type === 'TSQualifiedName') { + const object = parent.object; + parent = parent.object = { + type: 'MemberExpression', + start: object.start, + end: object.end, + object: object.left, + property: object.right, + computed: false, + optional: false, + }; + } + } + expression + " +)] +pub struct TSClassImplementsExpression<'a, 'b>(pub &'b TSClassImplements<'a>); + +impl ESTree for TSClassImplementsExpression<'_, '_> { + #[inline] // Because it just delegates + fn serialize(&self, serializer: S) { + TSTypeNameAsMemberExpression(&self.0.expression).serialize(serializer); + } +} + +struct TSTypeNameAsMemberExpression<'a, 'b>(&'b TSTypeName<'a>); + +impl ESTree for TSTypeNameAsMemberExpression<'_, '_> { + fn serialize(&self, serializer: S) { + match self.0 { + TSTypeName::IdentifierReference(ident) => { + TSTypeNameIdentifierReference(ident).serialize(serializer); + } + TSTypeName::QualifiedName(name) => { + // Convert to `TSQualifiedName` to `MemberExpression`. + // Recursively convert `left` to `MemberExpression` too if it's a `TSQualifiedName`. + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("MemberExpression")); + state.serialize_field("start", &name.span.start); + state.serialize_field("end", &name.span.end); + state.serialize_field("object", &TSTypeNameAsMemberExpression(&name.left)); + state.serialize_field("property", &name.right); + state.serialize_field("computed", &false); + state.serialize_field("optional", &false); + state.end(); + } + } + } +} + +/// Serializer for `params` field of `TSCallSignatureDeclaration`. +/// +/// Add `this_param` to start of the `params` array. +#[ast_meta] +#[estree( + ts_type = "ParamPattern[]", + raw_deser = " + const params = DESER[Box](POS_OFFSET.params); + const thisParam = DESER[Option>](POS_OFFSET.this_param); + if (thisParam !== null) params.unshift(thisParam); + params + " +)] +pub struct TSCallSignatureDeclarationParams<'a, 'b>(pub &'b TSCallSignatureDeclaration<'a>); + +impl ESTree for TSCallSignatureDeclarationParams<'_, '_> { + fn serialize(&self, serializer: S) { + let decl = self.0; + Concat2(&decl.this_param, decl.params.as_ref()).serialize(serializer); + } +} + +/// Serializer for `params` field of `TSMethodSignature`. +/// +/// Add `this_param` to start of the `params` array. +#[ast_meta] +#[estree( + ts_type = "ParamPattern[]", + raw_deser = " + const params = DESER[Box](POS_OFFSET.params); + const thisParam = DESER[Option>](POS_OFFSET.this_param); + if (thisParam !== null) params.unshift(thisParam); + params + " +)] +pub struct TSMethodSignatureParams<'a, 'b>(pub &'b TSMethodSignature<'a>); + +impl ESTree for TSMethodSignatureParams<'_, '_> { + fn serialize(&self, serializer: S) { + let sig = self.0; + Concat2(&sig.this_param, sig.params.as_ref()).serialize(serializer); + } +} + +/// Serializer for `params` field of `TSFunctionType`. +/// +/// Add `this_param` to start of the `params` array. +#[ast_meta] +#[estree( + ts_type = "ParamPattern[]", + raw_deser = " + const params = DESER[Box](POS_OFFSET.params); + const thisParam = DESER[Option>](POS_OFFSET.this_param); + if (thisParam !== null) params.unshift(thisParam); + params + " +)] +pub struct TSFunctionTypeParams<'a, 'b>(pub &'b TSFunctionType<'a>); + +impl ESTree for TSFunctionTypeParams<'_, '_> { + fn serialize(&self, serializer: S) { + let fn_type = self.0; + Concat2(&fn_type.this_param, fn_type.params.as_ref()).serialize(serializer); + } +} diff --git a/crates/oxc_ast_macros/CHANGELOG.md b/crates/oxc_ast_macros/CHANGELOG.md index 46d6e4a9b9c15..31f227fa22c9d 100644 --- a/crates/oxc_ast_macros/CHANGELOG.md +++ b/crates/oxc_ast_macros/CHANGELOG.md @@ -4,6 +4,19 @@ 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/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.70.0] - 2025-05-15 + +### Refactor + +- c1e6c9b ast_macros: Move logic out of generated code (#11049) (overlookmotel) +- 2a403cb ast_macros: Rename generated file (#11047) (overlookmotel) + +## [0.69.0] - 2025-05-09 + +### Styling + +- 62c3a4a ast_tools: Add full stop to end of generated comments (#10809) (overlookmotel) + ## [0.62.0] - 2025-04-01 ### Features diff --git a/crates/oxc_ast_macros/Cargo.toml b/crates/oxc_ast_macros/Cargo.toml index cb4ce4c98a22e..8be0a9ae142d2 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.68.1" +version = "0.70.0" authors.workspace = true categories.workspace = true edition.workspace = true @@ -21,6 +21,7 @@ proc-macro = true doctest = false [dependencies] +phf = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } syn = { workspace = true, features = ["full", "parsing", "printing", "proc-macro"] } diff --git a/crates/oxc_ast_macros/src/ast.rs b/crates/oxc_ast_macros/src/ast.rs index 7cc0a8857a4bc..86fe3341682fa 100644 --- a/crates/oxc_ast_macros/src/ast.rs +++ b/crates/oxc_ast_macros/src/ast.rs @@ -1,33 +1,108 @@ -use proc_macro2::TokenStream; +use std::mem; + +use proc_macro2::{TokenStream, TokenTree}; use quote::quote; -use syn::{Attribute, Fields, Ident, Item, ItemEnum, punctuated::Punctuated, token::Comma}; +use syn::{ + Attribute, Fields, FieldsNamed, Ident, Item, ItemEnum, ItemStruct, parse_quote, + punctuated::Punctuated, token::Comma, +}; -use crate::generated::get_trait_crate_and_generics; +use crate::generated::{derived_traits::get_trait_crate_and_generics, structs::STRUCTS}; -pub fn ast(input: &Item) -> TokenStream { - let (head, tail) = match input { - Item::Enum(enum_) => (enum_repr(enum_), assert_generated_derives(&enum_.attrs)), - Item::Struct(struct_) => (quote!(#[repr(C)]), assert_generated_derives(&struct_.attrs)), +/// `#[ast]` macro. +pub fn ast(item: &mut Item, args: TokenStream) -> TokenStream { + match item { + Item::Enum(item) => modify_enum(item), + Item::Struct(item) => modify_struct(item, args), _ => unreachable!(), + } +} + +/// Add `#[repr(...)]` and `#[derive(::oxc_ast_macros::Ast)]` to enum, +/// and static assertions for `#[generate_derive]`. +fn modify_enum(item: &ItemEnum) -> TokenStream { + // If enum has any non-unit variant, `#[repr(C, u8)]`, otherwise `#[repr(u8)]` + let repr = if item.variants.iter().any(|var| !matches!(var.fields, Fields::Unit)) { + quote!(#[repr(C, u8)]) + } else { + quote!(#[repr(u8)]) }; + let assertions = assert_generated_derives(&item.attrs); + quote! { + #repr #[derive(::oxc_ast_macros::Ast)] - #head - #input - #tail + #item + #assertions } } -/// If `enum_` has any non-unit variant, returns `#[repr(C, u8)]`, otherwise returns `#[repr(u8)]`. -fn enum_repr(enum_: &ItemEnum) -> TokenStream { - if enum_.variants.iter().any(|var| !matches!(var.fields, Fields::Unit)) { - quote!(#[repr(C, u8)]) - } else { - quote!(#[repr(u8)]) +/// Details of how `#[ast]` macro should modify a struct. +pub struct StructDetails { + pub field_order: Option<&'static [u8]>, +} + +/// Add `#[repr(C)]` and `#[derive(::oxc_ast_macros::Ast)]` to struct, +/// and static assertions for `#[generate_derive]`. +/// Re-order struct fields if instructed by `STRUCTS` data. +fn modify_struct(item: &mut ItemStruct, args: TokenStream) -> TokenStream { + let assertions = assert_generated_derives(&item.attrs); + + let item = reorder_struct_fields(item, args).unwrap_or_else(|| quote!(#item)); + + quote! { + #[repr(C)] + #[derive(::oxc_ast_macros::Ast)] + #item + #assertions } } +/// Re-order struct fields, depending on instructions in `STRUCTS` (which is codegen-ed). +fn reorder_struct_fields(item: &mut ItemStruct, args: TokenStream) -> Option { + // Skip foreign types + if let Some(TokenTree::Ident(ident)) = args.into_iter().next() { + if ident == "foreign" { + return None; + } + } + + // Get struct data. Exit if no fields need re-ordering. + let struct_name = item.ident.to_string(); + let field_order = STRUCTS[&struct_name].field_order?; + + // Re-order fields. + // `field_order` contains indexes of fields in the order they should be. + let fields = mem::replace(&mut item.fields, Fields::Unit); + let Fields::Named(FieldsNamed { brace_token, mut named }) = fields else { unreachable!() }; + + assert!( + named.len() == field_order.len(), + "Wrong number of fields for `{struct_name}` in `STRUCTS`" + ); + + // Create 2 sets of fields. + // 1st set are the fields in original order, each prefixed with `#[cfg(doc)]`. + // 2nd set are the fields in new order, each prefixed with `#[cfg(not(doc))]`. + // This is necessary so that fields are listed in original source order in docs. + let mut fields = named.clone().into_pairs().zip(field_order).collect::>(); + fields.sort_unstable_by_key(|(_, index)| **index); + + for field in &mut named { + field.attrs.insert(0, parse_quote!( #[cfg(doc)])); + } + + named.extend(fields.into_iter().map(|(mut pair, _)| { + pair.value_mut().attrs.insert(0, parse_quote!( #[cfg(not(doc))])); + pair + })); + + item.fields = Fields::Named(FieldsNamed { brace_token, named }); + + Some(quote!( #item )) +} + /// Generate assertions that traits used in `#[generate_derive]` are in scope. /// /// e.g. for `#[generate_derive(GetSpan)]`, it generates: @@ -44,17 +119,20 @@ fn enum_repr(enum_: &ItemEnum) -> TokenStream { /// If `GetSpan` is not in scope, or it is not the correct `oxc_span::GetSpan`, /// this will raise a compilation error. fn assert_generated_derives(attrs: &[Attribute]) -> TokenStream { - // NOTE: At this level we don't care if a trait is derived multiple times, It is the - // responsibility of the `ast_tools` to raise errors for those. + // We don't care here if a trait is derived multiple times. + // It is the responsibility of `oxc_ast_tools` to raise errors for those. let assertions = attrs .iter() .filter(|attr| attr.path().is_ident("generate_derive")) .flat_map(parse_attr) .map(|trait_ident| { let trait_name = trait_ident.to_string(); - let (trait_path, generics) = get_trait_crate_and_generics(&trait_name); + let Some((trait_path, generics)) = get_trait_crate_and_generics(&trait_name) else { + panic!("Invalid derive trait(generate_derive): {trait_name}"); + }; + + // These are wrapped in a scope to avoid the need for unique identifiers quote! {{ - // NOTE: these are wrapped in a scope to avoid the need for unique identifiers. trait AssertionTrait: #trait_path #generics {} impl AssertionTrait for T {} }} diff --git a/crates/oxc_ast_macros/src/generated/mod.rs b/crates/oxc_ast_macros/src/generated/derived_traits.rs similarity index 80% rename from crates/oxc_ast_macros/src/generated/mod.rs rename to crates/oxc_ast_macros/src/generated/derived_traits.rs index 9e62ea8785a5e..75ad105c3f8eb 100644 --- a/crates/oxc_ast_macros/src/generated/mod.rs +++ b/crates/oxc_ast_macros/src/generated/derived_traits.rs @@ -4,8 +4,8 @@ use proc_macro2::TokenStream; use quote::quote; -pub fn get_trait_crate_and_generics(trait_name: &str) -> (TokenStream, TokenStream) { - match trait_name { +pub fn get_trait_crate_and_generics(trait_name: &str) -> Option<(TokenStream, TokenStream)> { + let res = match trait_name { "CloneIn" => (quote!(::oxc_allocator::CloneIn), quote!(< 'static >)), "Dummy" => (quote!(::oxc_allocator::Dummy), quote!(< 'static >)), "TakeIn" => (quote!(::oxc_allocator::TakeIn), quote!(< 'static >)), @@ -14,6 +14,7 @@ pub fn get_trait_crate_and_generics(trait_name: &str) -> (TokenStream, TokenStre "GetSpanMut" => (quote!(::oxc_span::GetSpanMut), TokenStream::new()), "ContentEq" => (quote!(::oxc_span::ContentEq), TokenStream::new()), "ESTree" => (quote!(::oxc_estree::ESTree), TokenStream::new()), - _ => panic!("Invalid derive trait(generate_derive): {trait_name}"), - } + _ => return None, + }; + Some(res) } diff --git a/crates/oxc_ast_macros/src/generated/structs.rs b/crates/oxc_ast_macros/src/generated/structs.rs new file mode 100644 index 0000000000000..f785bc2ac9f64 --- /dev/null +++ b/crates/oxc_ast_macros/src/generated/structs.rs @@ -0,0 +1,302 @@ +// Auto-generated code, DO NOT EDIT DIRECTLY! +// To edit this generated file you have to edit `tasks/ast_tools/src/generators/assert_layouts.rs`. + +use crate::ast::StructDetails; + +/// Details of how `#[ast]` macro should modify structs. +#[expect(clippy::unreadable_literal)] +pub static STRUCTS: phf::Map<&'static str, StructDetails> = ::phf::Map { + key: 12913932095322966823, + disps: &[ + (0, 0), + (0, 15), + (0, 140), + (0, 50), + (0, 27), + (0, 26), + (1, 84), + (0, 50), + (0, 17), + (0, 184), + (1, 127), + (6, 133), + (0, 8), + (0, 29), + (2, 45), + (0, 1), + (0, 40), + (0, 0), + (1, 103), + (2, 67), + (3, 95), + (0, 68), + (1, 64), + (0, 139), + (0, 18), + (1, 6), + (0, 18), + (0, 148), + (0, 52), + (0, 22), + (13, 66), + (0, 22), + (1, 122), + (59, 42), + (0, 7), + (6, 26), + (1, 164), + (0, 1), + (0, 14), + (3, 52), + (24, 100), + (0, 130), + (0, 206), + (0, 5), + (0, 1), + (1, 18), + ], + entries: &[ + ("ImportNamespaceSpecifier", StructDetails { field_order: None }), + ("JSXFragment", StructDetails { field_order: None }), + ("JSXOpeningElement", StructDetails { field_order: None }), + ("TSTemplateLiteralType", StructDetails { field_order: None }), + ("ScopeId", StructDetails { field_order: None }), + ("Modifiers", StructDetails { field_order: None }), + ("PrivateIdentifier", StructDetails { field_order: None }), + ("BigIntLiteral", StructDetails { field_order: None }), + ("CatchParameter", StructDetails { field_order: None }), + ("TSAsExpression", StructDetails { field_order: None }), + ("Hashbang", StructDetails { field_order: None }), + ("TSTypeParameter", StructDetails { field_order: None }), + ("ForStatement", StructDetails { field_order: None }), + ("TSTypeAliasDeclaration", StructDetails { field_order: Some(&[0, 1, 2, 3, 5, 4]) }), + ("MetaProperty", StructDetails { field_order: None }), + ("NonMaxU32", StructDetails { field_order: None }), + ("RawTransferData", StructDetails { field_order: None }), + ("JSDocUnknownType", StructDetails { field_order: None }), + ("TSNeverKeyword", StructDetails { field_order: None }), + ("IdentifierName", StructDetails { field_order: None }), + ("StringLiteral", StructDetails { field_order: None }), + ("Error", StructDetails { field_order: Some(&[3, 0, 1, 2]) }), + ("ObjectPattern", StructDetails { field_order: None }), + ("TSNumberKeyword", StructDetails { field_order: None }), + ("EcmaScriptModule", StructDetails { field_order: Some(&[4, 0, 1, 2, 3]) }), + ("TSInstantiationExpression", StructDetails { field_order: None }), + ("CatchClause", StructDetails { field_order: None }), + ("BoundaryAssertion", StructDetails { field_order: None }), + ("TaggedTemplateExpression", StructDetails { field_order: None }), + ("AssignmentTargetPropertyIdentifier", StructDetails { field_order: None }), + ("ObjectProperty", StructDetails { field_order: Some(&[0, 3, 1, 2, 4, 5, 6]) }), + ("TemplateElementValue", StructDetails { field_order: None }), + ("BlockStatement", StructDetails { field_order: None }), + ("TSObjectKeyword", StructDetails { field_order: None }), + ("TSCallSignatureDeclaration", StructDetails { field_order: None }), + ("ConditionalExpression", StructDetails { field_order: None }), + ("TSTypeAssertion", StructDetails { field_order: None }), + ("TemplateLiteral", StructDetails { field_order: None }), + ("TSQualifiedName", StructDetails { field_order: None }), + ("ImportSpecifier", StructDetails { field_order: None }), + ("TryStatement", StructDetails { field_order: None }), + ("TSSymbolKeyword", StructDetails { field_order: None }), + ("UnaryExpression", StructDetails { field_order: Some(&[0, 2, 1]) }), + ("WithStatement", StructDetails { field_order: None }), + ( + "AccessorProperty", + StructDetails { field_order: Some(&[0, 5, 1, 2, 3, 4, 6, 7, 8, 9, 10]) }, + ), + ("ReturnStatement", StructDetails { field_order: None }), + ("AssignmentPattern", StructDetails { field_order: None }), + ("Program", StructDetails { field_order: Some(&[0, 7, 1, 2, 3, 4, 5, 6]) }), + ("TSInterfaceBody", StructDetails { field_order: None }), + ("JSXMemberExpression", StructDetails { field_order: None }), + ("JSXClosingElement", StructDetails { field_order: None }), + ("TSArrayType", StructDetails { field_order: None }), + ("TSIntrinsicKeyword", StructDetails { field_order: None }), + ("StaticImport", StructDetails { field_order: None }), + ("ExportDefaultDeclaration", StructDetails { field_order: None }), + ("ArrayPattern", StructDetails { field_order: None }), + ("SequenceExpression", StructDetails { field_order: None }), + ("CharacterClassRange", StructDetails { field_order: None }), + ("Disjunction", StructDetails { field_order: None }), + ("ThrowStatement", StructDetails { field_order: None }), + ("ForInStatement", StructDetails { field_order: None }), + ( + "ArrowFunctionExpression", + StructDetails { field_order: Some(&[0, 6, 7, 1, 2, 3, 4, 5, 8]) }, + ), + ("IdentifierReference", StructDetails { field_order: None }), + ("TSTypeOperator", StructDetails { field_order: Some(&[0, 2, 1]) }), + ("ErrorLabel", StructDetails { field_order: Some(&[1, 0]) }), + ("AwaitExpression", StructDetails { field_order: None }), + ("Quantifier", StructDetails { field_order: Some(&[0, 1, 2, 4, 3]) }), + ("CharacterClassEscape", StructDetails { field_order: None }), + ("ImportEntry", StructDetails { field_order: None }), + ("FormalParameter", StructDetails { field_order: None }), + ("TSNamespaceExportDeclaration", StructDetails { field_order: None }), + ("BreakStatement", StructDetails { field_order: None }), + ("TSParenthesizedType", StructDetails { field_order: None }), + ("TSLiteralType", StructDetails { field_order: None }), + ("TSVoidKeyword", StructDetails { field_order: None }), + ("StaticExport", StructDetails { field_order: None }), + ("V8IntrinsicExpression", StructDetails { field_order: None }), + ("TSModuleDeclaration", StructDetails { field_order: Some(&[0, 1, 2, 4, 5, 3]) }), + ("NamedReference", StructDetails { field_order: None }), + ("StaticBlock", StructDetails { field_order: None }), + ("TSInferType", StructDetails { field_order: None }), + ("TSMethodSignature", StructDetails { field_order: Some(&[0, 1, 7, 8, 9, 2, 3, 4, 5, 6]) }), + ("Elision", StructDetails { field_order: None }), + ("YieldExpression", StructDetails { field_order: Some(&[0, 2, 1]) }), + ("TSSatisfiesExpression", StructDetails { field_order: None }), + ("ThisExpression", StructDetails { field_order: None }), + ("UnicodePropertyEscape", StructDetails { field_order: Some(&[0, 3, 4, 1, 2]) }), + ("Decorator", StructDetails { field_order: None }), + ("ExportNamedDeclaration", StructDetails { field_order: Some(&[0, 1, 2, 3, 5, 4]) }), + ("VariableDeclaration", StructDetails { field_order: Some(&[0, 2, 1, 3]) }), + ("TSTypeAnnotation", StructDetails { field_order: None }), + ("ImportDefaultSpecifier", StructDetails { field_order: None }), + ("TSNullKeyword", StructDetails { field_order: None }), + ("TSBooleanKeyword", StructDetails { field_order: None }), + ("Comment", StructDetails { field_order: None }), + ("TSAnyKeyword", StructDetails { field_order: None }), + ("DynamicImport", StructDetails { field_order: None }), + ("ObjectExpression", StructDetails { field_order: None }), + ("DoWhileStatement", StructDetails { field_order: None }), + ("SymbolId", StructDetails { field_order: None }), + ("TSTypeReference", StructDetails { field_order: None }), + ("TSIndexedAccessType", StructDetails { field_order: None }), + ("ExportSpecifier", StructDetails { field_order: None }), + ("TSImportEqualsDeclaration", StructDetails { field_order: None }), + ("JSXText", StructDetails { field_order: None }), + ("TSIndexSignature", StructDetails { field_order: None }), + ("RegExpFlags", StructDetails { field_order: None }), + ("EmptyStatement", StructDetails { field_order: None }), + ("TSInterfaceDeclaration", StructDetails { field_order: Some(&[0, 1, 2, 3, 4, 6, 5]) }), + ("FunctionBody", StructDetails { field_order: None }), + ("UpdateExpression", StructDetails { field_order: Some(&[0, 2, 3, 1]) }), + ("Modifier", StructDetails { field_order: None }), + ("Pattern", StructDetails { field_order: None }), + ("TSUndefinedKeyword", StructDetails { field_order: None }), + ("ExpressionStatement", StructDetails { field_order: None }), + ("DebuggerStatement", StructDetails { field_order: None }), + ( + "PropertyDefinition", + StructDetails { field_order: Some(&[0, 5, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13]) }, + ), + ("JSXElement", StructDetails { field_order: None }), + ("IgnoreGroup", StructDetails { field_order: None }), + ("TSClassImplements", StructDetails { field_order: None }), + ("TSNonNullExpression", StructDetails { field_order: None }), + ("AssignmentTargetWithDefault", StructDetails { field_order: None }), + ("TSTypePredicate", StructDetails { field_order: Some(&[0, 1, 3, 2]) }), + ("TSExportAssignment", StructDetails { field_order: None }), + ("Character", StructDetails { field_order: Some(&[0, 2, 1]) }), + ("TSTypeParameterInstantiation", StructDetails { field_order: None }), + ("NewExpression", StructDetails { field_order: None }), + ("JSXOpeningFragment", StructDetails { field_order: None }), + ("ContinueStatement", StructDetails { field_order: None }), + ("SpreadElement", StructDetails { field_order: None }), + ("JSXIdentifier", StructDetails { field_order: None }), + ("PrivateInExpression", StructDetails { field_order: None }), + ("LabeledStatement", StructDetails { field_order: None }), + ("TSInterfaceHeritage", StructDetails { field_order: None }), + ("PrivateFieldExpression", StructDetails { field_order: None }), + ("TSNamedTupleMember", StructDetails { field_order: None }), + ("TSConstructSignatureDeclaration", StructDetails { field_order: None }), + ("LookAroundAssertion", StructDetails { field_order: Some(&[0, 2, 1]) }), + ("BinaryExpression", StructDetails { field_order: Some(&[0, 1, 3, 2]) }), + ("VariableDeclarator", StructDetails { field_order: Some(&[0, 3, 1, 2, 4]) }), + ("CallExpression", StructDetails { field_order: None }), + ("JSXNamespacedName", StructDetails { field_order: None }), + ("ObjectAssignmentTarget", StructDetails { field_order: None }), + ("Span", StructDetails { field_order: None }), + ("BindingIdentifier", StructDetails { field_order: None }), + ("JSXExpressionContainer", StructDetails { field_order: None }), + ("ForOfStatement", StructDetails { field_order: Some(&[0, 5, 1, 2, 3, 4]) }), + ("TSTypeParameterDeclaration", StructDetails { field_order: None }), + ("AssignmentExpression", StructDetails { field_order: Some(&[0, 3, 1, 2]) }), + ("TSOptionalType", StructDetails { field_order: None }), + ("ClassString", StructDetails { field_order: Some(&[0, 2, 1]) }), + ("AssignmentTargetRest", StructDetails { field_order: None }), + ("ChainExpression", StructDetails { field_order: None }), + ("SwitchStatement", StructDetails { field_order: None }), + ("BindingRestElement", StructDetails { field_order: None }), + ("ArrayAssignmentTarget", StructDetails { field_order: None }), + ("SourceType", StructDetails { field_order: None }), + ( + "Function", + StructDetails { field_order: Some(&[0, 8, 1, 9, 10, 11, 2, 3, 4, 5, 6, 7, 12]) }, + ), + ("LabelIdentifier", StructDetails { field_order: None }), + ("FormalParameters", StructDetails { field_order: Some(&[0, 3, 1, 2]) }), + ( + "MethodDefinition", + StructDetails { field_order: Some(&[0, 4, 1, 2, 3, 5, 6, 7, 8, 9, 10]) }, + ), + ("RegExpLiteral", StructDetails { field_order: None }), + ("TSPropertySignature", StructDetails { field_order: Some(&[0, 3, 4, 5, 1, 2]) }), + ("JSXSpreadAttribute", StructDetails { field_order: None }), + ("ClassStringDisjunction", StructDetails { field_order: Some(&[0, 2, 1]) }), + ("BindingPattern", StructDetails { field_order: None }), + ("CapturingGroup", StructDetails { field_order: None }), + ("TSBigIntKeyword", StructDetails { field_order: None }), + ("ReferenceId", StructDetails { field_order: None }), + ("IndexedReference", StructDetails { field_order: None }), + ("RegExp", StructDetails { field_order: None }), + ("TSFunctionType", StructDetails { field_order: None }), + ("TSExternalModuleReference", StructDetails { field_order: None }), + ("JSDocNonNullableType", StructDetails { field_order: None }), + ("WhileStatement", StructDetails { field_order: None }), + ("NullLiteral", StructDetails { field_order: None }), + ("TSStringKeyword", StructDetails { field_order: None }), + ("Alternative", StructDetails { field_order: None }), + ("TSIntersectionType", StructDetails { field_order: None }), + ("NameSpan", StructDetails { field_order: Some(&[1, 0]) }), + ("NumericLiteral", StructDetails { field_order: None }), + ("ImportDeclaration", StructDetails { field_order: Some(&[0, 1, 2, 4, 3, 5]) }), + ("JSXAttribute", StructDetails { field_order: None }), + ("LogicalExpression", StructDetails { field_order: Some(&[0, 1, 3, 2]) }), + ("TSEnumMember", StructDetails { field_order: None }), + ("TSIndexSignatureName", StructDetails { field_order: None }), + ("WithClause", StructDetails { field_order: None }), + ("TSUnionType", StructDetails { field_order: None }), + ("RegExpPattern", StructDetails { field_order: None }), + ("JSXClosingFragment", StructDetails { field_order: None }), + ("Class", StructDetails { field_order: Some(&[0, 9, 1, 2, 3, 4, 5, 6, 7, 10, 11, 8]) }), + ("TSTupleType", StructDetails { field_order: None }), + ("Dot", StructDetails { field_order: None }), + ("JSXEmptyExpression", StructDetails { field_order: None }), + ("TSUnknownKeyword", StructDetails { field_order: None }), + ("SwitchCase", StructDetails { field_order: None }), + ("TSEnumBody", StructDetails { field_order: None }), + ("JSXSpreadChild", StructDetails { field_order: None }), + ("JSDocNullableType", StructDetails { field_order: None }), + ("StaticMemberExpression", StructDetails { field_order: None }), + ("ClassBody", StructDetails { field_order: None }), + ("ArrayExpression", StructDetails { field_order: None }), + ("ParenthesizedExpression", StructDetails { field_order: None }), + ("ExportEntry", StructDetails { field_order: Some(&[1, 0, 2, 3, 4, 5, 6]) }), + ("ImportExpression", StructDetails { field_order: None }), + ("TSModuleBlock", StructDetails { field_order: None }), + ("TSTypeLiteral", StructDetails { field_order: None }), + ("TSTypeQuery", StructDetails { field_order: None }), + ("AssignmentTargetPropertyProperty", StructDetails { field_order: None }), + ("CharacterClass", StructDetails { field_order: Some(&[0, 2, 3, 4, 1]) }), + ("BooleanLiteral", StructDetails { field_order: None }), + ("TSEnumDeclaration", StructDetails { field_order: Some(&[0, 1, 2, 4, 5, 3]) }), + ("TSConditionalType", StructDetails { field_order: None }), + ("TemplateElement", StructDetails { field_order: None }), + ("TSMappedType", StructDetails { field_order: Some(&[0, 1, 2, 3, 5, 6, 4]) }), + ("Directive", StructDetails { field_order: None }), + ("IfStatement", StructDetails { field_order: None }), + ("TSImportType", StructDetails { field_order: None }), + ("TSConstructorType", StructDetails { field_order: Some(&[0, 4, 1, 2, 3]) }), + ("ExportAllDeclaration", StructDetails { field_order: None }), + ("TSRestType", StructDetails { field_order: None }), + ("TSThisType", StructDetails { field_order: None }), + ("ImportAttribute", StructDetails { field_order: None }), + ("Super", StructDetails { field_order: None }), + ("BindingProperty", StructDetails { field_order: None }), + ("ComputedMemberExpression", StructDetails { field_order: None }), + ("TSThisParameter", StructDetails { field_order: None }), + ], +}; diff --git a/crates/oxc_ast_macros/src/lib.rs b/crates/oxc_ast_macros/src/lib.rs index 7eb63752eac37..6f85bac680add 100644 --- a/crates/oxc_ast_macros/src/lib.rs +++ b/crates/oxc_ast_macros/src/lib.rs @@ -1,9 +1,13 @@ use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{Item, parse_macro_input}; mod ast; -mod generated; +mod generated { + pub mod derived_traits; + pub mod structs; +} /// This attribute serves two purposes: /// @@ -36,9 +40,9 @@ mod generated; /// /// Add assertions that traits used in `#[generate_derive(...)]` are in scope. #[proc_macro_attribute] -pub fn ast(_args: TokenStream, input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as Item); - let expanded = ast::ast(&input); +pub fn ast(args: TokenStream, input: TokenStream) -> TokenStream { + let mut input = parse_macro_input!(input as Item); + let expanded = ast::ast(&mut input, TokenStream2::from(args)); TokenStream::from(expanded) } diff --git a/crates/oxc_ast_visit/CHANGELOG.md b/crates/oxc_ast_visit/CHANGELOG.md index a3d75db490873..8f95f69abf4fa 100644 --- a/crates/oxc_ast_visit/CHANGELOG.md +++ b/crates/oxc_ast_visit/CHANGELOG.md @@ -4,6 +4,46 @@ 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/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.70.0] - 2025-05-15 + +### Bug Fixes + +- 2bdb338 ast_visit: Fix visitation order for `FormalParameters` in `Utf8ToUtf16Converter` (#11019) (overlookmotel) +- 282420c ast_visit: Fix visitation order for `TSTemplateLiteralType` in `Utf8ToUtf16Converter` (#11007) (overlookmotel) +- d0d04e3 ast_visit: Fix visitation order for `BindingPattern` in `Utf8ToUtf16Converter` (#11003) (overlookmotel) + +### Refactor + +- 287b3c3 ast_visit: Descriptive method param names in `Utf8ToUtf16Converter` (#10987) (overlookmotel) + +### Testing + +- a05361e ast/estree: Check span offsets are converted in ascending order in ESTree conformance tests (#10887) (overlookmotel) + +## [0.69.0] - 2025-05-09 + +- 2b5d826 ast: [**BREAKING**] Fix field order for `TSTypeAssertion` (#10906) (overlookmotel) + +- 1f35910 ast: [**BREAKING**] Fix field order for `TSNamedTupleMember` (#10905) (overlookmotel) + +- 8a3bba8 ast: [**BREAKING**] Fix field order for `PropertyDefinition` (#10902) (overlookmotel) + +- 5746d36 ast: [**BREAKING**] Fix field order for `NewExpression` (#10893) (overlookmotel) + +- 0139793 ast: [**BREAKING**] Re-order fields of `TaggedTemplateExpression` (#10889) (overlookmotel) + +- 6646b6b ast: [**BREAKING**] Fix field order for `JSXOpeningElement` (#10882) (overlookmotel) + +- cc2ed21 ast: [**BREAKING**] Fix field order for `JSXElement` and `JSXFragment` (#10881) (overlookmotel) + +### Bug Fixes + +- 2c09243 ast: Fix field order for `AccessorProperty` (#10878) (overlookmotel) + +### Styling + +- 62c3a4a ast_tools: Add full stop to end of generated comments (#10809) (overlookmotel) + ## [0.65.0] - 2025-04-21 - 99d82db ast: [**BREAKING**] Move `type_parameters` field to before `extends` in `TSInterfaceDeclaration` (#10476) (overlookmotel) diff --git a/crates/oxc_ast_visit/Cargo.toml b/crates/oxc_ast_visit/Cargo.toml index 8ef954db52e50..1b9e3731615e3 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.68.1" +version = "0.70.0" authors.workspace = true categories.workspace = true edition.workspace = true @@ -32,3 +32,5 @@ serialize = [ "oxc_span/serialize", "oxc_syntax/serialize", ] +# Only for conformance tests +conformance = [] diff --git a/crates/oxc_ast_visit/src/generated/utf8_to_utf16_converter.rs b/crates/oxc_ast_visit/src/generated/utf8_to_utf16_converter.rs index 1e47798b79702..48148efd7760e 100644 --- a/crates/oxc_ast_visit/src/generated/utf8_to_utf16_converter.rs +++ b/crates/oxc_ast_visit/src/generated/utf8_to_utf16_converter.rs @@ -398,24 +398,12 @@ impl<'a> VisitMut<'a> for Utf8ToUtf16Converter<'_> { self.convert_offset(&mut it.span.end); } - fn visit_binding_rest_element(&mut self, it: &mut BindingRestElement<'a>) { - self.convert_offset(&mut it.span.start); - walk_mut::walk_binding_rest_element(self, it); - self.convert_offset(&mut it.span.end); - } - fn visit_function(&mut self, it: &mut Function<'a>, flags: ScopeFlags) { self.convert_offset(&mut it.span.start); walk_mut::walk_function(self, it, flags); self.convert_offset(&mut it.span.end); } - fn visit_formal_parameters(&mut self, it: &mut FormalParameters<'a>) { - self.convert_offset(&mut it.span.start); - walk_mut::walk_formal_parameters(self, it); - self.convert_offset(&mut it.span.end); - } - fn visit_formal_parameter(&mut self, it: &mut FormalParameter<'a>) { self.convert_offset(&mut it.span.start); walk_mut::walk_formal_parameter(self, it); @@ -995,12 +983,6 @@ impl<'a> VisitMut<'a> for Utf8ToUtf16Converter<'_> { self.convert_offset(&mut it.span.end); } - fn visit_ts_template_literal_type(&mut self, it: &mut TSTemplateLiteralType<'a>) { - self.convert_offset(&mut it.span.start); - walk_mut::walk_ts_template_literal_type(self, it); - self.convert_offset(&mut it.span.end); - } - fn visit_ts_as_expression(&mut self, it: &mut TSAsExpression<'a>) { self.convert_offset(&mut it.span.start); walk_mut::walk_ts_as_expression(self, it); @@ -1079,9 +1061,13 @@ impl<'a> VisitMut<'a> for Utf8ToUtf16Converter<'_> { self.convert_offset(&mut it.span.end); } - fn visit_object_property(&mut self, it: &mut ObjectProperty<'a>) { - self.convert_offset(&mut it.span.start); - match (it.shorthand, &mut it.key, &mut it.value) { + fn visit_formal_parameters(&mut self, params: &mut FormalParameters<'a>) { + walk_mut::walk_formal_parameters(self, params); + } + + fn visit_object_property(&mut self, prop: &mut ObjectProperty<'a>) { + self.convert_offset(&mut prop.span.start); + match (prop.shorthand, &mut prop.key, &mut prop.value) { (true, PropertyKey::StaticIdentifier(key), Expression::Identifier(value)) => { self.visit_identifier_name(key); value.span = key.span; @@ -1091,12 +1077,50 @@ impl<'a> VisitMut<'a> for Utf8ToUtf16Converter<'_> { self.visit_expression(value); } } - self.convert_offset(&mut it.span.end); + self.convert_offset(&mut prop.span.end); } - fn visit_binding_property(&mut self, it: &mut BindingProperty<'a>) { - self.convert_offset(&mut it.span.start); - match (it.shorthand, &mut it.key, &mut it.value) { + fn visit_binding_pattern(&mut self, pattern: &mut BindingPattern<'a>) { + let span_end = match &mut pattern.kind { + BindingPatternKind::BindingIdentifier(ident) => { + self.convert_offset(&mut ident.span.start); + walk_mut::walk_binding_identifier(self, ident); + &mut ident.span.end + } + BindingPatternKind::ObjectPattern(obj_pattern) => { + self.convert_offset(&mut obj_pattern.span.start); + walk_mut::walk_object_pattern(self, obj_pattern); + &mut obj_pattern.span.end + } + BindingPatternKind::ArrayPattern(arr_pattern) => { + self.convert_offset(&mut arr_pattern.span.start); + walk_mut::walk_array_pattern(self, arr_pattern); + &mut arr_pattern.span.end + } + BindingPatternKind::AssignmentPattern(assign_pattern) => { + self.convert_offset(&mut assign_pattern.span.start); + walk_mut::walk_assignment_pattern(self, assign_pattern); + &mut assign_pattern.span.end + } + }; + if let Some(type_annotation) = &mut pattern.type_annotation { + self.visit_ts_type_annotation(type_annotation); + } + self.convert_offset(span_end); + } + + fn visit_binding_rest_element(&mut self, rest_element: &mut BindingRestElement<'a>) { + self.convert_offset(&mut rest_element.span.start); + self.visit_binding_pattern_kind(&mut rest_element.argument.kind); + if let Some(type_annotation) = &mut rest_element.argument.type_annotation { + self.visit_ts_type_annotation(type_annotation); + } + self.convert_offset(&mut rest_element.span.end); + } + + fn visit_binding_property(&mut self, prop: &mut BindingProperty<'a>) { + self.convert_offset(&mut prop.span.start); + match (prop.shorthand, &mut prop.key, &mut prop.value) { ( true, PropertyKey::StaticIdentifier(key), @@ -1118,7 +1142,7 @@ impl<'a> VisitMut<'a> for Utf8ToUtf16Converter<'_> { self.visit_binding_pattern(value); } } - self.convert_offset(&mut it.span.end); + self.convert_offset(&mut prop.span.end); } fn visit_export_named_declaration(&mut self, decl: &mut ExportNamedDeclaration<'a>) { @@ -1143,9 +1167,9 @@ impl<'a> VisitMut<'a> for Utf8ToUtf16Converter<'_> { } } - fn visit_export_specifier(&mut self, it: &mut ExportSpecifier<'a>) { - self.convert_offset(&mut it.span.start); - match (&mut it.local, &mut it.exported) { + fn visit_export_specifier(&mut self, specifier: &mut ExportSpecifier<'a>) { + self.convert_offset(&mut specifier.span.start); + match (&mut specifier.local, &mut specifier.exported) { ( ModuleExportName::IdentifierReference(local), ModuleExportName::IdentifierName(exported), @@ -1171,36 +1195,46 @@ impl<'a> VisitMut<'a> for Utf8ToUtf16Converter<'_> { self.visit_module_export_name(exported); } } - self.convert_offset(&mut it.span.end); + self.convert_offset(&mut specifier.span.end); } - fn visit_import_specifier(&mut self, it: &mut ImportSpecifier<'a>) { - self.convert_offset(&mut it.span.start); - match &mut it.imported { - ModuleExportName::IdentifierName(imported) if imported.span == it.local.span => { + fn visit_import_specifier(&mut self, specifier: &mut ImportSpecifier<'a>) { + self.convert_offset(&mut specifier.span.start); + match &mut specifier.imported { + ModuleExportName::IdentifierName(imported) if imported.span == specifier.local.span => { self.visit_identifier_name(imported); - it.local.span = imported.span; + specifier.local.span = imported.span; } imported => { self.visit_module_export_name(imported); - self.visit_binding_identifier(&mut it.local); + self.visit_binding_identifier(&mut specifier.local); } } - self.convert_offset(&mut it.span.end); + self.convert_offset(&mut specifier.span.end); } - fn visit_with_clause(&mut self, it: &mut WithClause<'a>) { - self.visit_import_attributes(&mut it.with_entries); + fn visit_with_clause(&mut self, with_clause: &mut WithClause<'a>) { + self.visit_import_attributes(&mut with_clause.with_entries); } - fn visit_template_literal(&mut self, it: &mut TemplateLiteral<'a>) { - self.convert_offset(&mut it.span.start); - for (quasi, expression) in it.quasis.iter_mut().zip(&mut it.expressions) { + fn visit_template_literal(&mut self, lit: &mut TemplateLiteral<'a>) { + self.convert_offset(&mut lit.span.start); + for (quasi, expression) in lit.quasis.iter_mut().zip(&mut lit.expressions) { self.visit_template_element(quasi); self.visit_expression(expression); } - self.visit_template_element(it.quasis.last_mut().unwrap()); - self.convert_offset(&mut it.span.end); + self.visit_template_element(lit.quasis.last_mut().unwrap()); + self.convert_offset(&mut lit.span.end); + } + + fn visit_ts_template_literal_type(&mut self, lit: &mut TSTemplateLiteralType<'a>) { + self.convert_offset(&mut lit.span.start); + for (quasi, ts_type) in lit.quasis.iter_mut().zip(&mut lit.types) { + self.visit_template_element(quasi); + self.visit_ts_type(ts_type); + } + self.visit_template_element(lit.quasis.last_mut().unwrap()); + self.convert_offset(&mut lit.span.end); } } diff --git a/crates/oxc_ast_visit/src/utf8_to_utf16.rs b/crates/oxc_ast_visit/src/utf8_to_utf16.rs index 86bc9ee00ad84..40f2ecd3f5f62 100644 --- a/crates/oxc_ast_visit/src/utf8_to_utf16.rs +++ b/crates/oxc_ast_visit/src/utf8_to_utf16.rs @@ -8,7 +8,7 @@ use oxc_syntax::module_record::{ModuleRecord, VisitMutModuleRecord}; use crate::VisitMut; -/// Convert UTF-8 span offsets to UTF-16. +/// Conversion table of UTF-8 span offsets to UTF-16. pub struct Utf8ToUtf16 { translations: Vec, } @@ -40,7 +40,13 @@ impl Utf8ToUtf16 { // Remove the dummy entry. // Therefore, `translations` always has at least 2 entries, if it has any. if translations.len() == 1 { - translations.clear(); + // In conformance tests, force offset conversion to happen on all inputs, + // even if they are pure ASCII + if cfg!(feature = "conformance") { + translations.push(Translation { utf8_offset: u32::MAX, utf16_difference: 0 }); + } else { + translations.clear(); + } } Self { translations } @@ -58,7 +64,7 @@ impl Utf8ToUtf16 { } else { // SAFETY: `translations` contains at least 2 entries if it's not empty. // We just checked it's not empty. - Some(unsafe { Utf8ToUtf16Converter::new(&self.translations) }) + Some(unsafe { Utf8ToUtf16Converter::new(&self.translations, false) }) } } @@ -69,6 +75,30 @@ impl Utf8ToUtf16 { } } + /// Convert all spans in AST to UTF-16. + /// + /// Additionally, checks that conversion of offsets during traversal via [`Utf8ToUtf16Converter`] + /// happens in ascending order of offset. Panics if it doesn't. + /// + /// This results in the fastest conversion, and [`Utf8ToUtf16Converter`] is designed to ensure that + /// [`Utf8ToUtf16Converter::convert_offset`] is called with offsets strictly in ascending order. + /// This should always be the case when the AST has come direct from parser. + /// It might well not be the case in an AST which has been modified, e.g. by transformer or minifier. + /// + /// This method is for use only in conformance tests, and requires `conformance` Cargo feature. + /// + /// # Panics + /// + /// Panics if offsets are converted out of order. + #[cfg(feature = "conformance")] + pub fn convert_program_with_ascending_order_checks(&self, program: &mut Program<'_>) { + assert!(self.translations.len() >= 2); + + // SAFETY: We just checked `translations` contains at least 2 entries + let mut converter = unsafe { Utf8ToUtf16Converter::new(&self.translations, true) }; + converter.visit_program(program); + } + /// Convert all spans in comments to UTF-16. pub fn convert_comments(&self, comments: &mut [Comment]) { if let Some(mut converter) = self.converter() { @@ -126,6 +156,14 @@ pub struct Utf8ToUtf16Converter<'t> { range_start_utf16: u32, /// Index of current `Translation` index: u32, + /// Previous UTF-8 offset which [`Utf8ToUtf16Converter::convert_offset`] was called with. + /// Only used in conformance tests. + #[cfg(feature = "conformance")] + previous_offset_utf8: u32, + /// `true` if offsets will be converted in ascending order. + /// Only used in conformance tests. + #[cfg(feature = "conformance")] + ascending_order: bool, } impl<'t> Utf8ToUtf16Converter<'t> { @@ -133,13 +171,24 @@ impl<'t> Utf8ToUtf16Converter<'t> { /// /// # SAFETY /// `translations` must contain at least 2 entries. - unsafe fn new(translations: &'t [Translation]) -> Self { + #[cfg_attr(not(feature = "conformance"), expect(unused_variables))] + unsafe fn new(translations: &'t [Translation], ascending_order: bool) -> Self { debug_assert!(translations.len() >= 2); // SAFETY: Caller guarantees `translations` contains at least 2 entries let range_len_utf8 = unsafe { translations.get_unchecked(1) }.utf8_offset; - Self { translations, range_start_utf8: 0, range_start_utf16: 0, range_len_utf8, index: 0 } + Self { + translations, + range_start_utf8: 0, + range_start_utf16: 0, + range_len_utf8, + index: 0, + #[cfg(feature = "conformance")] + previous_offset_utf8: 0, + #[cfg(feature = "conformance")] + ascending_order, + } } /// Reset this [`Utf8ToUtf16Converter`] to starting position. @@ -151,11 +200,21 @@ impl<'t> Utf8ToUtf16Converter<'t> { // SAFETY: Caller guaranteed `translations` contains at least 2 entries in `new` self.range_len_utf8 = unsafe { self.translations.get_unchecked(1) }.utf8_offset; self.index = 0; + + #[cfg(feature = "conformance")] + { + self.previous_offset_utf8 = 0; + } } /// Convert UTF-8 offset to UTF-16. /// /// Conversion is faster if `convert_offset` is called with offsets in ascending order. + /// + /// # Panics + /// Panics if `offset` is less than previous offset `convert_offset` was called with, + /// `conformance` Cargo features is enabled, and `Utf8ToUtf16Converter` was created with + /// `ascending_order: true`. // // This method is optimized for the offset being within the current range. // This will be the case if `convert_offset` is called with offsets in ascending order, @@ -171,6 +230,12 @@ impl<'t> Utf8ToUtf16Converter<'t> { pub fn convert_offset(&mut self, offset: &mut u32) { let utf8_offset = *offset; + #[cfg(feature = "conformance")] + if self.ascending_order { + assert!(utf8_offset >= self.previous_offset_utf8); + self.previous_offset_utf8 = utf8_offset; + } + // When AST has been modified, it may contain unspanned AST nodes. // Offset 0 always translates to 0. // Don't allow this to fall into the slow path, and don't update the current range, @@ -202,6 +267,11 @@ impl<'t> Utf8ToUtf16Converter<'t> { // Find the range containing this offset let utf8_offset = *offset; let (next_index, range_end_utf8) = if utf8_offset < self.range_start_utf8 { + #[cfg(feature = "conformance")] + { + assert!(!self.ascending_order); + } + self.find_range_before(utf8_offset) } else { self.find_range_after(utf8_offset) diff --git a/crates/oxc_cfg/Cargo.toml b/crates/oxc_cfg/Cargo.toml index ea7a46883e8af..9f6bb5e1d1663 100644 --- a/crates/oxc_cfg/Cargo.toml +++ b/crates/oxc_cfg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_cfg" -version = "0.68.1" +version = "0.70.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 4ccf456595362..87e56d2b3cab6 100644 --- a/crates/oxc_codegen/CHANGELOG.md +++ b/crates/oxc_codegen/CHANGELOG.md @@ -4,6 +4,30 @@ 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/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.70.0] - 2025-05-15 + +### Features + +- 4a72b58 codegen: Print comments inside `JSXEmptyExpression` (#11022) (Boshen) +- 1673ffb codegen: Rework printing normal / legal / annotation comments (#10997) (Boshen) + +### Refactor + +- 9225bde codegen: Make `Statement::Gen` code more compact (#10937) (Boshen) +- 751876b parser: Rewrite parse class element (#11035) (Boshen) + +## [0.69.0] - 2025-05-09 + +- ad4fbf4 ast: [**BREAKING**] Simplify `RegExpPattern` (#10834) (overlookmotel) + +### Bug Fixes + +- 2c05fa1 parser: Fix rhs precedence while parsing `PrivateInExpression` (#10866) (Boshen) +- 087af52 parser: Set the correct context for class property definition (#10859) (Boshen) + +### Refactor + + ## [0.68.1] - 2025-05-04 ### Bug Fixes diff --git a/crates/oxc_codegen/Cargo.toml b/crates/oxc_codegen/Cargo.toml index 9c66fccb5b0df..c0244dec7343a 100644 --- a/crates/oxc_codegen/Cargo.toml +++ b/crates/oxc_codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_codegen" -version = "0.68.1" +version = "0.70.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_codegen/src/comment.rs b/crates/oxc_codegen/src/comment.rs index f9bef059aaec7..d958ddc7c1cd4 100644 --- a/crates/oxc_codegen/src/comment.rs +++ b/crates/oxc_codegen/src/comment.rs @@ -1,7 +1,6 @@ -use oxc_span::{GetSpan, Span}; use rustc_hash::FxHashMap; -use oxc_ast::{Comment, CommentKind, ast::Argument}; +use oxc_ast::{Comment, CommentKind}; use oxc_syntax::identifier::is_line_terminator; use crate::{Codegen, LegalComment}; @@ -10,7 +9,12 @@ pub type CommentsMap = FxHashMap>; impl Codegen<'_> { pub(crate) fn build_comments(&mut self, comments: &[Comment]) { - self.comments.reserve(comments.len()); + if !self.options.comments + && self.options.legal_comments.is_none() + && !self.options.annotation_comments + { + return; + } let move_legal_comments = { let legal_comments = &self.options.legal_comments; matches!( @@ -23,10 +27,25 @@ impl Codegen<'_> { if comment.is_pure() || comment.is_no_side_effects() { continue; } - if comment.is_legal() && move_legal_comments { - self.legal_comments.push(*comment); + let mut add = false; + if comment.is_legal() { + if move_legal_comments { + self.legal_comments.push(*comment); + } else if self.options.print_legal_comment() { + add = true; + } + } else if comment.is_leading() { + if comment.is_annotation() { + if self.options.print_annotation_comment() { + add = true; + } + } else if self.options.print_normal_comment() { + add = true; + } + } + if add { + self.comments.entry(comment.attached_to).or_default().push(*comment); } - self.comments.entry(comment.attached_to).or_default().push(*comment); } } @@ -34,80 +53,30 @@ impl Codegen<'_> { self.comments.contains_key(&start) } - pub(crate) fn contains_comment_in_call_like_expression( - &self, - span: Span, - arguments: &[Argument<'_>], - ) -> (bool, bool) { - let has_comment_before_right_paren = - self.print_annotation_comment && span.end > 0 && self.has_comment(span.end - 1); - - let has_comment = has_comment_before_right_paren - || self.print_annotation_comment - && arguments.iter().any(|item| self.has_comment(item.span().start)); - - (has_comment, has_comment_before_right_paren) - } - - /// Whether to keep leading comments. - fn should_keep_leading_comment(comment: &Comment) -> bool { - comment.preceded_by_newline && comment.is_annotation() - } - pub(crate) fn print_leading_comments(&mut self, start: u32) { - if !self.print_any_comment { - return; + if let Some(comments) = self.comments.remove(&start) { + self.print_comments(&comments); } - let Some(comments) = self.comments.remove(&start) else { - return; - }; - let comments = - comments.into_iter().filter(Self::should_keep_leading_comment).collect::>(); - self.print_comments(&comments); } - pub(crate) fn get_statement_comments(&mut self, start: u32) -> Option> { - let comments = self.comments.remove(&start)?; - - let mut leading_comments = vec![]; - - for comment in comments { - if comment.is_legal() { - match &self.options.legal_comments { - LegalComment::None if self.options.comments => { - leading_comments.push(comment); - continue; - } - LegalComment::Inline => { - leading_comments.push(comment); - continue; - } - LegalComment::Eof | LegalComment::Linked(_) | LegalComment::External => { - /* noop, handled by `build_comments`. */ - continue; - } - LegalComment::None => {} - } - } - if Self::should_keep_leading_comment(&comment) { - leading_comments.push(comment); - } + pub(crate) fn get_comments(&mut self, start: u32) -> Option> { + if self.comments.is_empty() { + return None; } - - Some(leading_comments) + self.comments.remove(&start) } - /// A statement comment also includes legal comments #[inline] - pub(crate) fn print_statement_comments(&mut self, start: u32) { - if self.print_any_comment { - if let Some(comments) = self.get_statement_comments(start) { - self.print_comments(&comments); - } + pub(crate) fn print_comments_at(&mut self, start: u32) { + if let Some(comments) = self.get_comments(start) { + self.print_comments(&comments); } } pub(crate) fn print_expr_comments(&mut self, start: u32) -> bool { + if self.comments.is_empty() { + return false; + } let Some(comments) = self.comments.remove(&start) else { return false }; for comment in &comments { @@ -127,7 +96,7 @@ impl Codegen<'_> { pub(crate) fn print_comments(&mut self, comments: &[Comment]) { for (i, comment) in comments.iter().enumerate() { if i == 0 { - if comment.preceded_by_newline { + if comment.preceded_by_newline() { // Skip printing newline if this comment is already on a newline. if let Some(b) = self.last_byte() { match b { @@ -144,7 +113,7 @@ impl Codegen<'_> { } } if i >= 1 { - if comment.preceded_by_newline { + if comment.preceded_by_newline() { self.print_hard_newline(); self.print_indent(); } else if comment.is_legal() { @@ -153,7 +122,7 @@ impl Codegen<'_> { } self.print_comment(comment); if i == comments.len() - 1 { - if comment.is_line() || comment.followed_by_newline { + if comment.is_line() || comment.followed_by_newline() { self.print_hard_newline(); } else { self.print_next_indent_as_space = true; diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 4cd9888f42e60..0c7107e4d426e 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -49,15 +49,10 @@ impl Gen for Program<'_> { if let Some(hashbang) = &self.hashbang { hashbang.print(p, ctx); } - for directive in &self.directives { - directive.print(p, ctx); - } - for stmt in &self.body { - stmt.print(p, ctx); - p.print_semicolon_if_needed(); - } + p.print_directives_and_statements(&self.directives, &self.body, ctx); + p.print_semicolon_if_needed(); // Print trailing statement comments. - p.print_statement_comments(self.span.end); + p.print_comments_at(self.span.end); } } @@ -108,120 +103,41 @@ impl Gen for Statement<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { match self { Self::BlockStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::BreakStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::ContinueStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::DebuggerStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::DoWhileStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::EmptyStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::ExpressionStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::ForInStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::ForOfStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::ForStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::IfStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::LabeledStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::ReturnStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::SwitchStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::ThrowStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::TryStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::WhileStatement(stmt) => { - p.print_statement_comments(stmt.span.start); - stmt.print(p, ctx); - } - Self::WithStatement(stmt) => { - p.print_statement_comments(stmt.span.start); + p.print_comments_at(stmt.span.start); stmt.print(p, ctx); } - Self::ImportDeclaration(decl) => { - p.print_statement_comments(decl.span.start); - decl.print(p, ctx); - } - Self::ExportAllDeclaration(decl) => { - p.print_statement_comments(decl.span.start); - decl.print(p, ctx); - } - Self::ExportDefaultDeclaration(decl) => { - p.print_statement_comments(decl.span.start); - if let ExportDefaultDeclarationKind::FunctionDeclaration(func) = &decl.declaration { - if func.pure && p.print_annotation_comment { - p.print_str(NO_SIDE_EFFECTS_NEW_LINE_COMMENT); - } - } - decl.print(p, ctx); - } - Self::ExportNamedDeclaration(decl) => { - p.print_statement_comments(decl.span.start); - if let Some(Declaration::FunctionDeclaration(func)) = &decl.declaration { - if func.pure && p.print_annotation_comment { - p.print_str(NO_SIDE_EFFECTS_NEW_LINE_COMMENT); - } - } - decl.print(p, ctx); - } - Self::TSExportAssignment(decl) => { - p.print_statement_comments(decl.span.start); - decl.print(p, ctx); - } - Self::TSNamespaceExportDeclaration(decl) => { - p.print_statement_comments(decl.span.start); - decl.print(p, ctx); - } + Self::BreakStatement(stmt) => stmt.print(p, ctx), + Self::ContinueStatement(stmt) => stmt.print(p, ctx), + Self::DebuggerStatement(stmt) => stmt.print(p, ctx), + Self::DoWhileStatement(stmt) => stmt.print(p, ctx), + Self::EmptyStatement(stmt) => stmt.print(p, ctx), + Self::ExpressionStatement(stmt) => stmt.print(p, ctx), + Self::ForInStatement(stmt) => stmt.print(p, ctx), + Self::ForOfStatement(stmt) => stmt.print(p, ctx), + Self::ForStatement(stmt) => stmt.print(p, ctx), + Self::IfStatement(stmt) => stmt.print(p, ctx), + Self::LabeledStatement(stmt) => stmt.print(p, ctx), + Self::ReturnStatement(stmt) => stmt.print(p, ctx), + Self::SwitchStatement(stmt) => stmt.print(p, ctx), + Self::ThrowStatement(stmt) => stmt.print(p, ctx), + Self::TryStatement(stmt) => stmt.print(p, ctx), + Self::WhileStatement(stmt) => stmt.print(p, ctx), + Self::WithStatement(stmt) => stmt.print(p, ctx), + Self::ImportDeclaration(decl) => decl.print(p, ctx), + Self::ExportAllDeclaration(decl) => decl.print(p, ctx), + Self::ExportDefaultDeclaration(decl) => decl.print(p, ctx), + Self::ExportNamedDeclaration(decl) => decl.print(p, ctx), + Self::TSExportAssignment(decl) => decl.print(p, ctx), + Self::TSNamespaceExportDeclaration(decl) => decl.print(p, ctx), Self::VariableDeclaration(decl) => { - p.print_statement_comments(decl.span.start); + p.print_comments_at(decl.span.start); p.print_indent(); decl.print(p, ctx); p.print_semicolon_after_statement(); } Self::FunctionDeclaration(decl) => { - p.print_statement_comments(decl.span.start); - if decl.pure && p.print_annotation_comment { + p.print_comments_at(decl.span.start); + if decl.pure && p.options.print_annotation_comment() { p.print_indent(); p.print_str(NO_SIDE_EFFECTS_NEW_LINE_COMMENT); } @@ -230,38 +146,38 @@ impl Gen for Statement<'_> { p.print_soft_newline(); } Self::ClassDeclaration(decl) => { - p.print_statement_comments(decl.span.start); + p.print_comments_at(decl.span.start); p.print_indent(); decl.print(p, ctx); p.print_soft_newline(); } Self::TSModuleDeclaration(decl) => { - p.print_statement_comments(decl.span.start); + p.print_comments_at(decl.span.start); p.print_indent(); decl.print(p, ctx); p.print_soft_newline(); } Self::TSTypeAliasDeclaration(decl) => { - p.print_statement_comments(decl.span.start); p.print_indent(); + p.print_comments_at(decl.span.start); decl.print(p, ctx); p.print_semicolon_after_statement(); } Self::TSInterfaceDeclaration(decl) => { - p.print_statement_comments(decl.span.start); p.print_indent(); + p.print_comments_at(decl.span.start); decl.print(p, ctx); p.print_soft_newline(); } Self::TSEnumDeclaration(decl) => { - p.print_statement_comments(decl.span.start); p.print_indent(); + p.print_comments_at(decl.span.start); decl.print(p, ctx); p.print_soft_newline(); } Self::TSImportEqualsDeclaration(decl) => { - p.print_statement_comments(decl.span.start); p.print_indent(); + p.print_comments_at(decl.span.start); decl.print(p, ctx); p.print_semicolon_after_statement(); } @@ -271,6 +187,7 @@ impl Gen for Statement<'_> { impl Gen for ExpressionStatement<'_> { fn r#gen(&self, p: &mut Codegen, _ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.start_of_stmt = p.code_len(); @@ -285,6 +202,7 @@ impl Gen for ExpressionStatement<'_> { impl Gen for IfStatement<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); print_if(self, p, ctx); @@ -380,6 +298,7 @@ impl Gen for BlockStatement<'_> { impl Gen for ForStatement<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_space_before_identifier(); @@ -412,6 +331,7 @@ impl Gen for ForStatement<'_> { impl Gen for ForInStatement<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_space_before_identifier(); @@ -431,6 +351,7 @@ impl Gen for ForInStatement<'_> { impl Gen for ForOfStatement<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_space_before_identifier(); @@ -477,6 +398,7 @@ impl Gen for ForStatementLeft<'_> { impl Gen for WhileStatement<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_space_before_identifier(); @@ -491,6 +413,7 @@ impl Gen for WhileStatement<'_> { impl Gen for DoWhileStatement<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_space_before_identifier(); @@ -522,6 +445,7 @@ impl Gen for DoWhileStatement<'_> { impl Gen for EmptyStatement { fn r#gen(&self, p: &mut Codegen, _ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_semicolon(); @@ -531,6 +455,7 @@ impl Gen for EmptyStatement { impl Gen for ContinueStatement<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_space_before_identifier(); @@ -545,6 +470,7 @@ impl Gen for ContinueStatement<'_> { impl Gen for BreakStatement<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_space_before_identifier(); @@ -559,6 +485,7 @@ impl Gen for BreakStatement<'_> { impl Gen for SwitchStatement<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_space_before_identifier(); @@ -610,6 +537,7 @@ impl Gen for SwitchCase<'_> { impl Gen for ReturnStatement<'_> { fn r#gen(&self, p: &mut Codegen, _ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_space_before_identifier(); @@ -624,6 +552,7 @@ impl Gen for ReturnStatement<'_> { impl Gen for LabeledStatement<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); if !p.options.minify && (p.indent > 0 || p.print_next_indent_as_space) { p.add_source_mapping(self.span); p.print_indent(); @@ -637,6 +566,7 @@ impl Gen for LabeledStatement<'_> { impl Gen for TryStatement<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_space_before_identifier(); @@ -659,7 +589,7 @@ impl Gen for TryStatement<'_> { impl Gen for CatchClause<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { p.print_soft_space(); - p.print_statement_comments(self.span.start); + p.print_comments_at(self.span.start); p.print_str("catch"); if let Some(param) = &self.param { p.print_soft_space(); @@ -674,6 +604,7 @@ impl Gen for CatchClause<'_> { impl Gen for ThrowStatement<'_> { fn r#gen(&self, p: &mut Codegen, _ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_space_before_identifier(); @@ -686,6 +617,7 @@ impl Gen for ThrowStatement<'_> { impl Gen for WithStatement<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_space_before_identifier(); @@ -699,6 +631,7 @@ impl Gen for WithStatement<'_> { impl Gen for DebuggerStatement { fn r#gen(&self, p: &mut Codegen, _ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_space_before_identifier(); @@ -804,27 +737,18 @@ impl Gen for Function<'_> { impl Gen for FunctionBody<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { let span_end = self.span.end; - let comments_at_end = if p.print_any_comment && span_end > 0 { - p.get_statement_comments(span_end - 1) - } else { - None - }; - let is_empty = if self.is_empty() { - comments_at_end.is_none() || comments_at_end.as_ref().is_some_and(Vec::is_empty) + let comments_at_end = if span_end > 0 { p.get_comments(span_end - 1) } else { None }; + let single_line = if self.is_empty() { + comments_at_end.as_ref().is_none_or(|comments| comments.iter().all(|c| c.is_block())) } else { false }; - p.print_curly_braces(self.span, is_empty, |p| { - for directive in &self.directives { - directive.print(p, ctx); - } - for stmt in &self.statements { - p.print_semicolon_if_needed(); - stmt.print(p, ctx); - } + p.print_curly_braces(self.span, single_line, |p| { + p.print_directives_and_statements(&self.directives, &self.statements, ctx); // Print trailing statement comments. if let Some(comments) = comments_at_end { p.print_comments(&comments); + p.print_next_indent_as_space = false; } }); p.needs_semicolon = false; @@ -866,6 +790,7 @@ impl Gen for FormalParameters<'_> { impl Gen for ImportDeclaration<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_space_before_identifier(); @@ -1012,6 +937,12 @@ impl Gen for ImportAttribute<'_> { impl Gen for ExportNamedDeclaration<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); + if let Some(Declaration::FunctionDeclaration(func)) = &self.declaration { + if func.pure && p.options.print_annotation_comment() { + p.print_str(NO_SIDE_EFFECTS_NEW_LINE_COMMENT); + } + } p.add_source_mapping(self.span); p.print_indent(); p.print_str("export"); @@ -1065,6 +996,7 @@ impl Gen for ExportNamedDeclaration<'_> { impl Gen for TSExportAssignment<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { p.print_indent(); + p.print_comments_at(self.span.start); p.print_str("export = "); self.expression.print_expr(p, Precedence::Lowest, ctx); p.print_semicolon_after_statement(); @@ -1074,6 +1006,7 @@ impl Gen for TSExportAssignment<'_> { impl Gen for TSNamespaceExportDeclaration<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { p.print_indent(); + p.print_comments_at(self.span.start); p.print_str("export as namespace "); self.id.print(p, ctx); p.print_semicolon_after_statement(); @@ -1118,6 +1051,7 @@ impl Gen for ModuleExportName<'_> { impl Gen for ExportAllDeclaration<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); p.add_source_mapping(self.span); p.print_indent(); p.print_str("export"); @@ -1150,6 +1084,12 @@ impl Gen for ExportAllDeclaration<'_> { impl Gen for ExportDefaultDeclaration<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { + p.print_comments_at(self.span.start); + if let ExportDefaultDeclarationKind::FunctionDeclaration(func) = &self.declaration { + if func.pure && p.options.print_annotation_comment() { + p.print_str(NO_SIDE_EFFECTS_NEW_LINE_COMMENT); + } + } p.add_source_mapping(self.span); p.print_indent(); p.print_str("export default "); @@ -1195,13 +1135,13 @@ impl GenExpr for Expression<'_> { Self::ArrayExpression(expr) => expr.print(p, ctx), Self::ObjectExpression(expr) => expr.print_expr(p, precedence, ctx), Self::FunctionExpression(func) => { - if func.pure && p.print_annotation_comment { + if func.pure && p.options.print_annotation_comment() { p.print_str(NO_SIDE_EFFECTS_COMMENT); } func.print(p, ctx); } Self::ArrowFunctionExpression(func) => { - if func.pure && p.print_annotation_comment { + if func.pure && p.options.print_annotation_comment() { p.print_str(NO_SIDE_EFFECTS_COMMENT); } func.print_expr(p, precedence, ctx); @@ -1444,7 +1384,7 @@ impl GenExpr for CallExpression<'_> { let is_statement = p.start_of_stmt == p.code_len(); let is_export_default = p.start_of_default_export == p.code_len(); let mut wrap = precedence >= Precedence::New || ctx.intersects(Context::FORBID_CALL); - let pure = self.pure && p.print_annotation_comment; + let pure = self.pure && p.options.print_annotation_comment(); if precedence >= Precedence::Postfix && pure { wrap = true; } @@ -1466,22 +1406,7 @@ impl GenExpr for CallExpression<'_> { if let Some(type_parameters) = &self.type_arguments { type_parameters.print(p, ctx); } - p.print_ascii_byte(b'('); - - let (has_comment, has_comment_before_right_paren) = - p.contains_comment_in_call_like_expression(self.span, self.arguments.as_slice()); - if has_comment { - p.indent(); - p.print_list_with_comments(self.arguments.as_slice(), ctx); - // Handle `/* comment */);` - if !has_comment_before_right_paren || !p.print_expr_comments(self.span.end - 1) { - p.print_soft_newline(); - } - p.dedent(); - } else { - p.print_list(&self.arguments, ctx); - } - p.print_ascii_byte(b')'); + p.print_arguments(self.span, &self.arguments, ctx); p.add_source_mapping_end(self.span); }); } @@ -2213,7 +2138,7 @@ impl GenExpr for ChainExpression<'_> { impl GenExpr for NewExpression<'_> { fn gen_expr(&self, p: &mut Codegen, precedence: Precedence, ctx: Context) { let mut wrap = precedence >= self.precedence(); - let pure = self.pure && p.print_annotation_comment; + let pure = self.pure && p.options.print_annotation_comment(); if precedence >= Precedence::Postfix && pure { wrap = true; } @@ -2230,22 +2155,7 @@ impl GenExpr for NewExpression<'_> { // Omit the "()" when minifying, but only when safe to do so if !p.options.minify || !self.arguments.is_empty() || precedence >= Precedence::Postfix { - p.print_ascii_byte(b'('); - let (has_comment, has_comment_before_right_paren) = p - .contains_comment_in_call_like_expression(self.span, self.arguments.as_slice()); - if has_comment { - p.indent(); - p.print_list_with_comments(self.arguments.as_slice(), ctx); - // Handle `/* comment */);` - if !has_comment_before_right_paren || !p.print_expr_comments(self.span.end - 1) - { - p.print_soft_newline(); - } - p.dedent(); - } else { - p.print_list(&self.arguments, ctx); - } - p.print_ascii_byte(b')'); + p.print_arguments(self.span, &self.arguments, ctx); } }); } @@ -2472,7 +2382,9 @@ impl Gen for JSXAttribute<'_> { } impl Gen for JSXEmptyExpression { - fn r#gen(&self, _: &mut Codegen, _ctx: Context) {} + fn r#gen(&self, p: &mut Codegen, _ctx: Context) { + p.print_comments_at(self.span.end); + } } impl Gen for JSXExpression<'_> { @@ -3670,13 +3582,7 @@ impl Gen for TSModuleBlock<'_> { fn r#gen(&self, p: &mut Codegen, ctx: Context) { let is_empty = self.directives.is_empty() && self.body.is_empty(); p.print_curly_braces(self.span, is_empty, |p| { - for directive in &self.directives { - directive.print(p, ctx); - } - for stmt in &self.body { - p.print_semicolon_if_needed(); - stmt.print(p, ctx); - } + p.print_directives_and_statements(&self.directives, &self.body, ctx); }); p.needs_semicolon = false; } @@ -3855,21 +3761,7 @@ impl GenExpr for V8IntrinsicExpression<'_> { p.add_source_mapping(self.span); p.print_ascii_byte(b'%'); self.name.print(p, Context::empty()); - p.print_ascii_byte(b'('); - let (has_comment, has_comment_before_right_paren) = - p.contains_comment_in_call_like_expression(self.span, self.arguments.as_slice()); - if has_comment { - p.indent(); - p.print_list_with_comments(self.arguments.as_slice(), ctx); - // Handle `/* comment */);` - if !has_comment_before_right_paren || !p.print_expr_comments(self.span.end - 1) { - p.print_soft_newline(); - } - p.dedent(); - } else { - p.print_list(&self.arguments, ctx); - } - p.print_ascii_byte(b')'); + p.print_arguments(self.span, &self.arguments, ctx); p.add_source_mapping_end(self.span); }); } diff --git a/crates/oxc_codegen/src/lib.rs b/crates/oxc_codegen/src/lib.rs index 842ed8a22e41a..02e211a9a3356 100644 --- a/crates/oxc_codegen/src/lib.rs +++ b/crates/oxc_codegen/src/lib.rs @@ -15,10 +15,7 @@ mod str; use std::borrow::Cow; -use oxc_ast::ast::{ - Argument, BindingIdentifier, BlockStatement, Comment, Expression, IdentifierReference, Program, - Statement, -}; +use oxc_ast::ast::*; use oxc_data_structures::{code_buffer::CodeBuffer, stack::Stack}; use oxc_semantic::Scoping; use oxc_span::{GetSpan, SPAN, Span}; @@ -107,11 +104,6 @@ pub struct Codegen<'a> { /// Fast path for [CodegenOptions::single_quote] quote: Quote, - /// Fast path for if print comments - print_any_comment: bool, - print_legal_comment: bool, - print_annotation_comment: bool, - // Builders comments: CommentsMap, @@ -146,9 +138,6 @@ impl<'a> Codegen<'a> { #[must_use] pub fn new() -> Self { let options = CodegenOptions::default(); - let print_any_comment = options.print_any_comment(); - let print_legal_comment = options.print_legal_comment(); - let print_annotation_comment = options.print_annotation_comment(); Self { options, source_text: None, @@ -167,9 +156,6 @@ impl<'a> Codegen<'a> { is_jsx: false, indent: 0, quote: Quote::Double, - print_any_comment, - print_legal_comment, - print_annotation_comment, comments: CommentsMap::default(), legal_comments: vec![], sourcemap_builder: None, @@ -180,9 +166,6 @@ impl<'a> Codegen<'a> { #[must_use] pub fn with_options(mut self, options: CodegenOptions) -> Self { self.quote = if options.single_quote { Quote::Single } else { Quote::Double }; - self.print_any_comment = options.print_any_comment(); - self.print_legal_comment = options.print_legal_comment(); - self.print_annotation_comment = options.print_annotation_comment(); self.options = options; self } @@ -211,14 +194,7 @@ impl<'a> Codegen<'a> { self.quote = if self.options.single_quote { Quote::Single } else { Quote::Double }; self.source_text = Some(program.source_text); self.code.reserve(program.source_text.len()); - - if self.print_any_comment { - if program.comments.is_empty() { - self.print_any_comment = false; - } else { - self.build_comments(&program.comments); - } - } + self.build_comments(&program.comments); if let Some(path) = &self.options.source_map_path { self.sourcemap_builder = Some(SourcemapBuilder::new(path, program.source_text)); } @@ -469,6 +445,44 @@ impl<'a> Codegen<'a> { self.needs_semicolon = false; } + fn print_directives_and_statements( + &mut self, + directives: &[Directive<'_>], + stmts: &[Statement<'_>], + ctx: Context, + ) { + for directive in directives { + directive.print(self, ctx); + } + let Some((first, rest)) = stmts.split_first() else { + return; + }; + + // Ensure first string literal is not a directive. + let mut first_needs_parens = false; + if directives.is_empty() && !self.options.minify { + if let Statement::ExpressionStatement(s) = first { + let s = s.expression.without_parentheses(); + if matches!(s, Expression::StringLiteral(_)) { + first_needs_parens = true; + self.print_ascii_byte(b'('); + s.print_expr(self, Precedence::Lowest, ctx); + self.print_ascii_byte(b')'); + self.print_semicolon_after_statement(); + } + } + } + + if !first_needs_parens { + first.print(self, ctx); + } + + for stmt in rest { + self.print_semicolon_if_needed(); + stmt.print(self, ctx); + } + } + #[inline] fn print_list(&mut self, items: &[T], ctx: Context) { let Some((first, rest)) = items.split_first() else { @@ -482,6 +496,44 @@ impl<'a> Codegen<'a> { } } + #[inline] + fn print_expressions(&mut self, items: &[T], precedence: Precedence, ctx: Context) { + let Some((first, rest)) = items.split_first() else { + return; + }; + first.print_expr(self, precedence, ctx); + for item in rest { + self.print_comma(); + self.print_soft_space(); + item.print_expr(self, precedence, ctx); + } + } + + fn print_arguments(&mut self, span: Span, arguments: &[Argument<'_>], ctx: Context) { + self.print_ascii_byte(b'('); + + let has_comment_before_right_paren = span.end > 0 && self.has_comment(span.end - 1); + + let has_comment = has_comment_before_right_paren + || arguments.iter().any(|item| self.has_comment(item.span().start)); + + if has_comment { + self.indent(); + self.print_list_with_comments(arguments, ctx); + // Handle `/* comment */);` + if !has_comment_before_right_paren + || (span.end > 0 && !self.print_expr_comments(span.end - 1)) + { + self.print_soft_newline(); + } + self.dedent(); + self.print_indent(); + } else { + self.print_list(arguments, ctx); + } + self.print_ascii_byte(b')'); + } + fn print_list_with_comments(&mut self, items: &[Argument<'_>], ctx: Context) { let Some((first, rest)) = items.split_first() else { return; @@ -505,19 +557,6 @@ impl<'a> Codegen<'a> { } } - #[inline] - fn print_expressions(&mut self, items: &[T], precedence: Precedence, ctx: Context) { - let Some((first, rest)) = items.split_first() else { - return; - }; - first.print_expr(self, precedence, ctx); - for item in rest { - self.print_comma(); - self.print_soft_space(); - item.print_expr(self, precedence, ctx); - } - } - fn get_identifier_reference_name(&self, reference: &IdentifierReference<'a>) -> &'a str { if let Some(scoping) = &self.scoping { if let Some(reference_id) = reference.reference_id.get() { diff --git a/crates/oxc_codegen/src/options.rs b/crates/oxc_codegen/src/options.rs index 9f1b1f5be5eca..fd0ac0bf18d6e 100644 --- a/crates/oxc_codegen/src/options.rs +++ b/crates/oxc_codegen/src/options.rs @@ -13,19 +13,30 @@ pub struct CodegenOptions { /// Default is `false`. pub minify: bool, - /// Print all comments? + /// Print normal comments? + /// + /// At present, only some leading comments are preserved. + /// + /// Does not control legal and annotation comments. /// /// Default is `true`. pub comments: bool, - /// Print annotation comments, e.g. `/* #__PURE__ */` and `/* #__NO_SIDE_EFFECTS__ */`. + /// Print annotation comments. + /// + /// * jsdoc: `/** jsdoc */` + /// * pure: `/* #__PURE__ */` and `/* #__NO_SIDE_EFFECTS__ */` + /// * webpack: `/* webpackChunkName */` + /// * vite: `/* @vite-ignore */` + /// * coverage: `v8 ignore`, `c8 ignore`, `node:coverage`, `istanbul ignore` /// /// Default is `true`. pub annotation_comments: bool, /// Print legal comments. /// - /// + /// * starts with `//!` or `/*!`. + /// * contains `/* @license */` or `/* @preserve */` /// /// Default is [LegalComment::Inline]. pub legal_comments: LegalComment, @@ -64,16 +75,19 @@ impl CodegenOptions { } } - pub(crate) fn print_any_comment(&self) -> bool { + #[inline] + pub(crate) fn print_normal_comment(&self) -> bool { self.comments } + #[inline] pub(crate) fn print_legal_comment(&self) -> bool { - self.comments || !self.legal_comments.is_none() + self.legal_comments.is_inline() } + #[inline] pub(crate) fn print_annotation_comment(&self) -> bool { - self.comments || self.annotation_comments + self.annotation_comments } } diff --git a/crates/oxc_codegen/tests/integration/comments.rs b/crates/oxc_codegen/tests/integration/comments.rs index 43c6ad1aa7bf7..5937db66feb3c 100644 --- a/crates/oxc_codegen/tests/integration/comments.rs +++ b/crates/oxc_codegen/tests/integration/comments.rs @@ -1,3 +1,10 @@ +use crate::tester::test_same; + +#[test] +fn unit() { + test_same("
{/* Hello */}
;\n"); +} + pub mod jsdoc { use crate::snapshot; @@ -457,3 +464,64 @@ delete /* @__PURE__ */ (() => {})();", snapshot("pure_comments", &cases); } } + +pub mod options { + use oxc_codegen::{CodegenOptions, LegalComment}; + + use crate::codegen_options; + + #[test] + fn test() { + let code = " +//! Top Legal Comment +function foo() { + /** JSDoc Comment */ + function bar() { + /* #__PURE__ */ x(); + } + function baz() { + //! Function Legal Comment + } + x(/* Normal Comment */); + x(/** Call Expression Annotation Comment */ token); +}"; + + for comments in [true, false] { + for annotation in [true, false] { + for legal in [LegalComment::Inline, LegalComment::Eof, LegalComment::None] { + let options = CodegenOptions { + comments, + annotation_comments: annotation, + legal_comments: legal.clone(), + ..CodegenOptions::default() + }; + let printed = codegen_options(code, &options).code; + + if comments { + assert!(printed.contains("Normal Comment")); + } else { + assert!(!printed.contains("Normal Comment")); + } + + if annotation { + assert!(printed.contains("JSDoc Comment")); + assert!(printed.contains("__PURE__")); + assert!(printed.contains("Call Expression Annotation Comment")); + } else { + assert!(!printed.contains("JSDoc Comment")); + assert!(!printed.contains("__PURE__")); + assert!(!printed.contains("Call Expression Annotation Comment")); + } + + if legal.is_none() { + assert!(!printed.contains("Top Legal Comment")); + assert!(!printed.contains("Function Legal Comment")); + } else { + assert!(printed.contains("Top Legal Comment")); + assert!(printed.contains("Function Legal Comment")); + } + } + } + } + } +} diff --git a/crates/oxc_codegen/tests/integration/main.rs b/crates/oxc_codegen/tests/integration/main.rs index 03da1970a92b3..55b849c172c27 100644 --- a/crates/oxc_codegen/tests/integration/main.rs +++ b/crates/oxc_codegen/tests/integration/main.rs @@ -10,10 +10,12 @@ use oxc_codegen::{Codegen, CodegenOptions, CodegenReturn}; use oxc_parser::Parser; use oxc_span::SourceType; +#[track_caller] pub fn codegen(source_text: &str) -> String { codegen_options(source_text, &CodegenOptions::default()).code } +#[track_caller] pub fn codegen_options(source_text: &str, options: &CodegenOptions) -> CodegenReturn { let allocator = Allocator::default(); let source_type = SourceType::ts(); @@ -23,10 +25,12 @@ pub fn codegen_options(source_text: &str, options: &CodegenOptions) -> CodegenRe Codegen::new().with_options(options).build(&ret.program) } +#[track_caller] pub fn snapshot(name: &str, cases: &[&str]) { snapshot_options(name, cases, &CodegenOptions::default()); } +#[track_caller] pub fn snapshot_options(name: &str, cases: &[&str], options: &CodegenOptions) { use std::fmt::Write; diff --git a/crates/oxc_codegen/tests/integration/snapshots/jsodc.snap b/crates/oxc_codegen/tests/integration/snapshots/jsodc.snap index 22ae3612397cc..1fd5cd51d6d6a 100644 --- a/crates/oxc_codegen/tests/integration/snapshots/jsodc.snap +++ b/crates/oxc_codegen/tests/integration/snapshots/jsodc.snap @@ -172,6 +172,7 @@ this.Book = function(title) { /** The title of the book. */ this.title = title; }; +// https://github.com/oxc-project/oxc/issues/6006 export enum DefinitionKind { /** * Definition is a referenced variable. diff --git a/crates/oxc_codegen/tests/integration/snapshots/legal_eof_comments.snap b/crates/oxc_codegen/tests/integration/snapshots/legal_eof_comments.snap index 5949430f9a903..882b8431b32ce 100644 --- a/crates/oxc_codegen/tests/integration/snapshots/legal_eof_comments.snap +++ b/crates/oxc_codegen/tests/integration/snapshots/legal_eof_comments.snap @@ -100,10 +100,10 @@ function foo() { ---------- function foo() { (() => { - /** - * @preserve - */ - })(); + /** + * @preserve + */ +})(); /** * @preserve */ diff --git a/crates/oxc_codegen/tests/integration/snapshots/legal_inline_comments.snap b/crates/oxc_codegen/tests/integration/snapshots/legal_inline_comments.snap index 5b0e6df19c524..cf21f7bf039a7 100644 --- a/crates/oxc_codegen/tests/integration/snapshots/legal_inline_comments.snap +++ b/crates/oxc_codegen/tests/integration/snapshots/legal_inline_comments.snap @@ -92,10 +92,10 @@ function foo() { ---------- function foo() { (() => { - /** - * @preserve - */ - })(); + /** + * @preserve + */ +})(); /** * @preserve */ diff --git a/crates/oxc_codegen/tests/integration/snapshots/legal_linked_comments.snap b/crates/oxc_codegen/tests/integration/snapshots/legal_linked_comments.snap index d5f0f6b6d48d5..4de133c25a30f 100644 --- a/crates/oxc_codegen/tests/integration/snapshots/legal_linked_comments.snap +++ b/crates/oxc_codegen/tests/integration/snapshots/legal_linked_comments.snap @@ -85,10 +85,10 @@ function foo() { ---------- function foo() { (() => { - /** - * @preserve - */ - })(); + /** + * @preserve + */ +})(); /** * @preserve */ diff --git a/crates/oxc_codegen/tests/integration/snapshots/minify.snap b/crates/oxc_codegen/tests/integration/snapshots/minify.snap index f065931177a61..cec662af88cd6 100644 --- a/crates/oxc_codegen/tests/integration/snapshots/minify.snap +++ b/crates/oxc_codegen/tests/integration/snapshots/minify.snap @@ -219,3 +219,12 @@ export import b = require("b"); ---------- import a = require("a");export import b = require("b"); +########## 41 +class C { + static + static + static + bar() {} +} +---------- +class C{static static;static bar(){}} diff --git a/crates/oxc_codegen/tests/integration/snapshots/ts.snap b/crates/oxc_codegen/tests/integration/snapshots/ts.snap index 952a5584e00dc..bbd3b9fecdb20 100644 --- a/crates/oxc_codegen/tests/integration/snapshots/ts.snap +++ b/crates/oxc_codegen/tests/integration/snapshots/ts.snap @@ -307,9 +307,9 @@ import 'module-name'; import {} from "mod"; export let name1, name2; export const name3 = 1, name4 = 2; -export function functionName() {} +export function functionName() {/* … */} export class ClassName {} -export function* generatorFunctionName() {} +export function* generatorFunctionName() {/* … */} export const { name5, name2: bar } = o; export const [name6, name7] = array; export { name8, name81 }; @@ -331,3 +331,16 @@ export import b = require("b"); ---------- import a = require('a'); export import b = require('b'); + +########## 41 +class C { + static + static + static + bar() {} +} +---------- +class C { + static static; + static bar() {} +} diff --git a/crates/oxc_codegen/tests/integration/ts.rs b/crates/oxc_codegen/tests/integration/ts.rs index 5f571e3dcdf11..9a751c1b53507 100644 --- a/crates/oxc_codegen/tests/integration/ts.rs +++ b/crates/oxc_codegen/tests/integration/ts.rs @@ -118,12 +118,14 @@ export { default as name16 } from "module-name"; import a = require("a"); export import b = require("b"); "#, + "class C { + static + static + static + bar() {} +}", ]; snapshot("ts", &cases); - snapshot_options( - "minify", - &cases, - &CodegenOptions { minify: true, ..CodegenOptions::default() }, - ); + snapshot_options("minify", &cases, &CodegenOptions::minify()); } diff --git a/crates/oxc_data_structures/CHANGELOG.md b/crates/oxc_data_structures/CHANGELOG.md index 7da028a129521..ddc7c0ff86b5c 100644 --- a/crates/oxc_data_structures/CHANGELOG.md +++ b/crates/oxc_data_structures/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/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.69.0] - 2025-05-09 + +### Features + +- 05bf1be data_structures: Add `InlineString::as_mut_str` method (#10856) (overlookmotel) + +### Performance + +- 2d4b8c9 data_structures: Optimize `InlineString` (#10850) (overlookmotel) + ## [0.61.0] - 2025-03-20 ### Features diff --git a/crates/oxc_data_structures/Cargo.toml b/crates/oxc_data_structures/Cargo.toml index a627f093965ca..65df36287056c 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.68.1" +version = "0.70.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 780f3a47cab3d..865cae86c47cd 100644 --- a/crates/oxc_diagnostics/Cargo.toml +++ b/crates/oxc_diagnostics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_diagnostics" -version = "0.68.1" +version = "0.70.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_diagnostics/src/service.rs b/crates/oxc_diagnostics/src/service.rs index 7c90782e3f8db..7cd6e7590433a 100644 --- a/crates/oxc_diagnostics/src/service.rs +++ b/crates/oxc_diagnostics/src/service.rs @@ -211,8 +211,9 @@ impl DiagnosticService { // Setting to 1200 because graphical output may contain ansi escape codes and other decorations. if err_str.lines().any(|line| line.len() >= 1200) { let minified_diagnostic = Error::new( - OxcDiagnostic::warn("File is too long to fit on the screen") - .with_help(format!("{path:?} seems like a minified file")), + OxcDiagnostic::warn("File is too long to fit on the screen").with_help( + format!("{} seems like a minified file", path.display()), + ), ); if let Some(err_str) = self.reporter.render_error(minified_diagnostic) { diff --git a/crates/oxc_ecmascript/CHANGELOG.md b/crates/oxc_ecmascript/CHANGELOG.md index e1d74be701052..2ce93db6b1115 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/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.69.0] - 2025-05-09 + +### Refactor + +- 7c6ac7c ecmascript: Remove outdated todo comment (#10909) (Ulrich Stark) + ## [0.62.0] - 2025-04-01 - 4077868 ecmascript: [**BREAKING**] Introduce MayHaveSideEffectsContext (#10126) (sapphi-red) diff --git a/crates/oxc_ecmascript/Cargo.toml b/crates/oxc_ecmascript/Cargo.toml index b3f5286925787..d414296441805 100644 --- a/crates/oxc_ecmascript/Cargo.toml +++ b/crates/oxc_ecmascript/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_ecmascript" -version = "0.68.1" +version = "0.70.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_estree/CHANGELOG.md b/crates/oxc_estree/CHANGELOG.md index dd18c00688446..b9b2ad01d42f1 100644 --- a/crates/oxc_estree/CHANGELOG.md +++ b/crates/oxc_estree/CHANGELOG.md @@ -4,6 +4,21 @@ 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/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.69.0] - 2025-05-09 + +### Features + +- d066516 ast_tools: Support `#[estree(prepend_to)]` (#10849) (overlookmotel) + +### Performance + +- 49a6f97 napi/parser: Faster fixup of `BigInt`s and `RegExp`s (#10820) (overlookmotel) + +### Refactor + +- 5645684 ast/estree: Print header and footer on JSON AST with fixes on separate lines (#10869) (overlookmotel) +- b16331e ast/estree: Generalize concatenating fields with `Concat2` (#10848) (overlookmotel) + ## [0.63.0] - 2025-04-08 ### Performance diff --git a/crates/oxc_estree/Cargo.toml b/crates/oxc_estree/Cargo.toml index d03161e8976b2..06c127fb24ec1 100644 --- a/crates/oxc_estree/Cargo.toml +++ b/crates/oxc_estree/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_estree" -version = "0.68.1" +version = "0.70.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_formatter/src/write/arrow_function_expression.rs b/crates/oxc_formatter/src/write/arrow_function_expression.rs index 8cf1ba916b629..c57d20afcb13c 100644 --- a/crates/oxc_formatter/src/write/arrow_function_expression.rs +++ b/crates/oxc_formatter/src/write/arrow_function_expression.rs @@ -368,7 +368,7 @@ impl<'a, 'b> ArrowFunctionLayout<'a, 'b> { let mut current = arrow; let mut should_break = false; - let result = loop { + loop { if current.expression { if let Some(Statement::ExpressionStatement(expr_stmt)) = current.body.statements.first() @@ -405,9 +405,7 @@ impl<'a, 'b> ArrowFunctionLayout<'a, 'b> { options, }), }; - }; - - result + } } /// Returns a `true` result if the arrow function contains any elements which diff --git a/crates/oxc_isolated_declarations/CHANGELOG.md b/crates/oxc_isolated_declarations/CHANGELOG.md index a05ecd0039988..af331357a091d 100644 --- a/crates/oxc_isolated_declarations/CHANGELOG.md +++ b/crates/oxc_isolated_declarations/CHANGELOG.md @@ -4,6 +4,24 @@ 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/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.70.0] - 2025-05-15 + +### Features + +- 1673ffb codegen: Rework printing normal / legal / annotation comments (#10997) (Boshen) + +### Bug Fixes + +- 9a4b135 isolated-declarations: Lost leading comments of `export default` function/class/interface (#10990) (Dunqing) + +## [0.69.0] - 2025-05-09 + +- 8a3bba8 ast: [**BREAKING**] Fix field order for `PropertyDefinition` (#10902) (overlookmotel) + +### Bug Fixes + +- 2c09243 ast: Fix field order for `AccessorProperty` (#10878) (overlookmotel) + ## [0.68.0] - 2025-05-03 - a0a37e0 ast: [**BREAKING**] `AstBuilder` methods require an `Atom` with correct lifetime (#10735) (overlookmotel) diff --git a/crates/oxc_isolated_declarations/Cargo.toml b/crates/oxc_isolated_declarations/Cargo.toml index 2ddb722be6acf..45c29528dac0c 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.68.1" +version = "0.70.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_isolated_declarations/src/module.rs b/crates/oxc_isolated_declarations/src/module.rs index f3c57fc75a04f..6aea390cd456c 100644 --- a/crates/oxc_isolated_declarations/src/module.rs +++ b/crates/oxc_isolated_declarations/src/module.rs @@ -59,8 +59,30 @@ impl<'a> IsolatedDeclarations<'a> { declaration.map(|(var_decl, declaration)| { let exported = ModuleExportName::IdentifierName(self.ast.identifier_name(SPAN, "default")); + // When `var_decl` is Some, the comments are moved to the variable declaration, otherwise + // keep the comments on the export default declaration to avoid losing them. + // ```ts + // // comment + // export default function(): void {} + // + // // comment + // export default 1; + // ``` + // + // to + // + // ```ts + // // comment + // export default function(): void; + // + // // comment + // const _default = 1; + // export default _default; + // ``` + + let span = if var_decl.is_some() { SPAN } else { decl.span }; let declaration = - self.ast.module_declaration_export_default_declaration(SPAN, exported, declaration); + self.ast.module_declaration_export_default_declaration(span, exported, declaration); (var_decl, Statement::from(declaration)) }) } diff --git a/crates/oxc_isolated_declarations/tests/fixtures/export-default.ts b/crates/oxc_isolated_declarations/tests/fixtures/export-default.ts index 9508896126de7..facd97dbbadf4 100644 --- a/crates/oxc_isolated_declarations/tests/fixtures/export-default.ts +++ b/crates/oxc_isolated_declarations/tests/fixtures/export-default.ts @@ -1,6 +1,6 @@ -const defaultDelimitersClose = new Uint8Array([125, 125]) +const defaultDelimitersClose = new Uint8Array([125, 125]); +/** comment should be a leading comment of the class */ export default class Tokenizer { - public delimiterClose: Uint8Array = defaultDelimitersClose + public delimiterClose: Uint8Array = defaultDelimitersClose; } - diff --git a/crates/oxc_isolated_declarations/tests/snapshots/async-function.snap b/crates/oxc_isolated_declarations/tests/snapshots/async-function.snap index 6483f14c6221a..392e6cd3214ad 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/async-function.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/async-function.snap @@ -5,11 +5,14 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/async-function.ts ``` ==================== .D.TS ==================== +// Correct declare function asyncFunctionGood(): Promise; declare const asyncFunctionGoo2: () => Promise; declare class AsyncClassGood { method(): number; } +// Need to explicit return type for async functions +// Incorrect declare function asyncFunction(); declare const asyncFunction2: unknown; declare class AsyncClassBad { diff --git a/crates/oxc_isolated_declarations/tests/snapshots/export-default.snap b/crates/oxc_isolated_declarations/tests/snapshots/export-default.snap index 8a0e7db10d793..df54d8a07fdc4 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/export-default.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/export-default.snap @@ -5,6 +5,7 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/export-default.ts ``` ==================== .D.TS ==================== +/** comment should be a leading comment of the class */ export default class Tokenizer { delimiterClose: Uint8Array; } diff --git a/crates/oxc_isolated_declarations/tests/snapshots/function-parameters.snap b/crates/oxc_isolated_declarations/tests/snapshots/function-parameters.snap index e5769c27b5e7e..8b747b28e51ad 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/function-parameters.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/function-parameters.snap @@ -5,11 +5,13 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/function-parameters. ``` ==================== .D.TS ==================== +// Correct export declare function fnDeclGood(p?: T, rParam?: string): void; export declare function fnDeclGood2(p?: T, rParam?: number): void; export declare function fooGood([a, b]?: any[]): number; export declare const fooGood2: ({ a, b }?: object) => number; export declare function fooGood3({ a, b: [{ c }] }: object): void; +// Incorrect export declare function fnDeclBad(p: T, rParam: T, r2: T): void; export declare function fnDeclBad2(p: T, r2: T): void; export declare function fnDeclBad3(p: T, rParam?: T, r2: T): void; diff --git a/crates/oxc_isolated_declarations/tests/snapshots/function-signatures.snap b/crates/oxc_isolated_declarations/tests/snapshots/function-signatures.snap index c7998e7d279a1..98d20a093c95a 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/function-signatures.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/function-signatures.snap @@ -5,7 +5,10 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/function-signatures. ``` ==================== .D.TS ==================== +// All of these are valid function signatures under isolatedDeclarations export declare function A(): void; export declare function B(): (() => void) | undefined; +// There should be no declaration for the implementation signature, just the +// two overloads. export declare function C(x: string): void; export declare function C(x: number): void; diff --git a/crates/oxc_isolated_declarations/tests/snapshots/generator.snap b/crates/oxc_isolated_declarations/tests/snapshots/generator.snap index e6c2845ff49dc..7d4fa47293246 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/generator.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/generator.snap @@ -5,10 +5,13 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/generator.ts ``` ==================== .D.TS ==================== +// Correct declare function generatorGood(): Generator; declare class GeneratorClassGood { method(): Generator; } +// Need to explicit return type for async functions +// Incorrect declare function generatorBad(); declare class GeneratorClassBad { method(); diff --git a/crates/oxc_isolated_declarations/tests/snapshots/infer-expression.snap b/crates/oxc_isolated_declarations/tests/snapshots/infer-expression.snap index 3c344b8f6522f..33cc7b0957387 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/infer-expression.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/infer-expression.snap @@ -5,12 +5,17 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/infer-expression.ts ``` ==================== .D.TS ==================== +// Correct +// ParenthesizedExpression declare const n: number; declare const s: string; declare const t: string; declare const b: boolean; +// UnaryExpression declare let unaryA: number; declare const unaryB = -12n; +// Incorrect +// UnaryExpression declare const unaryC: unknown; declare const unaryD: unknown; declare const unaryE: {}; diff --git a/crates/oxc_isolated_declarations/tests/snapshots/infer-return-type.snap b/crates/oxc_isolated_declarations/tests/snapshots/infer-return-type.snap index 4c3bec1334d98..cadb34224cbf3 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/infer-return-type.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/infer-return-type.snap @@ -6,10 +6,14 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/infer-return-type.ts ==================== .D.TS ==================== declare function foo(): number; +// inferred type is number declare function bar(): number | undefined; +// inferred type is number | undefined declare function baz(); +// We can't infer return type if there are multiple return statements with different types declare function qux(): string; declare function quux(): string; +// Inferred type is string declare function returnFunctionOrNothing(): (() => number) | undefined; diff --git a/crates/oxc_isolated_declarations/tests/snapshots/set-get-accessor.snap b/crates/oxc_isolated_declarations/tests/snapshots/set-get-accessor.snap index d01e3a42106f9..0892ab614acc7 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/set-get-accessor.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/set-get-accessor.snap @@ -5,6 +5,7 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/set-get-accessor.ts ``` ==================== .D.TS ==================== +// Correct declare class Cls { get a(): number; set a(value); @@ -16,6 +17,7 @@ declare class Cls { private accessor e; private static accessor f; } +// Incorrect declare class ClsBad { get a(); set a(v); diff --git a/crates/oxc_isolated_declarations/tests/snapshots/signatures.snap b/crates/oxc_isolated_declarations/tests/snapshots/signatures.snap index 2bed4c93bd4a2..723ee1fd341d7 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/signatures.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/signatures.snap @@ -24,6 +24,7 @@ export interface I { set value(_: string); get value(): string; } +// Do nothing export interface Ref< T = any, S = T diff --git a/crates/oxc_language_server/CHANGELOG.md b/crates/oxc_language_server/CHANGELOG.md index 788274fc94f7a..72e7443bfb7b2 100644 --- a/crates/oxc_language_server/CHANGELOG.md +++ b/crates/oxc_language_server/CHANGELOG.md @@ -4,6 +4,24 @@ 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/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.16.11] - 2025-05-16 + +### Features + +- 078bf0b language_server: Better fallback handling when passing invalid `Options` values (#10930) (Sysix) +- be7f7e1 language_server/editor: Support multi workspace folders (#10875) (Sysix) + +### Bug Fixes + +- 89cc21b language_server: Normalize oxlintrc config path (#10982) (Sysix) +- 39063ce linter: Reword diagnostic message for no-control-regex (#10993) (camc314) + +### Refactor + +- 3cc1466 language_server: New configuration structure for `initialize` and `workspace/didChangeConfiguration` (#10890) (Sysix) +- bd2ef7d language_server: Use `Arc` for `diagnostic_report_map` (#10940) (Sysix) +- bb999a3 language_server: Avoid cloning linter by taking reference in LintService (#10907) (Ulrich Stark) + ## [0.16.10] - 2025-05-09 ### Features diff --git a/crates/oxc_language_server/Cargo.toml b/crates/oxc_language_server/Cargo.toml index e529d11bdfe81..67c39d43013be 100644 --- a/crates/oxc_language_server/Cargo.toml +++ b/crates/oxc_language_server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_language_server" -version = "0.16.10" +version = "0.16.11" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_language_server/README.md b/crates/oxc_language_server/README.md index bf3885d19709d..201823ee43a26 100644 --- a/crates/oxc_language_server/README.md +++ b/crates/oxc_language_server/README.md @@ -15,12 +15,9 @@ This crate provides an [LSP](https://microsoft.github.io/language-server-protoco - `source.fixAll.oxc`, behaves the same as `quickfix` only used when the `CodeActionContext#only` contains `source.fixAll.oxc`. -## Supported LSP Specifications from Server +## Workspace Options -### [initialize](https://microsoft.github.io/language-server-protocol/specification#initialize) - -Returns the [Server Capabilities](#server-capabilities).\ -Initialization Options: +These options can be passed with [initialize](#initialize), [workspace/didChangeConfiguration](#workspace/didChangeConfiguration) and [workspace/configuration](#workspace/configuration). | Option Key | Value(s) | Default | Description | | ------------ | ---------------------- | ---------- | ---------------------------------------------------------------------------------------------------- | @@ -28,6 +25,26 @@ Initialization Options: | `configPath` | `` \| `null` | `null` | Path to a oxlint configuration file, passing a string will disable nested configuration | | `flags` | `Map` | `` | Special oxc language server flags, currently only one flag key is supported: `disable_nested_config` | +## Supported LSP Specifications from Server + +### [initialize](https://microsoft.github.io/language-server-protocol/specification#initialize) + +Returns the [Server Capabilities](#server-capabilities).\ +The client can pass the workspace options like following: + +```json +{ + "initializationOptions": [{ + "workspaceUri": "file://workspace-directory", + "options": { + "run": "onType", + "configPath": null, + "flags": {} + } + }] +} +``` + #### Flags - `key: disable_nested_config`: Disabled nested configuration and searches only for `configPath` @@ -35,6 +52,8 @@ Initialization Options: ### [initialized](https://microsoft.github.io/language-server-protocol/specification#initialized) +When the client did not pass the workspace configuration in [initialize](#initialize), the server will request the configuration for every workspace with [workspace/configuration](#workspace/configuration). + ### [shutdown](https://microsoft.github.io/language-server-protocol/specification#shutdown) The server will reset the diagnostics for all open files and send one or more [textDocument/publishDiagnostics](#textdocumentpublishdiagnostics) requests to the client. @@ -43,7 +62,22 @@ The server will reset the diagnostics for all open files and send one or more [t #### [workspace/didChangeConfiguration](https://microsoft.github.io/language-server-protocol/specification#workspace_didChangeConfiguration) -The server expects this request when settings like `run`, `flags` or `configPath` are changed. +The client can pass the workspace options like following: + +```json +{ + "settings": [{ + "workspaceUri": "file://workspace-directory", + "options": { + "run": "onType", + "configPath": null, + "flags": {} + } + }] +} +``` + +When the client does not pass workspace options, the server will request them with [workspace/configuration](#workspace/configuration). The server will revalidate or reset the diagnostics for all open files and send one or more [textDocument/publishDiagnostics](#textdocumentpublishdiagnostics) requests to the client. #### [workspace/didChangeWatchedFiles](https://microsoft.github.io/language-server-protocol/specification#workspace_didChangeWatchedFiles) @@ -53,6 +87,11 @@ The server will revalidate the diagnostics for all open files and send one or mo Note: When nested configuration is active, the client should send all `.oxlintrc.json` configurations to the server after the [initialized](#initialized) response. +#### [workspace/didChangeWorkspaceFolders](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_didChangeWorkspaceFolders) + +The server expects this request when adding or removing workspace folders. +The server will request the specific workspace configuration, if the client supports it. + #### [workspace/executeCommand](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_executeCommand) Executes a [Command](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_executeCommand) if it exists. See [Server Capabilities](#server-capabilities) @@ -86,3 +125,24 @@ Returns a list of [CodeAction](https://microsoft.github.io/language-server-proto #### [textDocument/publishDiagnostics](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics) Returns a [PublishDiagnostic object](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#publishDiagnosticsParams) + +# Optional LSP Specifications from Client + +### Workspace + +#### [workspace/configuration](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_configuration) + +The server will request workspace configurations. The server expects the received items to match the order of the requested items. +Only will be requested when the `ClientCapabilities` has `workspace.configuration` set to true. + +The client can return a response like: + +```json +{ + [{ + "run": "onType", + "configPath": null, + "flags": {} + }] +} +``` diff --git a/crates/oxc_language_server/src/linter/isolated_lint_handler.rs b/crates/oxc_language_server/src/linter/isolated_lint_handler.rs index 92f2c1cf19754..0bc2ebeb27a69 100644 --- a/crates/oxc_language_server/src/linter/isolated_lint_handler.rs +++ b/crates/oxc_language_server/src/linter/isolated_lint_handler.rs @@ -130,16 +130,16 @@ impl IsolatedLintHandler { } let source_text = source_text.or_else(|| read_to_string(path).ok())?; - debug!("lint {path:?}"); + debug!("lint {}", path.display()); let lint_service_options = LintServiceOptions::new( self.options.root_path.clone(), vec![Arc::from(path.as_os_str())], ) .with_cross_module(self.options.use_cross_module); - // ToDo: do not clone the linter + let mut lint_service = - LintService::new(self.linter.clone(), lint_service_options).with_file_system(Box::new( + LintService::new(&self.linter, lint_service_options).with_file_system(Box::new( IsolatedLintHandlerFileSystem::new(path.to_path_buf(), source_text), )); let result = lint_service.run_source(allocator); diff --git a/crates/oxc_language_server/src/linter/server_linter.rs b/crates/oxc_language_server/src/linter/server_linter.rs index 8e984e4c3f34f..8e7127ee3ceae 100644 --- a/crates/oxc_language_server/src/linter/server_linter.rs +++ b/crates/oxc_language_server/src/linter/server_linter.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use std::path::{Component, Path, PathBuf}; use std::sync::Arc; use globset::Glob; @@ -28,8 +28,8 @@ impl ServerLinter { let nested_configs = Self::create_nested_configs(root_uri, options); let root_path = root_uri.to_file_path().unwrap(); let relative_config_path = options.config_path.clone(); - let oxlintrc = if relative_config_path.is_some() { - let config = root_path.join(relative_config_path.unwrap()); + let oxlintrc = if let Some(relative_config_path) = relative_config_path { + let config = normalize_path(root_path.join(relative_config_path)); if config.try_exists().is_ok_and(|exists| exists) { if let Ok(oxlintrc) = Oxlintrc::from_file(&config) { oxlintrc @@ -206,19 +206,55 @@ impl ServerLinter { } } +/// Normalize a path by removing `.` and resolving `..` components, +/// without touching the filesystem. +pub fn normalize_path>(path: P) -> PathBuf { + let mut result = PathBuf::new(); + + for component in path.as_ref().components() { + match component { + Component::ParentDir => { + result.pop(); + } + Component::CurDir => { + // Skip current directory component + } + Component::Normal(c) => { + result.push(c); + } + Component::RootDir | Component::Prefix(_) => { + result.push(component.as_os_str()); + } + } + } + + result +} + #[cfg(test)] mod test { - use std::{path::PathBuf, str::FromStr}; + use std::{ + path::{Path, PathBuf}, + str::FromStr, + }; use rustc_hash::FxHashMap; use tower_lsp_server::lsp_types::Uri; use crate::{ Options, - linter::server_linter::ServerLinter, + linter::server_linter::{ServerLinter, normalize_path}, tester::{Tester, get_file_uri}, }; + #[test] + fn test_normalize_path() { + assert_eq!( + normalize_path(Path::new("/root/directory/./.oxlintrc.json")), + Path::new("/root/directory/.oxlintrc.json") + ); + } + #[test] fn test_create_nested_configs_with_disabled_nested_configs() { let mut flags = FxHashMap::default(); diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index 45eed5ca4f04f..37f79452981b2 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -1,9 +1,8 @@ use futures::future::join_all; use log::{debug, info, warn}; -use oxc_linter::FixKind; -use rustc_hash::{FxBuildHasher, FxHashMap}; -use serde::{Deserialize, Serialize}; -use std::{fmt::Debug, str::FromStr}; +use options::{Options, Run, WorkspaceOption}; +use rustc_hash::FxBuildHasher; +use std::str::FromStr; use tokio::sync::{Mutex, OnceCell, SetError}; use tower_lsp_server::{ Client, LanguageServer, LspService, Server, @@ -11,9 +10,9 @@ use tower_lsp_server::{ lsp_types::{ CodeActionParams, CodeActionResponse, ConfigurationItem, Diagnostic, DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams, - DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, - ExecuteCommandParams, InitializeParams, InitializeResult, InitializedParams, ServerInfo, - Uri, WorkspaceEdit, + DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, + DidSaveTextDocumentParams, ExecuteCommandParams, InitializeParams, InitializeResult, + InitializedParams, ServerInfo, Uri, WorkspaceEdit, }, }; // # @@ -26,6 +25,7 @@ mod capabilities; mod code_actions; mod commands; mod linter; +mod options; #[cfg(test)] mod tester; mod worker; @@ -44,70 +44,76 @@ struct Backend { capabilities: OnceCell, } -#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy)] -#[serde(rename_all = "camelCase")] -pub enum Run { - OnSave, - #[default] - OnType, -} -#[derive(Debug, Default, Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -struct Options { - run: Run, - config_path: Option, - flags: FxHashMap, -} - -impl Options { - fn use_nested_configs(&self) -> bool { - !self.flags.contains_key("disable_nested_config") || self.config_path.is_some() - } - - fn fix_kind(&self) -> FixKind { - self.flags.get("fix_kind").map_or(FixKind::SafeFix, |kind| match kind.as_str() { - "safe_fix" => FixKind::SafeFix, - "safe_fix_or_suggestion" => FixKind::SafeFixOrSuggestion, - "dangerous_fix" => FixKind::DangerousFix, - "dangerous_fix_or_suggestion" => FixKind::DangerousFixOrSuggestion, - "none" => FixKind::None, - "all" => FixKind::All, - _ => { - info!("invalid fix_kind flag `{kind}`, fallback to `safe_fix`"); - FixKind::SafeFix - } - }) - } -} - impl LanguageServer for Backend { - #[expect(deprecated)] // TODO: FIXME + #[expect(deprecated)] // `params.root_uri` is deprecated, we are only falling back to it if no workspace folder is provided async fn initialize(&self, params: InitializeParams) -> Result { let server_version = env!("CARGO_PKG_VERSION"); + // initialization_options can be anything, so we are requesting `workspace/configuration` when no initialize options are provided let options = params.initialization_options.and_then(|mut value| { - let settings = value.get_mut("settings")?.take(); - serde_json::from_value::(settings).ok() + // the client supports the new settings object + if let Ok(new_settings) = serde_json::from_value::>(value.clone()) + { + // ToDo: validate they have the same length as params.workspace_folders + return Some(new_settings); + } + + let deprecated_settings = Options::try_from(value.get_mut("settings")?.take()).ok(); + + // the client has deprecated settings and has a deprecated root uri. + // handle all things like the old way + if deprecated_settings.is_some() && params.root_uri.is_some() { + return Some(vec![WorkspaceOption { + workspace_uri: params.root_uri.clone().unwrap(), + options: deprecated_settings.unwrap(), + }]); + } + + // no workspace options could be generated fallback to default one or request when possible + None }); + + info!("initialize: {options:?}"); + info!("language server version: {server_version}"); + let capabilities = Capabilities::from(params.capabilities); - // ToDo: add support for multiple workspace folders - // maybe fallback when the client does not support it - let root_worker = WorkspaceWorker::new(params.root_uri.unwrap()); + // client sent workspace folders + let workers = if let Some(workspace_folders) = ¶ms.workspace_folders { + workspace_folders + .iter() + .map(|workspace_folder| WorkspaceWorker::new(workspace_folder.uri.clone())) + .collect() + // client sent deprecated root uri + } else if let Some(root_uri) = params.root_uri { + vec![WorkspaceWorker::new(root_uri)] + // client is in single file mode, create no workers + } else { + vec![] + }; // When the client did not send our custom `initialization_options`, // or the client does not support `workspace/configuration` request, // start the linter. We do not start the linter when the client support the request, // we will init the linter after requesting for the workspace configuration. if !capabilities.workspace_configuration || options.is_some() { - root_worker.init_linter(&options.clone().unwrap_or_default()).await; + for worker in &workers { + worker + .init_linter( + &options + .clone() + .unwrap_or_default() + .iter() + .find(|workspace_option| { + worker.is_responsible_for_uri(&workspace_option.workspace_uri) + }) + .map(|workspace_options| workspace_options.options.clone()) + .unwrap_or_default(), + ) + .await; + } } - *self.workspace_workers.lock().await = vec![root_worker]; - - if let Some(value) = options { - info!("initialize: {value:?}"); - info!("language server version: {server_version}"); - } + *self.workspace_workers.lock().await = workers; self.capabilities.set(capabilities.clone()).map_err(|err| { let message = match err { @@ -176,12 +182,38 @@ impl LanguageServer for Backend { let workers = self.workspace_workers.lock().await; let new_diagnostics: papaya::HashMap, FxBuildHasher> = ConcurrentHashMap::default(); - let options = serde_json::from_value::(params.settings).ok(); + + // new valid configuration is passed + let options = serde_json::from_value::>(params.settings.clone()) + .ok() + .or_else(|| { + // fallback to old configuration + let options = serde_json::from_value::(params.settings).ok()?; + + // for all workers (default only one) + let options = workers + .iter() + .map(|worker| WorkspaceOption { + workspace_uri: worker.get_root_uri().clone(), + options: options.clone(), + }) + .collect(); + + Some(options) + }); // the client passed valid options. if let Some(options) = options { - for worker in workers.iter() { - let Some(diagnostics) = worker.did_change_configuration(&options).await else { + for option in options { + let Some(worker) = workers + .iter() + .find(|worker| worker.is_responsible_for_uri(&option.workspace_uri)) + else { + continue; + }; + + let Some(diagnostics) = worker.did_change_configuration(&option.options).await + else { continue; }; @@ -280,6 +312,51 @@ impl LanguageServer for Backend { self.publish_all_diagnostics(x).await; } + async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) { + let mut workers = self.workspace_workers.lock().await; + let mut cleared_diagnostics = vec![]; + + for folder in params.event.removed { + let Some((index, worker)) = workers + .iter() + .enumerate() + .find(|(_, worker)| worker.is_responsible_for_uri(&folder.uri)) + else { + continue; + }; + cleared_diagnostics.extend(worker.get_clear_diagnostics()); + workers.remove(index); + } + + self.publish_all_diagnostics(&cleared_diagnostics).await; + + // client support `workspace/configuration` request + if self.capabilities.get().is_some_and(|capabilities| capabilities.workspace_configuration) + { + let configurations = self + .request_workspace_configuration( + params.event.added.iter().map(|w| &w.uri).collect(), + ) + .await; + + for (index, folder) in params.event.added.iter().enumerate() { + let worker = WorkspaceWorker::new(folder.uri.clone()); + // get the configuration from the response and init the linter + let options = configurations.get(index).unwrap_or(&None); + worker.init_linter(options.as_ref().unwrap_or(&Options::default())).await; + workers.push(worker); + } + // client does not support the request + } else { + for folder in params.event.added { + let worker = WorkspaceWorker::new(folder.uri); + // use default options + worker.init_linter(&Options::default()).await; + workers.push(worker); + } + } + } + async fn did_save(&self, params: DidSaveTextDocumentParams) { debug!("oxc server did save"); let uri = ¶ms.text_document.uri; @@ -349,7 +426,7 @@ impl LanguageServer for Backend { let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else { return; }; - worker.remove_diagnostics(¶ms.text_document.uri).await; + worker.remove_diagnostics(¶ms.text_document.uri); } async fn code_action(&self, params: CodeActionParams) -> Result> { @@ -448,7 +525,7 @@ impl Backend { async fn clear_all_diagnostics(&self) { let mut cleared_diagnostics = vec![]; for worker in self.workspace_workers.lock().await.iter() { - cleared_diagnostics.extend(worker.get_clear_diagnostics().await); + cleared_diagnostics.extend(worker.get_clear_diagnostics()); } self.publish_all_diagnostics(&cleared_diagnostics).await; } diff --git a/crates/oxc_language_server/src/options.rs b/crates/oxc_language_server/src/options.rs new file mode 100644 index 0000000000000..eeedce6266a1c --- /dev/null +++ b/crates/oxc_language_server/src/options.rs @@ -0,0 +1,181 @@ +use log::info; +use oxc_linter::FixKind; +use rustc_hash::{FxBuildHasher, FxHashMap}; +use serde::{Deserialize, Deserializer, Serialize, de::Error}; +use serde_json::Value; +use tower_lsp_server::lsp_types::Uri; + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy)] +#[serde(rename_all = "camelCase")] +pub enum Run { + OnSave, + #[default] + OnType, +} + +#[derive(Debug, Default, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Options { + pub run: Run, + pub config_path: Option, + pub flags: FxHashMap, +} + +impl Options { + pub fn use_nested_configs(&self) -> bool { + !self.flags.contains_key("disable_nested_config") || self.config_path.is_some() + } + + pub fn fix_kind(&self) -> FixKind { + self.flags.get("fix_kind").map_or(FixKind::SafeFix, |kind| match kind.as_str() { + "safe_fix" => FixKind::SafeFix, + "safe_fix_or_suggestion" => FixKind::SafeFixOrSuggestion, + "dangerous_fix" => FixKind::DangerousFix, + "dangerous_fix_or_suggestion" => FixKind::DangerousFixOrSuggestion, + "none" => FixKind::None, + "all" => FixKind::All, + _ => { + info!("invalid fix_kind flag `{kind}`, fallback to `safe_fix`"); + FixKind::SafeFix + } + }) + } +} + +impl<'de> Deserialize<'de> for Options { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = Value::deserialize(deserializer)?; + Options::try_from(value).map_err(Error::custom) + } +} + +impl TryFrom for Options { + type Error = String; + + fn try_from(value: Value) -> Result { + let Some(object) = value.as_object() else { + return Err("no object passed".to_string()); + }; + + let mut flags = FxHashMap::with_capacity_and_hasher(2, FxBuildHasher); + if let Some(json_flags) = object.get("flags").and_then(|value| value.as_object()) { + if let Some(disable_nested_config) = + json_flags.get("disable_nested_config").and_then(|value| value.as_str()) + { + flags + .insert("disable_nested_config".to_string(), disable_nested_config.to_string()); + } + + if let Some(fix_kind) = json_flags.get("fix_kind").and_then(|value| value.as_str()) { + flags.insert("fix_kind".to_string(), fix_kind.to_string()); + } + } + + Ok(Self { + run: object + .get("run") + .map(|run| serde_json::from_value::(run.clone()).unwrap_or_default()) + .unwrap_or_default(), + config_path: object + .get("configPath") + .and_then(|config_path| serde_json::from_value::(config_path.clone()).ok()), + flags, + }) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct WorkspaceOption { + pub workspace_uri: Uri, + pub options: Options, +} + +#[cfg(test)] +mod test { + use serde_json::json; + + use super::{Options, Run, WorkspaceOption}; + + #[test] + fn test_valid_options_json() { + let json = json!({ + "run": "onSave", + "configPath": "./custom.json", + "flags": { + "disable_nested_config": "true", + "fix_kind": "dangerous_fix" + } + }); + + let options = Options::try_from(json).unwrap(); + assert_eq!(options.run, Run::OnSave); + assert_eq!(options.config_path, Some("./custom.json".into())); + assert_eq!(options.flags.get("disable_nested_config"), Some(&"true".to_string())); + assert_eq!(options.flags.get("fix_kind"), Some(&"dangerous_fix".to_string())); + } + + #[test] + fn test_empty_options_json() { + let json = json!({}); + + let options = Options::try_from(json).unwrap(); + assert_eq!(options.run, Run::OnType); + assert_eq!(options.config_path, None); + assert!(options.flags.is_empty()); + } + + #[test] + fn test_invalid_options_json() { + let json = json!({ + "run": true, + "configPath": "./custom.json" + }); + + let options = Options::try_from(json).unwrap(); + assert_eq!(options.run, Run::OnType); // fallback + assert_eq!(options.config_path, Some("./custom.json".into())); + assert!(options.flags.is_empty()); + } + + #[test] + fn test_invalid_flags_options_json() { + let json = json!({ + "configPath": "./custom.json", + "flags": { + "disable_nested_config": true, // should be string + "fix_kind": "dangerous_fix" + } + }); + + let options = Options::try_from(json).unwrap(); + assert_eq!(options.run, Run::OnType); // fallback + assert_eq!(options.config_path, Some("./custom.json".into())); + assert_eq!(options.flags.get("disable_nested_config"), None); + assert_eq!(options.flags.get("fix_kind"), Some(&"dangerous_fix".to_string())); + } + + #[test] + fn test_invalid_workspace_options_json() { + let json = json!([{ + "workspaceUri": "file:///root/", + "options": { + "run": true, + "configPath": "./custom.json" + } + }]); + + let workspace = serde_json::from_value::>(json).unwrap(); + + assert_eq!(workspace.len(), 1); + assert_eq!(workspace[0].workspace_uri.path().as_str(), "/root/"); + + let options = &workspace[0].options; + assert_eq!(options.run, Run::OnType); // fallback + assert_eq!(options.config_path, Some("./custom.json".into())); + assert!(options.flags.is_empty()); + } +} diff --git a/crates/oxc_language_server/src/snapshots/fixtures_linter_regexp_feature@index.ts.snap b/crates/oxc_language_server/src/snapshots/fixtures_linter_regexp_feature@index.ts.snap index ae1905f6443f0..d46ad5cba0688 100644 --- a/crates/oxc_language_server/src/snapshots/fixtures_linter_regexp_feature@index.ts.snap +++ b/crates/oxc_language_server/src/snapshots/fixtures_linter_regexp_feature@index.ts.snap @@ -4,7 +4,7 @@ input_file: crates/oxc_language_server/fixtures/linter/regexp_feature/index.ts --- code: "eslint(no-control-regex)" code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-control-regex.html" -message: "Unexpected control character\nhelp: '\\u0000' is not a valid control character." +message: "Unexpected control character\nhelp: '\\u0000' is a control character." range: Range { start: Position { line: 1, character: 13 }, end: Position { line: 1, character: 32 } } related_information[0].message: "" related_information[0].location.uri: "file:///fixtures/linter/regexp_feature/index.ts" diff --git a/crates/oxc_language_server/src/worker.rs b/crates/oxc_language_server/src/worker.rs index 0f57594f87c63..3e916f412531a 100644 --- a/crates/oxc_language_server/src/worker.rs +++ b/crates/oxc_language_server/src/worker.rs @@ -1,4 +1,4 @@ -use std::{str::FromStr, vec}; +use std::{str::FromStr, sync::Arc, vec}; use log::debug; use rustc_hash::FxBuildHasher; @@ -20,7 +20,7 @@ use crate::{ pub struct WorkspaceWorker { root_uri: Uri, server_linter: RwLock>, - diagnostics_report_map: RwLock>>, + diagnostics_report_map: Arc>>, options: Mutex, } @@ -29,7 +29,7 @@ impl WorkspaceWorker { Self { root_uri, server_linter: RwLock::new(None), - diagnostics_report_map: RwLock::new(ConcurrentHashMap::default()), + diagnostics_report_map: Arc::new(ConcurrentHashMap::default()), options: Mutex::new(Options::default()), } } @@ -54,8 +54,8 @@ impl WorkspaceWorker { self.server_linter.read().await.is_none() } - pub async fn remove_diagnostics(&self, uri: &Uri) { - self.diagnostics_report_map.read().await.pin().remove(&uri.to_string()); + pub fn remove_diagnostics(&self, uri: &Uri) { + self.diagnostics_report_map.pin().remove(&uri.to_string()); } async fn refresh_server_linter(&self) { @@ -85,7 +85,7 @@ impl WorkspaceWorker { let diagnostics = self.lint_file_internal(uri, content).await; if let Some(diagnostics) = &diagnostics { - self.update_diagnostics(uri, diagnostics).await; + self.update_diagnostics(uri, diagnostics); } diagnostics @@ -103,17 +103,13 @@ impl WorkspaceWorker { server_linter.run_single(uri, content) } - async fn update_diagnostics(&self, uri: &Uri, diagnostics: &[DiagnosticReport]) { - self.diagnostics_report_map - .read() - .await - .pin() - .insert(uri.to_string(), diagnostics.to_owned()); + fn update_diagnostics(&self, uri: &Uri, diagnostics: &[DiagnosticReport]) { + self.diagnostics_report_map.pin().insert(uri.to_string(), diagnostics.to_owned()); } async fn revalidate_diagnostics(&self) -> ConcurrentHashMap> { let diagnostics_map = ConcurrentHashMap::with_capacity_and_hasher( - self.diagnostics_report_map.read().await.len(), + self.diagnostics_report_map.len(), FxBuildHasher, ); let server_linter = self.server_linter.read().await; @@ -121,22 +117,22 @@ impl WorkspaceWorker { debug!("no server_linter initialized in the worker"); return diagnostics_map; }; - for uri in self.diagnostics_report_map.read().await.pin().keys() { + + for uri in self.diagnostics_report_map.pin_owned().keys() { if let Some(diagnostics) = server_linter.run_single(&Uri::from_str(uri).unwrap(), None) { + self.diagnostics_report_map.pin().insert(uri.clone(), diagnostics.clone()); diagnostics_map.pin().insert(uri.clone(), diagnostics); + } else { + self.diagnostics_report_map.pin().remove(uri); } } - *self.diagnostics_report_map.write().await = diagnostics_map.clone(); - diagnostics_map } - pub async fn get_clear_diagnostics(&self) -> Vec<(String, Vec)> { + pub fn get_clear_diagnostics(&self) -> Vec<(String, Vec)> { self.diagnostics_report_map - .read() - .await .pin() .keys() .map(|uri| (uri.clone(), vec![])) @@ -149,8 +145,7 @@ impl WorkspaceWorker { range: &Range, is_source_fix_all_oxc: bool, ) -> Vec { - let report_map = self.diagnostics_report_map.read().await; - let report_map_ref = report_map.pin_owned(); + let report_map_ref = self.diagnostics_report_map.pin_owned(); let value = match report_map_ref.get(&uri.to_string()) { Some(value) => value, // code actions / commands can be requested without opening the file @@ -190,8 +185,7 @@ impl WorkspaceWorker { } pub async fn get_diagnostic_text_edits(&self, uri: &Uri) -> Vec { - let report_map = self.diagnostics_report_map.read().await; - let report_map_ref = report_map.pin_owned(); + let report_map_ref = self.diagnostics_report_map.pin_owned(); let value = match report_map_ref.get(&uri.to_string()) { Some(value) => value, // code actions / commands can be requested without opening the file diff --git a/crates/oxc_linter/CHANGELOG.md b/crates/oxc_linter/CHANGELOG.md index b3a04c9c71a5b..d49d61363348c 100644 --- a/crates/oxc_linter/CHANGELOG.md +++ b/crates/oxc_linter/CHANGELOG.md @@ -4,6 +4,54 @@ 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/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.16.11] - 2025-05-16 + +- 4e5c73b span: [**BREAKING**] `SourceType::from_path(".js")` return js instead of jsx (#11038) (Boshen) + +### Features + +- eef93b4 linter: Add import/no-unassigned-import (#10970) (yefan) +- cc0112f linter: No-unused-vars add setting for `reportVarsOnlyUsedAsTypes` (#11009) (camc314) +- 17e49c3 linter: Implement configuration and checking loops for `eslint/no_constant_condition` (#10949) (Ulrich Stark) +- 21117ac linter: Implement react/forbid-elements (#10928) (Thomas BOCQUEZ) +- a064082 linter: Add import/consistent-type-specifier-style rule (#10858) (yefan) +- 4733b52 linter/no-extraneous-class: Add conditional fixer (#10798) (DonIsaac) + +### Bug Fixes + +- c52a9ba linter: Fix plugins inside overrides not being applied (#11057) (camc314) +- b12bd48 linter: Fix rule config not being correctly applied (#11055) (camc314) +- 9a368be linter: False negative in no-restriced-imports with `patterns` and side effects (#11027) (camc314) +- 8c2cfbc linter: False negative in no-restricted-imports (#11026) (camc314) +- 8956870 linter: False positive in no-unused-vars (#11002) (camc314) +- 33a60d2 linter: Skip eslint/no-redeclare when running on modules (#11004) (camc314) +- 39063ce linter: Reword diagnostic message for no-control-regex (#10993) (camc314) +- 9eedb58 linter: False positive with negative matches in no-restricted-imports (#10976) (camc314) +- 10e77d7 linter: Improve diagnostics for no-control-regex (#10959) (camc314) +- 82889ae linter/no-extraneous-class: Improve docs, reporting and code refactor (#10797) (DonIsaac) +- 11c34e7 linter/no-img-element: Improve diagnostic and docs (#10908) (DonIsaac) +- 126ae75 semantic: Distinguish class private elements (#11044) (magic-akari) +- 773d0de semantic: Correctly handle nested brackets in jsdoc parsing (#10922) (camc314) +- b215b6c semantic: Dont parse `@` as jsdoc tags inside `[`/`]` (#10919) (camc314) + +### Documentation + +- db6afb9 linter: Improve docs of no-debugger (#11033) (camc314) +- 16541de linter: Improve docs of default-param-last (#11032) (camc314) +- 2c2f3c4 linter: Improve docs of default-case-last (#11031) (camc314) +- 56bb9ce linter: Improve docs of array-callback-return (#11030) (camc314) +- 13dbcc6 linter: Correct docs for default config for no-redeclare (#10995) (camc314) +- a86cbb3 linter: Fix incorrect backticks of fenced code blocks (#10947) (Ulrich Stark) + +### Refactor + +- bb999a3 language_server: Avoid cloning linter by taking reference in LintService (#10907) (Ulrich Stark) +- d1b0c83 linter: Remove overrides index vec (#11058) (camc314) +- 7ad6cf8 linter: Store severity separately, remove `RuleWithSeverity` (#11051) (camchenry) +- e31c361 linter: Remove nested match statements in no-restricted-imports (#10975) (camc314) +- 6ad9d4f linter: Tidy `eslint/func-names` (#10923) (camc314) +- faf0a95 syntax: Rename `NameSpaceModule` to `NamespaceModule` (#10917) (Dunqing) + ## [0.16.10] - 2025-05-09 - ad4fbf4 ast: [**BREAKING**] Simplify `RegExpPattern` (#10834) (overlookmotel) diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index c71f6736d1bda..fd37f28ca898a 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_linter" -version = "0.16.10" +version = "0.16.11" authors.workspace = true categories.workspace = true edition.workspace = true @@ -49,7 +49,6 @@ convert_case = { workspace = true } cow-utils = { workspace = true } fast-glob = { workspace = true } globset = { workspace = true } -ignore = { workspace = true } indexmap = { workspace = true, features = ["rayon"] } itertools = { workspace = true } javascript-globals = { workspace = true } diff --git a/crates/oxc_linter/src/config/config_builder.rs b/crates/oxc_linter/src/config/config_builder.rs index af048f17bb023..9d0c54d65e614 100644 --- a/crates/oxc_linter/src/config/config_builder.rs +++ b/crates/oxc_linter/src/config/config_builder.rs @@ -4,14 +4,13 @@ use std::{ }; use itertools::Itertools; -use rustc_hash::FxHashSet; +use rustc_hash::FxHashMap; use oxc_diagnostics::OxcDiagnostic; use oxc_span::{CompactStr, format_compact_str}; use crate::{ AllowWarnDeny, LintConfig, LintFilter, LintFilterKind, Oxlintrc, RuleCategory, RuleEnum, - RuleWithSeverity, config::{ESLintRule, LintPlugins, OxlintOverrides, OxlintRules, overrides::OxlintOverride}, rules::RULES, }; @@ -20,7 +19,7 @@ use super::Config; #[must_use = "You dropped your builder without building a Linter! Did you mean to call .build()?"] pub struct ConfigStoreBuilder { - pub(super) rules: FxHashSet, + pub(super) rules: FxHashMap, config: LintConfig, overrides: OxlintOverrides, cache: RulesCache, @@ -39,7 +38,7 @@ impl ConfigStoreBuilder { /// You can think of this as `oxlint -A all`. pub fn empty() -> Self { let config = LintConfig::default(); - let rules = FxHashSet::default(); + let rules = FxHashMap::default(); let overrides = OxlintOverrides::default(); let cache = RulesCache::new(config.plugins); @@ -54,15 +53,8 @@ impl ConfigStoreBuilder { let config = LintConfig { plugins: LintPlugins::all(), ..LintConfig::default() }; let overrides = OxlintOverrides::default(); let cache = RulesCache::new(config.plugins); - Self { - rules: RULES - .iter() - .map(|rule| RuleWithSeverity { rule: rule.clone(), severity: AllowWarnDeny::Warn }) - .collect(), - config, - overrides, - cache, - } + let rules = RULES.iter().map(|rule| (rule.clone(), AllowWarnDeny::Warn)).collect(); + Self { rules, config, overrides, cache } } /// Create a [`ConfigStoreBuilder`] from a loaded or manually built [`Oxlintrc`]. @@ -104,7 +96,7 @@ impl ConfigStoreBuilder { let config = LintConfig { plugins, settings, env, globals, path: Some(path) }; let rules = - if start_empty { FxHashSet::default() } else { Self::warn_correctness(plugins) }; + if start_empty { FxHashMap::default() } else { Self::warn_correctness(plugins) }; let cache = RulesCache::new(config.plugins); let mut builder = Self { rules, config, overrides, cache }; @@ -222,12 +214,15 @@ impl ConfigStoreBuilder { } #[cfg(test)] - pub(crate) fn with_rule(mut self, rule: RuleWithSeverity) -> Self { - self.rules.insert(rule); + pub(crate) fn with_rule(mut self, rule: RuleEnum, severity: AllowWarnDeny) -> Self { + self.rules.insert(rule, severity); self } - pub(crate) fn with_rules>(mut self, rules: R) -> Self { + pub(crate) fn with_rules>( + mut self, + rules: R, + ) -> Self { self.rules.extend(rules); self } @@ -263,12 +258,12 @@ impl ConfigStoreBuilder { }, AllowWarnDeny::Allow => match filter { LintFilterKind::Category(category) => { - self.rules.retain(|rule| rule.category() != *category); + self.rules.retain(|rule, _| rule.category() != *category); } LintFilterKind::Rule(plugin, rule) => { - self.rules.retain(|r| r.plugin_name() != plugin || r.name() != rule); + self.rules.retain(|r, _| r.plugin_name() != plugin || r.name() != rule); } - LintFilterKind::Generic(name) => self.rules.retain(|rule| rule.name() != name), + LintFilterKind::Generic(name) => self.rules.retain(|rule, _| rule.name() != name), LintFilterKind::All => self.rules.clear(), }, } @@ -287,14 +282,13 @@ impl ConfigStoreBuilder { // NOTE: we may want to warn users if they're configuring a rule that does not exist. let rules_to_configure = all_rules.iter().filter(query); for rule in rules_to_configure { - match self.rules.take(rule) { - Some(mut existing_rule) => { - existing_rule.severity = severity; - self.rules.insert(existing_rule); - } - _ => { - self.rules.insert(RuleWithSeverity::new(rule.clone(), severity)); - } + // If the rule is already in the list, just update its severity. + // Otherwise, add it to the map. + + if let Some(existing_rule) = self.rules.get_mut(rule) { + *existing_rule = severity; + } else { + self.rules.insert(rule.clone(), severity); } } } @@ -306,17 +300,20 @@ impl ConfigStoreBuilder { // to be taken out. let plugins = self.plugins(); let mut rules = if self.cache.is_stale() { - self.rules.into_iter().filter(|r| plugins.contains(r.plugin_name().into())).collect() + self.rules + .into_iter() + .filter(|(r, _)| plugins.contains(r.plugin_name().into())) + .collect() } else { self.rules.into_iter().collect::>() }; - rules.sort_unstable_by_key(|r| r.id()); + rules.sort_unstable_by_key(|(r, _)| r.id()); Ok(Config::new(rules, self.config, self.overrides)) } /// Warn for all correctness rules in the given set of plugins. - fn warn_correctness(plugins: LintPlugins) -> FxHashSet { + fn warn_correctness(plugins: LintPlugins) -> FxHashMap { RULES .iter() .filter(|rule| { @@ -325,7 +322,7 @@ impl ConfigStoreBuilder { rule.category() == RuleCategory::Correctness && plugins.contains(LintPlugins::from(rule.plugin_name())) }) - .map(|rule| RuleWithSeverity { rule: rule.clone(), severity: AllowWarnDeny::Warn }) + .map(|rule| (rule.clone(), AllowWarnDeny::Warn)) .collect() } @@ -344,13 +341,13 @@ impl ConfigStoreBuilder { let new_rules = self .rules .iter() - .sorted_by_key(|x| (x.plugin_name(), x.name())) - .map(|r: &RuleWithSeverity| ESLintRule { + .sorted_by_key(|(r, _)| (r.plugin_name(), r.name())) + .map(|(r, severity)| ESLintRule { plugin_name: r.plugin_name().to_string(), - rule_name: r.rule.name().to_string(), - severity: r.severity, + rule_name: r.name().to_string(), + severity: *severity, config: rule_name_to_rule - .get(&get_name(r.plugin_name(), r.rule.name())) + .get(&get_name(r.plugin_name(), r.name())) .and_then(|r| r.config.clone()), }) .collect(); @@ -519,10 +516,10 @@ mod test { // populated with all correctness-level ESLint rules at a "warn" severity assert!(!builder.rules.is_empty()); - for rule in &builder.rules { + for (rule, severity) in &builder.rules { assert_eq!(rule.category(), RuleCategory::Correctness); - assert_eq!(rule.severity, AllowWarnDeny::Warn); - let plugin = rule.rule.plugin_name(); + assert_eq!(*severity, AllowWarnDeny::Warn); + let plugin = rule.plugin_name(); let name = rule.name(); assert!( builder.plugins().contains(plugin.into()), @@ -551,9 +548,9 @@ mod test { assert!(!builder.rules.is_empty()); assert_eq!(initial_rule_count, rule_count_after_deny); - for rule in &builder.rules { + for (rule, severity) in &builder.rules { assert_eq!(rule.category(), RuleCategory::Correctness); - assert_eq!(rule.severity, AllowWarnDeny::Deny); + assert_eq!(*severity, AllowWarnDeny::Deny); let plugin = rule.plugin_name(); let name = rule.name(); @@ -579,12 +576,12 @@ mod test { "Changing a single rule from warn to deny should not add a new one, just modify what's already there." ); - let no_const_assign = builder + let (_, severity) = builder .rules .iter() - .find(|r| r.plugin_name() == "eslint" && r.name() == "no-const-assign") + .find(|(r, _)| r.plugin_name() == "eslint" && r.name() == "no-const-assign") .expect("Could not find eslint/no-const-assign after configuring it to 'deny'"); - assert_eq!(no_const_assign.severity, AllowWarnDeny::Deny); + assert_eq!(*severity, AllowWarnDeny::Deny); } } // turn on a rule that isn't configured yet and set it to "warn" @@ -595,15 +592,15 @@ mod test { let filter = LintFilter::new(AllowWarnDeny::Warn, filter_string).unwrap(); let builder = ConfigStoreBuilder::default(); // sanity check: not already turned on - assert!(!builder.rules.iter().any(|r| r.name() == "no-console")); + assert!(!builder.rules.iter().any(|(r, _)| r.name() == "no-console")); let builder = builder.with_filter(&filter); - let no_console = builder + let (_, severity) = builder .rules .iter() - .find(|r| r.plugin_name() == "eslint" && r.name() == "no-console") + .find(|(r, _)| r.plugin_name() == "eslint" && r.name() == "no-console") .expect("Could not find eslint/no-console after configuring it to 'warn'"); - assert_eq!(no_console.severity, AllowWarnDeny::Warn); + assert_eq!(*severity, AllowWarnDeny::Warn); } } @@ -618,11 +615,11 @@ mod test { !builder.rules.is_empty(), "warning on categories after allowing all rules should populate the rules set" ); - for rule in &builder.rules { + for (rule, severity) in &builder.rules { let plugin = rule.plugin_name(); let name = rule.name(); assert_eq!( - rule.severity, + *severity, AllowWarnDeny::Warn, "{plugin}/{name} should have a warning severity" ); @@ -656,7 +653,7 @@ mod test { desired_plugins.set(LintPlugins::TYPESCRIPT, false); let linter = ConfigStoreBuilder::default().with_plugins(desired_plugins).build().unwrap(); - for rule in linter.base.rules.iter() { + for (rule, _) in linter.base.rules.iter() { let name = rule.name(); let plugin = rule.plugin_name(); assert_ne!( @@ -727,7 +724,7 @@ mod test { ) .unwrap(); let builder = ConfigStoreBuilder::from_oxlintrc(false, oxlintrc).unwrap(); - for rule in &builder.rules { + for (rule, severity) in &builder.rules { let name = rule.name(); let plugin = rule.plugin_name(); let category = rule.category(); @@ -735,24 +732,20 @@ mod test { RuleCategory::Correctness => { if name == "no-const-assign" { assert_eq!( - rule.severity, + *severity, AllowWarnDeny::Deny, "no-const-assign should be denied", ); } else { assert_eq!( - rule.severity, + *severity, AllowWarnDeny::Warn, "{plugin}/{name} should be a warning" ); } } RuleCategory::Suspicious => { - assert_eq!( - rule.severity, - AllowWarnDeny::Deny, - "{plugin}/{name} should be denied" - ); + assert_eq!(*severity, AllowWarnDeny::Deny, "{plugin}/{name} should be denied"); } invalid => { panic!("Found rule {plugin}/{name} with an unexpected category {invalid:?}"); @@ -796,25 +789,26 @@ mod test { update_rules_config .rules() .iter() - .any(|r| r.name() == "no-debugger" && r.severity == AllowWarnDeny::Warn) + .any(|(r, severity)| r.name() == "no-debugger" && *severity == AllowWarnDeny::Warn) ); assert!( update_rules_config .rules() .iter() - .any(|r| r.name() == "no-console" && r.severity == AllowWarnDeny::Warn) + .any(|(r, severity)| r.name() == "no-console" && *severity == AllowWarnDeny::Warn) ); assert!( !update_rules_config .rules() .iter() - .any(|r| r.name() == "no-null" && r.severity == AllowWarnDeny::Allow) + .any(|(r, severity)| r.name() == "no-null" && *severity == AllowWarnDeny::Allow) ); assert!( update_rules_config .rules() .iter() - .any(|r| r.name() == "prefer-as-const" && r.severity == AllowWarnDeny::Warn) + .any(|(r, severity)| r.name() == "prefer-as-const" + && *severity == AllowWarnDeny::Warn) ); } @@ -831,7 +825,7 @@ mod test { } "#, ); - assert!(warn_all.rules().iter().all(|r| r.severity == AllowWarnDeny::Warn)); + assert!(warn_all.rules().iter().all(|(_, severity)| *severity == AllowWarnDeny::Warn)); let deny_all = config_store_from_str( r#" @@ -844,7 +838,7 @@ mod test { } "#, ); - assert!(deny_all.rules().iter().all(|r| r.severity == AllowWarnDeny::Deny)); + assert!(deny_all.rules().iter().all(|(_, severity)| *severity == AllowWarnDeny::Deny)); let allow_all = config_store_from_str( r#" @@ -857,7 +851,7 @@ mod test { } "#, ); - assert!(allow_all.rules().iter().all(|r| r.severity == AllowWarnDeny::Allow)); + assert!(allow_all.rules().iter().all(|(_, severity)| *severity == AllowWarnDeny::Allow)); assert_eq!(allow_all.number_of_rules(), 0); let allow_and_override_config = config_store_from_str( @@ -879,19 +873,20 @@ mod test { allow_and_override_config .rules() .iter() - .any(|r| r.name() == "no-var" && r.severity == AllowWarnDeny::Warn) + .any(|(r, severity)| r.name() == "no-var" && *severity == AllowWarnDeny::Warn) ); assert!( allow_and_override_config .rules() .iter() - .any(|r| r.name() == "approx-constant" && r.severity == AllowWarnDeny::Deny) + .any(|(r, severity)| r.name() == "approx-constant" + && *severity == AllowWarnDeny::Deny) ); assert!( allow_and_override_config .rules() .iter() - .any(|r| r.name() == "no-null" && r.severity == AllowWarnDeny::Deny) + .any(|(r, severity)| r.name() == "no-null" && *severity == AllowWarnDeny::Deny) ); } diff --git a/crates/oxc_linter/src/config/config_store.rs b/crates/oxc_linter/src/config/config_store.rs index 111aebc7a47f5..fd9175009ce53 100644 --- a/crates/oxc_linter/src/config/config_store.rs +++ b/crates/oxc_linter/src/config/config_store.rs @@ -3,16 +3,19 @@ use std::{ sync::Arc, }; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashMap; use super::{LintConfig, LintPlugins, overrides::OxlintOverrides}; -use crate::{RuleWithSeverity, rules::RULES}; +use crate::{ + AllowWarnDeny, + rules::{RULES, RuleEnum}, +}; // TODO: support `categories` et. al. in overrides. #[derive(Debug)] pub struct ResolvedLinterState { // TODO: Arc + Vec -> SyncVec? It would save a pointer dereference. - pub rules: Arc<[RuleWithSeverity]>, + pub rules: Arc<[(RuleEnum, AllowWarnDeny)]>, pub config: Arc, } @@ -33,7 +36,7 @@ pub struct Config { impl Config { pub fn new( - rules: Vec, + rules: Vec<(RuleEnum, AllowWarnDeny)>, config: LintConfig, overrides: OxlintOverrides, ) -> Self { @@ -50,7 +53,7 @@ impl Config { self.base.config.plugins } - pub fn rules(&self) -> &Arc<[RuleWithSeverity]> { + pub fn rules(&self) -> &Arc<[(RuleEnum, AllowWarnDeny)]> { &self.base.rules } @@ -85,13 +88,20 @@ impl Config { let mut env = self.base.config.env.clone(); let mut globals = self.base.config.globals.clone(); let mut plugins = self.base.config.plugins; + + for override_config in overrides_to_apply.clone() { + if let Some(override_plugins) = override_config.plugins { + plugins |= override_plugins; + } + } + let mut rules = self .base .rules .iter() - .filter(|rule| plugins.contains(LintPlugins::from(rule.plugin_name()))) + .filter(|(rule, _)| plugins.contains(LintPlugins::from(rule.plugin_name()))) .cloned() - .collect::>(); + .collect::>(); let all_rules = RULES .iter() @@ -104,10 +114,6 @@ impl Config { override_config.rules.override_rules(&mut rules, &all_rules); } - if let Some(override_plugins) = override_config.plugins { - plugins |= override_plugins; - } - if let Some(override_env) = &override_config.env { override_env.override_envs(&mut env); } @@ -153,7 +159,7 @@ impl ConfigStore { self.nested_configs.is_empty().then_some(self.base.base.rules.len()) } - pub fn rules(&self) -> &Arc<[RuleWithSeverity]> { + pub fn rules(&self) -> &Arc<[(RuleEnum, AllowWarnDeny)]> { &self.base.base.rules } @@ -193,7 +199,7 @@ mod test { use super::{ConfigStore, OxlintOverrides}; use crate::{ - AllowWarnDeny, LintPlugins, RuleEnum, RuleWithSeverity, + AllowWarnDeny, LintPlugins, RuleEnum, config::{LintConfig, OxlintEnv, OxlintGlobals, OxlintSettings, config_store::Config}, }; @@ -204,11 +210,8 @@ mod test { } #[expect(clippy::default_trait_access)] - fn no_explicit_any() -> RuleWithSeverity { - RuleWithSeverity::new( - RuleEnum::TypescriptNoExplicitAny(Default::default()), - AllowWarnDeny::Warn, - ) + fn no_explicit_any() -> (RuleEnum, AllowWarnDeny) { + (RuleEnum::TypescriptNoExplicitAny(Default::default()), AllowWarnDeny::Warn) } /// an empty ruleset is a no-op @@ -229,10 +232,7 @@ mod test { assert_eq!(rules_for_source_file.rules.len(), 1); assert_eq!(rules_for_test_file.rules.len(), 1); - assert_eq!( - rules_for_test_file.rules[0].rule.id(), - rules_for_source_file.rules[0].rule.id() - ); + assert_eq!(rules_for_test_file.rules[0].0.id(), rules_for_source_file.rules[0].0.id()); } /// adding plugins but no rules is a no-op @@ -254,10 +254,7 @@ mod test { assert_eq!(rules_for_source_file.rules.len(), 1); assert_eq!(rules_for_test_file.rules.len(), 1); - assert_eq!( - rules_for_test_file.rules[0].rule.id(), - rules_for_source_file.rules[0].rule.id() - ); + assert_eq!(rules_for_test_file.rules[0].0.id(), rules_for_source_file.rules[0].0.id()); } #[test] @@ -324,11 +321,11 @@ mod test { let app = store.resolve("App.tsx".as_ref()).rules; assert_eq!(app.len(), 1); - assert_eq!(app[0].severity, AllowWarnDeny::Warn); + assert_eq!(app[0].1, AllowWarnDeny::Warn); let src_app = store.resolve("src/App.tsx".as_ref()).rules; assert_eq!(src_app.len(), 1); - assert_eq!(src_app[0].severity, AllowWarnDeny::Deny); + assert_eq!(src_app[0].1, AllowWarnDeny::Deny); } #[test] diff --git a/crates/oxc_linter/src/config/mod.rs b/crates/oxc_linter/src/config/mod.rs index 0b0314c9d4bf8..fab4d0472f9f5 100644 --- a/crates/oxc_linter/src/config/mod.rs +++ b/crates/oxc_linter/src/config/mod.rs @@ -50,7 +50,7 @@ mod test { use std::env; use oxc_span::CompactStr; - use rustc_hash::FxHashSet; + use rustc_hash::FxHashMap; use serde::Deserialize; use super::Oxlintrc; @@ -111,10 +111,10 @@ mod test { let fixture_path: std::path::PathBuf = env::current_dir().unwrap().join("fixtures/eslint_config_vitest_replace.json"); let config = Oxlintrc::from_file(&fixture_path).unwrap(); - let mut set = FxHashSet::default(); + let mut set = FxHashMap::default(); config.rules.override_rules(&mut set, &RULES); - let rule = set.into_iter().next().unwrap(); + let (rule, _) = set.into_iter().next().unwrap(); assert_eq!(rule.name(), "no-disabled-tests"); assert_eq!(rule.plugin_name(), "jest"); } diff --git a/crates/oxc_linter/src/config/overrides.rs b/crates/oxc_linter/src/config/overrides.rs index a8abf779f12ff..0761713d268aa 100644 --- a/crates/oxc_linter/src/config/overrides.rs +++ b/crates/oxc_linter/src/config/overrides.rs @@ -4,36 +4,17 @@ use std::{ path::Path, }; -use nonmax::NonMaxU32; use schemars::{JsonSchema, r#gen, schema::Schema}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; -use oxc_index::{Idx, IndexVec}; - use crate::{LintPlugins, OxlintEnv, OxlintGlobals, config::OxlintRules}; -#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct OverrideId(NonMaxU32); - -impl Idx for OverrideId { - #[expect(clippy::cast_possible_truncation)] - fn from_usize(idx: usize) -> Self { - assert!(idx < u32::MAX as usize); - // SAFETY: We just checked `idx` is a legal value for `NonMaxU32` - Self(unsafe { NonMaxU32::new_unchecked(idx as u32) }) - } - - fn index(self) -> usize { - self.0.get() as usize - } -} - // nominal wrapper required to add JsonSchema impl #[derive(Debug, Default, Clone, Deserialize, Serialize)] -pub struct OxlintOverrides(IndexVec); +pub struct OxlintOverrides(Vec); impl Deref for OxlintOverrides { - type Target = IndexVec; + type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 @@ -48,7 +29,7 @@ impl DerefMut for OxlintOverrides { impl IntoIterator for OxlintOverrides { type Item = OxlintOverride; - type IntoIter = as IntoIterator>::IntoIter; + type IntoIter = as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() @@ -58,7 +39,7 @@ impl IntoIterator for OxlintOverrides { impl OxlintOverrides { #[inline] pub fn empty() -> Self { - Self(IndexVec::new()) + Self(Vec::new()) } // must be explicitly defined to make serde happy diff --git a/crates/oxc_linter/src/config/oxlintrc.rs b/crates/oxc_linter/src/config/oxlintrc.rs index 9607d4431281d..51647b5579856 100644 --- a/crates/oxc_linter/src/config/oxlintrc.rs +++ b/crates/oxc_linter/src/config/oxlintrc.rs @@ -110,12 +110,15 @@ impl Oxlintrc { /// * Parse Failure pub fn from_file(path: &Path) -> Result { let mut string = read_to_string(path).map_err(|e| { - OxcDiagnostic::error(format!("Failed to parse config {path:?} with error {e:?}")) + OxcDiagnostic::error(format!( + "Failed to parse config {} with error {e:?}", + path.display() + )) })?; // jsonc support json_strip_comments::strip(&mut string).map_err(|err| { - OxcDiagnostic::error(format!("Failed to parse jsonc file {path:?}: {err:?}")) + OxcDiagnostic::error(format!("Failed to parse jsonc file {}: {err:?}", path.display())) })?; let json = serde_json::from_str::(&string).map_err(|err| { @@ -130,7 +133,10 @@ impl Oxlintrc { ) } }; - OxcDiagnostic::error(format!("Failed to parse eslint config {path:?}.\n{err}")) + OxcDiagnostic::error(format!( + "Failed to parse eslint config {}.\n{err}", + path.display() + )) })?; let mut config = Self::deserialize(&json).map_err(|err| { diff --git a/crates/oxc_linter/src/config/rules.rs b/crates/oxc_linter/src/config/rules.rs index 538b5aa61278b..bf37c6a77fc85 100644 --- a/crates/oxc_linter/src/config/rules.rs +++ b/crates/oxc_linter/src/config/rules.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, fmt}; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashMap; use schemars::{JsonSchema, r#gen::SchemaGenerator, schema::Schema}; use serde::{ Deserialize, Serialize, Serializer, @@ -11,12 +11,12 @@ use serde::{ use oxc_diagnostics::{Error, OxcDiagnostic}; use crate::{ - AllowWarnDeny, RuleWithSeverity, + AllowWarnDeny, rules::{RULES, RuleEnum}, utils::{is_eslint_rule_adapted_to_typescript, is_jest_rule_adapted_to_vitest}, }; -type RuleSet = FxHashSet; +type RuleSet = FxHashMap; // TS type is `Record` // - type SeverityConf = 0 | 1 | 2 | "off" | "warn" | "error"; @@ -60,8 +60,8 @@ pub struct ESLintRule { impl OxlintRules { pub(crate) fn override_rules(&self, rules_for_override: &mut RuleSet, all_rules: &[RuleEnum]) { use itertools::Itertools; - let mut rules_to_replace: Vec = vec![]; - let mut rules_to_remove: Vec = vec![]; + let mut rules_to_replace: Vec<(RuleEnum, AllowWarnDeny)> = vec![]; + let mut rules_to_remove: Vec = vec![]; // Rules can have the same name but different plugin names let lookup = self.rules.iter().into_group_map_by(|r| r.rule_name.as_str()); @@ -84,16 +84,14 @@ impl OxlintRules { { let config = rule_config.config.clone().unwrap_or_default(); let rule = rule.read_json(config); - rules_to_replace.push(RuleWithSeverity::new(rule, severity)); + rules_to_replace.push((rule, severity)); } } AllowWarnDeny::Allow => { - if let Some(rule) = rules_for_override - .iter() - .find(|r| r.name() == rule_name && r.plugin_name() == plugin_name) - { - let rule = rule.clone(); - rules_to_remove.push(rule); + if let Some((rule, _)) = rules_for_override.iter().find(|(r, _)| { + r.name() == rule_name && r.plugin_name() == plugin_name + }) { + rules_to_remove.push(rule.clone()); } // If the given rule is not found in the rule list (for example, if all rules are disabled), // then look it up in the entire rules list and add it. @@ -103,7 +101,7 @@ impl OxlintRules { { let config = rule_config.config.clone().unwrap_or_default(); let rule = rule.read_json(config); - rules_to_remove.push(RuleWithSeverity::new(rule, severity)); + rules_to_remove.push(rule); } } } @@ -111,7 +109,7 @@ impl OxlintRules { _ => { let rules = rules_for_override .iter() - .filter_map(|r| { + .filter_map(|(r, _)| { if r.name() == *name { Some((r.plugin_name(), r)) } else { None } }) .collect::>(); @@ -125,10 +123,8 @@ impl OxlintRules { if rule_config.severity.is_warn_deny() { let config = rule_config.config.clone().unwrap_or_default(); if let Some(rule) = rules.get(&plugin_name) { - rules_to_replace.push(RuleWithSeverity::new( - rule.read_json(config), - rule_config.severity, - )); + rules_to_replace + .push((rule.read_json(config), rule_config.severity)); } // If the given rule is not found in the rule list (for example, if all rules are disabled), // then look it up in the entire rules list and add it. @@ -136,10 +132,8 @@ impl OxlintRules { .iter() .find(|r| r.name() == rule_name && r.plugin_name() == plugin_name) { - rules_to_replace.push(RuleWithSeverity::new( - rule.read_json(config), - rule_config.severity, - )); + rules_to_replace + .push((rule.read_json(config), rule_config.severity)); } } else if let Some(&rule) = rules.get(&plugin_name) { rules_to_remove.push(rule.clone()); @@ -152,8 +146,9 @@ impl OxlintRules { for rule in rules_to_remove { rules_for_override.remove(&rule); } - for rule in rules_to_replace { - rules_for_override.replace(rule); + for (rule, severity) in rules_to_replace { + let _ = rules_for_override.remove(&rule); + rules_for_override.insert(rule, severity); } } } @@ -354,7 +349,7 @@ mod test { use serde_json::{Value, json}; use crate::{ - AllowWarnDeny, RuleWithSeverity, + AllowWarnDeny, rules::{RULES, RuleEnum}, }; @@ -417,9 +412,9 @@ mod test { r#override(&mut rules, &config); assert_eq!(rules.len(), 1, "{config:?}"); - let rule = rules.iter().next().unwrap(); + let (rule, severity) = rules.iter().next().unwrap(); assert_eq!(rule.name(), "no-console", "{config:?}"); - assert_eq!(rule.severity, AllowWarnDeny::Deny, "{config:?}"); + assert_eq!(severity, &AllowWarnDeny::Deny, "{config:?}"); } } @@ -440,15 +435,15 @@ mod test { 1, "eslint rules should be configurable by their typescript-eslint reimplementations: {config:?}" ); - let rule = rules.iter().next().unwrap(); + let (rule, severity) = rules.iter().next().unwrap(); assert_eq!( rule.name(), "no-console", "eslint rules should be configurable by their typescript-eslint reimplementations: {config:?}" ); assert_eq!( - rule.severity, - AllowWarnDeny::Deny, + severity, + &AllowWarnDeny::Deny, "eslint rules should be configurable by their typescript-eslint reimplementations: {config:?}" ); } @@ -456,10 +451,7 @@ mod test { #[test] fn test_override_allow() { let mut rules = RuleSet::default(); - rules.insert(RuleWithSeverity { - rule: RuleEnum::EslintNoConsole(Default::default()), - severity: AllowWarnDeny::Deny, - }); + rules.insert(RuleEnum::EslintNoConsole(Default::default()), AllowWarnDeny::Deny); r#override(&mut rules, &json!({ "eslint/no-console": "off" })); assert!(rules.is_empty()); @@ -478,23 +470,20 @@ mod test { r#override(&mut rules, config); assert_eq!(rules.len(), 1, "{config:?}"); - let rule = rules.iter().next().unwrap(); + let (rule, severity) = rules.iter().next().unwrap(); assert_eq!(rule.name(), "no-unused-vars", "{config:?}"); - assert_eq!(rule.severity, AllowWarnDeny::Deny, "{config:?}"); + assert_eq!(severity, &AllowWarnDeny::Deny, "{config:?}"); } for config in &configs { let mut rules = RuleSet::default(); - rules.insert(RuleWithSeverity { - rule: RuleEnum::EslintNoUnusedVars(Default::default()), - severity: AllowWarnDeny::Warn, - }); + rules.insert(RuleEnum::EslintNoUnusedVars(Default::default()), AllowWarnDeny::Warn); r#override(&mut rules, config); assert_eq!(rules.len(), 1, "{config:?}"); - let rule = rules.iter().next().unwrap(); + let (rule, severity) = rules.iter().next().unwrap(); assert_eq!(rule.name(), "no-unused-vars", "{config:?}"); - assert_eq!(rule.severity, AllowWarnDeny::Deny, "{config:?}"); + assert_eq!(severity, &AllowWarnDeny::Deny, "{config:?}"); } } } diff --git a/crates/oxc_linter/src/context/host.rs b/crates/oxc_linter/src/context/host.rs index c9bb43749d974..50c789797289b 100644 --- a/crates/oxc_linter/src/context/host.rs +++ b/crates/oxc_linter/src/context/host.rs @@ -5,13 +5,14 @@ use oxc_semantic::Semantic; use oxc_span::{SourceType, Span}; use crate::{ - FrameworkFlags, RuleWithSeverity, + AllowWarnDeny, FrameworkFlags, config::{LintConfig, LintPlugins}, disable_directives::{DisableDirectives, DisableDirectivesBuilder}, fixer::{FixKind, Message}, frameworks, module_record::ModuleRecord, options::LintOptions, + rules::RuleEnum, }; use super::{LintContext, plugin_name_to_prefix}; @@ -226,7 +227,7 @@ impl<'a> ContextHost<'a> { } /// Creates a new [`LintContext`] for a specific rule. - pub fn spawn(self: Rc, rule: &RuleWithSeverity) -> LintContext<'a> { + pub fn spawn(self: Rc, rule: &RuleEnum, severity: AllowWarnDeny) -> LintContext<'a> { let rule_name = rule.name(); let plugin_name = rule.plugin_name(); @@ -236,8 +237,8 @@ impl<'a> ContextHost<'a> { current_plugin_name: plugin_name, current_plugin_prefix: plugin_name_to_prefix(plugin_name), #[cfg(debug_assertions)] - current_rule_fix_capabilities: rule.rule.fix(), - severity: rule.severity.into(), + current_rule_fix_capabilities: rule.fix(), + severity: severity.into(), } } diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index 8030e83fe8d7d..5d053c5e7f272 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -38,7 +38,7 @@ pub use crate::{ module_record::ModuleRecord, options::LintOptions, options::{AllowWarnDeny, InvalidFilterKind, LintFilter, LintFilterKind}, - rule::{RuleCategory, RuleFixMeta, RuleMeta, RuleWithSeverity}, + rule::{RuleCategory, RuleFixMeta, RuleMeta}, service::{LintService, LintServiceOptions, RuntimeFileSystem}, utils::read_to_string, }; @@ -64,7 +64,6 @@ fn size_asserts() { #[derive(Debug, Clone)] pub struct Linter { - // rules: Vec, options: LintOptions, // config: Arc, config: ConfigStore, @@ -112,8 +111,8 @@ impl Linter { let rules = rules .iter() - .filter(|rule| rule.should_run(&ctx_host)) - .map(|rule| (rule, Rc::clone(&ctx_host).spawn(rule))); + .filter(|(rule, _)| rule.should_run(&ctx_host)) + .map(|(rule, severity)| (rule, Rc::clone(&ctx_host).spawn(rule, *severity))); let semantic = ctx_host.semantic(); diff --git a/crates/oxc_linter/src/rule.rs b/crates/oxc_linter/src/rule.rs index cb651f449cca3..a6f993600493f 100644 --- a/crates/oxc_linter/src/rule.rs +++ b/crates/oxc_linter/src/rule.rs @@ -1,16 +1,11 @@ -use std::{ - borrow::{Borrow, Cow}, - fmt, - hash::{Hash, Hasher}, - ops::Deref, -}; +use std::{borrow::Cow, fmt, hash::Hash}; use oxc_semantic::SymbolId; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{ - AllowWarnDeny, AstNode, FixKind, RuleEnum, + AstNode, FixKind, context::{ContextHost, LintContext}, utils::PossibleJestNode, }; @@ -265,46 +260,6 @@ impl From for FixKind { } } -#[derive(Debug, Clone)] -pub struct RuleWithSeverity { - pub rule: RuleEnum, - pub severity: AllowWarnDeny, -} - -impl Hash for RuleWithSeverity { - fn hash(&self, state: &mut H) { - self.rule.hash(state); - } -} - -impl PartialEq for RuleWithSeverity { - fn eq(&self, other: &Self) -> bool { - self.rule == other.rule - } -} - -impl Eq for RuleWithSeverity {} - -impl Deref for RuleWithSeverity { - type Target = RuleEnum; - - fn deref(&self) -> &Self::Target { - &self.rule - } -} - -impl Borrow for RuleWithSeverity { - fn borrow(&self) -> &RuleEnum { - &self.rule - } -} - -impl RuleWithSeverity { - pub fn new(rule: RuleEnum, severity: AllowWarnDeny) -> Self { - Self { rule, severity } - } -} - #[cfg(test)] mod test { use super::RuleCategory; diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index a0e640aba5705..bd8661376bad5 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -7,31 +7,31 @@ /// mod import { - pub mod exports_last; - pub mod group_exports; - pub mod no_absolute_path; - pub mod no_anonymous_default_export; - pub mod no_empty_named_blocks; - pub mod no_mutable_exports; - // pub mod no_deprecated; - // pub mod no_unused_modules; + pub mod consistent_type_specifier_style; pub mod default; pub mod export; + pub mod exports_last; pub mod first; + pub mod group_exports; pub mod max_dependencies; pub mod named; pub mod namespace; + pub mod no_absolute_path; pub mod no_amd; + pub mod no_anonymous_default_export; pub mod no_commonjs; pub mod no_cycle; pub mod no_default_export; pub mod no_duplicates; pub mod no_dynamic_require; + pub mod no_empty_named_blocks; + pub mod no_mutable_exports; pub mod no_named_as_default; pub mod no_named_as_default_member; pub mod no_named_default; pub mod no_namespace; pub mod no_self_import; + pub mod no_unassigned_import; pub mod no_webpack_loader_syntax; pub mod unambiguous; } @@ -284,6 +284,7 @@ mod react { pub mod button_has_type; pub mod checked_requires_onchange_or_readonly; pub mod exhaustive_deps; + pub mod forbid_elements; pub mod forward_ref_uses_ref; pub mod iframe_missing_sandbox; pub mod jsx_boolean_value; @@ -562,8 +563,6 @@ mod node { } oxc_macros::declare_all_lint_rules! { - // import::no_deprecated, - // import::no_unused_modules, eslint::array_callback_return, eslint::block_scoped_var, eslint::curly, @@ -707,11 +706,13 @@ oxc_macros::declare_all_lint_rules! { eslint::valid_typeof, eslint::vars_on_top, eslint::yoda, + import::consistent_type_specifier_style, import::default, import::export, import::exports_last, import::first, import::group_exports, + import::no_unassigned_import, import::no_empty_named_blocks, import::no_anonymous_default_export, import::no_absolute_path, @@ -891,6 +892,7 @@ oxc_macros::declare_all_lint_rules! { react::button_has_type, react::checked_requires_onchange_or_readonly, react::exhaustive_deps, + react::forbid_elements, react::forward_ref_uses_ref, react::iframe_missing_sandbox, react::jsx_filename_extension, 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 a6b712fad1f0c..c15f5d423033f 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 @@ -43,19 +43,34 @@ pub struct ArrayCallbackReturn { declare_oxc_lint!( /// ### What it does + /// /// Enforce return statements in callbacks of array methods /// /// ### Why is this bad? + /// /// Array has several methods for filtering, mapping, and folding. /// If we forget to write return statement in a callback of those, it’s probably a mistake. /// If you don’t want to use a return or don’t need the returned results, /// consider using .forEach instead. /// /// ### Example + /// + /// Examples of **incorrect** code for this rule: + /// + /// ```javascript + /// let foo = [1, 2, 3, 4]; + /// foo.map((a) => { + /// console.log(a) + /// }); + /// ``` + /// + /// Examples of **correct** code for this rule: + /// /// ```javascript /// let foo = [1, 2, 3, 4]; /// foo.map((a) => { /// console.log(a) + /// return a /// }); /// ``` ArrayCallbackReturn, diff --git a/crates/oxc_linter/src/rules/eslint/curly.rs b/crates/oxc_linter/src/rules/eslint/curly.rs index 86c0685ad5436..6c5301fff170c 100644 --- a/crates/oxc_linter/src/rules/eslint/curly.rs +++ b/crates/oxc_linter/src/rules/eslint/curly.rs @@ -249,7 +249,7 @@ fn should_have_braces<'a>( }; let braces_necessary = are_braces_necessary(body, ctx); - let should_block = if is_block && (is_not_single_statement || braces_necessary) { + if is_block && (is_not_single_statement || braces_necessary) { Some(true) } else if options.contains(&CurlyType::Multi) { Some(false) @@ -271,9 +271,7 @@ fn should_have_braces<'a>( }) } else { Some(true) - }; - - should_block + } } fn report_if_needed<'a>( diff --git a/crates/oxc_linter/src/rules/eslint/default_case_last.rs b/crates/oxc_linter/src/rules/eslint/default_case_last.rs index a0d430827028c..3b45ee6901f17 100644 --- a/crates/oxc_linter/src/rules/eslint/default_case_last.rs +++ b/crates/oxc_linter/src/rules/eslint/default_case_last.rs @@ -15,15 +15,20 @@ pub struct DefaultCaseLast; declare_oxc_lint!( /// ### What it does + /// /// Enforce default clauses in switch statements to be last /// /// ### Why is this bad? + /// /// A switch statement can optionally have a default clause. /// If present, it’s usually the last clause, but it doesn’t need to be. It is also allowed to put the default clause before all case clauses, or anywhere between. The behavior is mostly the same as if it was the last clause. The default block will be still executed only if there is no match in the case clauses (including those defined after the default), but there is also the ability to “fall through” from the default clause to the following clause in the list. However, such flow is not common and it would be confusing to the readers. /// Even if there is no “fall through” logic, it’s still unexpected to see the default clause before or between the case clauses. By convention, it is expected to be the last clause. /// If a switch statement should have a default clause, it’s considered a best practice to define it as the last clause. /// /// ### Example + /// + /// Examples of **incorrect** code for this rule: + /// /// ```javascript /// switch (foo) { /// default: @@ -46,6 +51,22 @@ declare_oxc_lint!( /// break; /// } /// ``` + /// + /// Examples of **correct** code for this rule: + /// + /// ```javascript + /// switch (foo) { + /// case 1: + /// bar(); + /// break; + /// case 2: + /// qux(); + /// break; + /// default: + /// baz(); + /// break; + /// } + /// ``` DefaultCaseLast, eslint, style diff --git a/crates/oxc_linter/src/rules/eslint/default_param_last.rs b/crates/oxc_linter/src/rules/eslint/default_param_last.rs index 8bac472be4673..48372684f61f3 100644 --- a/crates/oxc_linter/src/rules/eslint/default_param_last.rs +++ b/crates/oxc_linter/src/rules/eslint/default_param_last.rs @@ -16,21 +16,29 @@ pub struct DefaultParamLast; declare_oxc_lint!( /// ### What it does + /// /// Enforce default parameters to be last /// /// ### Why is this bad? + /// /// Putting default parameter at last allows function calls to omit optional tail arguments. /// /// ### Example - /// ```javascript - /// // Correct: optional argument can be omitted - /// function createUser(id, isAdmin = false) {} - /// createUser("tabby") /// + /// Examples of **incorrect** code for this rule: + /// + /// ```javascript /// // Incorrect: optional argument can **not** be omitted /// function createUser(isAdmin = false, id) {} /// createUser(undefined, "tabby") /// ``` + /// + /// Examples of **correct** code for this rule: + /// + /// ```javascript + /// function createUser(id, isAdmin = false) {} + /// createUser("tabby") + /// ``` DefaultParamLast, eslint, style diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index e42a6e4b6979c..678f251103e3b 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -151,9 +151,7 @@ fn is_object_or_class_method(parent_node: &AstNode) -> bool { match parent_node.kind() { AstKind::MethodDefinition(_) => true, AstKind::ObjectProperty(property) => { - property.method - || property.kind == PropertyKind::Get - || property.kind == PropertyKind::Set + property.method || matches!(property.kind, PropertyKind::Get | PropertyKind::Set) } _ => false, } @@ -169,37 +167,23 @@ fn has_inferred_name<'a>(function: &Function<'a>, parent_node: &AstNode<'a>) -> match parent_node.kind() { AstKind::VariableDeclarator(declarator) => { matches!(declarator.id.kind, BindingPatternKind::BindingIdentifier(_)) - && matches!(declarator.init.as_ref().unwrap(), Expression::FunctionExpression(function_expression) - if get_function_identifier(function_expression) == get_function_identifier(function) - ) - } - AstKind::ObjectProperty(property) => { - matches!(&property.value, Expression::FunctionExpression(function_expression) - if get_function_identifier(function_expression) == get_function_identifier(function) - ) + && declarator.init.as_ref().is_some_and(|init| is_same_function(init, function)) } + AstKind::ObjectProperty(property) => is_same_function(&property.value, function), AstKind::PropertyDefinition(definition) => { - matches!(&definition.value.as_ref().unwrap(), Expression::FunctionExpression(function_expression) - if get_function_identifier(function_expression) == get_function_identifier(function) - ) + definition.value.as_ref().is_some_and(|value| is_same_function(value, function)) } AstKind::AssignmentExpression(expression) => { matches!(expression.left, AssignmentTarget::AssignmentTargetIdentifier(_)) - && matches!(&expression.right, Expression::FunctionExpression(function_expression) - if get_function_identifier(function_expression) == get_function_identifier(function) - ) + && is_same_function(&expression.right, function) } AstKind::AssignmentTargetWithDefault(target) => { matches!(target.binding, AssignmentTarget::AssignmentTargetIdentifier(_)) - && matches!(&target.init, Expression::FunctionExpression(function_expression) - if get_function_identifier(function_expression) == get_function_identifier(function) - ) + && is_same_function(&target.init, function) } AstKind::AssignmentPattern(pattern) => { matches!(pattern.left.kind, BindingPatternKind::BindingIdentifier(_)) - && matches!(&pattern.right, Expression::FunctionExpression(function_expression) - if get_function_identifier(function_expression) == get_function_identifier(function) - ) + && is_same_function(&pattern.right, function) } AstKind::ObjectAssignmentTarget(target) => { for property in &target.properties { @@ -225,6 +209,12 @@ fn has_inferred_name<'a>(function: &Function<'a>, parent_node: &AstNode<'a>) -> } } +fn is_same_function<'a>(fn1: &Expression<'a>, fn2: &Function<'a>) -> bool { + matches!(fn1, Expression::FunctionExpression(function_expression) + if get_function_identifier(function_expression) == get_function_identifier(fn2) + ) +} + /** * Gets the identifier for the function */ @@ -369,15 +359,12 @@ fn guess_function_name<'a>(ctx: &LintContext<'a>, parent_id: NodeId) -> Option { - return prop.key.static_name().and_then(|name| { - if is_valid_identifier_name(&name) { Some(name) } else { None } - }); + return prop.key.static_name().filter(|name| is_valid_identifier_name(name)); } AstKind::PropertyDefinition(prop) => { - return prop.key.static_name().and_then(|name| { - if is_valid_identifier_name(&name) { Some(name) } else { None } - }); + return prop.key.static_name().filter(|name| is_valid_identifier_name(name)); } + _ => return None, } } diff --git a/crates/oxc_linter/src/rules/eslint/no_bitwise.rs b/crates/oxc_linter/src/rules/eslint/no_bitwise.rs index 64064ad67a8ac..c6237dbdd9321 100644 --- a/crates/oxc_linter/src/rules/eslint/no_bitwise.rs +++ b/crates/oxc_linter/src/rules/eslint/no_bitwise.rs @@ -94,7 +94,7 @@ declare_oxc_lint!( /// /// ```javascript /// const b = a|0; - /// `````` + /// ``` NoBitwise, eslint, restriction diff --git a/crates/oxc_linter/src/rules/eslint/no_constant_condition.rs b/crates/oxc_linter/src/rules/eslint/no_constant_condition.rs index f0dbf46af2d42..0a668695a7ae2 100644 --- a/crates/oxc_linter/src/rules/eslint/no_constant_condition.rs +++ b/crates/oxc_linter/src/rules/eslint/no_constant_condition.rs @@ -1,7 +1,8 @@ -use oxc_ast::AstKind; +use oxc_ast::{AstKind, ast::Expression}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; +use serde_json::Value; use crate::{AstNode, ast_util::IsConstant, context::LintContext, rule::Rule}; @@ -11,9 +12,38 @@ fn no_constant_condition_diagnostic(span: Span) -> OxcDiagnostic { .with_label(span) } +#[derive(Debug, Default, Clone, PartialEq)] +enum CheckLoops { + All, + #[default] + AllExceptWhileTrue, + None, +} + +impl CheckLoops { + fn from(value: &Value) -> Option { + match value { + Value::String(str) => match str.as_str() { + "all" => Some(Self::All), + "allExceptWhileTrue" => Some(Self::AllExceptWhileTrue), + "none" => Some(Self::None), + _ => None, + }, + Value::Bool(bool) => { + if *bool { + Some(Self::All) + } else { + Some(Self::None) + } + } + _ => None, + } + } +} + #[derive(Debug, Default, Clone)] pub struct NoConstantCondition { - _check_loops: bool, + check_loops: CheckLoops, } declare_oxc_lint!( @@ -31,73 +61,108 @@ declare_oxc_lint!( /// - `if`, `for`, `while`, or `do...while` statement /// - `?`: ternary expression /// - /// - /// ### Example + /// ### Examples /// /// Examples of **incorrect** code for this rule: /// ```js /// if (false) { - /// doSomethingUnfinished(); + /// doSomethingUnfinished(); /// } /// /// if (new Boolean(x)) { - /// doSomethingAlways(); + /// doSomethingAlways(); /// } /// if (x ||= true) { - /// doSomethingAlways(); + /// doSomethingAlways(); /// } /// /// do { - /// doSomethingForever(); + /// doSomethingForever(); /// } while (x = -1); /// ``` /// /// Examples of **correct** code for this rule: /// ```js /// if (x === 0) { - /// doSomething(); + /// doSomething(); /// } /// /// while (typeof x === "undefined") { - /// doSomething(); + /// doSomething(); /// } /// ``` + /// + /// ### Options + /// + /// #### checkLoops + /// + /// `{ type: "all" | "allExceptWhileTrue" | "none" | boolean, default: "allExceptWhileTrue" }` + /// + /// - `"all"` or `true` disallows constant expressions in loops + /// - `"allExceptWhileTrue"` disallows constant expressions in loops except while loops with expression `true` + /// - `"none"` or `false` allows constant expressions in loops NoConstantCondition, eslint, correctness ); impl Rule for NoConstantCondition { - fn from_configuration(value: serde_json::Value) -> Self { + fn from_configuration(value: Value) -> Self { let obj = value.get(0); Self { - _check_loops: obj + check_loops: obj .and_then(|v| v.get("checkLoops")) - .and_then(serde_json::Value::as_bool) + .and_then(CheckLoops::from) .unwrap_or_default(), } } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { match node.kind() { - AstKind::IfStatement(if_stmt) => { - if if_stmt.test.is_constant(true, ctx) { - ctx.diagnostic(no_constant_condition_diagnostic(if_stmt.test.span())); - } + AstKind::IfStatement(if_stmt) => check(ctx, &if_stmt.test), + AstKind::ConditionalExpression(condition_expr) => check(ctx, &condition_expr.test), + AstKind::WhileStatement(while_stmt) => self.check_loop(ctx, &while_stmt.test, true), + AstKind::DoWhileStatement(do_while_stmt) => { + self.check_loop(ctx, &do_while_stmt.test, false); } - AstKind::ConditionalExpression(condition_expr) => { - if condition_expr.test.is_constant(true, ctx) { - ctx.diagnostic(no_constant_condition_diagnostic(condition_expr.test.span())); - } + AstKind::ForStatement(for_stmt) => { + let Some(test) = &for_stmt.test else { + return; + }; + + self.check_loop(ctx, test, false); } _ => {} } } } +impl NoConstantCondition { + fn check_loop<'a>(&self, ctx: &LintContext<'a>, test: &'a Expression<'_>, is_while: bool) { + match self.check_loops { + CheckLoops::None => return, + CheckLoops::AllExceptWhileTrue if is_while => match test { + Expression::BooleanLiteral(bool) if bool.value => return, + _ => {} + }, + _ => {} + } + + check(ctx, test); + } +} + +fn check<'a>(ctx: &LintContext<'a>, test: &'a Expression<'_>) { + if test.is_constant(true, ctx) { + ctx.diagnostic(no_constant_condition_diagnostic(test.span())); + } +} + #[test] fn test() { + use serde_json::json; + use crate::tester::Tester; let pass = vec![ @@ -220,28 +285,36 @@ fn test() { // ("const undefined = 'lol'; if (undefined) {}", None), // ("function foo(Boolean) { if (Boolean(1)) {} }", None), // ("const Boolean = () => {}; if (Boolean(1)) {}", None), - // "if (Boolean()) {}", - // "if (undefined) {}", + // ("if (Boolean()) {}", None), + // ("if (undefined) {}", None), ("q > 0 ? 1 : 2;", None), ("`${a}` === a ? 1 : 2", None), ("`foo${a}` === a ? 1 : 2", None), ("tag`a` === a ? 1 : 2", None), ("tag`${a}` === a ? 1 : 2", None), - //TODO - // ("while(~!a);", None), - // ("while(a = b);", None), - // ("while(`${a}`);", None), - // ("for(;x < 10;);", None), - // ("for(;;);", None), - // ("for(;`${a}`;);", None), - // ("do{ }while(x)", None), - // ("while(x += 3) {}", None), - // ("while(tag`a`) {}", None), - // ("while(tag`${a}`) {}", None), - // ("while(`\\\n${a}`) {}", None), - // ("while(true);", Some(json!([{"checkLoops":false}]))), - // ("for(;true;);", Some(json!([{"checkLoops":false}]))), - // ("do{}while(true)", Some(json!([{"checkLoops":false}]))), + ("while(~!a);", None), + ("while(a = b);", None), + ("while(`${a}`);", None), + ("for(;x < 10;);", None), + ("for(;;);", None), + ("for(;`${a}`;);", None), + ("do{ }while(x)", None), + ("while(x += 3) {}", None), + ("while(tag`a`) {}", None), + ("while(tag`${a}`) {}", None), + ("while(`\\\n${a}`) {}", None), + ("while(true);", Some(json!([{ "checkLoops": false }]))), + ("for(;true;);", Some(json!([{ "checkLoops": false }]))), + ("do{}while(true)", Some(json!([{ "checkLoops": "none" }]))), + ("while(true);", Some(json!([{ "checkLoops": "none" }]))), + ("for(;true;);", Some(json!([{ "checkLoops": "none" }]))), + ("do{ }while(x);", Some(json!([{ "checkLoops": "all" }]))), + ("while(true);", Some(json!([{ "checkLoops": "allExceptWhileTrue" }]))), + ("while(true);", None), + ("while(a == b);", Some(json!([{ "checkLoops": "all" }]))), + ("for (let x = 0; x <= 10; x++) {};", Some(json!([{ "checkLoops": "all" }]))), + ("do{}while(true)", Some(json!([{ "checkLoops": false }]))), + // TODO // ("function* foo(){while(true){yield 'foo';}}", None), // ("function* foo(){for(;true;){yield 'foo';}}", None), // ("function* foo(){do{yield 'foo';}while(true)}", None), @@ -254,7 +327,6 @@ fn test() { ]; let fail = vec![ - ("if(-2);", None), ("if(-2);", None), ("if(true);", None), ("if(1);", None), @@ -306,11 +378,13 @@ fn test() { ("if((a &&= null) && b);", None), ("if(false || (a &&= false));", None), ("if((a &&= false) || false);", None), + ("while(x = 1);", None), ("if(typeof x){}", None), ("if(typeof 'abc' === 'string'){}", None), ("if(a = typeof b){}", None), ("if(a, typeof b){}", None), ("if(typeof 'a' == 'string' || typeof 'b' == 'string'){}", None), + ("while(typeof x){}", None), ("if(1 || void x);", None), ("if(void x);", None), ("if(y = void x);", None), @@ -370,39 +444,46 @@ fn test() { ("`` ? 1 : 2;", None), ("`foo` ? 1 : 2;", None), ("`foo${bar}` ? 1 : 2;", None), + ("for(;true;);", None), + ("for(;``;);", None), + ("for(;`foo`;);", None), + ("for(;`foo${bar}`;);", None), + ("do{}while(true)", None), + ("do{}while('1')", None), + ("do{}while(0)", None), + ("do{}while(t = -2)", None), + ("do{}while(``)", None), + ("do{}while(`foo`)", None), + ("do{}while(`foo${bar}`)", None), + ("while([]);", None), + ("while(~!0);", None), + ("while(x = 1);", Some(json!([{ "checkLoops": "all" }]))), + ("while(function(){});", None), + ("while(true);", Some(json!([{ "checkLoops": "all" }]))), + ("while(1);", None), + ("while(() => {});", None), + ("while(`foo`);", None), + ("while(``);", None), + ("while(`${'foo'}`);", None), + ("while(`${'foo' + 'bar'}`);", None), + ("do{ }while(x = 1)", Some(json!([{ "checkLoops": "all" }]))), + ("for (;true;) {};", Some(json!([{ "checkLoops": "all" }]))), // TODO - // ("for(;true;);", None), - // ("for(;``;);", None), - // ("for(;`foo`;);", None), - // ("for(;`foo${bar}`;);", None), - // ("do{}while(true)", None), - // ("do{}while('1')", None), - // ("do{}while(0)", None), - // ("do{}while(t = -2)", None), - // ("do{}while(``)", None), - // ("do{}while(`foo`)", None), - // ("do{}while(`foo${bar}`)", None), - // ("while([]);", None), - // ("while(~!0);", None), - // ("while(x = 1);", None), - // ("while(function(){});", None), - // ("while(true);", None), - // ("while(1);", None), - // ("while(() => {});", None), - // ("while(`foo`);", None), - // ("while(``);", None), - // ("while(`${'foo'}`);", None), - // ("while(`${'foo' + 'bar'}`);", None), - // ("function* foo(){while(true){} yield 'foo';}", None), - // ("function* foo(){while(true){if (true) {yield 'foo';}}}", None), - // ("function* foo(){while(true){yield 'foo';} while(true) {}}", None), - // ("var a = function* foo(){while(true){} yield 'foo';}", None), - // ("while (true) { function* foo() {yield;}}", None), + // ("function* foo(){while(true){} yield 'foo';}", Some(json!([{ "checkLoops": "all" }]))), + // ("function* foo(){while(true){} yield 'foo';}", Some(json!([{ "checkLoops": true }]))), + // ("function* foo(){while(true){if (true) {yield 'foo';}}}",Some(json!([{ "checkLoops": "all" }])),), + // ("function* foo(){while(true){if (true) {yield 'foo';}}}",Some(json!([{ "checkLoops": true }])),), + // ("function* foo(){while(true){yield 'foo';} while(true) {}}", Some(json!([{ "checkLoops": "all" }])),), + // ("function* foo(){while(true){yield 'foo';} while(true) {}}",Some(json!([{ "checkLoops": true }])),), + // ("var a = function* foo(){while(true){} yield 'foo';}",Some(json!([{ "checkLoops": "all" }])),), + // ("var a = function* foo(){while(true){} yield 'foo';}",Some(json!([{ "checkLoops": true }])),), + // ("while (true) { function* foo() {yield;}}", Some(json!([{ "checkLoops": "all" }]))), + // ("while (true) { function* foo() {yield;}}", Some(json!([{ "checkLoops": true }]))), // ("function* foo(){if (true) {yield 'foo';}}", None), // ("function* foo() {for (let foo = yield; true;) {}}", None), // ("function* foo() {for (foo = yield; true;) {}}", None), - // ("function foo() {while (true) {function* bar() {while (true) {yield;}}}}", None), - // ("function foo() {while (true) {const bar = function*() {while (true) {yield;}}}}", None), + // ("function foo() {while (true) {function* bar() {while (true) {yield;}}}}",Some(json!([{ "checkLoops": "all" }])),), + // ("function foo() {while (true) {const bar = function*() {while (true) {yield;}}}}",Some(json!([{ "checkLoops": "all" }])),), // ("function* foo() { for (let foo = 1 + 2 + 3 + (yield); true; baz) {}}", None), ]; diff --git a/crates/oxc_linter/src/rules/eslint/no_control_regex.rs b/crates/oxc_linter/src/rules/eslint/no_control_regex.rs index cf683780a40d8..abd87a066edd5 100644 --- a/crates/oxc_linter/src/rules/eslint/no_control_regex.rs +++ b/crates/oxc_linter/src/rules/eslint/no_control_regex.rs @@ -1,25 +1,86 @@ +use std::fmt::Write; + use itertools::Itertools as _; + use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_regular_expression::{ - ast::{CapturingGroup, Character, Pattern}, + ast::{CapturingGroup, Character, CharacterKind, Pattern}, visit::{Visit, walk}, }; use oxc_span::Span; use crate::{AstNode, context::LintContext, rule::Rule, utils::run_on_regex_node}; -fn no_control_regex_diagnostic(count: usize, regex: &str, span: Span) -> OxcDiagnostic { +fn no_control_regex_diagnostic(control_chars: &[Character], span: Span) -> OxcDiagnostic { + let count = control_chars.len(); debug_assert!(count > 0); - let (message, help) = if count == 1 { - ("Unexpected control character", format!("'{regex}' is not a valid control character.")) - } else { - ("Unexpected control characters", format!("'{regex}' are not valid control characters.")) - }; - OxcDiagnostic::warn(message).with_help(help).with_label(span) -} + let mut octal_chars = Vec::new(); + let mut null_chars = Vec::new(); + let mut other_chars = Vec::new(); + for ch in control_chars { + match ch.kind { + CharacterKind::Octal1 | CharacterKind::Octal2 | CharacterKind::Octal3 => { + octal_chars.push(ch); + } + CharacterKind::Null => { + null_chars.push(ch); + } + _ => { + other_chars.push(ch); + } + } + } + + let mut help = String::new(); + + if !other_chars.is_empty() { + let regexes = other_chars.iter().join(", "); + writeln!( + help, + "'{regexes}' {} {}control character{}.", + if other_chars.len() > 1 { "are" } else { "is" }, + if other_chars.len() > 1 { "" } else { "a " }, + if other_chars.len() > 1 { "s" } else { "" }, + ) + .unwrap(); + } + + if !octal_chars.is_empty() { + let regexes = octal_chars.iter().join(", "); + writeln!( + help, + "'{regexes}' {} {}control character{}. They look like backreferences, but there {} no corresponding capture group{}.", + if octal_chars.len() > 1 { "are" } else { "is" }, + if octal_chars.len() > 1 { "" } else { "a " }, + if octal_chars.len() > 1 { "s" } else { "" }, + if octal_chars.len() > 1 { "are" } else { "is" }, + if octal_chars.len() > 1 { "s" } else { "" } + ).unwrap(); + } + + if !null_chars.is_empty() { + writeln!(help, "'\\0' matches the null character (U+0000), which is a control character.") + .unwrap(); + } + + debug_assert!(!help.is_empty()); + debug_assert!(help.chars().last().is_some_and(|char| char == '\n')); + + if !help.is_empty() { + help.truncate(help.len() - 1); + } + + OxcDiagnostic::warn(if count > 1 { + "Unexpected control characters" + } else { + "Unexpected control character" + }) + .with_help(help) + .with_label(span) +} #[derive(Debug, Default, Clone)] pub struct NoControlRegex; @@ -84,9 +145,7 @@ fn check_pattern(context: &LintContext, pattern: &Pattern, span: Span) { finder.visit_pattern(pattern); if !finder.control_chars.is_empty() { - let num_control_chars = finder.control_chars.len(); - let violations = finder.control_chars.into_iter().map(|c| c.to_string()).join(", "); - context.diagnostic(no_control_regex_diagnostic(num_control_chars, &violations, span)); + context.diagnostic(no_control_regex_diagnostic(&finder.control_chars, span)); } } @@ -152,7 +211,7 @@ mod tests { use super::*; use crate::tester::Tester; - #[test] + #[test] // fn test_hex_literals() { Tester::new( NoControlRegex::NAME, @@ -298,6 +357,10 @@ mod tests { r"/\x0d/u", r"/\u{09}/u", r"/\x09/u", + r"/\0\1\2/", + r"/\x1f\2/", + r"/\x1f\0/", + r"/\x1f\0\2/", ], ) .test_and_snapshot(); diff --git a/crates/oxc_linter/src/rules/eslint/no_debugger.rs b/crates/oxc_linter/src/rules/eslint/no_debugger.rs index cd5b286c83a5f..1a035b1cdffb8 100644 --- a/crates/oxc_linter/src/rules/eslint/no_debugger.rs +++ b/crates/oxc_linter/src/rules/eslint/no_debugger.rs @@ -24,6 +24,8 @@ declare_oxc_lint!( /// /// ### Example /// + /// Examples of **incorrect** code for this rule: + /// /// ```javascript /// async function main() { /// const data = await getData(); @@ -31,6 +33,15 @@ declare_oxc_lint!( /// debugger; /// } /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```javascript + /// async function main() { + /// const data = await getData(); + /// const result = complexCalculation(data); + /// } + /// ``` + /// NoDebugger, eslint, correctness, diff --git a/crates/oxc_linter/src/rules/eslint/no_redeclare.rs b/crates/oxc_linter/src/rules/eslint/no_redeclare.rs index b20b934f0e856..6a98ca7313022 100644 --- a/crates/oxc_linter/src/rules/eslint/no_redeclare.rs +++ b/crates/oxc_linter/src/rules/eslint/no_redeclare.rs @@ -1,10 +1,14 @@ use javascript_globals::GLOBALS; + use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; +use oxc_span::{ModuleKind, Span}; use oxc_syntax::symbol::SymbolId; -use crate::{context::LintContext, rule::Rule}; +use crate::{ + context::{ContextHost, LintContext}, + rule::Rule, +}; fn no_redeclare_diagnostic(name: &str, decl_span: Span, re_decl_span: Span) -> OxcDiagnostic { OxcDiagnostic::warn(format!("'{name}' is already defined.")).with_labels([ @@ -52,7 +56,7 @@ declare_oxc_lint!( /// /// #### builtinGlobals /// - /// `{ type: bool, default: false }` + /// `{ type: bool, default: true }` /// /// When set `true`, it flags redeclaring built-in globals (e.g., `let Object = 1;`). NoRedeclare, @@ -116,6 +120,11 @@ impl Rule for NoRedeclare { } } } + + fn should_run(&self, ctx: &ContextHost) -> bool { + // Modules run in their own scope, and don't conflict with existing globals + ctx.source_type().module_kind() == ModuleKind::Script + } } #[test] @@ -178,9 +187,23 @@ fn test() { ("type foo = 1; export function foo(): void; export function foo() { }", None), ]; - Tester::new(NoRedeclare::NAME, NoRedeclare::PLUGIN, pass, fail).test_and_snapshot(); + Tester::new(NoRedeclare::NAME, NoRedeclare::PLUGIN, pass, fail) + .change_rule_path_extension(".cts") + .test_and_snapshot(); let fail = vec![("var foo;", None, Some(serde_json::json!({ "globals": { "foo": false }})))]; - Tester::new(NoRedeclare::NAME, NoRedeclare::PLUGIN, vec![], fail).test(); + Tester::new(NoRedeclare::NAME, NoRedeclare::PLUGIN, vec![], fail) + .change_rule_path_extension(".cts") + .test(); + + let pass = vec![( + "import { performance } from 'node:perf_hooks'; (() => { performance })", + None, + Some(serde_json::json!({ "globals": { "performance": "readonly" }})), + )]; + + Tester::new(NoRedeclare::NAME, NoRedeclare::PLUGIN, pass, vec![]) + .change_rule_path_extension(".ts") + .test(); } diff --git a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs index 1189cf60297b2..3e4c873d0e2da 100644 --- a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs @@ -1,4 +1,6 @@ -use ignore::gitignore::GitignoreBuilder; +use std::borrow::Cow; + +use globset::GlobBuilder; use lazy_regex::Regex; use oxc_ast::{ AstKind, @@ -10,7 +12,6 @@ use oxc_span::{CompactStr, Span}; use rustc_hash::FxHashMap; use serde::{Deserialize, Deserializer, de::Error}; use serde_json::Value; -use std::borrow::Cow; use crate::{ ModuleRecord, @@ -856,30 +857,37 @@ impl RestrictedPattern { return GlobResult::None; }; - let mut builder = GitignoreBuilder::new(""); - // returns always OK, will be fixed in the next version - let _ = builder.case_insensitive(!self.case_sensitive.unwrap_or(false)); + let case_insensitive = !self.case_sensitive.unwrap_or(false); - for group in groups { - // returns always OK - let _ = builder.add_line(None, group.as_str()); - } + let mut decision = GlobResult::None; - let Ok(gitignore) = builder.build() else { - return GlobResult::None; - }; + for raw_pat in groups { + let (negated, pat) = match raw_pat.strip_prefix('!') { + Some(rest) => (true, rest), + None => (false, raw_pat.as_str()), + }; - let matched = gitignore.matched(name, false); + // roughly based on https://github.com/BurntSushi/ripgrep/blob/6dfaec03e830892e787686917509c17860456db1/crates/ignore/src/gitignore.rs#L436-L516 + let mut pat = pat.to_string(); - if matched.is_whitelist() { - return GlobResult::Whitelist; - } + if !pat.starts_with('/') && !pat.chars().any(|c| c == '/') && (!pat.starts_with("**")) { + pat = format!("**/{pat}"); + } - if matched.is_none() { - return GlobResult::None; + let Ok(glob) = GlobBuilder::new(&pat) + .case_insensitive(case_insensitive) + .build() + .map(|g| g.compile_matcher()) + else { + continue; + }; + + if glob.is_match(name) { + decision = if negated { GlobResult::Whitelist } else { GlobResult::Found }; + } } - GlobResult::Found + decision } fn get_regex_result(&self, name: &str) -> bool { @@ -985,7 +993,13 @@ impl NoRestrictedImports { for (source, requests) in &module_record.requested_modules { for request in requests { - if request.is_import && module_record.import_entries.is_empty() { + if request.is_import + && (module_record.import_entries.is_empty() + || module_record + .import_entries + .iter() + .all(|entry| entry.statement_span != request.statement_span)) + { side_effect_import_map.entry(source).or_default().push(request.statement_span); } } @@ -994,12 +1008,43 @@ impl NoRestrictedImports { for path in &self.paths { for (source, spans) in &side_effect_import_map { if source.as_str() == path.name.as_str() && path.import_names.is_none() { - if let Some(span) = spans.iter().next() { + debug_assert!( + !spans.is_empty(), + "all import entries must have at least one import entry" + ); + if let Some(span) = spans.first() { ctx.diagnostic(diagnostic_path(*span, path.message.clone(), source)); } } } } + + for (source, spans) in &side_effect_import_map { + let mut whitelist_found = false; + let mut err = None; + for pattern in &self.patterns { + match pattern.get_group_glob_result(source) { + GlobResult::Whitelist => { + whitelist_found = true; + break; + } + GlobResult::Found => { + err = Some(get_diagnostic_from_import_name_result_pattern( + spans[0], + source, + &ImportNameResult::GeneralDisallowed, + pattern, + )); + } + GlobResult::None => {} + } + } + if !whitelist_found { + if let Some(err) = err { + ctx.diagnostic(err); + } + } + } } fn report_import_name_allowed(&self, ctx: &LintContext<'_>, entry: &ImportEntry) { @@ -1276,47 +1321,43 @@ fn get_diagnostic_from_import_name_result_pattern( diagnostic_pattern(span, pattern.message.clone(), source) } ImportNameResult::DefaultDisallowed => { - let diagnostic = match &pattern.import_names { - Some(import_names) => diagnostic_pattern_and_everything( + if let Some(import_names) = &pattern.import_names { + return diagnostic_pattern_and_everything( span, pattern.message.clone(), import_names.join(", ").as_str(), source, - ), - _ => match &pattern.import_name_pattern { - Some(import_name_patterns) => { - diagnostic_pattern_and_everything_with_regex_import_name( - span, - pattern.message.clone(), - import_name_patterns, - source, - ) - } - _ => match &pattern.allow_import_name_pattern { - Some(allow_import_name_pattern) => { - diagnostic_everything_with_allowed_import_name_pattern( - span, - pattern.message.clone(), - source, - allow_import_name_pattern.as_str(), - ) - } - _ => match &pattern.allow_import_names { - Some(allowed_import_names) => { - diagnostic_everything_with_allowed_import_name( - span, - pattern.message.clone(), - source, - allowed_import_names.join(", ").as_str(), - ) - } - _ => diagnostic_pattern(span, pattern.message.clone(), source), - }, - }, - }, - }; + ); + } - diagnostic + if let Some(import_name_patterns) = &pattern.import_name_pattern { + return diagnostic_pattern_and_everything_with_regex_import_name( + span, + pattern.message.clone(), + import_name_patterns, + source, + ); + } + + if let Some(allow_import_name_pattern) = &pattern.allow_import_name_pattern { + return diagnostic_everything_with_allowed_import_name_pattern( + span, + pattern.message.clone(), + source, + allow_import_name_pattern.as_str(), + ); + } + + if let Some(allowed_import_names) = &pattern.allow_import_names { + return diagnostic_everything_with_allowed_import_name( + span, + pattern.message.clone(), + source, + allowed_import_names.join(", ").as_str(), + ); + } + + diagnostic_pattern(span, pattern.message.clone(), source) } ImportNameResult::NameDisallowed(name_span) => match &pattern.allow_import_names { Some(allow_import_names) => diagnostic_allowed_import_name( @@ -1766,6 +1807,12 @@ fn test() { }] }])), ), + ( + r#"import a from "./index.mjs";"#, + Some( + serde_json::json!([{ "patterns": [{ "group": ["[@a-z]*", "!.*/**"], "message": "foo is forbidden, use bar instead" }] }]), + ), + ), ]; let pass_typescript = vec![ @@ -2988,6 +3035,12 @@ fn test() { }] }])), ), + ( + r#"import {x} from "foo"; import {x2} from "./index.mjs"; import {x3} from "index";"#, + Some( + serde_json::json!([{ "patterns": [{ "group": ["[@a-z]*", "!.*/**","./index.mjs"], "message": "foo is forbidden, use bar instead" }] }]), + ), + ), // ( // " // // error @@ -3006,6 +3059,19 @@ fn test() { // }] // }])), // ), + ( + r"import 'foo'; import {a} from 'b'", + Some( + serde_json::json!([{ "paths": [{ "name": "foo", "message": "foo is forbidden, use bar instead" }] }]), + ), + ), + // https://github.com/oxc-project/oxc/issues/10984 + ( + r"import 'foo'", + Some( + serde_json::json!([{ "patterns": [{ "group": ["foo"], "message": "foo is forbidden, use bar instead" }] }]), + ), + ), ]; let fail_typescript = vec![ 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 c285096caee6e..1bb49d91c4b27 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 @@ -295,6 +295,19 @@ fn test() { this.#x = 1; } }", + r"type Callback = () => Promise | T; + + export class Issue_11039 { + load: () => Promise; + + constructor(callback: Callback) { + this.load = () => this.#load(callback); + } + + async #load(callback: Callback) { + callback; + } + }", ]; let fail = vec![ diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs index b2cf55e887889..66b7ac4877999 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs @@ -281,7 +281,6 @@ impl NoUnusedVars { } let report = match symbol.references().rev().find(|r| r.is_write()) { Some(last_write) => { - // ahg let span = ctx.nodes().get_node(last_write.node_id()).kind().span(); diagnostic::assign(symbol, span, &self.vars_ignore_pattern) } @@ -346,7 +345,7 @@ impl NoUnusedVars { fn should_skip_symbol(symbol: &Symbol<'_, '_>) -> bool { const AMBIENT_NAMESPACE_FLAGS: SymbolFlags = - SymbolFlags::NameSpaceModule.union(SymbolFlags::Ambient); + SymbolFlags::NamespaceModule.union(SymbolFlags::Ambient); let flags = symbol.flags(); // 1. ignore enum members. Only enums get checked diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/options.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/options.rs index 7c9c2964373f2..569a682fe8e8d 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/options.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/options.rs @@ -205,6 +205,34 @@ pub struct NoUnusedVarsOptions { /// console.log(firstVar, secondVar); /// ``` pub report_used_ignore_pattern: bool, + + /// The `reportVarsOnlyUsedAsTypes` option is a boolean (default: `false`). + /// + /// If `true`, the rule will also report variables that are only used as types. + /// + /// ## Examples + /// + /// Examples of **incorrect** code for the `{ "reportVarsOnlyUsedAsTypes": true }` option: + /// + /// ```javascript + /// /* eslint no-unused-vars: ["error", { "reportVarsOnlyUsedAsTypes": true }] */ + /// + /// const myNumber: number = 4; + /// export type MyNumber = typeof myNumber + /// ``` + /// + /// Examples of **correct** code for the `{ "reportVarsOnlyUsedAsTypes": true }` option: + /// + /// ```javascript + /// export type MyNumber = number; + /// ``` + /// + /// Note: even with `{ "reportVarsOnlyUsedAsTypes": false }`, cases where the value is + /// only used a type within itself will still be reported: + /// ```javascript + /// function foo(): typeof foo {} + /// ``` + pub report_vars_only_used_as_types: bool, } /// Represents an `Option` with an additional `Default` variant, @@ -314,6 +342,7 @@ impl Default for NoUnusedVarsOptions { destructured_array_ignore_pattern: IgnorePattern::None, ignore_class_with_static_init_block: false, report_used_ignore_pattern: false, + report_vars_only_used_as_types: false, } } } @@ -555,6 +584,11 @@ impl TryFrom for NoUnusedVarsOptions { .map_or(Some(false), Value::as_bool) .unwrap_or(false); + let report_vars_only_used_as_types: bool = config + .get("reportVarsOnlyUsedAsTypes") + .map_or(Some(false), Value::as_bool) + .unwrap_or(false); + Ok(Self { vars, vars_ignore_pattern, @@ -566,6 +600,7 @@ impl TryFrom for NoUnusedVarsOptions { destructured_array_ignore_pattern, ignore_class_with_static_init_block, report_used_ignore_pattern, + report_vars_only_used_as_types, }) } Value::Null => Ok(Self::default()), diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs index 5e32ed09423f9..6449882038272 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs @@ -171,6 +171,10 @@ fn test_vars_self_use() { } foo(); ", + " + let cancel = () => {} + export function close() { cancel = cancel?.() } + ", ]; let fail = vec![ " @@ -183,6 +187,14 @@ fn test_vars_self_use() { return foo } ", + " + let cancel = () => {}; + cancel = cancel?.(); + ", + " + let cancel = () => {}; + { cancel = cancel?.(); } + ", ]; Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, fail) @@ -1253,6 +1265,33 @@ fn test_loops() { Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, fail).expect_fix(fix).test(); } +#[test] +fn test_report_vars_only_used_as_types() { + let pass = vec![ + ("const foo = 123; export type Foo = typeof foo;", None), + ( + "const foo = 123; export type Foo = typeof foo;", + Some(json!([{ "reportVarsOnlyUsedAsTypes": false, "varsIgnorePattern": "^_" }])), + ), + ( + "export const foo = 123; export type Foo = typeof foo;", + Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])), + ), + ]; + + let fail = vec![ + ( + "const foo = 123; export type Foo = typeof foo;", + Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])), + ), + ("function foo(): typeof foo {}", None), + ]; + + Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, fail) + .intentionally_allow_no_fix_tests() + .test(); +} + // #[test] // fn test_template() { // let pass = vec![]; diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/typescript_eslint.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/typescript_eslint.rs index 7861a3667acb4..b6bf3c2454d6c 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/typescript_eslint.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/typescript_eslint.rs @@ -3,8 +3,6 @@ use serde_json::json; use super::NoUnusedVars; use crate::{RuleMeta as _, tester::Tester}; -// TODO: port these over. I (@DonIsaac) would love some help with this... - #[test] fn test() { let pass = vec![ @@ -1731,28 +1729,28 @@ fn test() { const foo: number = 1; export type Foo = typeof foo; ", - None, + Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])), ), ( " declare const foo: number; export type Foo = typeof foo; ", - None, + Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])), ), ( " const foo: number = 1; export type Foo = typeof foo | string; ", - None, + Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])), ), ( " const foo: number = 1; export type Foo = (typeof foo | string) & { __brand: 'foo' }; ", - None, + Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])), ), ( " @@ -1763,7 +1761,7 @@ fn test() { }; export type Bar = typeof foo.bar; ", - None, + Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])), ), ( " @@ -1774,7 +1772,7 @@ fn test() { }; export type Bar = (typeof foo)['bar']; ", - None, + Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])), ), ]; 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 7d37196964601..12c601c1b05f5 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 @@ -1,6 +1,7 @@ //! This module contains logic for checking if any [`Reference`]s to a //! [`Symbol`] are considered a usage. +use itertools::Itertools; use oxc_ast::{AstKind, ast::*}; use oxc_semantic::{AstNode, NodeId, Reference, ScopeId, SymbolFlags, SymbolId}; use oxc_span::{GetSpan, Span}; @@ -34,7 +35,7 @@ impl<'a> Symbol<'_, 'a> { SymbolFlags::TypeAlias.union(SymbolFlags::TypeParameter).union(SymbolFlags::Interface); const ENUM: SymbolFlags = SymbolFlags::Enum.union(SymbolFlags::EnumMember); const NAMESPACE_LIKE: SymbolFlags = - SymbolFlags::NameSpaceModule.union(SymbolFlags::ValueModule); + SymbolFlags::NamespaceModule.union(SymbolFlags::ValueModule); !self.flags().intersects( IMPORT.union(TYPE).union(ENUM).union(NAMESPACE_LIKE).union(SymbolFlags::CatchVariable), @@ -137,11 +138,25 @@ impl<'a> Symbol<'_, 'a> { continue; } - if !self.flags().intersects(SymbolFlags::TypeImport.union(SymbolFlags::Import)) + // ```ts + // const foo = 123; + // export type Foo = typeof foo + // ``` + if options.report_vars_only_used_as_types + && !self.flags().intersects(SymbolFlags::TypeImport.union(SymbolFlags::Import)) && self.reference_contains_type_query(reference) { continue; } + // ``` + // function foo(): foo { } + // ``` + if self + .get_ref_relevant_node(reference) + .is_some_and(|node| self.declaration().span().contains_inclusive(node.span())) + { + continue; + } return true; } @@ -413,9 +428,31 @@ impl<'a> Symbol<'_, 'a> { match left { AssignmentTarget::AssignmentTargetIdentifier(id) => { if id.name == name { + // Compare *variable scopes* (the nearest function / TS module / class‑static block). + // + // If the variable scope is the same, the the variable is still unused + // ```ts + // let cancel = () => {}; + // { // plain block + // cancel = cancel?.(); // `cancel` is unused + // } + // ``` + // + // If the variable scope is different, the read can be observed later, so it counts as a real usage: + // ```ts + // let cancel = () => {}; + // function foo() { // new var‑scope + // cancel = cancel?.(); // `cancel` is used + // } + // ``` + if self.get_parent_variable_scope(self.get_ref_scope(reference)) + != self.get_parent_variable_scope(self.scope_id()) + { + return false; + } is_used_by_others = false; } else { - return false; // we can short-circuit + return false; } } AssignmentTarget::TSAsExpression(v) @@ -818,4 +855,18 @@ impl<'a> Symbol<'_, 'a> { }; } } + + /// Return the **variable scope** for the given `scope_id`. + /// + /// A variable scope is the closest ancestor scope (including `scope_id` + /// itself) whose kind can *outlive* the current execution slice: + /// * function‑like scopes + /// * class static blocks + /// * TypeScript namespace/module blocks + fn get_parent_variable_scope(&self, scope_id: ScopeId) -> ScopeId { + self.scoping() + .scope_ancestors(scope_id) + .find_or_last(|scope_id| self.scoping().scope_flags(*scope_id).is_var()) + .expect("scope iterator will always contain at least one element") + } } diff --git a/crates/oxc_linter/src/rules/import/consistent_type_specifier_style.rs b/crates/oxc_linter/src/rules/import/consistent_type_specifier_style.rs new file mode 100644 index 0000000000000..037227265b022 --- /dev/null +++ b/crates/oxc_linter/src/rules/import/consistent_type_specifier_style.rs @@ -0,0 +1,231 @@ +use oxc_ast::{AstKind, ast::ImportDeclarationSpecifier}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{GetSpan, Span}; +use serde_json::Value; + +use crate::{AstNode, context::LintContext, rule::Rule}; + +fn consistent_type_specifier_style_diagnostic(span: Span, mode: &Mode) -> OxcDiagnostic { + let (warn_msg, help_msg) = if *mode == Mode::PreferInline { + ( + "Prefer using inline type specifiers instead of a top-level type-only import.", + "Replace top‐level import type with an inline type specifier.", + ) + } else { + ( + "Prefer using a top-level type-only import instead of inline type specifiers.", + "Replace inline type specifiers with a top‐level import type statement.", + ) + }; + OxcDiagnostic::warn(warn_msg).with_help(help_msg).with_label(span) +} + +#[derive(Debug, Default, PartialEq, Clone)] +enum Mode { + #[default] + PreferTopLevel, + PreferInline, +} + +impl Mode { + pub fn from(raw: &str) -> Self { + if raw == "prefer-inline" { Self::PreferInline } else { Self::PreferTopLevel } + } +} + +#[derive(Debug, Default, Clone)] +pub struct ConsistentTypeSpecifierStyle { + mode: Mode, +} + +declare_oxc_lint!( + /// ### What it does + /// + /// This rule either enforces or bans the use of inline type-only markers for named imports. + /// + /// ### Why is this bad? + /// + /// Mixing top-level `import type { Foo } from 'foo'` with inline `{ type Bar }` + /// forces readers to mentally switch contexts when scanning your imports. + /// Enforcing one style makes it immediately obvious which imports are types and which are value imports. + /// + /// ### Examples + /// + /// Examples of incorrect code for the default `prefer-top-level` option: + /// ```typescript + /// import {type Foo} from 'Foo'; + /// import Foo, {type Bar} from 'Foo'; + /// ``` + /// + /// Examples of correct code for the default option: + /// ```typescript + /// import type {Foo} from 'Foo'; + /// import type Foo, {Bar} from 'Foo'; + /// ``` + /// + /// Examples of incorrect code for the `prefer-inline` option: + /// ```typescript + /// import type {Foo} from 'Foo'; + /// import type Foo, {Bar} from 'Foo'; + /// ``` + /// + /// Examples of correct code for the `prefer-inline` option: + /// ```typescript + /// import {type Foo} from 'Foo'; + /// import Foo, {type Bar} from 'Foo'; + /// ``` + ConsistentTypeSpecifierStyle, + import, + style, + conditional_fix +); + +impl Rule for ConsistentTypeSpecifierStyle { + fn from_configuration(value: Value) -> Self { + Self { mode: value.get(0).and_then(Value::as_str).map(Mode::from).unwrap_or_default() } + } + #[expect(clippy::cast_possible_truncation)] + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::ImportDeclaration(import_decl) = node.kind() else { + return; + }; + let Some(specifiers) = &import_decl.specifiers else { + return; + }; + let len = specifiers.len(); + if len == 0 + || (len == 1 + && !matches!(specifiers[0], ImportDeclarationSpecifier::ImportSpecifier(_))) + { + return; + } + if self.mode == Mode::PreferTopLevel && import_decl.import_kind.is_value() { + for item in specifiers { + if matches!(item, ImportDeclarationSpecifier::ImportSpecifier(specifier) if specifier.import_kind.is_type()) + { + ctx.diagnostic(consistent_type_specifier_style_diagnostic( + item.span(), + &self.mode, + )); + } + } + } + if self.mode == Mode::PreferInline && import_decl.import_kind.is_type() { + ctx.diagnostic_with_fix( + consistent_type_specifier_style_diagnostic(import_decl.span, &self.mode), + |fixer| { + let fixer = fixer.for_multifix(); + let mut rule_fixes = fixer.new_fix_with_capacity(len); + for item in specifiers { + rule_fixes.push(fixer.insert_text_before(item, "type ")); + } + // find the 'type' keyword and remove it + if let Some(type_token_span) = ctx + .source_range(Span::new(import_decl.span.start, specifiers[0].span().start)) + .find("type") + .map(|pos| { + let start = import_decl.span.start + pos as u32; + Span::new(start, start + 4) + }) + { + let remove_fix = fixer.delete_range(type_token_span); + rule_fixes.push(remove_fix); + } + rule_fixes.with_message("Convert to an `inline` type import") + }, + ); + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + use serde_json::json; + + let pass = vec![ + ("import Foo from 'Foo'", None), + ("import type Foo from 'Foo'", None), + ("import { Foo } from 'Foo';", None), + ("import { Foo as Bar } from 'Foo';", None), + ("import * as Foo from 'Foo';", None), + ("import 'Foo';", None), + ("import {} from 'Foo';", None), + ("import type {} from 'Foo';", None), + ("import type { Foo as Bar } from 'Foo';", Some(json!(["prefer-top-level"]))), + ("import type { Foo, Bar, Baz, Bam } from 'Foo';", Some(json!(["prefer-top-level"]))), + ("import type {Foo} from 'Foo'", Some(json!(["prefer-top-level"]))), + ("import {type Foo} from 'Foo'", Some(json!(["prefer-inline"]))), + ("import Foo from 'Foo';", Some(json!(["prefer-inline"]))), + ("import type Foo from 'Foo';", Some(json!(["prefer-inline"]))), + ("import { Foo } from 'Foo';", Some(json!(["prefer-inline"]))), + ("import { Foo as Bar } from 'Foo';", Some(json!(["prefer-inline"]))), + ("import * as Foo from 'Foo';", Some(json!(["prefer-inline"]))), + ("import 'Foo';", Some(json!(["prefer-inline"]))), + ("import {} from 'Foo';", Some(json!(["prefer-inline"]))), + ("import type {} from 'Foo';", Some(json!(["prefer-inline"]))), + ("import { type Foo } from 'Foo';", Some(json!(["prefer-inline"]))), + ("import { type Foo as Bar } from 'Foo';", Some(json!(["prefer-inline"]))), + ("import { type Foo, type Bar, Baz, Bam } from 'Foo';", Some(json!(["prefer-inline"]))), + ("import type * as Foo from 'Foo';", None), + ]; + + let fail = vec![ + ("import { type Foo, type Bar } from 'Foo'", None), + ("import type { Foo } from 'Foo'", Some(json!(["prefer-inline"]))), + ("import { type Foo as Bar } from 'Foo';", None), + ("import { type Foo, type Bar } from 'Foo';", None), + ("import { Foo, type Bar } from 'Foo';", None), + ("import { type Foo, Bar } from 'Foo';", None), + ("import Foo, { type Bar } from 'Foo';", None), + ("import Foo, { type Bar, Baz } from 'Foo';", None), + ("import { Component, type ComponentProps } from 'package-1';", None), + ("import type { Foo, Bar, Baz } from 'Foo';", Some(json!(["prefer-inline"]))), + ]; + + let fix = vec![ + ( + "import type { foo, bar } from 'foo'", + "import { type foo, type bar } from 'foo'", + Some(json!(["prefer-inline"])), + ), + ( + "import type{ foo } from 'foo'", + "import { type foo } from 'foo'", + Some(json!(["prefer-inline"])), + ), + ( + "import type /** comment */{ foo } from 'foo'", + "import /** comment */{ type foo } from 'foo'", + Some(json!(["prefer-inline"])), + ), + ( + "import type { foo, /** comments */ bar } from 'foo'", + "import { type foo, /** comments */ type bar } from 'foo'", + Some(json!(["prefer-inline"])), + ), + ( + r" + import type { + bar, + } from 'foo' + ", + r" + import { + type bar, + } from 'foo' + ", + Some(json!(["prefer-inline"])), + ), + ]; + + Tester::new( + ConsistentTypeSpecifierStyle::NAME, + ConsistentTypeSpecifierStyle::PLUGIN, + pass, + fail, + ) + .expect_fix(fix) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/rules/import/no_deprecated.rs b/crates/oxc_linter/src/rules/import/no_deprecated.rs deleted file mode 100644 index ae776c6ff03cb..0000000000000 --- a/crates/oxc_linter/src/rules/import/no_deprecated.rs +++ /dev/null @@ -1,117 +0,0 @@ -use oxc_diagnostics::{LabeledSpan, OxcDiagnostic}; -use oxc_macros::declare_oxc_lint; -// use oxc_span::{CompactStr, Span}; - -use crate::{ - context::{ContextHost, LintContext}, - rule::Rule, -}; - -// #[derive(Debug, Error, Diagnostic)] -// #[error("")] -// #[diagnostic(severity(warning), help(""))] -// struct NoDeprecatedDiagnostic(CompactStr, #[label] pub Span); - -/// -#[derive(Debug, Default, Clone)] -pub struct NoDeprecated; - -declare_oxc_lint!( - /// ### What it does - /// - /// Reports use of a deprecated name, as indicated by a JSDoc block with - /// a @deprecated tag or TomDoc Deprecated: comment. - /// - /// ### Why is this bad? - /// - /// ### Examples - /// - /// Examples of **incorrect** code for this rule: - /// ```javascript - /// ``` - /// - /// Examples of **correct** code for this rule: - /// ```javascript - /// ``` - NoDeprecated, - import, - nursery -); - -impl Rule for NoDeprecated { - fn run_once(&self, _ctx: &LintContext<'_>) {} -} - -#[test] -fn test() { - use crate::tester::Tester; - - let pass = vec![ - r"import { x } from './fake'", - r"import bar from './bar'", - r"import { fine } from './deprecated'", - r"import { _undocumented } from './deprecated'", - r"import { fn } from './deprecated'", - r"import { fine } from './tomdoc-deprecated'", - r"import { _undocumented } from './tomdoc-deprecated'", - r"import * as depd from './deprecated'", - r"import * as depd from './deprecated'; console.log(depd.fine())", - r"import { deepDep } from './deep-deprecated'", - r"import { deepDep } from './deep-deprecated'; console.log(deepDep.fine())", - r"import { deepDep } from './deep-deprecated'; function x(deepDep) { console.log(deepDep.MY_TERRIBLE_ACTION) }", - r"for (let { foo, bar } of baz) {}", - r"for (let [ foo, bar ] of baz) {}", - r"const { x, y } = bar", - r"const { x, y, ...z } = bar", - r"let x; export { x }", - r"let x; export { x as y }", - r"export const x = null", - r"export var x = null", - r"export let x = null", - r"export default x", - r"export default class x {}", - r#"import json from "./data.json""#, - r#"import foo from "./foobar.json";"#, - r#"import foo from "./foobar";"#, - r#"import { foo } from "./issue-370-commonjs-namespace/bar""#, - r#"export * from "./issue-370-commonjs-namespace/bar""#, - r#"import * as a from "./commonjs-namespace/a"; a.b"#, - r#"import { foo } from "./ignore.invalid.extension""#, - // hoisting - r#"function x(deepDep) { console.log(deepDep.MY_TERRIBLE_ACTION) } import { deepDep } from "./deep-deprecated""#, - // TypeScript - r#"import * as hasDeprecated from "./ts-deprecated.ts""#, - ]; - - let fail = vec![ - // r#"import './malformed.js'"#, - // r#"import { fn } from './deprecated'"#, - // r#"import TerribleClass from './deprecated'"#, - // r#"import { MY_TERRIBLE_ACTION } from './deprecated'"#, - // r#"import { fn } from './deprecated'"#, - // r#"import { fn } from './tomdoc-deprecated'"#, - // r#"import TerribleClass from './tomdoc-deprecated'"#, - // r#"import { MY_TERRIBLE_ACTION } from './tomdoc-deprecated'"#, - // r#"import { MY_TERRIBLE_ACTION } from './deprecated'; function shadow(MY_TERRIBLE_ACTION) { console.log(MY_TERRIBLE_ACTION); }"#, - // r#"import { MY_TERRIBLE_ACTION, fine } from './deprecated'; console.log(fine)"#, - // r#"import { MY_TERRIBLE_ACTION } from './deprecated'; console.log(MY_TERRIBLE_ACTION)"#, - // r#"import { MY_TERRIBLE_ACTION } from './deprecated'; console.log(someOther.MY_TERRIBLE_ACTION)"#, - // r#"import { MY_TERRIBLE_ACTION } from './deprecated'; console.log(MY_TERRIBLE_ACTION.whatever())"#, - // r#"import { MY_TERRIBLE_ACTION } from './deprecated'; console.log(MY_TERRIBLE_ACTION(this, is, the, worst))"#, - // r#"import Thing from './deprecated-file'"#, - // r#"import Thing from './deprecated-file'; console.log(other.Thing)"#, - // r#"import * as depd from './deprecated'; console.log(depd.MY_TERRIBLE_ACTION)"#, - // r#"import * as deep from './deep-deprecated'; console.log(deep.deepDep.MY_TERRIBLE_ACTION)"#, - // r#"import { deepDep } from './deep-deprecated'; console.log(deepDep.MY_TERRIBLE_ACTION)"#, - // r#"import { deepDep } from './deep-deprecated'; function x(deepNDep) { console.log(deepDep.MY_TERRIBLE_ACTION) }"#, - // // hoisting - // r#"console.log(MY_TERRIBLE_ACTION); import { MY_TERRIBLE_ACTION } from "./deprecated""#, - // // TypeScript - // r#"import { foo } from "./ts-deprecated.ts"; console.log(foo())"#, - ]; - - Tester::new(NoDeprecated::NAME, NoDeprecated::PLUGIN, pass, fail) - .change_rule_path("index.js") - .with_import_plugin(true) - .test_and_snapshot(); -} diff --git a/crates/oxc_linter/src/rules/import/no_unassigned_import.rs b/crates/oxc_linter/src/rules/import/no_unassigned_import.rs new file mode 100644 index 0000000000000..fcf3985dd4948 --- /dev/null +++ b/crates/oxc_linter/src/rules/import/no_unassigned_import.rs @@ -0,0 +1,175 @@ +use globset::{Glob, GlobSet, GlobSetBuilder}; +use oxc_ast::{ + AstKind, + ast::{Argument, Expression}, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{CompactStr, Span}; +use serde_json::Value; + +use crate::{AstNode, context::LintContext, rule::Rule}; + +fn no_unassigned_import_diagnostic(span: Span, msg: &str) -> OxcDiagnostic { + OxcDiagnostic::warn(msg.to_string()) + .with_help("Consider assigning the import to a variable or removing it if it's unused.") + .with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct NoUnassignedImport(Box); + +#[derive(Debug, Default, Clone)] +pub struct NoUnassignedImportConfig { + globs: GlobSet, +} + +impl std::ops::Deref for NoUnassignedImport { + type Target = NoUnassignedImportConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +declare_oxc_lint!( + /// ### What it does + /// + /// This rule aims to remove modules with side-effects by reporting when a module is imported but not assigned. + /// + /// ### Why is this bad? + /// + /// With both CommonJS' require and the ES6 modules' import syntax, + /// it is possible to import a module but not to use its result. + /// This can be done explicitly by not assigning the module to a variable. + /// Doing so can mean either of the following things: + /// * The module is imported but not used + /// * The module has side-effects. Having side-effects, + /// makes it hard to know whether the module is actually used or can be removed. + /// It can also make it harder to test or mock parts of your application. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// import 'should' + /// require('should') + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// import _ from 'foo' + /// import _, {foo} from 'foo' + /// import _, {foo as bar} from 'foo' + /// const _ = require('foo') + /// const {foo} = require('foo') + /// const {foo: bar} = require('foo') + /// bar(require('foo')) + /// ``` + NoUnassignedImport, + import, + suspicious, +); + +fn build_globset(patterns: Vec) -> Result { + if patterns.is_empty() { + return Ok(GlobSet::empty()); + } + let mut builder = GlobSetBuilder::new(); + for pattern in patterns { + let pattern_str = pattern.as_str(); + builder.add(Glob::new(pattern_str)?); + } + builder.build() +} + +impl Rule for NoUnassignedImport { + fn from_configuration(value: Value) -> Self { + let obj = value.get(0); + let allow = obj + .and_then(|v| v.get("allow")) + .and_then(Value::as_array) + .map(|v| v.iter().filter_map(Value::as_str).map(CompactStr::from).collect()) + .unwrap_or_default(); + Self(Box::new(NoUnassignedImportConfig { globs: build_globset(allow).unwrap_or_default() })) + } + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.kind() { + AstKind::ImportDeclaration(import_decl) => { + if import_decl.specifiers.is_some() { + return; + } + let source_str = import_decl.source.value.as_str(); + if !self.globs.is_match(source_str) { + ctx.diagnostic(no_unassigned_import_diagnostic( + import_decl.span, + "Imported module should be assigned", + )); + } + } + AstKind::ExpressionStatement(statement) => { + let Expression::CallExpression(call_expr) = &statement.expression else { + return; + }; + if !call_expr.is_require_call() { + return; + } + let first_arg = &call_expr.arguments[0]; + let Argument::StringLiteral(source_str) = first_arg else { + return; + }; + if !self.globs.is_match(source_str.value.as_str()) { + ctx.diagnostic(no_unassigned_import_diagnostic( + call_expr.span, + "A `require()` style import is forbidden.", + )); + } + } + _ => {} + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + use serde_json::json; + + let pass = vec![ + ("import _ from 'foo'", None), + ("import foo from 'foo'", None), + ("import foo, { bar } from 'foo'", None), + ("import * as _ from 'foo'", None), + ("require('lodash')()", None), + ("require('lodash').foo", None), + ("require('lodash').foo()", None), + ("const _ = require('./')", None), + ("bar(require('foo'))", None), + ("const [a, b] = require('lodash')", None), + ("import 'app.css'", Some(json!([{ "allow": ["**/*.css"]}]))), + ("import 'app.css'", Some(json!([{ "allow": ["*.css"]}]))), + ("import './app.css'", Some(json!([{ "allow": ["**/*.css"]}]))), + ("import '../dist/app.css'", Some(json!([{ "allow": ["**/*.css"]}]))), + ("import '../dist/app.js'", Some(json!([{ "allow": ["**/dist/**"]}]))), + ("import 'foo/bar'", Some(json!([{ "allow": ["foo/**"]}]))), + ("import 'foo/bar'", Some(json!([{ "allow": ["foo/bar"]}]))), + ("import 'babel-register'", Some(json!([{ "allow": ["babel-register"]}]))), + ("require('./app.css')", Some(json!([{ "allow": ["**/*.css"]}]))), + ("import './styles/app.css'", Some(json!([{ "allow": ["**/styles/*.css"]}]))), + ]; + + let fail = vec![ + ("require('should')", None), + ("import 'foo'", None), + ("import './styles/app.css'", Some(json!([{ "allow": ["styles/*.css"]}]))), + ("import './app.css'", Some(json!([{ "allow": ["**/*.js"]}]))), + ("import './app.css'", Some(json!([{ "allow": ["**/dir/**"]}]))), + ("import './app.js'", None), + ("require('./app.css')", Some(json!([{ "allow": ["**/*.js"]}]))), + ]; + + Tester::new(NoUnassignedImport::NAME, NoUnassignedImport::PLUGIN, pass, fail) + .change_rule_path("no-unassigned-import.js") + .with_import_plugin(true) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/rules/import/no_unused_modules.rs b/crates/oxc_linter/src/rules/import/no_unused_modules.rs deleted file mode 100644 index 5b8bb51784ace..0000000000000 --- a/crates/oxc_linter/src/rules/import/no_unused_modules.rs +++ /dev/null @@ -1,130 +0,0 @@ -use oxc_macros::declare_oxc_lint; -use oxc_span::Span; - -use crate::{ - context::{ContextHost, LintContext}, - rule::Rule, -}; - -fn no_exports_found(span: Span) -> OxcDiagnostic { - OxcDiagnostic::warn("No exports found").with_label(span) -} - -/// -#[derive(Debug, Default, Clone)] -pub struct NoUnusedModules { - missing_exports: bool, - unused_exports: bool, -} - -declare_oxc_lint!( - /// ### What it does - /// - /// Reports: - /// * modules without any exports - /// * individual exports not being statically imported or requireed from other modules in the same project - /// * dynamic imports are supported if argument is a literal string - /// - /// ### Why is this bad? - /// - /// ### Examples - /// - /// Examples of **incorrect** code for this rule: - /// ```javascript - /// ``` - /// - /// Examples of **correct** code for this rule: - /// ```javascript - /// ``` - NoUnusedModules, - import, - nursery -); - -impl Rule for NoUnusedModules { - fn from_configuration(value: serde_json::Value) -> Self { - Self { - missing_exports: value - .get("missingExports") - .and_then(serde_json::Value::as_bool) - .unwrap_or(false), - unused_exports: value - .get("unusedExports") - .and_then(serde_json::Value::as_bool) - .unwrap_or(false), - } - } - - fn run_once(&self, ctx: &LintContext<'_>) { - let module_record = ctx.module_record(); - if self.missing_exports && module_record.local_export_entries.is_empty() { - ctx.diagnostic(no_exports_found(Span::new(0, 0))); - } - if self.unused_exports { - // TODO: implement unused exports - } - } -} - -#[test] -fn test() { - use crate::tester::Tester; - use serde_json::json; - - let missing_exports_options = json!({ - "missingExports": true, - }); - - let pass = vec![ - ("export default function noOptions() {}", None), - ("export default () => 1", Some(missing_exports_options.clone())), - ("const a = 1; export { a }", Some(missing_exports_options.clone())), - ("function a() { return true }; export { a }", Some(missing_exports_options.clone())), - ("const a = 1; const b = 2; export { a, b }", Some(missing_exports_options.clone())), - ("const a = 1; export default a", Some(missing_exports_options.clone())), - ("export class Foo {}", Some(missing_exports_options.clone())), - ("export const [foobar] = [];", Some(missing_exports_options.clone())), - ("export const [foobar] = foobarFactory();", Some(missing_exports_options.clone())), - ( - "export default function NewComponent () { - return 'I am new component' - }", - Some(missing_exports_options.clone()), - ), - ( - "export default function NewComponent () { - return 'I am new component' - }", - Some(missing_exports_options.clone()), - ), - ]; - - let fail = vec![ - ("const a = 1", Some(missing_exports_options.clone())), - ("/* const a = 1 */", Some(missing_exports_options.clone())), - ]; - - Tester::new(NoUnusedModules::NAME, NoUnusedModules::PLUGIN, pass, fail) - .change_rule_path("missing-exports.js") - .with_import_plugin(true) - .test_and_snapshot(); - - // TODO: support unused exports - // let unused_exports_options = json!({ - // "unusedExports": true, - // "src": ["./no-unused-modules/**/*.js"], - // "ignoreExports": ["./no-unused-modules/*ignored*.js"], - // }); - - // let pass = vec![ - // ("export default function noOptions() {}", None), - // ("export default () => 1", Some(unused_exports_options)), - // ]; - - // let fail = vec![]; - - // Tester::new(NoUnusedModules::NAME, NoUnusedModules::PLUGIN, pass, fail) - // .change_rule_path("unused-exports.js") - // .with_import_plugin(true) - // .test_and_snapshot(); -} 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 591a4b2902aae..804f4c6c6ff63 100644 --- a/crates/oxc_linter/src/rules/jest/no_conditional_expect.rs +++ b/crates/oxc_linter/src/rules/jest/no_conditional_expect.rs @@ -181,7 +181,7 @@ fn check_parents<'a>( // 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| { 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 8c3590131374f..a82926aedb07a 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 @@ -212,7 +212,7 @@ fn is_var_declarator_or_test_block<'a>( } let node_name = get_node_name(&call_expr.callee); - if additional_test_block_functions.iter().any(|fn_name| node_name == fn_name) { + if additional_test_block_functions.contains(&node_name) { return true; } } diff --git a/crates/oxc_linter/src/rules/jsdoc/check_tag_names.rs b/crates/oxc_linter/src/rules/jsdoc/check_tag_names.rs index 74c078ea77897..4573ab8680f9d 100644 --- a/crates/oxc_linter/src/rules/jsdoc/check_tag_names.rs +++ b/crates/oxc_linter/src/rules/jsdoc/check_tag_names.rs @@ -562,6 +562,27 @@ fn test() { ])), None, ), + // https://github.com/oxc-project/oxc/issues/10910 + ( + " + /** + * @see [@parcel/watcher](https://github.com/parcel-bundler/watcher) + */ + function quux (foo) { } + ", + Some(serde_json::json!([ { "definedTags": [] } ])), + None, + ), + ( + " + /** + * @see [[[[]@foo] + */ + function quux (foo) { } + ", + None, + None, + ), ]; let fail = vec![ diff --git a/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs b/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs index d4374396c3864..c26a7f483feef 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs @@ -74,14 +74,14 @@ declare_oxc_lint!( /// Perform action /// Perform action /// - /// ```` + /// ``` /// /// All these anchor implementations indicate that the element is only used to execute JavaScript code. All the above should be replaced with: /// /// ```jsx /// /// ``` - /// ` + /// /// ### Why is this bad? /// There are **many reasons** why an anchor should not have a logic and have a correct `href` attribute: /// - it can disrupt the correct flow of the user navigation e.g. a user that wants to open the link diff --git a/crates/oxc_linter/src/rules/jsx_a11y/lang.rs b/crates/oxc_linter/src/rules/jsx_a11y/lang.rs index 11b2d60869bed..b983fc33f32fd 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/lang.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/lang.rs @@ -42,7 +42,7 @@ declare_oxc_lint!( /// ```jsx /// /// - /// ```` + /// ``` /// /// Examples of **correct** code for this rule: /// ```jsx diff --git a/crates/oxc_linter/src/rules/nextjs/no_img_element.rs b/crates/oxc_linter/src/rules/nextjs/no_img_element.rs index e5ccfcb68572d..c7bbf2fd638b3 100644 --- a/crates/oxc_linter/src/rules/nextjs/no_img_element.rs +++ b/crates/oxc_linter/src/rules/nextjs/no_img_element.rs @@ -1,14 +1,21 @@ -use oxc_ast::{AstKind, ast::JSXElementName}; +use oxc_ast::{ + AstKind, + ast::{JSXAttributeItem, JSXElementName}, +}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; +use oxc_span::{GetSpan, Span}; use crate::{AstNode, context::LintContext, rule::Rule}; -fn no_img_element_diagnostic(span: Span) -> OxcDiagnostic { - OxcDiagnostic::warn("Prevent usage of `` element due to slower LCP and higher bandwidth.") - .with_help("See https://nextjs.org/docs/messages/no-img-element") - .with_label(span) +fn no_img_element_diagnostic(span: Span, src_span: Option) -> OxcDiagnostic { + let mut diagnostic = OxcDiagnostic::warn("Using `` could result in slower LCP and higher bandwidth.") + .with_help("Consider using `` from `next/image` or a custom image loader to automatically optimize images.\nSee https://nextjs.org/docs/messages/no-img-element") + .with_label(span.label("Use `` from `next/image` instead.")); + if let Some(src_span) = src_span { + diagnostic = diagnostic.and_label(src_span.label("Use a static image import instead.")); + } + diagnostic } #[derive(Debug, Default, Clone)] @@ -17,16 +24,45 @@ pub struct NoImgElement; declare_oxc_lint!( /// ### What it does /// + /// Prevent the usage of `` element due to slower + /// [LCP](https://nextjs.org/learn/seo/lcp) and higher bandwidth. /// /// ### Why is this bad? /// + /// `` elements are not optimized for performance and can result in + /// slower LCP and higher bandwidth. Using [``](https://nextjs.org/docs/pages/api-reference/components/image) + /// from `next/image` will automatically optimize images and serve them as + /// static assets. /// /// ### Example + /// + /// Examples of **incorrect** code for this rule: + /// ```javascript + /// export function MyComponent() { + /// return ( + ///
+ /// Test picture + ///
+ /// ); + /// } + /// ``` + /// + /// Examples of **correct** code for this rule: /// ```javascript + /// import Image from "next/image"; + /// import testImage from "./test.png" + /// export function MyComponent() { + /// return ( + ///
+ /// Test picture + ///
+ /// ); + /// } /// ``` NoImgElement, nextjs, - correctness + correctness, + pending // TODO: add `import Image from "next/image"` (if missing), then change `` to `` ); impl Rule for NoImgElement { @@ -39,18 +75,16 @@ impl Rule for NoImgElement { return; }; - if jsx_opening_element_name.name.as_str() != "img" { + if jsx_opening_element_name.name != "img" { return; } - let Some(parent) = ctx.nodes().parent_node(node.id()) else { - return; - }; - let Some(parent) = ctx.nodes().parent_node(parent.id()) else { + // first two are self, parent. third is grandparent + let Some(grandparent) = ctx.nodes().ancestor_kinds(node.id()).nth(2) else { return; }; - if let AstKind::JSXElement(maybe_picture_jsx_elem) = parent.kind() { + if let AstKind::JSXElement(maybe_picture_jsx_elem) = grandparent { if let JSXElementName::Identifier(jsx_opening_element_name) = &maybe_picture_jsx_elem.opening_element.name { @@ -60,7 +94,18 @@ impl Rule for NoImgElement { } } - ctx.diagnostic(no_img_element_diagnostic(jsx_opening_element_name.span)); + let src_span: Option = jsx_opening_element + .attributes + .iter() + .filter_map(JSXAttributeItem::as_attribute) + .find_map(|attr| { + let ident = attr.name.as_identifier()?; + let value = attr.value.as_ref()?; + let lit = value.as_string_literal()?; + (ident.name == "src").then(|| lit.span()) + }); + + ctx.diagnostic(no_img_element_diagnostic(jsx_opening_element_name.span, src_span)); } } @@ -146,6 +191,13 @@ fn test() { ); } }"#, + // src is not a string literal, so diagnostic won't label it. + " + import somePicture from './foo.png'; + export const MyComponent = () => ( + foo + ); + ", ]; Tester::new(NoImgElement::NAME, NoImgElement::PLUGIN, pass, fail).test_and_snapshot(); diff --git a/crates/oxc_linter/src/rules/react/forbid_elements.rs b/crates/oxc_linter/src/rules/react/forbid_elements.rs new file mode 100644 index 0000000000000..229ceb02bcafb --- /dev/null +++ b/crates/oxc_linter/src/rules/react/forbid_elements.rs @@ -0,0 +1,323 @@ +use oxc_ast::{AstKind, ast::Argument}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{Atom, CompactStr, GetSpan, Span}; +use rustc_hash::FxHashMap; +use serde_json::Value; + +use crate::{ + AstNode, + context::{ContextHost, LintContext}, + rule::Rule, + utils::{get_element_type, is_react_function_call}, +}; + +fn forbid_elements_diagnostic( + element: &str, + help: Option, + span: Span, +) -> OxcDiagnostic { + if let Some(help) = help { + return OxcDiagnostic::warn(format!("<{element}> is forbidden.")) + .with_help(help) + .with_label(span); + } + + OxcDiagnostic::warn(format!("<{element}> is forbidden.")).with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct ForbidElements(Box); + +impl std::ops::Deref for ForbidElements { + type Target = ForbidElementsConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Default, Clone)] +pub struct ForbidElementsConfig { + forbid_elements: FxHashMap>, +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Allows you to configure a list of forbidden elements and to specify their desired replacements. + /// + /// ### Why is this bad? + /// + /// You may want to forbid usage of certain elements in favor of others, (e.g. forbid all
and use instead) + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```jsx + /// // [1, { "forbid": ["button"] }] + ///
+ /// React.createElement('div', {}, React.createElement('button', {}, React.createElement('input'))); + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```jsx + /// // [1, { "forbid": ["button"] }] + /// ", + Some( + serde_json::json!([{ "forbid": [{ "element": "button" }, { "element": "input" }] }]), + ), + ), + ( + "", + Some(serde_json::json!([{ "forbid": [{ "element": "button" }, "input"] }])), + ), + ( + "", + Some(serde_json::json!([{ "forbid": ["input", { "element": "button" }] }])), + ), + ( + " + · ────── + ╰──── + + ⚠ eslint-plugin-react(forbid-elements): is forbidden. + ╭─[forbid_elements.tsx:1:10] + 1 │ + · ───── + ╰──── + + ⚠ eslint-plugin-react(forbid-elements): + · ────── + ╰──── + + ⚠ eslint-plugin-react(forbid-elements): is forbidden. + ╭─[forbid_elements.tsx:1:10] + 1 │ + · ───── + ╰──── + + ⚠ eslint-plugin-react(forbid-elements): + · ────── + ╰──── + + ⚠ eslint-plugin-react(forbid-elements): is forbidden. + ╭─[forbid_elements.tsx:1:10] + 1 │ + · ───── + ╰──── + + ⚠ eslint-plugin-react(forbid-elements):