From 2d6dc1cc9778bea161f8521238d04da243847211 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 15 Sep 2022 14:01:28 +0200 Subject: [PATCH] Comments infrastructure --- Cargo.lock | 2 + crates/rome_formatter/Cargo.toml | 4 + crates/rome_formatter/src/comments.rs | 555 ++++++++-- crates/rome_formatter/src/comments/builder.rs | 999 ++++++++++++++++++ crates/rome_formatter/src/lib.rs | 22 +- 5 files changed, 1458 insertions(+), 124 deletions(-) create mode 100644 crates/rome_formatter/src/comments/builder.rs diff --git a/Cargo.lock b/Cargo.lock index d8fd1524a0c..1a4de63b6a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1571,6 +1571,8 @@ dependencies = [ "cfg-if", "countme", "indexmap", + "rome_js_parser", + "rome_js_syntax", "rome_rowan", "rustc-hash", "schemars", diff --git a/crates/rome_formatter/Cargo.toml b/crates/rome_formatter/Cargo.toml index fde1f730112..5c63d0c7b5b 100644 --- a/crates/rome_formatter/Cargo.toml +++ b/crates/rome_formatter/Cargo.toml @@ -18,5 +18,9 @@ schemars = { version = "0.8.10", optional = true } rustc-hash = "1.1.0" countme = "3.0.1" +[dev-dependencies] +rome_js_parser = { path = "../rome_js_parser"} +rome_js_syntax = { path = "../rome_js_syntax" } + [features] serde = ["dep:serde", "schemars", "rome_rowan/serde"] diff --git a/crates/rome_formatter/src/comments.rs b/crates/rome_formatter/src/comments.rs index 4c53a59b443..f022ae8bbee 100644 --- a/crates/rome_formatter/src/comments.rs +++ b/crates/rome_formatter/src/comments.rs @@ -1,12 +1,13 @@ +mod builder; mod map; -use rome_rowan::{ - Direction, Language, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxTriviaPieceComments, - WalkEvent, -}; +use self::{builder::CommentsBuilderVisitor, map::CommentsMap}; +use crate::{TextSize, TransformSourceMap}; +use rome_rowan::syntax::SyntaxElementKey; +use rome_rowan::{Language, SyntaxNode, SyntaxToken, SyntaxTriviaPieceComments}; +use rustc_hash::FxHashSet; #[cfg(debug_assertions)] use std::cell::RefCell; -use std::collections::HashSet; use std::rc::Rc; #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -47,43 +48,6 @@ pub enum CommentKind { Line, } -#[derive(Debug, Clone)] -pub struct SourceComment { - /// The number of lines appearing before this comment - lines_before: u32, - - /// The comment piece - piece: SyntaxTriviaPieceComments, -} - -impl SourceComment { - /// Creates a new trailing comment. A trailing comment always has 0 lines before. - pub fn trailing(piece: SyntaxTriviaPieceComments) -> Self { - Self { - lines_before: 0, - piece, - } - } - - /// Creates a leading comment with the specified lines before - pub fn leading(piece: SyntaxTriviaPieceComments, lines_before: u32) -> Self { - Self { - lines_before, - piece, - } - } - - /// Returns the underlining comment trivia piece - pub fn piece(&self) -> &SyntaxTriviaPieceComments { - &self.piece - } - - /// Returns the number of lines before directly before this comment - pub fn lines_before(&self) -> u32 { - self.lines_before - } -} - impl CommentKind { pub const fn is_line(&self) -> bool { matches!(self, CommentKind::Line) @@ -116,24 +80,236 @@ impl CommentKind { } } +#[derive(Debug, Clone)] +pub struct SourceComment { + /// The number of lines appearing before this comment + pub(crate) lines_before: u32, + + pub(crate) lines_after: u32, + + /// The comment piece + pub(crate) piece: SyntaxTriviaPieceComments, + + pub(crate) kind: CommentKind, +} + +impl SourceComment { + /// Returns the underlining comment trivia piece + pub fn piece(&self) -> &SyntaxTriviaPieceComments { + &self.piece + } + + /// Returns the number of lines before directly before this comment + pub fn lines_before(&self) -> u32 { + self.lines_before + } + + pub fn lines_after(&self) -> u32 { + self.lines_after + } + + /// The kind of the comment + pub fn kind(&self) -> CommentKind { + self.kind + } +} + +#[derive(Debug, Clone)] +pub struct DecoratedComment { + enclosing: SyntaxNode, + preceding: Option>, + following: Option>, + following_token: SyntaxToken, + position: CommentTextPosition, + lines_before: u32, + lines_after: u32, + comment: SyntaxTriviaPieceComments, + kind: CommentKind, +} + +impl DecoratedComment { + /// The node that fully encloses the comment (the comment's start and end position are fully in the + /// node's bounds). + pub fn enclosing_node(&self) -> &SyntaxNode { + &self.enclosing + } + + pub fn piece(&self) -> &SyntaxTriviaPieceComments { + &self.comment + } + + /// The node directly preceding the comment or [None] if the comment is preceded by a token or is the first + /// token in the program. + pub fn preceding_node(&self) -> Option<&SyntaxNode> { + self.preceding.as_ref() + } + + fn take_preceding_node(&mut self) -> Option> { + self.preceding.take() + } + + /// The node directly following the comment or [None] if the comment is followed by a token or is the last token in the program. + pub fn following_node(&self) -> Option<&SyntaxNode> { + self.following.as_ref() + } + + fn take_following_node(&mut self) -> Option> { + self.following.take() + } + + /// The number of lines between this comment and the **previous** token, comment or skipped trivia. + pub fn lines_before(&self) -> u32 { + self.lines_before + } + + pub fn lines_after(&self) -> u32 { + self.lines_after + } + + /// Returns the [kind](CommentKind) of the comment. + pub fn kind(&self) -> CommentKind { + self.kind + } + + pub fn position(&self) -> CommentTextPosition { + self.position + } + + pub fn following_token(&self) -> &SyntaxToken { + &self.following_token + } +} + +impl From> for SourceComment { + fn from(decorated: DecoratedComment) -> Self { + Self { + lines_before: decorated.lines_before, + lines_after: decorated.lines_after, + piece: decorated.comment, + kind: decorated.kind, + } + } +} + +/// The position of a comment in the source document. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum CommentTextPosition { + /// A comment that is separated by at least one line break from the following token + /// + /// ```javascript + /// a; /* this */ // or this + /// b; + EndOfLine, + + /// A Comment that is separated by at least one line break from the preceding token + /// + /// ```javascript + /// a; + /// /* comment */ /* or this */ + /// b; + /// ``` + OwnLine, + + /// A comment that is placed on the same line as the preceding and following token. + /// + /// ```javascript + /// a /* comment */ + b + /// ``` + SameLine, +} + +impl CommentTextPosition { + pub const fn is_same_line(&self) -> bool { + matches!(self, CommentTextPosition::SameLine) + } + + pub const fn is_own_line(&self) -> bool { + matches!(self, CommentTextPosition::OwnLine) + } + + pub const fn is_end_of_line(&self) -> bool { + matches!(self, CommentTextPosition::EndOfLine) + } +} + +#[derive(Debug)] +pub enum CommentPlacement { + /// Overrides the positioning of the comment to be a leading node comment. + Leading { + node: SyntaxNode, + comment: SourceComment, + }, + /// Overrides the positioning of the comment to be a trailing node comment. + Trailing { + node: SyntaxNode, + comment: SourceComment, + }, + + /// Makes this comment a dangling comment of `node` + Dangling { + node: SyntaxNode, + comment: SourceComment, + }, + + /// Uses the default positioning rules for the comment. + /// TODO document rules + Default(DecoratedComment), +} + +impl CommentPlacement { + #[inline] + pub fn leading(node: SyntaxNode, comment: impl Into>) -> Self { + Self::Leading { + node, + comment: comment.into(), + } + } + + pub fn dangling(node: SyntaxNode, comment: impl Into>) -> Self { + Self::Dangling { + node, + comment: comment.into(), + } + } + + #[inline] + pub fn trailing(node: SyntaxNode, comment: impl Into>) -> Self { + Self::Trailing { + node, + comment: comment.into(), + } + } + + #[inline] + pub fn or_else(self, or_else: F) -> Self + where + F: FnOnce(DecoratedComment) -> CommentPlacement, + { + match self { + CommentPlacement::Default(comment) => or_else(comment), + placement => placement, + } + } +} + /// Defines how to format comments for a specific [Language]. -pub trait CommentStyle { +pub trait CommentStyle: Default { + type Language: Language; + /// Returns `true` if a comment with the given `text` is a `rome-ignore format:` suppression comment. - fn is_suppression(&self, text: &str) -> bool; + fn is_suppression(_text: &str) -> bool { + false + } /// Returns the (kind)[CommentKind] of the comment - fn get_comment_kind(&self, comment: &SyntaxTriviaPieceComments) -> CommentKind; - - /// Returns `true` if a token with the passed `kind` marks the start of a group. Common group tokens are: - /// * left parentheses: `(`, `[`, `{` - fn is_group_start_token(&self, kind: L::Kind) -> bool; - - /// Returns `true` if a token with the passed `kind` marks the end of a group. Common group end tokens are: - /// * right parentheses: `)`, `]`, `}` - /// * end of statement token: `;` - /// * element separator: `,` or `.`. - /// * end of file token: `EOF` - fn is_group_end_token(&self, kind: L::Kind) -> bool; + fn get_comment_kind(comment: &SyntaxTriviaPieceComments) -> CommentKind; + + fn place_comment( + &self, + comment: DecoratedComment, + ) -> CommentPlacement { + CommentPlacement::Default(comment) + } } /// Type that stores the comments of a tree and gives access to: @@ -143,7 +319,7 @@ pub trait CommentStyle { /// * the dangling comments of a token /// /// Cloning `comments` is cheap as it only involves bumping a reference counter. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone, Default)] pub struct Comments { /// The use of a [Rc] is necessary to achieve that [Comments] has a lifetime that is independent from the [crate::Formatter]. /// Having independent lifetimes is necessary to support the use case where a (formattable object)[crate::Format] @@ -165,65 +341,117 @@ pub struct Comments { impl Comments { /// Extracts all the suppressions from `root` and its child nodes. - pub fn from_node(root: &SyntaxNode, language: &FormatLanguage) -> Self + pub fn from_node