feature(formatter): Add basic support for if & for statements
MODIFY
crates/formatter/src/cst.rs
crates/formatter/src/cst.rs
use crate::{token, FormatElement, Formatter, ToFormatElement};
use rslint_parser::ast::{
- ArrayExpr, ArrowExpr, AssignPattern, BlockStmt, Declarator, EmptyStmt, ExprStmt, FnDecl,
- IdentProp, Literal, Name, NameRef, ObjectExpr, ParameterList, ReturnStmt, Script, SequenceExpr,
- SinglePattern, VarDecl,
+ ArrayExpr, ArrowExpr, AssignPattern, BlockStmt, Condition, Declarator, EmptyStmt, ExprStmt,
+ FnDecl, ForStmt, ForStmtInit, ForStmtTest, ForStmtUpdate, IdentProp, IfStmt, Literal, Name,
+ NameRef, ObjectExpr, ParameterList, ReturnStmt, Script, SequenceExpr, SinglePattern, VarDecl,
};
use rslint_parser::{AstNode, AstToken, SyntaxKind, SyntaxNode, SyntaxToken};
@@ -60,6 +60,24 @@ impl ToFormatElement for SyntaxNode {
SyntaxKind::RETURN_STMT => ReturnStmt::cast(self.clone())
.unwrap()
.to_format_element(formatter),
+ SyntaxKind::IF_STMT => IfStmt::cast(self.clone())
+ .unwrap()
+ .to_format_element(formatter),
+ SyntaxKind::CONDITION => Condition::cast(self.clone())
+ .unwrap()
+ .to_format_element(formatter),
+ SyntaxKind::FOR_STMT => ForStmt::cast(self.clone())
+ .unwrap()
+ .to_format_element(formatter),
+ SyntaxKind::FOR_STMT_TEST => ForStmtTest::cast(self.clone())
+ .unwrap()
+ .to_format_element(formatter),
+ SyntaxKind::FOR_STMT_INIT => ForStmtInit::cast(self.clone())
+ .unwrap()
+ .to_format_element(formatter),
+ SyntaxKind::FOR_STMT_UPDATE => ForStmtUpdate::cast(self.clone())
+ .unwrap()
+ .to_format_element(formatter),
SyntaxKind::EMPTY_STMT => EmptyStmt::cast(self.clone())
.unwrap()
.to_format_element(formatter),

MODIFY
crates/formatter/src/format_element.rs
crates/formatter/src/format_element.rs
@@ -232,14 +232,14 @@ where
I: IntoIterator<Item = FormatElement>,
{
concat_elements(Intersperse::new(
- elements.into_iter().filter(|e| e != &FormatElement::Empty),
+ elements.into_iter().filter(|e| !e.is_empty()),
separator.into(),
))
}
/// Inserts a hard line break before and after the content and increases the indention level for the content by one.
///
-/// Doesn't create an indention if the passed in content is [FormatElement::Empty].
+/// Doesn't create an indention if the passed in content is [FormatElement.is_empty].
///
/// ## Examples
///
@@ -265,7 +265,7 @@ where
pub fn indent<T: Into<FormatElement>>(content: T) -> FormatElement {
let content = content.into();
- if content == FormatElement::Empty {
+ if content.is_empty() {
content
} else {
format_elements![
@@ -328,7 +328,7 @@ pub fn indent<T: Into<FormatElement>>(content: T) -> FormatElement {
pub fn soft_indent<T: Into<FormatElement>>(content: T) -> FormatElement {
let content = content.into();
- if content == FormatElement::Empty {
+ if content.is_empty() {
content
} else {
format_elements![
@@ -396,7 +396,7 @@ pub fn soft_indent<T: Into<FormatElement>>(content: T) -> FormatElement {
pub fn group_elements<T: Into<FormatElement>>(content: T) -> FormatElement {
let content = content.into();
- if content == FormatElement::Empty {
+ if content.is_empty() {
content
} else {
FormatElement::from(Group::new(content))
@@ -459,7 +459,7 @@ pub fn group_elements<T: Into<FormatElement>>(content: T) -> FormatElement {
pub fn if_group_breaks<T: Into<FormatElement>>(content: T) -> FormatElement {
let content = content.into();
- if content == FormatElement::Empty {
+ if content.is_empty() {
content
} else {
FormatElement::from(ConditionalGroupContent::new(
@@ -525,7 +525,7 @@ where
{
let flat_content = flat_content.into();
- if flat_content == FormatElement::Empty {
+ if flat_content.is_empty() {
flat_content
} else {
FormatElement::from(ConditionalGroupContent::new(
@@ -686,6 +686,13 @@ impl Deref for Token {
}
}
+impl FormatElement {
+ /// Returns true if the element contains no content.
+ pub fn is_empty(&self) -> bool {
+ self == &FormatElement::Empty
+ }
+}
+
impl From<Group> for FormatElement {
fn from(group: Group) -> Self {
FormatElement::Group(group)

MODIFY
crates/formatter/src/ts/declarators/declarator.rs
crates/formatter/src/ts/declarators/declarator.rs
-use crate::{concat_elements, space_token, token, FormatElement, Formatter, ToFormatElement};
+use crate::{concat_elements, space_token, FormatElement, Formatter, ToFormatElement};
use rslint_parser::ast::Declarator;
impl ToFormatElement for Declarator {
@@ -17,7 +17,6 @@ impl ToFormatElement for Declarator {
if let Some(expression) = self.value() {
tokens.push(formatter.format_node(expression));
}
- tokens.push(token(";"));
concat_elements(tokens)
}

MODIFY
crates/formatter/src/ts/declarators/var_decl.rs
crates/formatter/src/ts/declarators/var_decl.rs
use crate::{concat_elements, space_token, token, FormatElement, Formatter, ToFormatElement};
-use rslint_parser::ast::VarDecl;
+use rslint_parser::ast::{ForStmtInit, VarDecl};
+use rslint_parser::AstNode;
impl ToFormatElement for VarDecl {
fn to_format_element(&self, formatter: &Formatter) -> FormatElement {
@@ -21,6 +22,12 @@ impl ToFormatElement for VarDecl {
tokens.push(formatter.format_node(declarator));
}
+ // don't add a semicolon if the var decl is in the init section of a for statement to avoid
+ // terminating the `init` with two semicolons.
+ if self.syntax().parent().and_then(ForStmtInit::cast).is_none() {
+ tokens.push(token(";"));
+ }
+
concat_elements(tokens)
}
}

MODIFY
crates/formatter/src/ts/script.rs
crates/formatter/src/ts/script.rs
-use crate::{
- format_elements, hard_line_break, join_elements, FormatElement, Formatter, ToFormatElement,
-};
+use crate::ts::statements::format_statements;
+use crate::{format_elements, hard_line_break, FormatElement, Formatter, ToFormatElement};
use rslint_parser::ast::Script;
impl ToFormatElement for Script {
fn to_format_element(&self, formatter: &Formatter) -> FormatElement {
- let mut tokens = vec![];
+ let mut elements = vec![];
if let Some(shebang) = self.shebang_token() {
- tokens.push(formatter.format_token(&shebang));
- tokens.push(hard_line_break());
+ elements.push(formatter.format_token(&shebang));
+ elements.push(hard_line_break());
}
- let elements = self.items().map(|item| formatter.format_node(item));
+ elements.push(format_statements(self.items(), formatter));
- tokens.push(join_elements(hard_line_break(), elements));
-
- format_elements![concat_elements(tokens), hard_line_break()]
+ format_elements![concat_elements(elements), hard_line_break()]
}
}

MODIFY
crates/formatter/src/ts/statements/block.rs
crates/formatter/src/ts/statements/block.rs
-use rslint_parser::ast::BlockStmt;
+use rslint_parser::ast::{BlockStmt, IfStmt};
+use rslint_parser::AstNode;
+use crate::ts::statements::format_statements;
use crate::{
- format_elements, hard_line_break, indent, join_elements, token, FormatElement, Formatter,
- ToFormatElement,
+ format_elements, hard_line_break, indent, token, FormatElement, Formatter, ToFormatElement,
};
impl ToFormatElement for BlockStmt {
fn to_format_element(&self, formatter: &Formatter) -> FormatElement {
- let stmts = self.stmts().map(|stmt| formatter.format_node(stmt));
+ let stmts = format_statements(self.stmts(), formatter);
- format_elements![
- token("{"),
- indent(join_elements(hard_line_break(), stmts)),
- token("}")
- ]
+ // Formatting of curly braces for an:
+ // * empty block: same line `{}`,
+ // * empty block that is the 'cons' or 'alt' of an if statement: two lines `{\n}`
+ // * non empty block: put each stmt on its own line: `{\nstmt1;\nstmt2;\n}`
+ let body = if stmts.is_empty() && self.syntax().parent().and_then(IfStmt::cast).is_some() {
+ hard_line_break()
+ } else {
+ indent(stmts)
+ };
+
+ format_elements![token("{"), body, token("}")]
}
}

ADD
crates/formatter/src/ts/statements/condition.rs
+use crate::{format_elements, soft_indent, FormatElement, Formatter, ToFormatElement};
+use rslint_parser::ast::Condition;
+
+impl ToFormatElement for Condition {
+ fn to_format_element(&self, formatter: &Formatter) -> FormatElement {
+ format_elements![
+ formatter.format_token(&self.l_paren_token().unwrap()),
+ soft_indent(formatter.format_node(self.condition().unwrap())),
+ formatter.format_token(&self.r_paren_token().unwrap()),
+ ]
+ }
+}

ADD
crates/formatter/src/ts/statements/for_stmt.rs
+use crate::{
+ concat_elements, format_elements, group_elements, soft_indent, soft_line_break_or_space,
+ space_token, token, FormatElement, Formatter, ToFormatElement,
+};
+use rslint_parser::ast::{ForHead, ForStmt, ForStmtInit, ForStmtTest, ForStmtUpdate};
+
+impl ToFormatElement for ForStmt {
+ fn to_format_element(&self, formatter: &Formatter) -> FormatElement {
+ let inner = if self.init().is_some() || self.test().is_some() || self.update().is_some() {
+ let mut inner = vec![];
+ if let Some(init) = self.init() {
+ inner.push(formatter.format_node(init));
+ }
+
+ inner.push(token(";"));
+ inner.push(soft_line_break_or_space());
+
+ if let Some(test) = self.test() {
+ inner.push(formatter.format_node(test));
+ }
+
+ inner.push(token(";"));
+ inner.push(soft_line_break_or_space());
+
+ if let Some(update) = self.update() {
+ inner.push(formatter.format_node(update));
+ }
+
+ concat_elements(inner)
+ } else {
+ token(";;")
+ };
+
+ group_elements(format_elements![
+ formatter.format_token(&self.for_token().unwrap()),
+ space_token(),
+ formatter.format_token(&self.l_paren_token().unwrap()),
+ group_elements(soft_indent(inner)),
+ formatter.format_token(&self.r_paren_token().unwrap()),
+ space_token(),
+ formatter.format_node(self.cons().unwrap())
+ ])
+ }
+}
+
+impl ToFormatElement for ForStmtInit {
+ fn to_format_element(&self, formatter: &Formatter) -> FormatElement {
+ formatter.format_node(self.inner().unwrap())
+ }
+}
+
+impl ToFormatElement for ForHead {
+ fn to_format_element(&self, formatter: &Formatter) -> FormatElement {
+ match self {
+ ForHead::Decl(decl) => decl.to_format_element(formatter),
+ ForHead::Expr(expr) => expr.to_format_element(formatter),
+ }
+ }
+}
+
+impl ToFormatElement for ForStmtTest {
+ fn to_format_element(&self, formatter: &Formatter) -> FormatElement {
+ formatter.format_node(self.expr().unwrap())
+ }
+}
+
+impl ToFormatElement for ForStmtUpdate {
+ fn to_format_element(&self, formatter: &Formatter) -> FormatElement {
+ formatter.format_node(self.expr().unwrap())
+ }
+}

ADD
crates/formatter/src/ts/statements/if_stmt.rs
+use crate::{
+ format_elements, group_elements, space_token, FormatElement, Formatter, ToFormatElement,
+};
+use rslint_parser::ast::IfStmt;
+
+impl ToFormatElement for IfStmt {
+ fn to_format_element(&self, formatter: &Formatter) -> FormatElement {
+ let mut result = format_elements![
+ group_elements(format_elements![
+ formatter.format_token(&self.if_token().unwrap()),
+ space_token(),
+ formatter.format_node(self.condition().unwrap()),
+ space_token(),
+ ]),
+ formatter.format_node(self.cons().unwrap())
+ ];
+
+ if let Some(else_token) = self.else_token() {
+ result = format_elements![
+ result,
+ space_token(),
+ formatter.format_token(&else_token),
+ space_token(),
+ formatter.format_node(self.alt().unwrap()),
+ ]
+ };
+
+ result
+ }
+}

MODIFY
crates/formatter/src/ts/statements/mod.rs
crates/formatter/src/ts/statements/mod.rs
+use crate::{hard_line_break, join_elements, FormatElement, Formatter};
+use rslint_parser::ast::{AstChildren, Stmt};
+
mod block;
+mod condition;
mod empty_statement;
mod expression_statement;
+mod for_stmt;
+mod if_stmt;
mod return_statement;
mod statement;
+
+/// Formats a list of statements
+pub fn format_statements(stmts: AstChildren<Stmt>, formatter: &Formatter) -> FormatElement {
+ join_elements(
+ hard_line_break(),
+ stmts.map(|stmt| formatter.format_node(stmt)),
+ )
+}

MODIFY
crates/formatter/src/ts/statements/statement.rs
crates/formatter/src/ts/statements/statement.rs
@@ -8,10 +8,10 @@ impl ToFormatElement for Stmt {
Stmt::BlockStmt(block) => block.to_format_element(formatter),
Stmt::EmptyStmt(empty_statement) => empty_statement.to_format_element(formatter),
Stmt::ExprStmt(expr_stmt) => expr_stmt.to_format_element(formatter),
- Stmt::IfStmt(_) => todo!(),
+ Stmt::IfStmt(if_stmt) => if_stmt.to_format_element(formatter),
Stmt::DoWhileStmt(_) => todo!(),
Stmt::WhileStmt(_) => todo!(),
- Stmt::ForStmt(_) => todo!(),
+ Stmt::ForStmt(for_stmt) => for_stmt.to_format_element(formatter),
Stmt::ForInStmt(_) => todo!(),
Stmt::ContinueStmt(_) => todo!(),
Stmt::BreakStmt(_) => todo!(),

ADD
crates/formatter/tests/specs/js/for_stmt.expected.js
+for (;;) {
+ let x = 10;
+}
+for (x; ; ) {
+ let y = 20;
+}
+for (; y; ) {
+ let z = 8;
+}
+for (; ; z) {
+ let a = 3;
+}
+for (a; b; c) {
+ let b = 3;
+}
+for (
+ let aVeryLongVariableNameToEnforceLineBreaks = 0;
+ aVeryLongVariableNameToEnforceLineBreaks;
+ aVeryLongVariableNameToEnforceLineBreaks
+) {}

ADD
crates/formatter/tests/specs/js/for_stmt.js
+for (; ;) {
+ let x = 10;
+}
+for (x;;) {
+ let y = 20
+}
+for (;y;) {
+ let z = 8;
+}
+for (;;z) {
+ let a = 3;
+}
+for(a;b;c) {
+ let b = 3;
+}
+for(let aVeryLongVariableNameToEnforceLineBreaks = 0; aVeryLongVariableNameToEnforceLineBreaks; aVeryLongVariableNameToEnforceLineBreaks) {
+}

ADD
crates/formatter/tests/specs/js/if_stmt.expected.js
+if (true) {
+ let y = 20;
+} else {
+ let x = 10;
+}
+if (
+ aVeryLongVeriableNameSoThatTheConditionBreaksAcrossMultipleLinesAndIDontKnow
+) {
+} else {
+}
+if (true) {
+}

ADD
crates/formatter/tests/specs/js/if_stmt.js
+if (
+ true) {
+ let y = 20;
+} else {
+ let x= 10;
+}
+
+if (aVeryLongVeriableNameSoThatTheConditionBreaksAcrossMultipleLinesAndIDontKnow) {
+
+
+
+} else {
+
+
+}
+
+if (true) {
+
+
+}
+

feature(formatter): Add basic support for if & for statements (#1706)