feat(prettier): turn off preserve_parens and start working on need-parens (#1487)

This commit is contained in:
Boshen 2023-11-22 00:26:56 +08:00 committed by GitHub
parent f66e4d8ac3
commit 064353c97e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 389 additions and 75 deletions

View file

@ -117,6 +117,7 @@ pub enum AstKind<'a> {
JSXElementName(&'a JSXElementName<'a>),
JSXExpressionContainer(&'a JSXExpressionContainer<'a>),
JSXAttributeItem(&'a JSXAttributeItem<'a>),
JSXSpreadAttribute(&'a JSXSpreadAttribute<'a>),
JSXText(&'a JSXText),
// TypeScript
@ -369,6 +370,7 @@ impl<'a> GetSpan for AstKind<'a> {
Self::JSXElement(x) => x.span,
Self::JSXFragment(x) => x.span,
Self::JSXAttributeItem(x) => x.span(),
Self::JSXSpreadAttribute(x) => x.span,
Self::JSXText(x) => x.span,
Self::JSXExpressionContainer(x) => x.span,
@ -538,6 +540,7 @@ impl<'a> AstKind<'a> {
Self::JSXElement(_) => "JSXElement".into(),
Self::JSXFragment(_) => "JSXFragment".into(),
Self::JSXAttributeItem(_) => "JSXAttributeItem".into(),
Self::JSXSpreadAttribute(_) => "JSXSpreadAttribute".into(),
Self::JSXText(_) => "JSXText".into(),
Self::JSXExpressionContainer(_) => "JSXExpressionContainer".into(),

View file

@ -51,7 +51,7 @@ impl FormatRunner {
let source_text = std::fs::read_to_string(path).unwrap();
let allocator = Allocator::default();
let source_type = SourceType::from_path(path).unwrap();
let ret = Parser::new(&allocator, &source_text, source_type).parse();
let ret = Parser::new(&allocator, &source_text, source_type).preserve_parens(false).parse();
let _ = Prettier::new(&allocator, &source_text, ret.trivias, PrettierOptions::default())
.build(&ret.program);
}

View file

@ -17,7 +17,7 @@ fn main() {
let source_text = std::fs::read_to_string(path).unwrap_or_else(|_| panic!("{name} not found"));
let allocator = Allocator::default();
let source_type = SourceType::from_path(path).unwrap();
let ret = Parser::new(&allocator, &source_text, source_type).parse();
let ret = Parser::new(&allocator, &source_text, source_type).preserve_parens(false).parse();
let output = Prettier::new(&allocator, &source_text, ret.trivias, PrettierOptions::default())
.build(&ret.program);
println!("{output}");

View file

@ -115,15 +115,17 @@ impl<'a> Format<'a> for Statement<'a> {
impl<'a> Format<'a> for ExpressionStatement<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
let mut parts = p.vec();
if let Some(doc) = p.print_leading_comments(self.span) {
parts.push(doc);
}
parts.push(self.expression.format(p));
if p.options.semi {
parts.push(ss!(";"));
}
Doc::Array(parts)
wrap!(p, self, ExpressionStatement, {
let mut parts = p.vec();
if let Some(doc) = p.print_leading_comments(self.span) {
parts.push(doc);
}
parts.push(self.expression.format(p));
if p.options.semi {
parts.push(ss!(";"));
}
Doc::Array(parts)
})
}
}
@ -1291,7 +1293,9 @@ impl<'a> Format<'a> for RegExpLiteral {
impl<'a> Format<'a> for StringLiteral {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
p.str(&string::print_string(self.value.as_str(), p.options.single_quote))
wrap!(p, self, StringLiteral, {
p.str(&string::print_string(self.value.as_str(), p.options.single_quote))
})
}
}
@ -1303,11 +1307,13 @@ impl<'a> Format<'a> for ThisExpression {
impl<'a> Format<'a> for MemberExpression<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
match self {
Self::ComputedMemberExpression(expr) => expr.format(p),
Self::StaticMemberExpression(expr) => expr.format(p),
Self::PrivateFieldExpression(expr) => expr.format(p),
}
wrap!(p, self, MemberExpression, {
match self {
Self::ComputedMemberExpression(expr) => expr.format(p),
Self::StaticMemberExpression(expr) => expr.format(p),
Self::PrivateFieldExpression(expr) => expr.format(p),
}
})
}
}
@ -1380,7 +1386,7 @@ impl<'a> Format<'a> for ArrayExpressionElement<'a> {
impl<'a> Format<'a> for SpreadElement<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
array![p, ss!("..."), format!(p, self.argument)]
wrap!(p, self, SpreadElement, { array![p, ss!("..."), format!(p, self.argument)] })
}
}
@ -1488,16 +1494,18 @@ impl<'a> Format<'a> for ArrowExpression<'a> {
impl<'a> Format<'a> for YieldExpression<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
let mut parts = p.vec();
parts.push(ss!("yield"));
if self.delegate {
parts.push(ss!("*"));
}
if let Some(argument) = &self.argument {
parts.push(ss!(" "));
parts.push(format!(p, argument));
}
Doc::Array(parts)
wrap!(p, self, YieldExpression, {
let mut parts = p.vec();
parts.push(ss!("yield"));
if self.delegate {
parts.push(ss!("*"));
}
if let Some(argument) = &self.argument {
parts.push(ss!(" "));
parts.push(format!(p, argument));
}
Doc::Array(parts)
})
}
}
@ -1693,8 +1701,7 @@ impl<'a> Format<'a> for SequenceExpression<'a> {
impl<'a> Format<'a> for ParenthesizedExpression<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
// TODO: This wrap need to be removed and all logic should go into `need_parens`.
wrap!(p, self, ParenthesizedExpression, { self.expression.format(p) })
unreachable!("Parser preserve_parens option need to be set to false.");
}
}
@ -1738,19 +1745,21 @@ impl<'a> Format<'a> for TemplateElement {
impl<'a> Format<'a> for TaggedTemplateExpression<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
let mut parts = p.vec();
wrap!(p, self, TaggedTemplateExpression, {
let mut parts = p.vec();
parts.push(format!(p, self.tag));
parts.push(format!(p, self.tag));
if let Some(type_parameters) = &self.type_parameters {
parts.push(string!(p, "<"));
parts.push(format!(p, type_parameters));
parts.push(string!(p, ">"));
}
if let Some(type_parameters) = &self.type_parameters {
parts.push(string!(p, "<"));
parts.push(format!(p, type_parameters));
parts.push(string!(p, ">"));
}
parts.push(format!(p, self.quasi));
parts.push(format!(p, self.quasi));
Doc::Array(parts)
Doc::Array(parts)
})
}
}
@ -1805,7 +1814,7 @@ impl<'a> Format<'a> for MetaProperty {
impl<'a> Format<'a> for Class<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
class::print_class(p, self)
wrap!(p, self, Class, { class::print_class(p, self) })
}
}

