diff --git a/Cargo.lock b/Cargo.lock index ea97be506..d9b4020be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -897,16 +897,6 @@ dependencies = [ "cc", ] -[[package]] -name = "line-index" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e27e0ed5a392a7f5ba0b3808a2afccff16c64933312c84b57618b49d1209bd2" -dependencies = [ - "nohash-hasher", - "text-size", -] - [[package]] name = "lineage" version = "0.39.0" @@ -1541,6 +1531,7 @@ dependencies = [ "expect-test", "fancy-regex", "fern", + "ignore", "log", "minijinja", "pyo3", @@ -1599,8 +1590,8 @@ dependencies = [ "sqruff-lib-dialects", "strum", "strum_macros", + "thiserror", "toml", - "walkdir", ] [[package]] @@ -1667,7 +1658,6 @@ dependencies = [ name = "sqruff-wasm" version = "0.39.0" dependencies = [ - "line-index", "lineage", "serde", "serde_yaml", @@ -1746,12 +1736,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" -[[package]] -name = "text-size" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" - [[package]] name = "thiserror" version = "2.0.18" diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 63be34ca9..597a1726a 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -2706,15 +2706,15 @@ "REPO_MAPPING:rules_rust+,bazel_tools bazel_tools", "REPO_MAPPING:rules_rust+,rules_cc rules_cc+", "REPO_MAPPING:rules_rust+,rules_rust rules_rust+", - "FILE:@@//Cargo.lock ce742143d9a5758794b01425843394da1a57d7f7f61cbcfa0911af3d411b666b", + "FILE:@@//Cargo.lock 00386eccf8b6b91aed78e8512c18a8470e43d7cdf0d9da6eca90cc32efe040b7", "FILE:@@//Cargo.toml 8fbb9d9ad8bd861d59b023729fe884865690704382155a6e2bf0cb01c97c6c16", - "FILE:@@//crates/cli/Cargo.toml 69789bdb7a1ada8e986afa284402986c9a6e64055744c4af2f209941e1525a27", - "FILE:@@//crates/cli-lib/Cargo.toml da8973e7cfcfbbddcf7e1f506ae090a6ec3bdc7a8a9631950549a094ed8e5746", + "FILE:@@//crates/cli/Cargo.toml dc355023927e45c3be43a0778ab7ca343f761603931bf6fe43a8b1dba65ea022", + "FILE:@@//crates/cli-lib/Cargo.toml 48ac2d77c8ce62c2d18732ec0bd4d3ef17041cdad18bfb27b0006b60e4cab1d6", "FILE:@@//crates/cli-python/Cargo.toml 1be309c34494f9590292f3eb091b24f009ca2619da98ce711968ff43b4947b71", - "FILE:@@//crates/lib/Cargo.toml 6e6ee84636278cdbcc21490f7f89e68eb95eacd587448c209463de43df3de937", + "FILE:@@//crates/lib/Cargo.toml f53e9aba667ad0f07df23e59943c9ccefea84c8e87a6862619e61d41ebad7689", "FILE:@@//crates/lib-core/Cargo.toml dac961d744f0406b0cb2b12a40e979fde8e768ff584cfbac63edff3bce8af0a4", "FILE:@@//crates/lib-dialects/Cargo.toml 65f14eef0fd90412fdb48dbcd7ad7060aec5891bad7cd200d8e241ff19d544a9", - "FILE:@@//crates/lib-wasm/Cargo.toml dcafc87d240f6567664317913da006a8e786a195a1c0d7e93263b6ef7fa10874", + "FILE:@@//crates/lib-wasm/Cargo.toml 357d955d4c82929d74341c74a62ffe0a3278e9ec034dc7e955a78c81b770d7ce", "FILE:@@//crates/lineage/Cargo.toml d26ee434e736f8dd310937ca3dad5a5090d207ecf3e553dd627b4bdc576d82bb", "FILE:@@//crates/lsp/Cargo.toml 778e793284fd699e5170e18ecd56ab246a82ae6dfd3229a36cdd10a83fced904", "FILE:@@//crates/sqlinference/Cargo.toml 019ec868ee5b87094d99a7cc47cd51950d75095d7025e05133ed1b69dbea0809" @@ -2724,9 +2724,9 @@ "repoRuleId": "@@rules_rust+//crate_universe:extensions.bzl%_generate_repo", "attributes": { "contents": { - "BUILD.bazel": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'sqruff'\n###############################################################################\n\npackage(default_visibility = [\"//visibility:public\"])\n\nexports_files(\n [\n \"cargo-bazel.json\",\n \"crates.bzl\",\n \"defs.bzl\",\n ] + glob(\n allow_empty = True,\n include = [\"*.bazel\"],\n ),\n)\n\nfilegroup(\n name = \"srcs\",\n srcs = glob(\n allow_empty = True,\n include = [\n \"*.bazel\",\n \"*.bzl\",\n ],\n ),\n)\n\n# Workspace Member Dependencies\nalias(\n name = \"anstyle-1.0.14\",\n actual = \"@crates__anstyle-1.0.14//:anstyle\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"anstyle\",\n actual = \"@crates__anstyle-1.0.14//:anstyle\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"append-only-vec-0.1.8\",\n actual = \"@crates__append-only-vec-0.1.8//:append_only_vec\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"append-only-vec\",\n actual = \"@crates__append-only-vec-0.1.8//:append_only_vec\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"assert_cmd-2.2.2\",\n actual = \"@crates__assert_cmd-2.2.2//:assert_cmd\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"assert_cmd\",\n actual = \"@crates__assert_cmd-2.2.2//:assert_cmd\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap-4.6.1\",\n actual = \"@crates__clap-4.6.1//:clap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap\",\n actual = \"@crates__clap-4.6.1//:clap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap-markdown-0.1.5\",\n actual = \"@crates__clap-markdown-0.1.5//:clap_markdown\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap-markdown\",\n actual = \"@crates__clap-markdown-0.1.5//:clap_markdown\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"codspeed-criterion-compat-4.4.1\",\n actual = \"@crates__codspeed-criterion-compat-4.4.1//:codspeed_criterion_compat\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"criterion-4.4.1\",\n actual = \"@crates__codspeed-criterion-compat-4.4.1//:codspeed_criterion_compat\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"criterion\",\n actual = \"@crates__codspeed-criterion-compat-4.4.1//:codspeed_criterion_compat\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"common-path-1.0.0\",\n actual = \"@crates__common-path-1.0.0//:common_path\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"common-path\",\n actual = \"@crates__common-path-1.0.0//:common_path\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"configparser-3.2.0\",\n actual = \"@crates__configparser-3.2.0//:configparser\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"configparser\",\n actual = \"@crates__configparser-3.2.0//:configparser\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"console_error_panic_hook-0.1.7\",\n actual = \"@crates__console_error_panic_hook-0.1.7//:console_error_panic_hook\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"console_error_panic_hook\",\n actual = \"@crates__console_error_panic_hook-0.1.7//:console_error_panic_hook\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"enum_dispatch-0.3.13\",\n actual = \"@crates__enum_dispatch-0.3.13//:enum_dispatch\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"enum_dispatch\",\n actual = \"@crates__enum_dispatch-0.3.13//:enum_dispatch\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"expect-test-1.5.1\",\n actual = \"@crates__expect-test-1.5.1//:expect_test\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"expect-test\",\n actual = \"@crates__expect-test-1.5.1//:expect_test\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fancy-regex-0.18.0\",\n actual = \"@crates__fancy-regex-0.18.0//:fancy_regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fancy-regex\",\n actual = \"@crates__fancy-regex-0.18.0//:fancy_regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fern-0.7.1\",\n actual = \"@crates__fern-0.7.1//:fern\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fern\",\n actual = \"@crates__fern-0.7.1//:fern\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"getrandom-0.2.17\",\n actual = \"@crates__getrandom-0.2.17//:getrandom\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"getrandom\",\n actual = \"@crates__getrandom-0.2.17//:getrandom\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"glob-0.3.3\",\n actual = \"@crates__glob-0.3.3//:glob\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"glob\",\n actual = \"@crates__glob-0.3.3//:glob\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hashbrown-0.17.1\",\n actual = \"@crates__hashbrown-0.17.1//:hashbrown\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hashbrown\",\n actual = \"@crates__hashbrown-0.17.1//:hashbrown\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ignore-0.4.27\",\n actual = \"@crates__ignore-0.4.27//:ignore\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ignore\",\n actual = \"@crates__ignore-0.4.27//:ignore\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"indexmap-2.14.0\",\n actual = \"@crates__indexmap-2.14.0//:indexmap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"indexmap\",\n actual = \"@crates__indexmap-2.14.0//:indexmap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"itertools-0.15.0\",\n actual = \"@crates__itertools-0.15.0//:itertools\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"itertools\",\n actual = \"@crates__itertools-0.15.0//:itertools\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"js-sys-0.3.82\",\n actual = \"@crates__js-sys-0.3.82//:js_sys\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"js-sys\",\n actual = \"@crates__js-sys-0.3.82//:js_sys\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lazy-regex-3.6.0\",\n actual = \"@crates__lazy-regex-3.6.0//:lazy_regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lazy-regex\",\n actual = \"@crates__lazy-regex-3.6.0//:lazy_regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"line-index-0.1.2\",\n actual = \"@crates__line-index-0.1.2//:line_index\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"line-index\",\n actual = \"@crates__line-index-0.1.2//:line_index\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"log-0.4.33\",\n actual = \"@crates__log-0.4.33//:log\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"log\",\n actual = \"@crates__log-0.4.33//:log\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lsp-server-0.8.0\",\n actual = \"@crates__lsp-server-0.8.0//:lsp_server\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lsp-server\",\n actual = \"@crates__lsp-server-0.8.0//:lsp_server\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lsp-types-0.97.0\",\n actual = \"@crates__lsp-types-0.97.0//:lsp_types\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lsp-types\",\n actual = \"@crates__lsp-types-0.97.0//:lsp_types\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"mimalloc-0.1.52\",\n actual = \"@crates__mimalloc-0.1.52//:mimalloc\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"mimalloc\",\n actual = \"@crates__mimalloc-0.1.52//:mimalloc\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"minijinja-2.21.0\",\n actual = \"@crates__minijinja-2.21.0//:minijinja\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"minijinja\",\n actual = \"@crates__minijinja-2.21.0//:minijinja\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"nohash-hasher-0.2.0\",\n actual = \"@crates__nohash-hasher-0.2.0//:nohash_hasher\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"nohash-hasher\",\n actual = \"@crates__nohash-hasher-0.2.0//:nohash_hasher\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"pretty_assertions-1.4.1\",\n actual = \"@crates__pretty_assertions-1.4.1//:pretty_assertions\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"pretty_assertions\",\n actual = \"@crates__pretty_assertions-1.4.1//:pretty_assertions\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"pyo3-0.29.0\",\n actual = \"@crates__pyo3-0.29.0//:pyo3\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"pyo3\",\n actual = \"@crates__pyo3-0.29.0//:pyo3\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rayon-1.12.0\",\n actual = \"@crates__rayon-1.12.0//:rayon\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rayon\",\n actual = \"@crates__rayon-1.12.0//:rayon\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"regex-1.12.4\",\n actual = \"@crates__regex-1.12.4//:regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"regex\",\n actual = \"@crates__regex-1.12.4//:regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"regex-automata-0.4.14\",\n actual = \"@crates__regex-automata-0.4.14//:regex_automata\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"regex-automata\",\n actual = \"@crates__regex-automata-0.4.14//:regex_automata\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde-1.0.228\",\n actual = \"@crates__serde-1.0.228//:serde\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde\",\n actual = \"@crates__serde-1.0.228//:serde\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde-wasm-bindgen-0.6.5\",\n actual = \"@crates__serde-wasm-bindgen-0.6.5//:serde_wasm_bindgen\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde-wasm-bindgen\",\n actual = \"@crates__serde-wasm-bindgen-0.6.5//:serde_wasm_bindgen\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_json-1.0.150\",\n actual = \"@crates__serde_json-1.0.150//:serde_json\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_json\",\n actual = \"@crates__serde_json-1.0.150//:serde_json\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_with-3.21.0\",\n actual = \"@crates__serde_with-3.21.0//:serde_with\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_with\",\n actual = \"@crates__serde_with-3.21.0//:serde_with\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_yaml-0.9.34+deprecated\",\n actual = \"@crates__serde_yaml-0.9.34-deprecated//:serde_yaml\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_yaml\",\n actual = \"@crates__serde_yaml-0.9.34-deprecated//:serde_yaml\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"smol_str-0.3.6\",\n actual = \"@crates__smol_str-0.3.6//:smol_str\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"smol_str\",\n actual = \"@crates__smol_str-0.3.6//:smol_str\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"strum-0.28.0\",\n actual = \"@crates__strum-0.28.0//:strum\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"strum\",\n actual = \"@crates__strum-0.28.0//:strum\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"strum_macros-0.28.0\",\n actual = \"@crates__strum_macros-0.28.0//:strum_macros\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"strum_macros\",\n actual = \"@crates__strum_macros-0.28.0//:strum_macros\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tempfile-3.27.0\",\n actual = \"@crates__tempfile-3.27.0//:tempfile\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tempfile\",\n actual = \"@crates__tempfile-3.27.0//:tempfile\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"thiserror-2.0.18\",\n actual = \"@crates__thiserror-2.0.18//:thiserror\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"thiserror\",\n actual = \"@crates__thiserror-2.0.18//:thiserror\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"toml-0.9.12+spec-1.1.0\",\n actual = \"@crates__toml-0.9.12-spec-1.1.0//:toml\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"toml\",\n actual = \"@crates__toml-0.9.12-spec-1.1.0//:toml\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"walkdir-2.5.0\",\n actual = \"@crates__walkdir-2.5.0//:walkdir\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"walkdir\",\n actual = \"@crates__walkdir-2.5.0//:walkdir\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"wasm-bindgen-0.2.105\",\n actual = \"@crates__wasm-bindgen-0.2.105//:wasm_bindgen\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"wasm-bindgen\",\n actual = \"@crates__wasm-bindgen-0.2.105//:wasm_bindgen\",\n tags = [\"manual\"],\n)\n", + "BUILD.bazel": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'sqruff'\n###############################################################################\n\npackage(default_visibility = [\"//visibility:public\"])\n\nexports_files(\n [\n \"cargo-bazel.json\",\n \"crates.bzl\",\n \"defs.bzl\",\n ] + glob(\n allow_empty = True,\n include = [\"*.bazel\"],\n ),\n)\n\nfilegroup(\n name = \"srcs\",\n srcs = glob(\n allow_empty = True,\n include = [\n \"*.bazel\",\n \"*.bzl\",\n ],\n ),\n)\n\n# Workspace Member Dependencies\nalias(\n name = \"anstyle-1.0.14\",\n actual = \"@crates__anstyle-1.0.14//:anstyle\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"anstyle\",\n actual = \"@crates__anstyle-1.0.14//:anstyle\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"append-only-vec-0.1.8\",\n actual = \"@crates__append-only-vec-0.1.8//:append_only_vec\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"append-only-vec\",\n actual = \"@crates__append-only-vec-0.1.8//:append_only_vec\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"assert_cmd-2.2.2\",\n actual = \"@crates__assert_cmd-2.2.2//:assert_cmd\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"assert_cmd\",\n actual = \"@crates__assert_cmd-2.2.2//:assert_cmd\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap-4.6.1\",\n actual = \"@crates__clap-4.6.1//:clap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap\",\n actual = \"@crates__clap-4.6.1//:clap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap-markdown-0.1.5\",\n actual = \"@crates__clap-markdown-0.1.5//:clap_markdown\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap-markdown\",\n actual = \"@crates__clap-markdown-0.1.5//:clap_markdown\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"codspeed-criterion-compat-4.4.1\",\n actual = \"@crates__codspeed-criterion-compat-4.4.1//:codspeed_criterion_compat\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"criterion-4.4.1\",\n actual = \"@crates__codspeed-criterion-compat-4.4.1//:codspeed_criterion_compat\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"criterion\",\n actual = \"@crates__codspeed-criterion-compat-4.4.1//:codspeed_criterion_compat\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"common-path-1.0.0\",\n actual = \"@crates__common-path-1.0.0//:common_path\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"common-path\",\n actual = \"@crates__common-path-1.0.0//:common_path\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"configparser-3.2.0\",\n actual = \"@crates__configparser-3.2.0//:configparser\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"configparser\",\n actual = \"@crates__configparser-3.2.0//:configparser\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"console_error_panic_hook-0.1.7\",\n actual = \"@crates__console_error_panic_hook-0.1.7//:console_error_panic_hook\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"console_error_panic_hook\",\n actual = \"@crates__console_error_panic_hook-0.1.7//:console_error_panic_hook\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"enum_dispatch-0.3.13\",\n actual = \"@crates__enum_dispatch-0.3.13//:enum_dispatch\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"enum_dispatch\",\n actual = \"@crates__enum_dispatch-0.3.13//:enum_dispatch\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"expect-test-1.5.1\",\n actual = \"@crates__expect-test-1.5.1//:expect_test\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"expect-test\",\n actual = \"@crates__expect-test-1.5.1//:expect_test\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fancy-regex-0.18.0\",\n actual = \"@crates__fancy-regex-0.18.0//:fancy_regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fancy-regex\",\n actual = \"@crates__fancy-regex-0.18.0//:fancy_regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fern-0.7.1\",\n actual = \"@crates__fern-0.7.1//:fern\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fern\",\n actual = \"@crates__fern-0.7.1//:fern\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"getrandom-0.2.17\",\n actual = \"@crates__getrandom-0.2.17//:getrandom\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"getrandom\",\n actual = \"@crates__getrandom-0.2.17//:getrandom\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"glob-0.3.3\",\n actual = \"@crates__glob-0.3.3//:glob\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"glob\",\n actual = \"@crates__glob-0.3.3//:glob\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hashbrown-0.17.1\",\n actual = \"@crates__hashbrown-0.17.1//:hashbrown\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hashbrown\",\n actual = \"@crates__hashbrown-0.17.1//:hashbrown\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ignore-0.4.27\",\n actual = \"@crates__ignore-0.4.27//:ignore\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ignore\",\n actual = \"@crates__ignore-0.4.27//:ignore\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"indexmap-2.14.0\",\n actual = \"@crates__indexmap-2.14.0//:indexmap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"indexmap\",\n actual = \"@crates__indexmap-2.14.0//:indexmap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"itertools-0.15.0\",\n actual = \"@crates__itertools-0.15.0//:itertools\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"itertools\",\n actual = \"@crates__itertools-0.15.0//:itertools\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"js-sys-0.3.82\",\n actual = \"@crates__js-sys-0.3.82//:js_sys\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"js-sys\",\n actual = \"@crates__js-sys-0.3.82//:js_sys\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lazy-regex-3.6.0\",\n actual = \"@crates__lazy-regex-3.6.0//:lazy_regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lazy-regex\",\n actual = \"@crates__lazy-regex-3.6.0//:lazy_regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"log-0.4.33\",\n actual = \"@crates__log-0.4.33//:log\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"log\",\n actual = \"@crates__log-0.4.33//:log\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lsp-server-0.8.0\",\n actual = \"@crates__lsp-server-0.8.0//:lsp_server\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lsp-server\",\n actual = \"@crates__lsp-server-0.8.0//:lsp_server\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lsp-types-0.97.0\",\n actual = \"@crates__lsp-types-0.97.0//:lsp_types\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lsp-types\",\n actual = \"@crates__lsp-types-0.97.0//:lsp_types\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"mimalloc-0.1.52\",\n actual = \"@crates__mimalloc-0.1.52//:mimalloc\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"mimalloc\",\n actual = \"@crates__mimalloc-0.1.52//:mimalloc\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"minijinja-2.21.0\",\n actual = \"@crates__minijinja-2.21.0//:minijinja\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"minijinja\",\n actual = \"@crates__minijinja-2.21.0//:minijinja\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"nohash-hasher-0.2.0\",\n actual = \"@crates__nohash-hasher-0.2.0//:nohash_hasher\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"nohash-hasher\",\n actual = \"@crates__nohash-hasher-0.2.0//:nohash_hasher\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"pretty_assertions-1.4.1\",\n actual = \"@crates__pretty_assertions-1.4.1//:pretty_assertions\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"pretty_assertions\",\n actual = \"@crates__pretty_assertions-1.4.1//:pretty_assertions\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"pyo3-0.29.0\",\n actual = \"@crates__pyo3-0.29.0//:pyo3\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"pyo3\",\n actual = \"@crates__pyo3-0.29.0//:pyo3\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rayon-1.12.0\",\n actual = \"@crates__rayon-1.12.0//:rayon\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rayon\",\n actual = \"@crates__rayon-1.12.0//:rayon\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"regex-1.12.4\",\n actual = \"@crates__regex-1.12.4//:regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"regex\",\n actual = \"@crates__regex-1.12.4//:regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"regex-automata-0.4.14\",\n actual = \"@crates__regex-automata-0.4.14//:regex_automata\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"regex-automata\",\n actual = \"@crates__regex-automata-0.4.14//:regex_automata\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde-1.0.228\",\n actual = \"@crates__serde-1.0.228//:serde\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde\",\n actual = \"@crates__serde-1.0.228//:serde\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde-wasm-bindgen-0.6.5\",\n actual = \"@crates__serde-wasm-bindgen-0.6.5//:serde_wasm_bindgen\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde-wasm-bindgen\",\n actual = \"@crates__serde-wasm-bindgen-0.6.5//:serde_wasm_bindgen\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_json-1.0.150\",\n actual = \"@crates__serde_json-1.0.150//:serde_json\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_json\",\n actual = \"@crates__serde_json-1.0.150//:serde_json\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_with-3.21.0\",\n actual = \"@crates__serde_with-3.21.0//:serde_with\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_with\",\n actual = \"@crates__serde_with-3.21.0//:serde_with\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_yaml-0.9.34+deprecated\",\n actual = \"@crates__serde_yaml-0.9.34-deprecated//:serde_yaml\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_yaml\",\n actual = \"@crates__serde_yaml-0.9.34-deprecated//:serde_yaml\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"smol_str-0.3.6\",\n actual = \"@crates__smol_str-0.3.6//:smol_str\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"smol_str\",\n actual = \"@crates__smol_str-0.3.6//:smol_str\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"strum-0.28.0\",\n actual = \"@crates__strum-0.28.0//:strum\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"strum\",\n actual = \"@crates__strum-0.28.0//:strum\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"strum_macros-0.28.0\",\n actual = \"@crates__strum_macros-0.28.0//:strum_macros\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"strum_macros\",\n actual = \"@crates__strum_macros-0.28.0//:strum_macros\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tempfile-3.27.0\",\n actual = \"@crates__tempfile-3.27.0//:tempfile\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tempfile\",\n actual = \"@crates__tempfile-3.27.0//:tempfile\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"thiserror-2.0.18\",\n actual = \"@crates__thiserror-2.0.18//:thiserror\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"thiserror\",\n actual = \"@crates__thiserror-2.0.18//:thiserror\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"toml-0.9.12+spec-1.1.0\",\n actual = \"@crates__toml-0.9.12-spec-1.1.0//:toml\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"toml\",\n actual = \"@crates__toml-0.9.12-spec-1.1.0//:toml\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"wasm-bindgen-0.2.105\",\n actual = \"@crates__wasm-bindgen-0.2.105//:wasm_bindgen\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"wasm-bindgen\",\n actual = \"@crates__wasm-bindgen-0.2.105//:wasm_bindgen\",\n tags = [\"manual\"],\n)\n", "alias_rules.bzl": "\"\"\"Alias that transitions its target to `compilation_mode=opt`. Use `transition_alias=\"opt\"` to enable.\"\"\"\n\nload(\"@rules_cc//cc:defs.bzl\", \"CcInfo\")\nload(\"@rules_rust//rust:rust_common.bzl\", \"COMMON_PROVIDERS\")\n\ndef _transition_alias_impl(ctx):\n # `ctx.attr.actual` is a list of 1 item due to the transition\n providers = [ctx.attr.actual[0][provider] for provider in COMMON_PROVIDERS]\n if CcInfo in ctx.attr.actual[0]:\n providers.append(ctx.attr.actual[0][CcInfo])\n return providers\n\ndef _change_compilation_mode(compilation_mode):\n def _change_compilation_mode_impl(_settings, _attr):\n return {\n \"//command_line_option:compilation_mode\": compilation_mode,\n }\n\n return transition(\n implementation = _change_compilation_mode_impl,\n inputs = [],\n outputs = [\n \"//command_line_option:compilation_mode\",\n ],\n )\n\ndef _transition_alias_rule(compilation_mode):\n return rule(\n implementation = _transition_alias_impl,\n provides = COMMON_PROVIDERS,\n attrs = {\n \"actual\": attr.label(\n mandatory = True,\n doc = \"`rust_library()` target to transition to `compilation_mode=opt`.\",\n providers = COMMON_PROVIDERS,\n cfg = _change_compilation_mode(compilation_mode),\n ),\n \"_allowlist_function_transition\": attr.label(\n default = \"@bazel_tools//tools/allowlists/function_transition_allowlist\",\n ),\n },\n doc = \"Transitions a Rust library crate to the `compilation_mode=opt`.\",\n )\n\ntransition_alias_dbg = _transition_alias_rule(\"dbg\")\ntransition_alias_fastbuild = _transition_alias_rule(\"fastbuild\")\ntransition_alias_opt = _transition_alias_rule(\"opt\")\n", - "defs.bzl": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'sqruff'\n###############################################################################\n\"\"\"\n# `crates_repository` API\n\n- [aliases](#aliases)\n- [crate_deps](#crate_deps)\n- [all_crate_deps](#all_crate_deps)\n- [crate_repositories](#crate_repositories)\n\n\"\"\"\n\nload(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"new_git_repository\")\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\nload(\"@bazel_tools//tools/build_defs/repo:utils.bzl\", \"maybe\")\nload(\"@bazel_skylib//lib:selects.bzl\", \"selects\")\nload(\"@rules_rust//crate_universe/private:local_crate_mirror.bzl\", \"local_crate_mirror\")\n\n###############################################################################\n# MACROS API\n###############################################################################\n\n# An identifier that represent common dependencies (unconditional).\n_COMMON_CONDITION = \"\"\n\ndef _flatten_dependency_maps(all_dependency_maps):\n \"\"\"Flatten a list of dependency maps into one dictionary.\n\n Dependency maps have the following structure:\n\n ```python\n DEPENDENCIES_MAP = {\n # The first key in the map is a Bazel package\n # name of the workspace this file is defined in.\n \"workspace_member_package\": {\n\n # Not all dependencies are supported for all platforms.\n # the condition key is the condition required to be true\n # on the host platform.\n \"condition\": {\n\n # An alias to a crate target. # The label of the crate target the\n # Aliases are only crate names. # package name refers to.\n \"package_name\": \"@full//:label\",\n }\n }\n }\n ```\n\n Args:\n all_dependency_maps (list): A list of dicts as described above\n\n Returns:\n dict: A dictionary as described above\n \"\"\"\n dependencies = {}\n\n for workspace_deps_map in all_dependency_maps:\n for pkg_name, conditional_deps_map in workspace_deps_map.items():\n if pkg_name not in dependencies:\n non_frozen_map = dict()\n for key, values in conditional_deps_map.items():\n non_frozen_map.update({key: dict(values.items())})\n dependencies.setdefault(pkg_name, non_frozen_map)\n continue\n\n for condition, deps_map in conditional_deps_map.items():\n # If the condition has not been recorded, do so and continue\n if condition not in dependencies[pkg_name]:\n dependencies[pkg_name].setdefault(condition, dict(deps_map.items()))\n continue\n\n # Alert on any miss-matched dependencies\n inconsistent_entries = []\n for crate_name, crate_label in deps_map.items():\n existing = dependencies[pkg_name][condition].get(crate_name)\n if existing and existing != crate_label:\n inconsistent_entries.append((crate_name, existing, crate_label))\n dependencies[pkg_name][condition].update({crate_name: crate_label})\n\n return dependencies\n\ndef crate_deps(deps, package_name = None):\n \"\"\"Finds the fully qualified label of the requested crates for the package where this macro is called.\n\n Args:\n deps (list): The desired list of crate targets.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()`.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if not deps:\n return []\n\n if package_name == None:\n package_name = native.package_name()\n\n # Join both sets of dependencies\n dependencies = _flatten_dependency_maps([\n _NORMAL_DEPENDENCIES,\n _NORMAL_DEV_DEPENDENCIES,\n _PROC_MACRO_DEPENDENCIES,\n _PROC_MACRO_DEV_DEPENDENCIES,\n _BUILD_DEPENDENCIES,\n _BUILD_PROC_MACRO_DEPENDENCIES,\n ]).pop(package_name, {})\n\n # Combine all conditional packages so we can easily index over a flat list\n # TODO: Perhaps this should actually return select statements and maintain\n # the conditionals of the dependencies\n flat_deps = {}\n for deps_set in dependencies.values():\n for crate_name, crate_label in deps_set.items():\n flat_deps.update({crate_name: crate_label})\n\n missing_crates = []\n crate_targets = []\n for crate_target in deps:\n if crate_target not in flat_deps:\n missing_crates.append(crate_target)\n else:\n crate_targets.append(flat_deps[crate_target])\n\n if missing_crates:\n fail(\"Could not find crates `{}` among dependencies of `{}`. Available dependencies were `{}`\".format(\n missing_crates,\n package_name,\n dependencies,\n ))\n\n return crate_targets\n\ndef all_crate_deps(\n normal = False, \n normal_dev = False, \n proc_macro = False, \n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Finds the fully qualified label of all requested direct crate dependencies \\\n for the package where this macro is called.\n\n If no parameters are set, all normal dependencies are returned. Setting any one flag will\n otherwise impact the contents of the returned list.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_dependency_maps = []\n if normal:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n if normal_dev:\n all_dependency_maps.append(_NORMAL_DEV_DEPENDENCIES)\n if proc_macro:\n all_dependency_maps.append(_PROC_MACRO_DEPENDENCIES)\n if proc_macro_dev:\n all_dependency_maps.append(_PROC_MACRO_DEV_DEPENDENCIES)\n if build:\n all_dependency_maps.append(_BUILD_DEPENDENCIES)\n if build_proc_macro:\n all_dependency_maps.append(_BUILD_PROC_MACRO_DEPENDENCIES)\n\n # Default to always using normal dependencies\n if not all_dependency_maps:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n\n dependencies = _flatten_dependency_maps(all_dependency_maps).pop(package_name, None)\n\n if not dependencies:\n if dependencies == None:\n fail(\"Tried to get all_crate_deps for package \" + package_name + \" but that package had no Cargo.toml file\")\n else:\n return []\n\n crate_deps = list(dependencies.pop(_COMMON_CONDITION, {}).values())\n for condition, deps in dependencies.items():\n crate_deps += selects.with_or({\n tuple(_CONDITIONS[condition]): deps.values(),\n \"//conditions:default\": [],\n })\n\n return crate_deps\n\ndef aliases(\n normal = False,\n normal_dev = False,\n proc_macro = False,\n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Produces a map of Crate alias names to their original label\n\n If no dependency kinds are specified, `normal` and `proc_macro` are used by default.\n Setting any one flag will otherwise determine the contents of the returned dict.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n dict: The aliases of all associated packages\n \"\"\"\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_aliases_maps = []\n if normal:\n all_aliases_maps.append(_NORMAL_ALIASES)\n if normal_dev:\n all_aliases_maps.append(_NORMAL_DEV_ALIASES)\n if proc_macro:\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n if proc_macro_dev:\n all_aliases_maps.append(_PROC_MACRO_DEV_ALIASES)\n if build:\n all_aliases_maps.append(_BUILD_ALIASES)\n if build_proc_macro:\n all_aliases_maps.append(_BUILD_PROC_MACRO_ALIASES)\n\n # Default to always using normal aliases\n if not all_aliases_maps:\n all_aliases_maps.append(_NORMAL_ALIASES)\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n\n aliases = _flatten_dependency_maps(all_aliases_maps).pop(package_name, None)\n\n if not aliases:\n return dict()\n\n common_items = aliases.pop(_COMMON_CONDITION, {}).items()\n\n # If there are only common items in the dictionary, immediately return them\n if not len(aliases.keys()) == 1:\n return dict(common_items)\n\n # Build a single select statement where each conditional has accounted for the\n # common set of aliases.\n crate_aliases = {\"//conditions:default\": dict(common_items)}\n for condition, deps in aliases.items():\n condition_triples = _CONDITIONS[condition]\n for triple in condition_triples:\n if triple in crate_aliases:\n crate_aliases[triple].update(deps)\n else:\n crate_aliases.update({triple: dict(deps.items() + common_items)})\n\n return select(crate_aliases)\n\n###############################################################################\n# WORKSPACE MEMBER DEPS AND ALIASES\n###############################################################################\n\n_NORMAL_DEPENDENCIES = {\n \"crates/lineage\": {\n _COMMON_CONDITION: {\n \"append-only-vec\": Label(\"@crates//:append-only-vec-0.1.8\"),\n \"hashbrown\": Label(\"@crates//:hashbrown-0.17.1\"),\n \"indexmap\": Label(\"@crates//:indexmap-2.14.0\"),\n },\n },\n \"crates/cli\": {\n \"x86_64-pc-windows-msvc\": {\n \"mimalloc\": Label(\"@crates//:mimalloc-0.1.52\"),\n },\n },\n \"crates/cli-lib\": {\n _COMMON_CONDITION: {\n \"anstyle\": Label(\"@crates//:anstyle-1.0.14\"),\n \"clap\": Label(\"@crates//:clap-4.6.1\"),\n \"fern\": Label(\"@crates//:fern-0.7.1\"),\n \"log\": Label(\"@crates//:log-0.4.33\"),\n \"pyo3\": Label(\"@crates//:pyo3-0.29.0\"),\n \"serde\": Label(\"@crates//:serde-1.0.228\"),\n \"serde_json\": Label(\"@crates//:serde_json-1.0.150\"),\n \"strum\": Label(\"@crates//:strum-0.28.0\"),\n },\n },\n \"crates/cli-python\": {\n _COMMON_CONDITION: {\n \"pyo3\": Label(\"@crates//:pyo3-0.29.0\"),\n },\n },\n \"crates/lib\": {\n _COMMON_CONDITION: {\n \"common-path\": Label(\"@crates//:common-path-1.0.0\"),\n \"configparser\": Label(\"@crates//:configparser-3.2.0\"),\n \"fancy-regex\": Label(\"@crates//:fancy-regex-0.18.0\"),\n \"hashbrown\": Label(\"@crates//:hashbrown-0.17.1\"),\n \"indexmap\": Label(\"@crates//:indexmap-2.14.0\"),\n \"itertools\": Label(\"@crates//:itertools-0.15.0\"),\n \"lazy-regex\": Label(\"@crates//:lazy-regex-3.6.0\"),\n \"log\": Label(\"@crates//:log-0.4.33\"),\n \"nohash-hasher\": Label(\"@crates//:nohash-hasher-0.2.0\"),\n \"pretty_assertions\": Label(\"@crates//:pretty_assertions-1.4.1\"),\n \"pyo3\": Label(\"@crates//:pyo3-0.29.0\"),\n \"rayon\": Label(\"@crates//:rayon-1.12.0\"),\n \"regex\": Label(\"@crates//:regex-1.12.4\"),\n \"serde\": Label(\"@crates//:serde-1.0.228\"),\n \"serde_json\": Label(\"@crates//:serde_json-1.0.150\"),\n \"smol_str\": Label(\"@crates//:smol_str-0.3.6\"),\n \"strum\": Label(\"@crates//:strum-0.28.0\"),\n \"toml\": Label(\"@crates//:toml-0.9.12+spec-1.1.0\"),\n \"walkdir\": Label(\"@crates//:walkdir-2.5.0\"),\n },\n \"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\": {\n \"getrandom\": Label(\"@crates//:getrandom-0.2.17\"),\n },\n \"cfg(not(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\")))\": {\n \"ignore\": Label(\"@crates//:ignore-0.4.27\"),\n },\n },\n \"crates/lib-core\": {\n _COMMON_CONDITION: {\n \"fancy-regex\": Label(\"@crates//:fancy-regex-0.18.0\"),\n \"hashbrown\": Label(\"@crates//:hashbrown-0.17.1\"),\n \"indexmap\": Label(\"@crates//:indexmap-2.14.0\"),\n \"itertools\": Label(\"@crates//:itertools-0.15.0\"),\n \"log\": Label(\"@crates//:log-0.4.33\"),\n \"nohash-hasher\": Label(\"@crates//:nohash-hasher-0.2.0\"),\n \"pretty_assertions\": Label(\"@crates//:pretty_assertions-1.4.1\"),\n \"regex-automata\": Label(\"@crates//:regex-automata-0.4.14\"),\n \"serde\": Label(\"@crates//:serde-1.0.228\"),\n \"serde_yaml\": Label(\"@crates//:serde_yaml-0.9.34+deprecated\"),\n \"smol_str\": Label(\"@crates//:smol_str-0.3.6\"),\n \"strum\": Label(\"@crates//:strum-0.28.0\"),\n \"thiserror\": Label(\"@crates//:thiserror-2.0.18\"),\n },\n },\n \"crates/lib-dialects\": {\n _COMMON_CONDITION: {\n \"hashbrown\": Label(\"@crates//:hashbrown-0.17.1\"),\n \"itertools\": Label(\"@crates//:itertools-0.15.0\"),\n \"serde_yaml\": Label(\"@crates//:serde_yaml-0.9.34+deprecated\"),\n \"strum\": Label(\"@crates//:strum-0.28.0\"),\n },\n },\n \"crates/lsp\": {\n _COMMON_CONDITION: {\n \"console_error_panic_hook\": Label(\"@crates//:console_error_panic_hook-0.1.7\"),\n \"hashbrown\": Label(\"@crates//:hashbrown-0.17.1\"),\n \"js-sys\": Label(\"@crates//:js-sys-0.3.82\"),\n \"lsp-server\": Label(\"@crates//:lsp-server-0.8.0\"),\n \"lsp-types\": Label(\"@crates//:lsp-types-0.97.0\"),\n \"serde-wasm-bindgen\": Label(\"@crates//:serde-wasm-bindgen-0.6.5\"),\n \"serde_json\": Label(\"@crates//:serde_json-1.0.150\"),\n \"wasm-bindgen\": Label(\"@crates//:wasm-bindgen-0.2.105\"),\n },\n },\n \"crates/sqlinference\": {\n _COMMON_CONDITION: {\n \"hashbrown\": Label(\"@crates//:hashbrown-0.17.1\"),\n },\n },\n \"crates/lib-wasm\": {\n _COMMON_CONDITION: {\n \"line-index\": Label(\"@crates//:line-index-0.1.2\"),\n \"serde\": Label(\"@crates//:serde-1.0.228\"),\n \"serde_yaml\": Label(\"@crates//:serde_yaml-0.9.34+deprecated\"),\n \"wasm-bindgen\": Label(\"@crates//:wasm-bindgen-0.2.105\"),\n },\n },\n}\n\n\n_NORMAL_ALIASES = {\n \"crates/lineage\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli\": {\n \"x86_64-pc-windows-msvc\": {\n },\n },\n \"crates/cli-lib\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli-python\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lib\": {\n _COMMON_CONDITION: {\n },\n \"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\": {\n },\n \"cfg(not(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\")))\": {\n },\n },\n \"crates/lib-core\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lib-dialects\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lsp\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/sqlinference\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lib-wasm\": {\n _COMMON_CONDITION: {\n },\n },\n}\n\n\n_NORMAL_DEV_DEPENDENCIES = {\n \"crates/lineage\": {\n _COMMON_CONDITION: {\n \"strum\": Label(\"@crates//:strum-0.28.0\"),\n },\n },\n \"crates/cli\": {\n _COMMON_CONDITION: {\n \"assert_cmd\": Label(\"@crates//:assert_cmd-2.2.2\"),\n \"expect-test\": Label(\"@crates//:expect-test-1.5.1\"),\n \"tempfile\": Label(\"@crates//:tempfile-3.27.0\"),\n },\n },\n \"crates/cli-lib\": {\n _COMMON_CONDITION: {\n \"assert_cmd\": Label(\"@crates//:assert_cmd-2.2.2\"),\n \"clap-markdown\": Label(\"@crates//:clap-markdown-0.1.5\"),\n \"expect-test\": Label(\"@crates//:expect-test-1.5.1\"),\n \"fancy-regex\": Label(\"@crates//:fancy-regex-0.18.0\"),\n \"minijinja\": Label(\"@crates//:minijinja-2.21.0\"),\n \"tempfile\": Label(\"@crates//:tempfile-3.27.0\"),\n },\n },\n \"crates/cli-python\": {\n _COMMON_CONDITION: {\n \"assert_cmd\": Label(\"@crates//:assert_cmd-2.2.2\"),\n \"expect-test\": Label(\"@crates//:expect-test-1.5.1\"),\n \"tempfile\": Label(\"@crates//:tempfile-3.27.0\"),\n },\n },\n \"crates/lib\": {\n _COMMON_CONDITION: {\n \"criterion\": Label(\"@crates//:codspeed-criterion-compat-4.4.1\"),\n \"expect-test\": Label(\"@crates//:expect-test-1.5.1\"),\n \"glob\": Label(\"@crates//:glob-0.3.3\"),\n \"serde_with\": Label(\"@crates//:serde_with-3.21.0\"),\n \"serde_yaml\": Label(\"@crates//:serde_yaml-0.9.34+deprecated\"),\n },\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n _COMMON_CONDITION: {\n \"configparser\": Label(\"@crates//:configparser-3.2.0\"),\n \"expect-test\": Label(\"@crates//:expect-test-1.5.1\"),\n \"glob\": Label(\"@crates//:glob-0.3.3\"),\n \"rayon\": Label(\"@crates//:rayon-1.12.0\"),\n },\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_NORMAL_DEV_ALIASES = {\n \"crates/lineage\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli-lib\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli-python\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lib\": {\n _COMMON_CONDITION: {\n Label(\"@crates//:codspeed-criterion-compat-4.4.1\"): \"criterion\",\n },\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_PROC_MACRO_DEPENDENCIES = {\n \"crates/lineage\": {\n },\n \"crates/cli\": {\n },\n \"crates/cli-lib\": {\n _COMMON_CONDITION: {\n \"strum_macros\": Label(\"@crates//:strum_macros-0.28.0\"),\n },\n },\n \"crates/cli-python\": {\n },\n \"crates/lib\": {\n _COMMON_CONDITION: {\n \"enum_dispatch\": Label(\"@crates//:enum_dispatch-0.3.13\"),\n \"strum_macros\": Label(\"@crates//:strum_macros-0.28.0\"),\n },\n },\n \"crates/lib-core\": {\n _COMMON_CONDITION: {\n \"enum_dispatch\": Label(\"@crates//:enum_dispatch-0.3.13\"),\n \"strum_macros\": Label(\"@crates//:strum_macros-0.28.0\"),\n },\n },\n \"crates/lib-dialects\": {\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_PROC_MACRO_ALIASES = {\n \"crates/lineage\": {\n },\n \"crates/cli\": {\n },\n \"crates/cli-lib\": {\n },\n \"crates/cli-python\": {\n },\n \"crates/lib\": {\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_PROC_MACRO_DEV_DEPENDENCIES = {\n \"crates/lineage\": {\n },\n \"crates/cli\": {\n },\n \"crates/cli-lib\": {\n },\n \"crates/cli-python\": {\n },\n \"crates/lib\": {\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_PROC_MACRO_DEV_ALIASES = {\n \"crates/lineage\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli-lib\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli-python\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lib\": {\n _COMMON_CONDITION: {\n Label(\"@crates//:codspeed-criterion-compat-4.4.1\"): \"criterion\",\n },\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_BUILD_DEPENDENCIES = {\n \"crates/lineage\": {\n },\n \"crates/cli\": {\n },\n \"crates/cli-lib\": {\n },\n \"crates/cli-python\": {\n },\n \"crates/lib\": {\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_BUILD_ALIASES = {\n \"crates/lineage\": {\n },\n \"crates/cli\": {\n },\n \"crates/cli-lib\": {\n },\n \"crates/cli-python\": {\n },\n \"crates/lib\": {\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_DEPENDENCIES = {\n \"crates/lineage\": {\n },\n \"crates/cli\": {\n },\n \"crates/cli-lib\": {\n },\n \"crates/cli-python\": {\n },\n \"crates/lib\": {\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_ALIASES = {\n \"crates/lineage\": {\n },\n \"crates/cli\": {\n },\n \"crates/cli-lib\": {\n },\n \"crates/cli-python\": {\n },\n \"crates/lib\": {\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_CONDITIONS = {\n \"aarch64-apple-darwin\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"aarch64-pc-windows-gnullvm\": [],\n \"aarch64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"cfg(all(any(target_arch = \\\"x86_64\\\", target_arch = \\\"arm64ec\\\"), target_env = \\\"msvc\\\", not(windows_raw_dylib)))\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\": [],\n \"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), not(any(all(target_os = \\\"linux\\\", target_env = \\\"\\\"), getrandom_backend = \\\"custom\\\", getrandom_backend = \\\"linux_raw\\\", getrandom_backend = \\\"rdrand\\\", getrandom_backend = \\\"rndr\\\"))))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(all(target_arch = \\\"aarch64\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\": [],\n \"cfg(all(target_arch = \\\"wasm32\\\", not(target_os = \\\"wasi\\\")))\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\", target_env = \\\"p2\\\"))\": [],\n \"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\", target_env = \\\"p3\\\"))\": [],\n \"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\": [],\n \"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\": [],\n \"cfg(all(target_arch = \\\"x86_64\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\": [\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(all(target_os = \\\"uefi\\\", getrandom_backend = \\\"efi_rng\\\"))\": [],\n \"cfg(any())\": [],\n \"cfg(any(target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"hurd\\\", target_os = \\\"illumos\\\", target_os = \\\"cygwin\\\", all(target_os = \\\"horizon\\\", target_arch = \\\"arm\\\")))\": [],\n \"cfg(any(target_os = \\\"haiku\\\", target_os = \\\"redox\\\", target_os = \\\"nto\\\", target_os = \\\"aix\\\"))\": [],\n \"cfg(any(target_os = \\\"ios\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\", target_os = \\\"tvos\\\"))\": [],\n \"cfg(any(target_os = \\\"macos\\\", target_os = \\\"openbsd\\\", target_os = \\\"vita\\\", target_os = \\\"emscripten\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(any(unix, target_os = \\\"wasi\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:wasm32-wasip1\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\")))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(target_has_atomic = \\\"64\\\"))\": [],\n \"cfg(target_arch = \\\"spirv\\\")\": [],\n \"cfg(target_os = \\\"hermit\\\")\": [],\n \"cfg(target_os = \\\"netbsd\\\")\": [],\n \"cfg(target_os = \\\"solaris\\\")\": [],\n \"cfg(target_os = \\\"vxworks\\\")\": [],\n \"cfg(target_os = \\\"wasi\\\")\": [\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(unix)\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(windows)\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"i686-pc-windows-gnullvm\": [],\n \"wasm32-unknown-unknown\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"wasm32-wasip1\": [\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"x86_64-pc-windows-gnullvm\": [],\n \"x86_64-pc-windows-msvc\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"x86_64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"x86_64-unknown-nixos-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n}\n\n###############################################################################\n\ndef crate_repositories():\n \"\"\"A macro for defining repositories for all generated crates.\n\n Returns:\n A list of repos visible to the module through the module extension.\n \"\"\"\n maybe(\n http_archive,\n name = \"crates__aho-corasick-1.1.4\",\n sha256 = \"ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aho-corasick/1.1.4/download\"],\n strip_prefix = \"aho-corasick-1.1.4\",\n build_file = Label(\"@crates//crates:BUILD.aho-corasick-1.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__allocator-api2-0.2.21\",\n sha256 = \"683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/allocator-api2/0.2.21/download\"],\n strip_prefix = \"allocator-api2-0.2.21\",\n build_file = Label(\"@crates//crates:BUILD.allocator-api2-0.2.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__anes-0.1.6\",\n sha256 = \"4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anes/0.1.6/download\"],\n strip_prefix = \"anes-0.1.6\",\n build_file = Label(\"@crates//crates:BUILD.anes-0.1.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__anstream-1.0.0\",\n sha256 = \"824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstream/1.0.0/download\"],\n strip_prefix = \"anstream-1.0.0\",\n build_file = Label(\"@crates//crates:BUILD.anstream-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__anstyle-1.0.14\",\n sha256 = \"940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle/1.0.14/download\"],\n strip_prefix = \"anstyle-1.0.14\",\n build_file = Label(\"@crates//crates:BUILD.anstyle-1.0.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__anstyle-parse-1.0.0\",\n sha256 = \"52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-parse/1.0.0/download\"],\n strip_prefix = \"anstyle-parse-1.0.0\",\n build_file = Label(\"@crates//crates:BUILD.anstyle-parse-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__anstyle-query-1.1.5\",\n sha256 = \"40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-query/1.1.5/download\"],\n strip_prefix = \"anstyle-query-1.1.5\",\n build_file = Label(\"@crates//crates:BUILD.anstyle-query-1.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__anstyle-wincon-3.0.11\",\n sha256 = \"291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-wincon/3.0.11/download\"],\n strip_prefix = \"anstyle-wincon-3.0.11\",\n build_file = Label(\"@crates//crates:BUILD.anstyle-wincon-3.0.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__anyhow-1.0.102\",\n sha256 = \"7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anyhow/1.0.102/download\"],\n strip_prefix = \"anyhow-1.0.102\",\n build_file = Label(\"@crates//crates:BUILD.anyhow-1.0.102.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__append-only-vec-0.1.8\",\n sha256 = \"2114736faba96bcd79595c700d03183f61357b9fbce14852515e59f3bee4ed4a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/append-only-vec/0.1.8/download\"],\n strip_prefix = \"append-only-vec-0.1.8\",\n build_file = Label(\"@crates//crates:BUILD.append-only-vec-0.1.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__approx-0.5.1\",\n sha256 = \"cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/approx/0.5.1/download\"],\n strip_prefix = \"approx-0.5.1\",\n build_file = Label(\"@crates//crates:BUILD.approx-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__assert_cmd-2.2.2\",\n sha256 = \"2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/assert_cmd/2.2.2/download\"],\n strip_prefix = \"assert_cmd-2.2.2\",\n build_file = Label(\"@crates//crates:BUILD.assert_cmd-2.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__autocfg-1.5.0\",\n sha256 = \"c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/autocfg/1.5.0/download\"],\n strip_prefix = \"autocfg-1.5.0\",\n build_file = Label(\"@crates//crates:BUILD.autocfg-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__base64-0.22.1\",\n sha256 = \"72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/base64/0.22.1/download\"],\n strip_prefix = \"base64-0.22.1\",\n build_file = Label(\"@crates//crates:BUILD.base64-0.22.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bit-set-0.8.0\",\n sha256 = \"08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bit-set/0.8.0/download\"],\n strip_prefix = \"bit-set-0.8.0\",\n build_file = Label(\"@crates//crates:BUILD.bit-set-0.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bit-vec-0.8.0\",\n sha256 = \"5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bit-vec/0.8.0/download\"],\n strip_prefix = \"bit-vec-0.8.0\",\n build_file = Label(\"@crates//crates:BUILD.bit-vec-0.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bitflags-1.3.2\",\n sha256 = \"bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitflags/1.3.2/download\"],\n strip_prefix = \"bitflags-1.3.2\",\n build_file = Label(\"@crates//crates:BUILD.bitflags-1.3.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bitflags-2.11.0\",\n sha256 = \"843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitflags/2.11.0/download\"],\n strip_prefix = \"bitflags-2.11.0\",\n build_file = Label(\"@crates//crates:BUILD.bitflags-2.11.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__borsh-1.6.1\",\n sha256 = \"cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/borsh/1.6.1/download\"],\n strip_prefix = \"borsh-1.6.1\",\n build_file = Label(\"@crates//crates:BUILD.borsh-1.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bs58-0.5.1\",\n sha256 = \"bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bs58/0.5.1/download\"],\n strip_prefix = \"bs58-0.5.1\",\n build_file = Label(\"@crates//crates:BUILD.bs58-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bstr-1.12.1\",\n sha256 = \"63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bstr/1.12.1/download\"],\n strip_prefix = \"bstr-1.12.1\",\n build_file = Label(\"@crates//crates:BUILD.bstr-1.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bumpalo-3.20.2\",\n sha256 = \"5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bumpalo/3.20.2/download\"],\n strip_prefix = \"bumpalo-3.20.2\",\n build_file = Label(\"@crates//crates:BUILD.bumpalo-3.20.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bytes-1.11.1\",\n sha256 = \"1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bytes/1.11.1/download\"],\n strip_prefix = \"bytes-1.11.1\",\n build_file = Label(\"@crates//crates:BUILD.bytes-1.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__cast-0.3.0\",\n sha256 = \"37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cast/0.3.0/download\"],\n strip_prefix = \"cast-0.3.0\",\n build_file = Label(\"@crates//crates:BUILD.cast-0.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__cc-1.2.57\",\n sha256 = \"7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cc/1.2.57/download\"],\n strip_prefix = \"cc-1.2.57\",\n build_file = Label(\"@crates//crates:BUILD.cc-1.2.57.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__cfg-if-1.0.4\",\n sha256 = \"9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cfg-if/1.0.4/download\"],\n strip_prefix = \"cfg-if-1.0.4\",\n build_file = Label(\"@crates//crates:BUILD.cfg-if-1.0.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__cfg_aliases-0.2.1\",\n sha256 = \"613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cfg_aliases/0.2.1/download\"],\n strip_prefix = \"cfg_aliases-0.2.1\",\n build_file = Label(\"@crates//crates:BUILD.cfg_aliases-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__chrono-0.4.44\",\n sha256 = \"c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/chrono/0.4.44/download\"],\n strip_prefix = \"chrono-0.4.44\",\n build_file = Label(\"@crates//crates:BUILD.chrono-0.4.44.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__ciborium-0.2.2\",\n sha256 = \"42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ciborium/0.2.2/download\"],\n strip_prefix = \"ciborium-0.2.2\",\n build_file = Label(\"@crates//crates:BUILD.ciborium-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__ciborium-io-0.2.2\",\n sha256 = \"05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ciborium-io/0.2.2/download\"],\n strip_prefix = \"ciborium-io-0.2.2\",\n build_file = Label(\"@crates//crates:BUILD.ciborium-io-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__ciborium-ll-0.2.2\",\n sha256 = \"57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ciborium-ll/0.2.2/download\"],\n strip_prefix = \"ciborium-ll-0.2.2\",\n build_file = Label(\"@crates//crates:BUILD.ciborium-ll-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__clap-4.6.1\",\n sha256 = \"1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap/4.6.1/download\"],\n strip_prefix = \"clap-4.6.1\",\n build_file = Label(\"@crates//crates:BUILD.clap-4.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__clap-markdown-0.1.5\",\n sha256 = \"d2a2617956a06d4885b490697b5307ebb09fec10b088afc18c81762d848c2339\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap-markdown/0.1.5/download\"],\n strip_prefix = \"clap-markdown-0.1.5\",\n build_file = Label(\"@crates//crates:BUILD.clap-markdown-0.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__clap_builder-4.6.0\",\n sha256 = \"714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_builder/4.6.0/download\"],\n strip_prefix = \"clap_builder-4.6.0\",\n build_file = Label(\"@crates//crates:BUILD.clap_builder-4.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__clap_derive-4.6.1\",\n sha256 = \"f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_derive/4.6.1/download\"],\n strip_prefix = \"clap_derive-4.6.1\",\n build_file = Label(\"@crates//crates:BUILD.clap_derive-4.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__clap_lex-1.1.0\",\n sha256 = \"c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_lex/1.1.0/download\"],\n strip_prefix = \"clap_lex-1.1.0\",\n build_file = Label(\"@crates//crates:BUILD.clap_lex-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__codspeed-4.4.1\",\n sha256 = \"b684e94583e85a5ca7e1a6454a89d76a5121240f2fb67eb564129d9bafdb9db0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/codspeed/4.4.1/download\"],\n strip_prefix = \"codspeed-4.4.1\",\n build_file = Label(\"@crates//crates:BUILD.codspeed-4.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__codspeed-criterion-compat-4.4.1\",\n sha256 = \"2e65444156eb73ad7f57618188f8d4a281726d133ef55b96d1dcff89528609ab\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/codspeed-criterion-compat/4.4.1/download\"],\n strip_prefix = \"codspeed-criterion-compat-4.4.1\",\n build_file = Label(\"@crates//crates:BUILD.codspeed-criterion-compat-4.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__codspeed-criterion-compat-walltime-4.4.1\",\n sha256 = \"96389aaa4bbb872ea4924dc0335b2bb181bcf28d6eedbe8fea29afcc5bde36a6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/codspeed-criterion-compat-walltime/4.4.1/download\"],\n strip_prefix = \"codspeed-criterion-compat-walltime-4.4.1\",\n build_file = Label(\"@crates//crates:BUILD.codspeed-criterion-compat-walltime-4.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__colorchoice-1.0.5\",\n sha256 = \"1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/colorchoice/1.0.5/download\"],\n strip_prefix = \"colorchoice-1.0.5\",\n build_file = Label(\"@crates//crates:BUILD.colorchoice-1.0.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__colored-2.2.0\",\n sha256 = \"117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/colored/2.2.0/download\"],\n strip_prefix = \"colored-2.2.0\",\n build_file = Label(\"@crates//crates:BUILD.colored-2.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__common-path-1.0.0\",\n sha256 = \"2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/common-path/1.0.0/download\"],\n strip_prefix = \"common-path-1.0.0\",\n build_file = Label(\"@crates//crates:BUILD.common-path-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__configparser-3.2.0\",\n sha256 = \"b46dec724fd22199ebde05033a0cbae453bc3b1ecff11eb6a6bb3eec4b90c6a4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/configparser/3.2.0/download\"],\n strip_prefix = \"configparser-3.2.0\",\n build_file = Label(\"@crates//crates:BUILD.configparser-3.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__console_error_panic_hook-0.1.7\",\n sha256 = \"a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/console_error_panic_hook/0.1.7/download\"],\n strip_prefix = \"console_error_panic_hook-0.1.7\",\n build_file = Label(\"@crates//crates:BUILD.console_error_panic_hook-0.1.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__criterion-plot-0.5.0\",\n sha256 = \"6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/criterion-plot/0.5.0/download\"],\n strip_prefix = \"criterion-plot-0.5.0\",\n build_file = Label(\"@crates//crates:BUILD.criterion-plot-0.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__crossbeam-channel-0.5.15\",\n sha256 = \"82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-channel/0.5.15/download\"],\n strip_prefix = \"crossbeam-channel-0.5.15\",\n build_file = Label(\"@crates//crates:BUILD.crossbeam-channel-0.5.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__crossbeam-deque-0.8.6\",\n sha256 = \"9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-deque/0.8.6/download\"],\n strip_prefix = \"crossbeam-deque-0.8.6\",\n build_file = Label(\"@crates//crates:BUILD.crossbeam-deque-0.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__crossbeam-epoch-0.9.18\",\n sha256 = \"5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-epoch/0.9.18/download\"],\n strip_prefix = \"crossbeam-epoch-0.9.18\",\n build_file = Label(\"@crates//crates:BUILD.crossbeam-epoch-0.9.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__crossbeam-utils-0.8.21\",\n sha256 = \"d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-utils/0.8.21/download\"],\n strip_prefix = \"crossbeam-utils-0.8.21\",\n build_file = Label(\"@crates//crates:BUILD.crossbeam-utils-0.8.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__crunchy-0.2.4\",\n sha256 = \"460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crunchy/0.2.4/download\"],\n strip_prefix = \"crunchy-0.2.4\",\n build_file = Label(\"@crates//crates:BUILD.crunchy-0.2.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__darling-0.23.0\",\n sha256 = \"25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/darling/0.23.0/download\"],\n strip_prefix = \"darling-0.23.0\",\n build_file = Label(\"@crates//crates:BUILD.darling-0.23.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__darling_core-0.23.0\",\n sha256 = \"9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/darling_core/0.23.0/download\"],\n strip_prefix = \"darling_core-0.23.0\",\n build_file = Label(\"@crates//crates:BUILD.darling_core-0.23.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__darling_macro-0.23.0\",\n sha256 = \"ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/darling_macro/0.23.0/download\"],\n strip_prefix = \"darling_macro-0.23.0\",\n build_file = Label(\"@crates//crates:BUILD.darling_macro-0.23.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__deranged-0.5.8\",\n sha256 = \"7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/deranged/0.5.8/download\"],\n strip_prefix = \"deranged-0.5.8\",\n build_file = Label(\"@crates//crates:BUILD.deranged-0.5.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__diff-0.1.13\",\n sha256 = \"56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/diff/0.1.13/download\"],\n strip_prefix = \"diff-0.1.13\",\n build_file = Label(\"@crates//crates:BUILD.diff-0.1.13.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__difflib-0.4.0\",\n sha256 = \"6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/difflib/0.4.0/download\"],\n strip_prefix = \"difflib-0.4.0\",\n build_file = Label(\"@crates//crates:BUILD.difflib-0.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__dissimilar-1.0.11\",\n sha256 = \"aeda16ab4059c5fd2a83f2b9c9e9c981327b18aa8e3b313f7e6563799d4f093e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/dissimilar/1.0.11/download\"],\n strip_prefix = \"dissimilar-1.0.11\",\n build_file = Label(\"@crates//crates:BUILD.dissimilar-1.0.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__either-1.15.0\",\n sha256 = \"48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/either/1.15.0/download\"],\n strip_prefix = \"either-1.15.0\",\n build_file = Label(\"@crates//crates:BUILD.either-1.15.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__enum_dispatch-0.3.13\",\n sha256 = \"aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/enum_dispatch/0.3.13/download\"],\n strip_prefix = \"enum_dispatch-0.3.13\",\n build_file = Label(\"@crates//crates:BUILD.enum_dispatch-0.3.13.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__equivalent-1.0.2\",\n sha256 = \"877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/equivalent/1.0.2/download\"],\n strip_prefix = \"equivalent-1.0.2\",\n build_file = Label(\"@crates//crates:BUILD.equivalent-1.0.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__errno-0.3.14\",\n sha256 = \"39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/errno/0.3.14/download\"],\n strip_prefix = \"errno-0.3.14\",\n build_file = Label(\"@crates//crates:BUILD.errno-0.3.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__expect-test-1.5.1\",\n sha256 = \"63af43ff4431e848fb47472a920f14fa71c24de13255a5692e93d4e90302acb0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/expect-test/1.5.1/download\"],\n strip_prefix = \"expect-test-1.5.1\",\n build_file = Label(\"@crates//crates:BUILD.expect-test-1.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__fancy-regex-0.18.0\",\n sha256 = \"e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fancy-regex/0.18.0/download\"],\n strip_prefix = \"fancy-regex-0.18.0\",\n build_file = Label(\"@crates//crates:BUILD.fancy-regex-0.18.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__fastrand-2.3.0\",\n sha256 = \"37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fastrand/2.3.0/download\"],\n strip_prefix = \"fastrand-2.3.0\",\n build_file = Label(\"@crates//crates:BUILD.fastrand-2.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__fern-0.7.1\",\n sha256 = \"4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fern/0.7.1/download\"],\n strip_prefix = \"fern-0.7.1\",\n build_file = Label(\"@crates//crates:BUILD.fern-0.7.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__find-msvc-tools-0.1.9\",\n sha256 = \"5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/find-msvc-tools/0.1.9/download\"],\n strip_prefix = \"find-msvc-tools-0.1.9\",\n build_file = Label(\"@crates//crates:BUILD.find-msvc-tools-0.1.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__fluent-uri-0.1.4\",\n sha256 = \"17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fluent-uri/0.1.4/download\"],\n strip_prefix = \"fluent-uri-0.1.4\",\n build_file = Label(\"@crates//crates:BUILD.fluent-uri-0.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__foldhash-0.1.5\",\n sha256 = \"d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/foldhash/0.1.5/download\"],\n strip_prefix = \"foldhash-0.1.5\",\n build_file = Label(\"@crates//crates:BUILD.foldhash-0.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__foldhash-0.2.0\",\n sha256 = \"77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/foldhash/0.2.0/download\"],\n strip_prefix = \"foldhash-0.2.0\",\n build_file = Label(\"@crates//crates:BUILD.foldhash-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__getrandom-0.2.17\",\n sha256 = \"ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/getrandom/0.2.17/download\"],\n strip_prefix = \"getrandom-0.2.17\",\n build_file = Label(\"@crates//crates:BUILD.getrandom-0.2.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__getrandom-0.4.2\",\n sha256 = \"0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/getrandom/0.4.2/download\"],\n strip_prefix = \"getrandom-0.4.2\",\n build_file = Label(\"@crates//crates:BUILD.getrandom-0.4.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__glob-0.3.3\",\n sha256 = \"0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/glob/0.3.3/download\"],\n strip_prefix = \"glob-0.3.3\",\n build_file = Label(\"@crates//crates:BUILD.glob-0.3.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__globset-0.4.18\",\n sha256 = \"52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/globset/0.4.18/download\"],\n strip_prefix = \"globset-0.4.18\",\n build_file = Label(\"@crates//crates:BUILD.globset-0.4.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__half-2.7.1\",\n sha256 = \"6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/half/2.7.1/download\"],\n strip_prefix = \"half-2.7.1\",\n build_file = Label(\"@crates//crates:BUILD.half-2.7.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__hashbrown-0.15.5\",\n sha256 = \"9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.15.5/download\"],\n strip_prefix = \"hashbrown-0.15.5\",\n build_file = Label(\"@crates//crates:BUILD.hashbrown-0.15.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__hashbrown-0.17.1\",\n sha256 = \"ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.17.1/download\"],\n strip_prefix = \"hashbrown-0.17.1\",\n build_file = Label(\"@crates//crates:BUILD.hashbrown-0.17.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__heck-0.5.0\",\n sha256 = \"2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/heck/0.5.0/download\"],\n strip_prefix = \"heck-0.5.0\",\n build_file = Label(\"@crates//crates:BUILD.heck-0.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__hermit-abi-0.5.2\",\n sha256 = \"fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hermit-abi/0.5.2/download\"],\n strip_prefix = \"hermit-abi-0.5.2\",\n build_file = Label(\"@crates//crates:BUILD.hermit-abi-0.5.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__hex-0.4.3\",\n sha256 = \"7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hex/0.4.3/download\"],\n strip_prefix = \"hex-0.4.3\",\n build_file = Label(\"@crates//crates:BUILD.hex-0.4.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__id-arena-2.3.0\",\n sha256 = \"3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/id-arena/2.3.0/download\"],\n strip_prefix = \"id-arena-2.3.0\",\n build_file = Label(\"@crates//crates:BUILD.id-arena-2.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__ident_case-1.0.1\",\n sha256 = \"b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ident_case/1.0.1/download\"],\n strip_prefix = \"ident_case-1.0.1\",\n build_file = Label(\"@crates//crates:BUILD.ident_case-1.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__ignore-0.4.27\",\n sha256 = \"fe112b004901c62c2faa11f4f75e9864e0cc5af8da71c9115d184a3aa888749f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ignore/0.4.27/download\"],\n strip_prefix = \"ignore-0.4.27\",\n build_file = Label(\"@crates//crates:BUILD.ignore-0.4.27.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__indexmap-2.14.0\",\n sha256 = \"d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/indexmap/2.14.0/download\"],\n strip_prefix = \"indexmap-2.14.0\",\n build_file = Label(\"@crates//crates:BUILD.indexmap-2.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__is-terminal-0.4.17\",\n sha256 = \"3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/is-terminal/0.4.17/download\"],\n strip_prefix = \"is-terminal-0.4.17\",\n build_file = Label(\"@crates//crates:BUILD.is-terminal-0.4.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__is_terminal_polyfill-1.70.2\",\n sha256 = \"a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/is_terminal_polyfill/1.70.2/download\"],\n strip_prefix = \"is_terminal_polyfill-1.70.2\",\n build_file = Label(\"@crates//crates:BUILD.is_terminal_polyfill-1.70.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__itertools-0.10.5\",\n sha256 = \"b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itertools/0.10.5/download\"],\n strip_prefix = \"itertools-0.10.5\",\n build_file = Label(\"@crates//crates:BUILD.itertools-0.10.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__itertools-0.15.0\",\n sha256 = \"8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itertools/0.15.0/download\"],\n strip_prefix = \"itertools-0.15.0\",\n build_file = Label(\"@crates//crates:BUILD.itertools-0.15.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__itoa-1.0.17\",\n sha256 = \"92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itoa/1.0.17/download\"],\n strip_prefix = \"itoa-1.0.17\",\n build_file = Label(\"@crates//crates:BUILD.itoa-1.0.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__js-sys-0.3.82\",\n sha256 = \"b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/js-sys/0.3.82/download\"],\n strip_prefix = \"js-sys-0.3.82\",\n build_file = Label(\"@crates//crates:BUILD.js-sys-0.3.82.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__lazy-regex-3.6.0\",\n sha256 = \"6bae91019476d3ec7147de9aa291cadb6d870abf2f3015d2da73a90325ac1496\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lazy-regex/3.6.0/download\"],\n strip_prefix = \"lazy-regex-3.6.0\",\n build_file = Label(\"@crates//crates:BUILD.lazy-regex-3.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__lazy-regex-proc_macros-3.6.0\",\n sha256 = \"4de9c1e1439d8b7b3061b2d209809f447ca33241733d9a3c01eabf2dc8d94358\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lazy-regex-proc_macros/3.6.0/download\"],\n strip_prefix = \"lazy-regex-proc_macros-3.6.0\",\n build_file = Label(\"@crates//crates:BUILD.lazy-regex-proc_macros-3.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__lazy_static-1.5.0\",\n sha256 = \"bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lazy_static/1.5.0/download\"],\n strip_prefix = \"lazy_static-1.5.0\",\n build_file = Label(\"@crates//crates:BUILD.lazy_static-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__leb128fmt-0.1.0\",\n sha256 = \"09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/leb128fmt/0.1.0/download\"],\n strip_prefix = \"leb128fmt-0.1.0\",\n build_file = Label(\"@crates//crates:BUILD.leb128fmt-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__libc-0.2.183\",\n sha256 = \"b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/libc/0.2.183/download\"],\n strip_prefix = \"libc-0.2.183\",\n build_file = Label(\"@crates//crates:BUILD.libc-0.2.183.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__libmimalloc-sys-0.1.49\",\n sha256 = \"6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/libmimalloc-sys/0.1.49/download\"],\n strip_prefix = \"libmimalloc-sys-0.1.49\",\n build_file = Label(\"@crates//crates:BUILD.libmimalloc-sys-0.1.49.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__line-index-0.1.2\",\n sha256 = \"3e27e0ed5a392a7f5ba0b3808a2afccff16c64933312c84b57618b49d1209bd2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/line-index/0.1.2/download\"],\n strip_prefix = \"line-index-0.1.2\",\n build_file = Label(\"@crates//crates:BUILD.line-index-0.1.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__linux-raw-sys-0.12.1\",\n sha256 = \"32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/linux-raw-sys/0.12.1/download\"],\n strip_prefix = \"linux-raw-sys-0.12.1\",\n build_file = Label(\"@crates//crates:BUILD.linux-raw-sys-0.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__log-0.4.33\",\n sha256 = \"0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/log/0.4.33/download\"],\n strip_prefix = \"log-0.4.33\",\n build_file = Label(\"@crates//crates:BUILD.log-0.4.33.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__lsp-server-0.8.0\",\n sha256 = \"0ad8be6fe0ca81b8298bfbbe8a77e9fcd8895ad6c84cd7794d5ebadcbb09ae43\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lsp-server/0.8.0/download\"],\n strip_prefix = \"lsp-server-0.8.0\",\n build_file = Label(\"@crates//crates:BUILD.lsp-server-0.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__lsp-types-0.97.0\",\n sha256 = \"53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lsp-types/0.97.0/download\"],\n strip_prefix = \"lsp-types-0.97.0\",\n build_file = Label(\"@crates//crates:BUILD.lsp-types-0.97.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__memchr-2.8.0\",\n sha256 = \"f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memchr/2.8.0/download\"],\n strip_prefix = \"memchr-2.8.0\",\n build_file = Label(\"@crates//crates:BUILD.memchr-2.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__memo-map-0.3.3\",\n sha256 = \"38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memo-map/0.3.3/download\"],\n strip_prefix = \"memo-map-0.3.3\",\n build_file = Label(\"@crates//crates:BUILD.memo-map-0.3.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__mimalloc-0.1.52\",\n sha256 = \"2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/mimalloc/0.1.52/download\"],\n strip_prefix = \"mimalloc-0.1.52\",\n build_file = Label(\"@crates//crates:BUILD.mimalloc-0.1.52.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__minijinja-2.21.0\",\n sha256 = \"cb3d648e68cea56d9858d535ee28f9538404e2dd8cb08ed0bd05dca379477f39\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/minijinja/2.21.0/download\"],\n strip_prefix = \"minijinja-2.21.0\",\n build_file = Label(\"@crates//crates:BUILD.minijinja-2.21.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__nix-0.31.2\",\n sha256 = \"5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nix/0.31.2/download\"],\n strip_prefix = \"nix-0.31.2\",\n build_file = Label(\"@crates//crates:BUILD.nix-0.31.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__nohash-hasher-0.2.0\",\n sha256 = \"2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nohash-hasher/0.2.0/download\"],\n strip_prefix = \"nohash-hasher-0.2.0\",\n build_file = Label(\"@crates//crates:BUILD.nohash-hasher-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__num-conv-0.2.0\",\n sha256 = \"cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-conv/0.2.0/download\"],\n strip_prefix = \"num-conv-0.2.0\",\n build_file = Label(\"@crates//crates:BUILD.num-conv-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__num-traits-0.2.19\",\n sha256 = \"071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-traits/0.2.19/download\"],\n strip_prefix = \"num-traits-0.2.19\",\n build_file = Label(\"@crates//crates:BUILD.num-traits-0.2.19.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__once_cell-1.21.4\",\n sha256 = \"9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/once_cell/1.21.4/download\"],\n strip_prefix = \"once_cell-1.21.4\",\n build_file = Label(\"@crates//crates:BUILD.once_cell-1.21.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__once_cell_polyfill-1.70.2\",\n sha256 = \"384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/once_cell_polyfill/1.70.2/download\"],\n strip_prefix = \"once_cell_polyfill-1.70.2\",\n build_file = Label(\"@crates//crates:BUILD.once_cell_polyfill-1.70.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__oorandom-11.1.5\",\n sha256 = \"d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/oorandom/11.1.5/download\"],\n strip_prefix = \"oorandom-11.1.5\",\n build_file = Label(\"@crates//crates:BUILD.oorandom-11.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__plotters-0.3.7\",\n sha256 = \"5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/plotters/0.3.7/download\"],\n strip_prefix = \"plotters-0.3.7\",\n build_file = Label(\"@crates//crates:BUILD.plotters-0.3.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__plotters-backend-0.3.7\",\n sha256 = \"df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/plotters-backend/0.3.7/download\"],\n strip_prefix = \"plotters-backend-0.3.7\",\n build_file = Label(\"@crates//crates:BUILD.plotters-backend-0.3.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__plotters-svg-0.3.7\",\n sha256 = \"51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/plotters-svg/0.3.7/download\"],\n strip_prefix = \"plotters-svg-0.3.7\",\n build_file = Label(\"@crates//crates:BUILD.plotters-svg-0.3.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__portable-atomic-1.13.1\",\n sha256 = \"c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/portable-atomic/1.13.1/download\"],\n strip_prefix = \"portable-atomic-1.13.1\",\n build_file = Label(\"@crates//crates:BUILD.portable-atomic-1.13.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__powerfmt-0.2.0\",\n sha256 = \"439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/powerfmt/0.2.0/download\"],\n strip_prefix = \"powerfmt-0.2.0\",\n build_file = Label(\"@crates//crates:BUILD.powerfmt-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__predicates-3.1.4\",\n sha256 = \"ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/predicates/3.1.4/download\"],\n strip_prefix = \"predicates-3.1.4\",\n build_file = Label(\"@crates//crates:BUILD.predicates-3.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__predicates-core-1.0.10\",\n sha256 = \"cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/predicates-core/1.0.10/download\"],\n strip_prefix = \"predicates-core-1.0.10\",\n build_file = Label(\"@crates//crates:BUILD.predicates-core-1.0.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__predicates-tree-1.0.13\",\n sha256 = \"d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/predicates-tree/1.0.13/download\"],\n strip_prefix = \"predicates-tree-1.0.13\",\n build_file = Label(\"@crates//crates:BUILD.predicates-tree-1.0.13.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__pretty_assertions-1.4.1\",\n sha256 = \"3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pretty_assertions/1.4.1/download\"],\n strip_prefix = \"pretty_assertions-1.4.1\",\n build_file = Label(\"@crates//crates:BUILD.pretty_assertions-1.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__prettyplease-0.2.37\",\n sha256 = \"479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/prettyplease/0.2.37/download\"],\n strip_prefix = \"prettyplease-0.2.37\",\n build_file = Label(\"@crates//crates:BUILD.prettyplease-0.2.37.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__proc-macro2-1.0.106\",\n sha256 = \"8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/proc-macro2/1.0.106/download\"],\n strip_prefix = \"proc-macro2-1.0.106\",\n build_file = Label(\"@crates//crates:BUILD.proc-macro2-1.0.106.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__pyo3-0.29.0\",\n sha256 = \"cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pyo3/0.29.0/download\"],\n strip_prefix = \"pyo3-0.29.0\",\n build_file = Label(\"@crates//crates:BUILD.pyo3-0.29.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__pyo3-build-config-0.29.0\",\n sha256 = \"c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pyo3-build-config/0.29.0/download\"],\n strip_prefix = \"pyo3-build-config-0.29.0\",\n build_file = Label(\"@crates//crates:BUILD.pyo3-build-config-0.29.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__pyo3-ffi-0.29.0\",\n sha256 = \"ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pyo3-ffi/0.29.0/download\"],\n strip_prefix = \"pyo3-ffi-0.29.0\",\n build_file = Label(\"@crates//crates:BUILD.pyo3-ffi-0.29.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__pyo3-macros-0.29.0\",\n sha256 = \"9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pyo3-macros/0.29.0/download\"],\n strip_prefix = \"pyo3-macros-0.29.0\",\n build_file = Label(\"@crates//crates:BUILD.pyo3-macros-0.29.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__pyo3-macros-backend-0.29.0\",\n sha256 = \"4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pyo3-macros-backend/0.29.0/download\"],\n strip_prefix = \"pyo3-macros-backend-0.29.0\",\n build_file = Label(\"@crates//crates:BUILD.pyo3-macros-backend-0.29.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__quote-1.0.45\",\n sha256 = \"41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quote/1.0.45/download\"],\n strip_prefix = \"quote-1.0.45\",\n build_file = Label(\"@crates//crates:BUILD.quote-1.0.45.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__r-efi-6.0.0\",\n sha256 = \"f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/r-efi/6.0.0/download\"],\n strip_prefix = \"r-efi-6.0.0\",\n build_file = Label(\"@crates//crates:BUILD.r-efi-6.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__rayon-1.12.0\",\n sha256 = \"fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rayon/1.12.0/download\"],\n strip_prefix = \"rayon-1.12.0\",\n build_file = Label(\"@crates//crates:BUILD.rayon-1.12.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__rayon-core-1.13.0\",\n sha256 = \"22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rayon-core/1.13.0/download\"],\n strip_prefix = \"rayon-core-1.13.0\",\n build_file = Label(\"@crates//crates:BUILD.rayon-core-1.13.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__regex-1.12.4\",\n sha256 = \"f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex/1.12.4/download\"],\n strip_prefix = \"regex-1.12.4\",\n build_file = Label(\"@crates//crates:BUILD.regex-1.12.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__regex-automata-0.4.14\",\n sha256 = \"6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex-automata/0.4.14/download\"],\n strip_prefix = \"regex-automata-0.4.14\",\n build_file = Label(\"@crates//crates:BUILD.regex-automata-0.4.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__regex-syntax-0.8.11\",\n sha256 = \"d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex-syntax/0.8.11/download\"],\n strip_prefix = \"regex-syntax-0.8.11\",\n build_file = Label(\"@crates//crates:BUILD.regex-syntax-0.8.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__rustix-1.1.4\",\n sha256 = \"b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustix/1.1.4/download\"],\n strip_prefix = \"rustix-1.1.4\",\n build_file = Label(\"@crates//crates:BUILD.rustix-1.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__rustversion-1.0.22\",\n sha256 = \"b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustversion/1.0.22/download\"],\n strip_prefix = \"rustversion-1.0.22\",\n build_file = Label(\"@crates//crates:BUILD.rustversion-1.0.22.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__ryu-1.0.23\",\n sha256 = \"9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ryu/1.0.23/download\"],\n strip_prefix = \"ryu-1.0.23\",\n build_file = Label(\"@crates//crates:BUILD.ryu-1.0.23.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__same-file-1.0.6\",\n sha256 = \"93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/same-file/1.0.6/download\"],\n strip_prefix = \"same-file-1.0.6\",\n build_file = Label(\"@crates//crates:BUILD.same-file-1.0.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__semver-1.0.27\",\n sha256 = \"d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/semver/1.0.27/download\"],\n strip_prefix = \"semver-1.0.27\",\n build_file = Label(\"@crates//crates:BUILD.semver-1.0.27.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde-1.0.228\",\n sha256 = \"9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde/1.0.228/download\"],\n strip_prefix = \"serde-1.0.228\",\n build_file = Label(\"@crates//crates:BUILD.serde-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde-wasm-bindgen-0.6.5\",\n sha256 = \"8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde-wasm-bindgen/0.6.5/download\"],\n strip_prefix = \"serde-wasm-bindgen-0.6.5\",\n build_file = Label(\"@crates//crates:BUILD.serde-wasm-bindgen-0.6.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_core-1.0.228\",\n sha256 = \"41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_core/1.0.228/download\"],\n strip_prefix = \"serde_core-1.0.228\",\n build_file = Label(\"@crates//crates:BUILD.serde_core-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_derive-1.0.228\",\n sha256 = \"d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_derive/1.0.228/download\"],\n strip_prefix = \"serde_derive-1.0.228\",\n build_file = Label(\"@crates//crates:BUILD.serde_derive-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_json-1.0.150\",\n sha256 = \"e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_json/1.0.150/download\"],\n strip_prefix = \"serde_json-1.0.150\",\n build_file = Label(\"@crates//crates:BUILD.serde_json-1.0.150.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_repr-0.1.20\",\n sha256 = \"175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_repr/0.1.20/download\"],\n strip_prefix = \"serde_repr-0.1.20\",\n build_file = Label(\"@crates//crates:BUILD.serde_repr-0.1.20.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_spanned-1.1.1\",\n sha256 = \"6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_spanned/1.1.1/download\"],\n strip_prefix = \"serde_spanned-1.1.1\",\n build_file = Label(\"@crates//crates:BUILD.serde_spanned-1.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_with-3.21.0\",\n sha256 = \"76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_with/3.21.0/download\"],\n strip_prefix = \"serde_with-3.21.0\",\n build_file = Label(\"@crates//crates:BUILD.serde_with-3.21.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_with_macros-3.21.0\",\n sha256 = \"84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_with_macros/3.21.0/download\"],\n strip_prefix = \"serde_with_macros-3.21.0\",\n build_file = Label(\"@crates//crates:BUILD.serde_with_macros-3.21.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_yaml-0.9.34-deprecated\",\n sha256 = \"6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_yaml/0.9.34+deprecated/download\"],\n strip_prefix = \"serde_yaml-0.9.34+deprecated\",\n build_file = Label(\"@crates//crates:BUILD.serde_yaml-0.9.34+deprecated.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__shlex-1.3.0\",\n sha256 = \"0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/shlex/1.3.0/download\"],\n strip_prefix = \"shlex-1.3.0\",\n build_file = Label(\"@crates//crates:BUILD.shlex-1.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__smol_str-0.3.6\",\n sha256 = \"4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/smol_str/0.3.6/download\"],\n strip_prefix = \"smol_str-0.3.6\",\n build_file = Label(\"@crates//crates:BUILD.smol_str-0.3.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__statrs-0.18.0\",\n sha256 = \"2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/statrs/0.18.0/download\"],\n strip_prefix = \"statrs-0.18.0\",\n build_file = Label(\"@crates//crates:BUILD.statrs-0.18.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__strsim-0.11.1\",\n sha256 = \"7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/strsim/0.11.1/download\"],\n strip_prefix = \"strsim-0.11.1\",\n build_file = Label(\"@crates//crates:BUILD.strsim-0.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__strum-0.28.0\",\n sha256 = \"9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/strum/0.28.0/download\"],\n strip_prefix = \"strum-0.28.0\",\n build_file = Label(\"@crates//crates:BUILD.strum-0.28.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__strum_macros-0.28.0\",\n sha256 = \"ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/strum_macros/0.28.0/download\"],\n strip_prefix = \"strum_macros-0.28.0\",\n build_file = Label(\"@crates//crates:BUILD.strum_macros-0.28.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__syn-2.0.117\",\n sha256 = \"e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/syn/2.0.117/download\"],\n strip_prefix = \"syn-2.0.117\",\n build_file = Label(\"@crates//crates:BUILD.syn-2.0.117.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__target-lexicon-0.13.5\",\n sha256 = \"adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/target-lexicon/0.13.5/download\"],\n strip_prefix = \"target-lexicon-0.13.5\",\n build_file = Label(\"@crates//crates:BUILD.target-lexicon-0.13.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__tempfile-3.27.0\",\n sha256 = \"32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tempfile/3.27.0/download\"],\n strip_prefix = \"tempfile-3.27.0\",\n build_file = Label(\"@crates//crates:BUILD.tempfile-3.27.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__termtree-0.5.1\",\n sha256 = \"8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/termtree/0.5.1/download\"],\n strip_prefix = \"termtree-0.5.1\",\n build_file = Label(\"@crates//crates:BUILD.termtree-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__text-size-1.1.1\",\n sha256 = \"f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/text-size/1.1.1/download\"],\n strip_prefix = \"text-size-1.1.1\",\n build_file = Label(\"@crates//crates:BUILD.text-size-1.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__thiserror-2.0.18\",\n sha256 = \"4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thiserror/2.0.18/download\"],\n strip_prefix = \"thiserror-2.0.18\",\n build_file = Label(\"@crates//crates:BUILD.thiserror-2.0.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__thiserror-impl-2.0.18\",\n sha256 = \"ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thiserror-impl/2.0.18/download\"],\n strip_prefix = \"thiserror-impl-2.0.18\",\n build_file = Label(\"@crates//crates:BUILD.thiserror-impl-2.0.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__time-0.3.47\",\n sha256 = \"743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/time/0.3.47/download\"],\n strip_prefix = \"time-0.3.47\",\n build_file = Label(\"@crates//crates:BUILD.time-0.3.47.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__time-core-0.1.8\",\n sha256 = \"7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/time-core/0.1.8/download\"],\n strip_prefix = \"time-core-0.1.8\",\n build_file = Label(\"@crates//crates:BUILD.time-core-0.1.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__tinytemplate-1.2.1\",\n sha256 = \"be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tinytemplate/1.2.1/download\"],\n strip_prefix = \"tinytemplate-1.2.1\",\n build_file = Label(\"@crates//crates:BUILD.tinytemplate-1.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__tinyvec-1.11.0\",\n sha256 = \"3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tinyvec/1.11.0/download\"],\n strip_prefix = \"tinyvec-1.11.0\",\n build_file = Label(\"@crates//crates:BUILD.tinyvec-1.11.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__tinyvec_macros-0.1.1\",\n sha256 = \"1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tinyvec_macros/0.1.1/download\"],\n strip_prefix = \"tinyvec_macros-0.1.1\",\n build_file = Label(\"@crates//crates:BUILD.tinyvec_macros-0.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__toml-0.9.12-spec-1.1.0\",\n sha256 = \"cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml/0.9.12+spec-1.1.0/download\"],\n strip_prefix = \"toml-0.9.12+spec-1.1.0\",\n build_file = Label(\"@crates//crates:BUILD.toml-0.9.12+spec-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__toml_datetime-0.7.5-spec-1.1.0\",\n sha256 = \"92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_datetime/0.7.5+spec-1.1.0/download\"],\n strip_prefix = \"toml_datetime-0.7.5+spec-1.1.0\",\n build_file = Label(\"@crates//crates:BUILD.toml_datetime-0.7.5+spec-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__toml_parser-1.1.2-spec-1.1.0\",\n sha256 = \"a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_parser/1.1.2+spec-1.1.0/download\"],\n strip_prefix = \"toml_parser-1.1.2+spec-1.1.0\",\n build_file = Label(\"@crates//crates:BUILD.toml_parser-1.1.2+spec-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__toml_writer-1.1.1-spec-1.1.0\",\n sha256 = \"756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_writer/1.1.1+spec-1.1.0/download\"],\n strip_prefix = \"toml_writer-1.1.1+spec-1.1.0\",\n build_file = Label(\"@crates//crates:BUILD.toml_writer-1.1.1+spec-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__unicode-ident-1.0.24\",\n sha256 = \"e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-ident/1.0.24/download\"],\n strip_prefix = \"unicode-ident-1.0.24\",\n build_file = Label(\"@crates//crates:BUILD.unicode-ident-1.0.24.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__unicode-xid-0.2.6\",\n sha256 = \"ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-xid/0.2.6/download\"],\n strip_prefix = \"unicode-xid-0.2.6\",\n build_file = Label(\"@crates//crates:BUILD.unicode-xid-0.2.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__unsafe-libyaml-0.2.11\",\n sha256 = \"673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unsafe-libyaml/0.2.11/download\"],\n strip_prefix = \"unsafe-libyaml-0.2.11\",\n build_file = Label(\"@crates//crates:BUILD.unsafe-libyaml-0.2.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__utf8parse-0.2.2\",\n sha256 = \"06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/utf8parse/0.2.2/download\"],\n strip_prefix = \"utf8parse-0.2.2\",\n build_file = Label(\"@crates//crates:BUILD.utf8parse-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wait-timeout-0.2.1\",\n sha256 = \"09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wait-timeout/0.2.1/download\"],\n strip_prefix = \"wait-timeout-0.2.1\",\n build_file = Label(\"@crates//crates:BUILD.wait-timeout-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__walkdir-2.5.0\",\n sha256 = \"29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/walkdir/2.5.0/download\"],\n strip_prefix = \"walkdir-2.5.0\",\n build_file = Label(\"@crates//crates:BUILD.walkdir-2.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasi-0.11.1-wasi-snapshot-preview1\",\n sha256 = \"ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasi/0.11.1+wasi-snapshot-preview1/download\"],\n strip_prefix = \"wasi-0.11.1+wasi-snapshot-preview1\",\n build_file = Label(\"@crates//crates:BUILD.wasi-0.11.1+wasi-snapshot-preview1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasip2-1.0.2-wasi-0.2.9\",\n sha256 = \"9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasip2/1.0.2+wasi-0.2.9/download\"],\n strip_prefix = \"wasip2-1.0.2+wasi-0.2.9\",\n build_file = Label(\"@crates//crates:BUILD.wasip2-1.0.2+wasi-0.2.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasip3-0.4.0-wasi-0.3.0-rc-2026-01-06\",\n sha256 = \"5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasip3/0.4.0+wasi-0.3.0-rc-2026-01-06/download\"],\n strip_prefix = \"wasip3-0.4.0+wasi-0.3.0-rc-2026-01-06\",\n build_file = Label(\"@crates//crates:BUILD.wasip3-0.4.0+wasi-0.3.0-rc-2026-01-06.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasm-bindgen-0.2.105\",\n sha256 = \"da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen/0.2.105/download\"],\n strip_prefix = \"wasm-bindgen-0.2.105\",\n build_file = Label(\"@crates//crates:BUILD.wasm-bindgen-0.2.105.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasm-bindgen-macro-0.2.105\",\n sha256 = \"04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen-macro/0.2.105/download\"],\n strip_prefix = \"wasm-bindgen-macro-0.2.105\",\n build_file = Label(\"@crates//crates:BUILD.wasm-bindgen-macro-0.2.105.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasm-bindgen-macro-support-0.2.105\",\n sha256 = \"420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen-macro-support/0.2.105/download\"],\n strip_prefix = \"wasm-bindgen-macro-support-0.2.105\",\n build_file = Label(\"@crates//crates:BUILD.wasm-bindgen-macro-support-0.2.105.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasm-bindgen-shared-0.2.105\",\n sha256 = \"76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen-shared/0.2.105/download\"],\n strip_prefix = \"wasm-bindgen-shared-0.2.105\",\n build_file = Label(\"@crates//crates:BUILD.wasm-bindgen-shared-0.2.105.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasm-encoder-0.244.0\",\n sha256 = \"990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-encoder/0.244.0/download\"],\n strip_prefix = \"wasm-encoder-0.244.0\",\n build_file = Label(\"@crates//crates:BUILD.wasm-encoder-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasm-metadata-0.244.0\",\n sha256 = \"bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-metadata/0.244.0/download\"],\n strip_prefix = \"wasm-metadata-0.244.0\",\n build_file = Label(\"@crates//crates:BUILD.wasm-metadata-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasmparser-0.244.0\",\n sha256 = \"47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasmparser/0.244.0/download\"],\n strip_prefix = \"wasmparser-0.244.0\",\n build_file = Label(\"@crates//crates:BUILD.wasmparser-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__web-sys-0.3.82\",\n sha256 = \"3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/web-sys/0.3.82/download\"],\n strip_prefix = \"web-sys-0.3.82\",\n build_file = Label(\"@crates//crates:BUILD.web-sys-0.3.82.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__winapi-util-0.1.11\",\n sha256 = \"c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winapi-util/0.1.11/download\"],\n strip_prefix = \"winapi-util-0.1.11\",\n build_file = Label(\"@crates//crates:BUILD.winapi-util-0.1.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows-link-0.2.1\",\n sha256 = \"f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-link/0.2.1/download\"],\n strip_prefix = \"windows-link-0.2.1\",\n build_file = Label(\"@crates//crates:BUILD.windows-link-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows-sys-0.59.0\",\n sha256 = \"1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.59.0/download\"],\n strip_prefix = \"windows-sys-0.59.0\",\n build_file = Label(\"@crates//crates:BUILD.windows-sys-0.59.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows-sys-0.61.2\",\n sha256 = \"ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.61.2/download\"],\n strip_prefix = \"windows-sys-0.61.2\",\n build_file = Label(\"@crates//crates:BUILD.windows-sys-0.61.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows-targets-0.52.6\",\n sha256 = \"9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-targets/0.52.6/download\"],\n strip_prefix = \"windows-targets-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows-targets-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_aarch64_gnullvm-0.52.6\",\n sha256 = \"32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_gnullvm/0.52.6/download\"],\n strip_prefix = \"windows_aarch64_gnullvm-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_aarch64_gnullvm-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_aarch64_msvc-0.52.6\",\n sha256 = \"09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_msvc/0.52.6/download\"],\n strip_prefix = \"windows_aarch64_msvc-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_aarch64_msvc-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_i686_gnu-0.52.6\",\n sha256 = \"8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_gnu/0.52.6/download\"],\n strip_prefix = \"windows_i686_gnu-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_i686_gnu-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_i686_gnullvm-0.52.6\",\n sha256 = \"0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_gnullvm/0.52.6/download\"],\n strip_prefix = \"windows_i686_gnullvm-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_i686_gnullvm-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_i686_msvc-0.52.6\",\n sha256 = \"240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_msvc/0.52.6/download\"],\n strip_prefix = \"windows_i686_msvc-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_i686_msvc-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_x86_64_gnu-0.52.6\",\n sha256 = \"147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnu/0.52.6/download\"],\n strip_prefix = \"windows_x86_64_gnu-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_x86_64_gnu-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_x86_64_gnullvm-0.52.6\",\n sha256 = \"24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnullvm/0.52.6/download\"],\n strip_prefix = \"windows_x86_64_gnullvm-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_x86_64_gnullvm-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_x86_64_msvc-0.52.6\",\n sha256 = \"589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_msvc/0.52.6/download\"],\n strip_prefix = \"windows_x86_64_msvc-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_x86_64_msvc-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__winnow-0.7.15\",\n sha256 = \"df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winnow/0.7.15/download\"],\n strip_prefix = \"winnow-0.7.15\",\n build_file = Label(\"@crates//crates:BUILD.winnow-0.7.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__winnow-1.0.3\",\n sha256 = \"0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winnow/1.0.3/download\"],\n strip_prefix = \"winnow-1.0.3\",\n build_file = Label(\"@crates//crates:BUILD.winnow-1.0.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wit-bindgen-0.51.0\",\n sha256 = \"d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-bindgen/0.51.0/download\"],\n strip_prefix = \"wit-bindgen-0.51.0\",\n build_file = Label(\"@crates//crates:BUILD.wit-bindgen-0.51.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wit-bindgen-core-0.51.0\",\n sha256 = \"ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-bindgen-core/0.51.0/download\"],\n strip_prefix = \"wit-bindgen-core-0.51.0\",\n build_file = Label(\"@crates//crates:BUILD.wit-bindgen-core-0.51.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wit-bindgen-rust-0.51.0\",\n sha256 = \"b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-bindgen-rust/0.51.0/download\"],\n strip_prefix = \"wit-bindgen-rust-0.51.0\",\n build_file = Label(\"@crates//crates:BUILD.wit-bindgen-rust-0.51.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wit-bindgen-rust-macro-0.51.0\",\n sha256 = \"0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-bindgen-rust-macro/0.51.0/download\"],\n strip_prefix = \"wit-bindgen-rust-macro-0.51.0\",\n build_file = Label(\"@crates//crates:BUILD.wit-bindgen-rust-macro-0.51.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wit-component-0.244.0\",\n sha256 = \"9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-component/0.244.0/download\"],\n strip_prefix = \"wit-component-0.244.0\",\n build_file = Label(\"@crates//crates:BUILD.wit-component-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wit-parser-0.244.0\",\n sha256 = \"ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-parser/0.244.0/download\"],\n strip_prefix = \"wit-parser-0.244.0\",\n build_file = Label(\"@crates//crates:BUILD.wit-parser-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__yansi-1.0.1\",\n sha256 = \"cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/yansi/1.0.1/download\"],\n strip_prefix = \"yansi-1.0.1\",\n build_file = Label(\"@crates//crates:BUILD.yansi-1.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__zerocopy-0.8.46\",\n sha256 = \"5c5030500cb2d66bdfbb4ebc9563be6ce7005a4b5d0f26be0c523870fe372ca6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerocopy/0.8.46/download\"],\n strip_prefix = \"zerocopy-0.8.46\",\n build_file = Label(\"@crates//crates:BUILD.zerocopy-0.8.46.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__zerocopy-derive-0.8.46\",\n sha256 = \"a5f86989a046a79640b9d8867c823349a139367bda96549794fcc3313ce91f4e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerocopy-derive/0.8.46/download\"],\n strip_prefix = \"zerocopy-derive-0.8.46\",\n build_file = Label(\"@crates//crates:BUILD.zerocopy-derive-0.8.46.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__zmij-1.0.21\",\n sha256 = \"b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zmij/1.0.21/download\"],\n strip_prefix = \"zmij-1.0.21\",\n build_file = Label(\"@crates//crates:BUILD.zmij-1.0.21.bazel\"),\n )\n\n return [\n struct(repo=\"crates__anstyle-1.0.14\", is_dev_dep = False),\n struct(repo=\"crates__append-only-vec-0.1.8\", is_dev_dep = False),\n struct(repo=\"crates__clap-4.6.1\", is_dev_dep = False),\n struct(repo=\"crates__common-path-1.0.0\", is_dev_dep = False),\n struct(repo=\"crates__configparser-3.2.0\", is_dev_dep = False),\n struct(repo=\"crates__console_error_panic_hook-0.1.7\", is_dev_dep = False),\n struct(repo=\"crates__enum_dispatch-0.3.13\", is_dev_dep = False),\n struct(repo=\"crates__fancy-regex-0.18.0\", is_dev_dep = False),\n struct(repo=\"crates__fern-0.7.1\", is_dev_dep = False),\n struct(repo=\"crates__getrandom-0.2.17\", is_dev_dep = False),\n struct(repo=\"crates__hashbrown-0.17.1\", is_dev_dep = False),\n struct(repo=\"crates__ignore-0.4.27\", is_dev_dep = False),\n struct(repo=\"crates__indexmap-2.14.0\", is_dev_dep = False),\n struct(repo=\"crates__itertools-0.15.0\", is_dev_dep = False),\n struct(repo=\"crates__js-sys-0.3.82\", is_dev_dep = False),\n struct(repo=\"crates__lazy-regex-3.6.0\", is_dev_dep = False),\n struct(repo=\"crates__line-index-0.1.2\", is_dev_dep = False),\n struct(repo=\"crates__log-0.4.33\", is_dev_dep = False),\n struct(repo=\"crates__lsp-server-0.8.0\", is_dev_dep = False),\n struct(repo=\"crates__lsp-types-0.97.0\", is_dev_dep = False),\n struct(repo=\"crates__mimalloc-0.1.52\", is_dev_dep = False),\n struct(repo=\"crates__nohash-hasher-0.2.0\", is_dev_dep = False),\n struct(repo=\"crates__pretty_assertions-1.4.1\", is_dev_dep = False),\n struct(repo=\"crates__pyo3-0.29.0\", is_dev_dep = False),\n struct(repo=\"crates__rayon-1.12.0\", is_dev_dep = False),\n struct(repo=\"crates__regex-1.12.4\", is_dev_dep = False),\n struct(repo=\"crates__regex-automata-0.4.14\", is_dev_dep = False),\n struct(repo=\"crates__serde-1.0.228\", is_dev_dep = False),\n struct(repo=\"crates__serde-wasm-bindgen-0.6.5\", is_dev_dep = False),\n struct(repo=\"crates__serde_json-1.0.150\", is_dev_dep = False),\n struct(repo=\"crates__serde_yaml-0.9.34-deprecated\", is_dev_dep = False),\n struct(repo=\"crates__smol_str-0.3.6\", is_dev_dep = False),\n struct(repo=\"crates__strum-0.28.0\", is_dev_dep = False),\n struct(repo=\"crates__strum_macros-0.28.0\", is_dev_dep = False),\n struct(repo=\"crates__thiserror-2.0.18\", is_dev_dep = False),\n struct(repo=\"crates__toml-0.9.12-spec-1.1.0\", is_dev_dep = False),\n struct(repo=\"crates__walkdir-2.5.0\", is_dev_dep = False),\n struct(repo=\"crates__wasm-bindgen-0.2.105\", is_dev_dep = False),\n struct(repo = \"crates__assert_cmd-2.2.2\", is_dev_dep = True),\n struct(repo = \"crates__clap-markdown-0.1.5\", is_dev_dep = True),\n struct(repo = \"crates__codspeed-criterion-compat-4.4.1\", is_dev_dep = True),\n struct(repo = \"crates__expect-test-1.5.1\", is_dev_dep = True),\n struct(repo = \"crates__glob-0.3.3\", is_dev_dep = True),\n struct(repo = \"crates__minijinja-2.21.0\", is_dev_dep = True),\n struct(repo = \"crates__serde_with-3.21.0\", is_dev_dep = True),\n struct(repo = \"crates__tempfile-3.27.0\", is_dev_dep = True),\n ]\n" + "defs.bzl": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'sqruff'\n###############################################################################\n\"\"\"\n# `crates_repository` API\n\n- [aliases](#aliases)\n- [crate_deps](#crate_deps)\n- [all_crate_deps](#all_crate_deps)\n- [crate_repositories](#crate_repositories)\n\n\"\"\"\n\nload(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"new_git_repository\")\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\nload(\"@bazel_tools//tools/build_defs/repo:utils.bzl\", \"maybe\")\nload(\"@bazel_skylib//lib:selects.bzl\", \"selects\")\nload(\"@rules_rust//crate_universe/private:local_crate_mirror.bzl\", \"local_crate_mirror\")\n\n###############################################################################\n# MACROS API\n###############################################################################\n\n# An identifier that represent common dependencies (unconditional).\n_COMMON_CONDITION = \"\"\n\ndef _flatten_dependency_maps(all_dependency_maps):\n \"\"\"Flatten a list of dependency maps into one dictionary.\n\n Dependency maps have the following structure:\n\n ```python\n DEPENDENCIES_MAP = {\n # The first key in the map is a Bazel package\n # name of the workspace this file is defined in.\n \"workspace_member_package\": {\n\n # Not all dependencies are supported for all platforms.\n # the condition key is the condition required to be true\n # on the host platform.\n \"condition\": {\n\n # An alias to a crate target. # The label of the crate target the\n # Aliases are only crate names. # package name refers to.\n \"package_name\": \"@full//:label\",\n }\n }\n }\n ```\n\n Args:\n all_dependency_maps (list): A list of dicts as described above\n\n Returns:\n dict: A dictionary as described above\n \"\"\"\n dependencies = {}\n\n for workspace_deps_map in all_dependency_maps:\n for pkg_name, conditional_deps_map in workspace_deps_map.items():\n if pkg_name not in dependencies:\n non_frozen_map = dict()\n for key, values in conditional_deps_map.items():\n non_frozen_map.update({key: dict(values.items())})\n dependencies.setdefault(pkg_name, non_frozen_map)\n continue\n\n for condition, deps_map in conditional_deps_map.items():\n # If the condition has not been recorded, do so and continue\n if condition not in dependencies[pkg_name]:\n dependencies[pkg_name].setdefault(condition, dict(deps_map.items()))\n continue\n\n # Alert on any miss-matched dependencies\n inconsistent_entries = []\n for crate_name, crate_label in deps_map.items():\n existing = dependencies[pkg_name][condition].get(crate_name)\n if existing and existing != crate_label:\n inconsistent_entries.append((crate_name, existing, crate_label))\n dependencies[pkg_name][condition].update({crate_name: crate_label})\n\n return dependencies\n\ndef crate_deps(deps, package_name = None):\n \"\"\"Finds the fully qualified label of the requested crates for the package where this macro is called.\n\n Args:\n deps (list): The desired list of crate targets.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()`.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if not deps:\n return []\n\n if package_name == None:\n package_name = native.package_name()\n\n # Join both sets of dependencies\n dependencies = _flatten_dependency_maps([\n _NORMAL_DEPENDENCIES,\n _NORMAL_DEV_DEPENDENCIES,\n _PROC_MACRO_DEPENDENCIES,\n _PROC_MACRO_DEV_DEPENDENCIES,\n _BUILD_DEPENDENCIES,\n _BUILD_PROC_MACRO_DEPENDENCIES,\n ]).pop(package_name, {})\n\n # Combine all conditional packages so we can easily index over a flat list\n # TODO: Perhaps this should actually return select statements and maintain\n # the conditionals of the dependencies\n flat_deps = {}\n for deps_set in dependencies.values():\n for crate_name, crate_label in deps_set.items():\n flat_deps.update({crate_name: crate_label})\n\n missing_crates = []\n crate_targets = []\n for crate_target in deps:\n if crate_target not in flat_deps:\n missing_crates.append(crate_target)\n else:\n crate_targets.append(flat_deps[crate_target])\n\n if missing_crates:\n fail(\"Could not find crates `{}` among dependencies of `{}`. Available dependencies were `{}`\".format(\n missing_crates,\n package_name,\n dependencies,\n ))\n\n return crate_targets\n\ndef all_crate_deps(\n normal = False, \n normal_dev = False, \n proc_macro = False, \n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Finds the fully qualified label of all requested direct crate dependencies \\\n for the package where this macro is called.\n\n If no parameters are set, all normal dependencies are returned. Setting any one flag will\n otherwise impact the contents of the returned list.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_dependency_maps = []\n if normal:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n if normal_dev:\n all_dependency_maps.append(_NORMAL_DEV_DEPENDENCIES)\n if proc_macro:\n all_dependency_maps.append(_PROC_MACRO_DEPENDENCIES)\n if proc_macro_dev:\n all_dependency_maps.append(_PROC_MACRO_DEV_DEPENDENCIES)\n if build:\n all_dependency_maps.append(_BUILD_DEPENDENCIES)\n if build_proc_macro:\n all_dependency_maps.append(_BUILD_PROC_MACRO_DEPENDENCIES)\n\n # Default to always using normal dependencies\n if not all_dependency_maps:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n\n dependencies = _flatten_dependency_maps(all_dependency_maps).pop(package_name, None)\n\n if not dependencies:\n if dependencies == None:\n fail(\"Tried to get all_crate_deps for package \" + package_name + \" but that package had no Cargo.toml file\")\n else:\n return []\n\n crate_deps = list(dependencies.pop(_COMMON_CONDITION, {}).values())\n for condition, deps in dependencies.items():\n crate_deps += selects.with_or({\n tuple(_CONDITIONS[condition]): deps.values(),\n \"//conditions:default\": [],\n })\n\n return crate_deps\n\ndef aliases(\n normal = False,\n normal_dev = False,\n proc_macro = False,\n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Produces a map of Crate alias names to their original label\n\n If no dependency kinds are specified, `normal` and `proc_macro` are used by default.\n Setting any one flag will otherwise determine the contents of the returned dict.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n dict: The aliases of all associated packages\n \"\"\"\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_aliases_maps = []\n if normal:\n all_aliases_maps.append(_NORMAL_ALIASES)\n if normal_dev:\n all_aliases_maps.append(_NORMAL_DEV_ALIASES)\n if proc_macro:\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n if proc_macro_dev:\n all_aliases_maps.append(_PROC_MACRO_DEV_ALIASES)\n if build:\n all_aliases_maps.append(_BUILD_ALIASES)\n if build_proc_macro:\n all_aliases_maps.append(_BUILD_PROC_MACRO_ALIASES)\n\n # Default to always using normal aliases\n if not all_aliases_maps:\n all_aliases_maps.append(_NORMAL_ALIASES)\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n\n aliases = _flatten_dependency_maps(all_aliases_maps).pop(package_name, None)\n\n if not aliases:\n return dict()\n\n common_items = aliases.pop(_COMMON_CONDITION, {}).items()\n\n # If there are only common items in the dictionary, immediately return them\n if not len(aliases.keys()) == 1:\n return dict(common_items)\n\n # Build a single select statement where each conditional has accounted for the\n # common set of aliases.\n crate_aliases = {\"//conditions:default\": dict(common_items)}\n for condition, deps in aliases.items():\n condition_triples = _CONDITIONS[condition]\n for triple in condition_triples:\n if triple in crate_aliases:\n crate_aliases[triple].update(deps)\n else:\n crate_aliases.update({triple: dict(deps.items() + common_items)})\n\n return select(crate_aliases)\n\n###############################################################################\n# WORKSPACE MEMBER DEPS AND ALIASES\n###############################################################################\n\n_NORMAL_DEPENDENCIES = {\n \"crates/lineage\": {\n _COMMON_CONDITION: {\n \"append-only-vec\": Label(\"@crates//:append-only-vec-0.1.8\"),\n \"hashbrown\": Label(\"@crates//:hashbrown-0.17.1\"),\n \"indexmap\": Label(\"@crates//:indexmap-2.14.0\"),\n },\n },\n \"crates/cli\": {\n \"x86_64-pc-windows-msvc\": {\n \"mimalloc\": Label(\"@crates//:mimalloc-0.1.52\"),\n },\n },\n \"crates/cli-lib\": {\n _COMMON_CONDITION: {\n \"anstyle\": Label(\"@crates//:anstyle-1.0.14\"),\n \"clap\": Label(\"@crates//:clap-4.6.1\"),\n \"fern\": Label(\"@crates//:fern-0.7.1\"),\n \"ignore\": Label(\"@crates//:ignore-0.4.27\"),\n \"log\": Label(\"@crates//:log-0.4.33\"),\n \"pyo3\": Label(\"@crates//:pyo3-0.29.0\"),\n \"serde\": Label(\"@crates//:serde-1.0.228\"),\n \"serde_json\": Label(\"@crates//:serde_json-1.0.150\"),\n \"strum\": Label(\"@crates//:strum-0.28.0\"),\n },\n },\n \"crates/cli-python\": {\n _COMMON_CONDITION: {\n \"pyo3\": Label(\"@crates//:pyo3-0.29.0\"),\n },\n },\n \"crates/lib\": {\n _COMMON_CONDITION: {\n \"common-path\": Label(\"@crates//:common-path-1.0.0\"),\n \"configparser\": Label(\"@crates//:configparser-3.2.0\"),\n \"fancy-regex\": Label(\"@crates//:fancy-regex-0.18.0\"),\n \"hashbrown\": Label(\"@crates//:hashbrown-0.17.1\"),\n \"ignore\": Label(\"@crates//:ignore-0.4.27\"),\n \"indexmap\": Label(\"@crates//:indexmap-2.14.0\"),\n \"itertools\": Label(\"@crates//:itertools-0.15.0\"),\n \"lazy-regex\": Label(\"@crates//:lazy-regex-3.6.0\"),\n \"log\": Label(\"@crates//:log-0.4.33\"),\n \"nohash-hasher\": Label(\"@crates//:nohash-hasher-0.2.0\"),\n \"pretty_assertions\": Label(\"@crates//:pretty_assertions-1.4.1\"),\n \"pyo3\": Label(\"@crates//:pyo3-0.29.0\"),\n \"rayon\": Label(\"@crates//:rayon-1.12.0\"),\n \"regex\": Label(\"@crates//:regex-1.12.4\"),\n \"serde\": Label(\"@crates//:serde-1.0.228\"),\n \"serde_json\": Label(\"@crates//:serde_json-1.0.150\"),\n \"smol_str\": Label(\"@crates//:smol_str-0.3.6\"),\n \"strum\": Label(\"@crates//:strum-0.28.0\"),\n \"thiserror\": Label(\"@crates//:thiserror-2.0.18\"),\n \"toml\": Label(\"@crates//:toml-0.9.12+spec-1.1.0\"),\n },\n \"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\": {\n \"getrandom\": Label(\"@crates//:getrandom-0.2.17\"),\n },\n },\n \"crates/lib-core\": {\n _COMMON_CONDITION: {\n \"fancy-regex\": Label(\"@crates//:fancy-regex-0.18.0\"),\n \"hashbrown\": Label(\"@crates//:hashbrown-0.17.1\"),\n \"indexmap\": Label(\"@crates//:indexmap-2.14.0\"),\n \"itertools\": Label(\"@crates//:itertools-0.15.0\"),\n \"log\": Label(\"@crates//:log-0.4.33\"),\n \"nohash-hasher\": Label(\"@crates//:nohash-hasher-0.2.0\"),\n \"pretty_assertions\": Label(\"@crates//:pretty_assertions-1.4.1\"),\n \"regex-automata\": Label(\"@crates//:regex-automata-0.4.14\"),\n \"serde\": Label(\"@crates//:serde-1.0.228\"),\n \"serde_yaml\": Label(\"@crates//:serde_yaml-0.9.34+deprecated\"),\n \"smol_str\": Label(\"@crates//:smol_str-0.3.6\"),\n \"strum\": Label(\"@crates//:strum-0.28.0\"),\n \"thiserror\": Label(\"@crates//:thiserror-2.0.18\"),\n },\n },\n \"crates/lib-dialects\": {\n _COMMON_CONDITION: {\n \"hashbrown\": Label(\"@crates//:hashbrown-0.17.1\"),\n \"itertools\": Label(\"@crates//:itertools-0.15.0\"),\n \"serde_yaml\": Label(\"@crates//:serde_yaml-0.9.34+deprecated\"),\n \"strum\": Label(\"@crates//:strum-0.28.0\"),\n },\n },\n \"crates/lsp\": {\n _COMMON_CONDITION: {\n \"console_error_panic_hook\": Label(\"@crates//:console_error_panic_hook-0.1.7\"),\n \"hashbrown\": Label(\"@crates//:hashbrown-0.17.1\"),\n \"js-sys\": Label(\"@crates//:js-sys-0.3.82\"),\n \"lsp-server\": Label(\"@crates//:lsp-server-0.8.0\"),\n \"lsp-types\": Label(\"@crates//:lsp-types-0.97.0\"),\n \"serde-wasm-bindgen\": Label(\"@crates//:serde-wasm-bindgen-0.6.5\"),\n \"serde_json\": Label(\"@crates//:serde_json-1.0.150\"),\n \"wasm-bindgen\": Label(\"@crates//:wasm-bindgen-0.2.105\"),\n },\n },\n \"crates/sqlinference\": {\n _COMMON_CONDITION: {\n \"hashbrown\": Label(\"@crates//:hashbrown-0.17.1\"),\n },\n },\n \"crates/lib-wasm\": {\n _COMMON_CONDITION: {\n \"serde\": Label(\"@crates//:serde-1.0.228\"),\n \"serde_yaml\": Label(\"@crates//:serde_yaml-0.9.34+deprecated\"),\n \"wasm-bindgen\": Label(\"@crates//:wasm-bindgen-0.2.105\"),\n },\n },\n}\n\n\n_NORMAL_ALIASES = {\n \"crates/lineage\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli\": {\n \"x86_64-pc-windows-msvc\": {\n },\n },\n \"crates/cli-lib\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli-python\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lib\": {\n _COMMON_CONDITION: {\n },\n \"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\": {\n },\n },\n \"crates/lib-core\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lib-dialects\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lsp\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/sqlinference\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lib-wasm\": {\n _COMMON_CONDITION: {\n },\n },\n}\n\n\n_NORMAL_DEV_DEPENDENCIES = {\n \"crates/lineage\": {\n _COMMON_CONDITION: {\n \"strum\": Label(\"@crates//:strum-0.28.0\"),\n },\n },\n \"crates/cli\": {\n _COMMON_CONDITION: {\n \"assert_cmd\": Label(\"@crates//:assert_cmd-2.2.2\"),\n \"expect-test\": Label(\"@crates//:expect-test-1.5.1\"),\n \"tempfile\": Label(\"@crates//:tempfile-3.27.0\"),\n },\n },\n \"crates/cli-lib\": {\n _COMMON_CONDITION: {\n \"assert_cmd\": Label(\"@crates//:assert_cmd-2.2.2\"),\n \"clap-markdown\": Label(\"@crates//:clap-markdown-0.1.5\"),\n \"expect-test\": Label(\"@crates//:expect-test-1.5.1\"),\n \"fancy-regex\": Label(\"@crates//:fancy-regex-0.18.0\"),\n \"minijinja\": Label(\"@crates//:minijinja-2.21.0\"),\n \"tempfile\": Label(\"@crates//:tempfile-3.27.0\"),\n },\n },\n \"crates/cli-python\": {\n _COMMON_CONDITION: {\n \"assert_cmd\": Label(\"@crates//:assert_cmd-2.2.2\"),\n \"expect-test\": Label(\"@crates//:expect-test-1.5.1\"),\n \"tempfile\": Label(\"@crates//:tempfile-3.27.0\"),\n },\n },\n \"crates/lib\": {\n _COMMON_CONDITION: {\n \"criterion\": Label(\"@crates//:codspeed-criterion-compat-4.4.1\"),\n \"expect-test\": Label(\"@crates//:expect-test-1.5.1\"),\n \"glob\": Label(\"@crates//:glob-0.3.3\"),\n \"serde_with\": Label(\"@crates//:serde_with-3.21.0\"),\n \"serde_yaml\": Label(\"@crates//:serde_yaml-0.9.34+deprecated\"),\n },\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n _COMMON_CONDITION: {\n \"configparser\": Label(\"@crates//:configparser-3.2.0\"),\n \"expect-test\": Label(\"@crates//:expect-test-1.5.1\"),\n \"glob\": Label(\"@crates//:glob-0.3.3\"),\n \"rayon\": Label(\"@crates//:rayon-1.12.0\"),\n },\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_NORMAL_DEV_ALIASES = {\n \"crates/lineage\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli-lib\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli-python\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lib\": {\n _COMMON_CONDITION: {\n Label(\"@crates//:codspeed-criterion-compat-4.4.1\"): \"criterion\",\n },\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_PROC_MACRO_DEPENDENCIES = {\n \"crates/lineage\": {\n },\n \"crates/cli\": {\n },\n \"crates/cli-lib\": {\n _COMMON_CONDITION: {\n \"strum_macros\": Label(\"@crates//:strum_macros-0.28.0\"),\n },\n },\n \"crates/cli-python\": {\n },\n \"crates/lib\": {\n _COMMON_CONDITION: {\n \"enum_dispatch\": Label(\"@crates//:enum_dispatch-0.3.13\"),\n \"strum_macros\": Label(\"@crates//:strum_macros-0.28.0\"),\n },\n },\n \"crates/lib-core\": {\n _COMMON_CONDITION: {\n \"enum_dispatch\": Label(\"@crates//:enum_dispatch-0.3.13\"),\n \"strum_macros\": Label(\"@crates//:strum_macros-0.28.0\"),\n },\n },\n \"crates/lib-dialects\": {\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_PROC_MACRO_ALIASES = {\n \"crates/lineage\": {\n },\n \"crates/cli\": {\n },\n \"crates/cli-lib\": {\n },\n \"crates/cli-python\": {\n },\n \"crates/lib\": {\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_PROC_MACRO_DEV_DEPENDENCIES = {\n \"crates/lineage\": {\n },\n \"crates/cli\": {\n },\n \"crates/cli-lib\": {\n },\n \"crates/cli-python\": {\n },\n \"crates/lib\": {\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_PROC_MACRO_DEV_ALIASES = {\n \"crates/lineage\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli-lib\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/cli-python\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lib\": {\n _COMMON_CONDITION: {\n Label(\"@crates//:codspeed-criterion-compat-4.4.1\"): \"criterion\",\n },\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n _COMMON_CONDITION: {\n },\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_BUILD_DEPENDENCIES = {\n \"crates/lineage\": {\n },\n \"crates/cli\": {\n },\n \"crates/cli-lib\": {\n },\n \"crates/cli-python\": {\n },\n \"crates/lib\": {\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_BUILD_ALIASES = {\n \"crates/lineage\": {\n },\n \"crates/cli\": {\n },\n \"crates/cli-lib\": {\n },\n \"crates/cli-python\": {\n },\n \"crates/lib\": {\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_DEPENDENCIES = {\n \"crates/lineage\": {\n },\n \"crates/cli\": {\n },\n \"crates/cli-lib\": {\n },\n \"crates/cli-python\": {\n },\n \"crates/lib\": {\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_ALIASES = {\n \"crates/lineage\": {\n },\n \"crates/cli\": {\n },\n \"crates/cli-lib\": {\n },\n \"crates/cli-python\": {\n },\n \"crates/lib\": {\n },\n \"crates/lib-core\": {\n },\n \"crates/lib-dialects\": {\n },\n \"crates/lsp\": {\n },\n \"crates/sqlinference\": {\n },\n \"crates/lib-wasm\": {\n },\n}\n\n\n_CONDITIONS = {\n \"aarch64-apple-darwin\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"aarch64-pc-windows-gnullvm\": [],\n \"aarch64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"cfg(all(any(target_arch = \\\"x86_64\\\", target_arch = \\\"arm64ec\\\"), target_env = \\\"msvc\\\", not(windows_raw_dylib)))\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\": [],\n \"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), not(any(all(target_os = \\\"linux\\\", target_env = \\\"\\\"), getrandom_backend = \\\"custom\\\", getrandom_backend = \\\"linux_raw\\\", getrandom_backend = \\\"rdrand\\\", getrandom_backend = \\\"rndr\\\"))))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(all(target_arch = \\\"aarch64\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\": [],\n \"cfg(all(target_arch = \\\"wasm32\\\", not(target_os = \\\"wasi\\\")))\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\", target_env = \\\"p2\\\"))\": [],\n \"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\", target_env = \\\"p3\\\"))\": [],\n \"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\": [],\n \"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\": [],\n \"cfg(all(target_arch = \\\"x86_64\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\": [\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(all(target_os = \\\"uefi\\\", getrandom_backend = \\\"efi_rng\\\"))\": [],\n \"cfg(any())\": [],\n \"cfg(any(target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"hurd\\\", target_os = \\\"illumos\\\", target_os = \\\"cygwin\\\", all(target_os = \\\"horizon\\\", target_arch = \\\"arm\\\")))\": [],\n \"cfg(any(target_os = \\\"haiku\\\", target_os = \\\"redox\\\", target_os = \\\"nto\\\", target_os = \\\"aix\\\"))\": [],\n \"cfg(any(target_os = \\\"ios\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\", target_os = \\\"tvos\\\"))\": [],\n \"cfg(any(target_os = \\\"macos\\\", target_os = \\\"openbsd\\\", target_os = \\\"vita\\\", target_os = \\\"emscripten\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(any(unix, target_os = \\\"wasi\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:wasm32-wasip1\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(target_has_atomic = \\\"64\\\"))\": [],\n \"cfg(target_arch = \\\"spirv\\\")\": [],\n \"cfg(target_os = \\\"hermit\\\")\": [],\n \"cfg(target_os = \\\"netbsd\\\")\": [],\n \"cfg(target_os = \\\"solaris\\\")\": [],\n \"cfg(target_os = \\\"vxworks\\\")\": [],\n \"cfg(target_os = \\\"wasi\\\")\": [\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(unix)\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(windows)\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"i686-pc-windows-gnullvm\": [],\n \"wasm32-unknown-unknown\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"wasm32-wasip1\": [\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"x86_64-pc-windows-gnullvm\": [],\n \"x86_64-pc-windows-msvc\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"x86_64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"x86_64-unknown-nixos-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n}\n\n###############################################################################\n\ndef crate_repositories():\n \"\"\"A macro for defining repositories for all generated crates.\n\n Returns:\n A list of repos visible to the module through the module extension.\n \"\"\"\n maybe(\n http_archive,\n name = \"crates__aho-corasick-1.1.4\",\n sha256 = \"ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aho-corasick/1.1.4/download\"],\n strip_prefix = \"aho-corasick-1.1.4\",\n build_file = Label(\"@crates//crates:BUILD.aho-corasick-1.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__allocator-api2-0.2.21\",\n sha256 = \"683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/allocator-api2/0.2.21/download\"],\n strip_prefix = \"allocator-api2-0.2.21\",\n build_file = Label(\"@crates//crates:BUILD.allocator-api2-0.2.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__anes-0.1.6\",\n sha256 = \"4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anes/0.1.6/download\"],\n strip_prefix = \"anes-0.1.6\",\n build_file = Label(\"@crates//crates:BUILD.anes-0.1.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__anstream-1.0.0\",\n sha256 = \"824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstream/1.0.0/download\"],\n strip_prefix = \"anstream-1.0.0\",\n build_file = Label(\"@crates//crates:BUILD.anstream-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__anstyle-1.0.14\",\n sha256 = \"940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle/1.0.14/download\"],\n strip_prefix = \"anstyle-1.0.14\",\n build_file = Label(\"@crates//crates:BUILD.anstyle-1.0.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__anstyle-parse-1.0.0\",\n sha256 = \"52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-parse/1.0.0/download\"],\n strip_prefix = \"anstyle-parse-1.0.0\",\n build_file = Label(\"@crates//crates:BUILD.anstyle-parse-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__anstyle-query-1.1.5\",\n sha256 = \"40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-query/1.1.5/download\"],\n strip_prefix = \"anstyle-query-1.1.5\",\n build_file = Label(\"@crates//crates:BUILD.anstyle-query-1.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__anstyle-wincon-3.0.11\",\n sha256 = \"291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-wincon/3.0.11/download\"],\n strip_prefix = \"anstyle-wincon-3.0.11\",\n build_file = Label(\"@crates//crates:BUILD.anstyle-wincon-3.0.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__anyhow-1.0.102\",\n sha256 = \"7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anyhow/1.0.102/download\"],\n strip_prefix = \"anyhow-1.0.102\",\n build_file = Label(\"@crates//crates:BUILD.anyhow-1.0.102.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__append-only-vec-0.1.8\",\n sha256 = \"2114736faba96bcd79595c700d03183f61357b9fbce14852515e59f3bee4ed4a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/append-only-vec/0.1.8/download\"],\n strip_prefix = \"append-only-vec-0.1.8\",\n build_file = Label(\"@crates//crates:BUILD.append-only-vec-0.1.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__approx-0.5.1\",\n sha256 = \"cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/approx/0.5.1/download\"],\n strip_prefix = \"approx-0.5.1\",\n build_file = Label(\"@crates//crates:BUILD.approx-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__assert_cmd-2.2.2\",\n sha256 = \"2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/assert_cmd/2.2.2/download\"],\n strip_prefix = \"assert_cmd-2.2.2\",\n build_file = Label(\"@crates//crates:BUILD.assert_cmd-2.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__autocfg-1.5.0\",\n sha256 = \"c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/autocfg/1.5.0/download\"],\n strip_prefix = \"autocfg-1.5.0\",\n build_file = Label(\"@crates//crates:BUILD.autocfg-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__base64-0.22.1\",\n sha256 = \"72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/base64/0.22.1/download\"],\n strip_prefix = \"base64-0.22.1\",\n build_file = Label(\"@crates//crates:BUILD.base64-0.22.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bit-set-0.8.0\",\n sha256 = \"08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bit-set/0.8.0/download\"],\n strip_prefix = \"bit-set-0.8.0\",\n build_file = Label(\"@crates//crates:BUILD.bit-set-0.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bit-vec-0.8.0\",\n sha256 = \"5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bit-vec/0.8.0/download\"],\n strip_prefix = \"bit-vec-0.8.0\",\n build_file = Label(\"@crates//crates:BUILD.bit-vec-0.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bitflags-1.3.2\",\n sha256 = \"bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitflags/1.3.2/download\"],\n strip_prefix = \"bitflags-1.3.2\",\n build_file = Label(\"@crates//crates:BUILD.bitflags-1.3.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bitflags-2.11.0\",\n sha256 = \"843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitflags/2.11.0/download\"],\n strip_prefix = \"bitflags-2.11.0\",\n build_file = Label(\"@crates//crates:BUILD.bitflags-2.11.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__borsh-1.6.1\",\n sha256 = \"cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/borsh/1.6.1/download\"],\n strip_prefix = \"borsh-1.6.1\",\n build_file = Label(\"@crates//crates:BUILD.borsh-1.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bs58-0.5.1\",\n sha256 = \"bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bs58/0.5.1/download\"],\n strip_prefix = \"bs58-0.5.1\",\n build_file = Label(\"@crates//crates:BUILD.bs58-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bstr-1.12.1\",\n sha256 = \"63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bstr/1.12.1/download\"],\n strip_prefix = \"bstr-1.12.1\",\n build_file = Label(\"@crates//crates:BUILD.bstr-1.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bumpalo-3.20.2\",\n sha256 = \"5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bumpalo/3.20.2/download\"],\n strip_prefix = \"bumpalo-3.20.2\",\n build_file = Label(\"@crates//crates:BUILD.bumpalo-3.20.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__bytes-1.11.1\",\n sha256 = \"1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bytes/1.11.1/download\"],\n strip_prefix = \"bytes-1.11.1\",\n build_file = Label(\"@crates//crates:BUILD.bytes-1.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__cast-0.3.0\",\n sha256 = \"37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cast/0.3.0/download\"],\n strip_prefix = \"cast-0.3.0\",\n build_file = Label(\"@crates//crates:BUILD.cast-0.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__cc-1.2.57\",\n sha256 = \"7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cc/1.2.57/download\"],\n strip_prefix = \"cc-1.2.57\",\n build_file = Label(\"@crates//crates:BUILD.cc-1.2.57.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__cfg-if-1.0.4\",\n sha256 = \"9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cfg-if/1.0.4/download\"],\n strip_prefix = \"cfg-if-1.0.4\",\n build_file = Label(\"@crates//crates:BUILD.cfg-if-1.0.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__cfg_aliases-0.2.1\",\n sha256 = \"613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cfg_aliases/0.2.1/download\"],\n strip_prefix = \"cfg_aliases-0.2.1\",\n build_file = Label(\"@crates//crates:BUILD.cfg_aliases-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__chrono-0.4.44\",\n sha256 = \"c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/chrono/0.4.44/download\"],\n strip_prefix = \"chrono-0.4.44\",\n build_file = Label(\"@crates//crates:BUILD.chrono-0.4.44.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__ciborium-0.2.2\",\n sha256 = \"42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ciborium/0.2.2/download\"],\n strip_prefix = \"ciborium-0.2.2\",\n build_file = Label(\"@crates//crates:BUILD.ciborium-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__ciborium-io-0.2.2\",\n sha256 = \"05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ciborium-io/0.2.2/download\"],\n strip_prefix = \"ciborium-io-0.2.2\",\n build_file = Label(\"@crates//crates:BUILD.ciborium-io-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__ciborium-ll-0.2.2\",\n sha256 = \"57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ciborium-ll/0.2.2/download\"],\n strip_prefix = \"ciborium-ll-0.2.2\",\n build_file = Label(\"@crates//crates:BUILD.ciborium-ll-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__clap-4.6.1\",\n sha256 = \"1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap/4.6.1/download\"],\n strip_prefix = \"clap-4.6.1\",\n build_file = Label(\"@crates//crates:BUILD.clap-4.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__clap-markdown-0.1.5\",\n sha256 = \"d2a2617956a06d4885b490697b5307ebb09fec10b088afc18c81762d848c2339\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap-markdown/0.1.5/download\"],\n strip_prefix = \"clap-markdown-0.1.5\",\n build_file = Label(\"@crates//crates:BUILD.clap-markdown-0.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__clap_builder-4.6.0\",\n sha256 = \"714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_builder/4.6.0/download\"],\n strip_prefix = \"clap_builder-4.6.0\",\n build_file = Label(\"@crates//crates:BUILD.clap_builder-4.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__clap_derive-4.6.1\",\n sha256 = \"f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_derive/4.6.1/download\"],\n strip_prefix = \"clap_derive-4.6.1\",\n build_file = Label(\"@crates//crates:BUILD.clap_derive-4.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__clap_lex-1.1.0\",\n sha256 = \"c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_lex/1.1.0/download\"],\n strip_prefix = \"clap_lex-1.1.0\",\n build_file = Label(\"@crates//crates:BUILD.clap_lex-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__codspeed-4.4.1\",\n sha256 = \"b684e94583e85a5ca7e1a6454a89d76a5121240f2fb67eb564129d9bafdb9db0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/codspeed/4.4.1/download\"],\n strip_prefix = \"codspeed-4.4.1\",\n build_file = Label(\"@crates//crates:BUILD.codspeed-4.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__codspeed-criterion-compat-4.4.1\",\n sha256 = \"2e65444156eb73ad7f57618188f8d4a281726d133ef55b96d1dcff89528609ab\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/codspeed-criterion-compat/4.4.1/download\"],\n strip_prefix = \"codspeed-criterion-compat-4.4.1\",\n build_file = Label(\"@crates//crates:BUILD.codspeed-criterion-compat-4.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__codspeed-criterion-compat-walltime-4.4.1\",\n sha256 = \"96389aaa4bbb872ea4924dc0335b2bb181bcf28d6eedbe8fea29afcc5bde36a6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/codspeed-criterion-compat-walltime/4.4.1/download\"],\n strip_prefix = \"codspeed-criterion-compat-walltime-4.4.1\",\n build_file = Label(\"@crates//crates:BUILD.codspeed-criterion-compat-walltime-4.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__colorchoice-1.0.5\",\n sha256 = \"1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/colorchoice/1.0.5/download\"],\n strip_prefix = \"colorchoice-1.0.5\",\n build_file = Label(\"@crates//crates:BUILD.colorchoice-1.0.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__colored-2.2.0\",\n sha256 = \"117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/colored/2.2.0/download\"],\n strip_prefix = \"colored-2.2.0\",\n build_file = Label(\"@crates//crates:BUILD.colored-2.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__common-path-1.0.0\",\n sha256 = \"2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/common-path/1.0.0/download\"],\n strip_prefix = \"common-path-1.0.0\",\n build_file = Label(\"@crates//crates:BUILD.common-path-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__configparser-3.2.0\",\n sha256 = \"b46dec724fd22199ebde05033a0cbae453bc3b1ecff11eb6a6bb3eec4b90c6a4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/configparser/3.2.0/download\"],\n strip_prefix = \"configparser-3.2.0\",\n build_file = Label(\"@crates//crates:BUILD.configparser-3.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__console_error_panic_hook-0.1.7\",\n sha256 = \"a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/console_error_panic_hook/0.1.7/download\"],\n strip_prefix = \"console_error_panic_hook-0.1.7\",\n build_file = Label(\"@crates//crates:BUILD.console_error_panic_hook-0.1.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__criterion-plot-0.5.0\",\n sha256 = \"6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/criterion-plot/0.5.0/download\"],\n strip_prefix = \"criterion-plot-0.5.0\",\n build_file = Label(\"@crates//crates:BUILD.criterion-plot-0.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__crossbeam-channel-0.5.15\",\n sha256 = \"82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-channel/0.5.15/download\"],\n strip_prefix = \"crossbeam-channel-0.5.15\",\n build_file = Label(\"@crates//crates:BUILD.crossbeam-channel-0.5.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__crossbeam-deque-0.8.6\",\n sha256 = \"9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-deque/0.8.6/download\"],\n strip_prefix = \"crossbeam-deque-0.8.6\",\n build_file = Label(\"@crates//crates:BUILD.crossbeam-deque-0.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__crossbeam-epoch-0.9.18\",\n sha256 = \"5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-epoch/0.9.18/download\"],\n strip_prefix = \"crossbeam-epoch-0.9.18\",\n build_file = Label(\"@crates//crates:BUILD.crossbeam-epoch-0.9.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__crossbeam-utils-0.8.21\",\n sha256 = \"d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-utils/0.8.21/download\"],\n strip_prefix = \"crossbeam-utils-0.8.21\",\n build_file = Label(\"@crates//crates:BUILD.crossbeam-utils-0.8.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__crunchy-0.2.4\",\n sha256 = \"460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crunchy/0.2.4/download\"],\n strip_prefix = \"crunchy-0.2.4\",\n build_file = Label(\"@crates//crates:BUILD.crunchy-0.2.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__darling-0.23.0\",\n sha256 = \"25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/darling/0.23.0/download\"],\n strip_prefix = \"darling-0.23.0\",\n build_file = Label(\"@crates//crates:BUILD.darling-0.23.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__darling_core-0.23.0\",\n sha256 = \"9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/darling_core/0.23.0/download\"],\n strip_prefix = \"darling_core-0.23.0\",\n build_file = Label(\"@crates//crates:BUILD.darling_core-0.23.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__darling_macro-0.23.0\",\n sha256 = \"ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/darling_macro/0.23.0/download\"],\n strip_prefix = \"darling_macro-0.23.0\",\n build_file = Label(\"@crates//crates:BUILD.darling_macro-0.23.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__deranged-0.5.8\",\n sha256 = \"7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/deranged/0.5.8/download\"],\n strip_prefix = \"deranged-0.5.8\",\n build_file = Label(\"@crates//crates:BUILD.deranged-0.5.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__diff-0.1.13\",\n sha256 = \"56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/diff/0.1.13/download\"],\n strip_prefix = \"diff-0.1.13\",\n build_file = Label(\"@crates//crates:BUILD.diff-0.1.13.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__difflib-0.4.0\",\n sha256 = \"6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/difflib/0.4.0/download\"],\n strip_prefix = \"difflib-0.4.0\",\n build_file = Label(\"@crates//crates:BUILD.difflib-0.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__dissimilar-1.0.11\",\n sha256 = \"aeda16ab4059c5fd2a83f2b9c9e9c981327b18aa8e3b313f7e6563799d4f093e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/dissimilar/1.0.11/download\"],\n strip_prefix = \"dissimilar-1.0.11\",\n build_file = Label(\"@crates//crates:BUILD.dissimilar-1.0.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__either-1.15.0\",\n sha256 = \"48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/either/1.15.0/download\"],\n strip_prefix = \"either-1.15.0\",\n build_file = Label(\"@crates//crates:BUILD.either-1.15.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__enum_dispatch-0.3.13\",\n sha256 = \"aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/enum_dispatch/0.3.13/download\"],\n strip_prefix = \"enum_dispatch-0.3.13\",\n build_file = Label(\"@crates//crates:BUILD.enum_dispatch-0.3.13.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__equivalent-1.0.2\",\n sha256 = \"877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/equivalent/1.0.2/download\"],\n strip_prefix = \"equivalent-1.0.2\",\n build_file = Label(\"@crates//crates:BUILD.equivalent-1.0.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__errno-0.3.14\",\n sha256 = \"39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/errno/0.3.14/download\"],\n strip_prefix = \"errno-0.3.14\",\n build_file = Label(\"@crates//crates:BUILD.errno-0.3.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__expect-test-1.5.1\",\n sha256 = \"63af43ff4431e848fb47472a920f14fa71c24de13255a5692e93d4e90302acb0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/expect-test/1.5.1/download\"],\n strip_prefix = \"expect-test-1.5.1\",\n build_file = Label(\"@crates//crates:BUILD.expect-test-1.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__fancy-regex-0.18.0\",\n sha256 = \"e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fancy-regex/0.18.0/download\"],\n strip_prefix = \"fancy-regex-0.18.0\",\n build_file = Label(\"@crates//crates:BUILD.fancy-regex-0.18.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__fastrand-2.3.0\",\n sha256 = \"37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fastrand/2.3.0/download\"],\n strip_prefix = \"fastrand-2.3.0\",\n build_file = Label(\"@crates//crates:BUILD.fastrand-2.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__fern-0.7.1\",\n sha256 = \"4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fern/0.7.1/download\"],\n strip_prefix = \"fern-0.7.1\",\n build_file = Label(\"@crates//crates:BUILD.fern-0.7.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__find-msvc-tools-0.1.9\",\n sha256 = \"5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/find-msvc-tools/0.1.9/download\"],\n strip_prefix = \"find-msvc-tools-0.1.9\",\n build_file = Label(\"@crates//crates:BUILD.find-msvc-tools-0.1.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__fluent-uri-0.1.4\",\n sha256 = \"17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fluent-uri/0.1.4/download\"],\n strip_prefix = \"fluent-uri-0.1.4\",\n build_file = Label(\"@crates//crates:BUILD.fluent-uri-0.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__foldhash-0.1.5\",\n sha256 = \"d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/foldhash/0.1.5/download\"],\n strip_prefix = \"foldhash-0.1.5\",\n build_file = Label(\"@crates//crates:BUILD.foldhash-0.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__foldhash-0.2.0\",\n sha256 = \"77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/foldhash/0.2.0/download\"],\n strip_prefix = \"foldhash-0.2.0\",\n build_file = Label(\"@crates//crates:BUILD.foldhash-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__getrandom-0.2.17\",\n sha256 = \"ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/getrandom/0.2.17/download\"],\n strip_prefix = \"getrandom-0.2.17\",\n build_file = Label(\"@crates//crates:BUILD.getrandom-0.2.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__getrandom-0.4.2\",\n sha256 = \"0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/getrandom/0.4.2/download\"],\n strip_prefix = \"getrandom-0.4.2\",\n build_file = Label(\"@crates//crates:BUILD.getrandom-0.4.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__glob-0.3.3\",\n sha256 = \"0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/glob/0.3.3/download\"],\n strip_prefix = \"glob-0.3.3\",\n build_file = Label(\"@crates//crates:BUILD.glob-0.3.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__globset-0.4.18\",\n sha256 = \"52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/globset/0.4.18/download\"],\n strip_prefix = \"globset-0.4.18\",\n build_file = Label(\"@crates//crates:BUILD.globset-0.4.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__half-2.7.1\",\n sha256 = \"6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/half/2.7.1/download\"],\n strip_prefix = \"half-2.7.1\",\n build_file = Label(\"@crates//crates:BUILD.half-2.7.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__hashbrown-0.15.5\",\n sha256 = \"9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.15.5/download\"],\n strip_prefix = \"hashbrown-0.15.5\",\n build_file = Label(\"@crates//crates:BUILD.hashbrown-0.15.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__hashbrown-0.17.1\",\n sha256 = \"ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.17.1/download\"],\n strip_prefix = \"hashbrown-0.17.1\",\n build_file = Label(\"@crates//crates:BUILD.hashbrown-0.17.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__heck-0.5.0\",\n sha256 = \"2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/heck/0.5.0/download\"],\n strip_prefix = \"heck-0.5.0\",\n build_file = Label(\"@crates//crates:BUILD.heck-0.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__hermit-abi-0.5.2\",\n sha256 = \"fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hermit-abi/0.5.2/download\"],\n strip_prefix = \"hermit-abi-0.5.2\",\n build_file = Label(\"@crates//crates:BUILD.hermit-abi-0.5.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__hex-0.4.3\",\n sha256 = \"7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hex/0.4.3/download\"],\n strip_prefix = \"hex-0.4.3\",\n build_file = Label(\"@crates//crates:BUILD.hex-0.4.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__id-arena-2.3.0\",\n sha256 = \"3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/id-arena/2.3.0/download\"],\n strip_prefix = \"id-arena-2.3.0\",\n build_file = Label(\"@crates//crates:BUILD.id-arena-2.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__ident_case-1.0.1\",\n sha256 = \"b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ident_case/1.0.1/download\"],\n strip_prefix = \"ident_case-1.0.1\",\n build_file = Label(\"@crates//crates:BUILD.ident_case-1.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__ignore-0.4.27\",\n sha256 = \"fe112b004901c62c2faa11f4f75e9864e0cc5af8da71c9115d184a3aa888749f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ignore/0.4.27/download\"],\n strip_prefix = \"ignore-0.4.27\",\n build_file = Label(\"@crates//crates:BUILD.ignore-0.4.27.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__indexmap-2.14.0\",\n sha256 = \"d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/indexmap/2.14.0/download\"],\n strip_prefix = \"indexmap-2.14.0\",\n build_file = Label(\"@crates//crates:BUILD.indexmap-2.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__is-terminal-0.4.17\",\n sha256 = \"3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/is-terminal/0.4.17/download\"],\n strip_prefix = \"is-terminal-0.4.17\",\n build_file = Label(\"@crates//crates:BUILD.is-terminal-0.4.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__is_terminal_polyfill-1.70.2\",\n sha256 = \"a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/is_terminal_polyfill/1.70.2/download\"],\n strip_prefix = \"is_terminal_polyfill-1.70.2\",\n build_file = Label(\"@crates//crates:BUILD.is_terminal_polyfill-1.70.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__itertools-0.10.5\",\n sha256 = \"b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itertools/0.10.5/download\"],\n strip_prefix = \"itertools-0.10.5\",\n build_file = Label(\"@crates//crates:BUILD.itertools-0.10.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__itertools-0.15.0\",\n sha256 = \"8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itertools/0.15.0/download\"],\n strip_prefix = \"itertools-0.15.0\",\n build_file = Label(\"@crates//crates:BUILD.itertools-0.15.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__itoa-1.0.17\",\n sha256 = \"92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itoa/1.0.17/download\"],\n strip_prefix = \"itoa-1.0.17\",\n build_file = Label(\"@crates//crates:BUILD.itoa-1.0.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__js-sys-0.3.82\",\n sha256 = \"b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/js-sys/0.3.82/download\"],\n strip_prefix = \"js-sys-0.3.82\",\n build_file = Label(\"@crates//crates:BUILD.js-sys-0.3.82.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__lazy-regex-3.6.0\",\n sha256 = \"6bae91019476d3ec7147de9aa291cadb6d870abf2f3015d2da73a90325ac1496\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lazy-regex/3.6.0/download\"],\n strip_prefix = \"lazy-regex-3.6.0\",\n build_file = Label(\"@crates//crates:BUILD.lazy-regex-3.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__lazy-regex-proc_macros-3.6.0\",\n sha256 = \"4de9c1e1439d8b7b3061b2d209809f447ca33241733d9a3c01eabf2dc8d94358\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lazy-regex-proc_macros/3.6.0/download\"],\n strip_prefix = \"lazy-regex-proc_macros-3.6.0\",\n build_file = Label(\"@crates//crates:BUILD.lazy-regex-proc_macros-3.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__lazy_static-1.5.0\",\n sha256 = \"bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lazy_static/1.5.0/download\"],\n strip_prefix = \"lazy_static-1.5.0\",\n build_file = Label(\"@crates//crates:BUILD.lazy_static-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__leb128fmt-0.1.0\",\n sha256 = \"09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/leb128fmt/0.1.0/download\"],\n strip_prefix = \"leb128fmt-0.1.0\",\n build_file = Label(\"@crates//crates:BUILD.leb128fmt-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__libc-0.2.183\",\n sha256 = \"b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/libc/0.2.183/download\"],\n strip_prefix = \"libc-0.2.183\",\n build_file = Label(\"@crates//crates:BUILD.libc-0.2.183.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__libmimalloc-sys-0.1.49\",\n sha256 = \"6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/libmimalloc-sys/0.1.49/download\"],\n strip_prefix = \"libmimalloc-sys-0.1.49\",\n build_file = Label(\"@crates//crates:BUILD.libmimalloc-sys-0.1.49.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__linux-raw-sys-0.12.1\",\n sha256 = \"32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/linux-raw-sys/0.12.1/download\"],\n strip_prefix = \"linux-raw-sys-0.12.1\",\n build_file = Label(\"@crates//crates:BUILD.linux-raw-sys-0.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__log-0.4.33\",\n sha256 = \"0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/log/0.4.33/download\"],\n strip_prefix = \"log-0.4.33\",\n build_file = Label(\"@crates//crates:BUILD.log-0.4.33.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__lsp-server-0.8.0\",\n sha256 = \"0ad8be6fe0ca81b8298bfbbe8a77e9fcd8895ad6c84cd7794d5ebadcbb09ae43\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lsp-server/0.8.0/download\"],\n strip_prefix = \"lsp-server-0.8.0\",\n build_file = Label(\"@crates//crates:BUILD.lsp-server-0.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__lsp-types-0.97.0\",\n sha256 = \"53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lsp-types/0.97.0/download\"],\n strip_prefix = \"lsp-types-0.97.0\",\n build_file = Label(\"@crates//crates:BUILD.lsp-types-0.97.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__memchr-2.8.0\",\n sha256 = \"f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memchr/2.8.0/download\"],\n strip_prefix = \"memchr-2.8.0\",\n build_file = Label(\"@crates//crates:BUILD.memchr-2.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__memo-map-0.3.3\",\n sha256 = \"38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memo-map/0.3.3/download\"],\n strip_prefix = \"memo-map-0.3.3\",\n build_file = Label(\"@crates//crates:BUILD.memo-map-0.3.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__mimalloc-0.1.52\",\n sha256 = \"2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/mimalloc/0.1.52/download\"],\n strip_prefix = \"mimalloc-0.1.52\",\n build_file = Label(\"@crates//crates:BUILD.mimalloc-0.1.52.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__minijinja-2.21.0\",\n sha256 = \"cb3d648e68cea56d9858d535ee28f9538404e2dd8cb08ed0bd05dca379477f39\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/minijinja/2.21.0/download\"],\n strip_prefix = \"minijinja-2.21.0\",\n build_file = Label(\"@crates//crates:BUILD.minijinja-2.21.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__nix-0.31.2\",\n sha256 = \"5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nix/0.31.2/download\"],\n strip_prefix = \"nix-0.31.2\",\n build_file = Label(\"@crates//crates:BUILD.nix-0.31.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__nohash-hasher-0.2.0\",\n sha256 = \"2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nohash-hasher/0.2.0/download\"],\n strip_prefix = \"nohash-hasher-0.2.0\",\n build_file = Label(\"@crates//crates:BUILD.nohash-hasher-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__num-conv-0.2.0\",\n sha256 = \"cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-conv/0.2.0/download\"],\n strip_prefix = \"num-conv-0.2.0\",\n build_file = Label(\"@crates//crates:BUILD.num-conv-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__num-traits-0.2.19\",\n sha256 = \"071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-traits/0.2.19/download\"],\n strip_prefix = \"num-traits-0.2.19\",\n build_file = Label(\"@crates//crates:BUILD.num-traits-0.2.19.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__once_cell-1.21.4\",\n sha256 = \"9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/once_cell/1.21.4/download\"],\n strip_prefix = \"once_cell-1.21.4\",\n build_file = Label(\"@crates//crates:BUILD.once_cell-1.21.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__once_cell_polyfill-1.70.2\",\n sha256 = \"384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/once_cell_polyfill/1.70.2/download\"],\n strip_prefix = \"once_cell_polyfill-1.70.2\",\n build_file = Label(\"@crates//crates:BUILD.once_cell_polyfill-1.70.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__oorandom-11.1.5\",\n sha256 = \"d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/oorandom/11.1.5/download\"],\n strip_prefix = \"oorandom-11.1.5\",\n build_file = Label(\"@crates//crates:BUILD.oorandom-11.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__plotters-0.3.7\",\n sha256 = \"5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/plotters/0.3.7/download\"],\n strip_prefix = \"plotters-0.3.7\",\n build_file = Label(\"@crates//crates:BUILD.plotters-0.3.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__plotters-backend-0.3.7\",\n sha256 = \"df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/plotters-backend/0.3.7/download\"],\n strip_prefix = \"plotters-backend-0.3.7\",\n build_file = Label(\"@crates//crates:BUILD.plotters-backend-0.3.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__plotters-svg-0.3.7\",\n sha256 = \"51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/plotters-svg/0.3.7/download\"],\n strip_prefix = \"plotters-svg-0.3.7\",\n build_file = Label(\"@crates//crates:BUILD.plotters-svg-0.3.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__portable-atomic-1.13.1\",\n sha256 = \"c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/portable-atomic/1.13.1/download\"],\n strip_prefix = \"portable-atomic-1.13.1\",\n build_file = Label(\"@crates//crates:BUILD.portable-atomic-1.13.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__powerfmt-0.2.0\",\n sha256 = \"439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/powerfmt/0.2.0/download\"],\n strip_prefix = \"powerfmt-0.2.0\",\n build_file = Label(\"@crates//crates:BUILD.powerfmt-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__predicates-3.1.4\",\n sha256 = \"ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/predicates/3.1.4/download\"],\n strip_prefix = \"predicates-3.1.4\",\n build_file = Label(\"@crates//crates:BUILD.predicates-3.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__predicates-core-1.0.10\",\n sha256 = \"cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/predicates-core/1.0.10/download\"],\n strip_prefix = \"predicates-core-1.0.10\",\n build_file = Label(\"@crates//crates:BUILD.predicates-core-1.0.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__predicates-tree-1.0.13\",\n sha256 = \"d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/predicates-tree/1.0.13/download\"],\n strip_prefix = \"predicates-tree-1.0.13\",\n build_file = Label(\"@crates//crates:BUILD.predicates-tree-1.0.13.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__pretty_assertions-1.4.1\",\n sha256 = \"3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pretty_assertions/1.4.1/download\"],\n strip_prefix = \"pretty_assertions-1.4.1\",\n build_file = Label(\"@crates//crates:BUILD.pretty_assertions-1.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__prettyplease-0.2.37\",\n sha256 = \"479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/prettyplease/0.2.37/download\"],\n strip_prefix = \"prettyplease-0.2.37\",\n build_file = Label(\"@crates//crates:BUILD.prettyplease-0.2.37.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__proc-macro2-1.0.106\",\n sha256 = \"8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/proc-macro2/1.0.106/download\"],\n strip_prefix = \"proc-macro2-1.0.106\",\n build_file = Label(\"@crates//crates:BUILD.proc-macro2-1.0.106.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__pyo3-0.29.0\",\n sha256 = \"cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pyo3/0.29.0/download\"],\n strip_prefix = \"pyo3-0.29.0\",\n build_file = Label(\"@crates//crates:BUILD.pyo3-0.29.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__pyo3-build-config-0.29.0\",\n sha256 = \"c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pyo3-build-config/0.29.0/download\"],\n strip_prefix = \"pyo3-build-config-0.29.0\",\n build_file = Label(\"@crates//crates:BUILD.pyo3-build-config-0.29.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__pyo3-ffi-0.29.0\",\n sha256 = \"ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pyo3-ffi/0.29.0/download\"],\n strip_prefix = \"pyo3-ffi-0.29.0\",\n build_file = Label(\"@crates//crates:BUILD.pyo3-ffi-0.29.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__pyo3-macros-0.29.0\",\n sha256 = \"9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pyo3-macros/0.29.0/download\"],\n strip_prefix = \"pyo3-macros-0.29.0\",\n build_file = Label(\"@crates//crates:BUILD.pyo3-macros-0.29.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__pyo3-macros-backend-0.29.0\",\n sha256 = \"4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pyo3-macros-backend/0.29.0/download\"],\n strip_prefix = \"pyo3-macros-backend-0.29.0\",\n build_file = Label(\"@crates//crates:BUILD.pyo3-macros-backend-0.29.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__quote-1.0.45\",\n sha256 = \"41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quote/1.0.45/download\"],\n strip_prefix = \"quote-1.0.45\",\n build_file = Label(\"@crates//crates:BUILD.quote-1.0.45.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__r-efi-6.0.0\",\n sha256 = \"f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/r-efi/6.0.0/download\"],\n strip_prefix = \"r-efi-6.0.0\",\n build_file = Label(\"@crates//crates:BUILD.r-efi-6.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__rayon-1.12.0\",\n sha256 = \"fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rayon/1.12.0/download\"],\n strip_prefix = \"rayon-1.12.0\",\n build_file = Label(\"@crates//crates:BUILD.rayon-1.12.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__rayon-core-1.13.0\",\n sha256 = \"22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rayon-core/1.13.0/download\"],\n strip_prefix = \"rayon-core-1.13.0\",\n build_file = Label(\"@crates//crates:BUILD.rayon-core-1.13.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__regex-1.12.4\",\n sha256 = \"f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex/1.12.4/download\"],\n strip_prefix = \"regex-1.12.4\",\n build_file = Label(\"@crates//crates:BUILD.regex-1.12.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__regex-automata-0.4.14\",\n sha256 = \"6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex-automata/0.4.14/download\"],\n strip_prefix = \"regex-automata-0.4.14\",\n build_file = Label(\"@crates//crates:BUILD.regex-automata-0.4.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__regex-syntax-0.8.11\",\n sha256 = \"d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex-syntax/0.8.11/download\"],\n strip_prefix = \"regex-syntax-0.8.11\",\n build_file = Label(\"@crates//crates:BUILD.regex-syntax-0.8.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__rustix-1.1.4\",\n sha256 = \"b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustix/1.1.4/download\"],\n strip_prefix = \"rustix-1.1.4\",\n build_file = Label(\"@crates//crates:BUILD.rustix-1.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__rustversion-1.0.22\",\n sha256 = \"b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustversion/1.0.22/download\"],\n strip_prefix = \"rustversion-1.0.22\",\n build_file = Label(\"@crates//crates:BUILD.rustversion-1.0.22.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__ryu-1.0.23\",\n sha256 = \"9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ryu/1.0.23/download\"],\n strip_prefix = \"ryu-1.0.23\",\n build_file = Label(\"@crates//crates:BUILD.ryu-1.0.23.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__same-file-1.0.6\",\n sha256 = \"93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/same-file/1.0.6/download\"],\n strip_prefix = \"same-file-1.0.6\",\n build_file = Label(\"@crates//crates:BUILD.same-file-1.0.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__semver-1.0.27\",\n sha256 = \"d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/semver/1.0.27/download\"],\n strip_prefix = \"semver-1.0.27\",\n build_file = Label(\"@crates//crates:BUILD.semver-1.0.27.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde-1.0.228\",\n sha256 = \"9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde/1.0.228/download\"],\n strip_prefix = \"serde-1.0.228\",\n build_file = Label(\"@crates//crates:BUILD.serde-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde-wasm-bindgen-0.6.5\",\n sha256 = \"8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde-wasm-bindgen/0.6.5/download\"],\n strip_prefix = \"serde-wasm-bindgen-0.6.5\",\n build_file = Label(\"@crates//crates:BUILD.serde-wasm-bindgen-0.6.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_core-1.0.228\",\n sha256 = \"41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_core/1.0.228/download\"],\n strip_prefix = \"serde_core-1.0.228\",\n build_file = Label(\"@crates//crates:BUILD.serde_core-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_derive-1.0.228\",\n sha256 = \"d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_derive/1.0.228/download\"],\n strip_prefix = \"serde_derive-1.0.228\",\n build_file = Label(\"@crates//crates:BUILD.serde_derive-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_json-1.0.150\",\n sha256 = \"e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_json/1.0.150/download\"],\n strip_prefix = \"serde_json-1.0.150\",\n build_file = Label(\"@crates//crates:BUILD.serde_json-1.0.150.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_repr-0.1.20\",\n sha256 = \"175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_repr/0.1.20/download\"],\n strip_prefix = \"serde_repr-0.1.20\",\n build_file = Label(\"@crates//crates:BUILD.serde_repr-0.1.20.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_spanned-1.1.1\",\n sha256 = \"6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_spanned/1.1.1/download\"],\n strip_prefix = \"serde_spanned-1.1.1\",\n build_file = Label(\"@crates//crates:BUILD.serde_spanned-1.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_with-3.21.0\",\n sha256 = \"76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_with/3.21.0/download\"],\n strip_prefix = \"serde_with-3.21.0\",\n build_file = Label(\"@crates//crates:BUILD.serde_with-3.21.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_with_macros-3.21.0\",\n sha256 = \"84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_with_macros/3.21.0/download\"],\n strip_prefix = \"serde_with_macros-3.21.0\",\n build_file = Label(\"@crates//crates:BUILD.serde_with_macros-3.21.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_yaml-0.9.34-deprecated\",\n sha256 = \"6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_yaml/0.9.34+deprecated/download\"],\n strip_prefix = \"serde_yaml-0.9.34+deprecated\",\n build_file = Label(\"@crates//crates:BUILD.serde_yaml-0.9.34+deprecated.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__shlex-1.3.0\",\n sha256 = \"0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/shlex/1.3.0/download\"],\n strip_prefix = \"shlex-1.3.0\",\n build_file = Label(\"@crates//crates:BUILD.shlex-1.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__smol_str-0.3.6\",\n sha256 = \"4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/smol_str/0.3.6/download\"],\n strip_prefix = \"smol_str-0.3.6\",\n build_file = Label(\"@crates//crates:BUILD.smol_str-0.3.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__statrs-0.18.0\",\n sha256 = \"2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/statrs/0.18.0/download\"],\n strip_prefix = \"statrs-0.18.0\",\n build_file = Label(\"@crates//crates:BUILD.statrs-0.18.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__strsim-0.11.1\",\n sha256 = \"7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/strsim/0.11.1/download\"],\n strip_prefix = \"strsim-0.11.1\",\n build_file = Label(\"@crates//crates:BUILD.strsim-0.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__strum-0.28.0\",\n sha256 = \"9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/strum/0.28.0/download\"],\n strip_prefix = \"strum-0.28.0\",\n build_file = Label(\"@crates//crates:BUILD.strum-0.28.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__strum_macros-0.28.0\",\n sha256 = \"ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/strum_macros/0.28.0/download\"],\n strip_prefix = \"strum_macros-0.28.0\",\n build_file = Label(\"@crates//crates:BUILD.strum_macros-0.28.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__syn-2.0.117\",\n sha256 = \"e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/syn/2.0.117/download\"],\n strip_prefix = \"syn-2.0.117\",\n build_file = Label(\"@crates//crates:BUILD.syn-2.0.117.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__target-lexicon-0.13.5\",\n sha256 = \"adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/target-lexicon/0.13.5/download\"],\n strip_prefix = \"target-lexicon-0.13.5\",\n build_file = Label(\"@crates//crates:BUILD.target-lexicon-0.13.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__tempfile-3.27.0\",\n sha256 = \"32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tempfile/3.27.0/download\"],\n strip_prefix = \"tempfile-3.27.0\",\n build_file = Label(\"@crates//crates:BUILD.tempfile-3.27.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__termtree-0.5.1\",\n sha256 = \"8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/termtree/0.5.1/download\"],\n strip_prefix = \"termtree-0.5.1\",\n build_file = Label(\"@crates//crates:BUILD.termtree-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__thiserror-2.0.18\",\n sha256 = \"4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thiserror/2.0.18/download\"],\n strip_prefix = \"thiserror-2.0.18\",\n build_file = Label(\"@crates//crates:BUILD.thiserror-2.0.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__thiserror-impl-2.0.18\",\n sha256 = \"ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thiserror-impl/2.0.18/download\"],\n strip_prefix = \"thiserror-impl-2.0.18\",\n build_file = Label(\"@crates//crates:BUILD.thiserror-impl-2.0.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__time-0.3.47\",\n sha256 = \"743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/time/0.3.47/download\"],\n strip_prefix = \"time-0.3.47\",\n build_file = Label(\"@crates//crates:BUILD.time-0.3.47.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__time-core-0.1.8\",\n sha256 = \"7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/time-core/0.1.8/download\"],\n strip_prefix = \"time-core-0.1.8\",\n build_file = Label(\"@crates//crates:BUILD.time-core-0.1.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__tinytemplate-1.2.1\",\n sha256 = \"be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tinytemplate/1.2.1/download\"],\n strip_prefix = \"tinytemplate-1.2.1\",\n build_file = Label(\"@crates//crates:BUILD.tinytemplate-1.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__tinyvec-1.11.0\",\n sha256 = \"3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tinyvec/1.11.0/download\"],\n strip_prefix = \"tinyvec-1.11.0\",\n build_file = Label(\"@crates//crates:BUILD.tinyvec-1.11.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__tinyvec_macros-0.1.1\",\n sha256 = \"1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tinyvec_macros/0.1.1/download\"],\n strip_prefix = \"tinyvec_macros-0.1.1\",\n build_file = Label(\"@crates//crates:BUILD.tinyvec_macros-0.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__toml-0.9.12-spec-1.1.0\",\n sha256 = \"cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml/0.9.12+spec-1.1.0/download\"],\n strip_prefix = \"toml-0.9.12+spec-1.1.0\",\n build_file = Label(\"@crates//crates:BUILD.toml-0.9.12+spec-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__toml_datetime-0.7.5-spec-1.1.0\",\n sha256 = \"92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_datetime/0.7.5+spec-1.1.0/download\"],\n strip_prefix = \"toml_datetime-0.7.5+spec-1.1.0\",\n build_file = Label(\"@crates//crates:BUILD.toml_datetime-0.7.5+spec-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__toml_parser-1.1.2-spec-1.1.0\",\n sha256 = \"a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_parser/1.1.2+spec-1.1.0/download\"],\n strip_prefix = \"toml_parser-1.1.2+spec-1.1.0\",\n build_file = Label(\"@crates//crates:BUILD.toml_parser-1.1.2+spec-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__toml_writer-1.1.1-spec-1.1.0\",\n sha256 = \"756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_writer/1.1.1+spec-1.1.0/download\"],\n strip_prefix = \"toml_writer-1.1.1+spec-1.1.0\",\n build_file = Label(\"@crates//crates:BUILD.toml_writer-1.1.1+spec-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__unicode-ident-1.0.24\",\n sha256 = \"e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-ident/1.0.24/download\"],\n strip_prefix = \"unicode-ident-1.0.24\",\n build_file = Label(\"@crates//crates:BUILD.unicode-ident-1.0.24.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__unicode-xid-0.2.6\",\n sha256 = \"ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-xid/0.2.6/download\"],\n strip_prefix = \"unicode-xid-0.2.6\",\n build_file = Label(\"@crates//crates:BUILD.unicode-xid-0.2.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__unsafe-libyaml-0.2.11\",\n sha256 = \"673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unsafe-libyaml/0.2.11/download\"],\n strip_prefix = \"unsafe-libyaml-0.2.11\",\n build_file = Label(\"@crates//crates:BUILD.unsafe-libyaml-0.2.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__utf8parse-0.2.2\",\n sha256 = \"06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/utf8parse/0.2.2/download\"],\n strip_prefix = \"utf8parse-0.2.2\",\n build_file = Label(\"@crates//crates:BUILD.utf8parse-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wait-timeout-0.2.1\",\n sha256 = \"09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wait-timeout/0.2.1/download\"],\n strip_prefix = \"wait-timeout-0.2.1\",\n build_file = Label(\"@crates//crates:BUILD.wait-timeout-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__walkdir-2.5.0\",\n sha256 = \"29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/walkdir/2.5.0/download\"],\n strip_prefix = \"walkdir-2.5.0\",\n build_file = Label(\"@crates//crates:BUILD.walkdir-2.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasi-0.11.1-wasi-snapshot-preview1\",\n sha256 = \"ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasi/0.11.1+wasi-snapshot-preview1/download\"],\n strip_prefix = \"wasi-0.11.1+wasi-snapshot-preview1\",\n build_file = Label(\"@crates//crates:BUILD.wasi-0.11.1+wasi-snapshot-preview1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasip2-1.0.2-wasi-0.2.9\",\n sha256 = \"9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasip2/1.0.2+wasi-0.2.9/download\"],\n strip_prefix = \"wasip2-1.0.2+wasi-0.2.9\",\n build_file = Label(\"@crates//crates:BUILD.wasip2-1.0.2+wasi-0.2.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasip3-0.4.0-wasi-0.3.0-rc-2026-01-06\",\n sha256 = \"5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasip3/0.4.0+wasi-0.3.0-rc-2026-01-06/download\"],\n strip_prefix = \"wasip3-0.4.0+wasi-0.3.0-rc-2026-01-06\",\n build_file = Label(\"@crates//crates:BUILD.wasip3-0.4.0+wasi-0.3.0-rc-2026-01-06.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasm-bindgen-0.2.105\",\n sha256 = \"da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen/0.2.105/download\"],\n strip_prefix = \"wasm-bindgen-0.2.105\",\n build_file = Label(\"@crates//crates:BUILD.wasm-bindgen-0.2.105.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasm-bindgen-macro-0.2.105\",\n sha256 = \"04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen-macro/0.2.105/download\"],\n strip_prefix = \"wasm-bindgen-macro-0.2.105\",\n build_file = Label(\"@crates//crates:BUILD.wasm-bindgen-macro-0.2.105.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasm-bindgen-macro-support-0.2.105\",\n sha256 = \"420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen-macro-support/0.2.105/download\"],\n strip_prefix = \"wasm-bindgen-macro-support-0.2.105\",\n build_file = Label(\"@crates//crates:BUILD.wasm-bindgen-macro-support-0.2.105.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasm-bindgen-shared-0.2.105\",\n sha256 = \"76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen-shared/0.2.105/download\"],\n strip_prefix = \"wasm-bindgen-shared-0.2.105\",\n build_file = Label(\"@crates//crates:BUILD.wasm-bindgen-shared-0.2.105.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasm-encoder-0.244.0\",\n sha256 = \"990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-encoder/0.244.0/download\"],\n strip_prefix = \"wasm-encoder-0.244.0\",\n build_file = Label(\"@crates//crates:BUILD.wasm-encoder-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasm-metadata-0.244.0\",\n sha256 = \"bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-metadata/0.244.0/download\"],\n strip_prefix = \"wasm-metadata-0.244.0\",\n build_file = Label(\"@crates//crates:BUILD.wasm-metadata-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wasmparser-0.244.0\",\n sha256 = \"47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasmparser/0.244.0/download\"],\n strip_prefix = \"wasmparser-0.244.0\",\n build_file = Label(\"@crates//crates:BUILD.wasmparser-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__web-sys-0.3.82\",\n sha256 = \"3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/web-sys/0.3.82/download\"],\n strip_prefix = \"web-sys-0.3.82\",\n build_file = Label(\"@crates//crates:BUILD.web-sys-0.3.82.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__winapi-util-0.1.11\",\n sha256 = \"c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winapi-util/0.1.11/download\"],\n strip_prefix = \"winapi-util-0.1.11\",\n build_file = Label(\"@crates//crates:BUILD.winapi-util-0.1.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows-link-0.2.1\",\n sha256 = \"f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-link/0.2.1/download\"],\n strip_prefix = \"windows-link-0.2.1\",\n build_file = Label(\"@crates//crates:BUILD.windows-link-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows-sys-0.59.0\",\n sha256 = \"1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.59.0/download\"],\n strip_prefix = \"windows-sys-0.59.0\",\n build_file = Label(\"@crates//crates:BUILD.windows-sys-0.59.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows-sys-0.61.2\",\n sha256 = \"ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.61.2/download\"],\n strip_prefix = \"windows-sys-0.61.2\",\n build_file = Label(\"@crates//crates:BUILD.windows-sys-0.61.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows-targets-0.52.6\",\n sha256 = \"9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-targets/0.52.6/download\"],\n strip_prefix = \"windows-targets-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows-targets-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_aarch64_gnullvm-0.52.6\",\n sha256 = \"32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_gnullvm/0.52.6/download\"],\n strip_prefix = \"windows_aarch64_gnullvm-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_aarch64_gnullvm-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_aarch64_msvc-0.52.6\",\n sha256 = \"09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_msvc/0.52.6/download\"],\n strip_prefix = \"windows_aarch64_msvc-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_aarch64_msvc-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_i686_gnu-0.52.6\",\n sha256 = \"8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_gnu/0.52.6/download\"],\n strip_prefix = \"windows_i686_gnu-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_i686_gnu-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_i686_gnullvm-0.52.6\",\n sha256 = \"0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_gnullvm/0.52.6/download\"],\n strip_prefix = \"windows_i686_gnullvm-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_i686_gnullvm-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_i686_msvc-0.52.6\",\n sha256 = \"240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_msvc/0.52.6/download\"],\n strip_prefix = \"windows_i686_msvc-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_i686_msvc-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_x86_64_gnu-0.52.6\",\n sha256 = \"147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnu/0.52.6/download\"],\n strip_prefix = \"windows_x86_64_gnu-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_x86_64_gnu-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_x86_64_gnullvm-0.52.6\",\n sha256 = \"24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnullvm/0.52.6/download\"],\n strip_prefix = \"windows_x86_64_gnullvm-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_x86_64_gnullvm-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__windows_x86_64_msvc-0.52.6\",\n sha256 = \"589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_msvc/0.52.6/download\"],\n strip_prefix = \"windows_x86_64_msvc-0.52.6\",\n build_file = Label(\"@crates//crates:BUILD.windows_x86_64_msvc-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__winnow-0.7.15\",\n sha256 = \"df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winnow/0.7.15/download\"],\n strip_prefix = \"winnow-0.7.15\",\n build_file = Label(\"@crates//crates:BUILD.winnow-0.7.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__winnow-1.0.3\",\n sha256 = \"0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winnow/1.0.3/download\"],\n strip_prefix = \"winnow-1.0.3\",\n build_file = Label(\"@crates//crates:BUILD.winnow-1.0.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wit-bindgen-0.51.0\",\n sha256 = \"d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-bindgen/0.51.0/download\"],\n strip_prefix = \"wit-bindgen-0.51.0\",\n build_file = Label(\"@crates//crates:BUILD.wit-bindgen-0.51.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wit-bindgen-core-0.51.0\",\n sha256 = \"ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-bindgen-core/0.51.0/download\"],\n strip_prefix = \"wit-bindgen-core-0.51.0\",\n build_file = Label(\"@crates//crates:BUILD.wit-bindgen-core-0.51.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wit-bindgen-rust-0.51.0\",\n sha256 = \"b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-bindgen-rust/0.51.0/download\"],\n strip_prefix = \"wit-bindgen-rust-0.51.0\",\n build_file = Label(\"@crates//crates:BUILD.wit-bindgen-rust-0.51.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wit-bindgen-rust-macro-0.51.0\",\n sha256 = \"0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-bindgen-rust-macro/0.51.0/download\"],\n strip_prefix = \"wit-bindgen-rust-macro-0.51.0\",\n build_file = Label(\"@crates//crates:BUILD.wit-bindgen-rust-macro-0.51.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wit-component-0.244.0\",\n sha256 = \"9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-component/0.244.0/download\"],\n strip_prefix = \"wit-component-0.244.0\",\n build_file = Label(\"@crates//crates:BUILD.wit-component-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__wit-parser-0.244.0\",\n sha256 = \"ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-parser/0.244.0/download\"],\n strip_prefix = \"wit-parser-0.244.0\",\n build_file = Label(\"@crates//crates:BUILD.wit-parser-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__yansi-1.0.1\",\n sha256 = \"cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/yansi/1.0.1/download\"],\n strip_prefix = \"yansi-1.0.1\",\n build_file = Label(\"@crates//crates:BUILD.yansi-1.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__zerocopy-0.8.46\",\n sha256 = \"5c5030500cb2d66bdfbb4ebc9563be6ce7005a4b5d0f26be0c523870fe372ca6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerocopy/0.8.46/download\"],\n strip_prefix = \"zerocopy-0.8.46\",\n build_file = Label(\"@crates//crates:BUILD.zerocopy-0.8.46.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__zerocopy-derive-0.8.46\",\n sha256 = \"a5f86989a046a79640b9d8867c823349a139367bda96549794fcc3313ce91f4e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerocopy-derive/0.8.46/download\"],\n strip_prefix = \"zerocopy-derive-0.8.46\",\n build_file = Label(\"@crates//crates:BUILD.zerocopy-derive-0.8.46.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__zmij-1.0.21\",\n sha256 = \"b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zmij/1.0.21/download\"],\n strip_prefix = \"zmij-1.0.21\",\n build_file = Label(\"@crates//crates:BUILD.zmij-1.0.21.bazel\"),\n )\n\n return [\n struct(repo=\"crates__anstyle-1.0.14\", is_dev_dep = False),\n struct(repo=\"crates__append-only-vec-0.1.8\", is_dev_dep = False),\n struct(repo=\"crates__clap-4.6.1\", is_dev_dep = False),\n struct(repo=\"crates__common-path-1.0.0\", is_dev_dep = False),\n struct(repo=\"crates__configparser-3.2.0\", is_dev_dep = False),\n struct(repo=\"crates__console_error_panic_hook-0.1.7\", is_dev_dep = False),\n struct(repo=\"crates__enum_dispatch-0.3.13\", is_dev_dep = False),\n struct(repo=\"crates__fancy-regex-0.18.0\", is_dev_dep = False),\n struct(repo=\"crates__fern-0.7.1\", is_dev_dep = False),\n struct(repo=\"crates__getrandom-0.2.17\", is_dev_dep = False),\n struct(repo=\"crates__hashbrown-0.17.1\", is_dev_dep = False),\n struct(repo=\"crates__ignore-0.4.27\", is_dev_dep = False),\n struct(repo=\"crates__indexmap-2.14.0\", is_dev_dep = False),\n struct(repo=\"crates__itertools-0.15.0\", is_dev_dep = False),\n struct(repo=\"crates__js-sys-0.3.82\", is_dev_dep = False),\n struct(repo=\"crates__lazy-regex-3.6.0\", is_dev_dep = False),\n struct(repo=\"crates__log-0.4.33\", is_dev_dep = False),\n struct(repo=\"crates__lsp-server-0.8.0\", is_dev_dep = False),\n struct(repo=\"crates__lsp-types-0.97.0\", is_dev_dep = False),\n struct(repo=\"crates__mimalloc-0.1.52\", is_dev_dep = False),\n struct(repo=\"crates__nohash-hasher-0.2.0\", is_dev_dep = False),\n struct(repo=\"crates__pretty_assertions-1.4.1\", is_dev_dep = False),\n struct(repo=\"crates__pyo3-0.29.0\", is_dev_dep = False),\n struct(repo=\"crates__rayon-1.12.0\", is_dev_dep = False),\n struct(repo=\"crates__regex-1.12.4\", is_dev_dep = False),\n struct(repo=\"crates__regex-automata-0.4.14\", is_dev_dep = False),\n struct(repo=\"crates__serde-1.0.228\", is_dev_dep = False),\n struct(repo=\"crates__serde-wasm-bindgen-0.6.5\", is_dev_dep = False),\n struct(repo=\"crates__serde_json-1.0.150\", is_dev_dep = False),\n struct(repo=\"crates__serde_yaml-0.9.34-deprecated\", is_dev_dep = False),\n struct(repo=\"crates__smol_str-0.3.6\", is_dev_dep = False),\n struct(repo=\"crates__strum-0.28.0\", is_dev_dep = False),\n struct(repo=\"crates__strum_macros-0.28.0\", is_dev_dep = False),\n struct(repo=\"crates__thiserror-2.0.18\", is_dev_dep = False),\n struct(repo=\"crates__toml-0.9.12-spec-1.1.0\", is_dev_dep = False),\n struct(repo=\"crates__wasm-bindgen-0.2.105\", is_dev_dep = False),\n struct(repo = \"crates__assert_cmd-2.2.2\", is_dev_dep = True),\n struct(repo = \"crates__clap-markdown-0.1.5\", is_dev_dep = True),\n struct(repo = \"crates__codspeed-criterion-compat-4.4.1\", is_dev_dep = True),\n struct(repo = \"crates__expect-test-1.5.1\", is_dev_dep = True),\n struct(repo = \"crates__glob-0.3.3\", is_dev_dep = True),\n struct(repo = \"crates__minijinja-2.21.0\", is_dev_dep = True),\n struct(repo = \"crates__serde_with-3.21.0\", is_dev_dep = True),\n struct(repo = \"crates__tempfile-3.27.0\", is_dev_dep = True),\n ]\n" } } }, @@ -2740,7 +2740,7 @@ "https://static.crates.io/crates/aho-corasick/1.1.4/download" ], "strip_prefix": "aho-corasick-1.1.4", - "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'sqruff'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"aho_corasick\",\n deps = [\n \"@crates__memchr-2.8.0//:memchr\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"perf-literal\",\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"default\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"default\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"default\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aho-corasick\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.4\",\n)\n" + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'sqruff'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"aho_corasick\",\n deps = [\n \"@crates__memchr-2.8.0//:memchr\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"perf-literal\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aho-corasick\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.4\",\n)\n" } }, "crates__allocator-api2-0.2.21": { @@ -3965,19 +3965,6 @@ "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'sqruff'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"libmimalloc_sys\",\n deps = [\n \"@crates__libmimalloc-sys-0.1.49//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=libmimalloc-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.49\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates__cc-1.2.57//:cc\",\n ],\n edition = \"2018\",\n links = \"mimalloc\",\n pkg_name = \"libmimalloc-sys\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=libmimalloc-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.1.49\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" } }, - "crates__line-index-0.1.2": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "remote_patch_strip": 1, - "sha256": "3e27e0ed5a392a7f5ba0b3808a2afccff16c64933312c84b57618b49d1209bd2", - "type": "tar.gz", - "urls": [ - "https://static.crates.io/crates/line-index/0.1.2/download" - ], - "strip_prefix": "line-index-0.1.2", - "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'sqruff'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"line_index\",\n deps = [\n \"@crates__nohash-hasher-0.2.0//:nohash_hasher\",\n \"@crates__text-size-1.1.1//:text_size\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=line-index\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.2\",\n)\n" - } - }, "crates__linux-raw-sys-0.12.1": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { @@ -4040,7 +4027,7 @@ "https://static.crates.io/crates/memchr/2.8.0/download" ], "strip_prefix": "memchr-2.8.0", - "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'sqruff'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"memchr\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"default\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"default\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"default\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=memchr\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.8.0\",\n)\n" + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'sqruff'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"memchr\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=memchr\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.8.0\",\n)\n" } }, "crates__memo-map-0.3.3": { @@ -4797,19 +4784,6 @@ "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'sqruff'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"termtree\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=termtree\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.1\",\n)\n" } }, - "crates__text-size-1.1.1": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "remote_patch_strip": 1, - "sha256": "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233", - "type": "tar.gz", - "urls": [ - "https://static.crates.io/crates/text-size/1.1.1/download" - ], - "strip_prefix": "text-size-1.1.1", - "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'sqruff'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"text_size\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=text-size\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.1\",\n)\n" - } - }, "crates__thiserror-2.0.18": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { diff --git a/crates/cli-lib/Cargo.toml b/crates/cli-lib/Cargo.toml index f20dabc21..e4322d222 100644 --- a/crates/cli-lib/Cargo.toml +++ b/crates/cli-lib/Cargo.toml @@ -32,6 +32,7 @@ strum.workspace = true strum_macros.workspace = true fern = "0.7" log.workspace = true +ignore = "0.4.26" anstyle = "1.0" clap = { version = "4.6.1", features = ["derive"] } pyo3 = { version = "0.29.0", optional = true } diff --git a/crates/cli-lib/src/commands.rs b/crates/cli-lib/src/commands.rs index 597ca5068..ff004a545 100644 --- a/crates/cli-lib/src/commands.rs +++ b/crates/cli-lib/src/commands.rs @@ -20,7 +20,7 @@ pub struct Cli { pub dialect: Option, /// Show parse errors. #[arg(long, global = true, default_value = "false")] - pub parsing_errors: bool, + pub(crate) parsing_errors: bool, } #[derive(Debug, Subcommand)] diff --git a/crates/cli-lib/src/commands_fix.rs b/crates/cli-lib/src/commands_fix.rs index d1ada8c7b..d0d2eb146 100644 --- a/crates/cli-lib/src/commands_fix.rs +++ b/crates/cli-lib/src/commands_fix.rs @@ -1,6 +1,7 @@ use crate::commands::FixArgs; use crate::commands::Format; -use crate::linter; +use crate::commands_lint::{ApplyFixes, Input, LintCommand, run_lint_command}; +use sqruff_lib::api::{Mode, ParseErrors}; use sqruff_lib::core::config::FluffConfig; use std::path::Path; @@ -8,74 +9,42 @@ pub(crate) fn run_fix( args: FixArgs, config: FluffConfig, ignorer: impl Fn(&Path) -> bool + Send + Sync, - collect_parse_errors: bool, + parse_errors: ParseErrors, ) -> i32 { let FixArgs { paths, format } = args; - let mut linter = match linter(config, format, collect_parse_errors) { - Ok(l) => l, - Err(e) => { - eprintln!("{}", e); - return 1; - } - }; - let result = match linter.lint_paths(paths, true, &ignorer) { - Ok(result) => result, - Err(e) => { - eprintln!("{}", e.value); - return 1; - } - }; - - if !result.has_violations() { - println!("{} files processed, nothing to fix.", result.len()); - 0 - } else { - let any_unfixable_errors = result.has_unfixable_violations(); - let files = result.len(); - - for mut file in result { - if !file.has_fixes() { - continue; - } - let path = std::mem::take(&mut file.path); - let fixed = file.fix_string(); - std::fs::write(path, fixed).unwrap(); - } - - linter.formatter_mut().unwrap().completion_message(files); - - any_unfixable_errors as i32 - } + run_lint_command( + LintCommand { + mode: Mode::Fix, + input: Input::Paths(paths), + apply: ApplyFixes::ToDisk, + format, + }, + config, + ignorer, + parse_errors, + ) } -pub(crate) fn run_fix_stdin( - config: FluffConfig, - format: Format, - collect_parse_errors: bool, -) -> i32 { - let read_in = crate::stdin::read_std_in().unwrap(); - - let linter = match linter(config, format, collect_parse_errors) { - Ok(l) => l, +pub(crate) fn run_fix_stdin(config: FluffConfig, format: Format, parse_errors: ParseErrors) -> i32 { + let read_in = match crate::stdin::read_std_in() { + Ok(s) => s, Err(e) => { - eprintln!("{}", e); + eprintln!("Failed to read stdin: {e}"); return 1; } }; - let result = match linter.lint_string(&read_in, None, true) { - Ok(result) => result, - Err(e) => { - eprintln!("{}", e.value); - return 1; - } - }; - - let has_unfixable_errors = result.has_unfixable_violations(); - - println!("{}", result.fix_string()); - // if all fixable violations are fixable, return 0 else return 1 - has_unfixable_errors as i32 + run_lint_command( + LintCommand { + mode: Mode::Fix, + input: Input::Stdin(read_in), + apply: ApplyFixes::Stdout, + format, + }, + config, + |_| false, + parse_errors, + ) } #[cfg(test)] @@ -107,9 +76,31 @@ mod tests { format: Format::Human, }; let config = FluffConfig::default(); - run_fix(args, config, ignore_none, true); + run_fix(args, config, ignore_none, ParseErrors::Include); let after = std::fs::metadata(&path).unwrap().modified().unwrap(); assert_eq!(before, after); } + + #[test] + fn run_fix_writes_file_when_changes_exist() { + let mut tmp = NamedTempFile::new().unwrap(); + write!(tmp, "SELECT foo bar FROM tabs").unwrap(); + tmp.flush().unwrap(); + let tmp = tmp.into_temp_path(); + let path = tmp.to_path_buf(); + + let args = FixArgs { + paths: vec![path.clone()], + format: Format::Human, + }; + let config = FluffConfig::try_from_source("[sqruff]\nrules = AL02\n", None).unwrap(); + let exit_code = run_fix(args, config, ignore_none, ParseErrors::Include); + + assert_eq!(exit_code, 0); + assert_eq!( + std::fs::read_to_string(&path).unwrap(), + "SELECT foo AS bar FROM tabs" + ); + } } diff --git a/crates/cli-lib/src/commands_lint.rs b/crates/cli-lib/src/commands_lint.rs index 71296d0d4..3203e9865 100644 --- a/crates/cli-lib/src/commands_lint.rs +++ b/crates/cli-lib/src/commands_lint.rs @@ -1,58 +1,216 @@ use crate::commands::{Format, LintArgs}; -use crate::linter; +use crate::reporters::Reporter; +use sqruff_lib::api::{ + Engine, EngineOptions, FileReport, IgnoreMatcher, Mode, ParseErrors, PathDiscoveryOptions, + RunRequest, Source, SourceId, Workspace, +}; use sqruff_lib::core::config::FluffConfig; -use std::path::Path; +use std::borrow::Cow; +use std::path::{Path, PathBuf}; + +pub(crate) struct LintCommand { + pub mode: Mode, + pub input: Input, + pub apply: ApplyFixes, + pub format: Format, +} + +pub(crate) enum Input { + Paths(Vec), + Stdin(String), +} + +pub(crate) enum ApplyFixes { + Never, + ToDisk, + Stdout, +} pub(crate) fn run_lint( args: LintArgs, config: FluffConfig, ignorer: impl Fn(&Path) -> bool + Send + Sync, - collect_parse_errors: bool, + parse_errors: ParseErrors, ) -> i32 { let LintArgs { paths, format } = args; - let mut linter = match linter(config, format, collect_parse_errors) { - Ok(l) => l, - Err(e) => { - eprintln!("{}", e); - return 1; - } - }; - let result = match linter.lint_paths(paths, false, &ignorer) { - Ok(result) => result, + run_lint_command( + LintCommand { + mode: Mode::Check, + input: Input::Paths(paths), + apply: ApplyFixes::Never, + format, + }, + config, + ignorer, + parse_errors, + ) +} + +pub(crate) fn run_lint_stdin( + config: FluffConfig, + format: Format, + parse_errors: ParseErrors, +) -> i32 { + let read_in = match crate::stdin::read_std_in() { + Ok(s) => s, Err(e) => { - eprintln!("{}", e.value); + eprintln!("Failed to read stdin: {e}"); return 1; } }; - linter.formatter().unwrap().completion_message(result.len()); - - result.has_violations() as i32 + run_lint_command( + LintCommand { + mode: Mode::Check, + input: Input::Stdin(read_in), + apply: ApplyFixes::Never, + format, + }, + config, + |_| false, + parse_errors, + ) } -pub(crate) fn run_lint_stdin( +pub(crate) fn run_lint_command( + command: LintCommand, config: FluffConfig, - format: Format, - collect_parse_errors: bool, + ignorer: impl Fn(&Path) -> bool + Send + Sync, + parse_errors: ParseErrors, ) -> i32 { - let read_in = crate::stdin::read_std_in().unwrap(); - - let linter = match linter(config, format, collect_parse_errors) { - Ok(l) => l, + let mut reporter = Reporter::new(command.format, &config); + let workspace_root = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); + let workspace = match Workspace::new(workspace_root.clone()) { + Ok(workspace) => workspace, Err(e) => { - eprintln!("{}", e); + eprintln!("{}", e.message()); return 1; } }; - let result = match linter.lint_string(&read_in, None, false) { - Ok(result) => result, + let loaded_sources = match load_sources( + &command.input, + &workspace, + &workspace_root, + config.sql_file_exts(), + &ignorer, + ) { + Ok(sources) => sources, Err(e) => { - eprintln!("{}", e.value); + eprintln!("{}", e.message()); return 1; } }; + let engine = match Engine::new(config, EngineOptions { parse_errors }) { + Ok(engine) => engine, + Err(e) => { + eprintln!("{}", e.message()); + return 1; + } + }; + let sources = loaded_sources + .iter() + .map(|loaded| Source { + id: loaded.id.clone(), + text: Cow::Borrowed(loaded.text.as_ref()), + }) + .collect(); + let report = match engine.run(RunRequest { + mode: command.mode, + sources, + }) { + Ok(report) => report, + Err(e) => { + eprintln!("{}", e.message()); + return 1; + } + }; + + let files = report.files.len(); + let has_violations = report.files.iter().any(|file| !file.diagnostics.is_empty()); + + match command.apply { + ApplyFixes::Never => { + if let Err(error) = reporter.emit(&report) { + eprintln!("{error}"); + return 1; + } + has_violations as i32 + } + ApplyFixes::Stdout => { + let any_unfixable_errors = report.files.iter().any(has_unfixable_diagnostics); + if let Err(error) = reporter.emit_diagnostics(&report) { + eprintln!("{error}"); + return 1; + } + for file in report.files { + if let Some(fixed_source) = file.fixed_source { + println!("{fixed_source}"); + } + } + + any_unfixable_errors as i32 + } + ApplyFixes::ToDisk => { + if !has_violations { + println!("{files} files processed, nothing to fix."); + return 0; + } - linter.formatter().unwrap().completion_message(1); + let any_unfixable_errors = report.files.iter().any(has_unfixable_diagnostics); + + if let Err(e) = workspace.apply_fixes(&report) { + eprintln!("{}", e.message()); + return 1; + } + + if let Err(error) = reporter.emit(&report) { + eprintln!("{error}"); + return 1; + } + any_unfixable_errors as i32 + } + } +} + +fn load_sources( + input: &Input, + workspace: &Workspace, + working_dir: &Path, + file_exts: &[String], + ignorer: &(dyn Fn(&Path) -> bool + Send + Sync), +) -> Result>, sqruff_lib::api::SqruffError> { + match input { + Input::Stdin(text) => Ok(vec![Source { + id: SourceId::Stdin, + text: Cow::Owned(text.clone()), + }]), + Input::Paths(paths) => { + let ignore_matcher = ClosureIgnoreMatcher { ignorer }; + let options = PathDiscoveryOptions { + ignore_file_name: ".sqruffignore", + ignore_non_existent_files: false, + ignore_files: true, + working_dir: working_dir.to_path_buf(), + file_exts, + ignorer: Some(&ignore_matcher), + }; + workspace.discover_sources(paths, &options) + } + } +} + +struct ClosureIgnoreMatcher<'a> { + ignorer: &'a (dyn Fn(&Path) -> bool + Send + Sync), +} + +impl IgnoreMatcher for ClosureIgnoreMatcher<'_> { + fn is_ignored(&self, path: &Path) -> bool { + (self.ignorer)(path) + } +} - result.has_violations() as i32 +fn has_unfixable_diagnostics(file: &FileReport) -> bool { + file.diagnostics + .iter() + .any(|diagnostic| !diagnostic.fixable) } diff --git a/crates/cli-lib/src/commands_parse.rs b/crates/cli-lib/src/commands_parse.rs index 129c98114..765776121 100644 --- a/crates/cli-lib/src/commands_parse.rs +++ b/crates/cli-lib/src/commands_parse.rs @@ -1,5 +1,6 @@ use std::io::{self, BufRead}; +use sqruff_lib::api::ParseErrors; use sqruff_lib::core::{config::FluffConfig, linter::core::Linter}; use sqruff_lib_core::parser::segments::Tables; @@ -66,7 +67,7 @@ fn parse_and_output_tree( format: ParseFormat, ) -> i32 { // Create a linter and parse the SQL - let linter = match Linter::new(config.clone(), None, None, true) { + let linter = match Linter::new(config.clone(), None, ParseErrors::Include) { Ok(l) => l, Err(e) => { eprintln!("{}", e); diff --git a/crates/cli-lib/src/commands_templaters.rs b/crates/cli-lib/src/commands_templaters.rs index 8fcaaab46..77cbbdb74 100644 --- a/crates/cli-lib/src/commands_templaters.rs +++ b/crates/cli-lib/src/commands_templaters.rs @@ -2,6 +2,6 @@ use sqruff_lib::templaters::TEMPLATERS; pub(crate) fn templaters() { for templater in TEMPLATERS { - println!("{}", templater.name()); + println!("{}", templater.as_str()); } } diff --git a/crates/cli-lib/src/docs.rs b/crates/cli-lib/src/docs.rs index 87a233e8d..d8dcd0161 100644 --- a/crates/cli-lib/src/docs.rs +++ b/crates/cli-lib/src/docs.rs @@ -12,7 +12,7 @@ use sqruff_lib::core::rules::ErasedRule; #[cfg(feature = "codegen-docs")] use sqruff_lib::rules::rules; #[cfg(feature = "codegen-docs")] -use sqruff_lib::templaters::TEMPLATERS; +use sqruff_lib::templaters::{TEMPLATERS, TemplaterKind, TemplaterRuntime}; #[cfg(feature = "codegen-docs")] use sqruff_lib_core::dialects::init::DialectKind; #[cfg(feature = "codegen-docs")] @@ -58,7 +58,8 @@ pub(crate) fn codegen_docs() { let tmpl = env.get_template("templaters").unwrap(); let templaters = TEMPLATERS - .into_iter() + .iter() + .copied() .map(Templater::from) .collect::>(); let file_templaters = std::fs::File::create(docs_dir.join("templaters.md")).unwrap(); @@ -139,11 +140,12 @@ impl From for Dialect { } #[cfg(feature = "codegen-docs")] -impl From<&'static dyn sqruff_lib::templaters::Templater> for Templater { - fn from(value: &'static dyn sqruff_lib::templaters::Templater) -> Self { +impl From for Templater { + fn from(value: TemplaterKind) -> Self { + let runtime = TemplaterRuntime::from_kind(value); Templater { - name: value.name(), - description: value.description(), + name: runtime.name(), + description: runtime.description(), } } } diff --git a/crates/cli-lib/src/formatters.rs b/crates/cli-lib/src/formatters.rs index 1ca063b52..b7e425f3f 100644 --- a/crates/cli-lib/src/formatters.rs +++ b/crates/cli-lib/src/formatters.rs @@ -1,15 +1,16 @@ -pub(crate) mod github_annotation_native_formatter; -pub(crate) mod json; pub(crate) mod json_types; pub(crate) mod rules; pub(crate) mod utils; use std::borrow::Cow; +use std::collections::HashMap; use std::io::{Stderr, Write}; +use std::sync::OnceLock; use anstyle::{AnsiColor, Effects, Style}; -use sqruff_lib::Formatter; -use sqruff_lib::core::linter::linted_file::LintedFile; +use sqruff_lib::api::{LintDiagnostic, SkipReason}; +use sqruff_lib::rules as sqruff_rules; +#[cfg(test)] use sqruff_lib_core::errors::SQLBaseError; use crate::formatters::utils::{ @@ -18,6 +19,20 @@ use crate::formatters::utils::{ const LIGHT_GREY: Style = AnsiColor::Black.on_default().effects(Effects::BOLD); +fn rule_name_for_code(code: &str) -> Option<&'static str> { + static RULE_NAMES: OnceLock> = OnceLock::new(); + + RULE_NAMES + .get_or_init(|| { + sqruff_rules::rules() + .into_iter() + .map(|rule| (rule.code(), rule.name())) + .collect() + }) + .get(code) + .copied() +} + pub(crate) struct OutputStreamFormatter { output_stream: Option, plain_output: bool, @@ -26,36 +41,6 @@ pub(crate) struct OutputStreamFormatter { output_line_length: usize, } -impl Formatter for OutputStreamFormatter { - fn dispatch_file_violations(&self, linted_file: &LintedFile) { - if self.verbosity < 0 { - return; - } - - let s = self.format_file_violations(linted_file.path(), linted_file.violations()); - - self.dispatch(&s); - } - - fn dispatch_file_skip(&self, fname: &str, reason: &str) { - if self.verbosity < 0 { - return; - } - let filename = self.colorize(fname, LIGHT_GREY); - let skip = self.colorize("SKIP", AnsiColor::Yellow.on_default()); - self.dispatch(&format!("== [{filename}] {skip}: {reason}\n")); - } - - fn completion_message(&self, count: usize) { - self.dispatch(&format!("The linter processed {count} file(s).\n")); - self.dispatch(if self.plain_output { - "All Finished\n" - } else { - "All Finished 📜 🎉\n" - }); - } -} - impl OutputStreamFormatter { pub(crate) fn new(output_stream: Option, nocolor: bool, verbosity: i32) -> Self { Self { @@ -79,10 +64,38 @@ impl OutputStreamFormatter { } } - fn format_file_violations(&self, fname: &str, violations: &[SQLBaseError]) -> String { + pub(crate) fn dispatch_file_diagnostics(&self, fname: &str, diagnostics: &[LintDiagnostic]) { + if self.verbosity < 0 { + return; + } + + let s = self.format_file_diagnostics(fname, diagnostics); + self.dispatch(&s); + } + + pub(crate) fn dispatch_file_skip(&self, fname: &str, reason: &SkipReason) { + if self.verbosity < 0 { + return; + } + + let filename = self.colorize(fname, LIGHT_GREY); + let skip = self.colorize("SKIP", AnsiColor::Yellow.on_default()); + self.dispatch(&format!("== [{filename}] {skip}: {}\n", reason.message)); + } + + pub(crate) fn emit_completion(&self, count: usize) { + self.dispatch(&format!("The linter processed {count} file(s).\n")); + self.dispatch(if self.plain_output { + "All Finished\n" + } else { + "All Finished 📜 🎉\n" + }); + } + + fn format_file_diagnostics(&self, fname: &str, diagnostics: &[LintDiagnostic]) -> String { let mut text_buffer = String::new(); - let show = !violations.is_empty(); + let show = !diagnostics.is_empty(); if self.verbosity > 0 || show { let text = self.format_filename(fname, !show); @@ -91,8 +104,8 @@ impl OutputStreamFormatter { } if show { - for violation in violations { - let text = self.format_violation(violation, self.output_line_length); + for diagnostic in diagnostics { + let text = self.format_diagnostic(diagnostic, self.output_line_length); text_buffer.push_str(&text); text_buffer.push('\n'); } @@ -120,6 +133,7 @@ impl OutputStreamFormatter { format!("== [{filename}] {status}") } + #[cfg(test)] fn format_violation(&self, violation: &SQLBaseError, max_line_length: usize) -> String { let mut desc = violation.desc().to_string(); @@ -159,20 +173,47 @@ impl OutputStreamFormatter { out_buff } -} -/// A formatter that produces no output at all. -/// -/// Mirrors SQLFluff's `none` format type, which is used mostly for testing. -#[derive(Default)] -pub(crate) struct NullFormatter; + fn format_diagnostic(&self, diagnostic: &LintDiagnostic, max_line_length: usize) -> String { + let mut desc = diagnostic.message.clone(); + + let line_elem = format!("{:4}", diagnostic.line); + let pos_elem = format!("{:4}", diagnostic.column); + let code = diagnostic.code.as_deref().unwrap_or("????"); + + if let Some(rule_name) = diagnostic.code.as_deref().and_then(rule_name_for_code) { + let text = self.colorize(rule_name, LIGHT_GREY); + let text = format!(" [{text}]"); + desc.push_str(&text); + } + + let split_desc = split_string_on_spaces(&desc, max_line_length - 25); + let mut section_color = AnsiColor::Blue.on_default(); + + let mut out_buff = String::new(); + for (idx, line) in split_desc.into_iter().enumerate() { + if idx == 0 { + let rule_code = format!("{code:>4}"); -impl Formatter for NullFormatter { - fn dispatch_file_violations(&self, _linted_file: &LintedFile) {} + if rule_code.contains("PRS") { + section_color = AnsiColor::Red.on_default(); + } - fn dispatch_file_skip(&self, _fname: &str, _reason: &str) {} + let section = format!("L:{line_elem} | P:{pos_elem} | {rule_code} | "); + let section = self.colorize(§ion, section_color); + out_buff.push_str(§ion); + } else { + out_buff.push_str(&format!( + "\n{}{}", + " ".repeat(23), + self.colorize("| ", section_color), + )); + } + out_buff.push_str(line); + } - fn completion_message(&self, _count: usize) {} + out_buff + } } #[derive(Clone, Copy)] diff --git a/crates/cli-lib/src/formatters/github_annotation_native_formatter.rs b/crates/cli-lib/src/formatters/github_annotation_native_formatter.rs deleted file mode 100644 index 384b198f1..000000000 --- a/crates/cli-lib/src/formatters/github_annotation_native_formatter.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::io::{Stderr, Write}; -use std::sync::atomic::{AtomicBool, Ordering}; - -use sqruff_lib::Formatter; -use sqruff_lib::core::linter::linted_file::LintedFile; - -#[derive(Debug)] -pub(crate) struct GithubAnnotationNativeFormatter { - output_stream: Stderr, - pub has_fail: AtomicBool, -} - -impl GithubAnnotationNativeFormatter { - pub(crate) fn new(stderr: Stderr) -> Self { - Self { - output_stream: stderr, - has_fail: AtomicBool::new(false), - } - } - - fn dispatch(&self, s: &str) { - let mut output_stream = self.output_stream.lock(); - output_stream - .write_all(s.as_bytes()) - .and_then(|_| output_stream.flush()) - .unwrap_or_else(|e| panic!("failed to emit error: {e}")); - } -} - -impl Formatter for GithubAnnotationNativeFormatter { - fn dispatch_file_violations(&self, linted_file: &LintedFile) { - for violation in linted_file.violations() { - let message = format!( - "::error title=sqruff,file={},line={},col={}::{}: {}\n", - linted_file.path(), - violation.line_no, - violation.line_pos, - violation.rule_code(), - violation.description - ); - - self.dispatch(&message); - self.has_fail.store(true, Ordering::SeqCst); - } - } - - fn dispatch_file_skip(&self, _fname: &str, _reason: &str) { - // No-op for GitHub annotations - } - - fn completion_message(&self, _count: usize) { - // No-op - } -} diff --git a/crates/cli-lib/src/formatters/json.rs b/crates/cli-lib/src/formatters/json.rs deleted file mode 100644 index e63bd11d8..000000000 --- a/crates/cli-lib/src/formatters/json.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::sync::Mutex; - -use sqruff_lib::{Formatter, core::linter::linted_file::LintedFile}; - -use super::json_types::{Diagnostic, DiagnosticCollection}; - -#[derive(Default)] -pub(crate) struct JsonFormatter { - violations: Mutex, -} - -impl Formatter for JsonFormatter { - fn dispatch_file_violations(&self, linted_file: &LintedFile) { - let violations = linted_file.violations(); - let mut lock = self.violations.lock().unwrap(); - lock.entry(linted_file.path().into()).or_default().extend( - violations - .iter() - .map(|err| Diagnostic::from(err.clone())) - .collect::>(), - ); - } - - fn dispatch_file_skip(&self, _fname: &str, _reason: &str) { - // No-op for JSON output - } - - fn completion_message(&self, _count: usize) { - let lock = self.violations.lock().unwrap(); - let json = serde_json::to_string(&*lock).unwrap(); - println!("{json}"); - } -} diff --git a/crates/cli-lib/src/formatters/json_types.rs b/crates/cli-lib/src/formatters/json_types.rs index e9cbd6fcc..487787380 100644 --- a/crates/cli-lib/src/formatters/json_types.rs +++ b/crates/cli-lib/src/formatters/json_types.rs @@ -1,24 +1,29 @@ use std::collections::BTreeMap; use serde::Serialize; -use sqruff_lib_core::errors::SQLBaseError; +use sqruff_lib::api::LintDiagnostic; -impl From for Diagnostic { - fn from(value: SQLBaseError) -> Self { - let code = value.rule.map(|rule| rule.code.to_string()); +impl Diagnostic { + pub(crate) fn from_lint_diagnostic(value: &LintDiagnostic) -> Self { Diagnostic { range: Range { - start: Position::new(value.line_no as u32, value.line_pos as u32), - end: Position::new(value.line_no as u32, value.line_pos as u32), + start: Position::new(value.line as u32, value.column as u32), + end: Position::new(value.end_line as u32, value.end_column as u32), }, - message: value.description, + message: value.message.clone(), severity: DiagnosticSeverity::Warning, source: Some("sqruff".to_string()), - code, + code: value.code.clone(), } } } +impl From<&LintDiagnostic> for Diagnostic { + fn from(value: &LintDiagnostic) -> Self { + Self::from_lint_diagnostic(value) + } +} + /// Represents a line and character position, such as the position of the cursor. #[derive(Serialize)] struct Position { diff --git a/crates/cli-lib/src/ignore.rs b/crates/cli-lib/src/ignore.rs new file mode 100644 index 000000000..856e1b6b3 --- /dev/null +++ b/crates/cli-lib/src/ignore.rs @@ -0,0 +1,50 @@ +use ignore::gitignore::Gitignore; +use std::path::Path; + +/// The name of the ignore file that sqruff will look for in the root of the project and use to +/// determine which files to ignore. +const IGNORE_FILE_NAME: &str = ".sqruffignore"; + +pub(crate) struct IgnoreFile { + ignore: Gitignore, +} + +impl IgnoreFile { + /// Create a new instance of `IgnoreFile` from the root of the project. + pub(crate) fn new_from_root(root: &Path) -> Result { + let ignore_file = root.join(IGNORE_FILE_NAME); + if ignore_file.exists() { + let ignore = Gitignore::new(ignore_file); + match ignore { + (ignore, None) => Ok(IgnoreFile { ignore }), + (_, Some(err)) => Err(err.to_string()), + } + } else { + Ok(IgnoreFile { + ignore: Gitignore::empty(), + }) + } + } + + /// Check if the given path should be ignored. + pub(crate) fn is_ignored(&self, path: &Path) -> bool { + let is_dir = path.is_dir(); + let match_result = self.ignore.matched(path, is_dir); + let is_ignored = match_result.is_ignore(); + + if is_ignored { + let path_type = if is_dir { "directory" } else { "file" }; + log::debug!( + "Ignoring {} '{}' due to ignore pattern", + path_type, + path.display() + ); + + if let Some(pattern) = match_result.inner() { + log::debug!("Matched ignore pattern: '{}'", pattern.original()); + } + } + + is_ignored + } +} diff --git a/crates/cli-lib/src/lib.rs b/crates/cli-lib/src/lib.rs index 161ec465d..1225f0915 100644 --- a/crates/cli-lib/src/lib.rs +++ b/crates/cli-lib/src/lib.rs @@ -1,8 +1,7 @@ use clap::Parser as _; -use commands::Format; -use sqruff_lib::core::linter::core::Linter; +use sqruff_lib::api::ParseErrors; +use sqruff_lib::core::config::FluffConfig; use sqruff_lib::ignore::IgnoreFile; -use sqruff_lib::{Formatter, core::config::FluffConfig}; use sqruff_lib_core::dialects::init::DialectKind; use std::path::Path; use std::sync::Arc; @@ -11,10 +10,6 @@ use stdin::is_std_in_flag_input; use crate::commands::{Cli, Commands}; #[cfg(feature = "codegen-docs")] use crate::docs::codegen_docs; -use crate::formatters::github_annotation_native_formatter::GithubAnnotationNativeFormatter; -use crate::formatters::json::JsonFormatter; -use crate::formatters::{NullFormatter, OutputStreamFormatter}; - pub mod commands; mod commands_dialects; mod commands_fix; @@ -29,6 +24,7 @@ mod docs; mod formatters; mod github_action; mod logger; +mod reporters; mod stdin; #[cfg(feature = "codegen-docs")] @@ -44,7 +40,11 @@ where { let _ = logger::init(); let cli = Cli::parse_from(args); - let collect_parse_errors = cli.parsing_errors; + let parse_errors = if cli.parsing_errors { + ParseErrors::Include + } else { + ParseErrors::Suppress + }; let mut config: FluffConfig = if let Some(config) = cli.config.as_ref() { if !Path::new(config).is_file() { @@ -55,19 +55,19 @@ where std::process::exit(1); }; - match FluffConfig::try_from_file(Path::new(config)) { + match FluffConfig::from_file(Path::new(config)) { Ok(config) => config, - Err(err) => { - eprintln!("{err}"); - std::process::exit(1); + Err(error) => { + eprintln!("{}", error.message()); + return 1; } } } else { match FluffConfig::from_root(None, false, None) { Ok(config) => config, - Err(err) => { - eprintln!("{err}"); - std::process::exit(1); + Err(error) => { + eprintln!("{}", error.message()); + return 1; } } }; @@ -102,21 +102,24 @@ where eprintln!("{e}"); 1 } - Ok(false) => commands_lint::run_lint(args, config, ignorer, collect_parse_errors), - Ok(true) => commands_lint::run_lint_stdin(config, args.format, collect_parse_errors), + Ok(false) => commands_lint::run_lint(args, config, ignorer, parse_errors), + Ok(true) => commands_lint::run_lint_stdin(config, args.format, parse_errors), }, Commands::Fix(args) => match is_std_in_flag_input(&args.paths) { Err(e) => { eprintln!("{e}"); 1 } - Ok(false) => commands_fix::run_fix(args, config, ignorer, collect_parse_errors), - Ok(true) => commands_fix::run_fix_stdin(config, args.format, collect_parse_errors), + Ok(false) => commands_fix::run_fix(args, config, ignorer, parse_errors), + Ok(true) => commands_fix::run_fix_stdin(config, args.format, parse_errors), + }, + Commands::Lsp => match sqruff_lsp::run() { + Ok(()) => 0, + Err(e) => { + eprintln!("{e}"); + 1 + } }, - Commands::Lsp => { - sqruff_lsp::run(); - 0 - } Commands::Info => { commands_info::info(); 0 @@ -137,36 +140,3 @@ where Commands::Parse(args) => commands_parse::run_parse(args, config), } } - -pub(crate) fn linter( - config: FluffConfig, - format: Format, - collect_parse_errors: bool, -) -> Result { - let formatter: Arc = match format { - Format::Human => { - let output_stream = std::io::stderr().into(); - let formatter = OutputStreamFormatter::new( - output_stream, - config.get("nocolor", "core").as_bool().unwrap_or_default(), - config.get("verbose", "core").as_int().unwrap_or_default(), - ); - Arc::new(formatter) - } - Format::GithubAnnotationNative => { - let output_stream = std::io::stderr(); - let formatter = GithubAnnotationNativeFormatter::new(output_stream); - Arc::new(formatter) - } - Format::Json => { - let formatter = JsonFormatter::default(); - Arc::new(formatter) - } - Format::None => { - let formatter = NullFormatter; - Arc::new(formatter) - } - }; - - Linter::new(config, Some(formatter), None, collect_parse_errors) -} diff --git a/crates/cli-lib/src/reporters.rs b/crates/cli-lib/src/reporters.rs new file mode 100644 index 000000000..414fa422b --- /dev/null +++ b/crates/cli-lib/src/reporters.rs @@ -0,0 +1,57 @@ +pub(crate) mod github; +pub(crate) mod human; +pub(crate) mod json; + +use sqruff_lib::api::{RunReport, SourceId}; +use sqruff_lib::core::config::FluffConfig; + +use crate::commands::Format; +use crate::reporters::github::GithubReporter; +use crate::reporters::human::HumanReporter; +use crate::reporters::json::JsonReporter; + +pub(crate) type CliError = Box; + +pub(crate) enum Reporter { + Human(HumanReporter), + Json(JsonReporter), + Github(GithubReporter), + None, +} + +impl Reporter { + pub(crate) fn new(format: Format, config: &FluffConfig) -> Self { + match format { + Format::Human => Self::Human(HumanReporter::new(config)), + Format::GithubAnnotationNative => Self::Github(GithubReporter::new()), + Format::Json => Self::Json(JsonReporter), + Format::None => Self::None, + } + } + + pub(crate) fn emit(&mut self, report: &RunReport) -> Result<(), CliError> { + match self { + Self::Human(r) => r.emit(report), + Self::Json(r) => r.emit(report), + Self::Github(r) => r.emit(report), + Self::None => Ok(()), + } + } + + pub(crate) fn emit_diagnostics(&mut self, report: &RunReport) -> Result<(), CliError> { + match self { + Self::Human(r) => r.emit_diagnostics(report), + Self::Json(r) => r.emit(report), + Self::Github(r) => r.emit(report), + Self::None => Ok(()), + } + } +} + +pub(crate) fn display_source_id(source_id: &SourceId) -> String { + match source_id { + SourceId::Stdin => "".into(), + SourceId::Path(path) => path.to_string_lossy().into_owned(), + SourceId::Virtual(name) => name.clone(), + } +} diff --git a/crates/cli-lib/src/reporters/github.rs b/crates/cli-lib/src/reporters/github.rs new file mode 100644 index 000000000..48eb0d415 --- /dev/null +++ b/crates/cli-lib/src/reporters/github.rs @@ -0,0 +1,66 @@ +use std::io::{Stderr, Write}; + +use sqruff_lib::api::{LintDiagnostic, RunReport}; + +use crate::reporters::{CliError, display_source_id}; + +pub(crate) struct GithubReporter { + output_stream: Stderr, +} + +impl GithubReporter { + pub(crate) fn new() -> Self { + Self { + output_stream: std::io::stderr(), + } + } + + pub(crate) fn emit(&mut self, report: &RunReport) -> Result<(), CliError> { + for file in &report.files { + let filename = display_source_id(&file.source_id); + for diagnostic in &file.diagnostics { + self.emit_diagnostic(&filename, diagnostic)?; + } + } + + Ok(()) + } + + fn emit_diagnostic( + &mut self, + filename: &str, + diagnostic: &LintDiagnostic, + ) -> Result<(), CliError> { + let diagnostic = GithubAnnotation::from(diagnostic); + let message = format!( + "::error title=sqruff,file={},line={},col={}::{}: {}\n", + filename, diagnostic.line, diagnostic.column, diagnostic.code, diagnostic.message + ); + + let mut output_stream = self.output_stream.lock(); + output_stream.write_all(message.as_bytes())?; + output_stream.flush()?; + Ok(()) + } +} + +struct GithubAnnotation { + code: String, + line: usize, + column: usize, + message: String, +} + +impl From<&LintDiagnostic> for GithubAnnotation { + fn from(diagnostic: &LintDiagnostic) -> Self { + Self { + code: diagnostic + .code + .clone() + .unwrap_or_else(|| "????".to_string()), + line: diagnostic.line, + column: diagnostic.column, + message: diagnostic.message.clone(), + } + } +} diff --git a/crates/cli-lib/src/reporters/human.rs b/crates/cli-lib/src/reporters/human.rs new file mode 100644 index 000000000..c5afe1ad3 --- /dev/null +++ b/crates/cli-lib/src/reporters/human.rs @@ -0,0 +1,42 @@ +use sqruff_lib::api::RunReport; +use sqruff_lib::core::config::FluffConfig; + +use crate::formatters::OutputStreamFormatter; +use crate::reporters::{CliError, display_source_id}; + +pub(crate) struct HumanReporter { + formatter: OutputStreamFormatter, +} + +impl HumanReporter { + pub(crate) fn new(config: &FluffConfig) -> Self { + Self { + formatter: OutputStreamFormatter::new( + std::io::stderr().into(), + config.get("nocolor", "core").as_bool().unwrap_or_default(), + config.get("verbose", "core").as_int().unwrap_or_default(), + ), + } + } + + pub(crate) fn emit(&mut self, report: &RunReport) -> Result<(), CliError> { + self.emit_diagnostics(report)?; + self.formatter.emit_completion(report.files.len()); + Ok(()) + } + + pub(crate) fn emit_diagnostics(&mut self, report: &RunReport) -> Result<(), CliError> { + for file in &report.files { + let source_id = display_source_id(&file.source_id); + + if let Some(reason) = &file.skipped { + self.formatter.dispatch_file_skip(&source_id, reason); + } else { + self.formatter + .dispatch_file_diagnostics(&source_id, &file.diagnostics); + } + } + + Ok(()) + } +} diff --git a/crates/cli-lib/src/reporters/json.rs b/crates/cli-lib/src/reporters/json.rs new file mode 100644 index 000000000..f78f7e8fd --- /dev/null +++ b/crates/cli-lib/src/reporters/json.rs @@ -0,0 +1,42 @@ +use serde::Serialize; +use sqruff_lib::api::RunReport; + +use crate::formatters::json_types::{Diagnostic, DiagnosticCollection}; +use crate::reporters::{CliError, display_source_id}; + +#[derive(Default)] +pub(crate) struct JsonReporter; + +impl JsonReporter { + pub(crate) fn emit(&mut self, report: &RunReport) -> Result<(), CliError> { + let json_report = JsonReport::from(report); + serde_json::to_writer(std::io::stdout(), &json_report.diagnostics)?; + println!(); + Ok(()) + } +} + +#[derive(Serialize)] +struct JsonReport { + diagnostics: DiagnosticCollection, +} + +impl From<&RunReport> for JsonReport { + fn from(report: &RunReport) -> Self { + let diagnostics = report + .files + .iter() + .map(|file| { + ( + display_source_id(&file.source_id), + file.diagnostics + .iter() + .map(Diagnostic::from) + .collect::>(), + ) + }) + .collect(); + + Self { diagnostics } + } +} diff --git a/crates/cli-python/python/sqruff/templaters/dbt_templater.py b/crates/cli-python/python/sqruff/templaters/dbt_templater.py index 5d321e763..9e0df0fc8 100644 --- a/crates/cli-python/python/sqruff/templaters/dbt_templater.py +++ b/crates/cli-python/python/sqruff/templaters/dbt_templater.py @@ -925,7 +925,7 @@ def process_batch_from_rust( files: List[Tuple[str, str]], config_string: str, live_context: Dict[str, Any], -) -> List[Tuple[Optional[TemplatedFile], Optional[str]]]: +) -> List[Tuple[Optional[TemplatedFile], Optional[str], Optional[str]]]: """Process multiple files in a batch from the Rust side. This function provides optimized batch processing for dbt models by: @@ -939,9 +939,10 @@ def process_batch_from_rust( live_context: Context dictionary for templating Returns: - List of (TemplatedFile | None, error_message | None) tuples. - For each input file, either the TemplatedFile is present (success) - or an error message is present (failure). + List of (TemplatedFile | None, error_message | None, skip_reason | None) + tuples. For each input file, either the TemplatedFile is present + (success), an error message is present (failure), or a skip reason is + present (skipped). """ if not files: return [] @@ -962,11 +963,13 @@ def process_batch_from_rust( except Exception as e: # If sequencing fails, return error for all files error_msg = f"Failed to sequence files: {e}" - return [(None, error_msg) for _ in files] + return [(None, error_msg, None) for _ in files] # Process files in sequenced order, but we need to return results # in the original input order - results_by_fname: Dict[str, Tuple[Optional[TemplatedFile], Optional[str]]] = {} + results_by_fname: Dict[ + str, Tuple[Optional[TemplatedFile], Optional[str], Optional[str]] + ] = {} for fname in sequenced_fnames: if fname not in file_contents: @@ -985,25 +988,25 @@ def process_batch_from_rust( # Check for skipped file (e.g., disabled model or macro) if result[0] is None: skip_reason = result[1] or "unknown reason" - results_by_fname[fname] = (None, "SKIP:" + skip_reason) + results_by_fname[fname] = (None, None, skip_reason) continue (output, errors) = result if errors: # Combine error messages error_msgs = [str(e) for e in errors] - results_by_fname[fname] = (None, "; ".join(error_msgs)) + results_by_fname[fname] = (None, "; ".join(error_msgs), None) else: - results_by_fname[fname] = (output, None) + results_by_fname[fname] = (output, None, None) except Exception as e: - results_by_fname[fname] = (None, str(e)) + results_by_fname[fname] = (None, str(e), None) # Return results in original input order - results: List[Tuple[Optional[TemplatedFile], Optional[str]]] = [] + results: List[Tuple[Optional[TemplatedFile], Optional[str], Optional[str]]] = [] for fname in fnames_input_order: if fname in results_by_fname: results.append(results_by_fname[fname]) else: # File was in input but not processed (shouldn't happen) - results.append((None, f"File {fname} was not processed")) + results.append((None, f"File {fname} was not processed", None)) return results diff --git a/crates/cli-python/python/sqruff/templaters/dbt_templater_benchmark.py b/crates/cli-python/python/sqruff/templaters/dbt_templater_benchmark.py index f515ea3fc..961c6614f 100644 --- a/crates/cli-python/python/sqruff/templaters/dbt_templater_benchmark.py +++ b/crates/cli-python/python/sqruff/templaters/dbt_templater_benchmark.py @@ -206,7 +206,7 @@ def benchmark_batch_approach( live_context=live_context, ) # Check for errors - for templated_file, error in results: + for templated_file, error, skip_reason in results: if error: print(f" Error: {error}") except Exception as e: diff --git a/crates/cli-python/python/sqruff/templaters/dbt_templater_test.py b/crates/cli-python/python/sqruff/templaters/dbt_templater_test.py index f5e926604..c5f40b909 100644 --- a/crates/cli-python/python/sqruff/templaters/dbt_templater_test.py +++ b/crates/cli-python/python/sqruff/templaters/dbt_templater_test.py @@ -154,10 +154,11 @@ def test_batch_processing(): assert len(results) == len(files) success_count = 0 - for templated_file, error in results: + for templated_file, error, skip_reason in results: if error is None: - assert templated_file is not None - success_count += 1 + if skip_reason is None: + assert templated_file is not None + success_count += 1 else: # Sequencing errors would show up here - the bug we're guarding # against would produce: "Failed to sequence files: 'str' object @@ -223,7 +224,7 @@ def test_batch_processing_preserves_order(): assert len(results) == len(files) # For each result, if successful, the fname should match - for i, (templated_file, error) in enumerate(results): + for i, (templated_file, error, skip_reason) in enumerate(results): if templated_file is not None: expected_fname = files[i][1] # The templated file should be for the correct input file diff --git a/crates/cli-python/tests/dbt/output.stderr b/crates/cli-python/tests/dbt/output.stderr index b5b8964bb..7398a4411 100644 --- a/crates/cli-python/tests/dbt/output.stderr +++ b/crates/cli-python/tests/dbt/output.stderr @@ -14,5 +14,5 @@ L: 14 | P: 6 | JJ01 | Jinja tags should have a single whitespace on either | }}` [jinja.padding] L: 17 | P: 49 | JJ01 | Jinja tags should have a single whitespace on either | side: `{{this}}` -> `{{ this }}` [jinja.padding] -The linter processed 8 file(s). +The linter processed 9 file(s). All Finished diff --git a/crates/cli-python/tests/json/hql_file.stdout b/crates/cli-python/tests/json/hql_file.stdout index 88ec6ed26..01b31fb4d 100644 --- a/crates/cli-python/tests/json/hql_file.stdout +++ b/crates/cli-python/tests/json/hql_file.stdout @@ -1 +1 @@ -{"tests/lint/hql_file.hql":[{"range":{"start":{"line":1,"character":7},"end":{"line":1,"character":7}},"message":"Expected only single space before \"1\". Found \" \".","severity":"Warning","source":"sqruff","code":"LT01"},{"range":{"start":{"line":1,"character":11},"end":{"line":1,"character":11}},"message":"Files must end with a single trailing newline.","severity":"Warning","source":"sqruff","code":"LT12"}]} +{"tests/lint/hql_file.hql":[{"range":{"start":{"line":1,"character":7},"end":{"line":1,"character":10}},"message":"Expected only single space before \"1\". Found \" \".","severity":"Warning","source":"sqruff","code":"LT01"},{"range":{"start":{"line":1,"character":11},"end":{"line":1,"character":12}},"message":"Files must end with a single trailing newline.","severity":"Warning","source":"sqruff","code":"LT12"}]} diff --git a/crates/cli-python/tests/json/test_fail_whitespace_before_comma.stdout b/crates/cli-python/tests/json/test_fail_whitespace_before_comma.stdout index f5ad041a8..37e9aca03 100644 --- a/crates/cli-python/tests/json/test_fail_whitespace_before_comma.stdout +++ b/crates/cli-python/tests/json/test_fail_whitespace_before_comma.stdout @@ -1 +1 @@ -{"tests/lint/test_fail_whitespace_before_comma.sql":[{"range":{"start":{"line":1,"character":8},"end":{"line":1,"character":8}},"message":"Column expression without alias. Use explicit `AS` clause.","severity":"Warning","source":"sqruff","code":"AL03"},{"range":{"start":{"line":1,"character":9},"end":{"line":1,"character":9}},"message":"Unexpected whitespace before comma.","severity":"Warning","source":"sqruff","code":"LT01"},{"range":{"start":{"line":1,"character":11},"end":{"line":1,"character":11}},"message":"Column expression without alias. Use explicit `AS` clause.","severity":"Warning","source":"sqruff","code":"AL03"},{"range":{"start":{"line":1,"character":11},"end":{"line":1,"character":11}},"message":"Expected single whitespace between \",\" and \"4\".","severity":"Warning","source":"sqruff","code":"LT01"},{"range":{"start":{"line":1,"character":12},"end":{"line":1,"character":12}},"message":"Files must end with a single trailing newline.","severity":"Warning","source":"sqruff","code":"LT12"}]} +{"tests/lint/test_fail_whitespace_before_comma.sql":[{"range":{"start":{"line":1,"character":8},"end":{"line":1,"character":9}},"message":"Column expression without alias. Use explicit `AS` clause.","severity":"Warning","source":"sqruff","code":"AL03"},{"range":{"start":{"line":1,"character":9},"end":{"line":1,"character":10}},"message":"Unexpected whitespace before comma.","severity":"Warning","source":"sqruff","code":"LT01"},{"range":{"start":{"line":1,"character":11},"end":{"line":1,"character":12}},"message":"Column expression without alias. Use explicit `AS` clause.","severity":"Warning","source":"sqruff","code":"AL03"},{"range":{"start":{"line":1,"character":11},"end":{"line":1,"character":12}},"message":"Expected single whitespace between \",\" and \"4\".","severity":"Warning","source":"sqruff","code":"LT01"},{"range":{"start":{"line":1,"character":12},"end":{"line":1,"character":12}},"message":"Files must end with a single trailing newline.","severity":"Warning","source":"sqruff","code":"LT12"}]} diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index a2caeedc2..2bce2b9a2 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -71,6 +71,10 @@ harness = false name = "none_format" harness = false +[[test]] +name = "path_discovery" +harness = false + [package.metadata.cargo-machete] ignored = ["pyo3"] diff --git a/crates/cli/tests/fix_parse_errors.rs b/crates/cli/tests/fix_parse_errors.rs index 11f947d4c..c3ad2cd20 100644 --- a/crates/cli/tests/fix_parse_errors.rs +++ b/crates/cli/tests/fix_parse_errors.rs @@ -1,10 +1,12 @@ use core::str; +use std::fs; use std::path::{Path, PathBuf}; use assert_cmd::Command; fn main() { parse_errors(); + parse_errors_do_not_rewrite_files(); multiple_add_column_errors(); } @@ -43,6 +45,38 @@ fn parse_errors() { assert_eq!(output.status.code().unwrap(), 1); } +fn parse_errors_do_not_rewrite_files() { + let profile = if cfg!(debug_assertions) { + "debug" + } else { + "release" + }; + + let cargo_folder = Path::new(env!("CARGO_MANIFEST_DIR")); + let mut sqruff_path = PathBuf::from(cargo_folder); + sqruff_path.push(format!("../../target/{}/sqruff", profile)); + + let temp_dir = tempfile::tempdir().unwrap(); + let sql_path = temp_dir.path().join("parse_error.sql"); + let original = "SelEc"; + fs::write(&sql_path, original).unwrap(); + + let mut cmd = Command::new(sqruff_path); + cmd.env("HOME", PathBuf::from(env!("CARGO_MANIFEST_DIR"))); + cmd.arg("fix") + .arg("-f") + .arg("human") + .arg("--parsing-errors") + .arg(&sql_path); + cmd.current_dir(cargo_folder); + + let assert = cmd.assert(); + let output = assert.get_output(); + + assert_eq!(output.status.code().unwrap(), 1); + assert_eq!(fs::read_to_string(&sql_path).unwrap(), original); +} + fn multiple_add_column_errors() { let profile = if cfg!(debug_assertions) { "debug" diff --git a/crates/cli/tests/fix_return_code.rs b/crates/cli/tests/fix_return_code.rs index 09f8ccf36..122b20f3e 100644 --- a/crates/cli/tests/fix_return_code.rs +++ b/crates/cli/tests/fix_return_code.rs @@ -1,10 +1,14 @@ use core::str; +use std::fs; use std::path::{Path, PathBuf}; +use std::thread::sleep; +use std::time::Duration; use assert_cmd::Command; fn main() { fix_return_code(); + file_fix_return_code_and_writes(); } fn fix_return_code() { @@ -115,3 +119,76 @@ fn fix_return_code() { ); assert_eq!(output.status.code().unwrap(), 1); } + +fn file_fix_return_code_and_writes() { + let profile = if cfg!(debug_assertions) { + "debug" + } else { + "release" + }; + + let cargo_folder = Path::new(env!("CARGO_MANIFEST_DIR")); + let mut sqruff_path = PathBuf::from(cargo_folder); + sqruff_path.push(format!("../../target/{}/sqruff", profile)); + let temp_dir = tempfile::tempdir().unwrap(); + + let config_file = cargo_folder.join("tests/fix_return_code/fix_everything.cfg"); + let unchanged = temp_dir.path().join("unchanged.sql"); + fs::write(&unchanged, "SELECT foo AS bar FROM tabs\n").unwrap(); + let before = fs::metadata(&unchanged).unwrap().modified().unwrap(); + sleep(Duration::from_secs(1)); + + let mut cmd = Command::new(&sqruff_path); + cmd.env("HOME", cargo_folder); + cmd.arg("fix") + .arg("-f") + .arg("human") + .arg("--config") + .arg(&config_file) + .arg(&unchanged); + let output = cmd.assert(); + assert_eq!(output.get_output().status.code().unwrap(), 0); + assert_eq!( + fs::read_to_string(&unchanged).unwrap(), + "SELECT foo AS bar FROM tabs\n" + ); + assert_eq!( + fs::metadata(&unchanged).unwrap().modified().unwrap(), + before + ); + + let fixable = temp_dir.path().join("fixable.sql"); + fs::write(&fixable, "SELECT foo bar FROM tabs").unwrap(); + let mut cmd = Command::new(&sqruff_path); + cmd.env("HOME", cargo_folder); + cmd.arg("fix") + .arg("-f") + .arg("human") + .arg("--config") + .arg(&config_file) + .arg(&fixable); + let output = cmd.assert(); + assert_eq!(output.get_output().status.code().unwrap(), 0); + assert_eq!( + fs::read_to_string(&fixable).unwrap(), + "SELECT foo AS bar FROM tabs" + ); + + let config_file = cargo_folder.join("tests/fix_return_code/fix_some.cfg"); + let partially_fixable = temp_dir.path().join("partially_fixable.sql"); + fs::write(&partially_fixable, "SELECT foo bar, * FROM tabs").unwrap(); + let mut cmd = Command::new(&sqruff_path); + cmd.env("HOME", cargo_folder); + cmd.arg("fix") + .arg("-f") + .arg("human") + .arg("--config") + .arg(&config_file) + .arg(&partially_fixable); + let output = cmd.assert(); + assert_eq!(output.get_output().status.code().unwrap(), 1); + assert_eq!( + fs::read_to_string(&partially_fixable).unwrap(), + "SELECT foo AS bar, * FROM tabs" + ); +} diff --git a/crates/cli/tests/ignore_data_directory.rs b/crates/cli/tests/ignore_data_directory.rs index 355eddcb2..0f2fc5a22 100644 --- a/crates/cli/tests/ignore_data_directory.rs +++ b/crates/cli/tests/ignore_data_directory.rs @@ -99,8 +99,7 @@ fn test_ignore_data_directory_bug_reproduction() { ); } -/// Test that verifies sqruff does NOT traverse into ignored directories during file discovery -/// This test verifies the fix in the paths_from_path method +/// Test that verifies sqruff does NOT traverse into ignored directories during file discovery. #[test] fn test_directory_traversal_into_ignored_directories() { // Create a temporary directory for our test project @@ -151,8 +150,7 @@ fn test_directory_traversal_into_ignored_directories() { println!("STDOUT: {}", stdout); println!("STDERR: {}", stderr); - // Fixed behavior: sqruff should NOT discover and process files in ignored directories - // This proves that paths_from_path now respects ignore patterns during traversal + // Fixed behavior: sqruff should NOT discover and process files in ignored directories. let found_ignored_files = (1..=5).any(|i| { stdout.contains(&format!("file_{}.sql", i)) || stderr.contains(&format!("file_{}.sql", i)) }) || stdout.contains("deep_file.sql") @@ -162,19 +160,15 @@ fn test_directory_traversal_into_ignored_directories() { assert!( !found_ignored_files, "FIXED BEHAVIOR: sqruff should NOT traverse into ignored .data directories or process files within them. \ - This proves the paths_from_path method now respects ignore patterns during traversal. \ Found ignored files in output: stdout={}, stderr={}", stdout, stderr ); } -/// Test that verifies file discovery behavior through lint_paths API -/// This test uses the public lint_paths API with a dummy ignorer to test file discovery +/// Test that verifies file discovery behavior through the workspace API. #[test] -fn test_lint_paths_traverses_ignored_directories() { - use sqruff_lib::core::config::FluffConfig; - use sqruff_lib::core::linter::core::Linter; - use std::path::Path; +fn test_workspace_discovery_prunes_ignored_directories() { + use sqruff_lib::api::{PathDiscoveryOptions, Workspace}; // Create a temporary directory for our test project let temp_dir = TempDir::new().unwrap(); @@ -209,58 +203,40 @@ fn test_lint_paths_traverses_ignored_directories() { let sqruffignore_file = project_root.join(".sqruffignore"); fs::write(&sqruffignore_file, ".data\n").unwrap(); - // Create a linter instance - let mut linter = Linter::new( - FluffConfig::new(<_>::default(), None, None), - None, - None, - false, - ) - .unwrap(); - - // Create a dummy ignorer that doesn't ignore anything (to test the current broken behavior) - // In the current implementation, the ignorer is applied AFTER file discovery - let dummy_ignorer = |_path: &Path| false; // Don't ignore anything - - // Call lint_paths to test file discovery behavior - let lint_result = linter - .lint_paths( - vec![project_root.to_path_buf()], - false, // don't fix - &dummy_ignorer, - ) + let workspace = Workspace::new(project_root.to_path_buf()).unwrap(); + let options = PathDiscoveryOptions::new(project_root.to_path_buf()); + let files = workspace + .discover_sources(&[project_root.to_path_buf()], &options) .unwrap(); - // Convert to vector to access files - let files: Vec<_> = lint_result.into_iter().collect(); - println!("Linted files count: {}", files.len()); for file in &files { - println!("Linted file: {}", file.path); + println!("Linted file: {:?}", file.id); } - // Check if ignored files were processed (current broken behavior) - let found_ignored1 = files.iter().any(|file| file.path.contains("ignored1.sql")); - let found_ignored2 = files.iter().any(|file| file.path.contains("ignored2.sql")); + let found_ignored1 = files + .iter() + .any(|file| format!("{:?}", file.id).contains("ignored1.sql")); + let found_ignored2 = files + .iter() + .any(|file| format!("{:?}", file.id).contains("ignored2.sql")); let found_nested_ignored = files .iter() - .any(|file| file.path.contains("nested_ignored.sql")); - let found_regular = files.iter().any(|file| file.path.contains("regular.sql")); + .any(|file| format!("{:?}", file.id).contains("nested_ignored.sql")); + let found_regular = files + .iter() + .any(|file| format!("{:?}", file.id).contains("regular.sql")); // Regular file should always be found assert!( found_regular, "Regular file should be processed. Files: {:?}", - files.iter().map(|f| &f.path).collect::>() + files.iter().map(|f| &f.id).collect::>() ); - // Fixed behavior: lint_paths should NOT process files in ignored directories - // This proves that the underlying file discovery (paths_from_path) now respects ignore patterns - // Note: This test uses a dummy ignorer that doesn't ignore anything, so it tests the file discovery layer - // The actual ignore functionality is tested in the CLI layer tests above - let _any_ignored_found = found_ignored1 || found_ignored2 || found_nested_ignored; - - // Since this test uses a dummy ignorer that doesn't ignore anything, files should still be found - // This test verifies that the file discovery mechanism itself works correctly - // The actual ignore functionality is tested at the CLI level in the tests above + assert!( + !(found_ignored1 || found_ignored2 || found_nested_ignored), + "Ignored files should not be discovered. Files: {:?}", + files.iter().map(|f| &f.id).collect::>() + ); } diff --git a/crates/cli/tests/json/hql_file.stdout b/crates/cli/tests/json/hql_file.stdout index 88ec6ed26..01b31fb4d 100644 --- a/crates/cli/tests/json/hql_file.stdout +++ b/crates/cli/tests/json/hql_file.stdout @@ -1 +1 @@ -{"tests/lint/hql_file.hql":[{"range":{"start":{"line":1,"character":7},"end":{"line":1,"character":7}},"message":"Expected only single space before \"1\". Found \" \".","severity":"Warning","source":"sqruff","code":"LT01"},{"range":{"start":{"line":1,"character":11},"end":{"line":1,"character":11}},"message":"Files must end with a single trailing newline.","severity":"Warning","source":"sqruff","code":"LT12"}]} +{"tests/lint/hql_file.hql":[{"range":{"start":{"line":1,"character":7},"end":{"line":1,"character":10}},"message":"Expected only single space before \"1\". Found \" \".","severity":"Warning","source":"sqruff","code":"LT01"},{"range":{"start":{"line":1,"character":11},"end":{"line":1,"character":12}},"message":"Files must end with a single trailing newline.","severity":"Warning","source":"sqruff","code":"LT12"}]} diff --git a/crates/cli/tests/json/test_fail_whitespace_before_comma.stdout b/crates/cli/tests/json/test_fail_whitespace_before_comma.stdout index f5ad041a8..37e9aca03 100644 --- a/crates/cli/tests/json/test_fail_whitespace_before_comma.stdout +++ b/crates/cli/tests/json/test_fail_whitespace_before_comma.stdout @@ -1 +1 @@ -{"tests/lint/test_fail_whitespace_before_comma.sql":[{"range":{"start":{"line":1,"character":8},"end":{"line":1,"character":8}},"message":"Column expression without alias. Use explicit `AS` clause.","severity":"Warning","source":"sqruff","code":"AL03"},{"range":{"start":{"line":1,"character":9},"end":{"line":1,"character":9}},"message":"Unexpected whitespace before comma.","severity":"Warning","source":"sqruff","code":"LT01"},{"range":{"start":{"line":1,"character":11},"end":{"line":1,"character":11}},"message":"Column expression without alias. Use explicit `AS` clause.","severity":"Warning","source":"sqruff","code":"AL03"},{"range":{"start":{"line":1,"character":11},"end":{"line":1,"character":11}},"message":"Expected single whitespace between \",\" and \"4\".","severity":"Warning","source":"sqruff","code":"LT01"},{"range":{"start":{"line":1,"character":12},"end":{"line":1,"character":12}},"message":"Files must end with a single trailing newline.","severity":"Warning","source":"sqruff","code":"LT12"}]} +{"tests/lint/test_fail_whitespace_before_comma.sql":[{"range":{"start":{"line":1,"character":8},"end":{"line":1,"character":9}},"message":"Column expression without alias. Use explicit `AS` clause.","severity":"Warning","source":"sqruff","code":"AL03"},{"range":{"start":{"line":1,"character":9},"end":{"line":1,"character":10}},"message":"Unexpected whitespace before comma.","severity":"Warning","source":"sqruff","code":"LT01"},{"range":{"start":{"line":1,"character":11},"end":{"line":1,"character":12}},"message":"Column expression without alias. Use explicit `AS` clause.","severity":"Warning","source":"sqruff","code":"AL03"},{"range":{"start":{"line":1,"character":11},"end":{"line":1,"character":12}},"message":"Expected single whitespace between \",\" and \"4\".","severity":"Warning","source":"sqruff","code":"LT01"},{"range":{"start":{"line":1,"character":12},"end":{"line":1,"character":12}},"message":"Files must end with a single trailing newline.","severity":"Warning","source":"sqruff","code":"LT12"}]} diff --git a/crates/cli/tests/path_discovery.rs b/crates/cli/tests/path_discovery.rs new file mode 100644 index 000000000..ac5c712a9 --- /dev/null +++ b/crates/cli/tests/path_discovery.rs @@ -0,0 +1,97 @@ +use core::str; +use std::fs; +use std::path::{Path, PathBuf}; + +use assert_cmd::Command; + +fn main() { + ignored_directories_are_not_traversed(); + explicit_ignored_files_still_work(); + missing_paths_return_controlled_errors(); +} + +fn sqruff_path(cargo_folder: &Path) -> PathBuf { + let profile = if cfg!(debug_assertions) { + "debug" + } else { + "release" + }; + + let mut sqruff_path = PathBuf::from(cargo_folder); + sqruff_path.push(format!("../../target/{}/sqruff", profile)); + sqruff_path +} + +fn ignored_directories_are_not_traversed() { + let cargo_folder = Path::new(env!("CARGO_MANIFEST_DIR")); + let temp_dir = tempfile::tempdir().unwrap(); + let project = temp_dir.path(); + + fs::write(project.join(".sqruffignore"), "ignored/\n").unwrap(); + fs::write(project.join("regular.sql"), "SELECT 1;\n").unwrap(); + fs::create_dir_all(project.join("ignored").join("nested")).unwrap(); + fs::write( + project.join("ignored").join("nested").join("hidden.sql"), + "SELECT FROM\n", + ) + .unwrap(); + + let mut cmd = Command::new(sqruff_path(cargo_folder)); + cmd.arg("lint") + .arg("-f") + .arg("json") + .arg(project) + .current_dir(project) + .env("HOME", cargo_folder); + let output = cmd.assert(); + let stdout = str::from_utf8(&output.get_output().stdout).unwrap(); + let stderr = str::from_utf8(&output.get_output().stderr).unwrap(); + + assert!(!stdout.contains("hidden.sql")); + assert!(!stderr.contains("hidden.sql")); + assert!(stdout.contains("regular.sql") || stderr.contains("regular.sql")); +} + +fn explicit_ignored_files_still_work() { + let cargo_folder = Path::new(env!("CARGO_MANIFEST_DIR")); + let temp_dir = tempfile::tempdir().unwrap(); + let project = temp_dir.path(); + let ignored = project.join("ignored.sql"); + + fs::write(project.join(".sqruffignore"), "ignored.sql\n").unwrap(); + fs::write(&ignored, "SELECT 1;\n").unwrap(); + + let mut cmd = Command::new(sqruff_path(cargo_folder)); + cmd.arg("lint") + .arg("-f") + .arg("json") + .arg(&ignored) + .current_dir(project) + .env("HOME", cargo_folder); + let output = cmd.assert(); + let stdout = str::from_utf8(&output.get_output().stdout).unwrap(); + + assert_eq!(output.get_output().status.code().unwrap(), 1); + assert!(stdout.contains("ignored.sql")); + assert!(stdout.contains("LT01")); +} + +fn missing_paths_return_controlled_errors() { + let cargo_folder = Path::new(env!("CARGO_MANIFEST_DIR")); + let temp_dir = tempfile::tempdir().unwrap(); + let missing = temp_dir.path().join("missing.sql"); + + let mut cmd = Command::new(sqruff_path(cargo_folder)); + cmd.arg("lint") + .arg("-f") + .arg("human") + .arg(&missing) + .current_dir(temp_dir.path()) + .env("HOME", cargo_folder); + let output = cmd.assert(); + let stderr = str::from_utf8(&output.get_output().stderr).unwrap(); + + assert_eq!(output.get_output().status.code().unwrap(), 1); + assert!(stderr.contains("Specified path does not exist")); + assert!(!stderr.contains("panicked at")); +} diff --git a/crates/cli/tests/ui.rs b/crates/cli/tests/ui.rs index 1b028a2ba..5e194baa1 100644 --- a/crates/cli/tests/ui.rs +++ b/crates/cli/tests/ui.rs @@ -52,8 +52,12 @@ fn main() { let exit_code_str = output.status.code().unwrap().to_string(); let test_dir_str = lint_dir.to_string_lossy().to_string(); - let stderr_normalized: String = stderr_str.replace(&test_dir_str, "tests/lint"); - let stdout_normalized: String = stdout_str.replace(&test_dir_str, "tests/lint"); + let stderr_normalized: String = stderr_str + .replace(&test_dir_str, "tests/lint") + .replace('\\', "/"); + let stdout_normalized: String = stdout_str + .replace(&test_dir_str, "tests/lint") + .replace('\\', "/"); expect_file![expected_output_path_stderr].assert_eq(&stderr_normalized); expect_file![expected_output_path_stdout].assert_eq(&stdout_normalized); diff --git a/crates/cli/tests/ui_github.rs b/crates/cli/tests/ui_github.rs index 0db9e5d0e..b1d0bc628 100644 --- a/crates/cli/tests/ui_github.rs +++ b/crates/cli/tests/ui_github.rs @@ -55,8 +55,12 @@ fn main() { let exit_code_str = output.status.code().unwrap().to_string(); let test_dir_str = lint_dir.to_string_lossy().to_string(); - let stderr_normalized: String = stderr_str.replace(&test_dir_str, "tests/lint"); - let stdout_normalized: String = stdout_str.replace(&test_dir_str, "tests/lint"); + let stderr_normalized: String = stderr_str + .replace(&test_dir_str, "tests/lint") + .replace('\\', "/"); + let stdout_normalized: String = stdout_str + .replace(&test_dir_str, "tests/lint") + .replace('\\', "/"); expect_file![expected_output_path_stderr].assert_eq(&stderr_normalized); expect_file![expected_output_path_stdout].assert_eq(&stdout_normalized); diff --git a/crates/cli/tests/ui_json.rs b/crates/cli/tests/ui_json.rs index 4184cb003..a52075394 100644 --- a/crates/cli/tests/ui_json.rs +++ b/crates/cli/tests/ui_json.rs @@ -56,8 +56,17 @@ fn main() { let exit_code_str = output.status.code().unwrap().to_string(); let test_dir_str = lint_dir.to_string_lossy().to_string(); - let stderr_normalized: String = stderr_str.replace(&test_dir_str, "tests/lint"); - let stdout_normalized: String = stdout_str.replace(&test_dir_str, "tests/lint"); + let test_dir_json = test_dir_str.replace('\\', "\\\\"); + let stderr_normalized: String = stderr_str + .replace(&test_dir_json, "tests/lint") + .replace(&test_dir_str, "tests/lint") + .replace("tests/lint\\\\", "tests/lint/") + .replace("tests/lint\\", "tests/lint/"); + let stdout_normalized: String = stdout_str + .replace(&test_dir_json, "tests/lint") + .replace(&test_dir_str, "tests/lint") + .replace("tests/lint\\\\", "tests/lint/") + .replace("tests/lint\\", "tests/lint/"); expect_file![expected_output_path_stderr].assert_eq(&stderr_normalized); expect_file![expected_output_path_stdout].assert_eq(&stdout_normalized); diff --git a/crates/lib-core/src/errors.rs b/crates/lib-core/src/errors.rs index 8ddc39dd1..1ece0af69 100644 --- a/crates/lib-core/src/errors.rs +++ b/crates/lib-core/src/errors.rs @@ -154,6 +154,7 @@ impl SQLParseError { impl From for SQLBaseError { fn from(value: SQLParseError) -> Self { let (mut line_no, mut line_pos) = Default::default(); + let mut source_slice = Default::default(); let pos_marker = value .segment @@ -162,11 +163,13 @@ impl From for SQLBaseError { if let Some(pos_marker) = pos_marker { (line_no, line_pos) = pos_marker.source_position(); + source_slice = pos_marker.source_slice.clone(); } Self::default().config(|this| { this.line_no = line_no; this.line_pos = line_pos; + this.source_slice = source_slice; this.description = value.description; }) } @@ -188,6 +191,19 @@ impl SQLLexError { } } +impl From for SQLBaseError { + fn from(value: SQLLexError) -> Self { + SQLBaseError { + fixable: false, + line_no: value.position_marker.line_no(), + line_pos: value.position_marker.line_pos(), + description: value.message, + rule: None, + source_slice: value.position_marker.source_slice.clone(), + } + } +} + #[derive(Debug, Error)] #[error("{value}")] pub struct SQLFluffSkipFile { diff --git a/crates/lib-wasm/BUILD.bazel b/crates/lib-wasm/BUILD.bazel index b67f6c8fa..f6553df26 100644 --- a/crates/lib-wasm/BUILD.bazel +++ b/crates/lib-wasm/BUILD.bazel @@ -16,7 +16,6 @@ rust_library( # Crate deps for WASM build - explicit list to avoid pyo3 which doesn't support WASM WASM_CRATE_NAMES = [ - "line-index", "serde", "serde_yaml", "wasm-bindgen", diff --git a/crates/lib-wasm/Cargo.toml b/crates/lib-wasm/Cargo.toml index 5ce560d76..f9502a9ea 100644 --- a/crates/lib-wasm/Cargo.toml +++ b/crates/lib-wasm/Cargo.toml @@ -13,7 +13,6 @@ crate-type = ["cdylib", "rlib"] bench = false [dependencies] -line-index = "0.1.1" serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.9" sqruff-lib.workspace = true diff --git a/crates/lib-wasm/src/lib.rs b/crates/lib-wasm/src/lib.rs index 7fd890350..ba22b1d7e 100644 --- a/crates/lib-wasm/src/lib.rs +++ b/crates/lib-wasm/src/lib.rs @@ -1,11 +1,13 @@ -use line_index::LineIndex; use lineage::{Lineage, Node}; use serde::Serialize; +use sqruff_lib::api::{ + Engine, EngineOptions, LintDiagnostic, Mode, ParseErrors, Source, SourceId, SqruffError, +}; use sqruff_lib::core::config::FluffConfig; use sqruff_lib::core::linter::core::Linter as SqruffLinter; -use sqruff_lib::templaters::RAW_TEMPLATER; use sqruff_lib_core::parser::segments::{ErasedSegment, Tables}; use sqruff_lib_core::parser::{IndentationConfig, Parser}; +use std::borrow::Cow; use wasm_bindgen::prelude::*; #[wasm_bindgen] @@ -28,17 +30,32 @@ impl Diagnostic { #[wasm_bindgen] pub struct Linter { + engine: Engine, base: SqruffLinter, } -#[wasm_bindgen] -#[derive(PartialEq, Eq)] -pub enum Tool { - Format = "Format", - Cst = "Cst", - Lineage = "Lineage", - Templater = "Templater", - Lexer = "Lexer", +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Tool { + Format, + Cst, + Lineage, + Templater, + Lexer, +} + +impl TryFrom<&str> for Tool { + type Error = &'static str; + + fn try_from(value: &str) -> std::result::Result { + match value { + "Format" => Ok(Self::Format), + "Cst" => Ok(Self::Cst), + "Lineage" => Ok(Self::Lineage), + "Templater" => Ok(Self::Templater), + "Lexer" => Ok(Self::Lexer), + _ => Err("unsupported tool"), + } + } } #[wasm_bindgen] @@ -72,25 +89,68 @@ impl Result { #[wasm_bindgen] impl Linter { #[wasm_bindgen(constructor)] - pub fn new(source: &str) -> Self { - let config = FluffConfig::from_source(source, None); - let templater = SqruffLinter::get_templater(&config).unwrap_or(&RAW_TEMPLATER); - Self { - base: SqruffLinter::new(config, None, Some(templater), true).unwrap(), - } + pub fn new(source: &str) -> std::result::Result { + let config = FluffConfig::try_from_source(source, None) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + let engine = Engine::new( + config.clone(), + EngineOptions { + parse_errors: ParseErrors::Include, + }, + ) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + let base = SqruffLinter::new(config, None, ParseErrors::Include) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + Ok(Self { engine, base }) } #[wasm_bindgen] - pub fn check(&self, sql: &str, tool: Tool) -> Result { - let line_index = LineIndex::new(sql); + pub fn check(&self, sql: &str, tool: &str) -> Result { + let Ok(tool) = Tool::try_from(tool) else { + return Result { + diagnostics: Vec::new(), + secondary: format!("Error: unsupported tool: {tool}"), + }; + }; + match tool { + Tool::Format => self.check_with_engine(sql, Mode::Fix), + Tool::Cst | Tool::Lineage | Tool::Templater | Tool::Lexer => { + self.check_developer_tool(sql, tool) + } + } + } + + fn check_with_engine(&self, sql: &str, mode: Mode) -> Result { + let report = match self.engine_report(sql, mode) { + Ok(report) => report, + Err(e) => return result_from_error(e), + }; + + Result { + diagnostics: diagnostics_from_lint_diagnostics(&report.diagnostics), + secondary: report.fixed_source.unwrap_or_default(), + } + } + + fn check_developer_tool(&self, sql: &str, tool: Tool) -> Result { + let report = match self.engine_report(sql, Mode::Check) { + Ok(report) => report, + Err(e) => return result_from_error(e), + }; let tables = Tables::default(); - let parsed = self.base.parse_string(&tables, sql, None).unwrap(); + let parsed = match self.base.parse_string(&tables, sql, None) { + Ok(parsed) => parsed, + Err(e) => return result_from_str(&e.value), + }; - let templated = self + let templated = match self .base .render_string(sql, "".to_string(), self.base.config()) - .unwrap(); + { + Ok(t) => t, + Err(e) => return result_from_str(&e.value), + }; let cst = if tool == Tool::Cst { parsed.tree.clone() @@ -98,42 +158,11 @@ impl Linter { None }; - let result = match self.base.lint_parsed(&tables, parsed, tool == Tool::Format) { - Ok(result) => result, - Err(e) => { - return Result { - diagnostics: vec![Diagnostic { - message: e.value, - start_line_number: 1, - start_column: 1, - end_line_number: 1, - end_column: 1, - }], - secondary: String::new(), - }; - } - }; - let violations = result.violations(); - - let diagnostics = violations - .iter() - .map(|violation| { - let start = line_index.line_col(violation.source_slice.start.try_into().unwrap()); - let end = line_index.line_col(violation.source_slice.end.try_into().unwrap()); - - Diagnostic { - message: violation.description.clone(), - start_line_number: start.line + 1, - start_column: start.col + 1, - end_line_number: end.line + 1, - end_column: end.col + 1, - } - }) - .collect(); - let secondary = match tool { - Tool::Format => result.fix_string(), - Tool::Cst => cst.unwrap().stringify(false), + Tool::Cst => match cst { + Some(cst) => cst.stringify(false), + None => String::new(), + }, Tool::Lineage => { let parser = Parser::new( self.base.config().get_dialect(), @@ -150,14 +179,61 @@ impl Linter { let (segments, _errors) = lexer.lex(&lex_tables, sql); format_lexer_output(&segments) } - Tool::__Invalid => String::from("Error: unsupported tool"), + Tool::Format => String::new(), }; Result { - diagnostics, + diagnostics: diagnostics_from_lint_diagnostics(&report.diagnostics), secondary, } } + + fn engine_report( + &self, + sql: &str, + mode: Mode, + ) -> std::result::Result { + let source = Source { + id: SourceId::Stdin, + text: Cow::Borrowed(sql), + }; + + match mode { + Mode::Check => self.engine.check_source(source), + Mode::Fix => self.engine.fix_source(source), + } + } +} + +fn diagnostics_from_lint_diagnostics(diagnostics: &[LintDiagnostic]) -> Vec { + diagnostics.iter().map(to_wasm_diagnostic).collect() +} + +fn to_wasm_diagnostic(diagnostic: &LintDiagnostic) -> Diagnostic { + Diagnostic { + message: diagnostic.message.clone(), + start_line_number: diagnostic.line as u32, + start_column: diagnostic.column as u32, + end_line_number: diagnostic.end_line as u32, + end_column: diagnostic.end_column as u32, + } +} + +fn result_from_error(error: SqruffError) -> Result { + result_from_str(&error.to_string()) +} + +fn result_from_str(message: &str) -> Result { + Result { + diagnostics: vec![Diagnostic { + message: message.to_string(), + start_line_number: 1, + start_column: 1, + end_line_number: 1, + end_column: 1, + }], + secondary: String::new(), + } } fn print_tree( diff --git a/crates/lib/BUILD.bazel b/crates/lib/BUILD.bazel index 1420774f9..ab88059bb 100644 --- a/crates/lib/BUILD.bazel +++ b/crates/lib/BUILD.bazel @@ -53,8 +53,8 @@ WASM_CRATE_NAMES = [ "serde_json", "smol_str", "strum", + "thiserror", "toml", - "walkdir", ] WASM_PROC_MACRO_CRATE_NAMES = [ diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index 16a0b81ab..341ef1715 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -48,7 +48,6 @@ sqruff-lib-dialects.workspace = true fancy-regex = "0.18.0" itertools = "0.15.0" regex = "1" -walkdir = "2.5.0" enum_dispatch = "0.3.13" indexmap.workspace = true common-path = "1.0.0" @@ -56,6 +55,7 @@ configparser = "3.2.0" log.workspace = true pretty_assertions = "1.4.0" hashbrown.workspace = true +ignore = "0.4.23" lazy-regex = "3.2.0" rayon = "1.12.0" smol_str = "0.3.1" @@ -65,6 +65,7 @@ strum = "0.28.0" serde = { version = "1.0", features = ["derive"] } serde_yaml = { version = "0.9.34", optional = true } serde_json = "1" +thiserror = "2" toml = "0.9" # Only activated on python diff --git a/crates/lib/benches/depth_map.rs b/crates/lib/benches/depth_map.rs index 9a37bd528..79cd991ea 100644 --- a/crates/lib/benches/depth_map.rs +++ b/crates/lib/benches/depth_map.rs @@ -1,4 +1,5 @@ use criterion::{Criterion, criterion_group, criterion_main}; +use sqruff_lib::api::ParseErrors; use sqruff_lib::core::config::FluffConfig; use sqruff_lib::core::linter::core::Linter; use sqruff_lib::utils::reflow::depth_map::DepthMap; @@ -71,7 +72,7 @@ SELECT construct_depth_info('uuid-2'); SELECT construct_depth_info('uuid-3');"#; fn depth_map(c: &mut Criterion) { - let linter = Linter::new(FluffConfig::default(), None, None, false).unwrap(); + let linter = Linter::new(FluffConfig::default(), None, ParseErrors::Suppress).unwrap(); let tables = Tables::default(); let tree = linter .parse_string(&tables, COMPLEX_QUERY, None) diff --git a/crates/lib/benches/fix.rs b/crates/lib/benches/fix.rs index 5c8e9accb..0a1a089de 100644 --- a/crates/lib/benches/fix.rs +++ b/crates/lib/benches/fix.rs @@ -1,4 +1,5 @@ use criterion::{Criterion, criterion_group, criterion_main}; +use sqruff_lib::api::{Mode, ParseErrors}; use sqruff_lib::core::linter::core::Linter; use sqruff_lib_core::parser::segments::Tables; use std::hint::black_box; @@ -69,8 +70,7 @@ fn fix(c: &mut Criterion) { let linter = Linter::new( sqruff_lib::core::config::FluffConfig::default(), None, - None, - false, + ParseErrors::Suppress, ) .unwrap(); for (name, source) in passes { @@ -78,7 +78,7 @@ fn fix(c: &mut Criterion) { let parsed = linter.parse_string(&tables, &source, None).unwrap(); c.bench_function(name, |b| { - b.iter(|| black_box(linter.lint_parsed(&tables, parsed.clone(), true))); + b.iter(|| black_box(linter.lint_parsed(&tables, parsed.clone(), Mode::Fix))); }); } } diff --git a/crates/lib/src/api.rs b/crates/lib/src/api.rs new file mode 100644 index 000000000..6c7cb8d02 --- /dev/null +++ b/crates/lib/src/api.rs @@ -0,0 +1,15 @@ +pub mod diagnostic; +pub mod engine; +pub mod error; +pub mod options; +pub mod report; +pub mod source; +pub mod workspace; + +pub use diagnostic::LintDiagnostic; +pub use engine::Engine; +pub use error::SqruffError; +pub use options::{EngineOptions, Mode, ParseErrors, RunRequest}; +pub use report::{FileReport, RunReport, SkipReason}; +pub use source::{Source, SourceId}; +pub use workspace::{IgnoreFile, IgnoreMatcher, PathDiscoveryOptions, Workspace, discover_paths}; diff --git a/crates/lib/src/api/diagnostic.rs b/crates/lib/src/api/diagnostic.rs new file mode 100644 index 000000000..1da4854b9 --- /dev/null +++ b/crates/lib/src/api/diagnostic.rs @@ -0,0 +1,207 @@ +use std::ops::Range; + +use sqruff_lib_core::errors::SQLBaseError; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LintDiagnostic { + pub message: String, + pub code: Option, + pub line: usize, + pub column: usize, + pub end_line: usize, + pub end_column: usize, + pub source_range: Range, + pub fixable: bool, +} + +impl LintDiagnostic { + pub(crate) fn from_sql_error(error: &SQLBaseError, source: &str) -> Self { + let source_range = canonical_source_range(error, source); + let (line, column) = if error.line_no > 0 && error.line_pos > 0 { + (error.line_no, error.line_pos) + } else { + line_column_for_byte(source, source_range.start) + }; + let (end_line, end_column) = line_column_for_byte(source, source_range.end); + + Self { + message: error.desc().to_string(), + code: error.rule.as_ref().map(|rule| rule.code.to_string()), + line, + column, + end_line, + end_column, + source_range, + fixable: error.fixable, + } + } +} + +fn canonical_source_range(error: &SQLBaseError, source: &str) -> Range { + let mut range = clamp_range(error.source_slice.clone(), source); + + if range.is_empty() + && error.line_no > 0 + && error.line_pos > 0 + && let Some(start) = byte_for_line_column(source, error.line_no, error.line_pos) + { + range = start..next_meaningful_boundary(source, start); + } + + range +} + +fn clamp_range(range: Range, source: &str) -> Range { + let start = previous_char_boundary(source, range.start.min(source.len())); + let end = previous_char_boundary(source, range.end.min(source.len())); + + if start <= end { + start..end + } else { + start..start + } +} + +fn byte_for_line_column(source: &str, line: usize, column: usize) -> Option { + let line_start = line_start_byte(source, line)?; + let line_end = source[line_start..] + .find('\n') + .map_or(source.len(), |offset| line_start + offset); + let target_chars = column.saturating_sub(1); + + let mut byte = line_end; + for (index, (offset, _)) in source[line_start..line_end].char_indices().enumerate() { + if index == target_chars { + byte = line_start + offset; + break; + } + } + + Some(byte) +} + +fn line_start_byte(source: &str, line: usize) -> Option { + if line == 0 { + return None; + } + + let mut current_line = 1; + let mut line_start = 0; + + for (idx, byte) in source.bytes().enumerate() { + if current_line == line { + return Some(line_start); + } + + if byte == b'\n' { + current_line += 1; + line_start = idx + 1; + } + } + + (current_line == line).then_some(line_start) +} + +fn next_meaningful_boundary(source: &str, start: usize) -> usize { + if start >= source.len() { + return start; + } + + let start = previous_char_boundary(source, start); + let mut chars = source[start..].char_indices(); + let Some((_, first)) = chars.next() else { + return start; + }; + + if first == '\n' { + return start + first.len_utf8(); + } + + if first.is_alphanumeric() || first == '_' { + let mut end = start + first.len_utf8(); + for (offset, ch) in chars { + if ch.is_alphanumeric() || ch == '_' { + end = start + offset + ch.len_utf8(); + } else { + break; + } + } + return end; + } + + start + first.len_utf8() +} + +fn line_column_for_byte(source: &str, byte_offset: usize) -> (usize, usize) { + let byte_offset = previous_char_boundary(source, byte_offset.min(source.len())); + let mut line = 1; + let mut line_start = 0; + + for (idx, byte) in source.bytes().enumerate() { + if idx >= byte_offset { + break; + } + + if byte == b'\n' { + line += 1; + line_start = idx + 1; + } + } + + let column = source[line_start..byte_offset].chars().count() + 1; + (line, column) +} + +fn previous_char_boundary(source: &str, mut offset: usize) -> usize { + while offset > 0 && !source.is_char_boundary(offset) { + offset -= 1; + } + offset +} + +#[cfg(test)] +mod tests { + use sqruff_lib_core::errors::{ErrorStructRule, SQLBaseError}; + + use super::*; + + #[test] + fn from_sql_error_uses_existing_source_range() { + let error = SQLBaseError { + description: "bad spacing".into(), + rule: Some(ErrorStructRule { + name: "layout.spacing", + code: "LT01", + }), + line_no: 1, + line_pos: 7, + source_slice: 6..9, + fixable: true, + }; + + let diagnostic = LintDiagnostic::from_sql_error(&error, "select 1\n"); + + assert_eq!(diagnostic.code.as_deref(), Some("LT01")); + assert_eq!(diagnostic.source_range, 6..9); + assert_eq!((diagnostic.line, diagnostic.column), (1, 7)); + assert_eq!((diagnostic.end_line, diagnostic.end_column), (1, 10)); + assert!(diagnostic.fixable); + } + + #[test] + fn from_sql_error_expands_empty_range_from_line_column() { + let error = SQLBaseError { + description: "unparsable".into(), + line_no: 2, + line_pos: 1, + source_slice: 0..0, + ..Default::default() + }; + + let diagnostic = LintDiagnostic::from_sql_error(&error, "select 1\nfrom"); + + assert_eq!(diagnostic.source_range, 9..13); + assert_eq!((diagnostic.line, diagnostic.column), (2, 1)); + assert_eq!((diagnostic.end_line, diagnostic.end_column), (2, 5)); + } +} diff --git a/crates/lib/src/api/engine.rs b/crates/lib/src/api/engine.rs new file mode 100644 index 000000000..8c4034c30 --- /dev/null +++ b/crates/lib/src/api/engine.rs @@ -0,0 +1,352 @@ +use crate::core::config::FluffConfig; +use crate::core::linter::common::RenderedSource; +use crate::core::linter::core::Linter; +use crate::core::linter::linted_file::LintedFile; + +use super::{ + EngineOptions, FileReport, LintDiagnostic, Mode, RunReport, RunRequest, Source, SourceId, + SqruffError, +}; + +pub struct Engine { + inner: Linter, +} + +impl Engine { + pub fn new(config: FluffConfig, options: EngineOptions) -> Result { + let inner = Linter::new(config, None, options.parse_errors)?; + + Ok(Self { inner }) + } + + pub fn check_source(&self, source: Source<'_>) -> Result { + self.lint_source(source, Mode::Check) + } + + pub fn fix_source(&self, source: Source<'_>) -> Result { + self.lint_source(source, Mode::Fix) + } + + pub fn run(&self, request: RunRequest<'_>) -> Result { + let rendered = self + .inner + .render_sources(&request.sources, self.inner.config()) + .map_err(SqruffError::from)?; + let files = rendered + .into_iter() + .map(|rendered| self.lint_rendered_source(rendered, request.mode)) + .collect::, _>>()?; + + Ok(RunReport { files }) + } + + pub fn reload_config(&mut self, config: FluffConfig) -> Result<(), SqruffError> { + let parse_errors = self.inner.parse_errors(); + self.inner = Linter::new(config, None, parse_errors)?; + + Ok(()) + } + + fn lint_source(&self, source: Source<'_>, mode: Mode) -> Result { + let rendered = self + .inner + .render_source(source.text.as_ref(), &source.id, self.inner.config()) + .map_err(SqruffError::from)?; + self.lint_rendered_source(rendered, mode) + } + + fn lint_rendered_source( + &self, + rendered: RenderedSource, + mode: Mode, + ) -> Result { + let (source_id, rendered) = match rendered { + RenderedSource::Rendered { + source_id, + rendered, + } => (source_id, rendered), + RenderedSource::Skipped { source_id, reason } => { + return Ok(FileReport { + source_id, + diagnostics: Vec::new(), + fixed_source: None, + skipped: Some(reason), + }); + } + }; + let linted_file = self + .inner + .lint_rendered(rendered, mode) + .map_err(|error| SqruffError::Lint(error.value))?; + + Ok(file_report_from_linted_file(linted_file, source_id, mode)) + } +} + +fn file_report_from_linted_file( + linted_file: LintedFile, + source_id: SourceId, + mode: Mode, +) -> FileReport { + let diagnostics = linted_file + .violations() + .iter() + .map(|error| LintDiagnostic::from_sql_error(error, linted_file.source())) + .collect(); + let fixed_source = matches!(mode, Mode::Fix).then(|| linted_file.fix_string()); + + FileReport { + source_id, + diagnostics, + fixed_source, + skipped: None, + } +} + +#[cfg(test)] +mod tests { + use std::borrow::Cow; + use std::sync::Mutex; + + use crate::api::{ParseErrors, SkipReason}; + use crate::templaters::{ + ProcessingMode, Templater, TemplaterError, TemplaterInput, TemplaterOutput, + TemplaterRuntime, + }; + + use super::*; + + static SKIPPING_TEMPLATER: SkippingTemplater = SkippingTemplater; + static RECORDING_BATCH_TEMPLATER: RecordingBatchTemplater = RecordingBatchTemplater { + calls: Mutex::new(Vec::new()), + }; + + struct SkippingTemplater; + struct RecordingBatchTemplater { + calls: Mutex>, + } + + impl Templater for SkippingTemplater { + fn name(&self) -> &'static str { + "skipping" + } + + fn description(&self) -> &'static str { + "test templater that skips every source" + } + + fn processing_mode(&self) -> ProcessingMode { + ProcessingMode::Sequential + } + + fn process( + &self, + files: &[TemplaterInput<'_>], + _config: &FluffConfig, + ) -> Vec> { + files + .iter() + .map(|_| { + Ok(TemplaterOutput::Skipped(SkipReason { + message: "disabled by templater".into(), + })) + }) + .collect() + } + } + + impl RecordingBatchTemplater { + fn take_calls(&self) -> Vec { + std::mem::take(&mut *self.calls.lock().unwrap()) + } + } + + impl Templater for RecordingBatchTemplater { + fn name(&self) -> &'static str { + "recording-batch" + } + + fn description(&self) -> &'static str { + "test batch templater that records batch sizes" + } + + fn processing_mode(&self) -> ProcessingMode { + ProcessingMode::Batch + } + + fn process( + &self, + files: &[TemplaterInput<'_>], + _config: &FluffConfig, + ) -> Vec> { + self.calls.lock().unwrap().push(files.len()); + files + .iter() + .map(|file| { + sqruff_lib_core::templaters::TemplatedFile::new( + file.source.to_string(), + match file.source_id { + SourceId::Stdin => "".to_string(), + SourceId::Path(path) => path.to_string_lossy().into_owned(), + SourceId::Virtual(name) => name.clone(), + }, + None, + None, + None, + ) + .map(TemplaterOutput::Rendered) + .map_err(|error| { + TemplaterError::Failed(sqruff_lib_core::errors::SQLFluffUserError::new( + format!("templater error: {error}"), + )) + }) + }) + .collect() + } + } + + fn test_engine() -> Engine { + let config = FluffConfig::try_from_source( + r#" +[sqruff] +dialect = ansi +rules = LT01 +"#, + None, + ) + .unwrap(); + + Engine::new( + config, + EngineOptions { + parse_errors: ParseErrors::Include, + }, + ) + .unwrap() + } + + #[test] + fn check_source_reports_diagnostics() { + let report = test_engine() + .check_source(Source { + id: SourceId::Virtual("query.sql".into()), + text: Cow::Borrowed("select 1\n"), + }) + .unwrap(); + + assert_eq!(report.source_id, SourceId::Virtual("query.sql".into())); + assert!(report.fixed_source.is_none()); + assert!(report.skipped.is_none()); + assert!( + report + .diagnostics + .iter() + .any(|diagnostic| diagnostic.code.as_deref() == Some("LT01")) + ); + } + + #[test] + fn fix_source_returns_fixed_source() { + let report = test_engine() + .fix_source(Source { + id: SourceId::Stdin, + text: Cow::Borrowed("select 1\n"), + }) + .unwrap(); + + assert_eq!(report.source_id, SourceId::Stdin); + assert_eq!(report.fixed_source.as_deref(), Some("select 1\n")); + assert!( + report + .diagnostics + .iter() + .any(|diagnostic| diagnostic.code.as_deref() == Some("LT01")) + ); + } + + #[test] + fn check_source_reports_templater_skip() { + let config = FluffConfig::try_from_source( + r#" +[sqruff] +dialect = ansi +"#, + None, + ) + .unwrap(); + let engine = Engine { + inner: Linter::new( + config, + Some(TemplaterRuntime::custom(&SKIPPING_TEMPLATER)), + ParseErrors::Include, + ) + .unwrap(), + }; + + let report = engine + .check_source(Source { + id: SourceId::Virtual("disabled.sql".into()), + text: Cow::Borrowed("select 1\n"), + }) + .unwrap(); + + assert_eq!(report.source_id, SourceId::Virtual("disabled.sql".into())); + assert!(report.diagnostics.is_empty()); + assert!(report.fixed_source.is_none()); + assert_eq!( + report + .skipped + .as_ref() + .map(|reason| reason.message.as_str()), + Some("disabled by templater") + ); + } + + #[test] + fn run_batches_sources_for_batch_templaters() { + RECORDING_BATCH_TEMPLATER.take_calls(); + let config = FluffConfig::try_from_source( + r#" +[sqruff] +dialect = ansi +"#, + None, + ) + .unwrap(); + let engine = Engine { + inner: Linter::new( + config, + Some(TemplaterRuntime::custom(&RECORDING_BATCH_TEMPLATER)), + ParseErrors::Include, + ) + .unwrap(), + }; + + let report = engine + .run(RunRequest { + mode: Mode::Check, + sources: vec![ + Source { + id: SourceId::Virtual("one.sql".into()), + text: Cow::Borrowed("select 1\n"), + }, + Source { + id: SourceId::Virtual("two.sql".into()), + text: Cow::Borrowed("select 2\r\n"), + }, + ], + }) + .unwrap(); + + assert_eq!(RECORDING_BATCH_TEMPLATER.take_calls(), vec![2]); + assert_eq!(report.files.len(), 2); + assert_eq!( + report.files[0].source_id, + SourceId::Virtual("one.sql".into()) + ); + assert_eq!( + report.files[1].source_id, + SourceId::Virtual("two.sql".into()) + ); + } +} diff --git a/crates/lib/src/api/error.rs b/crates/lib/src/api/error.rs new file mode 100644 index 000000000..cdac09dcd --- /dev/null +++ b/crates/lib/src/api/error.rs @@ -0,0 +1,38 @@ +use std::path::PathBuf; + +#[derive(Debug, thiserror::Error)] +pub enum SqruffError { + #[error("config error: {0}")] + Config(String), + + #[error("templater error: {0}")] + Templater(String), + + #[error("parse error: {0}")] + Parse(String), + + #[error("lint error: {0}")] + Lint(String), + + #[error("I/O error for {path}: {source}")] + Io { + path: PathBuf, + #[source] + source: std::io::Error, + }, + + #[error("unsupported operation: {0}")] + Unsupported(&'static str), +} + +impl SqruffError { + pub fn message(&self) -> String { + match self { + Self::Config(message) + | Self::Templater(message) + | Self::Parse(message) + | Self::Lint(message) => message.clone(), + Self::Io { .. } | Self::Unsupported(_) => self.to_string(), + } + } +} diff --git a/crates/lib/src/api/options.rs b/crates/lib/src/api/options.rs new file mode 100644 index 000000000..b2aae1f12 --- /dev/null +++ b/crates/lib/src/api/options.rs @@ -0,0 +1,32 @@ +use super::Source; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Mode { + Check, + Fix, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ParseErrors { + Suppress, + Include, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct EngineOptions { + pub parse_errors: ParseErrors, +} + +impl Default for EngineOptions { + fn default() -> Self { + Self { + parse_errors: ParseErrors::Suppress, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RunRequest<'a> { + pub mode: Mode, + pub sources: Vec>, +} diff --git a/crates/lib/src/api/report.rs b/crates/lib/src/api/report.rs new file mode 100644 index 000000000..2749d9a8d --- /dev/null +++ b/crates/lib/src/api/report.rs @@ -0,0 +1,19 @@ +use super::{LintDiagnostic, SourceId}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RunReport { + pub files: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FileReport { + pub source_id: SourceId, + pub diagnostics: Vec, + pub fixed_source: Option, + pub skipped: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SkipReason { + pub message: String, +} diff --git a/crates/lib/src/api/source.rs b/crates/lib/src/api/source.rs new file mode 100644 index 000000000..03546a554 --- /dev/null +++ b/crates/lib/src/api/source.rs @@ -0,0 +1,15 @@ +use std::borrow::Cow; +use std::path::PathBuf; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SourceId { + Stdin, + Path(PathBuf), + Virtual(String), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Source<'a> { + pub id: SourceId, + pub text: Cow<'a, str>, +} diff --git a/crates/lib/src/api/workspace.rs b/crates/lib/src/api/workspace.rs new file mode 100644 index 000000000..130db98f1 --- /dev/null +++ b/crates/lib/src/api/workspace.rs @@ -0,0 +1,316 @@ +use std::borrow::Cow; +use std::collections::BTreeSet; +use std::path::{Path, PathBuf}; +use std::sync::OnceLock; + +use ignore::gitignore::Gitignore; +use sqruff_lib_core::helpers; + +use super::{RunReport, Source, SourceId, SqruffError}; + +const DEFAULT_IGNORE_FILE_NAME: &str = ".sqruffignore"; +const DEFAULT_SQL_FILE_EXTS: &[&str] = &[".sql", ".sql.j2", ".dml", ".ddl"]; + +pub trait IgnoreMatcher: Send + Sync { + fn is_ignored(&self, path: &Path) -> bool; +} + +impl IgnoreMatcher for F +where + F: Fn(&Path) -> bool + Send + Sync + ?Sized, +{ + fn is_ignored(&self, path: &Path) -> bool { + self(path) + } +} + +pub struct IgnoreFile { + ignore: Gitignore, +} + +impl IgnoreFile { + pub fn from_root(root: &Path) -> Result { + Self::from_root_with_name(root, DEFAULT_IGNORE_FILE_NAME) + } + + pub fn from_root_with_name(root: &Path, ignore_file_name: &str) -> Result { + let ignore_path = root.join(ignore_file_name); + if !ignore_path.exists() { + return Ok(Self { + ignore: Gitignore::empty(), + }); + } + + let (ignore, err) = Gitignore::new(ignore_path); + if let Some(err) = err { + return Err(SqruffError::Config(err.to_string())); + } + + Ok(Self { ignore }) + } +} + +impl IgnoreMatcher for IgnoreFile { + fn is_ignored(&self, path: &Path) -> bool { + let is_dir = path.is_dir(); + let match_result = self.ignore.matched(path, is_dir); + let is_ignored = match_result.is_ignore(); + + if is_ignored { + let path_type = if is_dir { "directory" } else { "file" }; + log::debug!( + "Ignoring {} '{}' due to ignore pattern", + path_type, + path.display() + ); + + if let Some(pattern) = match_result.inner() { + log::debug!("Matched ignore pattern: '{}'", pattern.original()); + } + } + + is_ignored + } +} + +pub struct Workspace { + pub root: PathBuf, + pub ignore_file: IgnoreFile, +} + +impl Workspace { + pub fn new(root: PathBuf) -> Result { + let ignore_file = IgnoreFile::from_root(&root)?; + Ok(Self { root, ignore_file }) + } + + pub fn discover_sources( + &self, + paths: &[PathBuf], + options: &PathDiscoveryOptions<'_>, + ) -> Result>, SqruffError> { + let effective_ignorer = options.ignorer.unwrap_or(&self.ignore_file); + let options = PathDiscoveryOptions { + ignore_file_name: options.ignore_file_name, + ignore_non_existent_files: options.ignore_non_existent_files, + ignore_files: options.ignore_files, + working_dir: options.working_dir.clone(), + file_exts: options.file_exts, + ignorer: Some(effective_ignorer), + }; + let mut sources = Vec::new(); + let paths = if paths.is_empty() { + vec![self.root.clone()] + } else { + paths.to_vec() + }; + + for path in paths { + if path.is_file() { + sources.push(source_from_path(path)?); + continue; + } + + for path in discover_paths(&path, &options)? { + sources.push(source_from_path(path)?); + } + } + + Ok(sources) + } + + pub fn apply_fixes(&self, report: &RunReport) -> Result<(), SqruffError> { + for file in &report.files { + if file + .diagnostics + .iter() + .any(|diagnostic| diagnostic.code.is_none()) + { + continue; + } + + let Some(fixed_source) = &file.fixed_source else { + continue; + }; + + let SourceId::Path(path) = &file.source_id else { + continue; + }; + + if std::fs::read_to_string(path).is_ok_and(|current| current == *fixed_source) { + continue; + } + + std::fs::write(path, fixed_source).map_err(|source| SqruffError::Io { + path: path.clone(), + source, + })?; + } + + Ok(()) + } +} + +pub struct PathDiscoveryOptions<'a> { + pub ignore_file_name: &'a str, + pub ignore_non_existent_files: bool, + pub ignore_files: bool, + pub working_dir: PathBuf, + pub file_exts: &'a [String], + pub ignorer: Option<&'a dyn IgnoreMatcher>, +} + +impl<'a> PathDiscoveryOptions<'a> { + pub fn new(working_dir: PathBuf) -> Self { + Self { + ignore_file_name: DEFAULT_IGNORE_FILE_NAME, + ignore_non_existent_files: false, + ignore_files: true, + working_dir, + file_exts: default_sql_file_exts(), + ignorer: None, + } + } +} + +pub fn discover_paths( + path: &Path, + options: &PathDiscoveryOptions<'_>, +) -> Result, SqruffError> { + let resolved = if path.is_absolute() { + path.to_path_buf() + } else { + options.working_dir.join(path) + }; + + let Ok(metadata) = std::fs::metadata(&resolved) else { + if options.ignore_non_existent_files { + return Ok(Vec::new()); + } + return Err(SqruffError::Config(format!( + "Specified path does not exist. Check it/they exist(s): {path:?}" + ))); + }; + + if metadata.is_file() { + return Ok(vec![helpers::normalize(path)]); + } + + let mut paths = BTreeSet::new(); + let ignore_file = if options.ignore_files { + Some(IgnoreFile::from_root_with_name( + &options.working_dir, + options.ignore_file_name, + )?) + } else { + None + }; + let fallback_ignorer = ignore_file + .as_ref() + .map(|ignore_file| ignore_file as &dyn IgnoreMatcher); + collect_paths(&resolved, options, fallback_ignorer, &mut paths)?; + + // If the original path was relative, strip the working_dir prefix to preserve + // relative paths in the output, matching the old WalkDir-based behavior. + if !path.is_absolute() { + let prefix = helpers::normalize(&options.working_dir); + let stripped: Vec = paths + .into_iter() + .map(|p| { + p.strip_prefix(&prefix) + .map(|rel| rel.to_path_buf()) + .unwrap_or(p) + }) + .collect(); + Ok(stripped) + } else { + Ok(paths.into_iter().collect()) + } +} + +fn collect_paths( + dir: &Path, + options: &PathDiscoveryOptions<'_>, + fallback_ignorer: Option<&dyn IgnoreMatcher>, + paths: &mut BTreeSet, +) -> Result<(), SqruffError> { + if is_ignored(dir, options, fallback_ignorer) { + log::debug!( + "Skipping directory '{}' during file discovery traversal", + dir.display() + ); + return Ok(()); + } + + let entries = std::fs::read_dir(dir).map_err(|source| SqruffError::Io { + path: dir.to_path_buf(), + source, + })?; + + for entry in entries { + let entry = entry.map_err(|source| SqruffError::Io { + path: dir.to_path_buf(), + source, + })?; + let path = entry.path(); + let file_type = entry.file_type().map_err(|source| SqruffError::Io { + path: path.clone(), + source, + })?; + + if file_type.is_dir() { + collect_paths(&path, options, fallback_ignorer, paths)?; + } else if file_type.is_file() + && is_lintable_file(&path, options.file_exts) + && !is_ignored(&path, options, fallback_ignorer) + { + paths.insert(helpers::normalize(&path)); + } + } + + Ok(()) +} + +fn is_ignored( + path: &Path, + options: &PathDiscoveryOptions<'_>, + fallback_ignorer: Option<&dyn IgnoreMatcher>, +) -> bool { + options + .ignorer + .is_some_and(|ignorer| ignorer.is_ignored(path)) + || fallback_ignorer.is_some_and(|ignorer| ignorer.is_ignored(path)) +} + +fn is_lintable_file(path: &Path, file_exts: &[String]) -> bool { + let Some(file_name) = path.file_name().and_then(|name| name.to_str()) else { + return false; + }; + let file_name = file_name.to_lowercase(); + + file_exts + .iter() + .any(|ext| file_name.ends_with(&ext.to_lowercase())) +} + +fn default_sql_file_exts() -> &'static [String] { + static EXTS: OnceLock> = OnceLock::new(); + EXTS.get_or_init(|| { + DEFAULT_SQL_FILE_EXTS + .iter() + .map(|ext| (*ext).to_string()) + .collect() + }) +} + +fn source_from_path(path: PathBuf) -> Result, SqruffError> { + let text = std::fs::read_to_string(&path).map_err(|source| SqruffError::Io { + path: path.clone(), + source, + })?; + + Ok(Source { + id: SourceId::Path(path), + text: Cow::Owned(text), + }) +} diff --git a/crates/lib/src/core/config.rs b/crates/lib/src/core/config.rs index 456b11ce0..68f1fd548 100644 --- a/crates/lib/src/core/config.rs +++ b/crates/lib/src/core/config.rs @@ -10,6 +10,7 @@ use sqruff_lib_core::parser::{IndentationConfig, Parser}; pub use sqruff_lib_core::value::Value; use sqruff_lib_dialects::kind_to_dialect; +use crate::api::SqruffError; use crate::templaters::TemplaterKind; use crate::utils::reflow::config::ReflowConfig; @@ -127,29 +128,27 @@ impl FluffConfig { /// from_file creates a config object from a file path. The path is used both /// to read the file content and to resolve relative `_path`/`_dir` values. - pub fn from_file(path: &Path) -> FluffConfig { - Self::try_from_file(path).unwrap() - } - - pub fn try_from_file(path: &Path) -> Result { + pub fn from_file(path: &Path) -> Result { let mut configs = HashMap::new(); ConfigLoader::try_load_config_file(path, &mut configs)?; Ok(FluffConfig::new(configs, None, None)) } - /// from_source creates a config object from a string. This is used for testing and for - /// loading a config from a string. + /// Creates a config object from a source string, falling back to defaults on invalid input. /// - /// The optional_path_specification is used to specify a path to use for relative paths in the - /// config. This is useful for testing. - pub fn from_source(source: &str, optional_path_specification: Option<&Path>) -> FluffConfig { - Self::try_from_source(source, optional_path_specification).unwrap() + /// Production code should use `try_from_source()` and surface the error. + #[cfg(test)] + pub fn from_source_or_default_for_tests( + source: &str, + optional_path_specification: Option<&Path>, + ) -> FluffConfig { + Self::try_from_source(source, optional_path_specification).unwrap_or_default() } pub fn try_from_source( source: &str, optional_path_specification: Option<&Path>, - ) -> Result { + ) -> Result { let configs = ConfigLoader::try_from_source(source, optional_path_specification)?; Ok(FluffConfig::new(configs, None, None)) } @@ -204,7 +203,8 @@ impl FluffConfig { ); let mut defaults = HashMap::new(); - ConfigLoader::incorporate_vals(&mut defaults, values); + ConfigLoader::incorporate_vals(&mut defaults, values) + .expect("built-in default config must be valid"); let mut configs = nested_combine(defaults, configs); @@ -269,7 +269,7 @@ impl FluffConfig { extra_config_path: Option, ignore_local_config: bool, overrides: Option>, - ) -> Result { + ) -> Result { let loader = ConfigLoader {}; let mut config = loader.try_load_config_up_to_path( ".", @@ -427,14 +427,14 @@ impl ConfigLoader { head.chain(tail) } - pub fn load_config_up_to_path( + pub fn load_config_up_to_path_or_default( &self, path: impl AsRef, extra_config_path: Option, ignore_local_config: bool, ) -> HashMap { self.try_load_config_up_to_path(path, extra_config_path, ignore_local_config) - .unwrap() + .unwrap_or_default() } pub fn try_load_config_up_to_path( @@ -442,7 +442,7 @@ impl ConfigLoader { path: impl AsRef, extra_config_path: Option, ignore_local_config: bool, - ) -> Result, SQLFluffUserError> { + ) -> Result, SqruffError> { let path = path.as_ref(); let config_stack = if ignore_local_config { @@ -461,14 +461,14 @@ impl ConfigLoader { Ok(nested_combine(config_stack)) } - pub fn load_config_at_path(&self, path: impl AsRef) -> HashMap { - self.try_load_config_at_path(path).unwrap() + pub fn load_config_at_path_or_default(&self, path: impl AsRef) -> HashMap { + self.try_load_config_at_path(path).unwrap_or_default() } pub fn try_load_config_at_path( &self, path: impl AsRef, - ) -> Result, SQLFluffUserError> { + ) -> Result, SqruffError> { let path = path.as_ref(); let filename_options = [ @@ -496,30 +496,41 @@ impl ConfigLoader { Ok(configs) } - pub fn from_source(source: &str, path: Option<&Path>) -> HashMap { - Self::try_from_source(source, path).unwrap() + #[cfg(test)] + pub fn from_source_or_default_for_tests( + source: &str, + path: Option<&Path>, + ) -> HashMap { + Self::try_from_source(source, path).unwrap_or_default() } pub fn try_from_source( source: &str, path: Option<&Path>, - ) -> Result, SQLFluffUserError> { + ) -> Result, SqruffError> { let mut configs = HashMap::new(); let elems = ConfigLoader::try_get_config_elems_from_file(path, Some(source))?; - ConfigLoader::incorporate_vals(&mut configs, elems); + ConfigLoader::incorporate_vals(&mut configs, elems)?; Ok(configs) } - pub fn load_config_file(path: impl AsRef, configs: &mut HashMap) { - Self::try_load_config_file(path, configs).unwrap(); + pub fn load_config_file_or_default( + path: impl AsRef, + configs: &mut HashMap, + ) { + let Ok(elems) = ConfigLoader::try_get_config_elems_from_file(path.as_ref().into(), None) + else { + return; + }; + let _ = ConfigLoader::incorporate_vals(configs, elems); } pub fn try_load_config_file( path: impl AsRef, configs: &mut HashMap, - ) -> Result<(), SQLFluffUserError> { + ) -> Result<(), SqruffError> { let elems = ConfigLoader::try_get_config_elems_from_file(path.as_ref().into(), None)?; - ConfigLoader::incorporate_vals(configs, elems); + ConfigLoader::incorporate_vals(configs, elems)?; Ok(()) } @@ -527,21 +538,26 @@ impl ConfigLoader { config_path: Option<&Path>, config_string: Option<&str>, ) -> Vec<(Vec, Value)> { - Self::try_get_config_elems_from_file(config_path, config_string).unwrap() + Self::try_get_config_elems_from_file(config_path, config_string).unwrap_or_default() } fn try_get_config_elems_from_file( config_path: Option<&Path>, config_string: Option<&str>, - ) -> Result, Value)>, SQLFluffUserError> { + ) -> Result, Value)>, SqruffError> { let content = match (config_path, config_string) { (None, None) => { - unimplemented!("One of fpath or config_string is required.") + return Err(SqruffError::Config( + "one of config path or config string is required".to_string(), + )); } (_, Some(text)) => text.to_owned(), - (Some(path), None) => std::fs::read_to_string(path).map_err(|err| { - config_error(config_path, format!("Unable to read config file: {err}")) - })?, + (Some(path), None) => { + std::fs::read_to_string(path).map_err(|source| SqruffError::Io { + path: path.to_path_buf(), + source, + })? + } }; if is_toml_config(config_path) { @@ -551,23 +567,14 @@ impl ConfigLoader { parse_ini_config_elems(&content, config_path) } - fn incorporate_vals(ctx: &mut HashMap, values: Vec<(Vec, Value)>) { + fn incorporate_vals( + ctx: &mut HashMap, + values: Vec<(Vec, Value)>, + ) -> Result<(), SqruffError> { for (path, value) in values { - let mut current_map = &mut *ctx; - for key in path.iter().take(path.len() - 1) { - match current_map - .entry(key.to_string()) - .or_insert_with(|| Value::Map(HashMap::new())) - .as_map_mut() - { - Some(slot) => current_map = slot, - None => panic!("Overriding config value with section! [{path:?}]"), - } - } - - let last_key = path.last().expect("Expected at least one element in path"); - current_map.insert(last_key.to_string(), value); + insert_config_path(ctx, &path, value)?; } + Ok(()) } } @@ -579,17 +586,17 @@ fn is_toml_config(config_path: Option<&Path>) -> bool { }) } -fn config_error(config_path: Option<&Path>, message: impl std::fmt::Display) -> SQLFluffUserError { +fn config_error(config_path: Option<&Path>, message: impl std::fmt::Display) -> SqruffError { let location = config_path .map(|path| path.display().to_string()) .unwrap_or_else(|| "config source".to_owned()); - SQLFluffUserError::new(format!("Error loading config from {location}: {}", message)) + SqruffError::Config(format!("Error loading config from {location}: {}", message)) } fn parse_ini_config_elems( content: &str, config_path: Option<&Path>, -) -> Result, Value)>, SQLFluffUserError> { +) -> Result, Value)>, SqruffError> { let mut buff = Vec::new(); let mut config = Ini::new(); @@ -616,9 +623,11 @@ fn parse_ini_config_elems( let name_lowercase = name.to_lowercase(); if name_lowercase == "load_macros_from_path" { - unimplemented!() + return Err(SqruffError::Unsupported( + "load_macros_from_path config is not implemented", + )); } else if name_lowercase.ends_with("_path") || name_lowercase.ends_with("_dir") { - value = resolve_relative_config_path(value, config_path); + value = resolve_relative_config_path(value, config_path, name)?; } let mut key = key.clone(); @@ -634,7 +643,7 @@ fn parse_ini_config_elems( fn parse_toml_config_elems( content: &str, config_path: Option<&Path>, -) -> Result, Value)>, SQLFluffUserError> { +) -> Result, Value)>, SqruffError> { let root = content .parse::() .map_err(|err| config_error(config_path, err))?; @@ -643,14 +652,14 @@ fn parse_toml_config_elems( for config_root in ["sqlfluff", "sqruff"] { if let Some(table) = root.get(config_root).and_then(toml::Value::as_table) { - collect_toml_config_elems(table, Vec::new(), config_path, &mut buff); + collect_toml_config_elems(table, Vec::new(), config_path, &mut buff)?; } } if let Some(tool) = root.get("tool").and_then(toml::Value::as_table) { for config_root in ["sqlfluff", "sqruff"] { if let Some(table) = tool.get(config_root).and_then(toml::Value::as_table) { - collect_toml_config_elems(table, Vec::new(), config_path, &mut buff); + collect_toml_config_elems(table, Vec::new(), config_path, &mut buff)?; } } } @@ -663,22 +672,24 @@ fn collect_toml_config_elems( section_path: Vec, config_path: Option<&Path>, buff: &mut Vec<(Vec, Value)>, -) { +) -> Result<(), SqruffError> { for (name, value) in table { match value { toml::Value::Table(table) => { let mut section_path = section_path.clone(); section_path.push(name.to_owned()); - collect_toml_config_elems(table, section_path, config_path, buff); + collect_toml_config_elems(table, section_path, config_path, buff)?; } value => { if name == "load_macros_from_path" { - unimplemented!() + return Err(SqruffError::Unsupported( + "load_macros_from_path config is not implemented", + )); } let mut value = toml_value_to_config_value(value); if name.ends_with("_path") || name.ends_with("_dir") { - value = resolve_relative_config_path(value, config_path); + value = resolve_relative_config_path(value, config_path, name)?; } let key = toml_config_key_path(§ion_path, name); @@ -686,6 +697,7 @@ fn collect_toml_config_elems( } } } + Ok(()) } fn toml_config_key_path(section_path: &[String], key: &str) -> Vec { @@ -736,18 +748,61 @@ fn toml_value_to_config_value(value: &toml::Value) -> Value { } } -fn resolve_relative_config_path(mut value: Value, config_path: Option<&Path>) -> Value { - let path = PathBuf::from(value.as_string().unwrap()); - if !path.is_absolute() { - let config_path = config_path.unwrap().parent().unwrap(); - let current_dir = std::env::current_dir().unwrap(); - let config_path = current_dir.join(config_path); - let config_path = std::path::absolute(config_path).unwrap(); +fn resolve_relative_config_path( + mut value: Value, + config_path: Option<&Path>, + name: &str, +) -> Result { + let Some(path_value) = value.as_string() else { + return Err(config_error( + config_path, + format!("invalid path value for config key '{name}'"), + )); + }; + let path = PathBuf::from(path_value); + if !path.is_absolute() + && let Some(config_path) = config_path.and_then(Path::parent) + && let Ok(current_dir) = std::env::current_dir() + && let Ok(config_path) = std::path::absolute(current_dir.join(config_path)) + { let path = config_path.join(path); let path: String = path.to_string_lossy().into(); value = Value::String(path.into()); } - value + Ok(value) +} + +fn insert_config_path( + ctx: &mut HashMap, + path: &[String], + value: Value, +) -> Result<(), SqruffError> { + let Some((key, rest)) = path.split_first() else { + return Ok(()); + }; + + if rest.is_empty() { + ctx.insert(key.to_string(), value); + return Ok(()); + } + + let entry = ctx + .entry(key.to_string()) + .or_insert_with(|| Value::Map(HashMap::new())); + if entry.as_map().is_none() { + return Err(SqruffError::Config(format!( + "overriding config value with section at '{}'", + path.join(":") + ))); + } + let Some(child) = entry.as_map_mut() else { + return Err(SqruffError::Config(format!( + "config path '{}' must contain only sections before the final value", + path.join(":") + ))); + }; + + insert_config_path(child, rest, value) } fn nested_combine(config_stack: Vec>) -> HashMap { @@ -796,7 +851,7 @@ mod tests { #[test] fn test_dialect_config_section_parsing() { // Test that [sqruff:dialect:snowflake] section is correctly parsed - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( r#" [sqruff] dialect = snowflake @@ -805,7 +860,8 @@ dialect = snowflake some_option = value "#, None, - ); + ) + .unwrap(); // Verify that the dialect config section is accessible let dialect_section = config.raw.get("dialect"); @@ -824,7 +880,7 @@ some_option = value #[test] fn test_dialect_config_empty_section() { // Test that empty [sqruff:dialect:bigquery] section works - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( r#" [sqruff] dialect = bigquery @@ -832,7 +888,8 @@ dialect = bigquery [sqruff:dialect:bigquery] "#, None, - ); + ) + .unwrap(); // The config should still be valid assert_eq!(config.get_dialect().name, DialectKind::Bigquery); @@ -841,13 +898,14 @@ dialect = bigquery #[test] fn test_dialect_without_config_section() { // Test that a dialect works without a config section - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( r#" [sqruff] dialect = postgres "#, None, - ); + ) + .unwrap(); // The config should still be valid assert_eq!(config.get_dialect().name, DialectKind::Postgres); @@ -855,26 +913,27 @@ dialect = postgres #[test] fn test_templater_kind_defaults_to_raw() { - let config = FluffConfig::from_source("", None); + let config = FluffConfig::try_from_source("", None).unwrap(); assert_eq!(config.templater_kind().unwrap(), TemplaterKind::Raw); } #[test] fn test_templater_kind_parses_placeholder() { - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( r#" [sqruff] templater = placeholder "#, None, - ); + ) + .unwrap(); assert_eq!(config.templater_kind().unwrap(), TemplaterKind::Placeholder); } #[test] fn test_templater_section_uses_typed_kind() { - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( r#" [sqruff] templater = placeholder @@ -883,7 +942,8 @@ templater = placeholder param_style = colon "#, None, - ); + ) + .unwrap(); let section = config .templater_section(TemplaterKind::Placeholder) @@ -989,7 +1049,11 @@ max_line_length = 39 ) .unwrap(); - let config = FluffConfig::new(ConfigLoader {}.load_config_at_path(&dir), None, None); + let config = FluffConfig::new( + ConfigLoader {}.load_config_at_path_or_default(&dir), + None, + None, + ); fs::remove_dir_all(&dir).unwrap(); assert_eq!(config.raw["core"]["max_line_length"].as_int(), Some(39)); @@ -1007,7 +1071,11 @@ max_line_length = 44 ) .unwrap(); - let config = FluffConfig::new(ConfigLoader {}.load_config_at_path(&dir), None, None); + let config = FluffConfig::new( + ConfigLoader {}.load_config_at_path_or_default(&dir), + None, + None, + ); fs::remove_dir_all(&dir).unwrap(); assert_eq!(config.raw["core"]["max_line_length"].as_int(), Some(44)); @@ -1030,7 +1098,7 @@ max_line_length = 44 #[cfg(feature = "python")] #[test] fn test_templater_context_uses_typed_kind() { - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( r#" [sqruff] templater = python @@ -1039,9 +1107,51 @@ templater = python blah = foo "#, None, - ); + ) + .unwrap(); let context = config.templater_context(TemplaterKind::Python).unwrap(); assert_eq!(context.get("blah").unwrap().as_string(), Some("foo")); } + + #[test] + fn try_from_source_returns_config_error_for_invalid_path_value() { + let err = FluffConfig::try_from_source( + r#" +[sqruff] +templater = dbt + +[sqruff:templater:dbt] +project_dir = 1 +"#, + None, + ) + .unwrap_err(); + + assert!(matches!(err, SqruffError::Config(_))); + assert!(err.to_string().contains("invalid path value")); + } + + #[test] + fn insert_config_path_rejects_scalar_section_conflicts() { + let mut config = HashMap::new(); + insert_config_path( + &mut config, + &["core".to_string()], + Value::String("not a table".into()), + ) + .unwrap(); + + let err = insert_config_path( + &mut config, + &["core".to_string(), "dialect".to_string()], + Value::String("ansi".into()), + ) + .unwrap_err(); + + assert!( + err.to_string() + .contains("overriding config value with section") + ); + } } diff --git a/crates/lib/src/core/linter/common.rs b/crates/lib/src/core/linter/common.rs index beb99c3cf..f151ef620 100644 --- a/crates/lib/src/core/linter/common.rs +++ b/crates/lib/src/core/linter/common.rs @@ -2,6 +2,8 @@ use sqruff_lib_core::errors::{SQLBaseError, SQLTemplaterError}; use sqruff_lib_core::parser::segments::ErasedSegment; use sqruff_lib_core::templaters::TemplatedFile; +use crate::api::{SkipReason, SourceId}; + /// An object to store the result of a templated file/string. /// /// This is notable as it's the intermediate state between what happens @@ -14,10 +16,24 @@ pub struct RenderedFile { pub source_str: String, } -/// Result of batch rendering: either a rendered file or a skipped file. -pub enum BatchRenderedResult { - Rendered(RenderedFile), - Skipped { filename: String, reason: String }, +pub enum RenderedSource { + Rendered { + source_id: SourceId, + rendered: RenderedFile, + }, + Skipped { + source_id: SourceId, + reason: SkipReason, + }, +} + +impl RenderedSource { + pub fn into_rendered(self) -> Option { + match self { + Self::Rendered { rendered, .. } => Some(rendered), + Self::Skipped { .. } => None, + } + } } /// An object to store the result of parsing a string. diff --git a/crates/lib/src/core/linter/core.rs b/crates/lib/src/core/linter/core.rs index 3ccc8cd48..dac70cc6d 100644 --- a/crates/lib/src/core/linter/core.rs +++ b/crates/lib/src/core/linter/core.rs @@ -1,76 +1,78 @@ use std::borrow::Cow; -use std::fs::File; -use std::io::{BufRead, BufReader}; -use std::path::{Path, PathBuf}; -use std::sync::{Arc, OnceLock}; +use std::sync::OnceLock; -use crate::Formatter; +use crate::api::{Mode, ParseErrors, Source, SourceId}; use crate::core::config::FluffConfig; -use crate::core::linter::common::{BatchRenderedResult, ParsedString, RenderedFile}; +use crate::core::linter::common::{ParsedString, RenderedFile, RenderedSource}; use crate::core::linter::linted_file::LintedFile; -use crate::core::linter::linting_result::LintingResult; use crate::core::rules::noqa::IgnoreMask; use crate::core::rules::{ErasedRule, Exception, LintPhase, RulePack}; use crate::rules::get_ruleset; -use crate::templaters::{ProcessingMode, Templater, TemplaterKind}; +use crate::templaters::{ + ProcessingMode, TemplaterError, TemplaterInput, TemplaterOutput, TemplaterRuntime, + source_id_name, +}; use hashbrown::{HashMap, HashSet}; use itertools::Itertools; -use rayon::iter::{IntoParallelRefIterator as _, ParallelIterator as _}; +use rayon::prelude::*; use smol_str::{SmolStr, ToSmolStr}; use sqruff_lib_core::dialects::Dialect; use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet}; use sqruff_lib_core::errors::{ - SQLBaseError, SQLFluffUserError, SQLLexError, SQLLintError, SQLParseError, SQLTemplaterError, + SQLBaseError, SQLFluffUserError, SQLLexError, SQLLintError, SQLParseError, }; -use sqruff_lib_core::helpers; use sqruff_lib_core::linter::compute_anchor_edit_info; use sqruff_lib_core::parser::Parser; use sqruff_lib_core::parser::segments::{ErasedSegment, Tables}; use sqruff_lib_core::templaters::TemplatedFile; -use walkdir::WalkDir; pub struct Linter { config: FluffConfig, - formatter: Option>, - templater: &'static dyn Templater, + templater: TemplaterRuntime, rules: OnceLock>, - /// include_parse_errors is a flag to indicate whether to include parse errors in the output - include_parse_errors: bool, + parse_errors: ParseErrors, +} + +struct NormalizedSource { + id: SourceId, + text: String, } impl Linter { pub fn new( config: FluffConfig, - formatter: Option>, - templater: Option<&'static dyn Templater>, - include_parse_errors: bool, - ) -> Result { - let templater: &'static dyn Templater = match templater { + templater: Option, + parse_errors: ParseErrors, + ) -> Result { + let templater = match templater { Some(templater) => templater, None => Linter::get_templater(&config)?, }; Ok(Linter { config, - formatter, templater, rules: OnceLock::new(), - include_parse_errors, + parse_errors, }) } - pub fn get_templater(config: &FluffConfig) -> Result<&'static dyn Templater, String> { - config.templater_kind().map(TemplaterKind::templater) + pub fn get_templater( + config: &FluffConfig, + ) -> Result { + TemplaterRuntime::from_config(config) } /// Lint strings directly. + #[deprecated(note = "use Engine::check_source or Engine::fix_source")] pub fn lint_string_wrapped( &mut self, sql: &str, - fix: bool, + mode: Mode, ) -> Result { let filename = "".to_owned(); - self.lint_string(sql, Some(filename), fix) + #[allow(deprecated)] + self.lint_string(sql, Some(filename), mode) } /// Parse a string. @@ -84,109 +86,24 @@ impl Linter { // Scan the raw file for config commands. self.config.process_raw_file_for_config(sql); - let rendered = self.render_string(sql, f_name.clone(), &self.config)?; + let rendered = self.render_string(sql, f_name, &self.config)?; Ok(self.parse_rendered(tables, rendered)) } /// Lint a string. + #[deprecated(note = "use Engine::check_source or Engine::fix_source")] pub fn lint_string( &self, sql: &str, filename: Option, - fix: bool, + mode: Mode, ) -> Result { let tables = Tables::default(); let parsed = self.parse_string(&tables, sql, filename)?; // Lint the file and return the LintedFile - self.lint_parsed(&tables, parsed, fix) - } - - /// ignorer is an optional argument that takes in a function that returns a bool based on the - /// path passed to it. If the function returns true, the path is ignored. - pub fn lint_paths( - &mut self, - mut paths: Vec, - fix: bool, - ignorer: &(dyn Fn(&Path) -> bool + Send + Sync), - ) -> Result { - if paths.is_empty() { - paths.push(std::env::current_dir().unwrap()); - } - - let mut expanded_paths = Vec::new(); - - for path in paths { - if path.is_file() { - expanded_paths.push(path.to_string_lossy().to_string()); - } else { - expanded_paths.extend(self.paths_from_path( - path, - None, - None, - None, - None, - Some(ignorer), - )); - }; - } - - let paths: Vec = expanded_paths - .into_iter() - .filter(|path| { - let should_ignore = ignorer(Path::new(path)); - if should_ignore { - log::debug!( - "Filtering out ignored file '{}' from final processing list", - path - ); - } - !should_ignore - }) - .collect_vec(); - - let mut files = Vec::with_capacity(paths.len()); - - match self.templater.processing_mode() { - ProcessingMode::Parallel => { - let results: Vec<_> = paths - .par_iter() - .map(|path| { - let rendered = self.render_file(path.clone()); - self.lint_rendered(rendered, fix) - }) - .collect(); - for result in results { - files.push(result?); - } - } - ProcessingMode::Batch => { - // Use batch processing for templaters that support it (e.g., dbt). - // This allows sharing expensive initialization (manifest loading) across files. - let batch_results = self.render_files_batch(&paths); - for result in batch_results { - match result { - BatchRenderedResult::Rendered(rendered) => { - files.push(self.lint_rendered(rendered, fix)?); - } - BatchRenderedResult::Skipped { filename, reason } => { - if let Some(formatter) = &self.formatter { - formatter.dispatch_file_skip(&filename, &reason); - } - } - } - } - } - ProcessingMode::Sequential => { - for path in &paths { - let rendered = self.render_file(path.clone()); - files.push(self.lint_rendered(rendered, fix)?); - } - } - } - - Ok(LintingResult::new(files)) + self.lint_parsed(&tables, parsed, mode) } pub fn get_rulepack(&self) -> Result { @@ -194,145 +111,21 @@ impl Linter { rs.get_rulepack(&self.config) } - pub fn render_file(&self, fname: String) -> RenderedFile { - let in_str = std::fs::read_to_string(&fname).unwrap(); - match self.render_string(&in_str, fname.clone(), &self.config) { - Ok(rendered) => rendered, - Err(err) => { - log::error!("Failed to template file {}: {:?}", fname, err); - let source_str = Self::normalise_newlines(&in_str).to_string(); - RenderedFile { - templated_file: TemplatedFile::new( - source_str.clone(), - fname.clone(), - None, - None, - None, - ) - .expect("Creating raw TemplatedFile should not fail"), - templater_violations: vec![SQLTemplaterError::new(format!( - "Failed to template file {fname}: {err}" - ))], - filename: fname, - source_str, - } - } - } - } - - /// Render multiple files in a batch using the templater's batch processing. - /// - /// This is more efficient for templaters like dbt that have expensive - /// initialization (manifest loading) that can be shared across files. - pub fn render_files_batch(&self, fnames: &[String]) -> Vec { - if fnames.is_empty() { - return Vec::new(); - } - - // Check dialect before processing - if let Some(_error) = self.config.verify_dialect_specified() { - // Return error rendered files for all files - return fnames - .iter() - .map(|fname| { - let source_str = std::fs::read_to_string(fname).unwrap_or_default(); - BatchRenderedResult::Rendered(RenderedFile { - templated_file: TemplatedFile::new( - source_str.clone(), - fname.clone(), - None, - None, - None, - ) - .expect("Creating raw TemplatedFile should not fail"), - templater_violations: vec![], - filename: fname.clone(), - source_str, - }) - }) - .collect(); - } - - // Read all files and prepare for batch processing - let files: Vec<(String, String)> = fnames - .iter() - .map(|fname| { - let content = std::fs::read_to_string(fname).unwrap_or_default(); - let normalized = Self::normalise_newlines(&content).to_string(); - (normalized, fname.clone()) - }) - .collect(); - - // Convert to slice of references for the process method - let file_refs: Vec<(&str, &str)> = files - .iter() - .map(|(content, fname)| (content.as_str(), fname.as_str())) - .collect(); - - // Process all files in batch - let results = self - .templater - .process(&file_refs, &self.config, &self.formatter); - - // Convert results to BatchRenderedResults, preserving order - results - .into_iter() - .zip(files.iter()) - .map(|(result, (source_str, fname))| match result { - Ok(templated_file) => BatchRenderedResult::Rendered(RenderedFile { - templated_file, - templater_violations: vec![], - filename: fname.clone(), - source_str: source_str.clone(), - }), - Err(err) => { - let err_str = err.to_string(); - if let Some(reason) = err_str.strip_prefix("SKIP:") { - return BatchRenderedResult::Skipped { - filename: fname.clone(), - reason: reason.to_string(), - }; - } - log::error!("Failed to template file {}: {:?}", fname, err); - // Return a minimal RenderedFile with the templater error as a - // violation. This prevents linting the raw source (which contains - // template syntax like {{ }}) and producing false positive LT01 - // spacing errors. - BatchRenderedResult::Rendered(RenderedFile { - templated_file: TemplatedFile::new( - source_str.clone(), - fname.clone(), - None, - None, - None, - ) - .expect("Creating raw TemplatedFile should not fail"), - templater_violations: vec![SQLTemplaterError::new(format!( - "Failed to template file {fname}: {err}" - ))], - filename: fname.clone(), - source_str: source_str.clone(), - }) - } - }) - .collect() - } - pub fn lint_rendered( &self, rendered: RenderedFile, - fix: bool, + mode: Mode, ) -> Result { let tables = Tables::default(); let parsed = self.parse_rendered(&tables, rendered); - self.lint_parsed(&tables, parsed, fix) + self.lint_parsed(&tables, parsed, mode) } pub fn lint_parsed( &self, tables: &Tables, parsed_string: ParsedString, - fix: bool, + mode: Mode, ) -> Result { let mut violations = parsed_string.violations; @@ -342,7 +135,7 @@ impl Linter { tables, erased_segment, &parsed_string.templated_file, - fix, + mode, )?; let patches = tree.iter_patches(&parsed_string.templated_file); (patches, ignore_mask, initial_linting_errors) @@ -365,10 +158,6 @@ impl Linter { ignore_mask, ); - if let Some(formatter) = &self.formatter { - formatter.dispatch_file_violations(&linted_file); - } - Ok(linted_file) } @@ -377,20 +166,22 @@ impl Linter { tables: &Tables, mut tree: ErasedSegment, templated_file: &TemplatedFile, - fix: bool, + mode: Mode, ) -> Result<(ErasedSegment, Option, Vec), SQLFluffUserError> { let mut initial_violations = Vec::new(); - let phases: &[_] = if fix { - &[LintPhase::Main, LintPhase::Post] - } else { - &[LintPhase::Main] + let phases: &[_] = match mode { + Mode::Check => &[LintPhase::Main], + Mode::Fix => &[LintPhase::Main, LintPhase::Post], }; let mut previous_versions: HashSet<(SmolStr, bool)> = [(tree.raw().to_smolstr(), false)].into_iter().collect(); // If we are fixing then we want to loop up to the runaway_limit, otherwise just // once for linting. - let loop_limit = if fix { 10 } else { 1 }; + let loop_limit = match mode { + Mode::Check => 1, + Mode::Fix => 10, + }; // Look for comment segments which might indicate lines to ignore. let (ignore_mask, violations): (Option, Vec) = { let disable_noqa = self @@ -453,7 +244,10 @@ impl Linter { // results returned won't be seen by the user anyway (linting errors ADDED by // rules changing SQL, are not reported back to the user - only initial linting // errors), so there's absolutely no reason to run them. - if fix && !is_first_linter_pass && !rule.is_fix_compatible() { + if matches!(mode, Mode::Fix) + && !is_first_linter_pass + && !rule.is_fix_compatible() + { continue; } @@ -476,7 +270,7 @@ impl Linter { !ignore_mask.is_masked(&result, rule.into()) }) { if !suppress_templated_violation - || (fix && !result.fixes.is_empty()) + || (matches!(mode, Mode::Fix) && !result.fixes.is_empty()) { compute_anchor_edit_info( &mut anchor_info, @@ -505,7 +299,7 @@ impl Linter { continue; } - if fix && !anchor_info.is_empty() { + if matches!(mode, Mode::Fix) && !anchor_info.is_empty() { let (new_tree, _, _) = tree.apply_fixes(&mut anchor_info); let has_source_fixes = !new_tree.get_all_source_fixes().is_empty(); @@ -522,7 +316,7 @@ impl Linter { } } - if fix && !changed { + if matches!(mode, Mode::Fix) && !changed { break; } } @@ -538,35 +332,176 @@ impl Linter { filename: String, config: &FluffConfig, ) -> Result { - let sql = Self::normalise_newlines(sql); - if let Some(error) = config.verify_dialect_specified() { return Err(error); } - let templater_violations = vec![]; - let mut results = self.templater.process( - &[(sql.as_ref(), filename.as_str())], - config, - &self.formatter, - ); + let sql = Self::normalise_newlines(sql); + let source_id = SourceId::Virtual(filename); + let input = TemplaterInput { + source: sql.as_ref(), + source_id: &source_id, + }; + + let mut results = self.templater.process(std::slice::from_ref(&input), config); match results.pop() { - Some(Ok(templated_file)) => Ok(RenderedFile { + Some(Ok(TemplaterOutput::Rendered(templated_file))) => Ok(RenderedFile { templated_file, - templater_violations, - filename, - source_str: sql.to_string(), + templater_violations: Vec::new(), + filename: source_id_name(&source_id), + source_str: sql.into_owned(), }), - Some(Err(err)) => Err(SQLFluffUserError::new(format!( - "Failed to template file {filename} with error {err:?}" - ))), + Some(Ok(TemplaterOutput::Skipped(_))) => Err(SQLFluffUserError::new( + "Templater skipped string input".to_string(), + )), + Some(Err(err)) => Err(err.into_user_error()), None => Err(SQLFluffUserError::new(format!( - "Templater returned no results for file {filename}" + "Templater returned no results for file {}", + source_id_name(&source_id), ))), } } + pub(crate) fn render_source( + &self, + sql: &str, + source_id: &SourceId, + config: &FluffConfig, + ) -> Result { + if let Some(error) = config.verify_dialect_specified() { + return Err(TemplaterError::Failed(error)); + } + + let sql = Self::normalise_newlines(sql); + let input = TemplaterInput { + source: sql.as_ref(), + source_id, + }; + + let mut results = self.templater.process(std::slice::from_ref(&input), config); + + match results.pop() { + Some(Ok(TemplaterOutput::Rendered(templated_file))) => Ok(RenderedSource::Rendered { + source_id: source_id.clone(), + rendered: RenderedFile { + templated_file, + templater_violations: Vec::new(), + filename: source_id_name(source_id), + source_str: sql.into_owned(), + }, + }), + Some(Ok(TemplaterOutput::Skipped(reason))) => Ok(RenderedSource::Skipped { + source_id: source_id.clone(), + reason, + }), + Some(Err(err)) => Err(err), + None => Err(TemplaterError::Failed(SQLFluffUserError::new(format!( + "Templater returned no results for file {}", + source_id_name(source_id), + )))), + } + } + + pub(crate) fn render_sources( + &self, + sources: &[Source<'_>], + config: &FluffConfig, + ) -> Result, TemplaterError> { + if let Some(error) = config.verify_dialect_specified() { + return Err(TemplaterError::Failed(error)); + } + + let normalized_sources = sources + .iter() + .map(|source| NormalizedSource { + id: source.id.clone(), + text: Self::normalise_newlines(source.text.as_ref()).into_owned(), + }) + .collect::>(); + + let outputs = match self.templater.processing_mode() { + ProcessingMode::Batch => self.process_templater_batch(&normalized_sources, config), + ProcessingMode::Parallel => normalized_sources + .par_iter() + .map(|source| self.process_templater_single(source, config)) + .collect::, _>>(), + ProcessingMode::Sequential => normalized_sources + .iter() + .map(|source| self.process_templater_single(source, config)) + .collect::, _>>(), + }?; + + if outputs.len() != normalized_sources.len() { + return Err(TemplaterError::Failed(SQLFluffUserError::new(format!( + "Templater returned {} result(s) for {} source(s)", + outputs.len(), + normalized_sources.len() + )))); + } + + Ok(outputs) + } + + fn process_templater_single( + &self, + source: &NormalizedSource, + config: &FluffConfig, + ) -> Result { + self.process_templater_batch(std::slice::from_ref(source), config) + .and_then(|mut rendered| { + rendered.pop().ok_or_else(|| { + TemplaterError::Failed(SQLFluffUserError::new(format!( + "Templater returned no results for file {}", + source_id_name(&source.id) + ))) + }) + }) + } + + fn process_templater_batch( + &self, + sources: &[NormalizedSource], + config: &FluffConfig, + ) -> Result, TemplaterError> { + let inputs = sources + .iter() + .map(|source| TemplaterInput { + source: source.text.as_str(), + source_id: &source.id, + }) + .collect::>(); + let results = self.templater.process(&inputs, config); + + if results.len() != sources.len() { + return Err(TemplaterError::Failed(SQLFluffUserError::new(format!( + "Templater returned {} result(s) for {} source(s)", + results.len(), + sources.len() + )))); + } + + sources + .iter() + .zip(results) + .map(|(source, result)| match result? { + TemplaterOutput::Rendered(templated_file) => Ok(RenderedSource::Rendered { + source_id: source.id.clone(), + rendered: RenderedFile { + templated_file, + templater_violations: Vec::new(), + filename: source_id_name(&source.id), + source_str: source.text.clone(), + }, + }), + TemplaterOutput::Skipped(reason) => Ok(RenderedSource::Skipped { + source_id: source.id.clone(), + reason, + }), + }) + .collect() + } + /// Parse a rendered file. pub fn parse_rendered(&self, tables: &Tables, rendered: RenderedFile) -> ParsedString { let templater_violations = rendered.templater_violations.clone(); @@ -595,9 +530,7 @@ impl Linter { rendered.templated_file.clone(), &self.config.dialect, ); - if !lvs.is_empty() { - unimplemented!("violations.extend(lvs);") - } + violations.extend(lvs.into_iter().map_into()); t } else { None @@ -605,8 +538,7 @@ impl Linter { let parsed: Option; if let Some(token_list) = tokens { - let (p, pvs) = - Self::parse_tokens(tables, &token_list, &self.config, self.include_parse_errors); + let (p, pvs) = Self::parse_tokens(tables, &token_list, &self.config, self.parse_errors); parsed = p; violations.extend(pvs.into_iter().map_into()); } else { @@ -626,7 +558,7 @@ impl Linter { tables: &Tables, tokens: &[ErasedSegment], config: &FluffConfig, - include_parse_errors: bool, + parse_errors: ParseErrors, ) -> (Option, Vec) { let parser: Parser = config.into(); let mut violations: Vec = Vec::new(); @@ -639,7 +571,9 @@ impl Linter { } }; - if include_parse_errors && let Some(parsed) = &parsed { + if matches!(parse_errors, ParseErrors::Include) + && let Some(parsed) = &parsed + { let unparsables = parsed.recursive_crawl( &SyntaxSet::single(SyntaxKind::Unparsable), true, @@ -683,180 +617,16 @@ impl Linter { lazy_regex::regex!("\r\n|\r").replace_all(string, "\n") } - // Return a set of sql file paths from a potentially more ambiguous path string. - // Here we also deal with the .sqlfluffignore file if present. - // When a path to a file to be linted is explicitly passed - // we look for ignore files in all directories that are parents of the file, - // up to the current directory. - // If the current directory is not a parent of the file we only - // look for an ignore file in the direct parent of the file. - fn paths_from_path( - &self, - path: PathBuf, - ignore_file_name: Option, - ignore_non_existent_files: Option, - ignore_files: Option, - working_path: Option, - ignorer: Option<&(dyn Fn(&Path) -> bool + Send + Sync)>, - ) -> Vec { - let ignore_file_name = ignore_file_name.unwrap_or_else(|| String::from(".sqlfluffignore")); - let ignore_non_existent_files = ignore_non_existent_files.unwrap_or(false); - let ignore_files = ignore_files.unwrap_or(true); - let _working_path = - working_path.unwrap_or_else(|| std::env::current_dir().unwrap().display().to_string()); - - let Ok(metadata) = std::fs::metadata(&path) else { - if ignore_non_existent_files { - return Vec::new(); - } else { - panic!("Specified path does not exist. Check it/they exist(s): {path:?}"); - } - }; - - // Files referred to exactly are also ignored if - // matched, but we warn the users when that happens - let is_exact_file = metadata.is_file(); - - let mut path_walk = if is_exact_file { - let path = Path::new(&path); - let dirpath = path.parent().unwrap().to_str().unwrap().to_string(); - let files = vec![path.file_name().unwrap().to_str().unwrap().to_string()]; - vec![(dirpath, None, files)] - } else { - let walkdir = WalkDir::new(&path); - let entries: Vec<_> = if let Some(ignorer) = ignorer { - // Apply ignorer during traversal to skip ignored directories entirely - walkdir - .into_iter() - .filter_entry(|entry| { - let should_ignore = ignorer(entry.path()); - if should_ignore { - let path_type = if entry.file_type().is_dir() { - "directory" - } else { - "file" - }; - log::debug!( - "Skipping {} '{}' during file discovery traversal", - path_type, - entry.path().display() - ); - } - !should_ignore - }) - .filter_map(Result::ok) - .collect() - } else { - // No ignorer provided, use original behavior - walkdir.into_iter().filter_map(Result::ok).collect() - }; - - // Group entries by directory to maintain the original data structure - let mut dir_files: HashMap> = HashMap::new(); - - for entry in entries { - if entry.file_type().is_file() { - let dirpath = entry.path().parent().unwrap().to_str().unwrap().to_string(); - let filename = entry.file_name().to_str().unwrap().to_string(); - dir_files.entry(dirpath).or_default().push(filename); - } - } - - dir_files - .into_iter() - .map(|(dirpath, files)| (dirpath, None, files)) - .collect_vec() - }; - - // TODO: - // let ignore_file_paths = ConfigLoader.find_ignore_config_files( - // path=path, working_path=working_path, ignore_file_name=ignore_file_name - // ); - let ignore_file_paths: Vec = Vec::new(); - - // Add paths that could contain "ignore files" - // to the path_walk list - let path_walk_ignore_file: Vec<(String, Option<()>, Vec)> = ignore_file_paths - .iter() - .map(|ignore_file_path| { - let ignore_file_path = Path::new(ignore_file_path); - - // Extracting the directory name from the ignore file path - let dir_name = ignore_file_path - .parent() - .unwrap() - .to_str() - .unwrap() - .to_string(); - - // Only one possible file, since we only - // have one "ignore file name" - let file_name = vec![ - ignore_file_path - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_string(), - ]; - - (dir_name, None, file_name) - }) - .collect(); - - path_walk.extend(path_walk_ignore_file); - - let mut buffer = Vec::new(); - let mut ignores = HashMap::new(); - let sql_file_exts = self.config.sql_file_exts(); - - for (dirpath, _, filenames) in path_walk { - for fname in filenames { - let fpath = Path::new(&dirpath).join(&fname); - - // Handle potential .sqlfluffignore files - if ignore_files && fname == ignore_file_name { - let file = File::open(&fpath).unwrap(); - let lines = BufReader::new(file).lines(); - let spec = lines.map_while(Result::ok); // Simple placeholder for pathspec logic - ignores.insert(dirpath.clone(), spec.collect::>()); - - // We don't need to process the ignore file any further - continue; - } - - // We won't purge files *here* because there's an edge case - // that the ignore file is processed after the sql file. - - // Scan for remaining files - for ext in sql_file_exts { - // is it a sql file? - if fname.to_lowercase().ends_with(ext) { - buffer.push(fpath.clone()); - } - } - } - } - - let mut filtered_buffer = HashSet::new(); - - for fpath in buffer { - let npath = helpers::normalize(&fpath).to_str().unwrap().to_string(); - filtered_buffer.insert(npath); - } - - let mut files = filtered_buffer.into_iter().collect_vec(); - files.sort(); - files - } - pub fn config(&self) -> &FluffConfig { &self.config } - pub fn config_mut(&mut self) -> &mut FluffConfig { + #[cfg(test)] + #[allow(dead_code)] + pub(crate) fn replace_config_for_test(&mut self, config: FluffConfig) { + self.templater = TemplaterRuntime::from_config(&config).unwrap(); + self.config = config; self.rules = OnceLock::new(); - &mut self.config } pub fn rules(&self) -> Result<&[ErasedRule], SQLFluffUserError> { @@ -868,54 +638,72 @@ impl Linter { Ok(self.rules.get().unwrap()) } - pub fn formatter(&self) -> Option<&Arc> { - self.formatter.as_ref() - } - - pub fn formatter_mut(&mut self) -> Option<&mut Arc> { - self.formatter.as_mut() + pub(crate) fn parse_errors(&self) -> ParseErrors { + self.parse_errors } } #[cfg(test)] mod tests { + use std::fs; + use std::path::{Path, PathBuf}; + use std::time::{SystemTime, UNIX_EPOCH}; + use sqruff_lib_core::parser::segments::Tables; + use crate::api::{Mode, ParseErrors, PathDiscoveryOptions, discover_paths}; use crate::core::config::FluffConfig; use crate::core::linter::core::Linter; fn postgres_all_rules_linter() -> Linter { - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( r#" [sqruff] dialect = postgres rules = all "#, None, - ); + ) + .unwrap(); - Linter::new(config, None, None, true).unwrap() + Linter::new(config, None, ParseErrors::Include).unwrap() } - fn normalise_paths(paths: Vec) -> Vec { + fn normalise_paths(paths: Vec) -> Vec { paths .into_iter() - .map(|path| path.replace(['/', '\\'], ".")) + .map(|path| { + let path = path.to_string_lossy().replace(['/', '\\'], "."); + if let Some(index) = path.find("test.") { + path[index..].to_string() + } else { + path + } + }) .collect() } + fn path_options() -> PathDiscoveryOptions<'static> { + let mut options = PathDiscoveryOptions::new(std::env::current_dir().unwrap()); + options.ignore_files = false; + options + } + + fn temp_project(name: &str) -> PathBuf { + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let path = std::env::temp_dir().join(format!("sqruff-{name}-{nanos}")); + fs::create_dir_all(&path).unwrap(); + path + } + #[test] fn test_linter_path_from_paths_dir() { // Test extracting paths from directories. - let lntr = Linter::new( - FluffConfig::new(<_>::default(), None, None), - None, - None, - false, - ) - .unwrap(); - let paths = - lntr.paths_from_path("test/fixtures/lexer".into(), None, None, None, None, None); + let options = path_options(); + let paths = discover_paths(Path::new("test/fixtures/lexer"), &options).unwrap(); let expected = vec![ "test.fixtures.lexer.basic.sql", "test.fixtures.lexer.block_comment.sql", @@ -926,66 +714,44 @@ rules = all #[test] fn test_linter_path_from_paths_default() { - // Test .sql files are found by default. - let lntr = Linter::new( - FluffConfig::new(<_>::default(), None, None), - None, - None, - false, - ) - .unwrap(); - let paths = normalise_paths(lntr.paths_from_path( - "test/fixtures/linter".into(), - None, - None, - None, - None, - None, - )); - assert!(paths.contains(&"test.fixtures.linter.passing.sql".to_string())); - assert!(paths.contains(&"test.fixtures.linter.passing_cap_extension.SQL".to_string())); - assert!(!paths.contains(&"test.fixtures.linter.discovery_file.txt".to_string())); - } + // Test configured default SQL file extensions are found by default. + let options = path_options(); + let project = temp_project("configured-exts"); + fs::write(project.join("query.sql"), "SELECT 1;\n").unwrap(); + fs::write(project.join("template.sql.j2"), "SELECT {{ value }};\n").unwrap(); + fs::write(project.join("statement.dml"), "SELECT 2;\n").unwrap(); + fs::write(project.join("schema.ddl"), "CREATE TABLE t (id INT);\n").unwrap(); + fs::write(project.join("notes.txt"), "not sql\n").unwrap(); - #[test] - fn test_linter_path_from_paths_exts() { - // Assuming Linter is initialized with a configuration similar to Python's - // FluffConfig - let config = - FluffConfig::new(<_>::default(), None, None).with_sql_file_exts(vec![".txt".into()]); - let lntr = Linter::new(config, None, None, false).unwrap(); + let paths = discover_paths(&project, &options).unwrap(); - let paths = - lntr.paths_from_path("test/fixtures/linter".into(), None, None, None, None, None); + assert!(paths.iter().any(|path| path.ends_with("query.sql"))); + assert!(paths.iter().any(|path| path.ends_with("template.sql.j2"))); + assert!(paths.iter().any(|path| path.ends_with("statement.dml"))); + assert!(paths.iter().any(|path| path.ends_with("schema.ddl"))); + assert!(!paths.iter().any(|path| path.ends_with("notes.txt"))); - // Normalizing paths as in the Python version - let normalized_paths = normalise_paths(paths); + fs::remove_dir_all(project).unwrap(); + } - // Assertions as per the Python test - assert!(!normalized_paths.contains(&"test.fixtures.linter.passing.sql".into())); - assert!( - !normalized_paths.contains(&"test.fixtures.linter.passing_cap_extension.SQL".into()) - ); - assert!(normalized_paths.contains(&"test.fixtures.linter.discovery_file.txt".into())); + #[test] + fn test_linter_path_from_paths_respects_case_insensitive_exts() { + let options = path_options(); + let paths = + normalise_paths(discover_paths(Path::new("test/fixtures/linter"), &options).unwrap()); + assert!(paths.contains(&"test.fixtures.linter.passing.sql".to_string())); + assert!(paths.contains(&"test.fixtures.linter.passing_cap_extension.SQL".to_string())); + assert!(!paths.contains(&"test.fixtures.linter.discovery_file.txt".to_string())); } #[test] fn test_linter_path_from_paths_file() { - let lntr = Linter::new( - FluffConfig::new(<_>::default(), None, None), - None, - None, - false, + let options = path_options(); + let paths = discover_paths( + Path::new("test/fixtures/linter/indentation_errors.sql"), + &options, ) .unwrap(); - let paths = lntr.paths_from_path( - "test/fixtures/linter/indentation_errors.sql".into(), - None, - None, - None, - None, - None, - ); assert_eq!( normalise_paths(paths), @@ -993,6 +759,37 @@ rules = all ); } + #[test] + fn test_linter_path_from_paths_missing_returns_error() { + let options = path_options(); + let err = discover_paths( + Path::new("test/fixtures/linter/does_not_exist.sql"), + &options, + ) + .unwrap_err(); + + assert!(err.to_string().contains("Specified path does not exist")); + } + + #[test] + fn test_linter_path_from_paths_prunes_ignored_directories() { + let project = temp_project("ignored-dir"); + let ignored_dir = project.join("ignored").join("nested"); + fs::create_dir_all(&ignored_dir).unwrap(); + fs::write(project.join("regular.sql"), "SELECT 1;\n").unwrap(); + fs::write(ignored_dir.join("hidden.sql"), "SELECT bad FROM hidden;\n").unwrap(); + + let ignorer = |path: &Path| path.file_name().is_some_and(|name| name == "ignored"); + let mut options = path_options(); + options.ignorer = Some(&ignorer); + let paths = discover_paths(&project, &options).unwrap(); + + assert_eq!(paths.len(), 1); + assert!(paths[0].ends_with("regular.sql")); + + fs::remove_dir_all(project).unwrap(); + } + // test__linter__skip_large_bytes // test__linter__path_from_paths__not_exist // test__linter__path_from_paths__not_exist_ignore @@ -1015,8 +812,7 @@ rules = all let linter = Linter::new( FluffConfig::new(<_>::default(), None, None), None, - None, - false, + ParseErrors::Suppress, ) .unwrap(); let tables = Tables::default(); @@ -1048,8 +844,7 @@ rules = all let linter = Linter::new( FluffConfig::new(<_>::default(), None, None), None, - None, - false, + ParseErrors::Suppress, ) .unwrap(); let tables = Tables::default(); @@ -1079,14 +874,12 @@ rules = all let linter = Linter::new( FluffConfig::new(<_>::default(), None, None), None, - None, - false, + ParseErrors::Suppress, ) .unwrap(); // Simulate a failed templater by creating a RenderedFile with - // templater_violations (this is what render_files_batch does when - // the dbt/jinja templater fails). + // templater_violations. let rendered = RenderedFile { templated_file: TemplatedFile::new( source.to_string(), @@ -1103,7 +896,7 @@ rules = all source_str: source.to_string(), }; - let result = linter.lint_rendered(rendered, false).unwrap(); + let result = linter.lint_rendered(rendered, Mode::Check).unwrap(); let violations = result.violations(); // Should have exactly 1 violation: the templater error. @@ -1135,7 +928,7 @@ from test; "#; let mut linter = postgres_all_rules_linter(); - let linted = linter.lint_string_wrapped(sql, false).unwrap(); + let linted = linter.lint_string_wrapped(sql, Mode::Check).unwrap(); let violations = linted.violations(); assert!( @@ -1156,7 +949,7 @@ from test; ); let fixed = postgres_all_rules_linter() - .lint_string_wrapped(sql, true) + .lint_string_wrapped(sql, Mode::Fix) .unwrap() .fix_string(); @@ -1180,7 +973,7 @@ from test; "#; let mut linter = postgres_all_rules_linter(); - let linted = linter.lint_string_wrapped(sql, false).unwrap(); + let linted = linter.lint_string_wrapped(sql, Mode::Check).unwrap(); let violations = linted.violations(); assert!( @@ -1193,7 +986,7 @@ from test; ); let fixed = postgres_all_rules_linter() - .lint_string_wrapped(sql, true) + .lint_string_wrapped(sql, Mode::Fix) .unwrap() .fix_string(); diff --git a/crates/lib/src/core/linter/linted_file.rs b/crates/lib/src/core/linter/linted_file.rs index bc4cdfb97..04607252b 100644 --- a/crates/lib/src/core/linter/linted_file.rs +++ b/crates/lib/src/core/linter/linted_file.rs @@ -9,8 +9,7 @@ use sqruff_lib_core::templaters::{RawFileSlice, TemplateSliceKind, TemplatedFile #[derive(Debug, Default, Clone)] pub struct LintedFile { - // FIXME: remove pub when we have a better way to handle this. - pub path: String, + path: String, patches: Vec, templated_file: TemplatedFile, violations: Vec, @@ -61,6 +60,10 @@ impl LintedFile { &self.violations } + pub fn source(&self) -> &str { + &self.templated_file.source_str + } + /// Use patches and raw file to fix the source file. /// /// This assumes that patches and slices have already diff --git a/crates/lib/src/core/rules/noqa.rs b/crates/lib/src/core/rules/noqa.rs index 9faac0304..65c9a6f8a 100644 --- a/crates/lib/src/core/rules/noqa.rs +++ b/crates/lib/src/core/rules/noqa.rs @@ -622,17 +622,17 @@ mod tests { /// Test "noqa" feature at the higher "Linter" level. fn test_linter_single_noqa() { let linter = Linter::new( - FluffConfig::from_source( + FluffConfig::try_from_source( r#" [sqruff] dialect = bigquery rules = AL02 - "#, +"#, None, - ), - None, + ) + .unwrap(), None, - false, + crate::api::ParseErrors::Suppress, ) .unwrap(); @@ -642,7 +642,9 @@ rules = AL02 FROM foo "#; - let result = linter.lint_string(sql, None, false).unwrap(); + let result = linter + .lint_string(sql, None, crate::api::Mode::Check) + .unwrap(); let violations = result.violations(); assert_eq!(violations.len(), 1); @@ -656,32 +658,32 @@ FROM foo /// Test "noqa" feature at the higher "Linter" level and turn off noqa fn test_linter_noqa_but_disabled() { let linter_without_disabled = Linter::new( - FluffConfig::from_source( + FluffConfig::try_from_source( r#" [sqruff] dialect = bigquery rules = AL02 - "#, +"#, None, - ), - None, + ) + .unwrap(), None, - false, + crate::api::ParseErrors::Suppress, ) .unwrap(); let linter_with_disabled = Linter::new( - FluffConfig::from_source( + FluffConfig::try_from_source( r#" [sqruff] dialect = bigquery rules = AL02 disable_noqa = True - "#, +"#, None, - ), - None, + ) + .unwrap(), None, - false, + crate::api::ParseErrors::Suppress, ) .unwrap(); @@ -690,9 +692,11 @@ disable_noqa = True col_b b --noqa FROM foo "#; - let result_with_disabled = linter_with_disabled.lint_string(sql, None, false).unwrap(); + let result_with_disabled = linter_with_disabled + .lint_string(sql, None, crate::api::Mode::Check) + .unwrap(); let result_without_disabled = linter_without_disabled - .lint_string(sql, None, false) + .lint_string(sql, None, crate::api::Mode::Check) .unwrap(); assert_eq!(result_without_disabled.violations().len(), 1); @@ -702,17 +706,17 @@ FROM foo #[test] fn test_range_code() { let linter_without_disabled = Linter::new( - FluffConfig::from_source( + FluffConfig::try_from_source( r#" [sqruff] dialect = bigquery rules = AL02 - "#, +"#, None, - ), - None, + ) + .unwrap(), None, - false, + crate::api::ParseErrors::Suppress, ) .unwrap(); let sql_disable_rule = r#"SELECT @@ -733,10 +737,10 @@ FROM foo FROM foo "#; let result_rule = linter_without_disabled - .lint_string(sql_disable_rule, None, false) + .lint_string(sql_disable_rule, None, crate::api::Mode::Check) .unwrap(); let result_all = linter_without_disabled - .lint_string(sql_disable_all, None, false) + .lint_string(sql_disable_all, None, crate::api::Mode::Check) .unwrap(); assert_eq!(result_rule.violations().len(), 3); diff --git a/crates/lib/src/core/test_functions.rs b/crates/lib/src/core/test_functions.rs index d04e491c5..61782e6a8 100644 --- a/crates/lib/src/core/test_functions.rs +++ b/crates/lib/src/core/test_functions.rs @@ -3,11 +3,12 @@ use sqruff_lib_core::dialects::init::DialectKind; use sqruff_lib_core::parser::segments::{ErasedSegment, Tables}; use sqruff_lib_dialects::kind_to_dialect; +use crate::api::ParseErrors; use crate::core::linter::core::Linter; pub fn parse_ansi_string(sql: &str) -> ErasedSegment { let tables = Tables::default(); - let linter = Linter::new(<_>::default(), None, None, false).unwrap(); + let linter = Linter::new(<_>::default(), None, ParseErrors::Suppress).unwrap(); linter .parse_string(&tables, sql, None) .unwrap() diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index a77f88e58..70b478291 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -1,3 +1,6 @@ +#![cfg_attr(test, allow(deprecated))] + +pub mod api; pub mod core; #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] pub mod ignore; @@ -6,9 +9,3 @@ pub mod templaters; #[cfg(test)] mod tests; pub mod utils; - -pub trait Formatter: Send + Sync { - fn dispatch_file_violations(&self, linted_file: &core::linter::linted_file::LintedFile); - fn dispatch_file_skip(&self, fname: &str, reason: &str); - fn completion_message(&self, count: usize); -} diff --git a/crates/lib/src/rules/aliasing/al05.rs b/crates/lib/src/rules/aliasing/al05.rs index c58fa916d..832b6fc15 100644 --- a/crates/lib/src/rules/aliasing/al05.rs +++ b/crates/lib/src/rules/aliasing/al05.rs @@ -596,23 +596,24 @@ from stanza; "#; fn postgres_al05_linter() -> Linter { - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( r#" [sqruff] rules = AL05 dialect = postgres "#, None, - ); + ) + .unwrap(); - Linter::new(config, None, None, true).unwrap() + Linter::new(config, None, crate::api::ParseErrors::Include).unwrap() } #[test] fn test_al05_postgres_json_operator_alias_is_used() { let mut linter = postgres_al05_linter(); let linted = linter - .lint_string_wrapped(POSTGRES_JSON_ALIAS_REPRODUCER, false) + .lint_string_wrapped(POSTGRES_JSON_ALIAS_REPRODUCER, crate::api::Mode::Check) .unwrap(); assert_eq!(linted.violations(), &[]); @@ -622,7 +623,7 @@ dialect = postgres fn test_al05_postgres_json_operator_fix_preserves_alias() { let mut linter = postgres_al05_linter(); let linted = linter - .lint_string_wrapped(POSTGRES_JSON_ALIAS_REPRODUCER, true) + .lint_string_wrapped(POSTGRES_JSON_ALIAS_REPRODUCER, crate::api::Mode::Fix) .unwrap(); assert_eq!(linted.fix_string(), POSTGRES_JSON_ALIAS_REPRODUCER); diff --git a/crates/lib/src/templaters.rs b/crates/lib/src/templaters.rs index c925949bd..008576019 100644 --- a/crates/lib/src/templaters.rs +++ b/crates/lib/src/templaters.rs @@ -1,13 +1,13 @@ -use std::sync::Arc; - use sqruff_lib_core::errors::SQLFluffUserError; use sqruff_lib_core::templaters::TemplatedFile; -use crate::Formatter; +use crate::api::{SkipReason, SourceId, SqruffError}; use crate::core::config::FluffConfig; use crate::templaters::placeholder::PlaceholderTemplater; use crate::templaters::raw::RawTemplater; +#[cfg(feature = "python")] +use crate::templaters::dbt::DBTTemplater; #[cfg(feature = "python")] use crate::templaters::jinja::JinjaTemplater; #[cfg(feature = "python")] @@ -27,27 +27,7 @@ pub mod types; pub use types::{PlaceholderStyle, TemplaterKind}; -pub static RAW_TEMPLATER: RawTemplater = RawTemplater; -pub static PLACEHOLDER_TEMPLATER: PlaceholderTemplater = PlaceholderTemplater; -#[cfg(feature = "python")] -pub static PYTHON_TEMPLATER: PythonTemplater = PythonTemplater; -#[cfg(feature = "python")] -pub static JINJA_TEMPLATER: JinjaTemplater = JinjaTemplater; -#[cfg(feature = "python")] -pub static DBT_TEMPLATER: dbt::DBTTemplater = dbt::DBTTemplater; - -// templaters returns all the templaters that are available in the library -#[cfg(feature = "python")] -pub static TEMPLATERS: [&'static dyn Templater; 5] = [ - &RAW_TEMPLATER, - &PLACEHOLDER_TEMPLATER, - &PYTHON_TEMPLATER, - &JINJA_TEMPLATER, - &DBT_TEMPLATER, -]; - -#[cfg(not(feature = "python"))] -pub static TEMPLATERS: [&'static dyn Templater; 2] = [&RAW_TEMPLATER, &PLACEHOLDER_TEMPLATER]; +pub static TEMPLATERS: &[TemplaterKind] = TemplaterKind::available(); /// How a templater processes files. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -64,6 +44,43 @@ pub enum ProcessingMode { Batch, } +pub struct TemplaterInput<'a> { + pub source: &'a str, + pub source_id: &'a SourceId, +} + +pub enum TemplaterOutput { + Rendered(TemplatedFile), + Skipped(SkipReason), +} + +#[derive(Debug)] +pub enum TemplaterError { + Failed(SQLFluffUserError), +} + +impl From for TemplaterError { + fn from(error: SQLFluffUserError) -> Self { + Self::Failed(error) + } +} + +impl TemplaterError { + pub fn into_user_error(self) -> SQLFluffUserError { + match self { + Self::Failed(error) => error, + } + } +} + +impl From for SqruffError { + fn from(error: TemplaterError) -> Self { + match error { + TemplaterError::Failed(error) => Self::Templater(error.value), + } + } +} + pub trait Templater: Send + Sync { /// The name of the templater. fn name(&self) -> &'static str; @@ -74,18 +91,152 @@ pub trait Templater: Send + Sync { /// Returns the processing mode for this templater. fn processing_mode(&self) -> ProcessingMode; - /// Process one or more files and return TemplatedFiles. + /// Process one or more files and return typed templater outcomes. /// /// Arguments: - /// - files: Slice of (file_content, file_name) tuples + /// - files: Input files with source text and identity. /// - config: The configuration to use - /// - formatter: Optional formatter for output /// /// Returns a vector of results in the same order as the input files. fn process( &self, - files: &[(&str, &str)], + files: &[TemplaterInput<'_>], + config: &FluffConfig, + ) -> Vec>; +} + +pub enum TemplaterRuntime { + Raw(RawTemplater), + Placeholder(PlaceholderTemplater), + + #[cfg(feature = "python")] + Python(PythonTemplater), + + #[cfg(feature = "python")] + Jinja(JinjaTemplater), + + #[cfg(feature = "python")] + Dbt(DBTTemplater), + + #[cfg(test)] + Custom(&'static dyn Templater), +} + +impl TemplaterRuntime { + pub fn from_config(config: &FluffConfig) -> Result { + let kind = config.templater_kind().map_err(SqruffError::Config)?; + Ok(Self::from_kind(kind)) + } + + pub fn from_kind(kind: TemplaterKind) -> Self { + match kind { + TemplaterKind::Raw => Self::Raw(RawTemplater), + TemplaterKind::Placeholder => Self::Placeholder(PlaceholderTemplater), + #[cfg(feature = "python")] + TemplaterKind::Python => Self::Python(PythonTemplater), + #[cfg(feature = "python")] + TemplaterKind::Jinja => Self::Jinja(JinjaTemplater), + #[cfg(feature = "python")] + TemplaterKind::Dbt => Self::Dbt(DBTTemplater), + } + } + + #[cfg(test)] + pub(crate) fn custom(templater: &'static dyn Templater) -> Self { + Self::Custom(templater) + } + + pub fn name(&self) -> &'static str { + ::name(self) + } + + pub fn description(&self) -> &'static str { + ::description(self) + } + + pub fn processing_mode(&self) -> ProcessingMode { + ::processing_mode(self) + } + + pub fn process( + &self, + files: &[TemplaterInput<'_>], + config: &FluffConfig, + ) -> Vec> { + ::process(self, files, config) + } +} + +impl Templater for TemplaterRuntime { + fn name(&self) -> &'static str { + match self { + Self::Raw(t) => t.name(), + Self::Placeholder(t) => t.name(), + #[cfg(feature = "python")] + Self::Python(t) => t.name(), + #[cfg(feature = "python")] + Self::Jinja(t) => t.name(), + #[cfg(feature = "python")] + Self::Dbt(t) => t.name(), + #[cfg(test)] + Self::Custom(t) => t.name(), + } + } + + fn description(&self) -> &'static str { + match self { + Self::Raw(t) => t.description(), + Self::Placeholder(t) => t.description(), + #[cfg(feature = "python")] + Self::Python(t) => t.description(), + #[cfg(feature = "python")] + Self::Jinja(t) => t.description(), + #[cfg(feature = "python")] + Self::Dbt(t) => t.description(), + #[cfg(test)] + Self::Custom(t) => t.description(), + } + } + + fn processing_mode(&self) -> ProcessingMode { + match self { + Self::Raw(t) => t.processing_mode(), + Self::Placeholder(t) => t.processing_mode(), + #[cfg(feature = "python")] + Self::Python(t) => t.processing_mode(), + #[cfg(feature = "python")] + Self::Jinja(t) => t.processing_mode(), + #[cfg(feature = "python")] + Self::Dbt(t) => t.processing_mode(), + #[cfg(test)] + Self::Custom(t) => t.processing_mode(), + } + } + + fn process( + &self, + files: &[TemplaterInput<'_>], config: &FluffConfig, - formatter: &Option>, - ) -> Vec>; + ) -> Vec> { + match self { + Self::Raw(t) => t.process(files, config), + Self::Placeholder(t) => t.process(files, config), + #[cfg(feature = "python")] + Self::Python(t) => t.process(files, config), + #[cfg(feature = "python")] + Self::Jinja(t) => t.process(files, config), + #[cfg(feature = "python")] + Self::Dbt(t) => t.process(files, config), + #[cfg(test)] + Self::Custom(t) => t.process(files, config), + } + } +} + +pub(crate) fn source_id_name(source_id: &SourceId) -> String { + match source_id { + SourceId::Stdin => "".to_string(), + SourceId::Path(path) => path.to_string_lossy().into_owned(), + SourceId::Virtual(name) => name.clone(), + } } diff --git a/crates/lib/src/templaters/dbt.rs b/crates/lib/src/templaters/dbt.rs index 5ba2e761d..31a4e0340 100644 --- a/crates/lib/src/templaters/dbt.rs +++ b/crates/lib/src/templaters/dbt.rs @@ -2,14 +2,13 @@ use super::Templater; use super::python::PythonTemplatedFile; use crate::core::config::FluffConfig; use crate::templaters::python_shared::PythonFluffConfig; -use crate::templaters::{Formatter, ProcessingMode, TemplaterKind}; +use crate::templaters::{ + ProcessingMode, TemplaterError, TemplaterInput, TemplaterKind, TemplaterOutput, source_id_name, +}; use pyo3::prelude::*; use pyo3::types::PyList; use pyo3::{Py, PyAny, Python}; use sqruff_lib_core::errors::SQLFluffUserError; -use sqruff_lib_core::templaters::TemplatedFile; -use std::sync::Arc; - pub struct DBTTemplater; impl Templater for DBTTemplater { @@ -124,24 +123,23 @@ The linter then operates on this compiled SQL."# fn process( &self, - files: &[(&str, &str)], + files: &[TemplaterInput<'_>], config: &FluffConfig, - _: &Option>, - ) -> Vec> { + ) -> Vec> { if files.is_empty() { return Vec::new(); } - Python::attach(|py| -> Vec> { + Python::attach(|py| -> Vec> { let main_module = match PyModule::import(py, "sqruff.templaters.dbt_templater") { Ok(m) => m, Err(e) => { return files .iter() .map(|_| { - Err(SQLFluffUserError::new(format!( + Err(TemplaterError::Failed(SQLFluffUserError::new(format!( "Failed to import dbt_templater module: {e:?}" - ))) + )))) }) .collect(); } @@ -153,9 +151,9 @@ The linter then operates on this compiled SQL."# return files .iter() .map(|_| { - Err(SQLFluffUserError::new(format!( + Err(TemplaterError::Failed(SQLFluffUserError::new(format!( "Failed to get process_batch_from_rust function: {e:?}" - ))) + )))) }) .collect(); } @@ -167,9 +165,9 @@ The linter then operates on this compiled SQL."# return files .iter() .map(|_| { - Err(SQLFluffUserError::new(format!( + Err(TemplaterError::Failed(SQLFluffUserError::new(format!( "Failed to create Python context: {e:?}" - ))) + )))) }) .collect(); } @@ -180,7 +178,7 @@ The linter then operates on this compiled SQL."# // Convert files to Python list of tuples let py_files: Vec<(String, String)> = files .iter() - .map(|(content, fname)| (content.to_string(), fname.to_string())) + .map(|file| (file.source.to_string(), source_id_name(file.source_id))) .collect(); let py_files_list = PyList::new(py, &py_files).unwrap(); @@ -189,37 +187,47 @@ The linter then operates on this compiled SQL."# match fun.call1(py, args) { Ok(returned) => { - // The Python function returns a list of (TemplatedFile | None, error_message | None) - let results: Vec<(Option, Option)> = - match returned.extract(py) { - Ok(r) => r, - Err(e) => { - return files - .iter() - .map(|_| { - Err(SQLFluffUserError::new(format!( - "Failed to extract batch results: {e:?}" - ))) - }) - .collect(); - } - }; + // The Python function returns: + // (TemplatedFile | None, error_message | None, skip_reason | None) + let results: Vec<( + Option, + Option, + Option, + )> = match returned.extract(py) { + Ok(r) => r, + Err(e) => { + return files + .iter() + .map(|_| { + Err(TemplaterError::Failed(SQLFluffUserError::new(format!( + "Failed to extract batch results: {e:?}" + )))) + }) + .collect(); + } + }; results .into_iter() - .map(|(templated_file, error)| { + .map(|(templated_file, error, skip_reason)| { if let Some(err_msg) = error { - Err(SQLFluffUserError::new(err_msg)) + Err(TemplaterError::Failed(SQLFluffUserError::new(err_msg))) + } else if let Some(reason) = skip_reason { + Ok(TemplaterOutput::Skipped(crate::api::SkipReason { + message: reason, + })) } else if let Some(tf) = templated_file { - tf.to_templated_file().map_err(|e| { - SQLFluffUserError::new(format!( - "Failed to convert dbt templated file: {e:?}" - )) - }) + tf.to_templated_file() + .map_err(|e| { + TemplaterError::Failed(SQLFluffUserError::new(format!( + "Failed to convert dbt templated file: {e:?}" + ))) + }) + .map(TemplaterOutput::Rendered) } else { - Err(SQLFluffUserError::new( + Err(TemplaterError::Failed(SQLFluffUserError::new( "No templated file or error returned".to_string(), - )) + ))) } }) .collect() @@ -227,9 +235,9 @@ The linter then operates on this compiled SQL."# Err(e) => files .iter() .map(|_| { - Err(SQLFluffUserError::new(format!( + Err(TemplaterError::Failed(SQLFluffUserError::new(format!( "Python batch templater error: {e:?}" - ))) + )))) }) .collect(), } diff --git a/crates/lib/src/templaters/jinja.rs b/crates/lib/src/templaters/jinja.rs index 50bcee59f..299ce7fd1 100644 --- a/crates/lib/src/templaters/jinja.rs +++ b/crates/lib/src/templaters/jinja.rs @@ -2,13 +2,13 @@ use super::Templater; use super::python::PythonTemplatedFile; use crate::core::config::FluffConfig; use crate::templaters::python_shared::PythonFluffConfig; -use crate::templaters::{Formatter, ProcessingMode, TemplaterKind}; +use crate::templaters::{ + ProcessingMode, TemplaterError, TemplaterInput, TemplaterKind, TemplaterOutput, source_id_name, +}; use pyo3::prelude::*; use pyo3::{Py, PyAny, Python}; use sqruff_lib_core::errors::SQLFluffUserError; use sqruff_lib_core::templaters::TemplatedFile; -use std::sync::Arc; - pub struct JinjaTemplater; impl JinjaTemplater { @@ -134,20 +134,26 @@ When `apply_dbt_builtins` is enabled (the default), common dbt functions like `r fn process( &self, - files: &[(&str, &str)], + files: &[TemplaterInput<'_>], config: &FluffConfig, - _: &Option>, - ) -> Vec> { + ) -> Vec> { files .iter() - .map(|(content, fname)| self.process_single(content, fname, config)) + .map(|file| { + let fname = source_id_name(file.source_id); + self.process_single(file.source, &fname, config) + .map(TemplaterOutput::Rendered) + .map_err(TemplaterError::Failed) + }) .collect() } } #[cfg(test)] mod tests { + use crate::api::SourceId; use crate::core::config::FluffConfig; + use crate::templaters::{TemplaterInput, TemplaterOutput}; use super::*; @@ -168,11 +174,21 @@ FROM events [sqruff] templater = jinja "; - let config = FluffConfig::from_source(source, None); + let config = FluffConfig::try_from_source(source, None).unwrap(); let templater = JinjaTemplater; - let results = templater.process(&[(JINJA_STRING, "test.sql")], &config, &None); - let processed = results.into_iter().next().unwrap().unwrap(); + let source_id = SourceId::Virtual("test.sql".into()); + let results = templater.process( + &[TemplaterInput { + source: JINJA_STRING, + source_id: &source_id, + }], + &config, + ); + let processed = match results.into_iter().next().unwrap().unwrap() { + TemplaterOutput::Rendered(file) => file, + TemplaterOutput::Skipped(reason) => panic!("jinja skipped: {}", reason.message), + }; assert_eq!( processed.templated(), @@ -186,15 +202,25 @@ FROM events [sqruff] templater = jinja "; - let config = FluffConfig::from_source(source, None); + let config = FluffConfig::try_from_source(source, None).unwrap(); let templater = JinjaTemplater; let instr = r#"{% if True %} {% set some_var %}1{% endset %} SELECT {{some_var}} {% endif %} "#; - let results = templater.process(&[(instr, "test.sql")], &config, &None); - let processed = results.into_iter().next().unwrap().unwrap(); + let source_id = SourceId::Virtual("test.sql".into()); + let results = templater.process( + &[TemplaterInput { + source: instr, + source_id: &source_id, + }], + &config, + ); + let processed = match results.into_iter().next().unwrap().unwrap() { + TemplaterOutput::Rendered(file) => file, + TemplaterOutput::Skipped(reason) => panic!("jinja skipped: {}", reason.message), + }; assert_eq!(processed.templated(), "\n \n SELECT 1\n\n"); } diff --git a/crates/lib/src/templaters/placeholder.rs b/crates/lib/src/templaters/placeholder.rs index cde5a08a9..e409218fb 100644 --- a/crates/lib/src/templaters/placeholder.rs +++ b/crates/lib/src/templaters/placeholder.rs @@ -1,15 +1,15 @@ -use hashbrown::HashMap; -use std::sync::Arc; - use fancy_regex::Regex; +use hashbrown::HashMap; use sqruff_lib_core::errors::SQLFluffUserError; use sqruff_lib_core::templaters::{ RawFileSlice, TemplateSliceKind, TemplatedFile, TemplatedFileSlice, }; -use crate::Formatter; use crate::core::config::FluffConfig; -use crate::templaters::{PlaceholderStyle, ProcessingMode, Templater}; +use crate::templaters::{ + PlaceholderStyle, ProcessingMode, Templater, TemplaterError, TemplaterInput, TemplaterOutput, + source_id_name, +}; #[derive(Default)] pub struct PlaceholderTemplater; @@ -286,13 +286,17 @@ Also consider making a pull request to the project to have your style added, it fn process( &self, - files: &[(&str, &str)], + files: &[TemplaterInput<'_>], config: &FluffConfig, - _: &Option>, - ) -> Vec> { + ) -> Vec> { files .iter() - .map(|(content, fname)| self.process_single(content, fname, config)) + .map(|file| { + let fname = source_id_name(file.source_id); + self.process_single(file.source, &fname, config) + .map(TemplaterOutput::Rendered) + .map_err(TemplaterError::Failed) + }) .collect() } } @@ -305,19 +309,46 @@ mod tests { type PlaceholderCase<'a> = (&'a str, &'a str, &'a str, Vec<(&'a str, &'a str)>); + fn process_one( + templater: &PlaceholderTemplater, + in_str: &str, + name: &str, + config: &FluffConfig, + ) -> Result { + let source_id = crate::api::SourceId::Virtual(name.to_string()); + templater + .process( + &[TemplaterInput { + source: in_str, + source_id: &source_id, + }], + config, + ) + .into_iter() + .next() + .unwrap() + .map(|output| match output { + TemplaterOutput::Rendered(file) => file, + TemplaterOutput::Skipped(reason) => { + panic!("placeholder templater skipped: {}", reason.message) + } + }) + .map_err(TemplaterError::into_user_error) + } + #[test] /// Test the templaters when nothing has to be replaced. fn test_templater_no_replacement() { let templater = PlaceholderTemplater {}; let in_str = "SELECT * FROM {{blah}} WHERE %(gnepr)s OR e~':'"; - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( " [sqruff:templater:placeholder] param_style = colon", None, - ); - let results = templater.process(&[(in_str, "test.sql")], &config, &None); - let out_str = results.into_iter().next().unwrap().unwrap(); + ) + .unwrap(); + let out_str = process_one(&templater, in_str, "test.sql", &config).unwrap(); let out = out_str.templated(); assert_eq!(in_str, out) } @@ -617,7 +648,7 @@ WHERE userid = 42 AND date > '2021-10-01' ]; for (in_str, param_style, expected_out, values) in cases { - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( format!( r#" [sqruff:templater:placeholder] @@ -633,10 +664,10 @@ param_style = {} ) .as_str(), None, - ); + ) + .unwrap(); let templater = PlaceholderTemplater {}; - let results = templater.process(&[(in_str, "test.sql")], &config, &None); - let out_str = results.into_iter().next().unwrap().unwrap(); + let out_str = process_one(&templater, in_str, "test.sql", &config).unwrap(); let out = out_str.templated(); assert_eq!(expected_out, out) } @@ -646,11 +677,10 @@ param_style = {} /// Test the error raised when config is incomplete, as in no param_regex /// nor param_style. fn test_templater_setup_none() { - let config = FluffConfig::from_source("", None); + let config = FluffConfig::try_from_source("", None).unwrap(); let templater = PlaceholderTemplater {}; let in_str = "SELECT 2+2"; - let results = templater.process(&[(in_str, "test.sql")], &config, &None); - let out_str = results.into_iter().next().unwrap(); + let out_str = process_one(&templater, in_str, "test.sql", &config); assert!(out_str.is_err()); assert_eq!( @@ -663,18 +693,18 @@ param_style = {} /// Test the error raised when both param_regex and param_style are /// provided. fn test_templater_setup_both_provided() { - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( r#" [sqruff:templater:placeholder] param_regex = __(?P[\w_]+)__ param_style = colon "#, None, - ); + ) + .unwrap(); let templater = PlaceholderTemplater {}; let in_str = "SELECT 2+2"; - let results = templater.process(&[(in_str, "test.sql")], &config, &None); - let out_str = results.into_iter().next().unwrap(); + let out_str = process_one(&templater, in_str, "test.sql", &config); assert!(out_str.is_err()); assert_eq!( @@ -686,18 +716,18 @@ param_style = colon #[test] /// Test custom regex templating. fn test_templater_custom_regex() { - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( r#" [sqruff:templater:placeholder] param_regex = __(?P[\w_]+)__ my_name = john "#, None, - ); + ) + .unwrap(); let templater = PlaceholderTemplater {}; let in_str = "SELECT bla FROM blob WHERE id = __my_name__"; - let results = templater.process(&[(in_str, "test")], &config, &None); - let out_str = results.into_iter().next().unwrap().unwrap(); + let out_str = process_one(&templater, in_str, "test", &config).unwrap(); let out = out_str.templated(); assert_eq!("SELECT bla FROM blob WHERE id = john", out) } @@ -705,17 +735,17 @@ my_name = john #[test] /// Test the exception raised when parameter styles is unknown. fn test_templater_styles_not_existing() { - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( r#" [sqruff:templater:placeholder] param_style = unknown "#, None, - ); + ) + .unwrap(); let templater = PlaceholderTemplater {}; let in_str = "SELECT * FROM {{blah}} WHERE %(gnepr)s OR e~':'"; - let results = templater.process(&[(in_str, "test.sql")], &config, &None); - let out_str = results.into_iter().next().unwrap(); + let out_str = process_one(&templater, in_str, "test.sql", &config); assert!(out_str.is_err()); assert_eq!( @@ -727,7 +757,7 @@ param_style = unknown #[test] /// Test the linter fully with this templater. fn test_templater_placeholder() { - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( r#" [sqruff] dialect = ansi @@ -738,11 +768,15 @@ rules = all param_style = percent "#, None, - ); + ) + .unwrap(); let sql = "SELECT a,b FROM users WHERE a = %s"; - let mut linter = Linter::new(config, None, None, false).unwrap(); - let result = linter.lint_string_wrapped(sql, true).unwrap().fix_string(); + let mut linter = Linter::new(config, None, crate::api::ParseErrors::Suppress).unwrap(); + let result = linter + .lint_string_wrapped(sql, crate::api::Mode::Fix) + .unwrap() + .fix_string(); assert_eq!(result, "SELECT\n a,\n b\nFROM users\nWHERE a = %s\n"); } diff --git a/crates/lib/src/templaters/python.rs b/crates/lib/src/templaters/python.rs index 3a8f170cd..ef5acf04a 100644 --- a/crates/lib/src/templaters/python.rs +++ b/crates/lib/src/templaters/python.rs @@ -8,13 +8,12 @@ use sqruff_lib_core::templaters::{ }; use super::Templater; -use crate::Formatter; use crate::core::config::FluffConfig; -use crate::templaters::ProcessingMode; use crate::templaters::TemplaterKind; use crate::templaters::python_shared::PythonFluffConfig; -use std::sync::Arc; - +use crate::templaters::{ + ProcessingMode, TemplaterError, TemplaterInput, TemplaterOutput, source_id_name, +}; #[derive(Default)] pub struct PythonTemplater; @@ -90,13 +89,17 @@ At the moment, dot notation is not supported in the templater." fn process( &self, - files: &[(&str, &str)], + files: &[TemplaterInput<'_>], config: &FluffConfig, - _formatter: &Option>, - ) -> Vec> { + ) -> Vec> { files .iter() - .map(|(content, fname)| self.process_single(content, fname, config)) + .map(|file| { + let fname = source_id_name(file.source_id); + self.process_single(file.source, &fname, config) + .map(TemplaterOutput::Rendered) + .map_err(TemplaterError::Failed) + }) .collect() } } @@ -260,6 +263,9 @@ impl PythonTemplatedFile { // Working on tests #[cfg(test)] mod tests { + use crate::api::SourceId; + use crate::templaters::{TemplaterInput, TemplaterOutput}; + use super::*; const PYTHON_STRING: &str = "SELECT * FROM {blah}"; @@ -274,12 +280,22 @@ templater = python [sqruff:templater:python:context] blah = foo "; - let config = FluffConfig::from_source(source, None); + let config = FluffConfig::try_from_source(source, None).unwrap(); let templater = PythonTemplater; - let results = templater.process(&[(PYTHON_STRING, "test.sql")], &config, &None); - let templated_file = results.into_iter().next().unwrap().unwrap(); + let source_id = SourceId::Virtual("test.sql".into()); + let results = templater.process( + &[TemplaterInput { + source: PYTHON_STRING, + source_id: &source_id, + }], + &config, + ); + let templated_file = match results.into_iter().next().unwrap().unwrap() { + TemplaterOutput::Rendered(file) => file, + TemplaterOutput::Skipped(reason) => panic!("python skipped: {}", reason.message), + }; assert_eq!(templated_file.templated(), "SELECT * FROM foo"); } @@ -371,11 +387,18 @@ templater = python [sqruff:templater:python:context] noblah = foo "; - let config = FluffConfig::from_source(source, None); + let config = FluffConfig::try_from_source(source, None).unwrap(); let templater = PythonTemplater; - let results = templater.process(&[(PYTHON_STRING, "test.sql")], &config, &None); + let source_id = SourceId::Virtual("test.sql".into()); + let results = templater.process( + &[TemplaterInput { + source: PYTHON_STRING, + source_id: &source_id, + }], + &config, + ); let templated_file = results.into_iter().next().unwrap(); assert!(templated_file.is_err()) diff --git a/crates/lib/src/templaters/python_shared.rs b/crates/lib/src/templaters/python_shared.rs index 286d36112..a53864e49 100644 --- a/crates/lib/src/templaters/python_shared.rs +++ b/crates/lib/src/templaters/python_shared.rs @@ -128,7 +128,7 @@ mod tests { #[test] fn test_fluff_base_config() { - let config = FluffConfig::from_source("", None); + let config = FluffConfig::try_from_source("", None).unwrap(); let python_fluff_config = PythonFluffConfig::from(config); diff --git a/crates/lib/src/templaters/raw.rs b/crates/lib/src/templaters/raw.rs index dbba20ba8..09189b54e 100644 --- a/crates/lib/src/templaters/raw.rs +++ b/crates/lib/src/templaters/raw.rs @@ -1,11 +1,10 @@ -use std::sync::Arc; - -use sqruff_lib_core::errors::SQLFluffUserError; use sqruff_lib_core::templaters::TemplatedFile; -use crate::Formatter; +use crate::api::SourceId; use crate::core::config::FluffConfig; -use crate::templaters::{ProcessingMode, Templater}; +use crate::templaters::{ + ProcessingMode, Templater, TemplaterError, TemplaterInput, TemplaterOutput, source_id_name, +}; #[derive(Default)] pub struct RawTemplater; @@ -14,10 +13,14 @@ impl RawTemplater { fn process_single( &self, in_str: &str, - f_name: &str, - ) -> Result { - TemplatedFile::new(in_str.to_string(), f_name.to_string(), None, None, None) - .map_err(|e| SQLFluffUserError::new(format!("Raw templater error: {e}"))) + source_id: &SourceId, + ) -> Result { + let f_name = source_id_name(source_id); + TemplatedFile::new(in_str.to_string(), f_name, None, None, None).map_err(|e| { + TemplaterError::Failed(sqruff_lib_core::errors::SQLFluffUserError::new(format!( + "Raw templater error: {e}" + ))) + }) } } @@ -36,13 +39,15 @@ impl Templater for RawTemplater { fn process( &self, - files: &[(&str, &str)], + files: &[TemplaterInput<'_>], _config: &FluffConfig, - _formatter: &Option>, - ) -> Vec> { + ) -> Vec> { files .iter() - .map(|(content, fname)| self.process_single(content, fname)) + .map(|file| { + self.process_single(file.source, file.source_id) + .map(TemplaterOutput::Rendered) + }) .collect() } } @@ -57,14 +62,20 @@ mod test { let templater = RawTemplater; let in_str = "SELECT * FROM {{blah}}"; + let source_id = SourceId::Virtual("test.sql".into()); let results = templater.process( - &[(in_str, "test.sql")], - &FluffConfig::from_source("", None), - &None, + &[TemplaterInput { + source: in_str, + source_id: &source_id, + }], + &FluffConfig::try_from_source("", None).unwrap(), ); assert_eq!(results.len(), 1); - let outstr = results.into_iter().next().unwrap().unwrap(); + let outstr = match results.into_iter().next().unwrap().unwrap() { + TemplaterOutput::Rendered(file) => file, + TemplaterOutput::Skipped(_) => panic!("raw templater should render"), + }; assert_eq!(outstr.templated_str, Some(in_str.to_string())); } } diff --git a/crates/lib/src/templaters/types.rs b/crates/lib/src/templaters/types.rs index 286942a65..4952b7dce 100644 --- a/crates/lib/src/templaters/types.rs +++ b/crates/lib/src/templaters/types.rs @@ -1,9 +1,5 @@ use fancy_regex::Regex; -#[cfg(feature = "python")] -use super::{DBT_TEMPLATER, JINJA_TEMPLATER, PYTHON_TEMPLATER}; -use super::{PLACEHOLDER_TEMPLATER, RAW_TEMPLATER, Templater}; - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum TemplaterKind { Raw, @@ -30,19 +26,6 @@ impl TemplaterKind { } } - pub fn templater(self) -> &'static dyn Templater { - match self { - Self::Raw => &RAW_TEMPLATER, - Self::Placeholder => &PLACEHOLDER_TEMPLATER, - #[cfg(feature = "python")] - Self::Python => &PYTHON_TEMPLATER, - #[cfg(feature = "python")] - Self::Jinja => &JINJA_TEMPLATER, - #[cfg(feature = "python")] - Self::Dbt => &DBT_TEMPLATER, - } - } - pub fn available_names() -> Vec<&'static str> { Self::available().iter().map(|kind| kind.as_str()).collect() } diff --git a/crates/lib/src/tests.rs b/crates/lib/src/tests.rs index bd848625d..5c7f821ba 100644 --- a/crates/lib/src/tests.rs +++ b/crates/lib/src/tests.rs @@ -1,4 +1,5 @@ use itertools::Itertools; +use sqruff_lib::api::ParseErrors; use sqruff_lib::core::config::FluffConfig; use sqruff_lib::core::linter::core::Linter; use sqruff_lib::core::test_functions::fresh_ansi_dialect; @@ -193,8 +194,7 @@ fn test_dialect_ansi_specific_segment_not_parse() { let lnt = Linter::new( FluffConfig::new(<_>::default(), None, None), None, - None, - false, + ParseErrors::Suppress, ) .unwrap(); let tables = Tables::default(); @@ -215,8 +215,7 @@ fn test_dialect_ansi_is_whitespace() { let lnt = Linter::new( FluffConfig::new(<_>::default(), None, None), None, - None, - false, + ParseErrors::Suppress, ) .unwrap(); let file_content = std::fs::read_to_string( @@ -249,8 +248,7 @@ fn test_dialect_ansi_parse_indented_joins() { let lnt = Linter::new( FluffConfig::new(<_>::default(), None, None), None, - None, - false, + ParseErrors::Suppress, ) .unwrap(); @@ -372,11 +370,12 @@ fn test_reindent_no_false_positive_in_jinja_for_loop() { let lnt = crate::core::linter::core::Linter::new( crate::core::config::FluffConfig::new(<_>::default(), None, None), None, - None, - false, + crate::api::ParseErrors::Suppress, ) .unwrap(); - let linted = lnt.lint_rendered(rendered, false).unwrap(); + let linted = lnt + .lint_rendered(rendered, crate::core::linter::core::Mode::Check) + .unwrap(); let layout: Vec<_> = linted .violations() @@ -459,11 +458,12 @@ fn test_lt12_reports_missing_source_newline_after_jinja_block() { let lnt = crate::core::linter::core::Linter::new( crate::core::config::FluffConfig::new(<_>::default(), None, None), None, - None, - false, + crate::api::ParseErrors::Suppress, ) .unwrap(); - let linted = lnt.lint_rendered(rendered, false).unwrap(); + let linted = lnt + .lint_rendered(rendered, crate::core::linter::core::Mode::Check) + .unwrap(); let lt12: Vec<_> = linted .violations() @@ -549,11 +549,12 @@ fn test_lt12_reports_extra_rendered_newline_before_jinja_block() { let lnt = crate::core::linter::core::Linter::new( crate::core::config::FluffConfig::new(<_>::default(), None, None), None, - None, - false, + crate::api::ParseErrors::Suppress, ) .unwrap(); - let linted = lnt.lint_rendered(rendered, false).unwrap(); + let linted = lnt + .lint_rendered(rendered, crate::core::linter::core::Mode::Check) + .unwrap(); let lt12: Vec<_> = linted .violations() diff --git a/crates/lib/src/utils/reflow/reindent.rs b/crates/lib/src/utils/reflow/reindent.rs index f790d27e9..31aeabe85 100644 --- a/crates/lib/src/utils/reflow/reindent.rs +++ b/crates/lib/src/utils/reflow/reindent.rs @@ -2100,8 +2100,10 @@ mod tests { use crate::core::linter::core::Linter; let sql = "with a as (select 1\nfrom t join u v on\n1=1\n)\nselect * from a\n"; - let linter = Linter::new(<_>::default(), None, None, false).unwrap(); - let result = linter.lint_string(sql, None, false).unwrap(); + let linter = Linter::new(<_>::default(), None, crate::api::ParseErrors::Suppress).unwrap(); + let result = linter + .lint_string(sql, None, crate::api::Mode::Check) + .unwrap(); // The panic is caught by catch_unwind and surfaced as an // "Unexpected exception" violation. Assert none are present. for v in result.violations() { diff --git a/crates/lib/src/utils/reflow/respace.rs b/crates/lib/src/utils/reflow/respace.rs index d3698935f..8050b1c1c 100644 --- a/crates/lib/src/utils/reflow/respace.rs +++ b/crates/lib/src/utils/reflow/respace.rs @@ -602,7 +602,7 @@ mod tests { fn parse_string_with_config(sql: &str, config: &FluffConfig) -> ErasedSegment { let tables = Tables::default(); - let linter = Linter::new(config.clone(), None, None, false).unwrap(); + let linter = Linter::new(config.clone(), None, crate::api::ParseErrors::Suppress).unwrap(); linter .parse_string(&tables, sql, None) .unwrap() @@ -784,7 +784,7 @@ mod tests { for spacing_after in cases { let _panic = enter_panic(format!("spacing_after={spacing_after}")); - let config = FluffConfig::from_source( + let config = FluffConfig::try_from_source( &format!( r#" [sqruff] @@ -795,7 +795,8 @@ spacing_after = {spacing_after} "# ), None, - ); + ) + .unwrap(); let root = parse_string_with_config("select 1 -- comment\n+ 2", &config); let seq = ReflowSequence::from_root(&root, &config); let new_seq = seq.respace(&Tables::default(), false, Filter::All); diff --git a/crates/lib/tests/rules.rs b/crates/lib/tests/rules.rs index b2c934040..f816b1978 100644 --- a/crates/lib/tests/rules.rs +++ b/crates/lib/tests/rules.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] + use std::path::Path; use std::str::FromStr; @@ -6,6 +8,7 @@ use hashbrown::HashMap; use rayon::prelude::*; use serde::Deserialize; use serde_with::{KeyValueMap, serde_as}; +use sqruff_lib::api::{Mode, ParseErrors}; use sqruff_lib::core::config::{FluffConfig, Value}; use sqruff_lib::core::linter::core::Linter; use sqruff_lib_core::dialects::init::DialectKind; @@ -76,22 +79,19 @@ fn main() { }); } -// FIXME: Simplify FluffConfig handling. It's quite chaotic right now. struct RuleTestState { - linter: Linter, core: HashMap, } impl RuleTestState { fn new() -> Self { - let mut linter = Linter::new(FluffConfig::default(), None, None, true).unwrap(); let mut core = HashMap::new(); core.insert( "core".to_string(), - linter.config_mut().raw.get("core").unwrap().clone(), + FluffConfig::default().raw.get("core").unwrap().clone(), ); - Self { linter, core } + Self { core } } } @@ -108,17 +108,14 @@ fn process_file(state: &mut RuleTestState, path: &Path, verbose: bool) { .map(|x| Value::String(x.into())) .collect::>(); - state - .core + let mut file_core = state.core.clone(); + file_core .get_mut("core") .unwrap() .as_map_mut() .unwrap() .insert("rule_allowlist".into(), Value::Array(file_rules)); - state.linter.config_mut().raw.extend(state.core.clone()); - state.linter.config_mut().reload_reflow(); - for case in file.cases { if verbose { println!("Processing case: {}", case.name); @@ -144,14 +141,12 @@ fn process_file(state: &mut RuleTestState, path: &Path, verbose: bool) { let has_config = !case.configs.is_empty(); let rule = &file.rule; - if has_config { - *state.linter.config_mut() = FluffConfig::new(case.configs.clone(), None, None); - state.linter.config_mut().raw.extend(state.core.clone()); + let config = if has_config { + let mut config = FluffConfig::new(case.configs.clone(), None, None); + config.raw.extend(file_core.clone()); if let Some(core) = case.configs.get("core").and_then(|it| it.as_map()) { - state - .linter - .config_mut() + config .raw .get_mut("core") .unwrap() @@ -160,7 +155,7 @@ fn process_file(state: &mut RuleTestState, path: &Path, verbose: bool) { .extend(core.clone()); } - for (config, value) in &case + for (config_name, value) in &case .configs .get("rules") .cloned() @@ -169,47 +164,45 @@ fn process_file(state: &mut RuleTestState, path: &Path, verbose: bool) { .cloned() .unwrap_or_default() { - if INDENT_CONFIG.contains(&config.as_str()) { - state - .linter - .config_mut() + if INDENT_CONFIG.contains(&config_name.as_str()) { + config .raw .get_mut("indentation") .unwrap() .as_map_mut() .unwrap() - .insert(config.clone(), value.clone()); + .insert(config_name.clone(), value.clone()); } } - state.linter.config_mut().reload_reflow(); - - // Recreate linter with proper templater after all config is set up - let templater = match Linter::get_templater(state.linter.config()) { - Ok(t) => t, - Err(e) => { - if std::env::var("SQRUFF_SKIP_UNSUPPORTED_TEMPLATERS").is_ok() { - println!("Skipping case '{}': {}", case.name, e); - *state.linter.config_mut() = FluffConfig::default(); - state.linter.config_mut().raw.extend(state.core.clone()); - state.linter.config_mut().reload_reflow(); - continue; - } else { - panic!( - "Unsupported templater in case '{}': {}. \ - Set SQRUFF_SKIP_UNSUPPORTED_TEMPLATERS=1 to skip these tests.", - case.name, e - ); - } + config.reload_reflow(); + config + } else { + let mut config = FluffConfig::default(); + config.raw.extend(file_core.clone()); + config.reload_reflow(); + config + }; + + let templater = match Linter::get_templater(&config) { + Ok(t) => t, + Err(e) => { + if std::env::var("SQRUFF_SKIP_UNSUPPORTED_TEMPLATERS").is_ok() { + println!("Skipping case '{}': {}", case.name, e); + continue; } - }; - state.linter = - Linter::new(state.linter.config().clone(), None, Some(templater), true).unwrap(); - } + panic!( + "Unsupported templater in case '{}': {}. \ + Set SQRUFF_SKIP_UNSUPPORTED_TEMPLATERS=1 to skip these tests.", + case.name, e + ); + } + }; + let mut linter = Linter::new(config, Some(templater), ParseErrors::Include).unwrap(); match case.kind { TestCaseKind::Pass { pass_str } => { - let result = state.linter.lint_string_wrapped(&pass_str, false).unwrap(); + let result = linter.lint_string_wrapped(&pass_str, Mode::Check).unwrap(); let error_string = format!( r#" The following test test can be used to recreate the issue: @@ -220,18 +213,18 @@ mod tests {{ #[test] fn test_example() {{ - let config = FluffConfig::from_source(" + let config = FluffConfig::try_from_source(" [sqruff] rules = {rule} dialect = {dialect} ", - None); + None).unwrap(); - let mut linter = Linter::new(config, None, None, true); + let mut linter = Linter::new(config, None, ParseErrors::Include); let pass_str = r"{pass_str}"; - let f = linter.lint_string_wrapped(&pass_str, false); + let f = linter.lint_string_wrapped(&pass_str, Mode::Check); assert_eq!(&f.violations, &[]); }} }} @@ -244,7 +237,7 @@ dialect = {dialect} assert_eq!(&result.violations(), &[], "{}", error_string); } TestCaseKind::Fail { fail_str } => { - let file = state.linter.lint_string_wrapped(&fail_str, false).unwrap(); + let file = linter.lint_string_wrapped(&fail_str, Mode::Check).unwrap(); assert_ne!(&file.violations(), &[]) } TestCaseKind::Fix { fail_str, fix_str } => { @@ -253,24 +246,11 @@ dialect = {dialect} "Fail and fix strings should not be equal" ); - let linted = state.linter.lint_string_wrapped(&fail_str, true).unwrap(); + let linted = linter.lint_string_wrapped(&fail_str, Mode::Fix).unwrap(); let actual = linted.fix_string(); pretty_assertions::assert_eq!(actual, fix_str); } } - - if has_config { - *state.linter.config_mut() = FluffConfig::default(); - state.linter.config_mut().raw.extend(state.core.clone()); - state.linter.config_mut().reload_reflow(); - - // Recreate linter with default templater to avoid leaking - // the custom templater (e.g. placeholder) into subsequent tests. - let templater = Linter::get_templater(state.linter.config()) - .expect("Default config should have a valid templater"); - state.linter = - Linter::new(state.linter.config().clone(), None, Some(templater), true).unwrap(); - } } } diff --git a/crates/lib/tests/templaters.rs b/crates/lib/tests/templaters.rs index 0259767a6..84f95d94f 100644 --- a/crates/lib/tests/templaters.rs +++ b/crates/lib/tests/templaters.rs @@ -2,8 +2,10 @@ use hashbrown::HashSet; use expect_test::expect_file; use glob::glob; +use sqruff_lib::api::SourceId; use sqruff_lib::core::config::FluffConfig; use sqruff_lib::core::linter::core::Linter; +use sqruff_lib::templaters::{TemplaterInput, TemplaterOutput}; use sqruff_lib_core::parser::Parser; use sqruff_lib_core::parser::lexer::Lexer; use sqruff_lib_core::parser::segments::Tables; @@ -24,7 +26,7 @@ fn main() { for templater_setup in &templaters_folders { println!("{:?}", templater_setup); let config = std::fs::read_to_string(templater_setup.join(".sqruff")).unwrap(); - let config = FluffConfig::from_source(&config, None); + let config = FluffConfig::try_from_source(&config, None).unwrap(); let templater = match Linter::get_templater(&config) { Ok(t) => t, @@ -51,13 +53,22 @@ fn main() { let lexer = Lexer::from(dialect); let parser = Parser::from(dialect); - let file_name = sql_file.to_string_lossy(); + let source_id = SourceId::Path(sql_file.clone()); let templated_file = templater - .process(&[(&sql, &file_name)], &config, &None) + .process( + &[TemplaterInput { + source: &sql, + source_id: &source_id, + }], + &config, + ) .into_iter() .next() .unwrap() .unwrap(); + let TemplaterOutput::Rendered(templated_file) = templated_file else { + panic!("templater fixture was skipped"); + }; let (tokens, errors) = lexer.lex(&tables, templated_file); assert!(errors.is_empty()); diff --git a/crates/lsp/src/lib.rs b/crates/lsp/src/lib.rs index 4d06eac72..a2a6132ca 100644 --- a/crates/lsp/src/lib.rs +++ b/crates/lsp/src/lib.rs @@ -1,5 +1,3 @@ -use std::path::{Path, PathBuf}; - use hashbrown::HashMap; use lsp_server::{Connection, Message, Request, RequestId, Response}; use lsp_types::notification::{ @@ -11,35 +9,36 @@ use lsp_types::{ Diagnostic, DiagnosticSeverity, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentFormattingParams, InitializeParams, InitializeResult, NumberOrString, OneOf, Position, PublishDiagnosticsParams, - Registration, ServerCapabilities, TextDocumentIdentifier, TextDocumentItem, - TextDocumentSyncCapability, TextDocumentSyncKind, Uri, VersionedTextDocumentIdentifier, + Registration, ServerCapabilities, TextDocumentItem, TextDocumentSyncCapability, + TextDocumentSyncKind, Uri, VersionedTextDocumentIdentifier, }; use serde_json::Value; +use sqruff_lib::api::{ + Engine, EngineOptions, LintDiagnostic, ParseErrors, Source, SourceId, SqruffError, +}; #[cfg(not(target_arch = "wasm32"))] use sqruff_lib::core::config::ConfigLoader; use sqruff_lib::core::config::FluffConfig; -use sqruff_lib::core::linter::core::Linter; #[cfg(not(target_arch = "wasm32"))] use sqruff_lib::ignore::IgnoreFile; -use sqruff_lib::templaters::RAW_TEMPLATER; +use std::borrow::Cow; +use std::path::{Path, PathBuf}; use wasm_bindgen::prelude::*; #[cfg(not(target_arch = "wasm32"))] -fn load_config(root: Option<&Path>) -> FluffConfig { +fn load_config(root: Option<&Path>) -> Result { if let Some(root) = root { let loader = ConfigLoader {}; - loader - .try_load_config_at_path(root) - .map(|config| FluffConfig::new(config, None, None)) - .unwrap_or_default() + let config = loader.try_load_config_up_to_path(root, None, false)?; + Ok(FluffConfig::new(config, None, None)) } else { - FluffConfig::from_root(None, false, None).unwrap_or_default() + FluffConfig::from_root(None, false, None) } } #[cfg(target_arch = "wasm32")] -fn load_config(_root: Option<&Path>) -> FluffConfig { - FluffConfig::default() +fn load_config(_root: Option<&Path>) -> Result { + Ok(FluffConfig::default()) } fn server_initialize_result() -> InitializeResult { @@ -54,9 +53,10 @@ fn server_initialize_result() -> InitializeResult { } pub struct LanguageServer { - linter: Linter, + engine: Engine, send_diagnostics_callback: Box, documents: HashMap, + startup_config_error: Option, #[cfg(not(target_arch = "wasm32"))] workspace_root: PathBuf, #[cfg(not(target_arch = "wasm32"))] @@ -74,46 +74,70 @@ impl Wasm { let send_diagnostics_callback = Box::leak(Box::new(send_diagnostics_callback)); - Self(LanguageServer::new(|diagnostics| { - let diagnostics = serde_wasm_bindgen::to_value(&diagnostics).unwrap(); - send_diagnostics_callback - .call1(&JsValue::null(), &diagnostics) - .unwrap(); - })) + Self(LanguageServer::new( + |diagnostics| match serde_wasm_bindgen::to_value(&diagnostics) { + Ok(diagnostics) => { + if let Err(e) = send_diagnostics_callback.call1(&JsValue::null(), &diagnostics) + { + eprintln!("Failed to send diagnostics: {e:?}"); + } + } + Err(e) => eprintln!("Failed to serialize diagnostics: {e:?}"), + }, + )) } #[wasm_bindgen(js_name = saveRegistrationOptions)] pub fn save_registration_options() -> JsValue { - serde_wasm_bindgen::to_value(&save_registration_options()).unwrap() + serde_wasm_bindgen::to_value(&save_registration_options()).unwrap_or(JsValue::NULL) } #[wasm_bindgen(js_name = updateConfig)] pub fn update_config(&mut self, source: &str) { - *self.0.linter.config_mut() = FluffConfig::from_source(source, None); - self.0.recheck_files(); + let new_config = match FluffConfig::try_from_source(source, None) { + Ok(config) => config, + Err(error) => { + eprintln!("Invalid config, keeping previous configuration: {error}"); + return; + } + }; + match self.0.set_config(new_config) { + Ok(()) => self.0.recheck_files(), + Err(error) => { + eprintln!("Invalid config, keeping previous configuration: {error}"); + } + } } #[wasm_bindgen(js_name = onInitialize)] pub fn on_initialize(&self) -> JsValue { - serde_wasm_bindgen::to_value(&server_initialize_result()).unwrap() + serde_wasm_bindgen::to_value(&server_initialize_result()).unwrap_or(JsValue::NULL) } #[wasm_bindgen(js_name = onNotification)] pub fn on_notification(&mut self, method: &str, params: JsValue) { - self.0 - .on_notification(method, serde_wasm_bindgen::from_value(params).unwrap()) + match serde_wasm_bindgen::from_value(params) { + Ok(params) => self.0.on_notification(method, params), + Err(e) => eprintln!("Failed to deserialize notification params: {e:?}"), + } } #[wasm_bindgen] pub fn format(&mut self, uri: JsValue) -> JsValue { - let uri = serde_wasm_bindgen::from_value(uri).unwrap(); + let uri = match serde_wasm_bindgen::from_value(uri) { + Ok(uri) => uri, + Err(e) => { + eprintln!("Failed to deserialize uri: {e:?}"); + return JsValue::NULL; + } + }; let edits = self.0.format(uri); - serde_wasm_bindgen::to_value(&edits).unwrap() + serde_wasm_bindgen::to_value(&edits).unwrap_or(JsValue::NULL) } #[wasm_bindgen(js_name = formatSource)] pub fn format_source(&mut self, source: &str) -> String { - self.0.format_source(source, None) + self.0.format_source(source) } } @@ -131,19 +155,37 @@ impl LanguageServer { .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))); #[cfg(not(target_arch = "wasm32"))] - let config = load_config(Some(&workspace_root)); - - #[cfg(target_arch = "wasm32")] - let _ = workspace_root; + let config_root = Some(workspace_root.as_path()); #[cfg(target_arch = "wasm32")] - let config = load_config(None); - - let templater = Linter::get_templater(&config).unwrap_or(&RAW_TEMPLATER); + let config_root = None; + + let (config, mut startup_config_error) = match load_config(config_root) { + Ok(config) => (config, None), + Err(error) => { + let message = format!("Failed to load config, using defaults: {error}"); + eprintln!("{message}"); + (FluffConfig::default(), Some(message)) + } + }; + let engine = match Self::new_engine(config) { + Ok(engine) => engine, + Err(error) => { + let message = + format!("Failed to create engine from config, using defaults: {error}"); + eprintln!("{message}"); + if startup_config_error.is_none() { + startup_config_error = Some(message); + } + Self::new_engine(FluffConfig::default()) + .expect("default config must produce a valid engine") + } + }; Self { - linter: Linter::new(config, None, Some(templater), false).unwrap(), + engine, send_diagnostics_callback: Box::new(send_diagnostics_callback), documents: HashMap::new(), + startup_config_error, #[cfg(not(target_arch = "wasm32"))] ignore_file: load_ignore_file(&workspace_root), #[cfg(not(target_arch = "wasm32"))] @@ -154,12 +196,17 @@ impl LanguageServer { fn on_request(&mut self, id: RequestId, method: &str, params: Value) -> Option { match method { Formatting::METHOD => { - let DocumentFormattingParams { - text_document: TextDocumentIdentifier { uri }, - .. - } = serde_json::from_value(params).unwrap(); - - let edits = self.format(uri); + let params: DocumentFormattingParams = match serde_json::from_value(params) { + Ok(p) => p, + Err(e) => { + return Some(Response::new_err( + id, + lsp_server::ErrorCode::InvalidParams as i32, + e.to_string(), + )); + } + }; + let edits = self.format(params.text_document.uri); Some(Response::new_ok(id, edits)) } _ => None, @@ -171,43 +218,51 @@ impl LanguageServer { return Vec::new(); } - let text = self.documents.get(&uri).cloned().unwrap(); - let filename = file_uri_to_path(&uri).map(|path| path.to_string_lossy().to_string()); - let new_text = self.format_source(&text, filename); - self.documents.insert(uri.clone(), new_text.clone()); - Self::build_edits(new_text) + let text = match self.documents.get(&uri).cloned() { + Some(text) => text, + None => return Vec::new(), + }; + let new_text = self.format_source(&text); + build_full_document_edit(&text, new_text) } - fn format_source(&mut self, source: &str, filename: Option) -> String { - match self.linter.lint_string(source, filename, true) { - Ok(tree) => tree.fix_string(), + fn format_source(&mut self, source: &str) -> String { + match self.engine.fix_source(Source { + id: SourceId::Stdin, + text: Cow::Borrowed(source), + }) { + Ok(report) => report.fixed_source.unwrap_or_else(|| source.to_string()), Err(e) => { - eprintln!("Failed to format source: {}", e.value); + eprintln!("Failed to format source: {e}"); source.to_string() } } } - fn build_edits(new_text: String) -> Vec { - let start_position = Position { - line: 0, - character: 0, - }; - let end_position = Position { - line: new_text.lines().count() as u32, - character: new_text.chars().count() as u32, - }; + fn set_config(&mut self, new_config: FluffConfig) -> Result<(), SqruffError> { + self.engine.reload_config(new_config)?; + Ok(()) + } - vec![lsp_types::TextEdit { - range: lsp_types::Range::new(start_position, end_position), - new_text, - }] + fn new_engine(config: FluffConfig) -> Result { + Engine::new( + config, + EngineOptions { + parse_errors: ParseErrors::Include, + }, + ) + } + + pub fn startup_config_error(&self) -> Option<&str> { + self.startup_config_error.as_deref() } pub fn on_notification(&mut self, method: &str, params: Value) { match method { DidOpenTextDocument::METHOD => { - let params: DidOpenTextDocumentParams = serde_json::from_value(params).unwrap(); + let Ok(params) = serde_json::from_value::(params) else { + return; + }; let TextDocumentItem { uri, language_id: _, @@ -219,7 +274,10 @@ impl LanguageServer { self.documents.insert(uri, text); } DidChangeTextDocument::METHOD => { - let params: DidChangeTextDocumentParams = serde_json::from_value(params).unwrap(); + let Ok(params) = serde_json::from_value::(params) + else { + return; + }; let content = params.content_changes[0].text.clone(); let VersionedTextDocumentIdentifier { uri, version: _ } = params.text_document; @@ -228,16 +286,31 @@ impl LanguageServer { self.documents.insert(uri, content); } DidCloseTextDocument::METHOD => { - let params: DidCloseTextDocumentParams = serde_json::from_value(params).unwrap(); + let Ok(params) = serde_json::from_value::(params) + else { + return; + }; self.documents.remove(¶ms.text_document.uri); } DidSaveTextDocument::METHOD => { - let params: DidSaveTextDocumentParams = serde_json::from_value(params).unwrap(); + let Ok(params) = serde_json::from_value::(params) else { + return; + }; let uri = params.text_document.uri.as_str(); if uri.ends_with(".sqlfluff") || uri.ends_with(".sqruff") { - if self.reload_config() { - self.recheck_files(); + let new_config = match self.load_workspace_config() { + Ok(config) => config, + Err(error) => { + eprintln!("Invalid config, keeping previous configuration: {error}"); + return; + } + }; + match self.set_config(new_config) { + Ok(()) => self.recheck_files(), + Err(error) => { + eprintln!("Invalid config, keeping previous configuration: {error}"); + } } } else if uri.ends_with(".sqruffignore") { self.reload_ignore_file(); @@ -261,41 +334,21 @@ impl LanguageServer { return; } - let filename = file_uri_to_path(&uri).map(|path| path.to_string_lossy().to_string()); - let result = match self.linter.lint_string(text, filename, false) { - Ok(result) => result, + let report = match self.engine.check_source(Source { + id: source_id_from_uri(&uri), + text: Cow::Borrowed(text), + }) { + Ok(report) => report, Err(e) => { - eprintln!("Failed to check file: {}", e.value); + eprintln!("Failed to check file: {e}"); return; } }; - let diagnostics = result - .into_violations() - .into_iter() - .map(|violation| { - let range = { - let pos = Position::new( - (violation.line_no as u32).saturating_sub(1), - (violation.line_pos as u32).saturating_sub(1), - ); - lsp_types::Range::new(pos, pos) - }; - - let code = violation - .rule - .map(|rule| NumberOrString::String(rule.code.to_string())); - - Diagnostic::new( - range, - DiagnosticSeverity::WARNING.into(), - code, - Some("sqruff".to_string()), - violation.description, - None, - None, - ) - }) + let diagnostics = report + .diagnostics + .iter() + .map(|diag| to_lsp_diagnostic(diag, text)) .collect(); let diagnostics = PublishDiagnosticsParams::new(uri.clone(), diagnostics, None); @@ -315,25 +368,15 @@ impl LanguageServer { } } - fn reload_config(&mut self) -> bool { - let new_config = { - #[cfg(not(target_arch = "wasm32"))] - { - load_config(Some(&self.workspace_root)) - } - - #[cfg(target_arch = "wasm32")] - { - load_config(None) - } - }; + fn load_workspace_config(&self) -> Result { + #[cfg(not(target_arch = "wasm32"))] + { + load_config(Some(&self.workspace_root)) + } - if Linter::get_templater(&new_config).is_ok() { - *self.linter.config_mut() = new_config; - true - } else { - eprintln!("Invalid templater in config, keeping previous configuration"); - false + #[cfg(target_arch = "wasm32")] + { + load_config(None) } } @@ -353,17 +396,18 @@ fn load_ignore_file(root: &Path) -> IgnoreFile { }) } -pub fn run() { +pub fn run() -> Result<(), Box> { let (connection, io_threads) = Connection::stdio(); - let (id, params) = connection.initialize_start().unwrap(); + let (id, params) = connection.initialize_start()?; - let init_param: InitializeParams = serde_json::from_value(params).unwrap(); - let initialize_result = serde_json::to_value(server_initialize_result()).unwrap(); - connection.initialize_finish(id, initialize_result).unwrap(); + let init_param: InitializeParams = serde_json::from_value(params)?; + let initialize_result = serde_json::to_value(server_initialize_result())?; + connection.initialize_finish(id, initialize_result)?; main_loop(connection, init_param); - io_threads.join().unwrap(); + io_threads.join()?; + Ok(()) } fn main_loop(connection: Connection, init_param: InitializeParams) { @@ -371,7 +415,9 @@ fn main_loop(connection: Connection, init_param: InitializeParams) { let workspace_root = workspace_root_from_initialize(&init_param); let mut lsp = LanguageServer::new_with_workspace_root(workspace_root, move |diagnostics| { let notification = new_notification::(diagnostics); - sender.send(Message::Notification(notification)).unwrap(); + if let Err(e) = sender.send(Message::Notification(notification)) { + eprintln!("Failed to send diagnostics notification: {e}"); + } }); let params = save_registration_options(); @@ -382,18 +428,19 @@ fn main_loop(connection: Connection, init_param: InitializeParams) { "client/registerCapability".to_owned(), params, ))) - .unwrap(); + .unwrap_or_else(|e| eprintln!("Failed to send registration request: {e}")); for message in &connection.receiver { match message { Message::Request(request) => { - if connection.handle_shutdown(&request).unwrap() { + if connection.handle_shutdown(&request).unwrap_or(false) { return; } if let Some(response) = lsp.on_request(request.id, &request.method, request.params) + && let Err(e) = connection.sender.send(Message::Response(response)) { - connection.sender.send(Message::Response(response)).unwrap(); + eprintln!("Failed to send response: {e}"); } } Message::Response(_) => {} @@ -568,149 +615,371 @@ where } } +fn to_lsp_diagnostic(diag: &LintDiagnostic, source: &str) -> Diagnostic { + let line_index = LineIndex::new(source); + let start = line_index.position(diag.source_range.start); + let end = line_index.position(diag.source_range.end); + let range = lsp_types::Range::new(start, end); + + let code = diag.code.clone().map(NumberOrString::String); + + Diagnostic::new( + range, + DiagnosticSeverity::WARNING.into(), + code, + Some("sqruff".to_string()), + diag.message.clone(), + None, + None, + ) +} + +fn source_id_from_uri(uri: &Uri) -> SourceId { + file_uri_to_path(uri).map_or_else(|| SourceId::Virtual(uri.to_string()), SourceId::Path) +} + +fn build_full_document_edit(old_text: &str, new_text: String) -> Vec { + vec![lsp_types::TextEdit { + range: full_document_range(old_text), + new_text, + }] +} + +fn full_document_range(text: &str) -> lsp_types::Range { + lsp_types::Range::new(Position::new(0, 0), LineIndex::new(text).end_position()) +} + +struct LineIndex { + line_starts: Vec, + text: String, +} + +impl LineIndex { + fn new(text: &str) -> Self { + let mut line_starts = vec![0]; + line_starts.extend( + text.bytes() + .enumerate() + .filter_map(|(idx, byte)| (byte == b'\n').then_some(idx + 1)), + ); + + Self { + line_starts, + text: text.to_string(), + } + } + + fn position(&self, byte_offset: usize) -> Position { + let byte_offset = byte_offset.min(self.text.len()); + let line = self.line_for_offset(byte_offset); + let line_start = self.line_starts[line]; + let character = self.text[line_start..byte_offset] + .chars() + .map(char::len_utf16) + .sum::(); + + Position::new(line as u32, character as u32) + } + + fn end_position(&self) -> Position { + self.position(self.text.len()) + } + + fn line_for_offset(&self, byte_offset: usize) -> usize { + match self.line_starts.binary_search(&byte_offset) { + Ok(line) => line, + Err(next_line) => next_line.saturating_sub(1), + } + } +} + #[cfg(test)] mod tests { use std::fs; + use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::time::{SystemTime, UNIX_EPOCH}; + use lsp_types::notification::{ + DidChangeTextDocument, DidOpenTextDocument, DidSaveTextDocument, + }; + use lsp_types::{ + DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, + TextDocumentContentChangeEvent, TextDocumentIdentifier, + }; + use super::*; - struct TempRoot { - path: PathBuf, + static CWD_LOCK: Mutex<()> = Mutex::new(()); + + struct Workspace { + root: PathBuf, + previous: PathBuf, } - impl TempRoot { - fn new() -> Self { - let suffix = SystemTime::now() + impl Workspace { + fn new(name: &str, config: &str) -> Self { + let previous = std::env::current_dir().unwrap(); + let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos(); - let path = std::env::temp_dir() - .join(format!("sqruff-lsp-test-{}-{suffix}", std::process::id())); - fs::create_dir_all(&path).unwrap(); - Self { path } + let root = std::env::temp_dir().join(format!("sqruff-lsp-{name}-{nanos}")); + fs::create_dir_all(&root).unwrap(); + fs::write(root.join(".sqruff"), config).unwrap(); + std::env::set_current_dir(&root).unwrap(); + + Self { root, previous } + } + + fn uri(&self, relative: &str) -> Uri { + file_uri(&self.root.join(relative)) + } + + fn write_config(&self, config: &str) { + fs::write(self.root.join(".sqruff"), config).unwrap(); } } - impl Drop for TempRoot { + impl Drop for Workspace { fn drop(&mut self) { - let _ = fs::remove_dir_all(&self.path); + std::env::set_current_dir(&self.previous).unwrap(); + fs::remove_dir_all(&self.root).unwrap(); } } - fn path_to_uri(path: &Path) -> Uri { - let path = path.canonicalize().unwrap(); + fn config(dialect: &str, templater: &str) -> String { + format!("[sqruff]\ndialect = {dialect}\ntemplater = {templater}\n") + } + + fn file_uri(path: &Path) -> Uri { let path = path.to_string_lossy().replace('\\', "/"); - let uri = if cfg!(windows) { - format!("file:///{path}") + let path = if path.starts_with('/') { + path } else { - format!("file://{path}") + format!("/{path}") }; - uri.parse().unwrap() + Uri::from_str(&format!("file://{path}")).unwrap() } - fn diagnostics_lsp(root: &Path) -> (LanguageServer, Arc>>) { + fn server_with_diagnostics() -> (LanguageServer, Arc>>) { let diagnostics = Arc::new(Mutex::new(Vec::new())); - let sent_diagnostics = Arc::clone(&diagnostics); - let lsp = - LanguageServer::new_with_workspace_root(Some(root.to_path_buf()), move |params| { - sent_diagnostics.lock().unwrap().push(params) - }); + let captured = Arc::clone(&diagnostics); + let server = LanguageServer::new(move |params| { + captured.lock().unwrap().push(params); + }); + + (server, diagnostics) + } + + fn open(server: &mut LanguageServer, uri: Uri, text: &str) { + let params = DidOpenTextDocumentParams { + text_document: TextDocumentItem { + uri, + language_id: "sql".to_string(), + version: 1, + text: text.to_string(), + }, + }; + server.on_notification( + DidOpenTextDocument::METHOD, + serde_json::to_value(params).unwrap(), + ); + } + + fn change(server: &mut LanguageServer, uri: Uri, text: &str) { + let params = DidChangeTextDocumentParams { + text_document: VersionedTextDocumentIdentifier { uri, version: 2 }, + content_changes: vec![TextDocumentContentChangeEvent { + range: None, + range_length: None, + text: text.to_string(), + }], + }; + server.on_notification( + DidChangeTextDocument::METHOD, + serde_json::to_value(params).unwrap(), + ); + } - (lsp, diagnostics) + fn save_config(server: &mut LanguageServer, uri: Uri) { + let params = DidSaveTextDocumentParams { + text_document: TextDocumentIdentifier { uri }, + text: None, + }; + server.on_notification( + DidSaveTextDocument::METHOD, + serde_json::to_value(params).unwrap(), + ); } #[test] - fn loads_config_from_workspace_root() { - let root = TempRoot::new(); - fs::write(root.path.join(".sqruff"), "[sqruff]\ndialect = postgres\n").unwrap(); + fn invalid_sql_publishes_diagnostics() { + let _guard = CWD_LOCK.lock().unwrap(); + let workspace = Workspace::new("invalid-sql", &config("ansi", "raw")); + let (mut server, diagnostics) = server_with_diagnostics(); - let (lsp, _) = diagnostics_lsp(&root.path); + open(&mut server, workspace.uri("bad.sql"), "SELECT FROM\n"); - assert_eq!(lsp.linter.config().dialect_kind().as_ref(), "postgres"); + let diagnostics = diagnostics.lock().unwrap(); + assert!(!diagnostics.last().unwrap().diagnostics.is_empty()); } #[test] - fn clears_diagnostics_for_sqruffignored_file() { - let root = TempRoot::new(); - fs::write( - root.path.join(".sqruff"), - "[sqruff]\ndialect = ansi\nrules = all\n", - ) - .unwrap(); - fs::write(root.path.join(".sqruffignore"), "ignored.sql\n").unwrap(); + fn ignored_files_publish_empty_diagnostics() { + let _guard = CWD_LOCK.lock().unwrap(); + let workspace = Workspace::new("ignored-file", &config("ansi", "raw")); + fs::write(workspace.root.join(".sqruffignore"), "ignored.sql\n").unwrap(); + let (mut server, diagnostics) = server_with_diagnostics(); - let checked = root.path.join("checked.sql"); - fs::write(&checked, "select 1").unwrap(); - let ignored = root.path.join("ignored.sql"); - fs::write(&ignored, "select 1").unwrap(); + open(&mut server, workspace.uri("ignored.sql"), "SELECT FROM\n"); - let (lsp, diagnostics) = diagnostics_lsp(&root.path); + let diagnostics = diagnostics.lock().unwrap(); + assert!(diagnostics.last().unwrap().diagnostics.is_empty()); + } - lsp.check_file(path_to_uri(&checked), "select 1"); - assert!( - !diagnostics - .lock() - .unwrap() - .last() - .unwrap() - .diagnostics - .is_empty(), - "test fixture should produce diagnostics for a non-ignored file", + #[test] + fn startup_config_error_is_visible() { + let _guard = CWD_LOCK.lock().unwrap(); + let _workspace = Workspace::new( + "invalid-startup-config", + "[sqruff]\ntemplater = dbt\n\n[sqruff:templater:dbt]\nproject_dir = 1\n", ); + let (server, _diagnostics) = server_with_diagnostics(); - lsp.check_file(path_to_uri(&ignored), "select 1"); assert!( - diagnostics - .lock() - .unwrap() - .last() - .unwrap() - .diagnostics - .is_empty(), - "ignored files should publish an empty diagnostics set", + server + .startup_config_error() + .is_some_and(|error| error.contains("invalid path value")) ); } #[test] - fn skips_formatting_sqruffignored_file() { - let root = TempRoot::new(); - fs::write( - root.path.join(".sqruff"), - "[sqruff]\ndialect = ansi\nrules = all\n", - ) - .unwrap(); - fs::write(root.path.join(".sqruffignore"), "ignored.sql\n").unwrap(); + fn formatting_returns_full_document_edit_for_old_range() { + let _guard = CWD_LOCK.lock().unwrap(); + let workspace = Workspace::new("format-range", &config("ansi", "raw")); + let (mut server, _diagnostics) = server_with_diagnostics(); + let uri = workspace.uri("format.sql"); - let ignored = root.path.join("ignored.sql"); - fs::write(&ignored, "select 1").unwrap(); + open(&mut server, uri.clone(), "SELECT 1"); + let edits = server.format(uri); - let (mut lsp, _) = diagnostics_lsp(&root.path); - let uri = path_to_uri(&ignored); - lsp.documents.insert(uri.clone(), "select 1".to_string()); + assert_eq!(edits.len(), 1); + assert_eq!(edits[0].range.start, Position::new(0, 0)); + assert_eq!(edits[0].range.end, Position::new(0, 9)); + assert_eq!(edits[0].new_text, "SELECT 1\n"); + } - assert!( - lsp.format(uri).is_empty(), - "ignored files should not receive formatting edits", - ); + #[test] + fn formatting_does_not_mutate_document_before_did_change() { + let _guard = CWD_LOCK.lock().unwrap(); + let workspace = Workspace::new("format-no-mutate", &config("ansi", "raw")); + let (mut server, _diagnostics) = server_with_diagnostics(); + let uri = workspace.uri("format.sql"); + + open(&mut server, uri.clone(), "SELECT 1"); + let edits = server.format(uri.clone()); + + assert_eq!(edits[0].new_text, "SELECT 1\n"); + assert_eq!(server.documents.get(&uri).unwrap(), "SELECT 1"); + + change(&mut server, uri.clone(), &edits[0].new_text); + assert_eq!(server.documents.get(&uri).unwrap(), "SELECT 1\n"); } #[test] - fn file_uri_with_localhost_authority_is_supported() { - let uri: Uri = if cfg!(windows) { - "file://localhost/C:/tmp/sqruff.sql".parse().unwrap() - } else { - "file://localhost/tmp/sqruff.sql".parse().unwrap() - }; + fn changing_dialect_reloads_diagnostics() { + let _guard = CWD_LOCK.lock().unwrap(); + let workspace = Workspace::new("reload-dialect", &config("ansi", "raw")); + let (mut server, diagnostics) = server_with_diagnostics(); + let uri = workspace.uri("postgres.sql"); + let sql = "SELECT DISTINCT ON (customer_id)\n customer_id\nFROM orders\nORDER BY customer_id;\n"; + + open(&mut server, uri, sql); + let ansi_count = diagnostics + .lock() + .unwrap() + .last() + .unwrap() + .diagnostics + .len(); + + workspace.write_config(&config("postgres", "raw")); + save_config(&mut server, workspace.uri(".sqruff")); + let postgres_count = diagnostics + .lock() + .unwrap() + .last() + .unwrap() + .diagnostics + .len(); + + assert!(ansi_count > postgres_count); + } - let path = file_uri_to_path(&uri).unwrap(); - assert!(path.ends_with("sqruff.sql")); + #[test] + fn changing_templater_reloads_templater() { + let _guard = CWD_LOCK.lock().unwrap(); + let workspace = Workspace::new("reload-templater", &config("postgres", "raw")); + let (mut server, diagnostics) = server_with_diagnostics(); + let uri = workspace.uri("placeholder.sql"); + let sql = "SELECT :x AS value\n"; + + open(&mut server, uri, sql); + let raw_count = diagnostics + .lock() + .unwrap() + .last() + .unwrap() + .diagnostics + .len(); + + workspace.write_config( + "[sqruff]\ndialect = postgres\ntemplater = placeholder\n\n[sqruff:templater:placeholder]\nparam_style = colon\nx = 1\n", + ); + save_config(&mut server, workspace.uri(".sqruff")); + let placeholder_count = diagnostics + .lock() + .unwrap() + .last() + .unwrap() + .diagnostics + .len(); + + assert!(raw_count > placeholder_count); } #[test] - fn file_uri_with_non_local_authority_is_rejected() { - let uri: Uri = "file://example.com/tmp/sqruff.sql".parse().unwrap(); - assert!(file_uri_to_path(&uri).is_none()); + fn invalid_templater_keeps_previous_config() { + let _guard = CWD_LOCK.lock().unwrap(); + let workspace = Workspace::new( + "invalid-templater", + "[sqruff]\ndialect = postgres\ntemplater = placeholder\n\n[sqruff:templater:placeholder]\nparam_style = colon\nx = 1\n", + ); + let (mut server, diagnostics) = server_with_diagnostics(); + let uri = workspace.uri("placeholder.sql"); + let sql = "SELECT :x AS value\n"; + + open(&mut server, uri, sql); + let before = diagnostics + .lock() + .unwrap() + .last() + .unwrap() + .diagnostics + .len(); + + workspace.write_config("[sqruff]\ndialect = postgres\ntemplater = not_real\n"); + save_config(&mut server, workspace.uri(".sqruff")); + let after = diagnostics + .lock() + .unwrap() + .last() + .unwrap() + .diagnostics + .len(); + + assert_eq!(before, 0); + assert_eq!(after, 0); } }