Add unknown ranges and open object values

This commit is contained in:
2026-08-14 08:00:22 +09:00
parent 8c50dd202d
commit 848c7f169f
53 changed files with 9047 additions and 6572 deletions
+57 -9
View File
@@ -1,8 +1,8 @@
use std::{error::Error, fmt};
use decodal::{
Ast, BinaryOp, CompareOp, Expr, ExprId, Field, Param, SourceForm, Span, SyntaxToken,
SyntaxTokenKind,
Ast, BinaryOp, CompareOp, Expr, ExprId, Field, ObjectRest, Param, SourceForm, Span,
SyntaxToken, SyntaxTokenKind,
ast::{MatchArm, UnaryOp},
parse_source, tokenize_source,
};
@@ -81,7 +81,7 @@ impl<'a> Formatter<'a> {
fn format(&self, root: ExprId, source_form: SourceForm) -> String {
let mut out = String::new();
if source_form == SourceForm::Fields {
let Expr::Object(fields) = &self.ast.get(root).expr else {
let Expr::Object { fields, .. } = &self.ast.get(root).expr else {
unreachable!("field-form sources parse to an object")
};
self.write_field_list(&mut out, fields, 0, self.source.len(), 0);
@@ -145,7 +145,7 @@ impl<'a> Formatter<'a> {
if self.has_comment(node.span)
&& !matches!(
node.expr,
Expr::Object(_) | Expr::Array(_) | Expr::Let { .. } | Expr::Match { .. }
Expr::Object { .. } | Expr::Array(_) | Expr::Let { .. } | Expr::Match { .. }
)
{
out.push_str(self.raw(node.span));
@@ -162,7 +162,9 @@ impl<'a> Formatter<'a> {
Expr::Literal(_) | Expr::Ident(_) | Expr::RegexConstraint(_) | Expr::Wildcard => {
out.push_str(self.raw(node.span));
}
Expr::Object(fields) => self.write_object(out, node.span, fields, indent),
Expr::Object { fields, rest } => {
self.write_object(out, node.span, fields, rest.as_ref(), indent)
}
Expr::Array(items) => self.write_array(out, node.span, items, indent),
Expr::ArrayConstraint { item } => {
out.push_str("[...");
@@ -241,15 +243,50 @@ impl<'a> Formatter<'a> {
}
}
fn write_object(&self, out: &mut String, span: Span, fields: &[Field], indent: usize) {
fn write_object(
&self,
out: &mut String,
span: Span,
fields: &[Field],
rest: Option<&ObjectRest>,
indent: usize,
) {
let (start, end) =
self.delimited_range(span, SyntaxTokenKind::LBrace, SyntaxTokenKind::RBrace);
if fields.is_empty() && !self.has_comment_between(start, end) {
if fields.is_empty() && rest.is_none() && !self.has_comment_between(start, end) {
out.push_str("{}");
return;
}
out.push_str("{\n");
self.write_field_list(out, fields, start, end, indent + INDENT);
let field_end = rest.map_or(end, |rest| rest.span.start as usize);
self.write_field_list(out, fields, start, field_end, indent + INDENT);
if let Some(rest) = rest {
write_indent(out, indent + INDENT);
out.push_str("...");
let value_start = self.ast.span(rest.value).start as usize;
let ellipsis = self
.tokens_between(rest.span.start as usize, value_start)
.find(|token| token.kind == SyntaxTokenKind::Ellipsis)
.expect("parsed object rest constraints contain `...`");
if self.has_comment_between(ellipsis.span.end as usize, value_start) {
self.write_between(
out,
ellipsis.span.end as usize,
value_start,
Some(ellipsis.span.end as usize),
indent + INDENT,
);
write_indent(out, indent + INDENT);
}
self.write_expr(out, rest.value, indent + INDENT, 0);
self.write_between(
out,
rest.span.end as usize,
end,
Some(rest.span.end as usize),
indent + INDENT,
);
}
write_indent(out, indent);
out.push('}');
}
@@ -524,7 +561,7 @@ impl<'a> Formatter<'a> {
Expr::As { narrower, wider } => {
self.is_inline_expr(*narrower) && self.is_inline_expr(*wider)
}
Expr::Object(_) | Expr::Let { .. } | Expr::Function { .. } | Expr::Match { .. } => {
Expr::Object { .. } | Expr::Let { .. } | Expr::Function { .. } | Expr::Match { .. } => {
false
}
}
@@ -707,6 +744,17 @@ mod tests {
);
}
#[test]
fn formats_unknown_and_object_rest_constraints() {
let source =
"value={hoge=Int;fuga=String;# remaining fields\n...# unconstrained\nUnknown};";
let formatted = format_source(source).unwrap();
assert_eq!(
formatted,
"value = {\n hoge = Int;\n fuga = String; # remaining fields\n ... # unconstrained\n Unknown\n};\n"
);
}
#[test]
fn preserves_regex_and_escaped_strings() {
let source = r#"value={pattern=/^api\/.+$/;text="a\n\"b";};"#;