From b68d88ec522ccf09792d4448505983034cb81346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Domen=20Ko=C5=BEar?= Date: Thu, 17 Oct 2024 17:19:23 +0100 Subject: [PATCH] WIP nix --- topiary-cli/Cargo.toml | 56 ++++--- topiary-cli/src/io.rs | 3 + topiary-config/Cargo.toml | 26 +-- topiary-config/languages.ncl | 8 + topiary-queries/Cargo.toml | 1 + topiary-queries/queries/nix.scm | 286 ++++++++++++++++++++++++++++++++ topiary-queries/src/lib.rs | 6 + 7 files changed, 350 insertions(+), 36 deletions(-) create mode 100644 topiary-queries/queries/nix.scm diff --git a/topiary-cli/Cargo.toml b/topiary-cli/Cargo.toml index 7eae00c9..0e78ab03 100644 --- a/topiary-cli/Cargo.toml +++ b/topiary-cli/Cargo.toml @@ -3,12 +3,12 @@ name = "topiary-cli" description = "CLI app for Topiary, the universal code formatter." categories = ["command-line-utilities", "development-tools", "text-processing"] keywords = [ - "cli", - "code-formatter", - "formatter", - "text", - "tree-sitter", - "utility", + "cli", + "code-formatter", + "formatter", + "text", + "tree-sitter", + "utility", ] version.workspace = true edition.workspace = true @@ -35,7 +35,12 @@ itertools = { workspace = true } log = { workspace = true } serde = { workspace = true, features = ["derive"] } tempfile = { workspace = true } -tokio = { workspace = true, features = ["fs", "rt-multi-thread", "sync", "macros"] } +tokio = { workspace = true, features = [ + "fs", + "rt-multi-thread", + "sync", + "macros", +] } toml = { workspace = true } topiary-core = { path = "../topiary-core" } topiary-config = { path = "../topiary-config" } @@ -58,34 +63,37 @@ predicates = { workspace = true } [features] default = [ - "contributed", - "json", - "nickel", - "ocaml", - "ocaml_interface", - "ocamllex", - "toml", - "tree_sitter_query" + "contributed", + "json", + "nickel", + "nix", + "ocaml", + "ocaml_interface", + "ocamllex", + "toml", + "tree_sitter_query", ] # Included by default -contributed = [ - "css" -] +contributed = ["css"] # Excluded by default -experimental = [ - "bash", - "rust", -] +experimental = ["bash", "rust"] +nix = ["topiary-config/nix", "topiary-queries/nix"] bash = ["topiary-config/bash", "topiary-queries/bash"] css = ["topiary-config/css", "topiary-queries/css"] json = ["topiary-config/json", "topiary-queries/json"] nickel = ["topiary-config/nickel", "topiary-queries/nickel"] ocaml = ["topiary-config/ocaml", "topiary-queries/ocaml"] -ocaml_interface = ["topiary-config/ocaml_interface", "topiary-queries/ocaml_interface"] +ocaml_interface = [ + "topiary-config/ocaml_interface", + "topiary-queries/ocaml_interface", +] ocamllex = ["topiary-config/ocamllex", "topiary-queries/ocamllex"] rust = ["topiary-config/rust", "topiary-queries/rust"] toml = ["topiary-config/toml", "topiary-queries/toml"] -tree_sitter_query = ["topiary-config/tree_sitter_query", "topiary-queries/tree_sitter_query"] +tree_sitter_query = [ + "topiary-config/tree_sitter_query", + "topiary-queries/tree_sitter_query", +] diff --git a/topiary-cli/src/io.rs b/topiary-cli/src/io.rs index 0c3a7753..648fd59f 100644 --- a/topiary-cli/src/io.rs +++ b/topiary-cli/src/io.rs @@ -350,6 +350,9 @@ where #[cfg(feature = "toml")] "toml" => Ok(topiary_queries::toml().into()), + #[cfg(feature = "nix")] + "nix" => Ok(topiary_queries::nix().into()), + #[cfg(feature = "tree_sitter_query")] "tree_sitter_query" => Ok(topiary_queries::tree_sitter_query().into()), diff --git a/topiary-config/Cargo.toml b/topiary-config/Cargo.toml index 36103a3d..db094589 100644 --- a/topiary-config/Cargo.toml +++ b/topiary-config/Cargo.toml @@ -33,11 +33,11 @@ git2.workspace = true libloading.workspace = true [features] -default = [ "parallel" ] +default = ["parallel"] # Enabling the `parallel` feature enables parallel computation where possible. # At the moment, this is only in grammar prefetching -parallel = [ "dep:rayon" ] +parallel = ["dep:rayon"] bash = [] css = [] @@ -46,6 +46,7 @@ nickel = [] ocaml = [] ocaml_interface = [] ocamllex = [] +nix = [] rust = [] toml = [] tree_sitter_query = [] @@ -53,14 +54,15 @@ tree_sitter_query = [] # This a convenience for the sake of downstream applications which don't # wish to cherry-pick grammars (e.g., the playground) all = [ - "bash", - "css", - "json", - "nickel", - "ocaml", - "ocaml_interface", - "ocamllex", - "rust", - "toml", - "tree_sitter_query" + "bash", + "css", + "json", + "nickel", + "ocaml", + "ocaml_interface", + "ocamllex", + "rust", + "nix", + "toml", + "tree_sitter_query", ] diff --git a/topiary-config/languages.ncl b/topiary-config/languages.ncl index 2d44d9a8..f38d3d2c 100644 --- a/topiary-config/languages.ncl +++ b/topiary-config/languages.ncl @@ -16,6 +16,14 @@ }, }, + nix = { + extensions = ["nix"], + grammar = { + git = https://github.com/nix-community/tree-sitter-nix"" + rev = "456b14a2fa6315abc7e02fcffaf4a1f35d4955d3", + }, + }, + json = { extensions = [ "json", diff --git a/topiary-queries/Cargo.toml b/topiary-queries/Cargo.toml index 45969a10..7db41c93 100644 --- a/topiary-queries/Cargo.toml +++ b/topiary-queries/Cargo.toml @@ -24,4 +24,5 @@ ocaml_interface = [] ocamllex = [] rust = [] toml = [] +nix = [] tree_sitter_query = [] diff --git a/topiary-queries/queries/nix.scm b/topiary-queries/queries/nix.scm new file mode 100644 index 00000000..5dce997e --- /dev/null +++ b/topiary-queries/queries/nix.scm @@ -0,0 +1,286 @@ +; Nix Formatter based on the RFC 166 + +; Leaf captures indicate parts of the code that should not be modified +(string) @leaf + +; Append a space after specific tokens +":" @append_space +"=" @append_space +"," @append_spaced_softline + +; Handle indentation and line breaks +; Define softline breaks and indentation starts/ends + +; Functions + +; Match function declarations with multiple parameters +(function + name: (identifier) @func.name + parameters: (parameters + (parameter + (identifier) @param.name)+) + body: (expression) @func.body +) @function_declaration + +; Ensure each parameter is on a new line with proper indentation +; Example transformation rules can be applied based on captures + +; Attribute Sets + +; Match attribute sets to ensure proper spacing and indentation +(attribute_set + "{" @append_spaced_softline @append_indent_start + (pair) @attr.pair + "}" @prepend_spaced_softline @prepend_indent_end +) @attribute_set + +; Ensure each key-value pair is on its own line +(pair + key: (identifier) @attr.key + "=" @append_space + value: (expression) @attr.value + ";" @append_spaced_softline +) + +; Lists + +; Match list literals to enforce spacing and indentation +(array + "[" @append_spaced_softline @append_indent_start + (_value) @list.value + "]" @prepend_spaced_softline @prepend_indent_end +) @array_literal + +; Ensure each list element is on its own line +(array + ", " @append_spaced_softline +) + +; If-Then-Else Expressions + +; Match if-then-else expressions to enforce line breaks and indentation +(if_expression + "if" @keyword.if @append_space + condition: (expression) @if.condition + "then" @keyword.then @append_space + then_branch: (expression) @if.then + "else" @keyword.else @append_space + else_branch: (if_expression / expression) @if.else +) + +; Ensure 'if', 'then', and 'else' start on new lines with proper indentation +(keyword.if) @keyword.if_start +(keyword.then) @keyword.then_start +(keyword.else) @keyword.else_start + +; Let-In Expressions + +; Match let-in expressions to enforce multiline bindings and indentation +(let_expression + "let" @keyword.let @append_spaced_softline @append_indent_start + bindings: (binding + (binding + name: (identifier) @bind.name + "=" @append_space + value: (expression) @bind.value + ";" @append_spaced_softline)+) + "in" @keyword.in @prepend_spaced_softline @prepend_indent_end + body: (expression) @let.body +) + +; Ensure each binding is on its own line with consistent indentation + +(binding + name: (identifier) @bind.name + "=" @append_space + value: (expression) @bind.value + ";" @append_spaced_softline +) + +; Operator Formatting + +; Match binary operations to handle operator placement and indentation +(binary_operator + left: (expression) @op.left + operator: (operator) @op.symbol + right: (expression) @op.right +) + +; Ensure operators start on new lines if they don't fit on a single line +; Example captures to adjust operator placement + +; Comments Handling + +; Match single-line and multi-line comments +(comment + (comment) @comment.all +) + +; Convert single-line /* */ comments to # comments +(single_line_comment) @comment.line + +; Preserve multi-line /** */ comments with proper indentation +(multi_line_comment) @comment.block + +; Strings and Interpolations + +; Match string literals and handle interpolations +(string + (interpolation + (simple_expression) @interp.simple + (complex_expression) @interp.complex + ) +) @string.interpolation + +; Ensure simple interpolations remain single-line and complex ones are multiline + +; Bindings + +; Match bindings to enforce proper indentation and line breaks +(binding + name: (identifier) @bind.name + "=" @append_space + value: (expression) @bind.value + ";" @append_spaced_softline +) + +; Ensure semicolons are placed correctly +(binding + ";" @semicolon.end +) + +; Inherit Statements + +; Match inherit statements to ensure proper formatting +(inherit_statement + "inherit" @keyword.inherit + (identifier) @inherit.item+ + ";" @inherit.semicolon +) + +; Ensure items in inherit are either all on the same line or each on a new line +(inherit_statement + "inherit" @keyword.inherit + "(" @inherit.parentheses_start + (identifier) @inherit.item + ")" @inherit.parentheses_end + ";" @inherit.semicolon +) + +; Handle inherit with source +(inherit_statement + "inherit" @keyword.inherit + "(" @inherit.parentheses_start + (expression) @inherit.source + ")" @inherit.parentheses_end + (identifier) @inherit.item+ + ";" @inherit.semicolon +) + +; Empty Objects and Arrays + +; Ensure empty objects and arrays are formatted without additional spaces or newlines +(attribute_set + "{" @object.open + "}" @object.close +) @empty_attribute_set + +(array + "[" @array.open + "]" @array.close +) @empty_array + +; Expand expressions based on line length and number of elements +; These rules help determine when to expand or keep single-line expressions + +; String Preservation + +; Ensure string quotes and contents are preserved +(string + "\"" @string.double_quote_start + (content) @string.content + "\"" @string.double_quote_end +) + +(string + "''" @string.single_quote_start + (content) @string.content + "''" @string.single_quote_end +) + +; Additional Rules + +; Handle let bindings indentation +(let_binding + (binding + name: (identifier) @bind.name + "=" @append_space + value: (expression) @bind.value + ";" @append_spaced_softline + )+ +) + +; Handle operator chaining with proper indentation +(operator_chain + (binary_operator + operator: (operator) @op.symbol + )+ +) + +; Handle assertions +(assert_statement + "assert" @keyword.assert @append_space + condition: (expression) @assert.condition + ";" @assert.semicolon +) + +; Handle 'with' expressions +(with_expression + "with" @keyword.with @append_space + attrs: (expression) @with.attrs + ";" @with.semicolon + body: (expression) @with.body +) + +; Handle comments within expressions +(comment + (single_line_comment) @comment.line + (multi_line_comment) @comment.block +) + +; Leaf Nodes Preservation +(string) @leaf + +; Additional formatting rules can be added here following the same pattern + +; Example of handling semicolon placement in bindings +(binding + name: (identifier) @bind.name + "=" @append_space + value: (expression) @bind.value + ";" @semicolon.end +) + +; Example of handling indentation for nested attribute sets +(attribute_set + "{" @append_spaced_softline @append_indent_start + (pair) @attr.pair + "}" @prepend_spaced_softline @prepend_indent_end +) @nested_attribute_set + +; Example of handling list elements +(array + "[" @append_spaced_softline @append_indent_start + (list_element) @list.element + "]" @prepend_spaced_softline @prepend_indent_end +) @formatted_array + +; Handle newline preservation rules +(empty_line + "\n\n" @collapse_empty_lines +) + +; Ensure consistent indentation levels +(indentation_rule + (block) @indent.block +) diff --git a/topiary-queries/src/lib.rs b/topiary-queries/src/lib.rs index 20c886e6..463c7f48 100644 --- a/topiary-queries/src/lib.rs +++ b/topiary-queries/src/lib.rs @@ -52,6 +52,12 @@ pub fn toml() -> &'static str { include_str!("../queries/toml.scm") } +/// Returns the Topiary-compatible query file for Nix. +#[cfg(feature = "nix")] +pub fn nix() -> &'static str { + include_str!("../queries/nix.scm") +} + /// Returns the Topiary-compatible query file for the /// Tree-sitter query language. #[cfg(feature = "tree_sitter_query")]