diff --git a/.github/renovate.json b/.github/renovate.json index a86816298edf8f..5af0c3b7ed3aab 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,7 +1,6 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["github>Boshen/renovate", "helpers:pinGitHubActionDigests"], - "ignorePaths": [ - "crates/oxc_linter/fixtures/**" - ] + "ignorePaths": ["crates/oxc_linter/fixtures/**"], + "ignoreDeps": ["@types/vscode"] } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2dd4c5645f348a..98d16407358e95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,7 +147,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: taiki-e/checkout-action@b13d20b7cda4e2f325ef19895128f7ff735c0b3d # v1.3.1 - - uses: crate-ci/typos@d1c850b2b5d502763520c25fb4a6a1128ad99bd9 # v1.28.3 + - uses: crate-ci/typos@685eb3d55be2f85191e8c84acb9f44d7756f84ab # v1.29.4 with: files: . diff --git a/.typos.toml b/.typos.toml index 64c9b75e1f8d3a..2a22bd82cefc27 100644 --- a/.typos.toml +++ b/.typos.toml @@ -24,6 +24,7 @@ extend-exclude = [ "tasks/prettier_conformance/snapshots", "tasks/transform_conformance/tests/**/output.js", "tasks/transform_conformance/overrides", + "tasks/transform_conformance/snapshots", "npm/oxc-wasm/oxc_wasm.js", ] diff --git a/Cargo.lock b/Cargo.lock index d1d2be451c7269..a32903f61e60fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "assert-unchecked" version = "0.1.2" @@ -174,6 +180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", + "regex-automata", "serde", ] @@ -287,15 +294,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "unicode-width 0.1.14", - "windows-sys 0.52.0", + "once_cell", + "unicode-width 0.2.0", + "windows-sys 0.59.0", ] [[package]] @@ -482,9 +489,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" @@ -515,9 +522,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ "env_filter", "humantime", @@ -542,9 +549,12 @@ dependencies = [ [[package]] name = "fast-glob" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb10ed0f8a3dca52477be37ac0fb8f9d1fd4cd8d311b4484bdd45c1c56e0c9ec" +checksum = "0eca69ef247d19faa15ac0156968637440824e5ff22baa5ee0cd35b2f7ea6a0f" +dependencies = [ + "arrayvec", +] [[package]] name = "fastrand" @@ -554,9 +564,9 @@ checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fixedbitset" -version = "0.4.2" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" @@ -701,9 +711,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "globset" @@ -753,7 +763,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -964,12 +974,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.14.5", "serde", ] @@ -995,9 +1005,9 @@ checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] @@ -1082,7 +1092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1384,7 +1394,7 @@ checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" [[package]] name = "oxc" -version = "0.43.0" +version = "0.44.0" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1404,9 +1414,9 @@ dependencies = [ [[package]] name = "oxc-browserslist" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d240f6572a29895f324ad834a42a0c4bad0739269a08d0181ad7d6db7537e0d1" +checksum = "1b585351ba1b93f0bf9683df23285cc0978ec35a6e3074794815756086770c14" dependencies = [ "js-sys", "nom", @@ -1414,7 +1424,7 @@ dependencies = [ "serde", "serde-wasm-bindgen", "serde_json", - "thiserror", + "thiserror 2.0.9", "time", "wasm-bindgen", ] @@ -1429,7 +1439,7 @@ dependencies = [ "owo-colors", "oxc-miette-derive", "textwrap", - "thiserror", + "thiserror 1.0.69", "unicode-width 0.2.0", ] @@ -1446,7 +1456,7 @@ dependencies = [ [[package]] name = "oxc_allocator" -version = "0.43.0" +version = "0.44.0" dependencies = [ "allocator-api2", "bumpalo", @@ -1457,7 +1467,7 @@ dependencies = [ [[package]] name = "oxc_ast" -version = "0.43.0" +version = "0.44.0" dependencies = [ "bitflags 2.6.0", "cow-utils", @@ -1475,7 +1485,7 @@ dependencies = [ [[package]] name = "oxc_ast_macros" -version = "0.43.0" +version = "0.44.0" dependencies = [ "proc-macro2", "quote", @@ -1523,7 +1533,7 @@ dependencies = [ [[package]] name = "oxc_cfg" -version = "0.43.0" +version = "0.44.0" dependencies = [ "bitflags 2.6.0", "itertools", @@ -1536,7 +1546,7 @@ dependencies = [ [[package]] name = "oxc_codegen" -version = "0.43.0" +version = "0.44.0" dependencies = [ "assert-unchecked", "base64", @@ -1597,7 +1607,7 @@ dependencies = [ [[package]] name = "oxc_data_structures" -version = "0.43.0" +version = "0.44.0" dependencies = [ "assert-unchecked", "ropey", @@ -1605,7 +1615,7 @@ dependencies = [ [[package]] name = "oxc_diagnostics" -version = "0.43.0" +version = "0.44.0" dependencies = [ "oxc-miette", "rustc-hash", @@ -1613,7 +1623,7 @@ dependencies = [ [[package]] name = "oxc_ecmascript" -version = "0.43.0" +version = "0.44.0" dependencies = [ "num-bigint", "num-traits", @@ -1624,7 +1634,7 @@ dependencies = [ [[package]] name = "oxc_estree" -version = "0.43.0" +version = "0.44.0" dependencies = [ "serde", ] @@ -1641,7 +1651,7 @@ dependencies = [ [[package]] name = "oxc_isolated_declarations" -version = "0.43.0" +version = "0.44.0" dependencies = [ "bitflags 2.6.0", "insta", @@ -1683,7 +1693,7 @@ dependencies = [ [[package]] name = "oxc_linter" -version = "0.15.3" +version = "0.15.5" dependencies = [ "bitflags 2.6.0", "convert_case", @@ -1741,7 +1751,7 @@ dependencies = [ [[package]] name = "oxc_mangler" -version = "0.43.0" +version = "0.44.0" dependencies = [ "itertools", "oxc_ast", @@ -1752,7 +1762,7 @@ dependencies = [ [[package]] name = "oxc_minifier" -version = "0.43.0" +version = "0.44.0" dependencies = [ "cow-utils", "insta", @@ -1767,6 +1777,7 @@ dependencies = [ "oxc_syntax", "oxc_traverse", "pico-args", + "rustc-hash", ] [[package]] @@ -1787,20 +1798,24 @@ dependencies = [ name = "oxc_minsize" version = "0.0.0" dependencies = [ + "cow-utils", "flate2", "humansize", "oxc_allocator", "oxc_codegen", "oxc_minifier", "oxc_parser", + "oxc_semantic", "oxc_span", "oxc_tasks_common", + "oxc_transformer", "rustc-hash", + "similar-asserts", ] [[package]] name = "oxc_napi" -version = "0.43.0" +version = "0.44.0" dependencies = [ "napi", "napi-derive", @@ -1809,7 +1824,7 @@ dependencies = [ [[package]] name = "oxc_parser" -version = "0.43.0" +version = "0.44.0" dependencies = [ "assert-unchecked", "bitflags 2.6.0", @@ -1890,7 +1905,7 @@ dependencies = [ [[package]] name = "oxc_regular_expression" -version = "0.43.0" +version = "0.44.0" dependencies = [ "oxc_allocator", "oxc_ast_macros", @@ -1905,9 +1920,9 @@ dependencies = [ [[package]] name = "oxc_resolver" -version = "3.0.1" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f7b4604348e24a53f114dabe3001306ca720cb83d007b1140afc26c1fdfa7b" +checksum = "bed381b6ab4bbfebfc7a011ad43b110ace8d201d02a39c0e09855f16b8f3f741" dependencies = [ "cfg-if", "dashmap 6.1.0", @@ -1918,13 +1933,13 @@ dependencies = [ "serde", "serde_json", "simdutf8", - "thiserror", + "thiserror 1.0.69", "tracing", ] [[package]] name = "oxc_semantic" -version = "0.43.0" +version = "0.44.0" dependencies = [ "assert-unchecked", "hashbrown 0.15.2", @@ -1948,9 +1963,9 @@ dependencies = [ [[package]] name = "oxc_sourcemap" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9d811a7c2879984b0fbb70ddbf0818f80247dccfcb5014be997019683aa5dfc" +checksum = "48557f779d04c8bfa8a930db5a3c35c0a86ff4e6bf1552ce446b9596a6e77c42" dependencies = [ "base64-simd", "cfg-if", @@ -1965,7 +1980,7 @@ dependencies = [ [[package]] name = "oxc_span" -version = "0.43.0" +version = "0.44.0" dependencies = [ "compact_str", "oxc-miette", @@ -1978,10 +1993,11 @@ dependencies = [ [[package]] name = "oxc_syntax" -version = "0.43.0" +version = "0.44.0" dependencies = [ "assert-unchecked", "bitflags 2.6.0", + "cow-utils", "nonmax", "oxc_allocator", "oxc_ast_macros", @@ -2036,7 +2052,7 @@ dependencies = [ [[package]] name = "oxc_transform_napi" -version = "0.43.0" +version = "0.44.0" dependencies = [ "napi", "napi-build", @@ -2049,7 +2065,7 @@ dependencies = [ [[package]] name = "oxc_transformer" -version = "0.43.0" +version = "0.44.0" dependencies = [ "base64", "compact_str", @@ -2082,7 +2098,7 @@ dependencies = [ [[package]] name = "oxc_traverse" -version = "0.43.0" +version = "0.44.0" dependencies = [ "compact_str", "itoa", @@ -2113,7 +2129,7 @@ dependencies = [ [[package]] name = "oxlint" -version = "0.15.3" +version = "0.15.5" dependencies = [ "bpaf", "glob", @@ -2165,7 +2181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.69", "ucd-trie", ] @@ -2205,9 +2221,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.5" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +checksum = "e2b1374ec32450264534c67d1ccb5ca09818c4db8fd87cf97478d0df2fa44c65" dependencies = [ "fixedbitset", "indexmap", @@ -2326,9 +2342,9 @@ checksum = "8bccbff07d5ed689c4087d20d7307a52ab6141edeedf487c3876a55b86cf63df" [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -2615,9 +2631,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -2635,9 +2651,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -2668,9 +2684,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "indexmap", "itoa", @@ -2747,6 +2763,20 @@ name = "similar" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +dependencies = [ + "bstr", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe85670573cd6f0fa97940f26e7e6601213c3b0555246c24234131f88c5709e" +dependencies = [ + "console", + "similar", +] [[package]] name = "siphasher" @@ -2838,9 +2868,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" dependencies = [ "proc-macro2", "quote", @@ -2888,7 +2918,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +dependencies = [ + "thiserror-impl 2.0.9", ] [[package]] @@ -2902,6 +2941,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.8" diff --git a/Cargo.toml b/Cargo.toml index 8c41dff0112930..6867f593fc1859 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,28 +78,29 @@ doc_lazy_continuation = "allow" # FIXME [workspace.dependencies] # publish = true -oxc = { version = "0.43.0", path = "crates/oxc" } -oxc_allocator = { version = "0.43.0", path = "crates/oxc_allocator" } -oxc_ast = { version = "0.43.0", path = "crates/oxc_ast" } -oxc_ast_macros = { version = "0.43.0", path = "crates/oxc_ast_macros" } -oxc_cfg = { version = "0.43.0", path = "crates/oxc_cfg" } -oxc_codegen = { version = "0.43.0", path = "crates/oxc_codegen" } -oxc_data_structures = { version = "0.43.0", path = "crates/oxc_data_structures" } -oxc_diagnostics = { version = "0.43.0", path = "crates/oxc_diagnostics" } -oxc_ecmascript = { version = "0.43.0", path = "crates/oxc_ecmascript" } -oxc_estree = { version = "0.43.0", path = "crates/oxc_estree" } -oxc_isolated_declarations = { version = "0.43.0", path = "crates/oxc_isolated_declarations" } -oxc_mangler = { version = "0.43.0", path = "crates/oxc_mangler" } -oxc_minifier = { version = "0.43.0", path = "crates/oxc_minifier" } -oxc_napi = { version = "0.43.0", path = "crates/oxc_napi" } -oxc_parser = { version = "0.43.0", path = "crates/oxc_parser" } -oxc_regular_expression = { version = "0.43.0", path = "crates/oxc_regular_expression" } -oxc_semantic = { version = "0.43.0", path = "crates/oxc_semantic" } -oxc_span = { version = "0.43.0", path = "crates/oxc_span" } -oxc_syntax = { version = "0.43.0", path = "crates/oxc_syntax" } -oxc_transform_napi = { version = "0.43.0", path = "napi/transform" } -oxc_transformer = { version = "0.43.0", path = "crates/oxc_transformer" } -oxc_traverse = { version = "0.43.0", path = "crates/oxc_traverse" } +oxc = { version = "0.44.0", path = "crates/oxc" } +oxc_allocator = { version = "0.44.0", path = "crates/oxc_allocator" } +oxc_ast = { version = "0.44.0", path = "crates/oxc_ast" } +oxc_ast_macros = { version = "0.44.0", path = "crates/oxc_ast_macros" } +oxc_cfg = { version = "0.44.0", path = "crates/oxc_cfg" } +oxc_codegen = { version = "0.44.0", path = "crates/oxc_codegen" } +oxc_data_structures = { version = "0.44.0", path = "crates/oxc_data_structures" } +oxc_diagnostics = { version = "0.44.0", path = "crates/oxc_diagnostics" } +oxc_ecmascript = { version = "0.44.0", path = "crates/oxc_ecmascript" } +oxc_estree = { version = "0.44.0", path = "crates/oxc_estree" } +oxc_isolated_declarations = { version = "0.44.0", path = "crates/oxc_isolated_declarations" } +oxc_mangler = { version = "0.44.0", path = "crates/oxc_mangler" } +oxc_minifier = { version = "0.44.0", path = "crates/oxc_minifier" } +oxc_napi = { version = "0.44.0", path = "crates/oxc_napi" } +oxc_parser = { version = "0.44.0", path = "crates/oxc_parser" } +oxc_parser_napi = { version = "0.44.0", path = "napi/parser" } +oxc_regular_expression = { version = "0.44.0", path = "crates/oxc_regular_expression" } +oxc_semantic = { version = "0.44.0", path = "crates/oxc_semantic" } +oxc_span = { version = "0.44.0", path = "crates/oxc_span" } +oxc_syntax = { version = "0.44.0", path = "crates/oxc_syntax" } +oxc_transform_napi = { version = "0.44.0", path = "napi/transform" } +oxc_transformer = { version = "0.44.0", path = "crates/oxc_transformer" } +oxc_traverse = { version = "0.44.0", path = "crates/oxc_traverse" } # publish = false oxc_linter = { path = "crates/oxc_linter" } @@ -114,7 +115,7 @@ napi-build = "2.1.3" napi-derive = "3.0.0-alpha" # Relaxed version so the user can decide which version to use. -indexmap = "2.7" +indexmap = "2" proc-macro2 = "1" quote = "1" rustc-hash = "2" @@ -156,7 +157,7 @@ hashbrown = "0.15.2" humansize = "2.1.3" ignore = "0.4.23" insta = "1.41.1" -itertools = "0.13.0" +itertools = "0.14.0" itoa = "1.0.14" jemallocator = "0.5.4" json-strip-comments = "1.0.4" @@ -171,7 +172,7 @@ mime_guess = "2.0.5" nonmax = "0.5.5" num-bigint = "0.4.6" num-traits = "0.2.19" -petgraph = "0.6.5" +petgraph = "0.7.0" phf = "0.11.2" pico-args = "0.5.0" prettyplease = "0.2.25" @@ -189,6 +190,7 @@ serde-wasm-bindgen = "0.6.5" sha1 = "0.10.6" simdutf8 = { version = "0.1.5", features = ["aarch64_neon"] } similar = "2.6.0" +similar-asserts = "1.6.0" string_wizard = "0.0.25" tempfile = "3.14.0" tokio = "1.42.0" @@ -201,7 +203,7 @@ walkdir = "2.5.0" wasm-bindgen = "0.2.99" [workspace.metadata.cargo-shear] -ignored = ["napi", "oxc_transform_napi", "prettyplease"] +ignored = ["napi", "oxc_transform_napi", "oxc_parser_napi", "prettyplease"] [profile.dev] # Disabling debug info speeds up local and CI builds, diff --git a/apps/oxlint/CHANGELOG.md b/apps/oxlint/CHANGELOG.md index f54162f99ba5b6..2af5c73d1de785 100644 --- a/apps/oxlint/CHANGELOG.md +++ b/apps/oxlint/CHANGELOG.md @@ -4,6 +4,27 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.15.5] - 2025-01-02 + +### Bug Fixes + +- 2b14a6f linter: Fix `ignorePattern` config for windows (#8214) (Alexander S.) + +### Testing + +- cb709c9 linter: Fix some oxlint tests on windows (#8204) (Cameron) + +## [0.15.4] - 2024-12-30 + +### Bug Fixes + +- f3050d4 linter: Exclude svelte files from `no_unused_vars` rule (#8170) (Yuichiro Yamashita) + +### Refactor + +- 6da0b21 oxlint: Remove unused `git.rs` (#7990) (Boshen) +- 58e7777 oxlint: Remove extra if check in `Walkdir` (#7989) (Boshen) + ## [0.15.3] - 2024-12-17 ### Styling diff --git a/apps/oxlint/Cargo.toml b/apps/oxlint/Cargo.toml index c4948db0cbba08..676ebf59fe45a6 100644 --- a/apps/oxlint/Cargo.toml +++ b/apps/oxlint/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxlint" -version = "0.15.3" +version = "0.15.5" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/apps/oxlint/src/lint.rs b/apps/oxlint/src/lint.rs index ffe842a8613742..48091425846a54 100644 --- a/apps/oxlint/src/lint.rs +++ b/apps/oxlint/src/lint.rs @@ -6,10 +6,11 @@ use std::{ }; use ignore::gitignore::Gitignore; + use oxc_diagnostics::{DiagnosticService, GraphicalReportHandler}; use oxc_linter::{ - loader::LINT_PARTIAL_LOADER_EXT, AllowWarnDeny, InvalidFilterKind, LintFilter, LintService, - LintServiceOptions, Linter, LinterBuilder, Oxlintrc, + loader::LINT_PARTIAL_LOADER_EXT, AllowWarnDeny, ConfigStoreBuilder, InvalidFilterKind, + LintFilter, LintOptions, LintService, LintServiceOptions, Linter, Oxlintrc, }; use oxc_span::VALID_EXTENSIONS; @@ -21,6 +22,7 @@ use crate::{ walk::{Extensions, Walk}, }; +#[derive(Debug)] pub struct LintRunner { options: LintCommand, cwd: PathBuf, @@ -114,13 +116,9 @@ impl Runner for LintRunner { } let mut oxlintrc = config_search_result.unwrap(); + let oxlint_wd = oxlintrc.path.parent().unwrap_or(&self.cwd).to_path_buf(); - let ignore_paths = oxlintrc - .ignore_patterns - .iter() - .map(|value| oxlintrc.path.parent().unwrap().join(value)) - .collect::>(); - let paths = Walk::new(&paths, &ignore_options, &ignore_paths) + let paths = Walk::new(&oxlint_wd, &paths, &ignore_options, &oxlintrc.ignore_patterns) .with_extensions(Extensions(extensions)) .paths(); @@ -130,20 +128,32 @@ impl Runner for LintRunner { let oxlintrc_for_print = if misc_options.print_config { Some(oxlintrc.clone()) } else { None }; - let builder = LinterBuilder::from_oxlintrc(false, oxlintrc) - .with_filters(filter) - .with_fix(fix_options.fix_kind()); + let config_builder = + ConfigStoreBuilder::from_oxlintrc(false, oxlintrc).with_filters(filter); if let Some(basic_config_file) = oxlintrc_for_print { return CliRunResult::PrintConfigResult { - config_file: builder.resolve_final_config_file(basic_config_file), + config_file: config_builder.resolve_final_config_file(basic_config_file), }; } let mut options = LintServiceOptions::new(self.cwd, paths) - .with_cross_module(builder.plugins().has_import()); + .with_cross_module(config_builder.plugins().has_import()); + + let lint_config = match config_builder.build() { + Ok(config) => config, + Err(diagnostic) => { + let handler = GraphicalReportHandler::new(); + let mut err = String::new(); + handler.render_report(&mut err, &diagnostic).unwrap(); + return CliRunResult::InvalidOptions { + message: format!("Failed to parse configuration file.\n{err}"), + }; + } + }; - let linter = builder.build(); + let linter = + Linter::new(LintOptions::default(), lint_config).with_fix(fix_options.fix_kind()); let tsconfig = basic_options.tsconfig; if let Some(path) = tsconfig.as_ref() { @@ -258,7 +268,10 @@ impl LintRunner { // when no file is found, the default configuration is returned fn find_oxlint_config(cwd: &Path, config: Option<&PathBuf>) -> Result { if let Some(config_path) = config { - return match Oxlintrc::from_file(config_path) { + let mut full_path = cwd.to_path_buf(); + full_path.push(config_path); + + return match Oxlintrc::from_file(&full_path) { Ok(config) => Ok(config), Err(diagnostic) => { let handler = GraphicalReportHandler::new(); @@ -281,9 +294,9 @@ impl LintRunner { } } -#[cfg(all(test, not(target_os = "windows")))] +#[cfg(test)] mod test { - use std::env; + use std::{env, path::MAIN_SEPARATOR_STR}; use super::LintRunner; use crate::cli::{lint_command, CliRunResult, LintResult, Runner}; @@ -301,10 +314,19 @@ mod test { fn test_with_cwd(cwd: &str, args: &[&str]) -> LintResult { let mut new_args = vec!["--silent"]; new_args.extend(args); + let options = lint_command().run_inner(new_args.as_slice()).unwrap(); let mut current_cwd = env::current_dir().unwrap(); - current_cwd.push(cwd); + + let part_cwd = if MAIN_SEPARATOR_STR == "/" { + cwd.into() + } else { + #[expect(clippy::disallowed_methods)] + cwd.replace('/', MAIN_SEPARATOR_STR) + }; + + current_cwd.push(part_cwd); match LintRunner::new(options).with_cwd(current_cwd).run() { CliRunResult::LintResult(lint_result) => lint_result, @@ -343,6 +365,8 @@ mod test { assert_eq!(result.number_of_errors, 0); } + // exclude because we are working with Glob, which only supports the current working directory + #[cfg(all(test, not(target_os = "windows")))] #[test] fn cwd() { let args = &["debugger.js"]; @@ -371,6 +395,8 @@ mod test { assert_eq!(result.number_of_errors, 0); } + // ToDo: lints all files under windows + #[cfg(all(test, not(target_os = "windows")))] #[test] fn wrong_extension() { let args = &["foo.asdf"]; @@ -628,7 +654,7 @@ mod test { let args = &["fixtures/svelte/debugger.svelte"]; let result = test(args); assert_eq!(result.number_of_files, 1); - assert_eq!(result.number_of_warnings, 2); + assert_eq!(result.number_of_warnings, 1); assert_eq!(result.number_of_errors, 0); } @@ -679,12 +705,16 @@ mod test { use std::fs; let file = "fixtures/linter/fix.js"; let args = &["--fix", file]; - let content = fs::read_to_string(file).unwrap(); + let content_original = fs::read_to_string(file).unwrap(); + #[expect(clippy::disallowed_methods)] + let content = content_original.replace("\r\n", "\n"); assert_eq!(&content, "debugger\n"); // Apply fix to the file. let _ = test(args); - assert_eq!(fs::read_to_string(file).unwrap(), "\n"); + #[expect(clippy::disallowed_methods)] + let new_content = fs::read_to_string(file).unwrap().replace("\r\n", "\n"); + assert_eq!(new_content, "\n"); // File should not be modified if no fix is applied. let modified_before = fs::metadata(file).unwrap().modified().unwrap(); @@ -693,7 +723,7 @@ mod test { assert_eq!(modified_before, modified_after); // Write the file back. - fs::write(file, content).unwrap(); + fs::write(file, content_original).unwrap(); } #[test] @@ -705,8 +735,10 @@ mod test { panic!("Expected PrintConfigResult, got {ret:?}") }; - let expect_json = - std::fs::read_to_string("fixtures/print_config/normal/expect.json").unwrap(); + #[expect(clippy::disallowed_methods)] + let expect_json = std::fs::read_to_string("fixtures/print_config/normal/expect.json") + .unwrap() + .replace("\r\n", "\n"); assert_eq!(config, expect_json.trim()); } @@ -727,8 +759,11 @@ mod test { panic!("Expected PrintConfigResult, got {ret:?}") }; - let expect_json = - std::fs::read_to_string("fixtures/print_config/ban_rules/expect.json").unwrap(); + #[expect(clippy::disallowed_methods)] + let expect_json = std::fs::read_to_string("fixtures/print_config/ban_rules/expect.json") + .unwrap() + .replace("\r\n", "\n"); + assert_eq!(config, expect_json.trim()); } @@ -774,11 +809,10 @@ mod test { #[test] fn test_config_ignore_patterns_directory() { - let result = test(&[ - "-c", - "fixtures/config_ignore_patterns/ignore_directory/eslintrc.json", + let result = test_with_cwd( "fixtures/config_ignore_patterns/ignore_directory", - ]); + &["-c", "eslintrc.json"], + ); assert_eq!(result.number_of_files, 1); } diff --git a/apps/oxlint/src/walk.rs b/apps/oxlint/src/walk.rs index 10cddc7e97b026..9d5c00b27a4f97 100644 --- a/apps/oxlint/src/walk.rs +++ b/apps/oxlint/src/walk.rs @@ -64,14 +64,14 @@ impl ignore::ParallelVisitor for WalkCollector { } } } - impl Walk { /// Will not canonicalize paths. /// # Panics pub fn new( + current_path: &PathBuf, paths: &[PathBuf], options: &IgnoreOptions, - config_ignore_patterns: &[PathBuf], + config_ignore_patterns: &Vec, ) -> Self { assert!(!paths.is_empty(), "At least one path must be provided to Walk::new"); @@ -91,7 +91,7 @@ impl Walk { if !options.no_ignore { inner.add_custom_ignore_filename(&options.ignore_path); - let mut override_builder = OverrideBuilder::new(Path::new("/")); + let mut override_builder = OverrideBuilder::new(current_path); if !options.ignore_pattern.is_empty() { for pattern in &options.ignore_pattern { // Meaning of ignore pattern is reversed @@ -103,7 +103,7 @@ impl Walk { if !config_ignore_patterns.is_empty() { for pattern in config_ignore_patterns { - let pattern = format!("!{}", pattern.to_str().unwrap()); + let pattern = format!("!{pattern}"); override_builder.add(&pattern).unwrap(); } } @@ -148,7 +148,7 @@ impl Walk { #[cfg(test)] mod test { - use std::{env, ffi::OsString}; + use std::{env, ffi::OsString, path::PathBuf}; use super::{Extensions, Walk}; use crate::cli::IgnoreOptions; @@ -164,7 +164,7 @@ mod test { symlinks: false, }; - let mut paths = Walk::new(&fixtures, &ignore_options, &[]) + let mut paths = Walk::new(&PathBuf::from("/"), &fixtures, &ignore_options, &vec![]) .with_extensions(Extensions(["js", "vue"].to_vec())) .paths() .into_iter() diff --git a/crates/oxc/Cargo.toml b/crates/oxc/Cargo.toml index 8b72f7782a1206..e7a1af65ca7112 100644 --- a/crates/oxc/Cargo.toml +++ b/crates/oxc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_allocator/Cargo.toml b/crates/oxc_allocator/Cargo.toml index 7949cdcd32a3e2..21596de120bd22 100644 --- a/crates/oxc_allocator/Cargo.toml +++ b/crates/oxc_allocator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_allocator" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_allocator/src/vec.rs b/crates/oxc_allocator/src/vec.rs index 4b1e22e30252de..29b0723a50d9b6 100644 --- a/crates/oxc_allocator/src/vec.rs +++ b/crates/oxc_allocator/src/vec.rs @@ -9,6 +9,7 @@ use std::{ mem::ManuallyDrop, ops, ptr::NonNull, + slice::SliceIndex, }; use allocator_api2::vec; @@ -253,16 +254,24 @@ impl<'alloc, T> IntoIterator for &'alloc Vec<'alloc, T> { } } -impl ops::Index for Vec<'_, T> { - type Output = T; +impl ops::Index for Vec<'_, T> +where + I: SliceIndex<[T]>, +{ + type Output = I::Output; - fn index(&self, index: usize) -> &Self::Output { + #[inline] + fn index(&self, index: I) -> &Self::Output { self.0.index(index) } } -impl ops::IndexMut for Vec<'_, T> { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { +impl ops::IndexMut for Vec<'_, T> +where + I: SliceIndex<[T]>, +{ + #[inline] + fn index_mut(&mut self, index: I) -> &mut Self::Output { self.0.index_mut(index) } } diff --git a/crates/oxc_ast/CHANGELOG.md b/crates/oxc_ast/CHANGELOG.md index 3a82de3741aa81..e194520cc56c39 100644 --- a/crates/oxc_ast/CHANGELOG.md +++ b/crates/oxc_ast/CHANGELOG.md @@ -4,6 +4,17 @@ 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.44.0] - 2024-12-25 + +- ad2a620 ast: [**BREAKING**] Add missing `AssignmentTargetProperty::computed` (#8097) (Boshen) + +### Features + +- c2daa20 ast: Add `Expression::into_inner_expression` (#8048) (overlookmotel) + +### Bug Fixes + + ## [0.43.0] - 2024-12-21 ### Features diff --git a/crates/oxc_ast/Cargo.toml b/crates/oxc_ast/Cargo.toml index bef862fbe6fd73..d4a7e8524c19e7 100644 --- a/crates/oxc_ast/Cargo.toml +++ b/crates/oxc_ast/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_ast" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 48a1330bef39e1..fe0c5e21b8767f 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -1477,7 +1477,6 @@ pub struct AssignmentPattern<'a> { pub right: Expression<'a>, } -// See serializer in serialize.rs #[ast(visit)] #[derive(Debug)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] @@ -1499,7 +1498,6 @@ pub struct BindingProperty<'a> { pub computed: bool, } -// See serializer in serialize.rs #[ast(visit)] #[derive(Debug)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] @@ -1644,7 +1642,6 @@ pub enum FunctionType { } /// -// See serializer in serialize.rs #[ast(visit)] #[derive(Debug)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index b024d418e7cff0..61bbe2661374c1 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -656,7 +656,7 @@ pub struct TSNullKeyword { pub span: Span, } -/// TypeScript `null` Keyword +/// TypeScript `undefined` Keyword /// /// ## Example /// ```ts diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs index e4b6e85877d9e9..21e7ccb1c470ad 100644 --- a/crates/oxc_ast/src/ast_impl/js.rs +++ b/crates/oxc_ast/src/ast_impl/js.rs @@ -2,8 +2,8 @@ #![warn(missing_docs)] use std::{borrow::Cow, fmt}; -use oxc_allocator::{Box, FromIn, Vec}; -use oxc_span::{Atom, GetSpan, Span}; +use oxc_allocator::{Box, Vec}; +use oxc_span::{Atom, Span}; use oxc_syntax::{operator::UnaryOperator, scope::ScopeFlags, symbol::SymbolId}; use crate::ast::*; @@ -72,6 +72,11 @@ impl<'a> Expression<'a> { matches!(self, Self::StringLiteral(_) | Self::TemplateLiteral(_)) } + /// Return `true` if the expression is a plain template. + pub fn is_no_substitution_template(&self) -> bool { + matches!(self, Expression::TemplateLiteral(e) if e.is_no_substitution_template()) + } + /// Returns `true` for [numeric](NumericLiteral) and [big int](BigIntLiteral) literals. pub fn is_number_literal(&self) -> bool { matches!(self, Self::NumericLiteral(_) | Self::BigIntLiteral(_)) @@ -767,14 +772,21 @@ impl Statement<'_> { | Statement::WhileStatement(_) ) } -} -impl<'a> FromIn<'a, Expression<'a>> for Statement<'a> { - fn from_in(expression: Expression<'a>, allocator: &'a oxc_allocator::Allocator) -> Self { - Statement::ExpressionStatement(Box::from_in( - ExpressionStatement { span: expression.span(), expression }, - allocator, - )) + /// Returns the single statement from block statement, or self + pub fn get_one_child(&self) -> Option<&Self> { + if let Statement::BlockStatement(block_stmt) = self { + return (block_stmt.body.len() == 1).then(|| &block_stmt.body[0]); + } + Some(self) + } + + /// Returns the single statement from block statement, or self + pub fn get_one_child_mut(&mut self) -> Option<&mut Self> { + if let Statement::BlockStatement(block_stmt) = self { + return (block_stmt.body.len() == 1).then_some(&mut block_stmt.body[0]); + } + Some(self) } } @@ -886,6 +898,11 @@ impl fmt::Display for VariableDeclarationKind { } impl ForStatementInit<'_> { + /// Is `var` declaration + pub fn is_var_declaration(&self) -> bool { + matches!(self, Self::VariableDeclaration(decl) if decl.kind.is_var()) + } + /// LexicalDeclaration[In, Yield, Await] : /// LetOrConst BindingList[?In, ?Yield, ?Await] ; pub fn is_lexical_declaration(&self) -> bool { diff --git a/crates/oxc_ast/src/ast_impl/ts.rs b/crates/oxc_ast/src/ast_impl/ts.rs index 3cbeb975ac61f4..06c02345f51abc 100644 --- a/crates/oxc_ast/src/ast_impl/ts.rs +++ b/crates/oxc_ast/src/ast_impl/ts.rs @@ -25,19 +25,14 @@ impl<'a> TSType<'a> { /// /// For qualified (i.e. namespaced) types, the left-most identifier is /// returned. - /// - /// ``` - /// let ty = get_type_for("foo.bar.Baz"); // TSType::TSQualifiedName - /// get_identifier_reference(&ty); // Some(IdentifierReference { name: "foo", .. }) - /// ``` - pub fn get_identifier_reference(&self) -> Option> { + pub fn get_identifier_reference(&self) -> Option<&IdentifierReference<'a>> { match self { TSType::TSTypeReference(reference) => { - Some(TSTypeName::get_first_name(&reference.type_name)) + Some(reference.type_name.get_identifier_reference()) } - TSType::TSQualifiedName(qualified) => Some(TSTypeName::get_first_name(&qualified.left)), + TSType::TSQualifiedName(name) => Some(name.left.get_identifier_reference()), TSType::TSTypeQuery(query) => match &query.expr_name { - TSTypeQueryExprName::IdentifierReference(ident) => Some((*ident).clone()), + TSTypeQueryExprName::IdentifierReference(ident) => Some(ident), _ => None, }, _ => None, @@ -87,10 +82,10 @@ impl<'a> TSTypeName<'a> { /// type Foo = Bar; // -> Bar /// type Foo = Bar.Baz; // -> Bar /// ``` - pub fn get_first_name(name: &TSTypeName<'a>) -> IdentifierReference<'a> { - match name { - TSTypeName::IdentifierReference(name) => (*name).clone(), - TSTypeName::QualifiedName(name) => TSTypeName::get_first_name(&name.left), + pub fn get_identifier_reference(&self) -> &IdentifierReference<'a> { + match self { + TSTypeName::IdentifierReference(ident) => ident, + TSTypeName::QualifiedName(name) => name.left.get_identifier_reference(), } } diff --git a/crates/oxc_ast/src/generated/ast_builder.rs b/crates/oxc_ast/src/generated/ast_builder.rs index 5f16d9ccfb02b6..94d973544c94d9 100644 --- a/crates/oxc_ast/src/generated/ast_builder.rs +++ b/crates/oxc_ast/src/generated/ast_builder.rs @@ -382,7 +382,7 @@ impl<'a> AstBuilder<'a> { /// - value: The boolean value itself #[inline] pub fn expression_boolean_literal(self, span: Span, value: bool) -> Expression<'a> { - Expression::BooleanLiteral(self.alloc(self.boolean_literal(span, value))) + Expression::BooleanLiteral(self.alloc_boolean_literal(span, value)) } /// Build an [`Expression::NullLiteral`] @@ -393,7 +393,7 @@ impl<'a> AstBuilder<'a> { /// - span: Node location in source code #[inline] pub fn expression_null_literal(self, span: Span) -> Expression<'a> { - Expression::NullLiteral(self.alloc(self.null_literal(span))) + Expression::NullLiteral(self.alloc_null_literal(span)) } /// Build an [`Expression::NumericLiteral`] @@ -413,7 +413,7 @@ impl<'a> AstBuilder<'a> { raw: Option>, base: NumberBase, ) -> Expression<'a> { - Expression::NumericLiteral(self.alloc(self.numeric_literal(span, value, raw, base))) + Expression::NumericLiteral(self.alloc_numeric_literal(span, value, raw, base)) } /// Build an [`Expression::BigIntLiteral`] @@ -434,7 +434,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - Expression::BigIntLiteral(self.alloc(self.big_int_literal(span, raw, base))) + Expression::BigIntLiteral(self.alloc_big_int_literal(span, raw, base)) } /// Build an [`Expression::RegExpLiteral`] @@ -452,7 +452,7 @@ impl<'a> AstBuilder<'a> { regex: RegExp<'a>, raw: Option>, ) -> Expression<'a> { - Expression::RegExpLiteral(self.alloc(self.reg_exp_literal(span, regex, raw))) + Expression::RegExpLiteral(self.alloc_reg_exp_literal(span, regex, raw)) } /// Build an [`Expression::StringLiteral`] @@ -473,7 +473,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - Expression::StringLiteral(self.alloc(self.string_literal(span, value, raw))) + Expression::StringLiteral(self.alloc_string_literal(span, value, raw)) } /// Build an [`Expression::TemplateLiteral`] @@ -491,7 +491,7 @@ impl<'a> AstBuilder<'a> { quasis: Vec<'a, TemplateElement<'a>>, expressions: Vec<'a, Expression<'a>>, ) -> Expression<'a> { - Expression::TemplateLiteral(self.alloc(self.template_literal(span, quasis, expressions))) + Expression::TemplateLiteral(self.alloc_template_literal(span, quasis, expressions)) } /// Build an [`Expression::Identifier`] @@ -506,7 +506,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - Expression::Identifier(self.alloc(self.identifier_reference(span, name))) + Expression::Identifier(self.alloc_identifier_reference(span, name)) } /// Build an [`Expression::MetaProperty`] @@ -524,7 +524,7 @@ impl<'a> AstBuilder<'a> { meta: IdentifierName<'a>, property: IdentifierName<'a>, ) -> Expression<'a> { - Expression::MetaProperty(self.alloc(self.meta_property(span, meta, property))) + Expression::MetaProperty(self.alloc_meta_property(span, meta, property)) } /// Build an [`Expression::Super`] @@ -535,7 +535,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn expression_super(self, span: Span) -> Expression<'a> { - Expression::Super(self.alloc(self.super_(span))) + Expression::Super(self.alloc_super(span)) } /// Build an [`Expression::ArrayExpression`] @@ -553,11 +553,7 @@ impl<'a> AstBuilder<'a> { elements: Vec<'a, ArrayExpressionElement<'a>>, trailing_comma: Option, ) -> Expression<'a> { - Expression::ArrayExpression(self.alloc(self.array_expression( - span, - elements, - trailing_comma, - ))) + Expression::ArrayExpression(self.alloc_array_expression(span, elements, trailing_comma)) } /// Build an [`Expression::ArrowFunctionExpression`] @@ -589,7 +585,7 @@ impl<'a> AstBuilder<'a> { T3: IntoIn<'a, Option>>>, T4: IntoIn<'a, Box<'a, FunctionBody<'a>>>, { - Expression::ArrowFunctionExpression(self.alloc(self.arrow_function_expression( + Expression::ArrowFunctionExpression(self.alloc_arrow_function_expression( span, expression, r#async, @@ -597,7 +593,7 @@ impl<'a> AstBuilder<'a> { params, return_type, body, - ))) + )) } /// Build an [`Expression::AssignmentExpression`] @@ -618,7 +614,7 @@ impl<'a> AstBuilder<'a> { right: Expression<'a>, ) -> Expression<'a> { Expression::AssignmentExpression( - self.alloc(self.assignment_expression(span, operator, left, right)), + self.alloc_assignment_expression(span, operator, left, right), ) } @@ -631,7 +627,7 @@ impl<'a> AstBuilder<'a> { /// - argument #[inline] pub fn expression_await(self, span: Span, argument: Expression<'a>) -> Expression<'a> { - Expression::AwaitExpression(self.alloc(self.await_expression(span, argument))) + Expression::AwaitExpression(self.alloc_await_expression(span, argument)) } /// Build an [`Expression::BinaryExpression`] @@ -651,9 +647,7 @@ impl<'a> AstBuilder<'a> { operator: BinaryOperator, right: Expression<'a>, ) -> Expression<'a> { - Expression::BinaryExpression( - self.alloc(self.binary_expression(span, left, operator, right)), - ) + Expression::BinaryExpression(self.alloc_binary_expression(span, left, operator, right)) } /// Build an [`Expression::CallExpression`] @@ -678,13 +672,13 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - Expression::CallExpression(self.alloc(self.call_expression( + Expression::CallExpression(self.alloc_call_expression( span, callee, type_parameters, arguments, optional, - ))) + )) } /// Build an [`Expression::ChainExpression`] @@ -696,7 +690,7 @@ impl<'a> AstBuilder<'a> { /// - expression #[inline] pub fn expression_chain(self, span: Span, expression: ChainElement<'a>) -> Expression<'a> { - Expression::ChainExpression(self.alloc(self.chain_expression(span, expression))) + Expression::ChainExpression(self.alloc_chain_expression(span, expression)) } /// Build an [`Expression::ClassExpression`] @@ -735,7 +729,7 @@ impl<'a> AstBuilder<'a> { T2: IntoIn<'a, Option>>>, T3: IntoIn<'a, Box<'a, ClassBody<'a>>>, { - Expression::ClassExpression(self.alloc(self.class( + Expression::ClassExpression(self.alloc_class( span, r#type, decorators, @@ -747,7 +741,7 @@ impl<'a> AstBuilder<'a> { body, r#abstract, declare, - ))) + )) } /// Build an [`Expression::ConditionalExpression`] @@ -768,7 +762,7 @@ impl<'a> AstBuilder<'a> { alternate: Expression<'a>, ) -> Expression<'a> { Expression::ConditionalExpression( - self.alloc(self.conditional_expression(span, test, consequent, alternate)), + self.alloc_conditional_expression(span, test, consequent, alternate), ) } @@ -810,7 +804,7 @@ impl<'a> AstBuilder<'a> { T4: IntoIn<'a, Option>>>, T5: IntoIn<'a, Option>>>, { - Expression::FunctionExpression(self.alloc(self.function( + Expression::FunctionExpression(self.alloc_function( span, r#type, id, @@ -822,7 +816,7 @@ impl<'a> AstBuilder<'a> { params, return_type, body, - ))) + )) } /// Build an [`Expression::ImportExpression`] @@ -842,9 +836,7 @@ impl<'a> AstBuilder<'a> { arguments: Vec<'a, Expression<'a>>, phase: Option, ) -> Expression<'a> { - Expression::ImportExpression( - self.alloc(self.import_expression(span, source, arguments, phase)), - ) + Expression::ImportExpression(self.alloc_import_expression(span, source, arguments, phase)) } /// Build an [`Expression::LogicalExpression`] @@ -864,9 +856,7 @@ impl<'a> AstBuilder<'a> { operator: LogicalOperator, right: Expression<'a>, ) -> Expression<'a> { - Expression::LogicalExpression( - self.alloc(self.logical_expression(span, left, operator, right)), - ) + Expression::LogicalExpression(self.alloc_logical_expression(span, left, operator, right)) } /// Build an [`Expression::NewExpression`] @@ -889,12 +879,12 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - Expression::NewExpression(self.alloc(self.new_expression( + Expression::NewExpression(self.alloc_new_expression( span, callee, arguments, type_parameters, - ))) + )) } /// Build an [`Expression::ObjectExpression`] @@ -912,11 +902,7 @@ impl<'a> AstBuilder<'a> { properties: Vec<'a, ObjectPropertyKind<'a>>, trailing_comma: Option, ) -> Expression<'a> { - Expression::ObjectExpression(self.alloc(self.object_expression( - span, - properties, - trailing_comma, - ))) + Expression::ObjectExpression(self.alloc_object_expression(span, properties, trailing_comma)) } /// Build an [`Expression::ParenthesizedExpression`] @@ -932,9 +918,7 @@ impl<'a> AstBuilder<'a> { span: Span, expression: Expression<'a>, ) -> Expression<'a> { - Expression::ParenthesizedExpression( - self.alloc(self.parenthesized_expression(span, expression)), - ) + Expression::ParenthesizedExpression(self.alloc_parenthesized_expression(span, expression)) } /// Build an [`Expression::SequenceExpression`] @@ -950,7 +934,7 @@ impl<'a> AstBuilder<'a> { span: Span, expressions: Vec<'a, Expression<'a>>, ) -> Expression<'a> { - Expression::SequenceExpression(self.alloc(self.sequence_expression(span, expressions))) + Expression::SequenceExpression(self.alloc_sequence_expression(span, expressions)) } /// Build an [`Expression::TaggedTemplateExpression`] @@ -973,12 +957,12 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - Expression::TaggedTemplateExpression(self.alloc(self.tagged_template_expression( + Expression::TaggedTemplateExpression(self.alloc_tagged_template_expression( span, tag, quasi, type_parameters, - ))) + )) } /// Build an [`Expression::ThisExpression`] @@ -989,7 +973,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn expression_this(self, span: Span) -> Expression<'a> { - Expression::ThisExpression(self.alloc(self.this_expression(span))) + Expression::ThisExpression(self.alloc_this_expression(span)) } /// Build an [`Expression::UnaryExpression`] @@ -1007,7 +991,7 @@ impl<'a> AstBuilder<'a> { operator: UnaryOperator, argument: Expression<'a>, ) -> Expression<'a> { - Expression::UnaryExpression(self.alloc(self.unary_expression(span, operator, argument))) + Expression::UnaryExpression(self.alloc_unary_expression(span, operator, argument)) } /// Build an [`Expression::UpdateExpression`] @@ -1027,9 +1011,7 @@ impl<'a> AstBuilder<'a> { prefix: bool, argument: SimpleAssignmentTarget<'a>, ) -> Expression<'a> { - Expression::UpdateExpression( - self.alloc(self.update_expression(span, operator, prefix, argument)), - ) + Expression::UpdateExpression(self.alloc_update_expression(span, operator, prefix, argument)) } /// Build an [`Expression::YieldExpression`] @@ -1047,7 +1029,7 @@ impl<'a> AstBuilder<'a> { delegate: bool, argument: Option>, ) -> Expression<'a> { - Expression::YieldExpression(self.alloc(self.yield_expression(span, delegate, argument))) + Expression::YieldExpression(self.alloc_yield_expression(span, delegate, argument)) } /// Build an [`Expression::PrivateInExpression`] @@ -1068,7 +1050,7 @@ impl<'a> AstBuilder<'a> { right: Expression<'a>, ) -> Expression<'a> { Expression::PrivateInExpression( - self.alloc(self.private_in_expression(span, left, operator, right)), + self.alloc_private_in_expression(span, left, operator, right), ) } @@ -1093,12 +1075,12 @@ impl<'a> AstBuilder<'a> { T1: IntoIn<'a, Box<'a, JSXOpeningElement<'a>>>, T2: IntoIn<'a, Option>>>, { - Expression::JSXElement(self.alloc(self.jsx_element( + Expression::JSXElement(self.alloc_jsx_element( span, opening_element, closing_element, children, - ))) + )) } /// Build an [`Expression::JSXFragment`] @@ -1118,12 +1100,12 @@ impl<'a> AstBuilder<'a> { closing_fragment: JSXClosingFragment, children: Vec<'a, JSXChild<'a>>, ) -> Expression<'a> { - Expression::JSXFragment(self.alloc(self.jsx_fragment( + Expression::JSXFragment(self.alloc_jsx_fragment( span, opening_fragment, closing_fragment, children, - ))) + )) } /// Build an [`Expression::TSAsExpression`] @@ -1141,11 +1123,7 @@ impl<'a> AstBuilder<'a> { expression: Expression<'a>, type_annotation: TSType<'a>, ) -> Expression<'a> { - Expression::TSAsExpression(self.alloc(self.ts_as_expression( - span, - expression, - type_annotation, - ))) + Expression::TSAsExpression(self.alloc_ts_as_expression(span, expression, type_annotation)) } /// Build an [`Expression::TSSatisfiesExpression`] @@ -1163,11 +1141,11 @@ impl<'a> AstBuilder<'a> { expression: Expression<'a>, type_annotation: TSType<'a>, ) -> Expression<'a> { - Expression::TSSatisfiesExpression(self.alloc(self.ts_satisfies_expression( + Expression::TSSatisfiesExpression(self.alloc_ts_satisfies_expression( span, expression, type_annotation, - ))) + )) } /// Build an [`Expression::TSTypeAssertion`] @@ -1185,11 +1163,7 @@ impl<'a> AstBuilder<'a> { expression: Expression<'a>, type_annotation: TSType<'a>, ) -> Expression<'a> { - Expression::TSTypeAssertion(self.alloc(self.ts_type_assertion( - span, - expression, - type_annotation, - ))) + Expression::TSTypeAssertion(self.alloc_ts_type_assertion(span, expression, type_annotation)) } /// Build an [`Expression::TSNonNullExpression`] @@ -1201,7 +1175,7 @@ impl<'a> AstBuilder<'a> { /// - expression #[inline] pub fn expression_ts_non_null(self, span: Span, expression: Expression<'a>) -> Expression<'a> { - Expression::TSNonNullExpression(self.alloc(self.ts_non_null_expression(span, expression))) + Expression::TSNonNullExpression(self.alloc_ts_non_null_expression(span, expression)) } /// Build an [`Expression::TSInstantiationExpression`] @@ -1222,11 +1196,11 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Box<'a, TSTypeParameterInstantiation<'a>>>, { - Expression::TSInstantiationExpression(self.alloc(self.ts_instantiation_expression( + Expression::TSInstantiationExpression(self.alloc_ts_instantiation_expression( span, expression, type_parameters, - ))) + )) } /// Build an [`IdentifierName`]. @@ -1527,7 +1501,7 @@ impl<'a> AstBuilder<'a> { span: Span, argument: Expression<'a>, ) -> ArrayExpressionElement<'a> { - ArrayExpressionElement::SpreadElement(self.alloc(self.spread_element(span, argument))) + ArrayExpressionElement::SpreadElement(self.alloc_spread_element(span, argument)) } /// Build an [`ArrayExpressionElement::Elision`] @@ -1621,7 +1595,7 @@ impl<'a> AstBuilder<'a> { computed: bool, ) -> ObjectPropertyKind<'a> { ObjectPropertyKind::ObjectProperty( - self.alloc(self.object_property(span, kind, key, value, method, shorthand, computed)), + self.alloc_object_property(span, kind, key, value, method, shorthand, computed), ) } @@ -1638,7 +1612,7 @@ impl<'a> AstBuilder<'a> { span: Span, argument: Expression<'a>, ) -> ObjectPropertyKind<'a> { - ObjectPropertyKind::SpreadProperty(self.alloc(self.spread_element(span, argument))) + ObjectPropertyKind::SpreadProperty(self.alloc_spread_element(span, argument)) } /// Build an [`ObjectProperty`]. @@ -1708,7 +1682,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - PropertyKey::StaticIdentifier(self.alloc(self.identifier_name(span, name))) + PropertyKey::StaticIdentifier(self.alloc_identifier_name(span, name)) } /// Build a [`PropertyKey::PrivateIdentifier`] @@ -1723,7 +1697,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - PropertyKey::PrivateIdentifier(self.alloc(self.private_identifier(span, name))) + PropertyKey::PrivateIdentifier(self.alloc_private_identifier(span, name)) } /// Build a [`TemplateLiteral`]. @@ -1870,7 +1844,7 @@ impl<'a> AstBuilder<'a> { optional: bool, ) -> MemberExpression<'a> { MemberExpression::ComputedMemberExpression( - self.alloc(self.computed_member_expression(span, object, expression, optional)), + self.alloc_computed_member_expression(span, object, expression, optional), ) } @@ -1892,7 +1866,7 @@ impl<'a> AstBuilder<'a> { optional: bool, ) -> MemberExpression<'a> { MemberExpression::StaticMemberExpression( - self.alloc(self.static_member_expression(span, object, property, optional)), + self.alloc_static_member_expression(span, object, property, optional), ) } @@ -1914,7 +1888,7 @@ impl<'a> AstBuilder<'a> { optional: bool, ) -> MemberExpression<'a> { MemberExpression::PrivateFieldExpression( - self.alloc(self.private_field_expression(span, object, field, optional)), + self.alloc_private_field_expression(span, object, field, optional), ) } @@ -2224,7 +2198,7 @@ impl<'a> AstBuilder<'a> { /// - argument: The expression being spread. #[inline] pub fn argument_spread_element(self, span: Span, argument: Expression<'a>) -> Argument<'a> { - Argument::SpreadElement(self.alloc(self.spread_element(span, argument))) + Argument::SpreadElement(self.alloc_spread_element(span, argument)) } /// Build an [`UpdateExpression`]. @@ -2520,7 +2494,7 @@ impl<'a> AstBuilder<'a> { A: IntoIn<'a, Atom<'a>>, { SimpleAssignmentTarget::AssignmentTargetIdentifier( - self.alloc(self.identifier_reference(span, name)), + self.alloc_identifier_reference(span, name), ) } @@ -2539,11 +2513,11 @@ impl<'a> AstBuilder<'a> { expression: Expression<'a>, type_annotation: TSType<'a>, ) -> SimpleAssignmentTarget<'a> { - SimpleAssignmentTarget::TSAsExpression(self.alloc(self.ts_as_expression( + SimpleAssignmentTarget::TSAsExpression(self.alloc_ts_as_expression( span, expression, type_annotation, - ))) + )) } /// Build a [`SimpleAssignmentTarget::TSSatisfiesExpression`] @@ -2561,11 +2535,11 @@ impl<'a> AstBuilder<'a> { expression: Expression<'a>, type_annotation: TSType<'a>, ) -> SimpleAssignmentTarget<'a> { - SimpleAssignmentTarget::TSSatisfiesExpression(self.alloc(self.ts_satisfies_expression( + SimpleAssignmentTarget::TSSatisfiesExpression(self.alloc_ts_satisfies_expression( span, expression, type_annotation, - ))) + )) } /// Build a [`SimpleAssignmentTarget::TSNonNullExpression`] @@ -2582,7 +2556,7 @@ impl<'a> AstBuilder<'a> { expression: Expression<'a>, ) -> SimpleAssignmentTarget<'a> { SimpleAssignmentTarget::TSNonNullExpression( - self.alloc(self.ts_non_null_expression(span, expression)), + self.alloc_ts_non_null_expression(span, expression), ) } @@ -2601,11 +2575,11 @@ impl<'a> AstBuilder<'a> { expression: Expression<'a>, type_annotation: TSType<'a>, ) -> SimpleAssignmentTarget<'a> { - SimpleAssignmentTarget::TSTypeAssertion(self.alloc(self.ts_type_assertion( + SimpleAssignmentTarget::TSTypeAssertion(self.alloc_ts_type_assertion( span, expression, type_annotation, - ))) + )) } /// Build a [`SimpleAssignmentTarget::TSInstantiationExpression`] @@ -2626,9 +2600,11 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Box<'a, TSTypeParameterInstantiation<'a>>>, { - SimpleAssignmentTarget::TSInstantiationExpression( - self.alloc(self.ts_instantiation_expression(span, expression, type_parameters)), - ) + SimpleAssignmentTarget::TSInstantiationExpression(self.alloc_ts_instantiation_expression( + span, + expression, + type_parameters, + )) } /// Build an [`AssignmentTargetPattern::ArrayAssignmentTarget`] @@ -2648,12 +2624,12 @@ impl<'a> AstBuilder<'a> { rest: Option>, trailing_comma: Option, ) -> AssignmentTargetPattern<'a> { - AssignmentTargetPattern::ArrayAssignmentTarget(self.alloc(self.array_assignment_target( + AssignmentTargetPattern::ArrayAssignmentTarget(self.alloc_array_assignment_target( span, elements, rest, trailing_comma, - ))) + )) } /// Build an [`AssignmentTargetPattern::ObjectAssignmentTarget`] @@ -2672,7 +2648,7 @@ impl<'a> AstBuilder<'a> { rest: Option>, ) -> AssignmentTargetPattern<'a> { AssignmentTargetPattern::ObjectAssignmentTarget( - self.alloc(self.object_assignment_target(span, properties, rest)), + self.alloc_object_assignment_target(span, properties, rest), ) } @@ -2803,7 +2779,7 @@ impl<'a> AstBuilder<'a> { init: Expression<'a>, ) -> AssignmentTargetMaybeDefault<'a> { AssignmentTargetMaybeDefault::AssignmentTargetWithDefault( - self.alloc(self.assignment_target_with_default(span, binding, init)), + self.alloc_assignment_target_with_default(span, binding, init), ) } @@ -2859,7 +2835,7 @@ impl<'a> AstBuilder<'a> { init: Option>, ) -> AssignmentTargetProperty<'a> { AssignmentTargetProperty::AssignmentTargetPropertyIdentifier( - self.alloc(self.assignment_target_property_identifier(span, binding, init)), + self.alloc_assignment_target_property_identifier(span, binding, init), ) } @@ -2881,7 +2857,7 @@ impl<'a> AstBuilder<'a> { computed: bool, ) -> AssignmentTargetProperty<'a> { AssignmentTargetProperty::AssignmentTargetPropertyProperty( - self.alloc(self.assignment_target_property_property(span, name, binding, computed)), + self.alloc_assignment_target_property_property(span, name, binding, computed), ) } @@ -2998,7 +2974,7 @@ impl<'a> AstBuilder<'a> { /// Build a [`Super`]. /// - /// If you want the built node to be allocated in the memory arena, use [`AstBuilder::alloc_super_`] instead. + /// If you want the built node to be allocated in the memory arena, use [`AstBuilder::alloc_super`] instead. /// /// ## Parameters /// - span: The [`Span`] covering this node @@ -3014,7 +2990,7 @@ impl<'a> AstBuilder<'a> { /// ## Parameters /// - span: The [`Span`] covering this node #[inline] - pub fn alloc_super_(self, span: Span) -> Box<'a, Super> { + pub fn alloc_super(self, span: Span) -> Box<'a, Super> { Box::new_in(self.super_(span), self.allocator) } @@ -3096,13 +3072,13 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - ChainElement::CallExpression(self.alloc(self.call_expression( + ChainElement::CallExpression(self.alloc_call_expression( span, callee, type_parameters, arguments, optional, - ))) + )) } /// Build a [`ChainElement::TSNonNullExpression`] @@ -3118,7 +3094,7 @@ impl<'a> AstBuilder<'a> { span: Span, expression: Expression<'a>, ) -> ChainElement<'a> { - ChainElement::TSNonNullExpression(self.alloc(self.ts_non_null_expression(span, expression))) + ChainElement::TSNonNullExpression(self.alloc_ts_non_null_expression(span, expression)) } /// Build a [`ParenthesizedExpression`]. @@ -3162,7 +3138,7 @@ impl<'a> AstBuilder<'a> { /// - body #[inline] pub fn statement_block(self, span: Span, body: Vec<'a, Statement<'a>>) -> Statement<'a> { - Statement::BlockStatement(self.alloc(self.block_statement(span, body))) + Statement::BlockStatement(self.alloc_block_statement(span, body)) } /// Build a [`Statement::BreakStatement`] @@ -3174,7 +3150,7 @@ impl<'a> AstBuilder<'a> { /// - label #[inline] pub fn statement_break(self, span: Span, label: Option>) -> Statement<'a> { - Statement::BreakStatement(self.alloc(self.break_statement(span, label))) + Statement::BreakStatement(self.alloc_break_statement(span, label)) } /// Build a [`Statement::ContinueStatement`] @@ -3190,7 +3166,7 @@ impl<'a> AstBuilder<'a> { span: Span, label: Option>, ) -> Statement<'a> { - Statement::ContinueStatement(self.alloc(self.continue_statement(span, label))) + Statement::ContinueStatement(self.alloc_continue_statement(span, label)) } /// Build a [`Statement::DebuggerStatement`] @@ -3201,7 +3177,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn statement_debugger(self, span: Span) -> Statement<'a> { - Statement::DebuggerStatement(self.alloc(self.debugger_statement(span))) + Statement::DebuggerStatement(self.alloc_debugger_statement(span)) } /// Build a [`Statement::DoWhileStatement`] @@ -3219,7 +3195,7 @@ impl<'a> AstBuilder<'a> { body: Statement<'a>, test: Expression<'a>, ) -> Statement<'a> { - Statement::DoWhileStatement(self.alloc(self.do_while_statement(span, body, test))) + Statement::DoWhileStatement(self.alloc_do_while_statement(span, body, test)) } /// Build a [`Statement::EmptyStatement`] @@ -3230,7 +3206,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn statement_empty(self, span: Span) -> Statement<'a> { - Statement::EmptyStatement(self.alloc(self.empty_statement(span))) + Statement::EmptyStatement(self.alloc_empty_statement(span)) } /// Build a [`Statement::ExpressionStatement`] @@ -3242,7 +3218,7 @@ impl<'a> AstBuilder<'a> { /// - expression #[inline] pub fn statement_expression(self, span: Span, expression: Expression<'a>) -> Statement<'a> { - Statement::ExpressionStatement(self.alloc(self.expression_statement(span, expression))) + Statement::ExpressionStatement(self.alloc_expression_statement(span, expression)) } /// Build a [`Statement::ForInStatement`] @@ -3262,7 +3238,7 @@ impl<'a> AstBuilder<'a> { right: Expression<'a>, body: Statement<'a>, ) -> Statement<'a> { - Statement::ForInStatement(self.alloc(self.for_in_statement(span, left, right, body))) + Statement::ForInStatement(self.alloc_for_in_statement(span, left, right, body)) } /// Build a [`Statement::ForOfStatement`] @@ -3284,9 +3260,7 @@ impl<'a> AstBuilder<'a> { right: Expression<'a>, body: Statement<'a>, ) -> Statement<'a> { - Statement::ForOfStatement( - self.alloc(self.for_of_statement(span, r#await, left, right, body)), - ) + Statement::ForOfStatement(self.alloc_for_of_statement(span, r#await, left, right, body)) } /// Build a [`Statement::ForStatement`] @@ -3308,7 +3282,7 @@ impl<'a> AstBuilder<'a> { update: Option>, body: Statement<'a>, ) -> Statement<'a> { - Statement::ForStatement(self.alloc(self.for_statement(span, init, test, update, body))) + Statement::ForStatement(self.alloc_for_statement(span, init, test, update, body)) } /// Build a [`Statement::IfStatement`] @@ -3328,7 +3302,7 @@ impl<'a> AstBuilder<'a> { consequent: Statement<'a>, alternate: Option>, ) -> Statement<'a> { - Statement::IfStatement(self.alloc(self.if_statement(span, test, consequent, alternate))) + Statement::IfStatement(self.alloc_if_statement(span, test, consequent, alternate)) } /// Build a [`Statement::LabeledStatement`] @@ -3346,7 +3320,7 @@ impl<'a> AstBuilder<'a> { label: LabelIdentifier<'a>, body: Statement<'a>, ) -> Statement<'a> { - Statement::LabeledStatement(self.alloc(self.labeled_statement(span, label, body))) + Statement::LabeledStatement(self.alloc_labeled_statement(span, label, body)) } /// Build a [`Statement::ReturnStatement`] @@ -3358,7 +3332,7 @@ impl<'a> AstBuilder<'a> { /// - argument #[inline] pub fn statement_return(self, span: Span, argument: Option>) -> Statement<'a> { - Statement::ReturnStatement(self.alloc(self.return_statement(span, argument))) + Statement::ReturnStatement(self.alloc_return_statement(span, argument)) } /// Build a [`Statement::SwitchStatement`] @@ -3376,7 +3350,7 @@ impl<'a> AstBuilder<'a> { discriminant: Expression<'a>, cases: Vec<'a, SwitchCase<'a>>, ) -> Statement<'a> { - Statement::SwitchStatement(self.alloc(self.switch_statement(span, discriminant, cases))) + Statement::SwitchStatement(self.alloc_switch_statement(span, discriminant, cases)) } /// Build a [`Statement::ThrowStatement`] @@ -3388,7 +3362,7 @@ impl<'a> AstBuilder<'a> { /// - argument: The expression being thrown, e.g. `err` in `throw err;` #[inline] pub fn statement_throw(self, span: Span, argument: Expression<'a>) -> Statement<'a> { - Statement::ThrowStatement(self.alloc(self.throw_statement(span, argument))) + Statement::ThrowStatement(self.alloc_throw_statement(span, argument)) } /// Build a [`Statement::TryStatement`] @@ -3413,7 +3387,7 @@ impl<'a> AstBuilder<'a> { T2: IntoIn<'a, Option>>>, T3: IntoIn<'a, Option>>>, { - Statement::TryStatement(self.alloc(self.try_statement(span, block, handler, finalizer))) + Statement::TryStatement(self.alloc_try_statement(span, block, handler, finalizer)) } /// Build a [`Statement::WhileStatement`] @@ -3431,7 +3405,7 @@ impl<'a> AstBuilder<'a> { test: Expression<'a>, body: Statement<'a>, ) -> Statement<'a> { - Statement::WhileStatement(self.alloc(self.while_statement(span, test, body))) + Statement::WhileStatement(self.alloc_while_statement(span, test, body)) } /// Build a [`Statement::WithStatement`] @@ -3449,7 +3423,7 @@ impl<'a> AstBuilder<'a> { object: Expression<'a>, body: Statement<'a>, ) -> Statement<'a> { - Statement::WithStatement(self.alloc(self.with_statement(span, object, body))) + Statement::WithStatement(self.alloc_with_statement(span, object, body)) } /// Build a [`Directive`]. @@ -3605,12 +3579,12 @@ impl<'a> AstBuilder<'a> { declarations: Vec<'a, VariableDeclarator<'a>>, declare: bool, ) -> Declaration<'a> { - Declaration::VariableDeclaration(self.alloc(self.variable_declaration( + Declaration::VariableDeclaration(self.alloc_variable_declaration( span, kind, declarations, declare, - ))) + )) } /// Build a [`Declaration::FunctionDeclaration`] @@ -3651,7 +3625,7 @@ impl<'a> AstBuilder<'a> { T4: IntoIn<'a, Option>>>, T5: IntoIn<'a, Option>>>, { - Declaration::FunctionDeclaration(self.alloc(self.function( + Declaration::FunctionDeclaration(self.alloc_function( span, r#type, id, @@ -3663,7 +3637,7 @@ impl<'a> AstBuilder<'a> { params, return_type, body, - ))) + )) } /// Build a [`Declaration::ClassDeclaration`] @@ -3702,7 +3676,7 @@ impl<'a> AstBuilder<'a> { T2: IntoIn<'a, Option>>>, T3: IntoIn<'a, Box<'a, ClassBody<'a>>>, { - Declaration::ClassDeclaration(self.alloc(self.class( + Declaration::ClassDeclaration(self.alloc_class( span, r#type, decorators, @@ -3714,7 +3688,7 @@ impl<'a> AstBuilder<'a> { body, r#abstract, declare, - ))) + )) } /// Build a [`Declaration::TSTypeAliasDeclaration`] @@ -3739,13 +3713,13 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - Declaration::TSTypeAliasDeclaration(self.alloc(self.ts_type_alias_declaration( + Declaration::TSTypeAliasDeclaration(self.alloc_ts_type_alias_declaration( span, id, type_parameters, type_annotation, declare, - ))) + )) } /// Build a [`Declaration::TSInterfaceDeclaration`] @@ -3773,14 +3747,14 @@ impl<'a> AstBuilder<'a> { T1: IntoIn<'a, Option>>>, T2: IntoIn<'a, Box<'a, TSInterfaceBody<'a>>>, { - Declaration::TSInterfaceDeclaration(self.alloc(self.ts_interface_declaration( + Declaration::TSInterfaceDeclaration(self.alloc_ts_interface_declaration( span, id, extends, type_parameters, body, declare, - ))) + )) } /// Build a [`Declaration::TSEnumDeclaration`] @@ -3803,7 +3777,7 @@ impl<'a> AstBuilder<'a> { declare: bool, ) -> Declaration<'a> { Declaration::TSEnumDeclaration( - self.alloc(self.ts_enum_declaration(span, id, members, r#const, declare)), + self.alloc_ts_enum_declaration(span, id, members, r#const, declare), ) } @@ -3827,7 +3801,7 @@ impl<'a> AstBuilder<'a> { declare: bool, ) -> Declaration<'a> { Declaration::TSModuleDeclaration( - self.alloc(self.ts_module_declaration(span, id, body, kind, declare)), + self.alloc_ts_module_declaration(span, id, body, kind, declare), ) } @@ -3848,12 +3822,12 @@ impl<'a> AstBuilder<'a> { module_reference: TSModuleReference<'a>, import_kind: ImportOrExportKind, ) -> Declaration<'a> { - Declaration::TSImportEqualsDeclaration(self.alloc(self.ts_import_equals_declaration( + Declaration::TSImportEqualsDeclaration(self.alloc_ts_import_equals_declaration( span, id, module_reference, import_kind, - ))) + )) } /// Build a [`VariableDeclaration`]. @@ -4218,12 +4192,12 @@ impl<'a> AstBuilder<'a> { declarations: Vec<'a, VariableDeclarator<'a>>, declare: bool, ) -> ForStatementInit<'a> { - ForStatementInit::VariableDeclaration(self.alloc(self.variable_declaration( + ForStatementInit::VariableDeclaration(self.alloc_variable_declaration( span, kind, declarations, declare, - ))) + )) } /// Build a [`ForInStatement`]. @@ -4330,12 +4304,12 @@ impl<'a> AstBuilder<'a> { declarations: Vec<'a, VariableDeclarator<'a>>, declare: bool, ) -> ForStatementLeft<'a> { - ForStatementLeft::VariableDeclaration(self.alloc(self.variable_declaration( + ForStatementLeft::VariableDeclaration(self.alloc_variable_declaration( span, kind, declarations, declare, - ))) + )) } /// Build a [`ForOfStatement`]. @@ -5005,7 +4979,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - BindingPatternKind::BindingIdentifier(self.alloc(self.binding_identifier(span, name))) + BindingPatternKind::BindingIdentifier(self.alloc_binding_identifier(span, name)) } /// Build a [`BindingPatternKind::ObjectPattern`] @@ -5026,7 +5000,7 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - BindingPatternKind::ObjectPattern(self.alloc(self.object_pattern(span, properties, rest))) + BindingPatternKind::ObjectPattern(self.alloc_object_pattern(span, properties, rest)) } /// Build a [`BindingPatternKind::ArrayPattern`] @@ -5047,7 +5021,7 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - BindingPatternKind::ArrayPattern(self.alloc(self.array_pattern(span, elements, rest))) + BindingPatternKind::ArrayPattern(self.alloc_array_pattern(span, elements, rest)) } /// Build a [`BindingPatternKind::AssignmentPattern`] @@ -5065,9 +5039,7 @@ impl<'a> AstBuilder<'a> { left: BindingPattern<'a>, right: Expression<'a>, ) -> BindingPatternKind<'a> { - BindingPatternKind::AssignmentPattern( - self.alloc(self.assignment_pattern(span, left, right)), - ) + BindingPatternKind::AssignmentPattern(self.alloc_assignment_pattern(span, left, right)) } /// Build an [`AssignmentPattern`]. @@ -6091,7 +6063,7 @@ impl<'a> AstBuilder<'a> { span: Span, body: Vec<'a, Statement<'a>>, ) -> ClassElement<'a> { - ClassElement::StaticBlock(self.alloc(self.static_block(span, body))) + ClassElement::StaticBlock(self.alloc_static_block(span, body)) } /// Build a [`ClassElement::MethodDefinition`] @@ -6128,7 +6100,7 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Box<'a, Function<'a>>>, { - ClassElement::MethodDefinition(self.alloc(self.method_definition( + ClassElement::MethodDefinition(self.alloc_method_definition( span, r#type, decorators, @@ -6140,7 +6112,7 @@ impl<'a> AstBuilder<'a> { r#override, optional, accessibility, - ))) + )) } /// Build a [`ClassElement::PropertyDefinition`] @@ -6183,7 +6155,7 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - ClassElement::PropertyDefinition(self.alloc(self.property_definition( + ClassElement::PropertyDefinition(self.alloc_property_definition( span, r#type, decorators, @@ -6198,7 +6170,7 @@ impl<'a> AstBuilder<'a> { readonly, type_annotation, accessibility, - ))) + )) } /// Build a [`ClassElement::AccessorProperty`] @@ -6233,7 +6205,7 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - ClassElement::AccessorProperty(self.alloc(self.accessor_property( + ClassElement::AccessorProperty(self.alloc_accessor_property( span, r#type, decorators, @@ -6244,7 +6216,7 @@ impl<'a> AstBuilder<'a> { definite, type_annotation, accessibility, - ))) + )) } /// Build a [`ClassElement::TSIndexSignature`] @@ -6269,13 +6241,13 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Box<'a, TSTypeAnnotation<'a>>>, { - ClassElement::TSIndexSignature(self.alloc(self.ts_index_signature( + ClassElement::TSIndexSignature(self.alloc_ts_index_signature( span, parameters, type_annotation, readonly, r#static, - ))) + )) } /// Build a [`MethodDefinition`]. @@ -6616,14 +6588,14 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - ModuleDeclaration::ImportDeclaration(self.alloc(self.import_declaration( + ModuleDeclaration::ImportDeclaration(self.alloc_import_declaration( span, specifiers, source, phase, with_clause, import_kind, - ))) + )) } /// Build a [`ModuleDeclaration::ExportAllDeclaration`] @@ -6648,13 +6620,13 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - ModuleDeclaration::ExportAllDeclaration(self.alloc(self.export_all_declaration( + ModuleDeclaration::ExportAllDeclaration(self.alloc_export_all_declaration( span, exported, source, with_clause, export_kind, - ))) + )) } /// Build a [`ModuleDeclaration::ExportDefaultDeclaration`] @@ -6672,11 +6644,11 @@ impl<'a> AstBuilder<'a> { declaration: ExportDefaultDeclarationKind<'a>, exported: ModuleExportName<'a>, ) -> ModuleDeclaration<'a> { - ModuleDeclaration::ExportDefaultDeclaration(self.alloc(self.export_default_declaration( + ModuleDeclaration::ExportDefaultDeclaration(self.alloc_export_default_declaration( span, declaration, exported, - ))) + )) } /// Build a [`ModuleDeclaration::ExportNamedDeclaration`] @@ -6703,14 +6675,14 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - ModuleDeclaration::ExportNamedDeclaration(self.alloc(self.export_named_declaration( + ModuleDeclaration::ExportNamedDeclaration(self.alloc_export_named_declaration( span, declaration, specifiers, source, export_kind, with_clause, - ))) + )) } /// Build a [`ModuleDeclaration::TSExportAssignment`] @@ -6726,9 +6698,7 @@ impl<'a> AstBuilder<'a> { span: Span, expression: Expression<'a>, ) -> ModuleDeclaration<'a> { - ModuleDeclaration::TSExportAssignment( - self.alloc(self.ts_export_assignment(span, expression)), - ) + ModuleDeclaration::TSExportAssignment(self.alloc_ts_export_assignment(span, expression)) } /// Build a [`ModuleDeclaration::TSNamespaceExportDeclaration`] @@ -6745,7 +6715,7 @@ impl<'a> AstBuilder<'a> { id: IdentifierName<'a>, ) -> ModuleDeclaration<'a> { ModuleDeclaration::TSNamespaceExportDeclaration( - self.alloc(self.ts_namespace_export_declaration(span, id)), + self.alloc_ts_namespace_export_declaration(span, id), ) } @@ -6965,12 +6935,12 @@ impl<'a> AstBuilder<'a> { local: BindingIdentifier<'a>, import_kind: ImportOrExportKind, ) -> ImportDeclarationSpecifier<'a> { - ImportDeclarationSpecifier::ImportSpecifier(self.alloc(self.import_specifier( + ImportDeclarationSpecifier::ImportSpecifier(self.alloc_import_specifier( span, imported, local, import_kind, - ))) + )) } /// Build an [`ImportDeclarationSpecifier::ImportDefaultSpecifier`] @@ -6987,7 +6957,7 @@ impl<'a> AstBuilder<'a> { local: BindingIdentifier<'a>, ) -> ImportDeclarationSpecifier<'a> { ImportDeclarationSpecifier::ImportDefaultSpecifier( - self.alloc(self.import_default_specifier(span, local)), + self.alloc_import_default_specifier(span, local), ) } @@ -7005,7 +6975,7 @@ impl<'a> AstBuilder<'a> { local: BindingIdentifier<'a>, ) -> ImportDeclarationSpecifier<'a> { ImportDeclarationSpecifier::ImportNamespaceSpecifier( - self.alloc(self.import_namespace_specifier(span, local)), + self.alloc_import_namespace_specifier(span, local), ) } @@ -7465,7 +7435,7 @@ impl<'a> AstBuilder<'a> { T4: IntoIn<'a, Option>>>, T5: IntoIn<'a, Option>>>, { - ExportDefaultDeclarationKind::FunctionDeclaration(self.alloc(self.function( + ExportDefaultDeclarationKind::FunctionDeclaration(self.alloc_function( span, r#type, id, @@ -7477,7 +7447,7 @@ impl<'a> AstBuilder<'a> { params, return_type, body, - ))) + )) } /// Build an [`ExportDefaultDeclarationKind::ClassDeclaration`] @@ -7516,7 +7486,7 @@ impl<'a> AstBuilder<'a> { T2: IntoIn<'a, Option>>>, T3: IntoIn<'a, Box<'a, ClassBody<'a>>>, { - ExportDefaultDeclarationKind::ClassDeclaration(self.alloc(self.class( + ExportDefaultDeclarationKind::ClassDeclaration(self.alloc_class( span, r#type, decorators, @@ -7528,7 +7498,7 @@ impl<'a> AstBuilder<'a> { body, r#abstract, declare, - ))) + )) } /// Build an [`ExportDefaultDeclarationKind::TSInterfaceDeclaration`] @@ -7556,8 +7526,13 @@ impl<'a> AstBuilder<'a> { T1: IntoIn<'a, Option>>>, T2: IntoIn<'a, Box<'a, TSInterfaceBody<'a>>>, { - ExportDefaultDeclarationKind::TSInterfaceDeclaration(self.alloc( - self.ts_interface_declaration(span, id, extends, type_parameters, body, declare), + ExportDefaultDeclarationKind::TSInterfaceDeclaration(self.alloc_ts_interface_declaration( + span, + id, + extends, + type_parameters, + body, + declare, )) } @@ -7806,7 +7781,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - TSEnumMemberName::Identifier(self.alloc(self.identifier_name(span, name))) + TSEnumMemberName::Identifier(self.alloc_identifier_name(span, name)) } /// Build a [`TSEnumMemberName::String`] @@ -7827,7 +7802,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - TSEnumMemberName::String(self.alloc(self.string_literal(span, value, raw))) + TSEnumMemberName::String(self.alloc_string_literal(span, value, raw)) } /// Build a [`TSTypeAnnotation`]. @@ -7899,7 +7874,7 @@ impl<'a> AstBuilder<'a> { /// - value: The boolean value itself #[inline] pub fn ts_literal_boolean_literal(self, span: Span, value: bool) -> TSLiteral<'a> { - TSLiteral::BooleanLiteral(self.alloc(self.boolean_literal(span, value))) + TSLiteral::BooleanLiteral(self.alloc_boolean_literal(span, value)) } /// Build a [`TSLiteral::NullLiteral`] @@ -7910,7 +7885,7 @@ impl<'a> AstBuilder<'a> { /// - span: Node location in source code #[inline] pub fn ts_literal_null_literal(self, span: Span) -> TSLiteral<'a> { - TSLiteral::NullLiteral(self.alloc(self.null_literal(span))) + TSLiteral::NullLiteral(self.alloc_null_literal(span)) } /// Build a [`TSLiteral::NumericLiteral`] @@ -7930,7 +7905,7 @@ impl<'a> AstBuilder<'a> { raw: Option>, base: NumberBase, ) -> TSLiteral<'a> { - TSLiteral::NumericLiteral(self.alloc(self.numeric_literal(span, value, raw, base))) + TSLiteral::NumericLiteral(self.alloc_numeric_literal(span, value, raw, base)) } /// Build a [`TSLiteral::BigIntLiteral`] @@ -7951,7 +7926,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - TSLiteral::BigIntLiteral(self.alloc(self.big_int_literal(span, raw, base))) + TSLiteral::BigIntLiteral(self.alloc_big_int_literal(span, raw, base)) } /// Build a [`TSLiteral::RegExpLiteral`] @@ -7969,7 +7944,7 @@ impl<'a> AstBuilder<'a> { regex: RegExp<'a>, raw: Option>, ) -> TSLiteral<'a> { - TSLiteral::RegExpLiteral(self.alloc(self.reg_exp_literal(span, regex, raw))) + TSLiteral::RegExpLiteral(self.alloc_reg_exp_literal(span, regex, raw)) } /// Build a [`TSLiteral::StringLiteral`] @@ -7990,7 +7965,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - TSLiteral::StringLiteral(self.alloc(self.string_literal(span, value, raw))) + TSLiteral::StringLiteral(self.alloc_string_literal(span, value, raw)) } /// Build a [`TSLiteral::TemplateLiteral`] @@ -8008,7 +7983,7 @@ impl<'a> AstBuilder<'a> { quasis: Vec<'a, TemplateElement<'a>>, expressions: Vec<'a, Expression<'a>>, ) -> TSLiteral<'a> { - TSLiteral::TemplateLiteral(self.alloc(self.template_literal(span, quasis, expressions))) + TSLiteral::TemplateLiteral(self.alloc_template_literal(span, quasis, expressions)) } /// Build a [`TSLiteral::UnaryExpression`] @@ -8026,7 +8001,7 @@ impl<'a> AstBuilder<'a> { operator: UnaryOperator, argument: Expression<'a>, ) -> TSLiteral<'a> { - TSLiteral::UnaryExpression(self.alloc(self.unary_expression(span, operator, argument))) + TSLiteral::UnaryExpression(self.alloc_unary_expression(span, operator, argument)) } /// Build a [`TSType::TSAnyKeyword`] @@ -8037,7 +8012,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_any_keyword(self, span: Span) -> TSType<'a> { - TSType::TSAnyKeyword(self.alloc(self.ts_any_keyword(span))) + TSType::TSAnyKeyword(self.alloc_ts_any_keyword(span)) } /// Build a [`TSType::TSBigIntKeyword`] @@ -8048,7 +8023,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_big_int_keyword(self, span: Span) -> TSType<'a> { - TSType::TSBigIntKeyword(self.alloc(self.ts_big_int_keyword(span))) + TSType::TSBigIntKeyword(self.alloc_ts_big_int_keyword(span)) } /// Build a [`TSType::TSBooleanKeyword`] @@ -8059,7 +8034,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_boolean_keyword(self, span: Span) -> TSType<'a> { - TSType::TSBooleanKeyword(self.alloc(self.ts_boolean_keyword(span))) + TSType::TSBooleanKeyword(self.alloc_ts_boolean_keyword(span)) } /// Build a [`TSType::TSIntrinsicKeyword`] @@ -8070,7 +8045,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_intrinsic_keyword(self, span: Span) -> TSType<'a> { - TSType::TSIntrinsicKeyword(self.alloc(self.ts_intrinsic_keyword(span))) + TSType::TSIntrinsicKeyword(self.alloc_ts_intrinsic_keyword(span)) } /// Build a [`TSType::TSNeverKeyword`] @@ -8081,7 +8056,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_never_keyword(self, span: Span) -> TSType<'a> { - TSType::TSNeverKeyword(self.alloc(self.ts_never_keyword(span))) + TSType::TSNeverKeyword(self.alloc_ts_never_keyword(span)) } /// Build a [`TSType::TSNullKeyword`] @@ -8092,7 +8067,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_null_keyword(self, span: Span) -> TSType<'a> { - TSType::TSNullKeyword(self.alloc(self.ts_null_keyword(span))) + TSType::TSNullKeyword(self.alloc_ts_null_keyword(span)) } /// Build a [`TSType::TSNumberKeyword`] @@ -8103,7 +8078,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_number_keyword(self, span: Span) -> TSType<'a> { - TSType::TSNumberKeyword(self.alloc(self.ts_number_keyword(span))) + TSType::TSNumberKeyword(self.alloc_ts_number_keyword(span)) } /// Build a [`TSType::TSObjectKeyword`] @@ -8114,7 +8089,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_object_keyword(self, span: Span) -> TSType<'a> { - TSType::TSObjectKeyword(self.alloc(self.ts_object_keyword(span))) + TSType::TSObjectKeyword(self.alloc_ts_object_keyword(span)) } /// Build a [`TSType::TSStringKeyword`] @@ -8125,7 +8100,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_string_keyword(self, span: Span) -> TSType<'a> { - TSType::TSStringKeyword(self.alloc(self.ts_string_keyword(span))) + TSType::TSStringKeyword(self.alloc_ts_string_keyword(span)) } /// Build a [`TSType::TSSymbolKeyword`] @@ -8136,7 +8111,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_symbol_keyword(self, span: Span) -> TSType<'a> { - TSType::TSSymbolKeyword(self.alloc(self.ts_symbol_keyword(span))) + TSType::TSSymbolKeyword(self.alloc_ts_symbol_keyword(span)) } /// Build a [`TSType::TSUndefinedKeyword`] @@ -8147,7 +8122,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_undefined_keyword(self, span: Span) -> TSType<'a> { - TSType::TSUndefinedKeyword(self.alloc(self.ts_undefined_keyword(span))) + TSType::TSUndefinedKeyword(self.alloc_ts_undefined_keyword(span)) } /// Build a [`TSType::TSUnknownKeyword`] @@ -8158,7 +8133,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_unknown_keyword(self, span: Span) -> TSType<'a> { - TSType::TSUnknownKeyword(self.alloc(self.ts_unknown_keyword(span))) + TSType::TSUnknownKeyword(self.alloc_ts_unknown_keyword(span)) } /// Build a [`TSType::TSVoidKeyword`] @@ -8169,7 +8144,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_void_keyword(self, span: Span) -> TSType<'a> { - TSType::TSVoidKeyword(self.alloc(self.ts_void_keyword(span))) + TSType::TSVoidKeyword(self.alloc_ts_void_keyword(span)) } /// Build a [`TSType::TSArrayType`] @@ -8181,7 +8156,7 @@ impl<'a> AstBuilder<'a> { /// - element_type #[inline] pub fn ts_type_array_type(self, span: Span, element_type: TSType<'a>) -> TSType<'a> { - TSType::TSArrayType(self.alloc(self.ts_array_type(span, element_type))) + TSType::TSArrayType(self.alloc_ts_array_type(span, element_type)) } /// Build a [`TSType::TSConditionalType`] @@ -8203,13 +8178,13 @@ impl<'a> AstBuilder<'a> { true_type: TSType<'a>, false_type: TSType<'a>, ) -> TSType<'a> { - TSType::TSConditionalType(self.alloc(self.ts_conditional_type( + TSType::TSConditionalType(self.alloc_ts_conditional_type( span, check_type, extends_type, true_type, false_type, - ))) + )) } /// Build a [`TSType::TSConstructorType`] @@ -8236,13 +8211,13 @@ impl<'a> AstBuilder<'a> { T2: IntoIn<'a, Box<'a, FormalParameters<'a>>>, T3: IntoIn<'a, Box<'a, TSTypeAnnotation<'a>>>, { - TSType::TSConstructorType(self.alloc(self.ts_constructor_type( + TSType::TSConstructorType(self.alloc_ts_constructor_type( span, r#abstract, type_parameters, params, return_type, - ))) + )) } /// Build a [`TSType::TSFunctionType`] @@ -8270,13 +8245,13 @@ impl<'a> AstBuilder<'a> { T3: IntoIn<'a, Box<'a, FormalParameters<'a>>>, T4: IntoIn<'a, Box<'a, TSTypeAnnotation<'a>>>, { - TSType::TSFunctionType(self.alloc(self.ts_function_type( + TSType::TSFunctionType(self.alloc_ts_function_type( span, type_parameters, this_param, params, return_type, - ))) + )) } /// Build a [`TSType::TSImportType`] @@ -8304,14 +8279,14 @@ impl<'a> AstBuilder<'a> { T1: IntoIn<'a, Option>>>, T2: IntoIn<'a, Option>>>, { - TSType::TSImportType(self.alloc(self.ts_import_type( + TSType::TSImportType(self.alloc_ts_import_type( span, is_type_of, parameter, qualifier, attributes, type_parameters, - ))) + )) } /// Build a [`TSType::TSIndexedAccessType`] @@ -8329,11 +8304,11 @@ impl<'a> AstBuilder<'a> { object_type: TSType<'a>, index_type: TSType<'a>, ) -> TSType<'a> { - TSType::TSIndexedAccessType(self.alloc(self.ts_indexed_access_type( + TSType::TSIndexedAccessType(self.alloc_ts_indexed_access_type( span, object_type, index_type, - ))) + )) } /// Build a [`TSType::TSInferType`] @@ -8348,7 +8323,7 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Box<'a, TSTypeParameter<'a>>>, { - TSType::TSInferType(self.alloc(self.ts_infer_type(span, type_parameter))) + TSType::TSInferType(self.alloc_ts_infer_type(span, type_parameter)) } /// Build a [`TSType::TSIntersectionType`] @@ -8360,7 +8335,7 @@ impl<'a> AstBuilder<'a> { /// - types #[inline] pub fn ts_type_intersection_type(self, span: Span, types: Vec<'a, TSType<'a>>) -> TSType<'a> { - TSType::TSIntersectionType(self.alloc(self.ts_intersection_type(span, types))) + TSType::TSIntersectionType(self.alloc_ts_intersection_type(span, types)) } /// Build a [`TSType::TSLiteralType`] @@ -8372,7 +8347,7 @@ impl<'a> AstBuilder<'a> { /// - literal #[inline] pub fn ts_type_literal_type(self, span: Span, literal: TSLiteral<'a>) -> TSType<'a> { - TSType::TSLiteralType(self.alloc(self.ts_literal_type(span, literal))) + TSType::TSLiteralType(self.alloc_ts_literal_type(span, literal)) } /// Build a [`TSType::TSMappedType`] @@ -8399,14 +8374,14 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Box<'a, TSTypeParameter<'a>>>, { - TSType::TSMappedType(self.alloc(self.ts_mapped_type( + TSType::TSMappedType(self.alloc_ts_mapped_type( span, type_parameter, name_type, type_annotation, optional, readonly, - ))) + )) } /// Build a [`TSType::TSNamedTupleMember`] @@ -8426,12 +8401,12 @@ impl<'a> AstBuilder<'a> { label: IdentifierName<'a>, optional: bool, ) -> TSType<'a> { - TSType::TSNamedTupleMember(self.alloc(self.ts_named_tuple_member( + TSType::TSNamedTupleMember(self.alloc_ts_named_tuple_member( span, element_type, label, optional, - ))) + )) } /// Build a [`TSType::TSQualifiedName`] @@ -8449,7 +8424,7 @@ impl<'a> AstBuilder<'a> { left: TSTypeName<'a>, right: IdentifierName<'a>, ) -> TSType<'a> { - TSType::TSQualifiedName(self.alloc(self.ts_qualified_name(span, left, right))) + TSType::TSQualifiedName(self.alloc_ts_qualified_name(span, left, right)) } /// Build a [`TSType::TSTemplateLiteralType`] @@ -8467,9 +8442,7 @@ impl<'a> AstBuilder<'a> { quasis: Vec<'a, TemplateElement<'a>>, types: Vec<'a, TSType<'a>>, ) -> TSType<'a> { - TSType::TSTemplateLiteralType( - self.alloc(self.ts_template_literal_type(span, quasis, types)), - ) + TSType::TSTemplateLiteralType(self.alloc_ts_template_literal_type(span, quasis, types)) } /// Build a [`TSType::TSThisType`] @@ -8480,7 +8453,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_this_type(self, span: Span) -> TSType<'a> { - TSType::TSThisType(self.alloc(self.ts_this_type(span))) + TSType::TSThisType(self.alloc_ts_this_type(span)) } /// Build a [`TSType::TSTupleType`] @@ -8496,7 +8469,7 @@ impl<'a> AstBuilder<'a> { span: Span, element_types: Vec<'a, TSTupleElement<'a>>, ) -> TSType<'a> { - TSType::TSTupleType(self.alloc(self.ts_tuple_type(span, element_types))) + TSType::TSTupleType(self.alloc_ts_tuple_type(span, element_types)) } /// Build a [`TSType::TSTypeLiteral`] @@ -8508,7 +8481,7 @@ impl<'a> AstBuilder<'a> { /// - members #[inline] pub fn ts_type_type_literal(self, span: Span, members: Vec<'a, TSSignature<'a>>) -> TSType<'a> { - TSType::TSTypeLiteral(self.alloc(self.ts_type_literal(span, members))) + TSType::TSTypeLiteral(self.alloc_ts_type_literal(span, members)) } /// Build a [`TSType::TSTypeOperatorType`] @@ -8526,11 +8499,7 @@ impl<'a> AstBuilder<'a> { operator: TSTypeOperatorOperator, type_annotation: TSType<'a>, ) -> TSType<'a> { - TSType::TSTypeOperatorType(self.alloc(self.ts_type_operator( - span, - operator, - type_annotation, - ))) + TSType::TSTypeOperatorType(self.alloc_ts_type_operator(span, operator, type_annotation)) } /// Build a [`TSType::TSTypePredicate`] @@ -8553,12 +8522,12 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - TSType::TSTypePredicate(self.alloc(self.ts_type_predicate( + TSType::TSTypePredicate(self.alloc_ts_type_predicate( span, parameter_name, asserts, type_annotation, - ))) + )) } /// Build a [`TSType::TSTypeQuery`] @@ -8579,7 +8548,7 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - TSType::TSTypeQuery(self.alloc(self.ts_type_query(span, expr_name, type_parameters))) + TSType::TSTypeQuery(self.alloc_ts_type_query(span, expr_name, type_parameters)) } /// Build a [`TSType::TSTypeReference`] @@ -8600,11 +8569,7 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - TSType::TSTypeReference(self.alloc(self.ts_type_reference( - span, - type_name, - type_parameters, - ))) + TSType::TSTypeReference(self.alloc_ts_type_reference(span, type_name, type_parameters)) } /// Build a [`TSType::TSUnionType`] @@ -8616,7 +8581,7 @@ impl<'a> AstBuilder<'a> { /// - types: The types in the union. #[inline] pub fn ts_type_union_type(self, span: Span, types: Vec<'a, TSType<'a>>) -> TSType<'a> { - TSType::TSUnionType(self.alloc(self.ts_union_type(span, types))) + TSType::TSUnionType(self.alloc_ts_union_type(span, types)) } /// Build a [`TSType::TSParenthesizedType`] @@ -8628,7 +8593,7 @@ impl<'a> AstBuilder<'a> { /// - type_annotation #[inline] pub fn ts_type_parenthesized_type(self, span: Span, type_annotation: TSType<'a>) -> TSType<'a> { - TSType::TSParenthesizedType(self.alloc(self.ts_parenthesized_type(span, type_annotation))) + TSType::TSParenthesizedType(self.alloc_ts_parenthesized_type(span, type_annotation)) } /// Build a [`TSType::JSDocNullableType`] @@ -8646,11 +8611,7 @@ impl<'a> AstBuilder<'a> { type_annotation: TSType<'a>, postfix: bool, ) -> TSType<'a> { - TSType::JSDocNullableType(self.alloc(self.js_doc_nullable_type( - span, - type_annotation, - postfix, - ))) + TSType::JSDocNullableType(self.alloc_js_doc_nullable_type(span, type_annotation, postfix)) } /// Build a [`TSType::JSDocNonNullableType`] @@ -8668,11 +8629,11 @@ impl<'a> AstBuilder<'a> { type_annotation: TSType<'a>, postfix: bool, ) -> TSType<'a> { - TSType::JSDocNonNullableType(self.alloc(self.js_doc_non_nullable_type( + TSType::JSDocNonNullableType(self.alloc_js_doc_non_nullable_type( span, type_annotation, postfix, - ))) + )) } /// Build a [`TSType::JSDocUnknownType`] @@ -8683,7 +8644,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn ts_type_js_doc_unknown_type(self, span: Span) -> TSType<'a> { - TSType::JSDocUnknownType(self.alloc(self.js_doc_unknown_type(span))) + TSType::JSDocUnknownType(self.alloc_js_doc_unknown_type(span)) } /// Build a [`TSConditionalType`]. @@ -9138,7 +9099,7 @@ impl<'a> AstBuilder<'a> { span: Span, type_annotation: TSType<'a>, ) -> TSTupleElement<'a> { - TSTupleElement::TSOptionalType(self.alloc(self.ts_optional_type(span, type_annotation))) + TSTupleElement::TSOptionalType(self.alloc_ts_optional_type(span, type_annotation)) } /// Build a [`TSTupleElement::TSRestType`] @@ -9154,7 +9115,7 @@ impl<'a> AstBuilder<'a> { span: Span, type_annotation: TSType<'a>, ) -> TSTupleElement<'a> { - TSTupleElement::TSRestType(self.alloc(self.ts_rest_type(span, type_annotation))) + TSTupleElement::TSRestType(self.alloc_ts_rest_type(span, type_annotation)) } /// Build a [`TSAnyKeyword`]. @@ -9523,7 +9484,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - TSTypeName::IdentifierReference(self.alloc(self.identifier_reference(span, name))) + TSTypeName::IdentifierReference(self.alloc_identifier_reference(span, name)) } /// Build a [`TSTypeName::QualifiedName`] @@ -9541,7 +9502,7 @@ impl<'a> AstBuilder<'a> { left: TSTypeName<'a>, right: IdentifierName<'a>, ) -> TSTypeName<'a> { - TSTypeName::QualifiedName(self.alloc(self.ts_qualified_name(span, left, right))) + TSTypeName::QualifiedName(self.alloc_ts_qualified_name(span, left, right)) } /// Build a [`TSQualifiedName`]. @@ -10140,13 +10101,13 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Box<'a, TSTypeAnnotation<'a>>>, { - TSSignature::TSIndexSignature(self.alloc(self.ts_index_signature( + TSSignature::TSIndexSignature(self.alloc_ts_index_signature( span, parameters, type_annotation, readonly, r#static, - ))) + )) } /// Build a [`TSSignature::TSPropertySignature`] @@ -10173,14 +10134,14 @@ impl<'a> AstBuilder<'a> { where T1: IntoIn<'a, Option>>>, { - TSSignature::TSPropertySignature(self.alloc(self.ts_property_signature( + TSSignature::TSPropertySignature(self.alloc_ts_property_signature( span, computed, optional, readonly, key, type_annotation, - ))) + )) } /// Build a [`TSSignature::TSCallSignatureDeclaration`] @@ -10207,13 +10168,13 @@ impl<'a> AstBuilder<'a> { T2: IntoIn<'a, Box<'a, FormalParameters<'a>>>, T3: IntoIn<'a, Option>>>, { - TSSignature::TSCallSignatureDeclaration(self.alloc(self.ts_call_signature_declaration( + TSSignature::TSCallSignatureDeclaration(self.alloc_ts_call_signature_declaration( span, type_parameters, this_param, params, return_type, - ))) + )) } /// Build a [`TSSignature::TSConstructSignatureDeclaration`] @@ -10238,8 +10199,11 @@ impl<'a> AstBuilder<'a> { T2: IntoIn<'a, Box<'a, FormalParameters<'a>>>, T3: IntoIn<'a, Option>>>, { - TSSignature::TSConstructSignatureDeclaration(self.alloc( - self.ts_construct_signature_declaration(span, type_parameters, params, return_type), + TSSignature::TSConstructSignatureDeclaration(self.alloc_ts_construct_signature_declaration( + span, + type_parameters, + params, + return_type, )) } @@ -10276,7 +10240,7 @@ impl<'a> AstBuilder<'a> { T3: IntoIn<'a, Box<'a, FormalParameters<'a>>>, T4: IntoIn<'a, Option>>>, { - TSSignature::TSMethodSignature(self.alloc(self.ts_method_signature( + TSSignature::TSMethodSignature(self.alloc_ts_method_signature( span, key, computed, @@ -10286,7 +10250,7 @@ impl<'a> AstBuilder<'a> { this_param, params, return_type, - ))) + )) } /// Build a [`TSIndexSignature`]. @@ -10906,7 +10870,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - TSTypePredicateName::Identifier(self.alloc(self.identifier_name(span, name))) + TSTypePredicateName::Identifier(self.alloc_identifier_name(span, name)) } /// Build a [`TSTypePredicateName::This`] @@ -11069,7 +11033,7 @@ impl<'a> AstBuilder<'a> { declare: bool, ) -> TSModuleDeclarationBody<'a> { TSModuleDeclarationBody::TSModuleDeclaration( - self.alloc(self.ts_module_declaration(span, id, body, kind, declare)), + self.alloc_ts_module_declaration(span, id, body, kind, declare), ) } @@ -11088,9 +11052,7 @@ impl<'a> AstBuilder<'a> { directives: Vec<'a, Directive<'a>>, body: Vec<'a, Statement<'a>>, ) -> TSModuleDeclarationBody<'a> { - TSModuleDeclarationBody::TSModuleBlock( - self.alloc(self.ts_module_block(span, directives, body)), - ) + TSModuleDeclarationBody::TSModuleBlock(self.alloc_ts_module_block(span, directives, body)) } /// Build a [`TSModuleBlock`]. @@ -11258,14 +11220,14 @@ impl<'a> AstBuilder<'a> { T1: IntoIn<'a, Option>>>, T2: IntoIn<'a, Option>>>, { - TSTypeQueryExprName::TSImportType(self.alloc(self.ts_import_type( + TSTypeQueryExprName::TSImportType(self.alloc_ts_import_type( span, is_type_of, parameter, qualifier, attributes, type_parameters, - ))) + )) } /// Build a [`TSImportType`]. @@ -11927,7 +11889,7 @@ impl<'a> AstBuilder<'a> { expression: StringLiteral<'a>, ) -> TSModuleReference<'a> { TSModuleReference::ExternalModuleReference( - self.alloc(self.ts_external_module_reference(span, expression)), + self.alloc_ts_external_module_reference(span, expression), ) } @@ -12428,7 +12390,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - JSXElementName::Identifier(self.alloc(self.jsx_identifier(span, name))) + JSXElementName::Identifier(self.alloc_jsx_identifier(span, name)) } /// Build a [`JSXElementName::IdentifierReference`] @@ -12443,7 +12405,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - JSXElementName::IdentifierReference(self.alloc(self.identifier_reference(span, name))) + JSXElementName::IdentifierReference(self.alloc_identifier_reference(span, name)) } /// Build a [`JSXElementName::NamespacedName`] @@ -12461,9 +12423,7 @@ impl<'a> AstBuilder<'a> { namespace: JSXIdentifier<'a>, property: JSXIdentifier<'a>, ) -> JSXElementName<'a> { - JSXElementName::NamespacedName( - self.alloc(self.jsx_namespaced_name(span, namespace, property)), - ) + JSXElementName::NamespacedName(self.alloc_jsx_namespaced_name(span, namespace, property)) } /// Build a [`JSXElementName::MemberExpression`] @@ -12481,9 +12441,7 @@ impl<'a> AstBuilder<'a> { object: JSXMemberExpressionObject<'a>, property: JSXIdentifier<'a>, ) -> JSXElementName<'a> { - JSXElementName::MemberExpression( - self.alloc(self.jsx_member_expression(span, object, property)), - ) + JSXElementName::MemberExpression(self.alloc_jsx_member_expression(span, object, property)) } /// Build a [`JSXElementName::ThisExpression`] @@ -12494,7 +12452,7 @@ impl<'a> AstBuilder<'a> { /// - span: The [`Span`] covering this node #[inline] pub fn jsx_element_name_this_expression(self, span: Span) -> JSXElementName<'a> { - JSXElementName::ThisExpression(self.alloc(self.this_expression(span))) + JSXElementName::ThisExpression(self.alloc_this_expression(span)) } /// Build a [`JSXNamespacedName`]. @@ -12585,9 +12543,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - JSXMemberExpressionObject::IdentifierReference( - self.alloc(self.identifier_reference(span, name)), - ) + JSXMemberExpressionObject::IdentifierReference(self.alloc_identifier_reference(span, name)) } /// Build a [`JSXMemberExpressionObject::MemberExpression`] @@ -12606,7 +12562,7 @@ impl<'a> AstBuilder<'a> { property: JSXIdentifier<'a>, ) -> JSXMemberExpressionObject<'a> { JSXMemberExpressionObject::MemberExpression( - self.alloc(self.jsx_member_expression(span, object, property)), + self.alloc_jsx_member_expression(span, object, property), ) } @@ -12621,7 +12577,7 @@ impl<'a> AstBuilder<'a> { self, span: Span, ) -> JSXMemberExpressionObject<'a> { - JSXMemberExpressionObject::ThisExpression(self.alloc(self.this_expression(span))) + JSXMemberExpressionObject::ThisExpression(self.alloc_this_expression(span)) } /// Build a [`JSXExpressionContainer`]. @@ -12702,7 +12658,7 @@ impl<'a> AstBuilder<'a> { name: JSXAttributeName<'a>, value: Option>, ) -> JSXAttributeItem<'a> { - JSXAttributeItem::Attribute(self.alloc(self.jsx_attribute(span, name, value))) + JSXAttributeItem::Attribute(self.alloc_jsx_attribute(span, name, value)) } /// Build a [`JSXAttributeItem::SpreadAttribute`] @@ -12718,7 +12674,7 @@ impl<'a> AstBuilder<'a> { span: Span, argument: Expression<'a>, ) -> JSXAttributeItem<'a> { - JSXAttributeItem::SpreadAttribute(self.alloc(self.jsx_spread_attribute(span, argument))) + JSXAttributeItem::SpreadAttribute(self.alloc_jsx_spread_attribute(span, argument)) } /// Build a [`JSXAttribute`]. @@ -12801,7 +12757,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - JSXAttributeName::Identifier(self.alloc(self.jsx_identifier(span, name))) + JSXAttributeName::Identifier(self.alloc_jsx_identifier(span, name)) } /// Build a [`JSXAttributeName::NamespacedName`] @@ -12819,9 +12775,7 @@ impl<'a> AstBuilder<'a> { namespace: JSXIdentifier<'a>, property: JSXIdentifier<'a>, ) -> JSXAttributeName<'a> { - JSXAttributeName::NamespacedName( - self.alloc(self.jsx_namespaced_name(span, namespace, property)), - ) + JSXAttributeName::NamespacedName(self.alloc_jsx_namespaced_name(span, namespace, property)) } /// Build a [`JSXAttributeValue::StringLiteral`] @@ -12842,7 +12796,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - JSXAttributeValue::StringLiteral(self.alloc(self.string_literal(span, value, raw))) + JSXAttributeValue::StringLiteral(self.alloc_string_literal(span, value, raw)) } /// Build a [`JSXAttributeValue::ExpressionContainer`] @@ -12859,7 +12813,7 @@ impl<'a> AstBuilder<'a> { expression: JSXExpression<'a>, ) -> JSXAttributeValue<'a> { JSXAttributeValue::ExpressionContainer( - self.alloc(self.jsx_expression_container(span, expression)), + self.alloc_jsx_expression_container(span, expression), ) } @@ -12884,12 +12838,12 @@ impl<'a> AstBuilder<'a> { T1: IntoIn<'a, Box<'a, JSXOpeningElement<'a>>>, T2: IntoIn<'a, Option>>>, { - JSXAttributeValue::Element(self.alloc(self.jsx_element( + JSXAttributeValue::Element(self.alloc_jsx_element( span, opening_element, closing_element, children, - ))) + )) } /// Build a [`JSXAttributeValue::Fragment`] @@ -12909,12 +12863,12 @@ impl<'a> AstBuilder<'a> { closing_fragment: JSXClosingFragment, children: Vec<'a, JSXChild<'a>>, ) -> JSXAttributeValue<'a> { - JSXAttributeValue::Fragment(self.alloc(self.jsx_fragment( + JSXAttributeValue::Fragment(self.alloc_jsx_fragment( span, opening_fragment, closing_fragment, children, - ))) + )) } /// Build a [`JSXIdentifier`]. @@ -12959,7 +12913,7 @@ impl<'a> AstBuilder<'a> { where A: IntoIn<'a, Atom<'a>>, { - JSXChild::Text(self.alloc(self.jsx_text(span, value))) + JSXChild::Text(self.alloc_jsx_text(span, value)) } /// Build a [`JSXChild::Element`] @@ -12983,12 +12937,7 @@ impl<'a> AstBuilder<'a> { T1: IntoIn<'a, Box<'a, JSXOpeningElement<'a>>>, T2: IntoIn<'a, Option>>>, { - JSXChild::Element(self.alloc(self.jsx_element( - span, - opening_element, - closing_element, - children, - ))) + JSXChild::Element(self.alloc_jsx_element(span, opening_element, closing_element, children)) } /// Build a [`JSXChild::Fragment`] @@ -13008,12 +12957,12 @@ impl<'a> AstBuilder<'a> { closing_fragment: JSXClosingFragment, children: Vec<'a, JSXChild<'a>>, ) -> JSXChild<'a> { - JSXChild::Fragment(self.alloc(self.jsx_fragment( + JSXChild::Fragment(self.alloc_jsx_fragment( span, opening_fragment, closing_fragment, children, - ))) + )) } /// Build a [`JSXChild::ExpressionContainer`] @@ -13029,7 +12978,7 @@ impl<'a> AstBuilder<'a> { span: Span, expression: JSXExpression<'a>, ) -> JSXChild<'a> { - JSXChild::ExpressionContainer(self.alloc(self.jsx_expression_container(span, expression))) + JSXChild::ExpressionContainer(self.alloc_jsx_expression_container(span, expression)) } /// Build a [`JSXChild::Spread`] @@ -13045,7 +12994,7 @@ impl<'a> AstBuilder<'a> { span: Span, expression: Expression<'a>, ) -> JSXChild<'a> { - JSXChild::Spread(self.alloc(self.jsx_spread_child(span, expression))) + JSXChild::Spread(self.alloc_jsx_spread_child(span, expression)) } /// Build a [`JSXSpreadChild`]. diff --git a/crates/oxc_ast/src/lib.rs b/crates/oxc_ast/src/lib.rs index 0010d1bb14c52f..4da8cae35cf9a6 100644 --- a/crates/oxc_ast/src/lib.rs +++ b/crates/oxc_ast/src/lib.rs @@ -119,18 +119,18 @@ fn size_asserts() { use crate::ast; - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); } #[test] diff --git a/crates/oxc_ast_macros/Cargo.toml b/crates/oxc_ast_macros/Cargo.toml index ec2058aa62b357..f9990fde6f5d57 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.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_cfg/Cargo.toml b/crates/oxc_cfg/Cargo.toml index 2f98dc83cfb528..a9d8bfd45af193 100644 --- a/crates/oxc_cfg/Cargo.toml +++ b/crates/oxc_cfg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_cfg" -version = "0.43.0" +version = "0.44.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 09385c7ead2098..beb04a02899741 100644 --- a/crates/oxc_codegen/CHANGELOG.md +++ b/crates/oxc_codegen/CHANGELOG.md @@ -4,6 +4,40 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.44.0] - 2024-12-25 + +- ad2a620 ast: [**BREAKING**] Add missing `AssignmentTargetProperty::computed` (#8097) (Boshen) + +### Features + +- 618b6aa codege: Minify whitespace in object getter / setter (#8080) (Boshen) +- 4727667 codegen: Minify arrow expr `(x) => y` -> `x => y` (#8078) (Boshen) +- 0562830 codegen: Minify string with backtick when needed (#8095) (Boshen) +- 6237c05 codegen: Minify more whitespace (#8089) (Boshen) +- 6355b7c codegen: Minify `export { 's' as 's' }` -> `export { 's' }` (#8093) (Boshen) +- fccfda9 codegen: Minify `class{static[computed]}` (#8088) (Boshen) +- f873139 codegen: Minify `for (_ of [])` -> `for(_ of[])` (#8086) (Boshen) +- 8b8cbcd codegen: Minify `case "foo"` -> `case"foo"` (#8085) (Boshen) +- 414c118 codegen: Minify `yield "s"` -> `yield"s"` (#8084) (Boshen) +- f8f067b codegen: Minify class method `async*fn(){}` (#8083) (Boshen) +- 1d5ae81 codegen: Minify `const [foo] = bar` -> `const[foo]=bar` (#8079) (Boshen) +- e3f78fb codegen: `new Foo()` -> `new Foo` when minify (#8077) (Boshen) +- d84d60a codegen: Minify numbers with large exponents (#8074) (Boshen) +- 373279b codegen: Balance string quotes when minify whitespace (#8072) (Boshen) + +### Bug Fixes + +- bdc241d codegen: Disallow template literals in object property key (#8108) (Boshen) +- 728ed20 codegen: Print `yield * ident` correctly (Boshen) + +### Performance + +- 78d2e83 sourcemap: Improve perf of `search_original_line_and_column` (#7926) (Cameron) + +### Refactor + +- 7110c7b codegen: Add `print_quoted_utf16` and `print_unquoted_utf16` methods (#8107) (Boshen) + ## [0.43.0] - 2024-12-21 ### Performance diff --git a/crates/oxc_codegen/Cargo.toml b/crates/oxc_codegen/Cargo.toml index 8f589f63126cfe..734bd3c470cc82 100644 --- a/crates/oxc_codegen/Cargo.toml +++ b/crates/oxc_codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_codegen" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_codegen/examples/codegen.rs b/crates/oxc_codegen/examples/codegen.rs index 5755d891ba311d..e941dfcbe34bd9 100644 --- a/crates/oxc_codegen/examples/codegen.rs +++ b/crates/oxc_codegen/examples/codegen.rs @@ -3,7 +3,7 @@ use std::path::Path; use oxc_allocator::Allocator; use oxc_codegen::{CodeGenerator, CodegenOptions}; -use oxc_parser::{Parser, ParserReturn}; +use oxc_parser::{ParseOptions, Parser, ParserReturn}; use oxc_span::SourceType; use pico_args::Arguments; @@ -52,7 +52,12 @@ fn parse<'a>( source_text: &'a str, source_type: SourceType, ) -> Option> { - let ret = Parser::new(allocator, source_text, source_type).parse(); + let ret = Parser::new(allocator, source_text, source_type) + .with_options(ParseOptions { + allow_return_outside_function: true, + ..ParseOptions::default() + }) + .parse(); if !ret.errors.is_empty() { for error in ret.errors { println!("{:?}", error.with_source_code(source_text.to_string())); diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 549624b3f1d98e..2c1a961c790832 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -4,7 +4,6 @@ use cow_utils::CowUtils; use oxc_ast::ast::*; use oxc_span::GetSpan; use oxc_syntax::{ - identifier::{LS, PS}, operator::UnaryOperator, precedence::{GetPrecedence, Precedence}, }; @@ -302,7 +301,12 @@ fn print_if(if_stmt: &IfStatement<'_>, p: &mut Codegen, ctx: Context) { p.print_soft_newline(); } } - stmt => p.print_body(stmt, false, ctx), + stmt => { + p.print_body(stmt, false, ctx); + if if_stmt.alternate.is_some() { + p.print_indent(); + } + } } if let Some(alternate) = if_stmt.alternate.as_ref() { p.print_semicolon_if_needed(); @@ -1163,7 +1167,7 @@ impl GenExpr for Expression<'_> { Self::BooleanLiteral(lit) => lit.print(p, ctx), Self::NullLiteral(lit) => lit.print(p, ctx), Self::NumericLiteral(lit) => lit.print_expr(p, precedence, ctx), - Self::BigIntLiteral(lit) => lit.print(p, ctx), + Self::BigIntLiteral(lit) => lit.print_expr(p, precedence, ctx), Self::RegExpLiteral(lit) => lit.print(p, ctx), Self::StringLiteral(lit) => lit.print(p, ctx), Self::Identifier(ident) => ident.print(p, ctx), @@ -1302,16 +1306,20 @@ impl GenExpr for NumericLiteral<'_> { } } -impl Gen for BigIntLiteral<'_> { - fn gen(&self, p: &mut Codegen, _ctx: Context) { +impl GenExpr for BigIntLiteral<'_> { + fn gen_expr(&self, p: &mut Codegen, precedence: Precedence, _ctx: Context) { let raw = self.raw.as_str().cow_replace('_', ""); - if raw.starts_with('-') { - p.print_space_before_operator(Operator::Unary(UnaryOperator::UnaryNegation)); - } - p.print_space_before_identifier(); p.add_source_mapping(self.span); - p.print_str(&raw); + if !raw.starts_with('-') { + p.print_str(&raw); + } else if precedence >= Precedence::Prefix { + p.print_ascii_byte(b'('); + p.print_str(&raw); + p.print_ascii_byte(b')'); + } else { + p.print_str(&raw); + } } } @@ -1334,133 +1342,11 @@ impl Gen for RegExpLiteral<'_> { } } -fn print_unquoted_str(s: &str, quote: u8, p: &mut Codegen) { - let mut chars = s.chars().peekable(); - - while let Some(c) = chars.next() { - match c { - '\x00' => { - if chars.peek().is_some_and(|&next| next.is_ascii_digit()) { - p.print_str("\\x00"); - } else { - p.print_str("\\0"); - } - } - '\x07' => { - p.print_str("\\x07"); - } - // \b - '\u{8}' => { - p.print_str("\\b"); - } - // \v - '\u{b}' => { - p.print_str("\\v"); - } - // \f - '\u{c}' => { - p.print_str("\\f"); - } - '\n' => { - p.print_str("\\n"); - } - '\r' => { - p.print_str("\\r"); - } - '\x1B' => { - p.print_str("\\x1B"); - } - '\\' => { - p.print_str("\\\\"); - } - '\'' => { - if quote == b'\'' { - p.print_ascii_byte(b'\\'); - } - p.print_ascii_byte(b'\''); - } - '\"' => { - if quote == b'"' { - p.print_ascii_byte(b'\\'); - } - p.print_ascii_byte(b'"'); - } - '`' => { - if quote == b'`' { - p.print_ascii_byte(b'\\'); - } - p.print_ascii_byte(b'`'); - } - '$' => { - if chars.peek() == Some(&'{') { - p.print_ascii_byte(b'\\'); - } - p.print_ascii_byte(b'$'); - } - // Allow `U+2028` and `U+2029` in string literals - // - // - LS => p.print_str("\\u2028"), - PS => p.print_str("\\u2029"), - '\u{a0}' => { - p.print_str("\\xA0"); - } - _ => { - p.print_str(c.encode_utf8([0; 4].as_mut())); - } - } - } -} - impl Gen for StringLiteral<'_> { fn gen(&self, p: &mut Codegen, _ctx: Context) { p.add_source_mapping(self.span); let s = self.value.as_str(); - - let quote = if p.options.minify { - let mut single_cost: u32 = 0; - let mut double_cost: u32 = 0; - let mut backtick_cost: u32 = 0; - let mut bytes = s.as_bytes().iter().peekable(); - while let Some(b) = bytes.next() { - match b { - b'\n' if p.options.minify => { - backtick_cost = backtick_cost.saturating_sub(1); - } - b'\'' => { - single_cost += 1; - } - b'"' => { - double_cost += 1; - } - b'`' => { - backtick_cost += 1; - } - b'$' => { - if bytes.peek() == Some(&&b'{') { - backtick_cost += 1; - } - } - _ => {} - } - } - let mut quote = b'"'; - if double_cost > single_cost { - quote = b'\''; - if single_cost > backtick_cost { - quote = b'`'; - } - } else if double_cost > backtick_cost { - quote = b'`'; - } - quote - } else { - p.quote - }; - - p.print_ascii_byte(quote); - print_unquoted_str(s, quote, p); - p.print_ascii_byte(quote); + p.print_quoted_utf16(s, /* allow_backtick */ true); } } @@ -1770,6 +1656,9 @@ impl Gen for PropertyKey<'_> { match self { Self::StaticIdentifier(ident) => ident.print(p, ctx), Self::PrivateIdentifier(ident) => ident.print(p, ctx), + Self::StringLiteral(s) => { + p.print_quoted_utf16(s.value.as_str(), /* allow_backtick */ false); + } match_expression!(Self) => { self.to_expression().print_expr(p, Precedence::Comma, Context::empty()); } @@ -3425,7 +3314,7 @@ impl Gen for TSLiteral<'_> { Self::BooleanLiteral(decl) => decl.print(p, ctx), Self::NullLiteral(decl) => decl.print(p, ctx), Self::NumericLiteral(decl) => decl.print_expr(p, Precedence::Lowest, ctx), - Self::BigIntLiteral(decl) => decl.print(p, ctx), + Self::BigIntLiteral(decl) => decl.print_expr(p, Precedence::Lowest, ctx), Self::RegExpLiteral(decl) => decl.print(p, ctx), Self::StringLiteral(decl) => decl.print(p, ctx), Self::TemplateLiteral(decl) => decl.print(p, ctx), diff --git a/crates/oxc_codegen/src/lib.rs b/crates/oxc_codegen/src/lib.rs index c26c14f61b84ee..9c15e0f9fea665 100644 --- a/crates/oxc_codegen/src/lib.rs +++ b/crates/oxc_codegen/src/lib.rs @@ -21,7 +21,7 @@ use oxc_ast::ast::{ use oxc_mangler::Mangler; use oxc_span::{GetSpan, Span, SPAN}; use oxc_syntax::{ - identifier::{is_identifier_part, is_identifier_part_ascii}, + identifier::{is_identifier_part, is_identifier_part_ascii, LS, PS}, operator::{BinaryOperator, UnaryOperator, UpdateOperator}, precedence::Precedence, }; @@ -348,6 +348,17 @@ impl<'a> Codegen<'a> { } } + #[inline] + fn wrap(&mut self, wrap: bool, mut f: F) { + if wrap { + self.print_ascii_byte(b'('); + } + f(self); + if wrap { + self.print_ascii_byte(b')'); + } + } + #[inline] fn print_indent(&mut self) { if self.options.minify { @@ -576,6 +587,106 @@ impl<'a> Codegen<'a> { } } + fn print_quoted_utf16(&mut self, s: &str, allow_backtick: bool) { + let quote = if self.options.minify { + let mut single_cost: i32 = 0; + let mut double_cost: i32 = 0; + let mut backtick_cost: i32 = 0; + let mut bytes = s.as_bytes().iter().peekable(); + while let Some(b) = bytes.next() { + match b { + b'\n' if self.options.minify => backtick_cost = backtick_cost.saturating_sub(1), + b'\'' => single_cost += 1, + b'"' => double_cost += 1, + b'`' => backtick_cost += 1, + b'$' => { + if bytes.peek() == Some(&&b'{') { + backtick_cost += 1; + } + } + _ => {} + } + } + let mut quote = b'"'; + if double_cost > single_cost { + quote = b'\''; + if single_cost > backtick_cost && allow_backtick { + quote = b'`'; + } + } else if double_cost > backtick_cost && allow_backtick { + quote = b'`'; + } + quote + } else { + self.quote + }; + + self.print_ascii_byte(quote); + self.print_unquoted_utf16(s, quote); + self.print_ascii_byte(quote); + } + + fn print_unquoted_utf16(&mut self, s: &str, quote: u8) { + let mut chars = s.chars().peekable(); + + while let Some(c) = chars.next() { + match c { + '\x00' => { + if chars.peek().is_some_and(|&next| next.is_ascii_digit()) { + self.print_str("\\x00"); + } else { + self.print_str("\\0"); + } + } + '\x07' => self.print_str("\\x07"), + '\u{8}' => self.print_str("\\b"), // \b + '\u{b}' => self.print_str("\\v"), // \v + '\u{c}' => self.print_str("\\f"), // \f + '\n' => { + if quote == b'`' { + self.print_ascii_byte(b'\n'); + } else { + self.print_str("\\n"); + } + } + '\r' => self.print_str("\\r"), + '\x1B' => self.print_str("\\x1B"), + '\\' => self.print_str("\\\\"), + // Allow `U+2028` and `U+2029` in string literals + // + // + LS => self.print_str("\\u2028"), + PS => self.print_str("\\u2029"), + '\u{a0}' => self.print_str("\\xA0"), + '\'' => { + if quote == b'\'' { + self.print_ascii_byte(b'\\'); + } + self.print_ascii_byte(b'\''); + } + '\"' => { + if quote == b'"' { + self.print_ascii_byte(b'\\'); + } + self.print_ascii_byte(b'"'); + } + '`' => { + if quote == b'`' { + self.print_ascii_byte(b'\\'); + } + self.print_ascii_byte(b'`'); + } + '$' => { + if chars.peek() == Some(&'{') { + self.print_ascii_byte(b'\\'); + } + self.print_ascii_byte(b'$'); + } + _ => self.print_str(c.encode_utf8([0; 4].as_mut())), + } + } + } + // `get_minified_number` from terser // https://github.com/terser/terser/blob/c5315c3fd6321d6b2e076af35a70ef532f498505/lib/output.js#L2418 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_possible_wrap)] @@ -630,17 +741,6 @@ impl<'a> Codegen<'a> { candidates.into_iter().min_by_key(String::len).unwrap() } - #[inline] - fn wrap(&mut self, wrap: bool, mut f: F) { - if wrap { - self.print_ascii_byte(b'('); - } - f(self); - if wrap { - self.print_ascii_byte(b')'); - } - } - fn add_source_mapping(&mut self, span: Span) { if span == SPAN { return; diff --git a/crates/oxc_codegen/src/sourcemap_builder.rs b/crates/oxc_codegen/src/sourcemap_builder.rs index 06fb2a4643e655..08f59ffa533c18 100644 --- a/crates/oxc_codegen/src/sourcemap_builder.rs +++ b/crates/oxc_codegen/src/sourcemap_builder.rs @@ -1,4 +1,4 @@ -use std::{cmp::max, path::Path, sync::Arc}; +use std::{path::Path, sync::Arc}; use nonmax::NonMaxU32; use oxc_index::{Idx, IndexVec}; @@ -235,9 +235,7 @@ impl SourcemapBuilder { let lines = &self.line_offset_tables.lines; let mut idx = self.last_line_lookup as usize; - let cap = idx.saturating_sub(16); - idx = max(idx.saturating_sub(1), cap); - while idx > cap && lines[idx].byte_offset_to_start_of_line > position { + while lines[idx].byte_offset_to_start_of_line > position { idx -= 1; } diff --git a/crates/oxc_codegen/tests/integration/unit.rs b/crates/oxc_codegen/tests/integration/unit.rs index 1a8525f828e947..007e41c80da27c 100644 --- a/crates/oxc_codegen/tests/integration/unit.rs +++ b/crates/oxc_codegen/tests/integration/unit.rs @@ -39,10 +39,7 @@ fn expr() { test("delete 2e308", "delete (0, Infinity);\n"); test_minify("delete 2e308", "delete(1/0);"); - test_minify( - r#";'eval("\'\\vstr\\ving\\v\'") === "\\vstr\\ving\\v"'"#, - r#";`eval("'\\vstr\\ving\\v'") === "\\vstr\\ving\\v"`;"#, - ); + test_minify_same(r#"({"http://a\r\" \n<'b:b@c\r\nd/e?f":{}});"#); } #[test] @@ -90,6 +87,18 @@ fn for_stmt() { ); } +#[test] +fn if_stmt() { + test( + "function f() { if (foo) return foo; else if (bar) return foo; }", + "function f() {\n\tif (foo) return foo;\n\telse if (bar) return foo;\n}\n", + ); + test_minify( + "function f() { if (foo) return foo; else if (bar) return foo; }", + "function f(){if(foo)return foo;else if(bar)return foo}", + ); +} + #[test] fn shorthand() { test("let _ = { x }", "let _ = { x };\n"); @@ -424,3 +433,12 @@ fn getter_setter() { test_minify("({ get [foo]() {} })", "({get[foo](){}});"); test_minify("({ set [foo]() {} })", "({set[foo](){}});"); } + +#[test] +fn string() { + test_minify( + r#";'eval("\'\\vstr\\ving\\v\'") === "\\vstr\\ving\\v"'"#, + r#";`eval("'\\vstr\\ving\\v'") === "\\vstr\\ving\\v"`;"#, + ); + test_minify(r#"foo("\n")"#, "foo(`\n`);"); +} diff --git a/crates/oxc_data_structures/Cargo.toml b/crates/oxc_data_structures/Cargo.toml index 68cc03592a3f36..c6b852140b18f0 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.43.0" +version = "0.44.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 066668e3fa2afa..c03673ba615478 100644 --- a/crates/oxc_diagnostics/Cargo.toml +++ b/crates/oxc_diagnostics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_diagnostics" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_ecmascript/CHANGELOG.md b/crates/oxc_ecmascript/CHANGELOG.md index 92d6f3c50003b6..f0b101c074a1e5 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.44.0] - 2024-12-25 + +### Features + +- 5397fe9 minifier: Constant fold `undefined?.bar` -> `undefined` (#8075) (Boshen) + ## [0.42.0] - 2024-12-18 ### Features diff --git a/crates/oxc_ecmascript/Cargo.toml b/crates/oxc_ecmascript/Cargo.toml index 9ad61b109d81ca..b943cb987b3921 100644 --- a/crates/oxc_ecmascript/Cargo.toml +++ b/crates/oxc_ecmascript/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_ecmascript" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs index a3682629084663..9f8c97b17d1da6 100644 --- a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs +++ b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, cmp::Ordering}; use num_bigint::BigInt; -use num_traits::Zero; +use num_traits::{ToPrimitive, Zero}; use oxc_ast::ast::*; @@ -124,13 +124,6 @@ pub trait ConstantEvaluation<'a> { _ => None, } } - Expression::AssignmentExpression(assign_expr) => { - match assign_expr.operator { - AssignmentOperator::LogicalAnd | AssignmentOperator::LogicalOr => None, - // For ASSIGN, the value is the value of the RHS. - _ => self.get_boolean_value(&assign_expr.right), - } - } expr => { use crate::ToBoolean; expr.to_boolean() @@ -156,6 +149,10 @@ pub trait ConstantEvaluation<'a> { UnaryOperator::Void => Some(f64::NAN), _ => None, }, + Expression::SequenceExpression(s) => { + s.expressions.last().and_then(|e| self.eval_to_number(e)) + } + Expression::ObjectExpression(e) if e.properties.is_empty() => Some(f64::NAN), expr => { use crate::ToNumber; expr.to_number() @@ -193,6 +190,7 @@ pub trait ConstantEvaluation<'a> { Expression::StringLiteral(lit) => { Some(ConstantValue::String(Cow::Borrowed(lit.value.as_str()))) } + Expression::StaticMemberExpression(e) => self.eval_static_member_expression(e), _ => None, } } @@ -253,39 +251,20 @@ pub trait ConstantEvaluation<'a> { }; Some(ConstantValue::Number(val)) } - #[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + #[expect(clippy::cast_sign_loss)] BinaryOperator::ShiftLeft | BinaryOperator::ShiftRight | BinaryOperator::ShiftRightZeroFill => { - let left_num = self.get_side_free_number_value(left); - let right_num = self.get_side_free_number_value(right); - if let (Some(left_val), Some(right_val)) = (left_num, right_num) { - if left_val.fract() != 0.0 || right_val.fract() != 0.0 { - return None; - } - // only the lower 5 bits are used when shifting, so don't do anything - // if the shift amount is outside [0,32) - if !(0.0..32.0).contains(&right_val) { - return None; - } - let right_val_int = right_val as u32; - let bits = left_val.to_int_32(); - - let result_val: f64 = match operator { - BinaryOperator::ShiftLeft => f64::from(bits.wrapping_shl(right_val_int)), - BinaryOperator::ShiftRight => f64::from(bits.wrapping_shr(right_val_int)), - BinaryOperator::ShiftRightZeroFill => { - // JavaScript always treats the result of >>> as unsigned. - // We must force Rust to do the same here. - let bits = bits as u32; - let res = bits.wrapping_shr(right_val_int); - f64::from(res) - } - _ => unreachable!(), - }; - return Some(ConstantValue::Number(result_val)); - } - None + let left = self.get_side_free_number_value(left)?; + let right = self.get_side_free_number_value(right)?; + let left = left.to_int_32(); + let right = (right.to_int_32() as u32) & 31; + Some(ConstantValue::Number(match operator { + BinaryOperator::ShiftLeft => f64::from(left << right), + BinaryOperator::ShiftRight => f64::from(left >> right), + BinaryOperator::ShiftRightZeroFill => f64::from((left as u32) >> right), + _ => unreachable!(), + })) } BinaryOperator::LessThan => { self.is_less_than(left, right, true).map(|value| match value { @@ -347,6 +326,22 @@ pub trait ConstantEvaluation<'a> { } None } + BinaryOperator::Instanceof => { + if left.may_have_side_effects() { + return None; + } + if let Some(right_ident) = right.get_identifier_reference() { + let name = right_ident.name.as_str(); + if matches!(name, "Object" | "Number" | "Boolean" | "String") + && self.is_global_reference(right_ident) + { + return Some(ConstantValue::Boolean( + name == "Object" && ValueType::from(left).is_object(), + )); + } + } + None + } _ => None, } } @@ -391,49 +386,62 @@ pub trait ConstantEvaluation<'a> { }; Some(ConstantValue::String(Cow::Borrowed(s))) } - UnaryOperator::Void => { - if (!expr.argument.is_number() || !expr.argument.is_number_0()) - && !expr.may_have_side_effects() - { - return Some(ConstantValue::Undefined); - } - None - } + UnaryOperator::Void => (expr.argument.is_literal() || !expr.may_have_side_effects()) + .then_some(ConstantValue::Undefined), UnaryOperator::LogicalNot => { self.get_boolean_value(&expr.argument).map(|b| !b).map(ConstantValue::Boolean) } UnaryOperator::UnaryPlus => { self.eval_to_number(&expr.argument).map(ConstantValue::Number) } - UnaryOperator::UnaryNegation => { - let ty = ValueType::from(&expr.argument); - match ty { - ValueType::BigInt => { - self.eval_to_big_int(&expr.argument).map(|v| -v).map(ConstantValue::BigInt) - } - ValueType::Number => self - .eval_to_number(&expr.argument) - .map(|v| if v.is_nan() { v } else { -v }) - .map(ConstantValue::Number), - _ => None, + UnaryOperator::UnaryNegation => match ValueType::from(&expr.argument) { + ValueType::BigInt => { + self.eval_to_big_int(&expr.argument).map(|v| -v).map(ConstantValue::BigInt) } - } - UnaryOperator::BitwiseNot => { - let ty = ValueType::from(&expr.argument); - match ty { - ValueType::BigInt => { - self.eval_to_big_int(&expr.argument).map(|v| !v).map(ConstantValue::BigInt) + ValueType::Number => self + .eval_to_number(&expr.argument) + .map(|v| if v.is_nan() { v } else { -v }) + .map(ConstantValue::Number), + ValueType::Undefined => Some(ConstantValue::Number(f64::NAN)), + ValueType::Null => Some(ConstantValue::Number(-0.0)), + _ => None, + }, + UnaryOperator::BitwiseNot => match ValueType::from(&expr.argument) { + ValueType::BigInt => { + self.eval_to_big_int(&expr.argument).map(|v| !v).map(ConstantValue::BigInt) + } + #[expect(clippy::cast_lossless)] + _ => self + .eval_to_number(&expr.argument) + .map(|v| (!v.to_int_32()) as f64) + .map(ConstantValue::Number), + }, + UnaryOperator::Delete => None, + } + } + + fn eval_static_member_expression( + &self, + expr: &StaticMemberExpression<'a>, + ) -> Option> { + match expr.property.name.as_str() { + "length" => { + if let Some(ConstantValue::String(s)) = self.eval_expression(&expr.object) { + // TODO(perf): no need to actually convert, only need the length + Some(ConstantValue::Number(s.encode_utf16().count().to_f64().unwrap())) + } else { + if expr.object.may_have_side_effects() { + return None; + } + + if let Expression::ArrayExpression(arr) = &expr.object { + Some(ConstantValue::Number(arr.elements.len().to_f64().unwrap())) + } else { + None } - #[expect(clippy::cast_lossless)] - ValueType::Number => self - .eval_to_number(&expr.argument) - .map(|v| !v.to_int_32()) - .map(|v| v as f64) - .map(ConstantValue::Number), - _ => None, } } - UnaryOperator::Delete => None, + _ => None, } } diff --git a/crates/oxc_ecmascript/src/constant_evaluation/value_type.rs b/crates/oxc_ecmascript/src/constant_evaluation/value_type.rs index c9493729c7d51d..d2130876b9433d 100644 --- a/crates/oxc_ecmascript/src/constant_evaluation/value_type.rs +++ b/crates/oxc_ecmascript/src/constant_evaluation/value_type.rs @@ -1,4 +1,4 @@ -use oxc_ast::ast::Expression; +use oxc_ast::ast::{BinaryExpression, Expression}; use oxc_syntax::operator::{BinaryOperator, UnaryOperator}; /// JavaScript Language Type @@ -40,6 +40,14 @@ impl ValueType { pub fn is_boolean(self) -> bool { self == Self::Boolean } + + pub fn is_object(self) -> bool { + self == Self::Object + } + + pub fn is_undetermined(self) -> bool { + self == Self::Undetermined + } } /// `get_known_value_type` @@ -77,31 +85,58 @@ impl<'a> From<&Expression<'a>> for ValueType { Self::Number } UnaryOperator::UnaryPlus => Self::Number, - UnaryOperator::LogicalNot => Self::Boolean, + UnaryOperator::LogicalNot | UnaryOperator::Delete => Self::Boolean, UnaryOperator::Typeof => Self::String, - _ => Self::Undetermined, - }, - Expression::BinaryExpression(binary_expr) => match binary_expr.operator { - BinaryOperator::Addition => { - let left_ty = Self::from(&binary_expr.left); - let right_ty = Self::from(&binary_expr.right); - if left_ty == Self::String || right_ty == Self::String { - return Self::String; - } - // There are some pretty weird cases for object types: - // {} + [] === "0" - // [] + {} === "[object Object]" - if left_ty == Self::Object || right_ty == Self::Object { - return Self::Undetermined; - } - Self::Undetermined - } - _ => Self::Undetermined, + UnaryOperator::BitwiseNot => Self::Undetermined, }, + Expression::BinaryExpression(e) => Self::from(&**e), Expression::SequenceExpression(e) => { e.expressions.last().map_or(ValueType::Undetermined, Self::from) } + Expression::AssignmentExpression(e) => Self::from(&e.right), _ => Self::Undetermined, } } } + +impl<'a> From<&BinaryExpression<'a>> for ValueType { + fn from(e: &BinaryExpression<'a>) -> Self { + match e.operator { + BinaryOperator::Addition => { + let left_ty = Self::from(&e.left); + let right_ty = Self::from(&e.right); + if left_ty == Self::String || right_ty == Self::String { + return Self::String; + } + // There are some pretty weird cases for object types: + // {} + [] === "0" + // [] + {} === "[object Object]" + if left_ty == Self::Object || right_ty == Self::Object { + return Self::Undetermined; + } + Self::Undetermined + } + BinaryOperator::Subtraction + | BinaryOperator::Multiplication + | BinaryOperator::Division + | BinaryOperator::Remainder + | BinaryOperator::ShiftLeft + | BinaryOperator::BitwiseOR + | BinaryOperator::ShiftRight + | BinaryOperator::BitwiseXOR + | BinaryOperator::BitwiseAnd + | BinaryOperator::Exponential + | BinaryOperator::ShiftRightZeroFill => Self::Number, + BinaryOperator::Instanceof + | BinaryOperator::In + | BinaryOperator::Equality + | BinaryOperator::Inequality + | BinaryOperator::StrictEquality + | BinaryOperator::StrictInequality + | BinaryOperator::LessThan + | BinaryOperator::LessEqualThan + | BinaryOperator::GreaterThan + | BinaryOperator::GreaterEqualThan => Self::Boolean, + } + } +} diff --git a/crates/oxc_ecmascript/src/to_int_32.rs b/crates/oxc_ecmascript/src/to_int_32.rs index d0a69a48cf16dc..468a8c5688c4c9 100644 --- a/crates/oxc_ecmascript/src/to_int_32.rs +++ b/crates/oxc_ecmascript/src/to_int_32.rs @@ -56,6 +56,11 @@ impl ToInt32 for f64 { let number = *self; + // NOTE: this also matches with negative zero + if !number.is_finite() || number == 0.0 { + return 0; + } + if number.is_finite() && number <= f64::from(i32::MAX) && number >= f64::from(i32::MIN) { let i = number as i32; if f64::from(i) == number { diff --git a/crates/oxc_ecmascript/src/to_number.rs b/crates/oxc_ecmascript/src/to_number.rs index 927f90693cb869..34bfa90863cb9c 100644 --- a/crates/oxc_ecmascript/src/to_number.rs +++ b/crates/oxc_ecmascript/src/to_number.rs @@ -28,6 +28,10 @@ impl<'a> ToNumber<'a> for Expression<'a> { use crate::StringToNumber; Some(lit.value.as_str().string_to_number()) } + Expression::UnaryExpression(unary) if unary.operator.is_not() => { + let number = unary.argument.to_number()?; + Some(if number == 0.0 { 1.0 } else { 0.0 }) + } _ => None, } } diff --git a/crates/oxc_estree/Cargo.toml b/crates/oxc_estree/Cargo.toml index b374b90e9cefa8..cd081a349ebe02 100644 --- a/crates/oxc_estree/Cargo.toml +++ b/crates/oxc_estree/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_estree" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_isolated_declarations/Cargo.toml b/crates/oxc_isolated_declarations/Cargo.toml index fdaa95e0e87963..0bac7463a76996 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.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_isolated_declarations/src/scope.rs b/crates/oxc_isolated_declarations/src/scope.rs index 5bffc4656997f3..7c4d181b548db9 100644 --- a/crates/oxc_isolated_declarations/src/scope.rs +++ b/crates/oxc_isolated_declarations/src/scope.rs @@ -112,7 +112,7 @@ impl<'a> Visit<'a> for ScopeTree<'a> { fn visit_ts_type_query(&mut self, ty: &TSTypeQuery<'a>) { if let Some(type_name) = ty.expr_name.as_ts_type_name() { - let ident = TSTypeName::get_first_name(type_name); + let ident = TSTypeName::get_identifier_reference(type_name); self.add_reference(ident.name.clone(), KindFlags::Value); } else { walk_ts_type_query(self, ty); diff --git a/crates/oxc_language_server/src/linter/server_linter.rs b/crates/oxc_language_server/src/linter/server_linter.rs index 22104b9768385f..3195ce0d71e360 100644 --- a/crates/oxc_language_server/src/linter/server_linter.rs +++ b/crates/oxc_language_server/src/linter/server_linter.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use tower_lsp::lsp_types::Url; -use oxc_linter::{FixKind, Linter}; +use oxc_linter::{ConfigStoreBuilder, FixKind, LintOptions, Linter}; use crate::linter::error_with_position::DiagnosticReport; use crate::linter::isolated_lint_handler::IsolatedLintHandler; @@ -13,7 +13,9 @@ pub struct ServerLinter { impl ServerLinter { pub fn new() -> Self { - let linter = Linter::default().with_fix(FixKind::SafeFix); + let config_store = + ConfigStoreBuilder::default().build().expect("Failed to build config store"); + let linter = Linter::new(LintOptions::default(), config_store).with_fix(FixKind::SafeFix); Self { linter: Arc::new(linter) } } diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index 7d166f6c79c4ec..431f3732afcbec 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -20,7 +20,7 @@ use tower_lsp::{ Client, LanguageServer, LspService, Server, }; -use oxc_linter::{FixKind, LinterBuilder, Oxlintrc}; +use oxc_linter::{ConfigStoreBuilder, FixKind, LintOptions, Linter, Oxlintrc}; use crate::capabilities::{Capabilities, CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC}; use crate::linter::error_with_position::DiagnosticReport; @@ -488,10 +488,11 @@ impl Backend { let mut linter = self.server_linter.write().await; let config = Oxlintrc::from_file(&config_path) .expect("should have initialized linter with new options"); + let config_store = ConfigStoreBuilder::from_oxlintrc(true, config.clone()) + .build() + .expect("failed to build config"); *linter = ServerLinter::new_with_linter( - LinterBuilder::from_oxlintrc(true, config.clone()) - .with_fix(FixKind::SafeFix) - .build(), + Linter::new(LintOptions::default(), config_store).with_fix(FixKind::SafeFix), ); return Some(config); } diff --git a/crates/oxc_linter/CHANGELOG.md b/crates/oxc_linter/CHANGELOG.md index c8b188a8d05354..d2dd4943368d10 100644 --- a/crates/oxc_linter/CHANGELOG.md +++ b/crates/oxc_linter/CHANGELOG.md @@ -4,6 +4,60 @@ 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.15.5] - 2025-01-02 + +### Features + +- 0e168b8 linter: Catch more cases in const-comparisons (#8215) (Cameron) +- bde44a3 linter: Add `statement_span` to `ModuleRecord/ImportEntry` (#8195) (Alexander S.) +- ccaa9f7 linter: Implement `eslint/new-cap` (#8146) (Alexander S.) + +### Bug Fixes + +- 2b14a6f linter: Fix `ignorePattern` config for windows (#8214) (Alexander S.) + +## [0.15.4] - 2024-12-30 + +- ed75e42 semantic: [**BREAKING**] Make SymbolTable fields `pub(crate)` instead of `pub` (#7999) (Boshen) + +### Features + +- 47cea9a linter: Implement `eslint/no-extra-label` (#8181) (Anson Heung) +- ef76e28 linter: Implement `eslint/no-multi-assign` (#8158) (Anson Heung) +- 384858b linter: Implement `jsx-a11y/no-noninteractive-tabindex` (#8167) (Tyler Earls) +- afc21a6 linter: Implement `eslint/vars-on-top` (#8157) (Yuichiro Yamashita) +- 65796c4 linter: Implement `eslint/prefer-rest-params` (#8155) (Yuichiro Yamashita) +- 5234d96 linter: Implement `eslint/no-nested-ternary` (#8150) (Yuichiro Yamashita) +- 1c5db72 linter: Implement eslint/no-labels (#8131) (Anson Heung) +- 0b04288 linter: Move `import/named` to nursery (#8068) (Boshen) + +### Bug Fixes + +- f3050d4 linter: Exclude svelte files from `no_unused_vars` rule (#8170) (Yuichiro Yamashita) +- faf7464 linter: Disable rule `react/rules-of-hook` by file extension (#8168) (Alexander S.) +- 1171e00 linter: Disable `react/rules-of-hooks` for vue and svelte files (#8165) (Alexander S.) +- 1b9a5ba linter: False positiver in private member expr in oxc/const-comparison (#8164) (camc314) +- 6bd9ddb linter: False positive in `typescript/ban-tslint-comment` (#8094) (dalaoshu) +- 10a1fd5 linter: Rule: `no-restricted-imports` support option `patterns` with `group` key (#8050) (Alexander S.) +- b3f38ae linter: Rule `no-restricted-imports`: support option `allowImportNames` (#8002) (Alexander S.) +- 340cc90 linter: Rule `no-restricted-imports`: fix option "importNames" (#7943) (Alexander S.) +- ec2128e linter: Fix line calculation for `eslint/max-lines` in diagnostics (#7962) (Dmitry Zakharov) +- 79af100 semantic: Reference flags not correctly resolved when after an export stmt (#8134) (camc314) + +### Performance + +- d8d2ec6 linter: Run rules which require typescript syntax only when source type is actually typescript (#8166) (Alexander S.) +- 2736657 semantic: Allocate `UnresolvedReferences` in allocator (#8046) (Boshen) + +### Refactor + +- 774babb linter: Read `exported_bindings_from_star_export` lazily (#8062) (Boshen) +- 547c102 linter: Use `RwLock` instead of `FxDashMap` for module record data (#8061) (Boshen) +- 952d7e4 linter: Rename `flat.rs` to `config.rs` (#8033) (camc314) +- 50848ed linter: Simplify `ConfigStore` to prep for nested configs (#8032) (camc314) +- b2a4a78 linter: Remove unused `with_rules` and `set_rule` methods (#8029) (camc314) +- 02f968d semantic: Change `Bindings` to a plain `FxHashMap` (#8019) (Boshen) + ## [0.15.3] - 2024-12-17 ### Features diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index 10105e422f1528..9eb0a54a63f196 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_linter" -version = "0.15.3" +version = "0.15.5" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_linter/src/builder.rs b/crates/oxc_linter/src/config/config_builder.rs similarity index 87% rename from crates/oxc_linter/src/builder.rs rename to crates/oxc_linter/src/config/config_builder.rs index 768236b68f76ca..1823633d3fb4d4 100644 --- a/crates/oxc_linter/src/builder.rs +++ b/crates/oxc_linter/src/config/config_builder.rs @@ -3,44 +3,43 @@ use std::{ fmt, }; +use oxc_diagnostics::OxcDiagnostic; use oxc_span::CompactStr; use rustc_hash::FxHashSet; use crate::{ config::{ConfigStore, ESLintRule, LintPlugins, OxlintOverrides, OxlintRules}, rules::RULES, - AllowWarnDeny, FixKind, FrameworkFlags, LintConfig, LintFilter, LintFilterKind, LintOptions, - Linter, Oxlintrc, RuleCategory, RuleEnum, RuleWithSeverity, + AllowWarnDeny, LintConfig, LintFilter, LintFilterKind, Oxlintrc, RuleCategory, RuleEnum, + RuleWithSeverity, }; #[must_use = "You dropped your builder without building a Linter! Did you mean to call .build()?"] -pub struct LinterBuilder { +pub struct ConfigStoreBuilder { pub(super) rules: FxHashSet, - options: LintOptions, config: LintConfig, overrides: OxlintOverrides, cache: RulesCache, } -impl Default for LinterBuilder { +impl Default for ConfigStoreBuilder { fn default() -> Self { Self { rules: Self::warn_correctness(LintPlugins::default()), ..Self::empty() } } } -impl LinterBuilder { - /// Create a [`LinterBuilder`] with default plugins enabled and no +impl ConfigStoreBuilder { + /// Create a [`ConfigStoreBuilder`] with default plugins enabled and no /// configured rules. /// /// You can think of this as `oxlint -A all`. pub fn empty() -> Self { - let options = LintOptions::default(); let config = LintConfig::default(); let rules = FxHashSet::default(); let overrides = OxlintOverrides::default(); let cache = RulesCache::new(config.plugins); - Self { rules, options, config, overrides, cache } + Self { rules, config, overrides, cache } } /// Warn on all rules in all plugins and categories, including those in `nursery`. @@ -48,7 +47,6 @@ impl LinterBuilder { /// /// You can think of this as `oxlint -W all -W nursery`. pub fn all() -> Self { - let options = LintOptions::default(); let config = LintConfig { plugins: LintPlugins::all(), ..LintConfig::default() }; let overrides = OxlintOverrides::default(); let cache = RulesCache::new(config.plugins); @@ -57,31 +55,30 @@ impl LinterBuilder { .iter() .map(|rule| RuleWithSeverity { rule: rule.clone(), severity: AllowWarnDeny::Warn }) .collect(), - options, config, overrides, cache, } } - /// Create a [`LinterBuilder`] from a loaded or manually built [`Oxlintrc`]. + /// Create a [`ConfigStoreBuilder`] from a loaded or manually built [`Oxlintrc`]. /// `start_empty` will configure the builder to contain only the /// configuration settings from the config. When this is `false`, the config /// will be applied on top of a default [`Oxlintrc`]. /// /// # Example - /// Here's how to create a [`Linter`] from a `.oxlintrc.json` file. + /// Here's how to create a [`ConfigStore`] from a `.oxlintrc.json` file. /// ``` - /// use oxc_linter::{LinterBuilder, Oxlintrc}; + /// use oxc_linter::{ConfigBuilder, Oxlintrc}; /// let oxlintrc = Oxlintrc::from_file("path/to/.oxlintrc.json").unwrap(); - /// let linter = LinterBuilder::from_oxlintrc(true, oxlintrc).build(); + /// let config_store = ConfigStoreBuilder::from_oxlintrc(true, oxlintrc).build(); /// // you can use `From` as a shorthand for `from_oxlintrc(false, oxlintrc)` - /// let linter = LinterBuilder::from(oxlintrc).build(); + /// let config_store = ConfigStoreBuilder::from(oxlintrc).build(); /// ``` /// /// # Errors /// - /// Will return a [`LinterBuilderError::UnknownRules`] if there are unknown rules in the + /// Will return a [`ConfigBuilderError::UnknownRules`] if there are unknown rules in the /// config. This can happen if the plugin for a rule is not enabled, or the rule name doesn't /// match any recognized rules. pub fn from_oxlintrc(start_empty: bool, oxlintrc: Oxlintrc) -> Self { @@ -99,11 +96,10 @@ impl LinterBuilder { } = oxlintrc; let config = LintConfig { plugins, settings, env, globals, path: Some(path) }; - let options = LintOptions::default(); let rules = if start_empty { FxHashSet::default() } else { Self::warn_correctness(plugins) }; let cache = RulesCache::new(config.plugins); - let mut builder = Self { rules, options, config, overrides, cache }; + let mut builder = Self { rules, config, overrides, cache }; if !categories.is_empty() { builder = builder.with_filters(categories.filters()); @@ -117,24 +113,6 @@ impl LinterBuilder { builder } - #[inline] - pub fn with_framework_hints(mut self, flags: FrameworkFlags) -> Self { - self.options.framework_hints = flags; - self - } - - #[inline] - pub fn and_framework_hints(mut self, flags: FrameworkFlags) -> Self { - self.options.framework_hints |= flags; - self - } - - #[inline] - pub fn with_fix(mut self, fix: FixKind) -> Self { - self.options.fix = fix; - self - } - /// Configure what linter plugins are enabled. /// /// Turning on a plugin will not automatically enable any of its rules. You must do this @@ -146,8 +124,8 @@ impl LinterBuilder { /// This method sets what plugins are enabled and disabled, overwriting whatever existing /// config is set. If you are looking to add/remove plugins, use [`and_plugins`] /// - /// [`with_filters`]: LinterBuilder::with_filters - /// [`and_plugins`]: LinterBuilder::and_plugins + /// [`with_filters`]: ConfigStoreBuilder::with_filters + /// [`and_plugins`]: ConfigStoreBuilder::and_plugins #[inline] pub fn with_plugins(mut self, plugins: LintPlugins) -> Self { self.config.plugins = plugins; @@ -157,7 +135,7 @@ impl LinterBuilder { /// Enable or disable a set of plugins, leaving unrelated plugins alone. /// - /// See [`LinterBuilder::with_plugins`] for details on how plugin configuration affects your + /// See [`ConfigStoreBuilder::with_plugins`] for details on how plugin configuration affects your /// rules. #[inline] pub fn and_plugins(mut self, plugins: LintPlugins, enabled: bool) -> Self { @@ -241,8 +219,8 @@ impl LinterBuilder { } } - #[must_use] - pub fn build(self) -> Linter { + /// # Errors + pub fn build(self) -> Result { // When a plugin gets disabled before build(), rules for that plugin aren't removed until // with_filters() gets called. If the user never calls it, those now-undesired rules need // to be taken out. @@ -253,8 +231,7 @@ impl LinterBuilder { self.rules.into_iter().collect::>() }; rules.sort_unstable_by_key(|r| r.id()); - let config = ConfigStore::new(rules, self.config, self.overrides); - Linter::new(self.options, config) + Ok(ConfigStore::new(rules, self.config, self.overrides)) } /// Warn for all correctness rules in the given set of plugins. @@ -309,8 +286,8 @@ fn get_name(plugin_name: &str, rule_name: &str) -> CompactStr { } } -impl TryFrom for LinterBuilder { - type Error = LinterBuilderError; +impl TryFrom for ConfigStoreBuilder { + type Error = ConfigBuilderError; #[inline] fn try_from(oxlintrc: Oxlintrc) -> Result { @@ -318,27 +295,26 @@ impl TryFrom for LinterBuilder { } } -impl fmt::Debug for LinterBuilder { +impl fmt::Debug for ConfigStoreBuilder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("LinterBuilder") + f.debug_struct("ConfigStoreBuilder") .field("rules", &self.rules) - .field("options", &self.options) .field("config", &self.config) .finish_non_exhaustive() } } -/// An error that can occur while building a [`Linter`] from an [`Oxlintrc`]. +/// An error that can occur while building a [`ConfigStore`] from an [`Oxlintrc`]. #[derive(Debug, Clone)] -pub enum LinterBuilderError { +pub enum ConfigBuilderError { /// There were unknown rules that could not be matched to any known plugins/rules. UnknownRules { rules: Vec }, } -impl std::fmt::Display for LinterBuilderError { +impl std::fmt::Display for ConfigBuilderError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - LinterBuilderError::UnknownRules { rules } => { + ConfigBuilderError::UnknownRules { rules } => { write!(f, "unknown rules: ")?; for rule in rules { write!(f, "{}", rule.full_name())?; @@ -349,7 +325,7 @@ impl std::fmt::Display for LinterBuilderError { } } -impl std::error::Error for LinterBuilderError {} +impl std::error::Error for ConfigBuilderError {} struct RulesCache { all_rules: RefCell>>, @@ -378,9 +354,9 @@ impl RulesCache { // initialize() is called), all_rules will be some if and only if last_fresh_plugins == // plugins. Right before creation, (::new()) and before initialize() is called, these two // fields will be equal _but all_rules will be none_. This is OK for this function, but is - // a possible future foot-gun. LinterBuilder uses this to re-build its rules list in + // a possible future foot-gun. ConfigBuilder uses this to re-build its rules list in // ::build(). If cache is created but never made stale (by changing plugins), - // LinterBuilder's rule list won't need updating anyways, meaning its sound for this to + // ConfigBuilder's rule list won't need updating anyways, meaning its sound for this to // return `false`. self.last_fresh_plugins != self.plugins } @@ -446,7 +422,7 @@ mod test { #[test] fn test_builder_default() { - let builder = LinterBuilder::default(); + let builder = ConfigStoreBuilder::default(); assert_eq!(builder.plugins(), LintPlugins::default()); // populated with all correctness-level ESLint rules at a "warn" severity @@ -465,14 +441,14 @@ mod test { #[test] fn test_builder_empty() { - let builder = LinterBuilder::empty(); + let builder = ConfigStoreBuilder::empty(); assert_eq!(builder.plugins(), LintPlugins::default()); assert!(builder.rules.is_empty()); } #[test] fn test_filter_deny_on_default() { - let builder = LinterBuilder::default(); + let builder = ConfigStoreBuilder::default(); let initial_rule_count = builder.rules.len(); let builder = builder.with_filters([LintFilter::deny(RuleCategory::Correctness)]); @@ -500,7 +476,7 @@ mod test { #[test] fn test_filter_deny_single_enabled_rule_on_default() { for filter_string in ["no-const-assign", "eslint/no-const-assign"] { - let builder = LinterBuilder::default(); + let builder = ConfigStoreBuilder::default(); let initial_rule_count = builder.rules.len(); let builder = @@ -526,7 +502,7 @@ mod test { fn test_filter_warn_single_disabled_rule_on_default() { for filter_string in ["no-console", "eslint/no-console"] { let filter = LintFilter::new(AllowWarnDeny::Warn, filter_string).unwrap(); - let builder = LinterBuilder::default(); + let builder = ConfigStoreBuilder::default(); // sanity check: not already turned on assert!(!builder.rules.iter().any(|r| r.name() == "no-console")); let builder = builder.with_filter(filter); @@ -542,9 +518,11 @@ mod test { #[test] fn test_filter_allow_all_then_warn() { - let builder = - LinterBuilder::default() - .with_filters([LintFilter::new(AllowWarnDeny::Allow, "all").unwrap()]); + let builder = ConfigStoreBuilder::default().with_filters([LintFilter::new( + AllowWarnDeny::Allow, + "all", + ) + .unwrap()]); assert!(builder.rules.is_empty(), "Allowing all rules should empty out the rules list"); let builder = builder.with_filters([LintFilter::warn(RuleCategory::Correctness)]); @@ -570,7 +548,7 @@ mod test { #[test] fn test_rules_after_plugin_added() { - let builder = LinterBuilder::default(); + let builder = ConfigStoreBuilder::default(); let initial_rule_count = builder.rules.len(); let builder = builder.and_plugins(LintPlugins::IMPORT, true); @@ -589,7 +567,7 @@ mod test { let mut desired_plugins = LintPlugins::default(); desired_plugins.set(LintPlugins::TYPESCRIPT, false); - let linter = LinterBuilder::default().with_plugins(desired_plugins).build(); + let linter = ConfigStoreBuilder::default().with_plugins(desired_plugins).build().unwrap(); for rule in linter.rules().iter() { let name = rule.name(); let plugin = rule.plugin_name(); @@ -603,11 +581,11 @@ mod test { #[test] fn test_plugin_configuration() { - let builder = LinterBuilder::default(); + let builder = ConfigStoreBuilder::default(); let initial_plugins = builder.plugins(); // ========================================================================================== - // Test LinterBuilder::and_plugins, which deltas the plugin list instead of overriding it + // Test ConfigStoreBuilder::and_plugins, which deltas the plugin list instead of overriding it // ========================================================================================== // Enable eslint plugin. Since it's already enabled, this does nothing. @@ -632,7 +610,7 @@ mod test { assert_eq!(initial_plugins, builder.plugins()); // ========================================================================================== - // Test LinterBuilder::with_plugins, which _does_ override plugins + // Test ConfigStoreBuilder::with_plugins, which _does_ override plugins // ========================================================================================== let builder = builder.with_plugins(LintPlugins::ESLINT); @@ -660,7 +638,7 @@ mod test { "#, ) .unwrap(); - let builder = LinterBuilder::from_oxlintrc(false, oxlintrc); + let builder = ConfigStoreBuilder::from_oxlintrc(false, oxlintrc); for rule in &builder.rules { let name = rule.name(); let plugin = rule.plugin_name(); diff --git a/crates/oxc_linter/src/config/mod.rs b/crates/oxc_linter/src/config/mod.rs index 7ab4bf56bf6dce..c1011d7c25f245 100644 --- a/crates/oxc_linter/src/config/mod.rs +++ b/crates/oxc_linter/src/config/mod.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; mod categories; +mod config_builder; mod config_store; mod env; mod globals; @@ -9,6 +10,7 @@ mod oxlintrc; mod plugins; mod rules; mod settings; +pub use config_builder::{ConfigBuilderError, ConfigStoreBuilder}; pub use config_store::ConfigStore; pub(crate) use config_store::ResolvedLinterState; pub use env::OxlintEnv; @@ -20,7 +22,7 @@ pub use rules::{ESLintRule, OxlintRules}; pub use settings::{jsdoc::JSDocPluginSettings, OxlintSettings}; #[derive(Debug, Default, Clone)] -pub(crate) struct LintConfig { +pub struct LintConfig { pub(crate) plugins: LintPlugins, pub(crate) settings: OxlintSettings, /// Environments enable and disable collections of global variables. diff --git a/crates/oxc_linter/src/config/oxlintrc.rs b/crates/oxc_linter/src/config/oxlintrc.rs index 37bf6566e24149..e6cf382512afc0 100644 --- a/crates/oxc_linter/src/config/oxlintrc.rs +++ b/crates/oxc_linter/src/config/oxlintrc.rs @@ -127,10 +127,7 @@ impl Oxlintrc { OxcDiagnostic::error(format!("Failed to parse config with error {err:?}")) })?; - // Get absolute path from `path` - let absolute_path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); - - config.path = absolute_path; + config.path = path.to_path_buf(); Ok(config) } diff --git a/crates/oxc_linter/src/config/rules.rs b/crates/oxc_linter/src/config/rules.rs index e661f876f9fe92..251026b5f088c2 100644 --- a/crates/oxc_linter/src/config/rules.rs +++ b/crates/oxc_linter/src/config/rules.rs @@ -160,6 +160,10 @@ fn transform_rule_and_plugin_name<'a>( return (rule_name, "eslint"); } + if plugin_name == "unicorn" && rule_name == "no-negated-condition" { + return ("no-negated-condition", "eslint"); + } + (rule_name, plugin_name) } diff --git a/crates/oxc_linter/src/context/host.rs b/crates/oxc_linter/src/context/host.rs index d44590447af2d3..db7268da01ee22 100644 --- a/crates/oxc_linter/src/context/host.rs +++ b/crates/oxc_linter/src/context/host.rs @@ -10,7 +10,7 @@ use crate::{ frameworks, module_record::ModuleRecord, options::LintOptions, - utils, FrameworkFlags, RuleWithSeverity, + FrameworkFlags, RuleWithSeverity, }; use super::{plugin_name_to_prefix, LintContext}; @@ -171,7 +171,7 @@ impl<'a> ContextHost<'a> { /// Creates a new [`LintContext`] for a specific rule. pub fn spawn(self: Rc, rule: &RuleWithSeverity) -> LintContext<'a> { let rule_name = rule.name(); - let plugin_name = self.map_jest_rule_to_vitest(rule); + let plugin_name = rule.plugin_name(); LintContext { parent: self, @@ -198,22 +198,6 @@ impl<'a> ContextHost<'a> { } } - /// Maps Jest rule names and maps to Vitest rules when possible, returning the original plugin otherwise. - /// - /// Many Vitest rules are essentially ports of the Jest plugin rules with minor modifications. - /// For these rules, we use the corresponding jest rules with some adjustments for compatibility. - fn map_jest_rule_to_vitest(&self, rule: &RuleWithSeverity) -> &'static str { - let plugin_name = rule.plugin_name(); - if self.plugins.has_vitest() - && plugin_name == "jest" - && utils::is_jest_rule_adapted_to_vitest(rule.name()) - { - "vitest" - } else { - plugin_name - } - } - /// Inspect the target file for clues about what frameworks are being used. /// Should only be called once immediately after construction. /// diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index ffcf7a3bf3ecac..27ef351b768e5a 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -4,7 +4,6 @@ mod tester; mod ast_util; -mod builder; mod config; mod context; mod disable_directives; @@ -29,23 +28,22 @@ use oxc_semantic::{AstNode, Semantic}; use rules::RULES; pub use crate::{ - builder::{LinterBuilder, LinterBuilderError}, - config::{ESLintRule, LintPlugins, Oxlintrc}, + config::{ + ConfigBuilderError, ConfigStore, ConfigStoreBuilder, ESLintRule, LintPlugins, Oxlintrc, + }, context::LintContext, fixer::FixKind, frameworks::FrameworkFlags, module_record::ModuleRecord, + options::LintOptions, options::{AllowWarnDeny, InvalidFilterKind, LintFilter, LintFilterKind}, rule::{RuleCategory, RuleFixMeta, RuleMeta, RuleWithSeverity}, service::{LintService, LintServiceOptions}, }; use crate::{ - config::{ - ConfigStore, LintConfig, OxlintEnv, OxlintGlobals, OxlintSettings, ResolvedLinterState, - }, + config::{LintConfig, OxlintEnv, OxlintGlobals, OxlintSettings, ResolvedLinterState}, context::ContextHost, fixer::{Fixer, Message}, - options::LintOptions, rules::RuleEnum, table::RuleTable, utils::iter_possible_jest_call_node, @@ -57,7 +55,7 @@ fn size_asserts() { // `RuleEnum` runs in a really tight loop, make sure it is small for CPU cache. // A reduction from 168 bytes to 16 results 15% performance improvement. // See codspeed in https://github.com/oxc-project/oxc/pull/1783 - assert!(std::mem::size_of::() == 16); + assert_eq!(size_of::(), 16); } #[derive(Debug)] @@ -68,14 +66,8 @@ pub struct Linter { config: ConfigStore, } -impl Default for Linter { - fn default() -> Self { - LinterBuilder::default().build() - } -} - impl Linter { - pub(crate) fn new(options: LintOptions, config: ConfigStore) -> Self { + pub fn new(options: LintOptions, config: ConfigStore) -> Self { Self { options, config } } @@ -103,10 +95,6 @@ impl Linter { self.config.number_of_rules() } - pub(crate) fn rules(&self) -> &Arc<[RuleWithSeverity]> { - self.config.rules() - } - pub fn run<'a>( &self, path: &Path, diff --git a/crates/oxc_linter/src/module_record.rs b/crates/oxc_linter/src/module_record.rs index 996032d5fc104c..5918b7b17b9a10 100644 --- a/crates/oxc_linter/src/module_record.rs +++ b/crates/oxc_linter/src/module_record.rs @@ -156,6 +156,16 @@ impl<'a> From<&oxc_syntax::module_record::NameSpan<'a>> for NameSpan { /// ``` #[derive(Debug, Clone, PartialEq, Eq)] pub struct ImportEntry { + /// Span of the import statement. + /// + /// ## Examples + /// + /// ```ts + /// import { foo } from "mod"; + /// ^^^^^^^^^^^^^^^^^^^^^^^^^ + /// ``` + pub statement_span: Span, + /// String value of the ModuleSpecifier of the ImportDeclaration. /// /// ## Examples @@ -212,6 +222,7 @@ pub struct ImportEntry { impl<'a> From<&oxc_syntax::module_record::ImportEntry<'a>> for ImportEntry { fn from(other: &oxc_syntax::module_record::ImportEntry<'a>) -> Self { Self { + statement_span: other.statement_span, module_request: NameSpan::from(&other.module_request), import_name: ImportImportName::from(&other.import_name), local_name: NameSpan::from(&other.local_name), diff --git a/crates/oxc_linter/src/options/mod.rs b/crates/oxc_linter/src/options/mod.rs index 7012ac83995f56..0c5056a5c986f9 100644 --- a/crates/oxc_linter/src/options/mod.rs +++ b/crates/oxc_linter/src/options/mod.rs @@ -9,7 +9,7 @@ use crate::{fixer::FixKind, FrameworkFlags}; /// Subset of options used directly by the linter. #[derive(Debug, Default, Clone, Copy)] #[cfg_attr(test, derive(PartialEq))] -pub(crate) struct LintOptions { +pub struct LintOptions { pub fix: FixKind, pub framework_hints: FrameworkFlags, } diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 220c9d1c4df144..8dfdead36c74a3 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -45,6 +45,7 @@ mod eslint { pub mod max_classes_per_file; pub mod max_lines; pub mod max_params; + pub mod new_cap; pub mod no_alert; pub mod no_array_constructor; pub mod no_async_promise_executor; @@ -81,6 +82,7 @@ mod eslint { pub mod no_ex_assign; pub mod no_extend_native; pub mod no_extra_boolean_cast; + pub mod no_extra_label; pub mod no_fallthrough; pub mod no_func_assign; pub mod no_global_assign; @@ -90,9 +92,13 @@ mod eslint { pub mod no_irregular_whitespace; pub mod no_iterator; pub mod no_label_var; + pub mod no_labels; pub mod no_loss_of_precision; pub mod no_magic_numbers; + pub mod no_multi_assign; pub mod no_multi_str; + pub mod no_negated_condition; + pub mod no_nested_ternary; pub mod no_new; pub mod no_new_func; pub mod no_new_native_nonconstructor; @@ -140,6 +146,7 @@ mod eslint { pub mod prefer_exponentiation_operator; pub mod prefer_numeric_literals; pub mod prefer_object_has_own; + pub mod prefer_rest_params; pub mod prefer_spread; pub mod radix; pub mod require_await; @@ -151,6 +158,7 @@ mod eslint { pub mod unicode_bom; pub mod use_isnan; pub mod valid_typeof; + pub mod vars_on_top; pub mod yoda; } @@ -319,7 +327,6 @@ mod unicorn { pub mod no_length_as_slice_end; pub mod no_lonely_if; pub mod no_magic_array_flat_depth; - pub mod no_negated_condition; pub mod no_negation_in_equality_check; pub mod no_nested_ternary; pub mod no_new_array; @@ -410,6 +417,7 @@ mod jsx_a11y { pub mod no_aria_hidden_on_focusable; pub mod no_autofocus; pub mod no_distracting_elements; + pub mod no_noninteractive_tabindex; pub mod no_redundant_roles; pub mod prefer_tag_over_role; pub mod role_has_required_aria_props; @@ -535,6 +543,11 @@ oxc_macros::declare_all_lint_rules! { eslint::max_classes_per_file, eslint::max_lines, eslint::max_params, + eslint::new_cap, + eslint::no_extra_label, + eslint::no_multi_assign, + eslint::no_nested_ternary, + eslint::no_labels, eslint::no_restricted_imports, eslint::no_object_constructor, eslint::no_duplicate_imports, @@ -584,6 +597,7 @@ oxc_macros::declare_all_lint_rules! { eslint::no_label_var, eslint::no_loss_of_precision, eslint::no_magic_numbers, + eslint::no_negated_condition, eslint::no_multi_str, eslint::no_new_func, eslint::no_new_native_nonconstructor, @@ -627,6 +641,7 @@ oxc_macros::declare_all_lint_rules! { eslint::no_var, eslint::no_void, eslint::no_with, + eslint::prefer_rest_params, eslint::prefer_exponentiation_operator, eslint::prefer_numeric_literals, eslint::prefer_object_has_own, @@ -641,6 +656,7 @@ oxc_macros::declare_all_lint_rules! { eslint::unicode_bom, eslint::use_isnan, eslint::valid_typeof, + eslint::vars_on_top, eslint::yoda, import::default, import::export, @@ -745,6 +761,7 @@ oxc_macros::declare_all_lint_rules! { jsx_a11y::lang, jsx_a11y::media_has_caption, jsx_a11y::mouse_events_have_key_events, + jsx_a11y::no_noninteractive_tabindex, jsx_a11y::no_access_key, jsx_a11y::no_aria_hidden_on_focusable, jsx_a11y::no_autofocus, @@ -916,7 +933,6 @@ oxc_macros::declare_all_lint_rules! { unicorn::no_length_as_slice_end, unicorn::no_lonely_if, unicorn::no_magic_array_flat_depth, - unicorn::no_negated_condition, unicorn::no_negation_in_equality_check, unicorn::no_nested_ternary, unicorn::no_new_array, diff --git a/crates/oxc_linter/src/rules/eslint/new_cap.rs b/crates/oxc_linter/src/rules/eslint/new_cap.rs new file mode 100644 index 00000000000000..d1283782c0d32b --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/new_cap.rs @@ -0,0 +1,789 @@ +use crate::{context::LintContext, rule::Rule, AstNode}; +use oxc_ast::{ + ast::{ChainElement, ComputedMemberExpression, Expression}, + AstKind, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{CompactStr, GetSpan, Span}; +use regex::Regex; + +fn new_cap_diagnostic(span: Span, cap: &GetCapResult) -> OxcDiagnostic { + let msg = if *cap == GetCapResult::Lower { + "A constructor name should not start with a lowercase letter." + } else { + "A function with a name starting with an uppercase letter should only be used as a constructor." + }; + + OxcDiagnostic::warn(msg).with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct NewCap(Box); + +#[derive(Debug, Default, Clone)] +pub struct NewCapConfig { + new_is_cap: bool, + cap_is_new: bool, + new_is_cap_exceptions: Vec, + new_is_cap_exception_pattern: Option, + cap_is_new_exceptions: Vec, + cap_is_new_exception_pattern: Option, + properties: bool, +} + +impl std::ops::Deref for NewCap { + type Target = NewCapConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +fn bool_serde_value(map: &serde_json::Map, key: &str) -> bool { + let Some(value) = map.get(key) else { + return true; // default value + }; + + value.as_bool().unwrap_or(true) +} + +fn vec_str_serde_value( + map: &serde_json::Map, + key: &str, + default_value: Vec, +) -> Vec { + let Some(value) = map.get(key) else { + return default_value; // default value + }; + + let Some(array_value) = value.as_array() else { + return default_value; // default value + }; + + array_value + .iter() + .map(|value| CompactStr::new(value.as_str().unwrap_or_default())) + .collect::>() +} + +fn regex_serde_value(map: &serde_json::Map, key: &str) -> Option { + let value = map.get(key)?; + let regex_string = value.as_str()?; + + if let Ok(regex) = Regex::new(regex_string) { + return Some(regex); + } + + None +} + +impl From<&serde_json::Value> for NewCap { + fn from(raw: &serde_json::Value) -> Self { + let Some(config_entry) = raw.get(0) else { + return Self(Box::new(NewCapConfig { + new_is_cap: true, + cap_is_new: true, + new_is_cap_exceptions: caps_allowed_vec(), + new_is_cap_exception_pattern: None, + cap_is_new_exceptions: vec![], + cap_is_new_exception_pattern: None, + properties: true, + })); + }; + + let config = config_entry + .as_object() + .map_or_else( + || { + Err(OxcDiagnostic::warn( + "eslint/new-cap: invalid configuration, expected object.", + )) + }, + Ok, + ) + .unwrap(); + + Self(Box::new(NewCapConfig { + new_is_cap: bool_serde_value(config, "newIsCap"), + cap_is_new: bool_serde_value(config, "capIsNew"), + new_is_cap_exceptions: vec_str_serde_value( + config, + "newIsCapExceptions", + caps_allowed_vec(), + ), + new_is_cap_exception_pattern: regex_serde_value(config, "newIsCapExceptionPattern"), + cap_is_new_exceptions: vec_str_serde_value(config, "capIsNewExceptions", vec![]), + cap_is_new_exception_pattern: regex_serde_value(config, "capIsNewExceptionPattern"), + properties: bool_serde_value(config, "properties"), + })) + } +} + +const CAPS_ALLOWED: [&str; 11] = [ + "Array", "Boolean", "Date", "Error", "Function", "Number", "Object", "RegExp", "String", + "Symbol", "BigInt", +]; + +fn caps_allowed_vec() -> Vec { + CAPS_ALLOWED.iter().map(|x| CompactStr::new(x)).collect::>() +} + +declare_oxc_lint!( + /// ### What it does + /// + /// This rule requires constructor names to begin with a capital letter. + /// + /// ### Why is this bad? + /// + /// The new operator in JavaScript creates a new instance of a particular type of object. + /// That type of object is represented by a constructor function. + /// Since constructor functions are just regular functions, the only defining characteristic + /// is that new is being used as part of the call. + /// Native JavaScript functions begin with an uppercase letter to distinguish those functions + /// that are to be used as constructors from functions that are not. + /// Many style guides recommend following this pattern + /// to more easily determine which functions are to be used as constructors. + /// + /// **Warning**: + /// The option `newIsCapExceptionPattern` and `capIsNewExceptionPattern` are implemented with + /// the [rust regex syntax](https://docs.rs/regex/latest/regex/). Many JavaScript features + /// are not supported (Lookahead, Lookbehinds, ...). + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// function foo(arg) { + /// return Boolean(arg); + /// } + /// ``` + /// + /// Examples of **incorrect** code for this rule with the default `{ "newIsCap": true }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "newIsCap": true }]*/ + /// + /// var friend = new person(); + /// ``` + /// + /// Examples of **correct** code for this rule with the default `{ "newIsCap": true }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "newIsCap": true }]*/ + /// + /// var friend = new Person(); + /// ``` + /// + /// Examples of **correct** code for this rule with the `{ "newIsCap": false }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "newIsCap": false }]*/ + /// + /// var friend = new person(); + /// ``` + /// + /// Examples of **incorrect** code for this rule with the default `{ "capIsNew": true }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "capIsNew": true }]*/ + /// + /// var colleague = Person(); + /// ``` + /// + /// Examples of **correct** code for this rule with the default `{ "capIsNew": true }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "capIsNew": true }]*/ + /// + /// var colleague = new Person(); + /// ``` + /// + /// Examples of **correct** code for this rule with the `{ "capIsNew": false }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "capIsNew": false }]*/ + /// + /// var colleague = Person(); + /// ``` + /// + /// Examples of additional **correct** code for this rule with the `{ "newIsCapExceptions": ["events"] }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "newIsCapExceptions": ["events"] }]*/ + /// + /// var events = require('events'); + /// + /// var emitter = new events(); + /// ``` + /// + /// Examples of additional **correct** code for this rule with the `{ "newIsCapExceptionPattern": "^person\\.." }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "newIsCapExceptionPattern": "^person\\.." }]*/ + /// + /// var friend = new person.acquaintance(); + /// + /// var bestFriend = new person.friend(); + /// ``` + /// + /// Examples of additional **correct** code for this rule with the `{ "newIsCapExceptionPattern": "\\.bar$" }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "newIsCapExceptionPattern": "\\.bar$" }]*/ + /// + /// var friend = new person.bar(); + /// ``` + /// + /// Examples of additional **correct** code for this rule with the `{ "capIsNewExceptions": ["Person"] }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "capIsNewExceptions": ["Person"] }]*/ + /// + /// function foo(arg) { + /// return Person(arg); + /// } + /// ``` + /// + /// Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "^person\\.." }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^person\\.." }]*/ + /// + /// var friend = person.Acquaintance(); + /// var bestFriend = person.Friend(); + /// ``` + /// + /// Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "\\.Bar$" }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "capIsNewExceptionPattern": "\\.Bar$" }]*/ + /// + /// foo.Bar(); + /// ``` + /// + /// Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "^Foo" }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^Foo" }]*/ + /// + /// var x = Foo(42); + /// + /// var y = Foobar(42); + /// + /// var z = Foo.Bar(42); + /// ``` + /// + /// ### properties + /// + /// Examples of **incorrect** code for this rule with the default `{ "properties": true }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "properties": true }]*/ + /// + /// var friend = new person.acquaintance(); + /// ``` + /// + /// Examples of **correct** code for this rule with the default `{ "properties": true }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "properties": true }]*/ + /// + /// var friend = new person.Acquaintance(); + /// ``` + /// + /// Examples of **correct** code for this rule with the `{ "properties": false }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "properties": false }]*/ + /// + /// var friend = new person.acquaintance(); + /// ``` + /// + /// Examples of **incorrect** code for this rule with the default `{ "newIsCap": true }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "newIsCap": true }]*/ + /// + /// var friend = new person(); + /// ``` + /// + /// Examples of **correct** code for this rule with the default `{ "newIsCap": true }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "newIsCap": true }]*/ + /// + /// var friend = new Person(); + /// ``` + /// + /// Examples of **correct** code for this rule with the `{ "newIsCap": false }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "newIsCap": false }]*/ + /// + /// var friend = new person(); + /// ``` + /// + /// Examples of **incorrect** code for this rule with the default `{ "capIsNew": true }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "capIsNew": true }]*/ + /// + /// var colleague = Person(); + /// ``` + /// + /// Examples of **correct** code for this rule with the default `{ "capIsNew": true }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "capIsNew": true }]*/ + /// + /// var colleague = new Person(); + /// ``` + /// + /// Examples of **correct** code for this rule with the `{ "capIsNew": false }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "capIsNew": false }]*/ + /// + /// var colleague = Person(); + /// ``` + /// + /// Examples of additional **correct** code for this rule with the `{ "newIsCapExceptions": ["events"] }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "newIsCapExceptions": ["events"] }]*/ + /// + /// var events = require('events'); + /// + /// var emitter = new events(); + /// ``` + /// + /// Examples of additional **correct** code for this rule with the `{ "newIsCapExceptionPattern": "^person\\.." }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "newIsCapExceptionPattern": "^person\\.." }]*/ + /// + /// var friend = new person.acquaintance(); + /// + /// var bestFriend = new person.friend(); + /// ``` + /// + /// Examples of additional **correct** code for this rule with the `{ "newIsCapExceptionPattern": "\\.bar$" }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "newIsCapExceptionPattern": "\\.bar$" }]*/ + /// + /// var friend = new person.bar(); + /// ``` + /// + /// Examples of additional **correct** code for this rule with the `{ "capIsNewExceptions": ["Person"] }` option: + /// + /// ::: correct + /// + /// ```js + /// /*eslint new-cap: ["error", { "capIsNewExceptions": ["Person"] }]*/ + /// + /// function foo(arg) { + /// return Person(arg); + /// } + /// ``` + /// + /// Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "^person\\.." }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^person\\.." }]*/ + /// + /// var friend = person.Acquaintance(); + /// var bestFriend = person.Friend(); + /// ``` + /// + /// Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "\\.Bar$" }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "capIsNewExceptionPattern": "\\.Bar$" }]*/ + /// + /// foo.Bar(); + /// ``` + /// + /// Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "^Foo" }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^Foo" }]*/ + /// + /// var x = Foo(42); + /// + /// var y = Foobar(42); + /// + /// var z = Foo.Bar(42); + /// ``` + /// + /// Examples of **incorrect** code for this rule with the default `{ "properties": true }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "properties": true }]*/ + /// + /// var friend = new person.acquaintance(); + /// ``` + /// + /// + /// Examples of **correct** code for this rule with the default `{ "properties": true }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "properties": true }]*/ + /// + /// var friend = new person.Acquaintance(); + /// ``` + /// + /// Examples of **correct** code for this rule with the `{ "properties": false }` option: + /// + /// ```js + /// /*eslint new-cap: ["error", { "properties": false }]*/ + /// + /// var friend = new person.acquaintance(); + /// ``` + NewCap, + style, + pending // TODO: maybe? +); + +impl Rule for NewCap { + fn from_configuration(value: serde_json::Value) -> Self { + NewCap::from(&value) + } + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.kind() { + AstKind::NewExpression(expression) if self.new_is_cap => { + let callee = expression.callee.without_parentheses(); + + let Some(short_name) = &extract_name_from_expression(callee) else { + return; + }; + + let Some(name) = &extract_name_deep_from_expression(callee) else { + return; + }; + + let capitalization = &get_cap(short_name); + + let allowed = *capitalization != GetCapResult::Lower + || is_cap_allowed_expression( + short_name, + name, + &self.new_is_cap_exceptions, + self.new_is_cap_exception_pattern.as_ref(), + ) + || (!self.properties && short_name != name); + + if !allowed { + ctx.diagnostic(new_cap_diagnostic(callee.span(), capitalization)); + } + } + AstKind::CallExpression(expression) if self.cap_is_new => { + let callee = expression.callee.without_parentheses(); + + let Some(short_name) = &extract_name_from_expression(callee) else { + return; + }; + + let Some(name) = &extract_name_deep_from_expression(callee) else { + return; + }; + + let capitalization = &get_cap(short_name); + + let mut caps_is_new_exceptions = self.cap_is_new_exceptions.clone(); + caps_is_new_exceptions.append(&mut caps_allowed_vec()); + + let allowed = *capitalization != GetCapResult::Upper + || is_cap_allowed_expression( + short_name, + name, + &caps_is_new_exceptions, + self.cap_is_new_exception_pattern.as_ref(), + ) + || (!self.properties && short_name != name); + + if !allowed { + ctx.diagnostic(new_cap_diagnostic(callee.span(), capitalization)); + } + } + _ => (), + } + } +} + +fn extract_name_deep_from_expression(expression: &Expression) -> Option { + if let Some(identifier) = expression.get_identifier_reference() { + return Some(identifier.name.clone().into()); + } + + match expression.without_parentheses() { + Expression::StaticMemberExpression(expression) => { + let prop_name = expression.property.name.clone().into_compact_str(); + let obj_name = + extract_name_deep_from_expression(expression.object.without_parentheses()); + + if let Some(obj_name) = obj_name { + let new_name = format!("{obj_name}.{prop_name}"); + return Some(CompactStr::new(&new_name)); + } + + Some(prop_name) + } + Expression::ComputedMemberExpression(expression) => { + let prop_name = get_computed_member_name(expression)?; + let obj_name = + extract_name_deep_from_expression(expression.object.without_parentheses()); + + if let Some(obj_name) = obj_name { + let new_name = format!("{obj_name}.{prop_name}"); + return Some(CompactStr::new(&new_name)); + } + + Some(prop_name) + } + Expression::ChainExpression(chain) => match &chain.expression { + ChainElement::CallExpression(call) => extract_name_deep_from_expression(&call.callee), + ChainElement::TSNonNullExpression(non_null) => { + extract_name_deep_from_expression(&non_null.expression) + } + ChainElement::StaticMemberExpression(expression) => { + let prop_name = expression.property.name.clone().into_compact_str(); + let obj_name = + extract_name_deep_from_expression(expression.object.without_parentheses()); + + if let Some(obj_name) = obj_name { + let new_name = format!("{obj_name}.{prop_name}"); + return Some(CompactStr::new(&new_name)); + } + + Some(prop_name) + } + ChainElement::ComputedMemberExpression(expression) => { + let prop_name = get_computed_member_name(expression)?; + let obj_name = + extract_name_deep_from_expression(expression.object.without_parentheses()); + + if let Some(obj_name) = obj_name { + let new_name = format!("{obj_name}.{prop_name}"); + return Some(CompactStr::new(&new_name)); + } + + Some(prop_name) + } + ChainElement::PrivateFieldExpression(_) => None, + }, + _ => None, + } +} + +fn get_computed_member_name(computed_member: &ComputedMemberExpression) -> Option { + let expression = computed_member.expression.without_parentheses(); + + match &expression { + Expression::StringLiteral(lit) => Some(lit.value.as_ref().into()), + Expression::TemplateLiteral(lit) if lit.expressions.is_empty() && lit.quasis.len() == 1 => { + Some(lit.quasis[0].value.raw.as_ref().into()) + } + Expression::RegExpLiteral(lit) => lit.raw.as_ref().map(|x| x.clone().into_compact_str()), + _ => None, + } +} + +fn extract_name_from_expression(expression: &Expression) -> Option { + if let Some(identifier) = expression.get_identifier_reference() { + return Some(identifier.name.clone().into()); + } + + match expression.without_parentheses() { + Expression::StaticMemberExpression(expression) => { + Some(expression.property.name.clone().into_compact_str()) + } + Expression::ComputedMemberExpression(expression) => get_computed_member_name(expression), + Expression::ChainExpression(chain) => match &chain.expression { + ChainElement::CallExpression(call) => extract_name_from_expression(&call.callee), + ChainElement::TSNonNullExpression(non_null) => { + extract_name_from_expression(&non_null.expression) + } + ChainElement::StaticMemberExpression(expression) => { + Some(expression.property.name.clone().into_compact_str()) + } + ChainElement::ComputedMemberExpression(expression) => { + get_computed_member_name(expression) + } + ChainElement::PrivateFieldExpression(_) => None, + }, + _ => None, + } +} + +fn is_cap_allowed_expression( + short_name: &CompactStr, + name: &CompactStr, + exceptions: &[CompactStr], + patterns: Option<&Regex>, +) -> bool { + if exceptions.contains(name) || exceptions.contains(short_name) { + return true; + } + + if name == "Date.UTC" { + return true; + } + + if let Some(pattern) = &patterns { + return pattern.find(name).is_some(); + }; + + false +} + +#[derive(PartialEq, Debug)] +enum GetCapResult { + Upper, + Lower, + NonAlpha, +} + +fn get_cap(string: &CompactStr) -> GetCapResult { + let first_char = string.chars().next().unwrap(); + + if !first_char.is_alphabetic() { + return GetCapResult::NonAlpha; + } + + if first_char.is_lowercase() { + return GetCapResult::Lower; + } + + GetCapResult::Upper +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("var x = new Constructor();", None), + ("var x = new a.b.Constructor();", None), + ("var x = new a.b['Constructor']();", None), + ("var x = new a.b[Constructor]();", None), + ("var x = new a.b[constructor]();", None), + ("var x = new function(){};", None), + ("var x = new _;", None), + ("var x = new $;", None), + ("var x = new Σ;", None), + ("var x = new _x;", None), + ("var x = new $x;", None), + ("var x = new this;", None), + ("var x = Array(42)", None), + ("var x = Boolean(42)", None), + ("var x = Date(42)", None), + ("var x = Date.UTC(2000, 0)", None), + ("var x = Error('error')", None), + ("var x = Function('return 0')", None), + ("var x = Number(42)", None), + ("var x = Object(null)", None), + ("var x = RegExp(42)", None), + ("var x = String(42)", None), + ("var x = Symbol('symbol')", None), + ("var x = BigInt('1n')", None), + ("var x = _();", None), + ("var x = $();", None), + ("var x = Foo(42)", Some(serde_json::json!([{ "capIsNew": false }]))), + ("var x = bar.Foo(42)", Some(serde_json::json!([{ "capIsNew": false }]))), + ("var x = Foo.bar(42)", Some(serde_json::json!([{ "capIsNew": false }]))), + ("var x = bar[Foo](42)", None), + ("var x = bar['Foo'](42)", Some(serde_json::json!([{ "capIsNew": false }]))), + ("var x = Foo.bar(42)", None), + ("var x = new foo(42)", Some(serde_json::json!([{ "newIsCap": false }]))), + ("var o = { 1: function() {} }; o[1]();", None), + ("var o = { 1: function() {} }; new o[1]();", None), + ( + "var x = Foo(42);", + Some(serde_json::json!([{ "capIsNew": true, "capIsNewExceptions": ["Foo"] }])), + ), + ("var x = Foo(42);", Some(serde_json::json!([{ "capIsNewExceptionPattern": "^Foo" }]))), + ( + "var x = new foo(42);", + Some(serde_json::json!([{ "newIsCap": true, "newIsCapExceptions": ["foo"] }])), + ), + ("var x = new foo(42);", Some(serde_json::json!([{ "newIsCapExceptionPattern": "^foo" }]))), + ("var x = Object(42);", Some(serde_json::json!([{ "capIsNewExceptions": ["Foo"] }]))), + ("var x = Foo.Bar(42);", Some(serde_json::json!([{ "capIsNewExceptions": ["Bar"] }]))), + ("var x = Foo.Bar(42);", Some(serde_json::json!([{ "capIsNewExceptions": ["Foo.Bar"] }]))), + ( + "var x = Foo.Bar(42);", + Some(serde_json::json!([{ "capIsNewExceptionPattern": "^Foo\\.." }])), + ), + ("var x = new foo.bar(42);", Some(serde_json::json!([{ "newIsCapExceptions": ["bar"] }]))), + ( + "var x = new foo.bar(42);", + Some(serde_json::json!([{ "newIsCapExceptions": ["foo.bar"] }])), + ), + ( + "var x = new foo.bar(42);", + Some(serde_json::json!([{ "newIsCapExceptionPattern": "^foo\\.." }])), + ), + ("var x = new foo.bar(42);", Some(serde_json::json!([{ "properties": false }]))), + ("var x = Foo.bar(42);", Some(serde_json::json!([{ "properties": false }]))), + ( + "var x = foo.Bar(42);", + Some(serde_json::json!([{ "capIsNew": false, "properties": false }])), + ), + ("foo?.bar();", None), // { "ecmaVersion": 2020 }, + ("(foo?.bar)();", None), // { "ecmaVersion": 2020 }, + ("new (foo?.Bar)();", None), // { "ecmaVersion": 2020 }, + ("(foo?.Bar)();", Some(serde_json::json!([{ "properties": false }]))), // { "ecmaVersion": 2020 }, + ("new (foo?.bar)();", Some(serde_json::json!([{ "properties": false }]))), // { "ecmaVersion": 2020 }, + ("Date?.UTC();", None), // { "ecmaVersion": 2020 }, + ("(Date?.UTC)();", None), // { "ecmaVersion": 2020 } + ]; + + let fail = vec![ + ("var x = new c();", None), + ("var x = new φ;", None), + ("var x = new a.b.c;", None), + ("var x = new a.b['c'];", None), + ("var b = Foo();", None), + ("var b = a.Foo();", None), + ("var b = a['Foo']();", None), + ("var b = a.Date.UTC();", None), + ("var b = UTC();", None), + ("var a = B.C();", None), + ( + "var a = B + .C();", + None, + ), + ("var a = new B.c();", None), + ( + "var a = new B. + c();", + None, + ), + ("var a = new c();", None), + ("var a = new b[ ( 'foo' ) ]();", None), // { "ecmaVersion": 6 }, + ("var a = new b[`foo`];", None), // { "ecmaVersion": 6 }, + // ( + // "var a = b[`\\ + // Foo`]();", + // None, + // ), // { "ecmaVersion": 6 }, + ("var x = Foo.Bar(42);", Some(serde_json::json!([{ "capIsNewExceptions": ["Foo"] }]))), + ( + "var x = Bar.Foo(42);", + Some(serde_json::json!([{ "capIsNewExceptionPattern": "^Foo\\.." }])), + ), + ("var x = new foo.bar(42);", Some(serde_json::json!([{ "newIsCapExceptions": ["foo"] }]))), + ( + "var x = new bar.foo(42);", + Some(serde_json::json!([{ "newIsCapExceptionPattern": "^foo\\.." }])), + ), + ("new (foo?.bar)();", None), // { "ecmaVersion": 2020 }, + ("foo?.Bar();", None), // { "ecmaVersion": 2020 }, + ("(foo?.Bar)();", None), // { "ecmaVersion": 2020 } + ]; + + Tester::new(NewCap::NAME, NewCap::CATEGORY, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/rules/eslint/no_extra_label.rs b/crates/oxc_linter/src/rules/eslint/no_extra_label.rs new file mode 100644 index 00000000000000..e2868599fc06c6 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_extra_label.rs @@ -0,0 +1,278 @@ +use oxc_ast::{ast::LabelIdentifier, AstKind}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{GetSpan, Span}; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +fn no_extra_label_diagnostic(label: &LabelIdentifier) -> OxcDiagnostic { + let label_name = &label.name; + OxcDiagnostic::warn(format!("This label '{label_name}' is unnecessary")) + .with_help(format!("Remove this label. It will have the same result because the labeled statement '{label_name}' has no nested loops or switches",)) + .with_label(label.span) +} + +#[derive(Debug, Default, Clone)] +pub struct NoExtraLabel; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow unnecessary labels. + /// + /// ### Why is this bad? + /// + /// If a loop contains no nested loops or switches, labeling the loop is unnecessary. + /// ```js + /// A: while (a) { + /// break A; + /// } + /// ``` + /// You can achieve the same result by removing the label and using `break` or `continue` without a label. + /// Probably those labels would confuse developers because they expect labels to jump to further. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// A: while (a) { + /// break A; + /// } + /// + /// B: for (let i = 0; i < 10; ++i) { + /// break B; + /// } + /// + /// C: switch (a) { + /// case 0: + /// break C; + /// } + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// while (a) { + /// break; + /// } + /// + /// for (let i = 0; i < 10; ++i) { + /// break; + /// } + /// + /// switch (a) { + /// case 0: + /// break; + /// } + /// + /// A: { + /// break A; + /// } + /// + /// B: while (a) { + /// while (b) { + /// break B; + /// } + /// } + /// + /// C: switch (a) { + /// case 0: + /// while (b) { + /// break C; + /// } + /// break; + /// } + /// ``` + NoExtraLabel, + style, + fix +); + +impl Rule for NoExtraLabel { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::BreakStatement(break_stmt) = node.kind() { + if let Some(label) = &break_stmt.label { + report_label_if_extra(label, node, ctx); + } + } + if let AstKind::ContinueStatement(cont_stmt) = node.kind() { + if let Some(label) = &cont_stmt.label { + report_label_if_extra(label, node, ctx); + } + } + } +} + +fn report_label_if_extra(label: &LabelIdentifier, node: &AstNode, ctx: &LintContext) { + let nodes = ctx.nodes(); + for ancestor_id in nodes.ancestor_ids(node.id()) { + if !is_breakable_statement(nodes.kind(ancestor_id)) { + continue; + } + let Some(AstKind::LabeledStatement(labeled_stmt)) = nodes.parent_kind(ancestor_id) else { + return; // no need to check outer loops/switches + }; + if labeled_stmt.label.name != label.name { + return; + } + + let keyword_len: u32 = match node.kind() { + AstKind::BreakStatement(_) => 5, + AstKind::ContinueStatement(_) => 8, + _ => unreachable!(), + }; + + let keyword_end = node.span().start + keyword_len; + let delete_span = Span::new(keyword_end, label.span.end); + + let diagnostic = no_extra_label_diagnostic(label); + if ctx.comments().iter().any(|comment| delete_span.contains_inclusive(comment.span)) { + // No autofix to avoid deleting comments between keyword and label + // e.g. `break /* comment */ label;` + ctx.diagnostic(diagnostic); + } else { + // e.g. `break label;` -> `break;` + ctx.diagnostic_with_fix(diagnostic, |fixer| fixer.delete_range(delete_span)); + } + return; + } +} + +fn is_breakable_statement(kind: AstKind) -> bool { + match kind { + kind if kind.is_iteration_statement() => true, + AstKind::SwitchStatement(_) => true, + _ => false, + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "A: break A;", + "A: { if (a) break A; }", + "A: { while (b) { break A; } }", + "A: { switch (b) { case 0: break A; } }", + "A: while (a) { while (b) { break; } break; }", + "A: while (a) { while (b) { break A; } }", + "A: while (a) { while (b) { continue A; } }", + "A: while (a) { switch (b) { case 0: break A; } }", + "A: while (a) { switch (b) { case 0: continue A; } }", + "A: switch (a) { case 0: while (b) { break A; } }", + "A: switch (a) { case 0: switch (b) { case 0: break A; } }", + "A: for (;;) { while (b) { break A; } }", + "A: do { switch (b) { case 0: break A; break; } } while (a);", + "A: for (a in obj) { while (b) { break A; } }", + "A: for (a of ary) { switch (b) { case 0: break A; } }", // { "ecmaVersion": 6 } + ]; + + let fail = vec![ + "A: while (a) break A;", + "A: while (a) { B: { continue A; } }", + "X: while (x) { A: while (a) { B: { break A; break B; continue X; } } }", + "A: do { break A; } while (a);", + "A: for (;;) { break A; }", + "A: for (a in obj) { break A; }", + "A: for (a of ary) { break A; }", // { "ecmaVersion": 6 }, + "A: switch (a) { case 0: break A; }", + "X: while (x) { A: switch (a) { case 0: break A; } }", + "X: switch (a) { case 0: A: while (b) break A; }", + " + A: while (true) { + break A; + while (true) { + break A; + } + } + ", + "A: while(true) { /*comment*/break A; }", + "A: while(true) { break/**/ A; }", + "A: while(true) { continue /**/ A; }", + "A: while(true) { break /**/A; }", + "A: while(true) { continue/**/A; }", + "A: while(true) { continue A/*comment*/; }", + "A: while(true) { break A//comment + }", + "A: while(true) { break A/*comment*/ + foo() }", + ]; + + let fix = vec![ + ("A: while (a) break A;", "A: while (a) break;", None), + ("A: while (a) { B: { continue A; } }", "A: while (a) { B: { continue; } }", None), + ( + "X: while (x) { A: while (a) { B: { break A; break B; continue X; } } }", + "X: while (x) { A: while (a) { B: { break; break B; continue X; } } }", + None, + ), + ("A: do { break A; } while (a);", "A: do { break; } while (a);", None), + ("A: for (;;) { break A; }", "A: for (;;) { break; }", None), + ("A: for (a in obj) { break A; }", "A: for (a in obj) { break; }", None), + ("A: for (a of ary) { break A; }", "A: for (a of ary) { break; }", None), + ("A: switch (a) { case 0: break A; }", "A: switch (a) { case 0: break; }", None), + ( + "X: while (x) { A: switch (a) { case 0: break A; } }", + "X: while (x) { A: switch (a) { case 0: break; } }", + None, + ), + ( + "X: switch (a) { case 0: A: while (b) break A; }", + "X: switch (a) { case 0: A: while (b) break; }", + None, + ), + ( + " + A: while (true) { + break A; + while (true) { + break A; + } + } + ", + " + A: while (true) { + break; + while (true) { + break A; + } + } + ", + None, + ), + ("A: while(true) { /*comment*/break A; }", "A: while(true) { /*comment*/break; }", None), + ( + "A: while(true) { continue A/*comment*/; }", + "A: while(true) { continue/*comment*/; }", + None, + ), + ( + "A: while(true) { break A//comment + }", + "A: while(true) { break//comment + }", + None, + ), + ( + "A: while(true) { break A/*comment*/ + foo() }", + "A: while(true) { break/*comment*/ + foo() }", + None, + ), + // Do not fix if a comment sits between break/continue and label + ( + r"A: while(true) { break /*comment*/ A; }", + r"A: while(true) { break /*comment*/ A; }", + None, + ), + ( + r"A: while(true) { continue /*comment*/ A; }", + r"A: while(true) { continue /*comment*/ A; }", + None, + ), + ]; + Tester::new(NoExtraLabel::NAME, NoExtraLabel::CATEGORY, pass, fail) + .expect_fix(fix) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/rules/eslint/no_labels.rs b/crates/oxc_linter/src/rules/eslint/no_labels.rs new file mode 100644 index 00000000000000..4fb28a7cc410bc --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_labels.rs @@ -0,0 +1,272 @@ +use oxc_ast::{ + ast::{LabelIdentifier, Statement}, + AstKind, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_semantic::NodeId; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +fn no_labels_diagnostic(message: &'static str, label_span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn(message).with_label(label_span) +} + +#[derive(Debug, Default, Clone)] +pub struct NoLabels { + allow_loop: bool, + allow_switch: bool, +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow labeled statements. + /// + /// ### Why is this bad? + /// + /// Labeled statements in JavaScript are used in conjunction with `break` and `continue` to control flow around multiple loops. For example: + /// ```js + /// outer: + /// while (true) { + /// while (true) { + /// break outer; + /// } + /// } + /// ``` + /// The `break outer` statement ensures that this code will not result in an infinite loop because control is returned to the next statement after the `outer` label was applied. If this statement was changed to be just `break`, control would flow back to the outer `while` statement and an infinite loop would result. + /// While convenient in some cases, labels tend to be used only rarely and are frowned upon by some as a remedial form of flow control that is more error prone and harder to understand. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// label: + /// while(true) { + /// // ... + /// } + /// + /// label: + /// while(true) { + /// break label; + /// } + /// + /// label: + /// while(true) { + /// continue label; + /// } + /// + /// label: + /// switch (a) { + /// case 0: + /// break label; + /// } + /// + /// label: + /// { + /// break label; + /// } + /// + /// label: + /// if (a) { + /// break label; + /// } + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// var f = { + /// label: "foo" + /// }; + /// + /// while (true) { + /// break; + /// } + /// + /// while (true) { + /// continue; + /// } + /// ``` + /// + /// ### Options + /// + /// The options allow labels with loop or switch statements: + /// * `"allowLoop"` (`boolean`, default is `false`) - If this option was set `true`, this rule ignores labels which are sticking to loop statements. + /// * `"allowSwitch"` (`boolean`, default is `false`) - If this option was set `true`, this rule ignores labels which are sticking to switch statements. + /// + /// Actually labeled statements in JavaScript can be used with other than loop and switch statements. + /// However, this way is ultra rare, not well-known, so this would be confusing developers. + /// + /// #### allowLoop + /// + /// Examples of **correct** code for the `{ "allowLoop": true }` option: + /// ```js + /// label: + /// while (true) { + /// break label; + /// } + /// ``` + /// + /// #### allowSwitch + /// + /// Examples of **correct** code for the `{ "allowSwitch": true }` option: + /// ```js + /// label: + /// switch (a) { + /// case 0: + /// break label; + /// } + /// ``` + NoLabels, + style, +); + +impl Rule for NoLabels { + fn from_configuration(value: serde_json::Value) -> Self { + let allow_loop = value + .get(0) + .and_then(|config| config.get("allowLoop")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(false); + + let allow_switch = value + .get(0) + .and_then(|config| config.get("allowSwitch")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(false); + + Self { allow_loop, allow_switch } + } + + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::LabeledStatement(labeled_stmt) = node.kind() { + if !self.is_allowed(&labeled_stmt.body) { + let label_span = labeled_stmt.label.span; + ctx.diagnostic(no_labels_diagnostic( + "Labeled statement is not allowed", + label_span, + )); + } + } + + if let AstKind::BreakStatement(break_stmt) = node.kind() { + let Some(label) = &break_stmt.label else { return }; + + if !self.is_allowed_in_break_or_continue(label, node.id(), ctx) { + ctx.diagnostic(no_labels_diagnostic( + "Label in break statement is not allowed", + label.span, + )); + } + } + + if let AstKind::ContinueStatement(cont_stmt) = node.kind() { + let Some(label) = &cont_stmt.label else { return }; + + if !self.is_allowed_in_break_or_continue(label, node.id(), ctx) { + ctx.diagnostic(no_labels_diagnostic( + "Label in continue statement is not allowed", + label.span, + )); + } + } + } +} + +impl NoLabels { + fn is_allowed(&self, stmt: &Statement) -> bool { + match stmt { + stmt if stmt.is_iteration_statement() => self.allow_loop, + Statement::SwitchStatement(_) => self.allow_switch, + _ => false, + } + } + + /// Whether the `label` in break/continue statement is allowed. + fn is_allowed_in_break_or_continue<'a>( + &self, + label: &LabelIdentifier<'a>, + stmt_node_id: NodeId, + ctx: &LintContext<'a>, + ) -> bool { + let nodes = ctx.nodes(); + for ancestor_kind in nodes.ancestor_kinds(stmt_node_id) { + if let AstKind::LabeledStatement(labeled_stmt) = ancestor_kind { + if label.name == labeled_stmt.label.name { + return self.is_allowed(&labeled_stmt.body); + } + } + } + false + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("var f = { label: foo ()}", None), + ("while (true) {}", None), + ("while (true) { break; }", None), + ("while (true) { continue; }", None), + ("A: while (a) { break A; }", Some(serde_json::json!([{ "allowLoop": true }]))), + ( + "A: do { if (b) { break A; } } while (a);", + Some(serde_json::json!([{ "allowLoop": true }])), + ), + ( + "A: for (var a in obj) { for (;;) { switch (a) { case 0: continue A; } } }", + Some(serde_json::json!([{ "allowLoop": true }])), + ), + ("A: switch (a) { case 0: break A; }", Some(serde_json::json!([{ "allowSwitch": true }]))), + ]; + + let fail = vec![ + ("label: while(true) {}", None), + ("label: while (true) { break label; }", None), + ("label: while (true) { continue label; }", None), + ("A: var foo = 0;", None), + ("A: break A;", None), + ("A: { if (foo()) { break A; } bar(); };", None), + ("A: if (a) { if (foo()) { break A; } bar(); };", None), + ("A: switch (a) { case 0: break A; default: break; };", None), + ("A: switch (a) { case 0: B: { break A; } default: break; };", None), + ("A: var foo = 0;", Some(serde_json::json!([{ "allowLoop": true }]))), + ("A: break A;", Some(serde_json::json!([{ "allowLoop": true }]))), + ( + "A: { if (foo()) { break A; } bar(); };", + Some(serde_json::json!([{ "allowLoop": true }])), + ), + ( + "A: if (a) { if (foo()) { break A; } bar(); };", + Some(serde_json::json!([{ "allowLoop": true }])), + ), + ( + "A: switch (a) { case 0: break A; default: break; };", + Some(serde_json::json!([{ "allowLoop": true }])), + ), + ("A: var foo = 0;", Some(serde_json::json!([{ "allowSwitch": true }]))), + ("A: break A;", Some(serde_json::json!([{ "allowSwitch": true }]))), + ( + "A: { if (foo()) { break A; } bar(); };", + Some(serde_json::json!([{ "allowSwitch": true }])), + ), + ( + "A: if (a) { if (foo()) { break A; } bar(); };", + Some(serde_json::json!([{ "allowSwitch": true }])), + ), + ("A: while (a) { break A; }", Some(serde_json::json!([{ "allowSwitch": true }]))), + ( + "A: do { if (b) { break A; } } while (a);", + Some(serde_json::json!([{ "allowSwitch": true }])), + ), + ( + "A: for (var a in obj) { for (;;) { switch (a) { case 0: break A; } } }", + Some(serde_json::json!([{ "allowSwitch": true }])), + ), + ]; + + Tester::new(NoLabels::NAME, NoLabels::CATEGORY, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/rules/eslint/no_multi_assign.rs b/crates/oxc_linter/src/rules/eslint/no_multi_assign.rs new file mode 100644 index 00000000000000..72a1b548ed5cf5 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_multi_assign.rs @@ -0,0 +1,226 @@ +use oxc_ast::{ast::Expression, AstKind}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +fn no_multi_assign_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Do not use chained assignment") + .with_help("Separate each assignment into its own statement") + .with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct NoMultiAssign { + ignore_non_declaration: bool, +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow use of chained assignment expressions. + /// + /// ### Why is this bad? + /// + /// Chaining the assignment of variables can lead to unexpected results and be difficult to read. + /// ```js + /// (function() { + /// const foo = bar = 0; // Did you mean `foo = bar == 0`? + /// bar = 1; // This will not fail since `bar` is not constant. + /// })(); + /// console.log(bar); // This will output 1 since `bar` is not scoped. + /// ``` + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// var a = b = c = 5; + /// + /// const foo = bar = "baz"; + /// + /// let d = + /// e = + /// f; + /// + /// class Foo { + /// a = b = 10; + /// } + /// + /// a = b = "quux"; + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// var a = 5; + /// var b = 5; + /// var c = 5; + /// + /// const foo = "baz"; + /// const bar = "baz"; + /// + /// let d = c; + /// let e = c; + /// + /// class Foo { + /// a = 10; + /// b = 10; + /// } + /// + /// a = "quux"; + /// b = "quux"; + /// ``` + /// + /// ### Options + /// + /// This rule has an object option: + /// * `"ignoreNonDeclaration"`: When set to `true`, the rule allows chains that don't include initializing a variable in a declaration or initializing a class field. Default is `false`. + /// + /// #### ignoreNonDeclaration + /// + /// Examples of **correct** code for the `{ "ignoreNonDeclaration": true }` option: + /// ```js + /// let a; + /// let b; + /// a = b = "baz"; + /// + /// const x = {}; + /// const y = {}; + /// x.one = y.one = 1; + /// ``` + /// + /// Examples of **incorrect** code for the `{ "ignoreNonDeclaration": true }` option: + /// ```js + /// let a = b = "baz"; + /// + /// const foo = bar = 1; + /// + /// class Foo { + /// a = b = 10; + /// } + /// ``` + NoMultiAssign, + style, +); + +impl Rule for NoMultiAssign { + fn from_configuration(value: serde_json::Value) -> Self { + let ignore_non_declaration = value + .get(0) + .and_then(|config| config.get("ignoreNonDeclaration")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(false); + + Self { ignore_non_declaration } + } + + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + // e.g. `var a = b = c;` + if let AstKind::VariableDeclarator(declarator) = node.kind() { + let Some(Expression::AssignmentExpression(assign_expr)) = &declarator.init else { + return; + }; + ctx.diagnostic(no_multi_assign_diagnostic(assign_expr.span)); + } + + // e.g. `class A { a = b = 1; }` + if let AstKind::PropertyDefinition(prop_def) = node.kind() { + let Some(Expression::AssignmentExpression(assign_expr)) = &prop_def.value else { + return; + }; + ctx.diagnostic(no_multi_assign_diagnostic(assign_expr.span)); + } + + // e.g. `let a; let b; a = b = 1;` + if !self.ignore_non_declaration { + if let AstKind::AssignmentExpression(parent_expr) = node.kind() { + let Expression::AssignmentExpression(expr) = &parent_expr.right else { + return; + }; + ctx.diagnostic(no_multi_assign_diagnostic(expr.span)); + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ( + "var a, b, c, + d = 0;", + None, + ), + ( + "var a = 1; var b = 2; var c = 3; + var d = 0;", + None, + ), + ("var a = 1 + (b === 10 ? 5 : 4);", None), + ("const a = 1, b = 2, c = 3;", None), // { "ecmaVersion": 6 }, + ( + "const a = 1; + const b = 2; + const c = 3;", + None, + ), // { "ecmaVersion": 6 }, + ("for(var a = 0, b = 0;;){}", None), + ("for(let a = 0, b = 0;;){}", None), // { "ecmaVersion": 6 }, + ("for(const a = 0, b = 0;;){}", None), // { "ecmaVersion": 6 }, + ("export let a, b;", None), // { "ecmaVersion": 6, "sourceType": "module" }, + ( + "export let a, + b = 0;", + None, + ), // { "ecmaVersion": 6, "sourceType": "module" }, + ( + "const x = {};const y = {};x.one = y.one = 1;", + Some(serde_json::json!([{ "ignoreNonDeclaration": true }])), + ), // { "ecmaVersion": 6 }, + ("let a, b;a = b = 1", Some(serde_json::json!([{ "ignoreNonDeclaration": true }]))), // { "ecmaVersion": 6 }, + ("class C { [foo = 0] = 0 }", None), // { "ecmaVersion": 2022 } + ]; + + let fail = vec![ + ("var a = b = c;", None), + ("var a = b = c = d;", None), + ("let foo = bar = cee = 100;", None), // { "ecmaVersion": 6 }, + ("a=b=c=d=e", None), + ("a=b=c", None), + ( + "a + =b + =c", + None, + ), + ("var a = (b) = (((c)))", None), + ("var a = ((b)) = (c)", None), + ("var a = b = ( (c * 12) + 2)", None), + ( + "var a = + ((b)) + = (c)", + None, + ), + ("a = b = '=' + c + 'foo';", None), + ("a = b = 7 * 12 + 5;", None), + ( + "const x = {}; + const y = x.one = 1;", + Some(serde_json::json!([{ "ignoreNonDeclaration": true }])), + ), // { "ecmaVersion": 6 }, + ("let a, b;a = b = 1", Some(serde_json::json!([{}]))), // { "ecmaVersion": 6 }, + ("let x, y;x = y = 'baz'", Some(serde_json::json!([{ "ignoreNonDeclaration": false }]))), // { "ecmaVersion": 6 }, + ("const a = b = 1", Some(serde_json::json!([{ "ignoreNonDeclaration": true }]))), // { "ecmaVersion": 6 }, + ("class C { field = foo = 0 }", None), // { "ecmaVersion": 2022 }, + ( + "class C { field = foo = 0 }", + Some(serde_json::json!([{ "ignoreNonDeclaration": true }])), + ), // { "ecmaVersion": 2022 } + ]; + + Tester::new(NoMultiAssign::NAME, NoMultiAssign::CATEGORY, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/rules/unicorn/no_negated_condition.rs b/crates/oxc_linter/src/rules/eslint/no_negated_condition.rs similarity index 85% rename from crates/oxc_linter/src/rules/unicorn/no_negated_condition.rs rename to crates/oxc_linter/src/rules/eslint/no_negated_condition.rs index 2786b86798af2b..60ffa1770a1693 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_negated_condition.rs +++ b/crates/oxc_linter/src/rules/eslint/no_negated_condition.rs @@ -106,6 +106,20 @@ fn test() { use crate::tester::Tester; let pass = vec![ + "if (a) {}", + "if (a) {} else {}", + "if (!a) {}", + "if (!a) {} else if (b) {}", + "if (!a) {} else if (b) {} else {}", + "if (a == b) {}", + "if (a == b) {} else {}", + "if (a != b) {}", + "if (a != b) {} else if (b) {}", + "if (a != b) {} else if (b) {} else {}", + "if (a !== b) {}", + "if (a === b) {} else {}", + "a ? b : c", + // Test cases from eslint-plugin-unicorn r"if (a) {}", r"if (a) {} else {}", r"if (!a) {}", @@ -122,6 +136,13 @@ fn test() { ]; let fail = vec![ + "if (!a) {;} else {;}", + "if (a != b) {;} else {;}", + "if (a !== b) {;} else {;}", + "!a ? b : c", + "a != b ? c : d", + "a !== b ? c : d", + // Test cases from eslint-plugin-unicorn r"if (!a) {;} else {;}", r"if (a != b) {;} else {;}", r"if (a !== b) {;} else {;}", diff --git a/crates/oxc_linter/src/rules/eslint/no_nested_ternary.rs b/crates/oxc_linter/src/rules/eslint/no_nested_ternary.rs new file mode 100644 index 00000000000000..2508793abe4cdd --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_nested_ternary.rs @@ -0,0 +1,100 @@ +use oxc_ast::ast::Expression; +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +fn no_nested_ternary_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Do not nest ternary expressions.").with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct NoNestedTernary; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallows nested ternary expressions to improve code readability and maintainability. + /// + /// ### Why is this bad? + /// + /// Nested ternary expressions make code harder to read and understand. They can lead to complex, difficult-to-debug logic. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// const result = condition1 ? (condition2 ? "a" : "b") : "c"; + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// let result; + /// if (condition1) { + /// result = condition2 ? "a" : "b"; + /// } else { + /// result = "c"; + /// } + /// ``` + NoNestedTernary, + style, +); + +impl Rule for NoNestedTernary { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::ConditionalExpression(node) = node.kind() { + if matches!( + node.consequent.get_inner_expression(), + Expression::ConditionalExpression(_) + ) || matches!( + node.alternate.get_inner_expression(), + Expression::ConditionalExpression(_) + ) { + ctx.diagnostic(no_nested_ternary_diagnostic(node.span)); + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "foo ? doBar() : doBaz();", + "var foo = bar === baz ? qux : quxx;", + "var result = foo && bar ? baz : qux || quux;", + "var result = foo ? bar : baz === qux;", + "foo ? doSomething(a, b) : doSomethingElse(c, d);", + // Parenthesized Expressions + "var result = (foo ? bar : baz) || qux;", + "var result = (foo ? bar : baz) && qux;", + "var result = foo === bar ? (baz || qux) : quux;", + "var result = (foo ? bar : baz) ? qux : quux;", + // TypeScript + "var result = foo! ? bar : baz;", + "var result = foo ? bar! : baz;", + "var result = (foo as boolean) ? bar : baz;", + "var result = foo ? (bar as string) : baz;", + ]; + + let fail = vec![ + "foo ? bar : baz === qux ? quxx : foobar;", + "foo ? baz === qux ? quxx : foobar : bar;", + // Parenthesized Expressions + "var result = foo ? (bar ? baz : qux) : quux;", + "var result = foo ? (bar === baz ? qux : quux) : foobar;", + "doSomething(foo ? bar : baz ? qux : quux);", + // Comment + "var result = foo /* comment */ ? bar : baz ? qux : quux;", + // TypeScript + "var result = foo! ? bar : baz! ? qux : quux;", + "var result = foo ? bar! : (baz! ? qux : quux);", + "var result = (foo as boolean) ? bar : (baz as string) ? qux : quux;", + "var result = foo ? (bar as string) : (baz as number ? qux : quux);", + ]; + + Tester::new(NoNestedTernary::NAME, NoNestedTernary::CATEGORY, pass, fail).test_and_snapshot(); +} 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 6ae3bb022f9321..9a5f605ca6ec7b 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 @@ -214,12 +214,12 @@ impl Rule for NoUnusedVars { } fn should_run(&self, ctx: &ContextHost) -> bool { - // ignore .d.ts and vue files. + // ignore .d.ts and vue/svelte files. // 1. declarations have side effects (they get merged together) - // 2. vue scripts declare variables that get used in the template, which + // 2. vue/svelte scripts declare variables that get used in the template, which // we can't detect !ctx.source_type().is_typescript_definition() - && !ctx.file_path().extension().is_some_and(|ext| ext == "vue") + && !ctx.file_path().extension().is_some_and(|ext| ext == "vue" || ext == "svelte") } } diff --git a/crates/oxc_linter/src/rules/eslint/prefer_rest_params.rs b/crates/oxc_linter/src/rules/eslint/prefer_rest_params.rs new file mode 100644 index 00000000000000..f7807fc8fba51f --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/prefer_rest_params.rs @@ -0,0 +1,126 @@ +use crate::{context::LintContext, rule::Rule, AstNode}; +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{GetSpan, Span}; + +fn prefer_rest_params_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Use the rest parameters instead of 'arguments'.").with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct PreferRestParams; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallows the use of the `arguments` object and instead enforces the use of rest parameters. + /// + /// ### Why is this bad? + /// + /// The `arguments` object does not have methods from `Array.prototype`, making it inconvenient for array-like operations. + /// Using rest parameters provides a more intuitive and efficient way to handle variadic arguments. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```javascript + /// function foo() { + /// console.log(arguments); + /// } + /// + /// function foo(action) { + /// var args = Array.prototype.slice.call(arguments, 1); + /// action.apply(null, args); + /// } + /// + /// function foo(action) { + /// var args = [].slice.call(arguments, 1); + /// action.apply(null, args); + /// } + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```javascript + /// function foo(...args) { + /// console.log(args); + /// } + /// + /// function foo(action, ...args) { + /// action.apply(null, args); // Or use `action(...args)` (related to `prefer-spread` rule). + /// } + /// + /// // Note: Implicit `arguments` can be shadowed. + /// function foo(arguments) { + /// console.log(arguments); // This refers to the first argument. + /// } + /// function foo() { + /// var arguments = 0; + /// console.log(arguments); // This is a local variable. + /// } + /// ``` + PreferRestParams, + style, +); + +impl Rule for PreferRestParams { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::IdentifierReference(identifier) = node.kind() { + if identifier.name != "arguments" + || !is_inside_of_function(node, ctx) + || is_not_normal_member_access(node, ctx) + { + return; + } + let binding = ctx.scopes().find_binding(node.scope_id(), "arguments"); + if binding.is_none() { + ctx.diagnostic(prefer_rest_params_diagnostic(node.span())); + } + } + } +} + +fn is_inside_of_function(node: &AstNode, ctx: &LintContext) -> bool { + let mut current = node; + while let Some(parent) = ctx.nodes().parent_node(current.id()) { + if matches!(parent.kind(), AstKind::Function(_)) { + return true; + } + current = parent; + } + false +} + +fn is_not_normal_member_access(identifier: &AstNode, ctx: &LintContext) -> bool { + let parent = ctx.nodes().parent_node(identifier.id()); + if let Some(parent) = parent { + if let AstKind::MemberExpression(member) = parent.kind() { + return member.object().span() == identifier.span() && !member.is_computed(); + } + } + false +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "arguments;", + "function foo(arguments) { arguments; }", + "function foo() { var arguments; arguments; }", + "var foo = () => arguments;", + "function foo(...args) { args; }", + "function foo() { arguments.length; }", + "function foo() { arguments.callee; }", + ]; + + let fail = vec![ + "function foo() { arguments; }", + "function foo() { arguments[0]; }", + "function foo() { arguments[1]; }", + "function foo() { arguments[Symbol.iterator]; }", + ]; + + Tester::new(PreferRestParams::NAME, PreferRestParams::CATEGORY, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/rules/eslint/vars_on_top.rs b/crates/oxc_linter/src/rules/eslint/vars_on_top.rs new file mode 100644 index 00000000000000..b2d4226262f69a --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/vars_on_top.rs @@ -0,0 +1,544 @@ +use oxc_ast::ast::{Declaration, Expression, Program, Statement, VariableDeclarationKind}; +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{GetSpan, Span}; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +fn vars_on_top_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("All 'var' declarations must be at the top of the function scope.") + .with_help("Consider moving this to the top of the functions scope or using let or const to declare this variable.") + .with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct VarsOnTop; + +declare_oxc_lint!( + /// ### What it does + /// + /// Enforces that all `var` declarations are placed at the top of their containing scope. + /// + /// ### Why is this bad? + /// + /// In JavaScript, `var` declarations are hoisted to the top of their containing scope. Placing `var` declarations at the top explicitly improves code readability and maintainability by making the scope of variables clear. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// function doSomething() { + /// if (true) { + /// var first = true; + /// } + /// var second; + /// } + /// + /// function doSomethingElse() { + /// for (var i = 0; i < 10; i++) {} + /// } + /// + /// f(); + /// var a; + /// + /// class C { + /// static { + /// if (something) { + /// var a = true; + /// } + /// } + /// static { + /// f(); + /// var a; + /// } + /// } + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// function doSomething() { + /// var first; + /// var second; + /// if (true) { + /// first = true; + /// } + /// } + /// + /// function doSomethingElse() { + /// var i; + /// for (i = 0; i < 10; i++) {} + /// } + /// + /// var a; + /// f(); + /// + /// class C { + /// static { + /// var a; + /// if (something) { + /// a = true; + /// } + /// } + /// static { + /// var a; + /// f(); + /// } + /// } + /// ``` + VarsOnTop, + style, +); + +impl Rule for VarsOnTop { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::VariableDeclaration(declaration) = node.kind() else { + return; + }; + if declaration.kind != VariableDeclarationKind::Var { + return; + } + let Some(parent) = ctx.nodes().parent_node(node.id()) else { + return; + }; + + match parent.kind() { + AstKind::ExportNamedDeclaration(_) => { + if let Some(grand_parent) = ctx.nodes().parent_node(parent.id()) { + if let AstKind::Program(grand_parent) = grand_parent.kind() { + global_var_check(parent, grand_parent, ctx); + } + } + } + AstKind::Program(parent) => { + global_var_check(node, parent, ctx); + } + _ => block_scope_var_check(node, ctx), + } + } +} + +fn looks_like_directive(node: &Statement) -> bool { + matches!( + node, + Statement::ExpressionStatement(expr_stmt) if matches!( + &expr_stmt.expression, + Expression::StringLiteral(_) + ) + ) +} + +fn looks_like_import(node: &Statement) -> bool { + matches!(node, Statement::ImportDeclaration(_)) +} + +fn is_variable_declaration(node: &Statement) -> bool { + if matches!(node, Statement::VariableDeclaration(_)) { + return true; + } + + if let Statement::ExportNamedDeclaration(export) = node { + return matches!(export.declaration, Some(Declaration::VariableDeclaration(_))); + } + + false +} + +fn is_var_on_top(node: &AstNode, statements: &[Statement], ctx: &LintContext) -> bool { + let mut i = 0; + let len = statements.len(); + let parent = ctx.nodes().parent_node(node.id()); + + if let Some(parent) = parent { + if !matches!(parent.kind(), AstKind::StaticBlock(_)) { + while i < len { + if !looks_like_directive(&statements[i]) && !looks_like_import(&statements[i]) { + break; + } + i += 1; + } + } + } + + let node_span = node.span(); + while i < len { + if !is_variable_declaration(&statements[i]) { + return false; + } + let stmt_span = statements[i].span(); + + if stmt_span == node_span { + return true; + } + i += 1; + } + + false +} + +fn global_var_check(node: &AstNode, parent: &Program, ctx: &LintContext) { + if !is_var_on_top(node, &parent.body, ctx) { + ctx.diagnostic(vars_on_top_diagnostic(node.span())); + } +} + +fn block_scope_var_check(node: &AstNode, ctx: &LintContext) { + if let Some(parent) = ctx.nodes().parent_node(node.id()) { + match parent.kind() { + AstKind::BlockStatement(block) => { + if check_var_on_top_in_function_scope(node, &block.body, parent, ctx) { + return; + } + } + AstKind::FunctionBody(block) => { + if check_var_on_top_in_function_scope(node, &block.statements, parent, ctx) { + return; + } + } + AstKind::StaticBlock(block) => { + if is_var_on_top(node, &block.body, ctx) { + return; + } + } + _ => {} + } + } + ctx.diagnostic(vars_on_top_diagnostic(node.span())); +} + +fn check_var_on_top_in_function_scope( + node: &AstNode, + statements: &[Statement], + parent: &AstNode, + ctx: &LintContext, +) -> bool { + if let Some(grandparent) = ctx.nodes().parent_node(parent.id()) { + if matches!( + grandparent.kind(), + AstKind::Function(_) | AstKind::FunctionBody(_) | AstKind::ArrowFunctionExpression(_) + ) && is_var_on_top(node, statements, ctx) + { + return true; + } + } + + false +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "var first = 0; + function foo() { + first = 2; + } + ", +"function foo() { + } + ", +"function foo() { + var first; + if (true) { + first = true; + } else { + first = 1; + } + } + ", +"function foo() { + var first; + var second = 1; + var third; + var fourth = 1, fifth, sixth = third; + var seventh; + if (true) { + third = true; + } + first = second; + } + ", +"function foo() { + var i; + for (i = 0; i < 10; i++) { + alert(i); + } + } + ", +"function foo() { + var outer; + function inner() { + var inner = 1; + var outer = inner; + } + outer = 1; + } + ", +"function foo() { + var first; + //Hello + var second = 1; + first = second; + } + ", +"function foo() { + var first; + /* + Hello Clarice + */ + var second = 1; + first = second; + } + ", +"function foo() { + var first; + var second = 1; + function bar(){ + var first; + first = 5; + } + first = second; + } + ", +"function foo() { + var first; + var second = 1; + function bar(){ + var third; + third = 5; + } + first = second; + } + ", +"function foo() { + var first; + var bar = function(){ + var third; + third = 5; + } + first = 5; + } + ", +"function foo() { + var first; + first.onclick(function(){ + var third; + third = 5; + }); + first = 5; + } + ", +"function foo() { + var i = 0; + for (let j = 0; j < 10; j++) { + alert(j); + } + i = i + 1; + }", // { "ecmaVersion": 6 }, +"'use strict'; var x; f();", +"'use strict'; 'directive'; var x; var y; f();", +"function f() { 'use strict'; var x; f(); }", +"function f() { 'use strict'; 'directive'; var x; var y; f(); }", +"import React from 'react'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"'use strict'; import React from 'react'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"import React from 'react'; 'use strict'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"import * as foo from 'mod.js'; 'use strict'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"import { square, diag } from 'lib'; 'use strict'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"import { default as foo } from 'lib'; 'use strict'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"import 'src/mylib'; 'use strict'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"import theDefault, { named1, named2 } from 'src/mylib'; 'use strict'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"export var x; + var y; + var z;", // { "ecmaVersion": 6, "sourceType": "module" }, +"var x; + export var y; + var z;", // { "ecmaVersion": 6, "sourceType": "module" }, +"var x; + var y; + export var z;", // { "ecmaVersion": 6, "sourceType": "module" }, +"class C { + static { + var x; + } + }", // { "ecmaVersion": 2022 }, +"class C { + static { + var x; + foo(); + } + }", // { "ecmaVersion": 2022 }, +"class C { + static { + var x; + var y; + } + }", // { "ecmaVersion": 2022 }, +"class C { + static { + var x; + var y; + foo(); + } + }", // { "ecmaVersion": 2022 }, +"class C { + static { + let x; + var y; + } + }", // { "ecmaVersion": 2022 }, +"class C { + static { + foo(); + let x; + } + }", // { "ecmaVersion": 2022 } + ]; + + let fail = vec![ + "var first = 0; + function foo() { + first = 2; + second = 2; + } + var second = 0;", + "function foo() { + var first; + first = 1; + first = 2; + first = 3; + first = 4; + var second = 1; + second = 2; + first = second; + }", + "function foo() { + var first; + if (true) { + var second = true; + } + first = second; + }", + "function foo() { + for (var i = 0; i < 10; i++) { + alert(i); + } + }", + "function foo() { + var first = 10; + var i; + for (i = 0; i < first; i ++) { + var second = i; + } + }", + "function foo() { + var first = 10; + var i; + switch (first) { + case 10: + var hello = 1; + break; + } + }", + "function foo() { + var first = 10; + var i; + try { + var hello = 1; + } catch (e) { + alert('error'); + } + }", + "function foo() { + var first = 10; + var i; + try { + asdf; + } catch (e) { + var hello = 1; + } + }", + "function foo() { + var first = 10; + while (first) { + var hello = 1; + } + }", + "function foo() { + var first = 10; + do { + var hello = 1; + } while (first == 10); + }", + "function foo() { + var first = [1,2,3]; + for (var item in first) { + item++; + } + }", + "function foo() { + var first = [1,2,3]; + var item; + for (item in first) { + var hello = item; + } + }", + "var foo = () => { + var first = [1,2,3]; + var item; + for (item in first) { + var hello = item; + } + }", // { "ecmaVersion": 6 }, + "'use strict'; 0; var x; f();", + "'use strict'; var x; 'directive'; var y; f();", + "function f() { 'use strict'; 0; var x; f(); }", + "function f() { 'use strict'; var x; 'directive'; var y; f(); }", + "export function f() {} + var x;", // { "ecmaVersion": 6, "sourceType": "module" }, + "var x; + export function f() {} + var y;", // { "ecmaVersion": 6, "sourceType": "module" }, + "import {foo} from 'foo'; + export {foo}; + var test = 1;", // { "ecmaVersion": 6, "sourceType": "module" }, + "export {foo} from 'foo'; + var test = 1;", // { "ecmaVersion": 6, "sourceType": "module" }, + "export * from 'foo'; + var test = 1;", // { "ecmaVersion": 6, "sourceType": "module" }, + "class C { + static { + foo(); + var x; + } + }", // { "ecmaVersion": 2022 }, + "class C { + static { + 'use strict'; + var x; + } + }", // { "ecmaVersion": 2022 }, + "class C { + static { + var x; + foo(); + var y; + } + }", // { "ecmaVersion": 2022 }, + "class C { + static { + if (foo) { + var x; + } + } + }", // { "ecmaVersion": 2022 }, + "class C { + static { + if (foo) + var x; + } + }", // { "ecmaVersion": 2022 } + ]; + + Tester::new(VarsOnTop::NAME, VarsOnTop::CATEGORY, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/rules/import/no_namespace.rs b/crates/oxc_linter/src/rules/import/no_namespace.rs index 6e98a40f5b0451..ddf15b2d15271d 100644 --- a/crates/oxc_linter/src/rules/import/no_namespace.rs +++ b/crates/oxc_linter/src/rules/import/no_namespace.rs @@ -115,11 +115,9 @@ impl Rule for NoNamespace { return; } - if self - .ignore - .iter() - .any(|pattern| glob_match(pattern, source.trim_start_matches("./"))) - { + if self.ignore.iter().any(|pattern| { + glob_match(pattern.as_str(), source.trim_start_matches("./")) + }) { return; } diff --git a/crates/oxc_linter/src/rules/jsdoc/require_returns.rs b/crates/oxc_linter/src/rules/jsdoc/require_returns.rs index fd8079cf536138..366071c323cf41 100644 --- a/crates/oxc_linter/src/rules/jsdoc/require_returns.rs +++ b/crates/oxc_linter/src/rules/jsdoc/require_returns.rs @@ -26,7 +26,7 @@ fn missing_returns_diagnostic(span: Span) -> OxcDiagnostic { } fn duplicate_returns_diagnostic(span: Span) -> OxcDiagnostic { OxcDiagnostic::warn("Duplicate `@returns` tags.") - .with_help("Remove redundunt `@returns` tag.") + .with_help("Remove redundant `@returns` tag.") .with_label(span) } diff --git a/crates/oxc_linter/src/rules/jsdoc/require_yields.rs b/crates/oxc_linter/src/rules/jsdoc/require_yields.rs index 8382bc3409030f..95707e09bc0aa0 100644 --- a/crates/oxc_linter/src/rules/jsdoc/require_yields.rs +++ b/crates/oxc_linter/src/rules/jsdoc/require_yields.rs @@ -24,7 +24,7 @@ fn missing_yields(span: Span) -> OxcDiagnostic { fn duplicate_yields(span: Span) -> OxcDiagnostic { OxcDiagnostic::warn("Duplicate `@yields` tags.") - .with_help("Remove redundunt `@yields` tag.") + .with_help("Remove redundant `@yields` tag.") .with_label(span) } diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs new file mode 100644 index 00000000000000..2251324f9ee4df --- /dev/null +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs @@ -0,0 +1,217 @@ +use oxc_ast::{ + ast::{JSXAttributeItem, JSXAttributeValue}, + AstKind, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{CompactStr, Span}; +use phf::phf_set; + +use crate::{ + context::LintContext, + rule::Rule, + utils::{get_element_type, has_jsx_prop_ignore_case}, + AstNode, +}; + +fn no_noninteractive_tabindex_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("tabIndex should only be declared on interactive elements") + .with_help("tabIndex attribute should be removed") + .with_label(span) +} + +#[derive(Debug, Clone)] +pub struct NoNoninteractiveTabindex(Box); + +#[derive(Debug, Clone)] +struct NoNoninteractiveTabindexConfig { + tags: Vec, + roles: Vec, + allow_expression_values: bool, +} + +impl Default for NoNoninteractiveTabindex { + fn default() -> Self { + Self(Box::new(NoNoninteractiveTabindexConfig { + roles: vec![CompactStr::new("tabpanel")], + allow_expression_values: true, + tags: vec![], + })) + } +} + +declare_oxc_lint!( + /// ### What it does + /// This rule checks that non-interactive elements don't have a tabIndex which would make them interactive via keyboard navigation. + /// + /// ### Why is this bad? + /// + /// Tab key navigation should be limited to elements on the page that can be interacted with. + /// Thus it is not necessary to add a tabindex to items in an unordered list, for example, + /// to make them navigable through assistive technology. + /// + /// These applications already afford page traversal mechanisms based on the HTML of the page. + /// Generally, we should try to reduce the size of the page's tab ring rather than increasing it. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```jsx + ///
+ ///
+ ///
+ ///
+ /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```jsx + ///
+ /// + ///