Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
feat(rome_formatter): format import syntax (#1992)
Browse files Browse the repository at this point in the history
* feat(rome_formatter): initial formatting of module

* chore: reduce bloat

* feat: format import syntax

* chore: extract logic to a new API

* test: bare import tests

* feat: default import syntax

* feat: named import formatting

* feat: namespace import

* feat: correctly retain line breaks

* chore: add formatting to CST

* chore: clean code

* chore: rebase and update test

* code review

* rebase

* clippy fixes

* rebase and fix issues

* code review

* code review

* rebase

* remove old code
  • Loading branch information
ematipico authored Jan 31, 2022
1 parent 7418d1f commit 9a7baf0
Show file tree
Hide file tree
Showing 42 changed files with 730 additions and 141 deletions.
87 changes: 70 additions & 17 deletions crates/rome_formatter/src/cst.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
use crate::{ts::format_statements, FormatElement, FormatResult, Formatter, ToFormatElement};
use crate::{FormatElement, FormatResult, Formatter, ToFormatElement};
use rslint_parser::ast::{
JsArrayBindingPattern, JsArrayExpression, JsArrowFunctionExpression, JsBlockStatement,
JsBooleanLiteralExpression, JsCallArguments, JsCallExpression, JsCaseClause, JsCatchClause,
JsClassStatement, JsConstructorParameters, JsContinueStatement, JsDebuggerStatement,
JsDefaultClause, JsDoWhileStatement, JsEmptyStatement, JsExpressionStatement, JsFinallyClause,
JsForInStatement, JsForStatement, JsFunctionStatement, JsGetterClassMember,
JsIdentifierBinding, JsIdentifierExpression, JsIfStatement, JsLabeledStatement, JsModule,
JsNullLiteralExpression, JsNumberLiteralExpression, JsObjectExpression, JsParameters,
JsPropertyClassMember, JsPropertyObjectMember, JsReturnStatement, JsScript,
JsSequenceExpression, JsSetterClassMember, JsShorthandPropertyObjectMember, JsSpread,
JsStatementList, JsStaticInitializationBlockClassMember, JsStringLiteralExpression,
JsSwitchStatement, JsTemplate, JsTemplateChunkElement, JsTemplateElement, JsTryStatement,
JsUnknownAssignment, JsUnknownBinding, JsUnknownExpression, JsUnknownImportAssertionEntry,
JsUnknownMember, JsUnknownNamedImportSpecifier, JsUnknownParameter, JsUnknownStatement,
JsVariableDeclaration, JsVariableDeclarations, JsVariableStatement, JsWhileStatement,
JsWithStatement,
JsDefaultClause, JsDefaultImportSpecifier, JsDoWhileStatement, JsEmptyStatement,
JsExpressionStatement, JsFinallyClause, JsForInStatement, JsForStatement, JsFunctionStatement,
JsGetterClassMember, JsIdentifierBinding, JsIdentifierExpression, JsIfStatement,
JsImportAssertion, JsImportAssertionEntry, JsImportBareClause, JsImportCallExpression,
JsImportDefaultClause, JsImportNamedClause, JsImportNamespaceClause, JsLabeledStatement,
JsLiteralExportName, JsModule, JsModuleSource, JsNamedImportSpecifier, JsNamedImportSpecifiers,
JsNamespaceImportSpecifier, JsNullLiteralExpression, JsNumberLiteralExpression,
JsObjectExpression, JsParameters, JsPropertyClassMember, JsPropertyObjectMember,
JsReturnStatement, JsScript, JsSequenceExpression, JsSetterClassMember,
JsShorthandNamedImportSpecifier, JsShorthandPropertyObjectMember, JsSpread, JsStatementList,
JsStaticInitializationBlockClassMember, JsStringLiteralExpression, JsSwitchStatement,
JsTemplate, JsTemplateChunkElement, JsTemplateElement, JsTryStatement, JsUnknownAssignment,
JsUnknownBinding, JsUnknownExpression, JsUnknownImportAssertionEntry, JsUnknownMember,
JsUnknownNamedImportSpecifier, JsUnknownParameter, JsUnknownStatement, JsVariableDeclaration,
JsVariableDeclarations, JsVariableStatement, JsWhileStatement, JsWithStatement,
};
use rslint_parser::{AstNode, JsSyntaxKind, SyntaxNode};

Expand Down Expand Up @@ -170,6 +173,13 @@ impl ToFormatElement for SyntaxNode {
JsSyntaxKind::JS_PROPERTY_CLASS_MEMBER => JsPropertyClassMember::cast(self.clone())
.unwrap()
.to_format_element(formatter),

JsSyntaxKind::JS_DEFAULT_IMPORT_SPECIFIER => {
JsDefaultImportSpecifier::cast(self.clone())
.unwrap()
.to_format_element(formatter)
}

JsSyntaxKind::JS_UNKNOWN_BINDING => JsUnknownBinding::cast(self.clone())
.unwrap()
.to_format_element(formatter),
Expand Down Expand Up @@ -214,14 +224,57 @@ impl ToFormatElement for SyntaxNode {
JsSyntaxKind::JS_TEMPLATE_CHUNK_ELEMENT => JsTemplateChunkElement::cast(self.clone())
.unwrap()
.to_format_element(formatter),
JsSyntaxKind::JS_IMPORT_BARE_CLAUSE => JsImportBareClause::cast(self.clone())
.unwrap()
.to_format_element(formatter),
JsSyntaxKind::JS_MODULE_SOURCE => JsModuleSource::cast(self.clone())
.unwrap()
.to_format_element(formatter),
JsSyntaxKind::JS_IMPORT_ASSERTION => JsImportAssertion::cast(self.clone())
.unwrap()
.to_format_element(formatter),
JsSyntaxKind::JS_IMPORT_NAMED_CLAUSE => JsImportNamedClause::cast(self.clone())
.unwrap()
.to_format_element(formatter),

JsSyntaxKind::JS_STATEMENT_LIST => Ok(format_statements(
JsStatementList::cast(self.clone()).unwrap(),
formatter,
)),
JsSyntaxKind::JS_STATEMENT_LIST => {
Ok(formatter.format_list(JsStatementList::cast(self.clone()).unwrap()))
}
JsSyntaxKind::JS_VARIABLE_DECLARATIONS => JsVariableDeclarations::cast(self.clone())
.unwrap()
.to_format_element(formatter),
JsSyntaxKind::JS_IMPORT_ASSERTION_ENTRY => JsImportAssertionEntry::cast(self.clone())
.unwrap()
.to_format_element(formatter),

JsSyntaxKind::JS_IMPORT_CALL_EXPRESSION => JsImportCallExpression::cast(self.clone())
.unwrap()
.to_format_element(formatter),
JsSyntaxKind::JS_IMPORT_DEFAULT_CLAUSE => JsImportDefaultClause::cast(self.clone())
.unwrap()
.to_format_element(formatter),
JsSyntaxKind::JS_LITERAL_EXPORT_NAME => JsLiteralExportName::cast(self.clone())
.unwrap()
.to_format_element(formatter),
JsSyntaxKind::JS_IMPORT_NAMESPACE_CLAUSE => JsImportNamespaceClause::cast(self.clone())
.unwrap()
.to_format_element(formatter),
JsSyntaxKind::JS_NAMESPACE_IMPORT_SPECIFIER => {
JsNamespaceImportSpecifier::cast(self.clone())
.unwrap()
.to_format_element(formatter)
}
JsSyntaxKind::JS_NAMED_IMPORT_SPECIFIER => JsNamedImportSpecifier::cast(self.clone())
.unwrap()
.to_format_element(formatter),
JsSyntaxKind::JS_NAMED_IMPORT_SPECIFIERS => JsNamedImportSpecifiers::cast(self.clone())
.unwrap()
.to_format_element(formatter),
JsSyntaxKind::JS_SHORTHAND_NAMED_IMPORT_SPECIFIER => {
JsShorthandNamedImportSpecifier::cast(self.clone())
.unwrap()
.to_format_element(formatter)
}

_ => todo!(
"Implement formatting for the {:?} syntax kind.",
Expand Down
36 changes: 32 additions & 4 deletions crates/rome_formatter/src/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ use std::collections::HashSet;
use crate::{
concat_elements, empty_element, empty_line,
format_element::{normalize_newlines, Token},
format_elements, hard_line_break, if_group_breaks, if_group_fits_on_single_line, line_suffix,
soft_line_break_or_space, space_token, FormatElement, FormatOptions, FormatResult,
ToFormatElement,
format_elements, hard_line_break, if_group_breaks, if_group_fits_on_single_line,
join_elements_hard_line, line_suffix, soft_line_break_or_space, space_token, FormatElement,
FormatOptions, FormatResult, ToFormatElement,
};
use rome_rowan::SyntaxElement;
#[cfg(debug_assertions)]
use rslint_parser::SyntaxNodeExt;
use rslint_parser::{AstNode, AstSeparatedList, SyntaxNode, SyntaxToken};
use rslint_parser::{AstNode, AstNodeList, AstSeparatedList, SyntaxNode, SyntaxToken};

/// Handles the formatting of a CST and stores the options how the CST should be formatted (user preferences).
/// The formatter is passed to the [ToFormatElement] implementation of every node in the CST so that they
Expand Down Expand Up @@ -266,6 +266,34 @@ impl Formatter {
Ok(result.into_iter())
}

/// It formats a list of nodes that are not separated. It's a ad-hoc function to
/// format lists that implement [rslint_parser::AstNodeList].
///
/// The elements of the list are joined together using [join_elements_hard_line], which will
/// end up separated by hard lines or empty lines.
///
/// If the formatter fails to format an element, said element gets printed verbatim.
pub fn format_list<List, Node: AstNode + ToFormatElement>(&self, list: List) -> FormatElement
where
List: AstNodeList<Node>,
{
let formatted_list = list.iter().map(|module_item| {
let snapshot = self.snapshot();
let elem = match self.format_node(&module_item) {
Ok(result) => result,
Err(_) => {
self.restore(snapshot);
self.format_verbatim(module_item.syntax())
.trim_start()
.trim_end()
}
};

(module_item, elem)
});
join_elements_hard_line(formatted_list)
}

fn print_leading_trivia(&self, token: &SyntaxToken) -> FormatElement {
let mut line_count = 0;
let mut elements = Vec::new();
Expand Down
6 changes: 2 additions & 4 deletions crates/rome_formatter/src/ts/auxiliary/function_body.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use rslint_parser::ast::JsFunctionBody;

use crate::ts::statements::format_statements;
use crate::{
block_indent, format_elements, FormatElement, FormatResult, Formatter, ToFormatElement,
};
use rslint_parser::ast::JsFunctionBody;

impl ToFormatElement for JsFunctionBody {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
Expand All @@ -12,7 +10,7 @@ impl ToFormatElement for JsFunctionBody {
|open_token_trailing, close_token_leading| {
Ok(block_indent(format_elements![
open_token_trailing,
format_statements(self.statements(), formatter),
formatter.format_list(self.statements()),
close_token_leading,
]))
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::ts::statements::format_statements;
use crate::{
block_indent, format_elements, space_token, FormatElement, FormatResult, Formatter,
ToFormatElement,
Expand All @@ -13,7 +12,7 @@ impl ToFormatElement for JsStaticInitializationBlockClassMember {
|open_token_trailing, close_token_leading| {
Ok(block_indent(format_elements![
open_token_trailing,
format_statements(self.statements(), formatter),
formatter.format_list(self.statements()),
close_token_leading,
]))
},
Expand Down
26 changes: 8 additions & 18 deletions crates/rome_formatter/src/ts/directives.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
use crate::{
format_elements, join_elements_hard_line, token, FormatElement, FormatResult, Formatter,
empty_element, format_elements, hard_line_break, token, FormatElement, FormatResult, Formatter,
ToFormatElement,
};
use rslint_parser::ast::{AstNode, AstNodeList, JsDirective, JsDirectiveList};
use rslint_parser::ast::{AstNodeList, JsDirective, JsDirectiveList};

pub fn format_directives(directives: JsDirectiveList, formatter: &Formatter) -> FormatElement {
join_elements_hard_line(directives.iter().map(|directive| {
let snapshot = formatter.snapshot();
let elem = match formatter.format_node(&directive) {
Ok(result) => result,
Err(_) => {
formatter.restore(snapshot);
formatter
.format_verbatim(directive.syntax())
.trim_start()
.trim_end()
}
};

(directive, elem)
}))
pub fn format_directives_list(directives: JsDirectiveList, formatter: &Formatter) -> FormatElement {
if !directives.is_empty() {
format_elements![formatter.format_list(directives), hard_line_break()]
} else {
empty_element()
}
}

impl ToFormatElement for JsDirective {
Expand Down
15 changes: 15 additions & 0 deletions crates/rome_formatter/src/ts/import/any_import_assertion_entry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use crate::{FormatElement, FormatResult, Formatter, ToFormatElement};
use rslint_parser::ast::JsAnyImportAssertionEntry;

impl ToFormatElement for JsAnyImportAssertionEntry {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
match self {
JsAnyImportAssertionEntry::JsImportAssertionEntry(assertion_entry) => {
assertion_entry.to_format_element(formatter)
}
JsAnyImportAssertionEntry::JsUnknownImportAssertionEntry(unknown_assertion_entry) => {
unknown_assertion_entry.to_format_element(formatter)
}
}
}
}
17 changes: 17 additions & 0 deletions crates/rome_formatter/src/ts/import/any_import_clause.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use crate::{FormatElement, FormatResult, Formatter, ToFormatElement};
use rslint_parser::ast::AnyJsImportClause;

impl ToFormatElement for AnyJsImportClause {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
match self {
AnyJsImportClause::JsImportBareClause(bare_clause) => {
bare_clause.to_format_element(formatter)
}
AnyJsImportClause::JsImportDefaultClause(e) => e.to_format_element(formatter),
AnyJsImportClause::JsImportNamedClause(named_clause) => {
named_clause.to_format_element(formatter)
}
AnyJsImportClause::JsImportNamespaceClause(e) => e.to_format_element(formatter),
}
}
}
11 changes: 11 additions & 0 deletions crates/rome_formatter/src/ts/import/any_named_import.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use crate::{FormatElement, FormatResult, Formatter, ToFormatElement};
use rslint_parser::ast::JsAnyNamedImport;

impl ToFormatElement for JsAnyNamedImport {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
match self {
JsAnyNamedImport::JsNamedImportSpecifiers(e) => e.to_format_element(formatter),
JsAnyNamedImport::JsNamespaceImportSpecifier(e) => e.to_format_element(formatter),
}
}
}
16 changes: 16 additions & 0 deletions crates/rome_formatter/src/ts/import/any_named_import_specifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use crate::{FormatElement, FormatResult, Formatter, ToFormatElement};
use rslint_parser::ast::JsAnyNamedImportSpecifier;

impl ToFormatElement for JsAnyNamedImportSpecifier {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
match self {
JsAnyNamedImportSpecifier::JsNamedImportSpecifier(e) => e.to_format_element(formatter),
JsAnyNamedImportSpecifier::JsShorthandNamedImportSpecifier(e) => {
e.to_format_element(formatter)
}
JsAnyNamedImportSpecifier::JsUnknownNamedImportSpecifier(e) => {
e.to_format_element(formatter)
}
}
}
}
41 changes: 41 additions & 0 deletions crates/rome_formatter/src/ts/import/assertion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use crate::{
empty_element, format_elements, group_elements, if_group_fits_on_single_line, join_elements,
soft_indent, soft_line_break_or_space, space_token, token, FormatElement, FormatResult,
Formatter, ToFormatElement,
};
use rslint_parser::ast::JsImportAssertion;
use rslint_parser::AstSeparatedList;

impl ToFormatElement for JsImportAssertion {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
let assert_token = formatter.format_token(&self.assert_token()?)?;
let assertions = self.assertions();

let space = if assertions.is_empty() {
empty_element()
} else {
if_group_fits_on_single_line(space_token())
};

let result = group_elements(formatter.format_delimited(
&self.l_curly_token()?,
|leading, trailing| {
Ok(format_elements!(
space.clone(),
soft_indent(format_elements![
leading,
join_elements(
soft_line_break_or_space(),
formatter.format_separated(assertions, || token(","))?
),
trailing
]),
space,
))
},
&self.r_curly_token()?,
)?);

Ok(format_elements![assert_token, space_token(), result])
}
}
15 changes: 15 additions & 0 deletions crates/rome_formatter/src/ts/import/assertion_entry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use crate::{
format_elements, space_token, FormatElement, FormatResult, Formatter, ToFormatElement,
};
use rslint_parser::ast::JsImportAssertionEntry;

impl ToFormatElement for JsImportAssertionEntry {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
Ok(format_elements![
formatter.format_token(&self.key()?)?,
formatter.format_token(&self.colon_token()?)?,
space_token(),
formatter.format_token(&self.value_token()?)?,
])
}
}
18 changes: 18 additions & 0 deletions crates/rome_formatter/src/ts/import/bare_clause.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::{
empty_element, format_elements, space_token, FormatElement, FormatResult, Formatter,
ToFormatElement,
};
use rslint_parser::ast::JsImportBareClause;

impl ToFormatElement for JsImportBareClause {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
let source = formatter.format_node(&self.source()?)?;
let assertion = if let Some(assertion) = self.assertion() {
format_elements![space_token(), formatter.format_node(&assertion)?]
} else {
empty_element()
};

Ok(format_elements![source, assertion])
}
}
11 changes: 11 additions & 0 deletions crates/rome_formatter/src/ts/import/default_import_specifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use crate::{format_elements, FormatElement, FormatResult, Formatter, ToFormatElement};
use rslint_parser::ast::JsDefaultImportSpecifier;

impl ToFormatElement for JsDefaultImportSpecifier {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
Ok(format_elements![
formatter.format_node(&self.local_name()?)?,
formatter.format_token(&self.trailing_comma_token()?)?
])
}
}
11 changes: 11 additions & 0 deletions crates/rome_formatter/src/ts/import/import_call_expression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use crate::{format_elements, FormatElement, FormatResult, Formatter, ToFormatElement};
use rslint_parser::ast::JsImportCallExpression;

impl ToFormatElement for JsImportCallExpression {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
Ok(format_elements![
formatter.format_token(&self.import_token()?)?,
formatter.format_node(&self.arguments()?)?,
])
}
}
Loading

0 comments on commit 9a7baf0

Please sign in to comment.