Skip to main content

nargo_formatter/
lib.rs

1#![warn(missing_docs)]
2
3//! Nargo 代码格式化工具,用于统一 Nargo 项目的代码风格。
4//!
5//! 该模块提供了一个 `NargoFormatter` 结构体,用于格式化 Nargo、TS、TSX、JS、JSX 文件。
6//! 它能够处理 Nargo 单文件组件(SFC)的不同区块,包括 `<template>`、`<script>`、`<style>` 和自定义块。
7
8use nargo_ir::{JsExpr, JsStmt, TemplateNodeIR, Trivia};
9use nargo_parser::Parser;
10use nargo_types::{Error, NargoValue, Result};
11use oak_core::source::{SourceBuffer, ToSource};
12use oak_json::{parse, JsonValue};
13use serde::{Deserialize, Serialize};
14use std::sync::Arc;
15
16/// 格式化配置选项
17///
18/// 定义了代码格式化的各种配置选项,包括缩进、引号、分号等。
19#[derive(Debug, Clone, Serialize, Deserialize, Default)]
20pub struct FormatterConfig {
21    /// 缩进大小
22    #[serde(alias = "indentSize", default = "default_indent_size")]
23    pub indent_size: usize,
24    /// 缩进类型(spaces 或 tabs)
25    #[serde(alias = "indentType", default = "default_indent_type")]
26    pub indent_type: IndentType,
27    /// 行宽
28    #[serde(alias = "lineWidth", default = "default_line_width")]
29    pub line_width: usize,
30    /// 是否使用单引号
31    #[serde(alias = "singleQuote", default = "default_single_quote")]
32    pub single_quote: bool,
33    /// 是否使用分号
34    #[serde(default = "default_semi")]
35    pub semi: bool,
36    /// 尾随逗号选项
37    #[serde(alias = "trailingComma", default = "default_trailing_comma")]
38    pub trailing_comma: TrailingComma,
39    /// 是否在属性后添加空格
40    #[serde(alias = "attrSpacing", default = "default_attr_spacing")]
41    pub attr_spacing: bool,
42    /// 是否在自闭合标签前添加空格
43    #[serde(alias = "selfClosingSpace", default = "default_self_closing_space")]
44    pub self_closing_space: bool,
45    /// 是否在标签之间添加空行
46    #[serde(alias = "tagSpacing", default = "default_tag_spacing")]
47    pub tag_spacing: bool,
48    /// 是否在箭头函数参数周围添加括号
49    #[serde(alias = "arrowParens", default = "default_arrow_parens")]
50    pub arrow_parens: bool,
51    /// 是否在对象字面量大括号周围添加空格
52    #[serde(alias = "objectSpacing", default = "default_object_spacing")]
53    pub object_spacing: bool,
54    /// 是否在数组字面量方括号周围添加空格
55    #[serde(alias = "arraySpacing", default = "default_array_spacing")]
56    pub array_spacing: bool,
57    /// 是否在函数参数周围添加空格
58    #[serde(alias = "functionSpacing", default = "default_function_spacing")]
59    pub function_spacing: bool,
60    /// 是否在区块之间添加空行
61    #[serde(alias = "blockSpacing", default = "default_block_spacing")]
62    pub block_spacing: bool,
63}
64
65/// 默认缩进大小
66fn default_indent_size() -> usize {
67    2
68}
69
70/// 默认缩进类型
71fn default_indent_type() -> IndentType {
72    IndentType::Spaces
73}
74
75/// 默认行宽
76fn default_line_width() -> usize {
77    80
78}
79
80/// 默认是否使用单引号
81fn default_single_quote() -> bool {
82    true
83}
84
85/// 默认是否使用分号
86fn default_semi() -> bool {
87    false
88}
89
90/// 默认尾随逗号选项
91fn default_trailing_comma() -> TrailingComma {
92    TrailingComma::Es5
93}
94
95/// 默认是否在属性后添加空格
96fn default_attr_spacing() -> bool {
97    true
98}
99
100/// 默认是否在自闭合标签前添加空格
101fn default_self_closing_space() -> bool {
102    true
103}
104
105/// 默认是否在标签之间添加空行
106fn default_tag_spacing() -> bool {
107    true
108}
109
110/// 默认是否在箭头函数参数周围添加括号
111fn default_arrow_parens() -> bool {
112    true
113}
114
115/// 默认是否在对象字面量大括号周围添加空格
116fn default_object_spacing() -> bool {
117    true
118}
119
120/// 默认是否在数组字面量方括号周围添加空格
121fn default_array_spacing() -> bool {
122    true
123}
124
125/// 默认是否在函数参数周围添加空格
126fn default_function_spacing() -> bool {
127    true
128}
129
130/// 默认是否在区块之间添加空行
131fn default_block_spacing() -> bool {
132    true
133}
134
135/// 缩进类型
136#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Default)]
137pub enum IndentType {
138    /// 使用空格
139    #[serde(alias = "spaces")]
140    #[default]
141    Spaces,
142    /// 使用制表符
143    #[serde(alias = "tabs")]
144    Tabs,
145}
146
147/// 尾随逗号选项
148#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Default)]
149pub enum TrailingComma {
150    /// 无尾随逗号
151    #[serde(alias = "none")]
152    None,
153    /// ES5 风格(对象和数组)
154    #[serde(alias = "es5")]
155    #[default]
156    Es5,
157    /// 所有可能的地方
158    #[serde(alias = "all")]
159    All,
160}
161
162/// 格式化 Nargo 相关文件的工具
163///
164/// 支持格式化 Nargo、TS、TSX、JS、JSX 文件,能够处理 Nargo 单文件组件的不同区块。
165pub struct NargoFormatter {
166    /// 解析器实例
167    parser: Parser<'static>,
168    /// 解析器注册表
169    registry: Arc<nargo_parser::ParserRegistry>,
170    /// 格式化配置
171    config: FormatterConfig,
172}
173
174impl NargoFormatter {
175    /// 创建新的格式化器实例,使用默认配置
176    ///
177    /// # 返回值
178    /// 格式化器实例
179    pub fn new() -> Self {
180        Self::with_config(FormatterConfig::default())
181    }
182
183    /// 创建新的格式化器实例,使用自定义配置
184    ///
185    /// # 参数
186    /// - `config`: 格式化配置
187    ///
188    /// # 返回值
189    /// 格式化器实例
190    pub fn with_config(config: FormatterConfig) -> Self {
191        let mut registry = nargo_parser::ParserRegistry::new();
192
193        // 注册模板解析器
194        registry.register_template_parser("vue", std::sync::Arc::new(nargo_parser::OakVueTemplateParser));
195
196        // 注册脚本解析器
197        registry.register_script_parser("ts", std::sync::Arc::new(nargo_parser::OakTypeScriptParser));
198        registry.register_script_parser("js", std::sync::Arc::new(nargo_parser::OakTypeScriptParser));
199
200        // 注册样式解析器
201        registry.register_style_parser("css", std::sync::Arc::new(nargo_parser::OakCssParser));
202
203        // 注册元数据解析器
204        // 暂时不注册 json 解析器,因为 nargo_parser 没有导出 JsonOaksParser
205
206        let registry = std::sync::Arc::new(registry);
207        let parser = Parser::new("format".to_string(), "", registry.clone());
208        Self { parser, registry, config }
209    }
210
211    /// 从文件加载配置
212    ///
213    /// # 参数
214    /// - `file_path`: 配置文件路径
215    ///
216    /// # 返回值
217    /// 配置实例
218    pub fn load_config_from_file(file_path: &str) -> Result<FormatterConfig> {
219        let content = std::fs::read_to_string(file_path)?;
220        let config: FormatterConfig = serde_json::from_str(&content).map_err(|e| nargo_types::Error::external_error("serde_json".to_string(), e.to_string(), nargo_types::Span::default()))?;
221        Ok(config)
222    }
223
224    /// 保存配置到文件
225    ///
226    /// # 参数
227    /// - `config`: 配置实例
228    /// - `file_path`: 配置文件路径
229    ///
230    /// # 返回值
231    /// 操作结果
232    pub fn save_config_to_file(config: &FormatterConfig, file_path: &str) -> Result<()> {
233        let content = serde_json::to_string_pretty(config).map_err(|e| nargo_types::Error::external_error("serde_json".to_string(), e.to_string(), nargo_types::Span::default()))?;
234        std::fs::write(file_path, content)?;
235        Ok(())
236    }
237
238    /// 格式化源代码
239    ///
240    /// # 参数
241    /// - `source`: 源代码字符串
242    ///
243    /// # 返回值
244    /// 格式化后的代码
245    pub async fn format(&self, source: &str) -> Result<String> {
246        // 暂时禁用 JSON 格式化,因为 oak-json 的 formatter 不可用
247        // if let Ok(json_value) = parse(source) {
248        //     let doc = json_value.to_doc();
249        //     let mut buffer = SourceBuffer::new();
250        //     doc.to_source(&mut buffer);
251        //     return Ok(buffer.to_string());
252        // }
253
254        let mut parser = Parser::new("format".to_string(), source, self.registry.clone());
255        let ir = parser.parse_all()?;
256        let mut output = String::new();
257
258        if let Some(template) = &ir.template {
259            output.push_str("<template>\n");
260            for node in &template.nodes {
261                let formatted = self.format_template_node(node, 1);
262                if !formatted.is_empty() {
263                    output.push_str(&formatted);
264                }
265            }
266            output.push_str("</template>\n\n");
267        }
268
269        if let Some(script) = &ir.script {
270            output.push_str("<script>\n");
271            for stmt in &script.body {
272                let formatted = self.format_js_stmt(stmt, 1);
273                let trimmed = formatted.trim();
274                if !trimmed.is_empty() && trimmed != ";" {
275                    output.push_str(&formatted);
276                    output.push('\n');
277                }
278            }
279            output.push_str("</script>\n\n");
280        }
281
282        for style in &ir.styles {
283            output.push_str("<style");
284            if style.lang != "css" {
285                output.push_str(&format!(" lang=\"{}\"", style.lang));
286            }
287            if style.scoped {
288                output.push_str(" scoped");
289            }
290            output.push_str(">");
291            output.push_str(&style.code);
292            if !style.code.ends_with('\n') {
293                output.push('\n');
294            }
295            output.push_str("</style>\n\n");
296        }
297
298        for block in &ir.custom_blocks {
299            output.push_str(&format!("<{}", block.name));
300            for (k, v) in &block.attributes {
301                output.push_str(&format!(" {}=\"{}\"", k, v));
302            }
303            output.push_str(">");
304            output.push_str(&block.content);
305            if !block.content.ends_with('\n') {
306                output.push('\n');
307            }
308            output.push_str(&format!("</{}>\n\n", block.name));
309        }
310
311        Ok(output.trim().to_string() + "\n")
312    }
313
314    /// 获取缩进字符串
315    ///
316    /// # 参数
317    /// - `indent`: 缩进级别
318    ///
319    /// # 返回值
320    /// 缩进字符串
321    fn get_indent(&self, indent: usize) -> String {
322        match self.config.indent_type {
323            IndentType::Spaces => " ".repeat(self.config.indent_size * indent),
324            IndentType::Tabs => "\t".repeat(indent),
325        }
326    }
327
328    /// 格式化模板节点
329    ///
330    /// # 参数
331    /// - `node`: 模板节点
332    /// - `indent`: 缩进级别
333    ///
334    /// # 返回值
335    /// 格式化后的字符串
336    fn format_template_node(&self, node: &TemplateNodeIR, indent: usize) -> String {
337        let indent_str = self.get_indent(indent);
338        match node {
339            TemplateNodeIR::Text(text, _, _) => {
340                let trimmed = text.trim();
341                if trimmed.is_empty() {
342                    String::new()
343                }
344                else {
345                    format!("{}{}\n", indent_str, trimmed)
346                }
347            }
348            TemplateNodeIR::Element(el) => {
349                let trivia_str = self.format_trivia(&el.trivia, indent);
350                let mut s = format!("{}{}<{}", trivia_str, indent_str, el.tag);
351
352                for attr in &el.attributes {
353                    let name = if attr.is_directive {
354                        if attr.name.starts_with("v-") {
355                            attr.name.clone()
356                        }
357                        else {
358                            format!("v-{}", attr.name)
359                        }
360                    }
361                    else if attr.is_dynamic {
362                        if attr.name.starts_with(':') {
363                            attr.name.clone()
364                        }
365                        else {
366                            format!(":{}", attr.name)
367                        }
368                    }
369                    else {
370                        attr.name.clone()
371                    };
372
373                    if let Some(val) = &attr.value {
374                        let spacing = if self.config.attr_spacing { " " } else { "" };
375                        s.push_str(&format!(" {}{}=\"{}\"", name, spacing, val));
376                    }
377                    else if let Some(val_ast) = &attr.value_ast {
378                        let spacing = if self.config.attr_spacing { " " } else { "" };
379                        s.push_str(&format!(" {}{}=\"{}\"", name, spacing, self.format_js_expr(val_ast)));
380                    }
381                    else {
382                        s.push_str(&format!(" {}", name));
383                    }
384                }
385
386                if el.children.is_empty() {
387                    let spacing = if self.config.self_closing_space { " " } else { "" };
388                    s.push_str(&format!("{}/>", spacing));
389                }
390                else {
391                    s.push_str(">");
392                    for child in &el.children {
393                        s.push_str(&self.format_template_node(child, indent + 1));
394                    }
395                    s.push_str(&format!("{}</{}>\n", indent_str, el.tag));
396                }
397                s
398            }
399            TemplateNodeIR::Interpolation(expr) => {
400                format!("{}{{{{{}}}}}\n", indent_str, self.format_js_expr(&expr.ast.as_ref().unwrap()))
401            }
402            TemplateNodeIR::Comment(comment, _, _) => {
403                format!("{}<!--{}-->\n", indent_str, comment)
404            }
405            _ => String::new(),
406        }
407    }
408
409    /// 格式化 JS 语句
410    ///
411    /// # 参数
412    /// - `stmt`: JS 语句
413    /// - `indent`: 缩进级别
414    ///
415    /// # 返回值
416    /// 格式化后的字符串
417    fn format_js_stmt(&self, stmt: &JsStmt, indent: usize) -> String {
418        let indent_str = self.get_indent(indent);
419        let trivia = stmt.trivia();
420        let trivia_str = self.format_trivia(trivia, indent);
421
422        let stmt_core = match stmt {
423            JsStmt::Expr(expr, _, _) => {
424                let expr_str = self.format_js_expr(expr);
425                if self.config.semi {
426                    format!("{};\n", expr_str)
427                }
428                else {
429                    format!("{}\n", expr_str)
430                }
431            }
432            JsStmt::Import { source, specifiers, .. } => {
433                let quote = if self.config.single_quote { "'" } else { "\"" };
434                format!("import {{ {} }} from {}{}{};\n", specifiers.join(", "), quote, source, quote)
435            }
436            JsStmt::Export { declaration, .. } => {
437                let decl_str = self.format_js_stmt(declaration, 0);
438                if decl_str.starts_with("export") {
439                    decl_str
440                }
441                else {
442                    format!("export {}", decl_str)
443                }
444            }
445            JsStmt::ExportAll { source, .. } => {
446                let quote = if self.config.single_quote { "'" } else { "\"" };
447                format!("export * from {}{}{};\n", quote, source, quote)
448            }
449            JsStmt::ExportNamed { source, specifiers, .. } => {
450                if let Some(src) = source {
451                    let quote = if self.config.single_quote { "'" } else { "\"" };
452                    format!("export {{ {} }} from {}{}{};\n", specifiers.join(", "), quote, src, quote)
453                }
454                else {
455                    format!("export {{ {} }}{}\n", specifiers.join(", "), if self.config.semi { ";" } else { "" })
456                }
457            }
458            JsStmt::VariableDecl { kind, id, init, .. } => {
459                if let Some(init_expr) = init {
460                    format!("{} {} = {}{}\n", kind, id, self.format_js_expr(init_expr), if self.config.semi { ";" } else { "" })
461                }
462                else {
463                    format!("{} {}{}\n", kind, id, if self.config.semi { ";" } else { "" })
464                }
465            }
466            JsStmt::FunctionDecl { id, params, body, .. } => {
467                let mut s = format!("function {}({}) {{\n", id, params.join(", "));
468                for sub_stmt in body {
469                    s.push_str(&self.format_js_stmt(sub_stmt, indent + 1));
470                }
471                s.push_str(&format!("{}}}\n", indent_str));
472                s
473            }
474            _ => String::new(),
475        };
476
477        format!("{}{}{}", trivia_str, indent_str, stmt_core)
478    }
479
480    /// 格式化 JS 表达式
481    ///
482    /// # 参数
483    /// - `expr`: JS 表达式
484    ///
485    /// # 返回值
486    /// 格式化后的字符串
487    fn format_js_expr(&self, expr: &JsExpr) -> String {
488        let trivia = expr.trivia();
489        let mut s = String::new();
490        for comment in &trivia.leading_comments {
491            s.push_str(&format!("//{}\n", comment.content));
492        }
493
494        let expr_core = match expr {
495            JsExpr::Literal(val, _, _) => self.format_nargo_value(val),
496            JsExpr::Identifier(id, _, _) => id.clone(),
497            JsExpr::Unary { op, argument, .. } => {
498                format!("{}{}", op, self.format_js_expr(argument))
499            }
500            JsExpr::Binary { left, op, right, .. } => {
501                format!("{} {} {}", self.format_js_expr(left), op, self.format_js_expr(right))
502            }
503            JsExpr::Call { callee, args, .. } => {
504                let args_str: Vec<_> = args.iter().map(|a| self.format_js_expr(a)).collect();
505                let spacing = if self.config.function_spacing { " " } else { "" };
506                format!("{}{}({}{}{})", self.format_js_expr(callee), spacing, spacing, args_str.join(", "), spacing)
507            }
508            JsExpr::Member { object, property, computed, .. } => {
509                if *computed {
510                    format!("{}[{}]", self.format_js_expr(object), self.format_js_expr(property))
511                }
512                else {
513                    if let JsExpr::Identifier(ref prop, _, _) = **property {
514                        format!("{}.{}", self.format_js_expr(object), prop)
515                    }
516                    else {
517                        format!("{}[{}]", self.format_js_expr(object), self.format_js_expr(property))
518                    }
519                }
520            }
521            JsExpr::Object(props, _, _) => {
522                let mut props_vec: Vec<_> = props.iter().collect();
523                props_vec.sort_by(|a, b| a.0.cmp(b.0));
524                let props_str: Vec<_> = props_vec.iter().map(|(k, v)| format!("{}: {}", k, self.format_js_expr(v))).collect();
525                let spacing = if self.config.object_spacing { " " } else { "" };
526                format!("{{{}{}{}}}", spacing, props_str.join(", "), spacing)
527            }
528            JsExpr::Array(elements, _, _) => {
529                let elems_str: Vec<_> = elements.iter().map(|e| self.format_js_expr(e)).collect();
530                let spacing = if self.config.array_spacing { " " } else { "" };
531                format!("[{}]", elems_str.join(", "))
532            }
533            JsExpr::ArrowFunction { params, body, .. } => {
534                if self.config.arrow_parens || params.len() != 1 {
535                    format!("({}) => {}", params.join(", "), self.format_js_expr(body))
536                }
537                else {
538                    format!("{} => {}", params[0], self.format_js_expr(body))
539                }
540            }
541            JsExpr::Conditional { test, consequent, alternate, .. } => {
542                format!("{} ? {} : {}", self.format_js_expr(test), self.format_js_expr(consequent), self.format_js_expr(alternate))
543            }
544            JsExpr::TemplateLiteral { quasis, expressions, .. } => {
545                let mut s = "`".to_string();
546                for i in 0..quasis.len() {
547                    s.push_str(&quasis[i]);
548                    if i < expressions.len() {
549                        s.push_str(&format!("${{{}}}", self.format_js_expr(&expressions[i])));
550                    }
551                }
552                s.push('`');
553                s
554            }
555            _ => String::new(),
556        };
557
558        s.push_str(&expr_core);
559        s
560    }
561
562    /// 格式化 trivia(注释等)
563    ///
564    /// # 参数
565    /// - `trivia`: trivia 信息
566    /// - `indent`: 缩进级别
567    ///
568    /// # 返回值
569    /// 格式化后的字符串
570    fn format_trivia(&self, trivia: &Trivia, indent: usize) -> String {
571        let indent_str = self.get_indent(indent);
572        let mut s = String::new();
573
574        for comment in &trivia.leading_comments {
575            s.push_str(&format!("{}//{}\n", indent_str, comment.content));
576        }
577
578        s
579    }
580
581    /// 格式化 Nargo 值
582    ///
583    /// # 参数
584    /// - `val`: Nargo 值
585    ///
586    /// # 返回值
587    /// 格式化后的字符串
588    fn format_nargo_value(&self, val: &NargoValue) -> String {
589        match val {
590            NargoValue::Null => "null".to_string(),
591            NargoValue::Bool(b) => b.to_string(),
592            NargoValue::Number(n) => n.to_string(),
593            NargoValue::String(s) => {
594                if self.config.single_quote {
595                    format!("'{}'", s)
596                }
597                else {
598                    format!("\"{}\"", s)
599                }
600            }
601            NargoValue::Array(arr) => {
602                let items: Vec<_> = arr.iter().map(|v| self.format_nargo_value(v)).collect();
603                format!("[{}]", items.join(", "))
604            }
605            NargoValue::Object(obj) => {
606                let items: Vec<_> = obj.iter().map(|(k, v)| format!("{}: {}", k, self.format_nargo_value(v))).collect();
607                format!("{{ {} }}", items.join(", "))
608            }
609            NargoValue::Signal(s) => format!("${}", s),
610            NargoValue::Raw(r) => r.clone(),
611            _ => format!("{:?}", val),
612        }
613    }
614}