View file

@ -1,4 +1,19 @@
use oxc_ast::AstKind;
//! Direct port of needs-parens for adding or removing parentheses.
//!
//! See <https://github.com/prettier/prettier/blob/main/src/language-js/needs-parens.js>
#![allow(
clippy::unused_self,
clippy::match_same_arms,
clippy::match_like_matches_macro,
clippy::single_match
)]
use oxc_ast::{
ast::{AssignmentTarget, ChainElement, Expression, ModuleDeclaration, SimpleAssignmentTarget},
AstKind,
};
use oxc_span::{GetSpan, Span};
use oxc_syntax::operator::{BinaryOperator, UnaryOperator, UpdateOperator};
use crate::{array, doc::Doc, ss, Prettier};
@ -12,35 +27,305 @@ impl<'a> Prettier<'a> {
}
fn need_parens(&self, kind: AstKind<'a>) -> bool {
match kind {
// Only statements don't need parentheses.
kind if kind.is_statement() => return false,
AstKind::SequenceExpression(_) => {
let parent = self.parent_kind();
if matches!(parent, AstKind::Program(_)) {
return false;
}
}
AstKind::ObjectExpression(_) => {
let parent = self.parent_kind();
if matches!(parent, AstKind::Program(_)) {
return true;
}
}
AstKind::AssignmentExpression(_) => {
let parent = self.parent_kind();
if matches!(parent, AstKind::ArrowExpression(arrow_expr) if arrow_expr.expression) {
return true;
}
}
// NOTE: This is a fallback which should be removed when all code are ported.
AstKind::ParenthesizedExpression(_) => return true,
_ => {}
let parent_kind = self.parent_kind();
if self.check_parent_kind(kind, parent_kind) {
return true;
}
match kind {
AstKind::SequenceExpression(_) => !matches!(parent_kind, AstKind::Program(_)),
AstKind::ObjectExpression(e) => self.check_object_function_class(e.span),
AstKind::Function(f) if f.expression => self.check_object_function_class(f.span),
AstKind::Class(c) if c.is_expression() => self.check_object_function_class(c.span),
AstKind::AssignmentExpression(_) => {
matches!(parent_kind, AstKind::ArrowExpression(arrow_expr) if arrow_expr.expression)
}
AstKind::UpdateExpression(update_expr) => match parent_kind {
AstKind::UnaryExpression(unary_expr) => {
update_expr.prefix
&& ((update_expr.operator == UpdateOperator::Increment
&& unary_expr.operator == UnaryOperator::UnaryPlus)
|| (update_expr.operator == UpdateOperator::Decrement
&& unary_expr.operator == UnaryOperator::UnaryNegation))
}
_ => self.check_update_unary(update_expr.span),
},
AstKind::UnaryExpression(unary_expr) => match parent_kind {
AstKind::UnaryExpression(parent_expr) => {
let u_op = unary_expr.operator;
u_op == parent_expr.operator
&& (matches!(u_op, UnaryOperator::UnaryPlus | UnaryOperator::UnaryNegation))
}
_ => self.check_update_unary(unary_expr.span),
},
AstKind::YieldExpression(e) => match parent_kind {
AstKind::AwaitExpression(_) => true,
_ => self.check_yield_await(e.span),
},
AstKind::AwaitExpression(e) => self.check_yield_await(e.span),
AstKind::TSTypeAssertion(e) => self.check_binarish(e.span),
AstKind::TSAsExpression(e) => self.check_binarish(e.span),
AstKind::TSSatisfiesExpression(e) => self.check_binarish(e.span),
AstKind::LogicalExpression(e) => self.check_binarish(e.span),
AstKind::BinaryExpression(e) => match parent_kind {
AstKind::UpdateExpression(_) => true,
_ => self.check_binarish(e.span),
},
AstKind::MemberExpression(e) => self.check_member_call(e.span()),
AstKind::CallExpression(e) => self.check_member_call(e.span),
AstKind::TaggedTemplateExpression(e) => {
self.check_member_call_tagged_template_ts_non_null(e.span)
}
AstKind::TSNonNullExpression(e) => {
self.check_member_call_tagged_template_ts_non_null(e.span)
}
AstKind::Function(e) if e.expression => match parent_kind {
AstKind::CallExpression(call_expr) => call_expr.callee.span() == e.span,
AstKind::NewExpression(new_expr) => new_expr.callee.span() == e.span,
AstKind::TaggedTemplateExpression(_) => true,
_ => false,
},
AstKind::ArrowExpression(e) => match parent_kind {
AstKind::CallExpression(call_expr) => call_expr.callee.span() == e.span,
AstKind::NewExpression(new_expr) => new_expr.callee.span() == e.span,
AstKind::MemberExpression(member_expr) => member_expr.object().span() == e.span,
AstKind::TSAsExpression(_)
| AstKind::TSSatisfiesExpression(_)
| AstKind::TSNonNullExpression(_)
| AstKind::TaggedTemplateExpression(_)
| AstKind::UnaryExpression(_)
| AstKind::LogicalExpression(_)
| AstKind::AwaitExpression(_)
| AstKind::TSTypeAssertion(_) => true,
AstKind::ConditionalExpression(cond_expr) => cond_expr.test.span() == e.span,
_ => false,
},
AstKind::Class(class) if class.is_expression() => match parent_kind {
AstKind::NewExpression(new_expr) => new_expr.callee.span() == class.span,
_ => false,
},
_ => false,
}
}
fn check_parent_kind(&self, kind: AstKind<'a>, parent_kind: AstKind<'a>) -> bool {
match parent_kind {
AstKind::Class(class) => {
if let Some(h) = &class.super_class {
match kind {
AstKind::ArrowExpression(e) if e.span == h.span() => return true,
AstKind::AssignmentExpression(e) if e.span == h.span() => return true,
AstKind::AwaitExpression(e) if e.span == h.span() => return true,
AstKind::BinaryExpression(e) if e.span == h.span() => return true,
AstKind::ConditionalExpression(e) if e.span == h.span() => return true,
AstKind::LogicalExpression(e) if e.span == h.span() => return true,
AstKind::NewExpression(e) if e.span == h.span() => return true,
AstKind::ObjectExpression(e) if e.span == h.span() => return true,
AstKind::SequenceExpression(e) if e.span == h.span() => return true,
AstKind::TaggedTemplateExpression(e) if e.span == h.span() => return true,
AstKind::UnaryExpression(e) if e.span == h.span() => return true,
AstKind::UpdateExpression(e) if e.span == h.span() => return true,
AstKind::YieldExpression(e) if e.span == h.span() => return true,
AstKind::TSNonNullExpression(e) if e.span == h.span() => return true,
AstKind::Class(e)
if e.is_expression()
&& !e.decorators.is_empty()
&& e.span == h.span() =>
{
return true
}
_ => {}
}
}
}
AstKind::ModuleDeclaration(ModuleDeclaration::ExportDefaultDeclaration(_)) => {
return matches!(kind, AstKind::SequenceExpression(_))
|| self.should_wrap_function_for_export_default(kind)
}
_ => {}
}
false
}
fn check_object_function_class(&self, span: Span) -> bool {
for ast_kind in self.nodes.iter().rev() {
if let AstKind::ExpressionStatement(e) = ast_kind {
if Self::starts_with_no_lookahead_token(&e.expression, span) {
return true;
}
}
}
false
}
fn check_update_unary(&self, span: Span) -> bool {
match self.parent_kind() {
AstKind::MemberExpression(member_expr) => member_expr.object().span() == span,
AstKind::TaggedTemplateExpression(_) => true,
AstKind::CallExpression(call_expr) => call_expr.callee.span() == span,
AstKind::NewExpression(new_expr) => new_expr.callee.span() == span,
AstKind::BinaryExpression(bin_expr) => {
bin_expr.left.span() == span && bin_expr.operator == BinaryOperator::Exponential
}
AstKind::TSNonNullExpression(_) => true,
_ => false,
}
}
fn check_yield_await(&self, span: Span) -> bool {
match self.parent_kind() {
AstKind::TaggedTemplateExpression(_)
| AstKind::UnaryExpression(_)
| AstKind::LogicalExpression(_)
| AstKind::SpreadElement(_)
| AstKind::TSAsExpression(_)
| AstKind::TSSatisfiesExpression(_)
| AstKind::TSNonNullExpression(_)
| AstKind::BinaryExpression(_) => true,
AstKind::MemberExpression(member_expr) => member_expr.object().span() == span,
AstKind::NewExpression(new_expr) => new_expr.callee.span() == span,
AstKind::CallExpression(new_expr) => new_expr.callee.span() == span,
AstKind::ConditionalExpression(con_expr) => con_expr.test.span() == span,
_ => false,
}
}
fn check_binarish(&self, span: Span) -> bool {
match self.parent_kind() {
AstKind::TSAsExpression(_) => !self.is_binary_cast_expression(span),
AstKind::TSSatisfiesExpression(_) => !self.is_binary_cast_expression(span),
AstKind::ConditionalExpression(_) => self.is_binary_cast_expression(span),
AstKind::NewExpression(new_expr) => new_expr.callee.span() == span,
AstKind::CallExpression(new_expr) => new_expr.callee.span() == span,
AstKind::Class(class) => class.super_class.as_ref().is_some_and(|e| e.span() == span),
AstKind::TSTypeAssertion(_)
| AstKind::TaggedTemplateExpression(_)
| AstKind::UnaryExpression(_)
| AstKind::JSXSpreadAttribute(_)
| AstKind::SpreadElement(_)
| AstKind::AwaitExpression(_)
| AstKind::TSNonNullExpression(_)
| AstKind::UpdateExpression(_) => true,
AstKind::MemberExpression(member_expr) => member_expr.object().span() == span,
AstKind::AssignmentExpression(assign_expr) => {
assign_expr.left.span() == span && self.is_binary_cast_expression(span)
}
AstKind::AssignmentPattern(assign_pat) => {
assign_pat.left.span() == span && self.is_binary_cast_expression(span)
}
_ => false,
}
}
fn check_member_call(&self, span: Span) -> bool {
// if (shouldAddParenthesesToChainElement(path)) {
// return true;
// }
self.check_member_call_tagged_template_ts_non_null(span)
}
fn check_member_call_tagged_template_ts_non_null(&self, span: Span) -> bool {
match self.parent_kind() {
AstKind::NewExpression(new_expr) if new_expr.callee.span() == span => true,
_ => false,
}
}
fn should_wrap_function_for_export_default(&self, kind: AstKind<'a>) -> bool {
matches!(kind, AstKind::Function(f) if f.expression)
|| matches!(kind, AstKind::Class(c) if c.is_expression())
}
fn is_binary_cast_expression(&self, _span: Span) -> bool {
false
}
fn starts_with_no_lookahead_token(e: &Expression<'a>, span: Span) -> bool {
match e {
Expression::BinaryExpression(e) => Self::starts_with_no_lookahead_token(&e.left, span),
Expression::LogicalExpression(e) => Self::starts_with_no_lookahead_token(&e.left, span),
Expression::AssignmentExpression(e) => match &e.left {
AssignmentTarget::SimpleAssignmentTarget(t) => match t {
SimpleAssignmentTarget::AssignmentTargetIdentifier(_) => false,
SimpleAssignmentTarget::MemberAssignmentTarget(e) => {
Self::starts_with_no_lookahead_token(e.object(), span)
}
SimpleAssignmentTarget::TSAsExpression(e) => {
Self::starts_with_no_lookahead_token(&e.expression, span)
}
SimpleAssignmentTarget::TSSatisfiesExpression(e) => {
Self::starts_with_no_lookahead_token(&e.expression, span)
}
SimpleAssignmentTarget::TSNonNullExpression(e) => {
Self::starts_with_no_lookahead_token(&e.expression, span)
}
SimpleAssignmentTarget::TSTypeAssertion(e) => {
Self::starts_with_no_lookahead_token(&e.expression, span)
}
},
AssignmentTarget::AssignmentTargetPattern(_) => false,
},
Expression::MemberExpression(e) => {
Self::starts_with_no_lookahead_token(e.object(), span)
}
Expression::TaggedTemplateExpression(e) => {
if matches!(e.tag, Expression::FunctionExpression(_)) {
return false;
}
Self::starts_with_no_lookahead_token(&e.tag, span)
}
Expression::CallExpression(e) => {
if matches!(e.callee, Expression::FunctionExpression(_)) {
return false;
}
Self::starts_with_no_lookahead_token(&e.callee, span)
}
Expression::ConditionalExpression(e) => {
Self::starts_with_no_lookahead_token(&e.test, span)
}
Expression::UpdateExpression(e) => {
!e.prefix
&& match &e.argument {
SimpleAssignmentTarget::AssignmentTargetIdentifier(_) => false,
SimpleAssignmentTarget::MemberAssignmentTarget(e) => {
Self::starts_with_no_lookahead_token(e.object(), span)
}
SimpleAssignmentTarget::TSAsExpression(e) => {
Self::starts_with_no_lookahead_token(&e.expression, span)
}
SimpleAssignmentTarget::TSSatisfiesExpression(e) => {
Self::starts_with_no_lookahead_token(&e.expression, span)
}
SimpleAssignmentTarget::TSNonNullExpression(e) => {
Self::starts_with_no_lookahead_token(&e.expression, span)
}
SimpleAssignmentTarget::TSTypeAssertion(e) => {
Self::starts_with_no_lookahead_token(&e.expression, span)
}
}
}
Expression::SequenceExpression(e) => e
.expressions
.get(0)
.map_or(false, |e| Self::starts_with_no_lookahead_token(e, span)),
Expression::ChainExpression(e) => match &e.expression {
ChainElement::CallExpression(e) => {
Self::starts_with_no_lookahead_token(&e.callee, span)
}
ChainElement::MemberExpression(e) => {
Self::starts_with_no_lookahead_token(e.object(), span)
}
},
Expression::TSSatisfiesExpression(e) => {
Self::starts_with_no_lookahead_token(&e.expression, span)
}
Expression::TSAsExpression(e) => {
Self::starts_with_no_lookahead_token(&e.expression, span)
}
Expression::TSNonNullExpression(e) => {
Self::starts_with_no_lookahead_token(&e.expression, span)
}
_ => e.span() == span,
}
}
}

