Skip to main content

cairo_lang_formatter/
lib.rs

1//! Cairo formatter.
2//!
3//! This crate is responsible for formatting Cairo code.
4pub mod cairo_formatter;
5pub mod formatter_impl;
6pub mod node_properties;
7
8use cairo_lang_diagnostics::DiagnosticsBuilder;
9use cairo_lang_filesystem::ids::{FileKind, FileLongId, SmolStrId, VirtualFile};
10use cairo_lang_parser::parser::Parser;
11use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode};
12use cairo_lang_utils::Intern;
13use salsa::Database;
14use serde::{Deserialize, Serialize};
15
16pub use crate::cairo_formatter::{CairoFormatter, FormatOutcome, StdinFmt};
17use crate::formatter_impl::FormatterImpl;
18
19#[cfg(test)]
20mod test;
21
22pub const CAIRO_FMT_IGNORE: &str = ".cairofmtignore";
23
24/// Returns the formatted syntax tree as a string.
25/// # Arguments
26/// * `db` - The syntax group.
27/// * `syntax_root` - The syntax root.
28/// * `config` - The formatter configuration.
29/// # Returns
30/// * `String` - The formatted file.
31pub fn get_formatted_file(
32    db: &dyn Database,
33    syntax_root: &SyntaxNode<'_>,
34    config: FormatterConfig,
35) -> String {
36    let mut formatter = FormatterImpl::new(db, config);
37    formatter.get_formatted_string(syntax_root)
38}
39
40/// Formats Cairo code given as a string.
41/// # Arguments
42/// * `db` - The syntax group.
43/// * `content` - The code to format.
44/// # Returns
45/// * `String` - The formatted code.
46pub fn format_string(db: &dyn Database, content: String) -> String {
47    let virtual_file = FileLongId::Virtual(VirtualFile {
48        parent: None,
49        name: SmolStrId::from(db, "string_to_format"),
50        content: SmolStrId::from(db, &content),
51        code_mappings: [].into(),
52        kind: FileKind::Module,
53        original_item_removed: false,
54    })
55    .intern(db);
56    let mut diagnostics = DiagnosticsBuilder::default();
57    let syntax_root =
58        Parser::parse_file(db, &mut diagnostics, virtual_file, content.as_str()).as_syntax_node();
59    get_formatted_file(db, &syntax_root, FormatterConfig::default())
60}
61
62/// This enum is used to control how multi-element collections (i.e. arrays, tuples)
63/// are broken into lines. It provides two options: `SingleBreakPoint` and `LineByLine`, allowing
64/// flexible configuration based on desired readability or space efficiency.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66pub enum CollectionsBreakingBehavior {
67    /// Keeps all elements of the collection on a single line, where possible.
68    SingleBreakPoint,
69    /// Breaks each element of the collection onto a new line for improved readability.
70    LineByLine,
71}
72
73/// Impl CollectionsBreakingBehavior from bool, where true is `LineByLine` and false is
74/// `SingleBreakPoint`. This adheres to the existing behavior of the formatter CLI.
75impl From<bool> for CollectionsBreakingBehavior {
76    fn from(b: bool) -> Self {
77        if b {
78            CollectionsBreakingBehavior::LineByLine
79        } else {
80            CollectionsBreakingBehavior::SingleBreakPoint
81        }
82    }
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct BreakingBehaviorConfig {
87    pub tuple: CollectionsBreakingBehavior,
88    pub fixed_array: CollectionsBreakingBehavior,
89    pub macro_call: CollectionsBreakingBehavior,
90}
91#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(rename_all = "kebab-case")]
93pub struct FormatterConfig {
94    pub tab_size: usize,
95    pub max_line_length: usize,
96    pub sort_module_level_items: bool,
97    pub breaking_behavior: BreakingBehaviorConfig,
98    pub merge_use_items: bool,
99    pub allow_duplicate_uses: bool,
100}
101
102// Config params
103// TODO(Gil): export to file and load from file
104const TAB_SIZE: usize = 4;
105const MAX_LINE_LENGTH: usize = 100;
106
107impl FormatterConfig {
108    pub fn new(
109        tab_size: usize,
110        max_line_length: usize,
111        sort_module_level_items: bool,
112        breaking_behavior: BreakingBehaviorConfig,
113        merge_use_items: bool,
114        allow_duplicate_uses: bool,
115    ) -> Self {
116        Self {
117            tab_size,
118            max_line_length,
119            sort_module_level_items,
120            breaking_behavior,
121            merge_use_items,
122            allow_duplicate_uses,
123        }
124    }
125
126    pub fn sort_module_level_items(mut self, sort_module_level_items: Option<bool>) -> Self {
127        if let Some(sort) = sort_module_level_items {
128            self.sort_module_level_items = sort;
129        }
130        self
131    }
132
133    pub fn tuple_breaking_behavior(
134        mut self,
135        behavior: Option<CollectionsBreakingBehavior>,
136    ) -> Self {
137        if let Some(behavior) = behavior {
138            self.breaking_behavior.tuple = behavior;
139        }
140        self
141    }
142
143    pub fn fixed_array_breaking_behavior(
144        mut self,
145        behavior: Option<CollectionsBreakingBehavior>,
146    ) -> Self {
147        if let Some(behavior) = behavior {
148            self.breaking_behavior.fixed_array = behavior;
149        }
150        self
151    }
152
153    pub fn macro_call_breaking_behavior(
154        mut self,
155        behavior: Option<CollectionsBreakingBehavior>,
156    ) -> Self {
157        if let Some(behavior) = behavior {
158            self.breaking_behavior.macro_call = behavior;
159        }
160        self
161    }
162    pub fn merge_use_items(mut self, merge: Option<bool>) -> Self {
163        if let Some(merge) = merge {
164            self.merge_use_items = merge;
165        }
166        self
167    }
168    pub fn allow_duplicate_uses(mut self, allow: Option<bool>) -> Self {
169        if let Some(allow) = allow {
170            self.allow_duplicate_uses = allow;
171        }
172        self
173    }
174}
175impl Default for FormatterConfig {
176    fn default() -> Self {
177        Self {
178            tab_size: TAB_SIZE,
179            max_line_length: MAX_LINE_LENGTH,
180            sort_module_level_items: true,
181            breaking_behavior: BreakingBehaviorConfig {
182                tuple: CollectionsBreakingBehavior::LineByLine,
183                fixed_array: CollectionsBreakingBehavior::SingleBreakPoint,
184                macro_call: CollectionsBreakingBehavior::SingleBreakPoint,
185            },
186            merge_use_items: true,
187            allow_duplicate_uses: false,
188        }
189    }
190}