diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index e8393757..4600ac92 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -33,8 +33,10 @@ jobs: steps: - name: Checkout PR branch uses: actions/checkout@v4 + - name: Free Disk Space uses: ./.github/actions/free-disk-space + - name: Install toolchain uses: moonrepo/setup-rust@v1 with: @@ -43,15 +45,23 @@ jobs: cache-base: main env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Setup Biome - uses: biomejs/setup-biome@v2 - with: - version: latest + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install JS dependencies + run: bun install + + - name: Setup Just + uses: extractions/setup-just@v3 + + - name: Echo Tool Versions + run: | + just format-ci-versions + - name: Run format run: | - cargo fmt --all --check - taplo format --check - biome format + just format-ci actionlint: name: Lint GitHub Actions @@ -96,17 +106,22 @@ jobs: - name: Setup sqlx-cli run: cargo install sqlx-cli - - name: Setup Biome - uses: biomejs/setup-biome@v2 - with: - version: latest + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install JS dependencies + run: bun install + + - name: Setup Just + uses: extractions/setup-just@v3 + + - name: Echo Tool Versions + run: | + just lint-ci-versions - name: Run Lints run: | - cargo sqlx prepare --check --workspace - cargo clippy --fix - cargo run -p rules_check - biome lint --write + just lint-ci - name: Check for changes run: | diff --git a/Cargo.toml b/Cargo.toml index ef29bcaa..aaaa9035 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ homepage = "https://supabase.com/" keywords = ["linter", "typechecker", "postgres", "language-server"] license = "MIT" repository = "https://github.com/supabase-community/postgres-language-server" -rust-version = "1.85.0" +rust-version = "1.86.0" [workspace.dependencies] # supporting crates unrelated to postgres diff --git a/crates/pgt_cli/src/cli_options.rs b/crates/pgt_cli/src/cli_options.rs index d1bfaee9..5c41c7fc 100644 --- a/crates/pgt_cli/src/cli_options.rs +++ b/crates/pgt_cli/src/cli_options.rs @@ -18,10 +18,6 @@ pub struct CliOptions { #[bpaf(long("use-server"), switch, fallback(false))] pub use_server: bool, - /// Skip connecting to the database and only run checks that don't require a database connection. - #[bpaf(long("skip-db"), switch, fallback(false))] - pub skip_db: bool, - /// Print additional diagnostics, and some diagnostics show more information. Also, print out what files were processed and which ones were modified. #[bpaf(long("verbose"), switch, fallback(false))] pub verbose: bool, diff --git a/crates/pgt_cli/src/commands/mod.rs b/crates/pgt_cli/src/commands/mod.rs index b166a033..ebd16e3d 100644 --- a/crates/pgt_cli/src/commands/mod.rs +++ b/crates/pgt_cli/src/commands/mod.rs @@ -307,7 +307,6 @@ pub(crate) trait CommandRunner: Sized { configuration, vcs_base_path, gitignore_matches, - skip_db: cli_options.skip_db, })?; let execution = self.get_execution(cli_options, console, workspace)?; diff --git a/crates/pgt_completions/src/providers/helper.rs b/crates/pgt_completions/src/providers/helper.rs index 274ded20..2e4ef8a9 100644 --- a/crates/pgt_completions/src/providers/helper.rs +++ b/crates/pgt_completions/src/providers/helper.rs @@ -7,9 +7,7 @@ pub(crate) fn get_completion_text_with_schema( item_name: &str, item_schema_name: &str, ) -> Option { - if item_schema_name == "public" { - None - } else if ctx.schema_name.is_some() { + if item_schema_name == "public" || ctx.schema_name.is_some() { None } else { let node = ctx.node_under_cursor.unwrap(); diff --git a/crates/pgt_completions/src/sanitization.rs b/crates/pgt_completions/src/sanitization.rs index 59eb609f..248a0ffa 100644 --- a/crates/pgt_completions/src/sanitization.rs +++ b/crates/pgt_completions/src/sanitization.rs @@ -219,19 +219,19 @@ mod tests { .set_language(tree_sitter_sql::language()) .expect("Error loading sql language"); - let mut tree = parser.parse(input, None).unwrap(); + let tree = parser.parse(input, None).unwrap(); // select | from users; <-- just right, one space after select token, one space before from - assert!(cursor_inbetween_nodes(&mut tree, TextSize::new(7))); + assert!(cursor_inbetween_nodes(&tree, TextSize::new(7))); // select| from users; <-- still on select token - assert!(!cursor_inbetween_nodes(&mut tree, TextSize::new(6))); + assert!(!cursor_inbetween_nodes(&tree, TextSize::new(6))); // select |from users; <-- already on from token - assert!(!cursor_inbetween_nodes(&mut tree, TextSize::new(8))); + assert!(!cursor_inbetween_nodes(&tree, TextSize::new(8))); // select from users;| - assert!(!cursor_inbetween_nodes(&mut tree, TextSize::new(19))); + assert!(!cursor_inbetween_nodes(&tree, TextSize::new(19))); } #[test] @@ -243,29 +243,29 @@ mod tests { .set_language(tree_sitter_sql::language()) .expect("Error loading sql language"); - let mut tree = parser.parse(input, None).unwrap(); + let tree = parser.parse(input, None).unwrap(); // select * from| <-- still on previous token assert!(!cursor_prepared_to_write_token_after_last_node( - &mut tree, + &tree, TextSize::new(13) )); // select * from | <-- too far off, two spaces afterward assert!(!cursor_prepared_to_write_token_after_last_node( - &mut tree, + &tree, TextSize::new(15) )); // select * |from <-- it's within assert!(!cursor_prepared_to_write_token_after_last_node( - &mut tree, + &tree, TextSize::new(9) )); // select * from | <-- just right assert!(cursor_prepared_to_write_token_after_last_node( - &mut tree, + &tree, TextSize::new(14) )); } @@ -295,26 +295,26 @@ mod tests { .set_language(tree_sitter_sql::language()) .expect("Error loading sql language"); - let mut tree = parser.parse(input, None).unwrap(); + let tree = parser.parse(input, None).unwrap(); // select * from ;| <-- it's after the statement - assert!(!cursor_before_semicolon(&mut tree, TextSize::new(19))); + assert!(!cursor_before_semicolon(&tree, TextSize::new(19))); // select * from| ; <-- still touches the from - assert!(!cursor_before_semicolon(&mut tree, TextSize::new(13))); + assert!(!cursor_before_semicolon(&tree, TextSize::new(13))); // not okay to be ON the semi. // select * from |; - assert!(!cursor_before_semicolon(&mut tree, TextSize::new(18))); + assert!(!cursor_before_semicolon(&tree, TextSize::new(18))); // anything is fine here // select * from | ; // select * from | ; // select * from | ; // select * from |; - assert!(cursor_before_semicolon(&mut tree, TextSize::new(14))); - assert!(cursor_before_semicolon(&mut tree, TextSize::new(15))); - assert!(cursor_before_semicolon(&mut tree, TextSize::new(16))); - assert!(cursor_before_semicolon(&mut tree, TextSize::new(17))); + assert!(cursor_before_semicolon(&tree, TextSize::new(14))); + assert!(cursor_before_semicolon(&tree, TextSize::new(15))); + assert!(cursor_before_semicolon(&tree, TextSize::new(16))); + assert!(cursor_before_semicolon(&tree, TextSize::new(17))); } } diff --git a/crates/pgt_completions/src/test_helper.rs b/crates/pgt_completions/src/test_helper.rs index fc2cf403..b1c5b399 100644 --- a/crates/pgt_completions/src/test_helper.rs +++ b/crates/pgt_completions/src/test_helper.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use pgt_schema_cache::SchemaCache; use pgt_test_utils::test_database::get_new_test_db; use sqlx::Executor; @@ -25,9 +27,9 @@ impl From<&str> for InputQuery { } } -impl ToString for InputQuery { - fn to_string(&self) -> String { - self.sql.clone() +impl Display for InputQuery { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.sql) } } diff --git a/crates/pgt_configuration/src/database.rs b/crates/pgt_configuration/src/database.rs index 209f86dc..39efb8d1 100644 --- a/crates/pgt_configuration/src/database.rs +++ b/crates/pgt_configuration/src/database.rs @@ -10,6 +10,8 @@ use serde::{Deserialize, Serialize}; #[partial(serde(rename_all = "camelCase", default, deny_unknown_fields))] pub struct DatabaseConfiguration { /// The host of the database. + /// Required if you want database-related features. + /// All else falls back to sensible defaults. #[partial(bpaf(long("host")))] pub host: String, @@ -35,11 +37,17 @@ pub struct DatabaseConfiguration { /// The connection timeout in seconds. #[partial(bpaf(long("conn_timeout_secs"), fallback(Some(10)), debug_fallback))] pub conn_timeout_secs: u16, + + /// Actively disable all database-related features. + #[partial(bpaf(long("disable-db"), switch, fallback(Some(false))))] + #[partial(cfg_attr(feature = "schema", schemars(skip)))] + pub disable_connection: bool, } impl Default for DatabaseConfiguration { fn default() -> Self { Self { + disable_connection: false, host: "127.0.0.1".to_string(), port: 5432, username: "postgres".to_string(), diff --git a/crates/pgt_configuration/src/lib.rs b/crates/pgt_configuration/src/lib.rs index f262450d..fcf0b5c6 100644 --- a/crates/pgt_configuration/src/lib.rs +++ b/crates/pgt_configuration/src/lib.rs @@ -110,8 +110,9 @@ impl PartialConfiguration { username: Some("postgres".to_string()), password: Some("postgres".to_string()), database: Some("postgres".to_string()), - conn_timeout_secs: Some(10), allow_statement_executions_against: Default::default(), + conn_timeout_secs: Some(10), + disable_connection: Some(false), }), } } diff --git a/crates/pgt_lsp/src/session.rs b/crates/pgt_lsp/src/session.rs index 64adf16a..db17dfd1 100644 --- a/crates/pgt_lsp/src/session.rs +++ b/crates/pgt_lsp/src/session.rs @@ -449,7 +449,6 @@ impl Session { configuration: fs_configuration, vcs_base_path, gitignore_matches, - skip_db: false, }); if let Err(error) = result { diff --git a/crates/pgt_lsp/tests/server.rs b/crates/pgt_lsp/tests/server.rs index 8e40c097..581ea1fe 100644 --- a/crates/pgt_lsp/tests/server.rs +++ b/crates/pgt_lsp/tests/server.rs @@ -773,14 +773,15 @@ async fn test_execute_statement() -> Result<()> { .to_string(); let host = test_db.connect_options().get_host().to_string(); - let conf = PartialConfiguration { + let mut conf = PartialConfiguration::init(); + conf.merge_with(PartialConfiguration { db: Some(PartialDatabaseConfiguration { database: Some(database), host: Some(host), ..Default::default() }), ..Default::default() - }; + }); fs.insert( url!("postgrestools.jsonc").to_file_path().unwrap(), diff --git a/crates/pgt_statement_splitter/src/lib.rs b/crates/pgt_statement_splitter/src/lib.rs index e5e995b7..5f6ca92f 100644 --- a/crates/pgt_statement_splitter/src/lib.rs +++ b/crates/pgt_statement_splitter/src/lib.rs @@ -85,6 +85,13 @@ mod tests { } } + #[test] + fn ts_with_timezone() { + Tester::from("alter table foo add column bar timestamp with time zone;").expect_statements( + vec!["alter table foo add column bar timestamp with time zone;"], + ); + } + #[test] fn failing_lexer() { let input = "select 1443ddwwd33djwdkjw13331333333333"; diff --git a/crates/pgt_statement_splitter/src/parser/common.rs b/crates/pgt_statement_splitter/src/parser/common.rs index ab3f8173..56d8d86a 100644 --- a/crates/pgt_statement_splitter/src/parser/common.rs +++ b/crates/pgt_statement_splitter/src/parser/common.rs @@ -249,6 +249,8 @@ pub(crate) fn unknown(p: &mut Parser, exclude: &[SyntaxKind]) { SyntaxKind::Ordinality, // WITH CHECK should not start a new statement SyntaxKind::Check, + // TIMESTAMP WITH TIME ZONE should not start a new statement + SyntaxKind::Time, ] .iter() .all(|x| Some(x) != next.as_ref()) diff --git a/crates/pgt_workspace/src/settings.rs b/crates/pgt_workspace/src/settings.rs index d4ea462a..f9275aa9 100644 --- a/crates/pgt_workspace/src/settings.rs +++ b/crates/pgt_workspace/src/settings.rs @@ -268,6 +268,7 @@ impl Default for LinterSettings { /// Database settings for the entire workspace #[derive(Debug)] pub struct DatabaseSettings { + pub enable_connection: bool, pub host: String, pub port: u16, pub username: String, @@ -280,6 +281,7 @@ pub struct DatabaseSettings { impl Default for DatabaseSettings { fn default() -> Self { Self { + enable_connection: false, host: "127.0.0.1".to_string(), port: 5432, username: "postgres".to_string(), @@ -295,6 +297,13 @@ impl From for DatabaseSettings { fn from(value: PartialDatabaseConfiguration) -> Self { let d = DatabaseSettings::default(); + // "host" is the minimum required setting for database features + // to be enabled. + let enable_connection = value + .host + .as_ref() + .is_some_and(|_| value.disable_connection.is_none_or(|disabled| !disabled)); + let database = value.database.unwrap_or(d.database); let host = value.host.unwrap_or(d.host); @@ -312,6 +321,8 @@ impl From for DatabaseSettings { .unwrap_or(false); Self { + enable_connection, + port: value.port.unwrap_or(d.port), username: value.username.unwrap_or(d.username), password: value.password.unwrap_or(d.password), diff --git a/crates/pgt_workspace/src/workspace.rs b/crates/pgt_workspace/src/workspace.rs index 54f7200b..873dd83e 100644 --- a/crates/pgt_workspace/src/workspace.rs +++ b/crates/pgt_workspace/src/workspace.rs @@ -73,7 +73,6 @@ pub struct UpdateSettingsParams { pub vcs_base_path: Option, pub gitignore_matches: Vec, pub workspace_directory: Option, - pub skip_db: bool, } #[derive(Debug, serde::Serialize, serde::Deserialize)] diff --git a/crates/pgt_workspace/src/workspace/server.rs b/crates/pgt_workspace/src/workspace/server.rs index 3bf540cc..3c14f352 100644 --- a/crates/pgt_workspace/src/workspace/server.rs +++ b/crates/pgt_workspace/src/workspace/server.rs @@ -168,12 +168,10 @@ impl Workspace for WorkspaceServer { tracing::info!("Updated settings in workspace"); - if !params.skip_db { - self.connection - .write() - .unwrap() - .set_conn_settings(&self.settings().as_ref().db); - } + self.connection + .write() + .unwrap() + .set_conn_settings(&self.settings().as_ref().db); tracing::info!("Updated Db connection settings"); diff --git a/crates/pgt_workspace/src/workspace/server/change.rs b/crates/pgt_workspace/src/workspace/server/change.rs index db743b36..6e86abcf 100644 --- a/crates/pgt_workspace/src/workspace/server/change.rs +++ b/crates/pgt_workspace/src/workspace/server/change.rs @@ -1470,7 +1470,7 @@ mod tests { assert_eq!(old_stmt_text, "select * from"); } - _ => assert!(false, "Did not yield a modified statement."), + _ => unreachable!("Did not yield a modified statement."), } assert_document_integrity(&doc); @@ -1516,7 +1516,7 @@ mod tests { assert_eq!(old_stmt_text, "select * from"); } - _ => assert!(false, "Did not yield a modified statement."), + _ => unreachable!("Did not yield a modified statement."), } assert_document_integrity(&doc); @@ -1559,7 +1559,7 @@ mod tests { assert_eq!(new_stmt_text, "select * from users"); } - _ => assert!(false, "Did not yield a modified statement."), + _ => unreachable!("Did not yield a modified statement."), } assert_document_integrity(&doc); diff --git a/crates/pgt_workspace/src/workspace/server/db_connection.rs b/crates/pgt_workspace/src/workspace/server/db_connection.rs index d1be3131..d002c0a2 100644 --- a/crates/pgt_workspace/src/workspace/server/db_connection.rs +++ b/crates/pgt_workspace/src/workspace/server/db_connection.rs @@ -16,6 +16,11 @@ impl DbConnection { } pub(crate) fn set_conn_settings(&mut self, settings: &DatabaseSettings) { + if !settings.enable_connection { + tracing::info!("Database connection disabled."); + return; + } + let config = PgConnectOptions::new() .host(&settings.host) .port(settings.port) diff --git a/crates/pgt_workspace/src/workspace/server/document.rs b/crates/pgt_workspace/src/workspace/server/document.rs index f2c500cc..67ed991c 100644 --- a/crates/pgt_workspace/src/workspace/server/document.rs +++ b/crates/pgt_workspace/src/workspace/server/document.rs @@ -43,7 +43,7 @@ impl Document { .any(|d| d.severity() == Severity::Fatal) } - pub fn iter<'a>(&'a self) -> StatementIterator<'a> { + pub fn iter(&self) -> StatementIterator<'_> { StatementIterator::new(self) } } diff --git a/crates/pgt_workspace/src/workspace/server/parsed_document.rs b/crates/pgt_workspace/src/workspace/server/parsed_document.rs index 01f18d3c..92f33926 100644 --- a/crates/pgt_workspace/src/workspace/server/parsed_document.rs +++ b/crates/pgt_workspace/src/workspace/server/parsed_document.rs @@ -353,7 +353,7 @@ impl<'a> StatementMapper<'a> for GetCompletionsMapper { pub struct GetCompletionsFilter { pub cursor_position: TextSize, } -impl<'a> StatementFilter<'a> for GetCompletionsFilter { +impl StatementFilter<'_> for GetCompletionsFilter { fn predicate(&self, _id: &StatementId, range: &TextRange, content: &str) -> bool { let is_terminated_by_semi = content.chars().last().is_some_and(|c| c == ';'); @@ -367,7 +367,7 @@ impl<'a> StatementFilter<'a> for GetCompletionsFilter { } pub struct NoFilter; -impl<'a> StatementFilter<'a> for NoFilter { +impl StatementFilter<'_> for NoFilter { fn predicate(&self, _id: &StatementId, _range: &TextRange, _content: &str) -> bool { true } @@ -383,7 +383,7 @@ impl CursorPositionFilter { } } -impl<'a> StatementFilter<'a> for CursorPositionFilter { +impl StatementFilter<'_> for CursorPositionFilter { fn predicate(&self, _id: &StatementId, range: &TextRange, _content: &str) -> bool { range.contains(self.pos) } @@ -399,7 +399,7 @@ impl IdFilter { } } -impl<'a> StatementFilter<'a> for IdFilter { +impl StatementFilter<'_> for IdFilter { fn predicate(&self, id: &StatementId, _range: &TextRange, _content: &str) -> bool { *id == self.id } diff --git a/docs/schemas/0.0.0/schema.json b/docs/schemas/0.0.0/schema.json index 086adf3d..faba3b5c 100644 --- a/docs/schemas/0.0.0/schema.json +++ b/docs/schemas/0.0.0/schema.json @@ -100,7 +100,7 @@ ] }, "host": { - "description": "The host of the database.", + "description": "The host of the database. Required if you want database-related features. All else falls back to sensible defaults.", "type": [ "string", "null" diff --git a/docs/schemas/latest/schema.json b/docs/schemas/latest/schema.json index 086adf3d..faba3b5c 100644 --- a/docs/schemas/latest/schema.json +++ b/docs/schemas/latest/schema.json @@ -100,7 +100,7 @@ ] }, "host": { - "description": "The host of the database.", + "description": "The host of the database. Required if you want database-related features. All else falls back to sensible defaults.", "type": [ "string", "null" diff --git a/justfile b/justfile index 3e7163b3..a55207ae 100644 --- a/justfile +++ b/justfile @@ -10,16 +10,15 @@ alias rg := reset-git # Installs the tools needed to develop install-tools: cargo install cargo-binstall - cargo binstall cargo-insta taplo-cli + cargo binstall cargo-insta taplo-cli sqlx-cli cargo binstall --git "https://github.com/astral-sh/uv" uv bun install # Upgrades the tools needed to develop upgrade-tools: cargo install cargo-binstall --force - cargo binstall cargo-insta taplo-cli --force + cargo binstall cargo-insta taplo-cli sqlx-cli --force cargo binstall --git "https://github.com/astral-sh/uv" uv --force - bun install # Generates code generated files for the linter gen-lint: @@ -41,6 +40,16 @@ format: taplo format bun biome format --write +format-ci: + cargo fmt --all --check + taplo format --check + bun biome format + +format-ci-versions: + cargo --version + taplo --version + echo "Biome $(bun biome --version)" + [unix] _touch file: touch {{file}} @@ -72,11 +81,27 @@ lint-fix: cargo run -p rules_check bun biome lint --write +lint-ci-versions: + rustc --version + rustup --version + cargo --version + cargo sqlx --version + cargo clippy --version + echo "Biome $(bun biome --version)" + +lint-ci: + cargo sqlx prepare --check --workspace + cargo clippy --fix + cargo run -p rules_check + bun biome lint --write + serve-docs: uv sync uv run mkdocs serve # When you finished coding, run this command. Note that you should have already committed your changes. +# If you haven't run `sqlx prepare` at least once, you need to run `docker compose up` +# to lint the queries. ready: git diff --exit-code --quiet cargo run -p xtask_codegen -- configuration @@ -113,4 +138,4 @@ merge-main: # We recommend to install `bunyan` (npm i -g bunyan) and pipe the output through there for color-coding: # just show-logs | bunyan show-logs: - tail -f $(ls $PGT_LOG_PATH/server.log.* | sort -t- -k2,2 -k3,3 -k4,4 | tail -n 1) + tail -f $(ls $PGT_LOG_PATH/server.log.* | sort -t- -k2,2 -k3,3 -k4,4 | tail -n 1) \ No newline at end of file diff --git a/packages/@postgrestools/backend-jsonrpc/src/workspace.ts b/packages/@postgrestools/backend-jsonrpc/src/workspace.ts index 5c9a1ac4..a35dad81 100644 --- a/packages/@postgrestools/backend-jsonrpc/src/workspace.ts +++ b/packages/@postgrestools/backend-jsonrpc/src/workspace.ts @@ -179,21 +179,36 @@ export interface GetCompletionsParams { */ position: TextSize; } -export interface CompletionResult { +export interface CompletionsResult { items: CompletionItem[]; } export interface CompletionItem { + completion_text?: CompletionText; description: string; kind: CompletionItemKind; label: string; preselected: boolean; - score: number; + /** + * String used for sorting by LSP clients. + */ + sort_text: string; +} +/** + * The text that the editor should fill in. If `None`, the `label` should be used. Tables, for example, might have different completion_texts: + +label: "users", description: "Schema: auth", completion_text: "auth.users". + */ +export interface CompletionText { + /** + * A `range` is required because some editors replace the current token, others naively insert the text. Having a range where start == end makes it an insertion. + */ + range: TextRange; + text: string; } -export type CompletionItemKind = "table" | "function" | "column"; +export type CompletionItemKind = "table" | "function" | "column" | "schema"; export interface UpdateSettingsParams { configuration: PartialConfiguration; gitignore_matches: string[]; - skip_db: boolean; vcs_base_path?: string; workspace_directory?: string; } @@ -240,7 +255,7 @@ export interface PartialDatabaseConfiguration { */ database?: string; /** - * The host of the database. + * The host of the database. Required if you want database-related features. All else falls back to sensible defaults. */ host?: string; /** @@ -414,7 +429,7 @@ export interface Workspace { pullDiagnostics( params: PullDiagnosticsParams, ): Promise; - getCompletions(params: GetCompletionsParams): Promise; + getCompletions(params: GetCompletionsParams): Promise; updateSettings(params: UpdateSettingsParams): Promise; openFile(params: OpenFileParams): Promise; changeFile(params: ChangeFileParams): Promise; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ef2c880a..4501f2a1 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,7 +1,3 @@ [toolchain] -# The default profile includes rustc, rust-std, cargo, rust-docs, rustfmt and clippy. -# https://rust-lang.github.io/rustup/concepts/profiles.html profile = "default" -# For some reason, rustfmt is not included in the default profile. Add it here. -components = ["rustfmt"] -channel = "nightly" +channel = "1.86.0"