View file

@ -1,4 +1,4 @@
Compatibility: 138/601 (22.96%)
Compatibility: 131/601 (21.80%)
# Failed
@ -85,7 +85,6 @@ Compatibility: 138/601 (22.96%)
* assignment-comments/string.js
### async
* async/await-parse.js
* async/conditional-expression.js
* async/inline-await.js
* async/nested.js
@ -136,6 +135,9 @@ Compatibility: 138/601 (22.96%)
### call/invalid
* call/invalid/null-arguments-item.js
### chain-expression
* chain-expression/test.js
### class-comment
* class-comment/class-property.js
* class-comment/misc.js
@ -313,14 +315,21 @@ Compatibility: 138/601 (22.96%)
* eol/range-1.js
* eol/range-and-cursor-1.js
### es6modules
* es6modules/export_default_function_expression.js
* es6modules/export_default_function_expression_named.js
### export
* export/blank-line-between-specifiers.js
* export/same-local-and-exported.js
### export-default
* export-default/binary_and_template.js
* export-default/body.js
* export-default/class_instance.js
* export-default/function_in_template.js
* export-default/function_tostring.js
* export-default/iife.js
### export-default/escaped
* export-default/escaped/default-escaped.js
@ -330,12 +339,14 @@ Compatibility: 138/601 (22.96%)
### expression_statement
* expression_statement/no_regression.js
* expression_statement/use_strict.js
### for
* for/comment.js
* for/continue-and-break-comment-1.js
* for/continue-and-break-comment-2.js
* for/continue-and-break-comment-without-blocks.js
* for/in.js
### function
* function/function_expression.js
@ -371,6 +382,7 @@ Compatibility: 138/601 (22.96%)
* generator/async.js
### identifier/for-of
* identifier/for-of/await.js
* identifier/for-of/let.js
### identifier/parentheses
@ -411,6 +423,12 @@ Compatibility: 138/601 (22.96%)
### line-suffix-boundary
* line-suffix-boundary/boundary.js
### literal
* literal/number.js
### logical-assignment
* logical-assignment/logical-assignment.js
### logical_expressions
* logical_expressions/issue-7024.js
* logical_expressions/logical_expression_operators.js
@ -446,6 +464,9 @@ Compatibility: 138/601 (22.96%)
* method-chain/this.js
* method-chain/tuple-and-record.js
### method-chain/print-width-120
* method-chain/print-width-120/constructor.js
### module-blocks
* module-blocks/comments.js
* module-blocks/module-blocks.js
@ -649,11 +670,9 @@ Compatibility: 138/601 (22.96%)
### unary
* unary/object.js
* unary/series.js
### unary-expression
* unary-expression/comments.js
* unary-expression/urnary_expression.js
### unicode
* unicode/combining-characters.js
@ -666,7 +685,5 @@ Compatibility: 138/601 (22.96%)
* while/indent.js
### yield
* yield/arrow.js
* yield/conditional.js
* yield/jsx-without-parenthesis.js
* yield/jsx.js

View file

@ -311,7 +311,7 @@ impl TestRunner {
fn prettier(path: &Path, source_text: &str, prettier_options: PrettierOptions) -> String {
let allocator = Allocator::default();
let source_type = SourceType::from_path(path).unwrap();
let ret = Parser::new(&allocator, source_text, source_type).parse();
let ret = Parser::new(&allocator, source_text, source_type).preserve_parens(false).parse();
Prettier::new(&allocator, source_text, ret.trivias, prettier_options).build(&ret.program)
}
}