Skip to content

Commit

Permalink
Add rune fmt subcommand (#492)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: John-John Tedro <[email protected]>
  • Loading branch information
tgolsson and udoprog authored May 2, 2023
1 parent 266283f commit 802101c
Show file tree
Hide file tree
Showing 30 changed files with 2,852 additions and 59 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ jobs:
components: rustfmt
- run: cargo fmt --all -- --check

runefmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- run: shopt -s globstar ; cargo run --bin rune -- fmt --check **/*.rn

clippy:
runs-on: ubuntu-latest
steps:
Expand Down
3 changes: 2 additions & 1 deletion crates/rune/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ emit = ["codespan-reporting"]
bench = []
workspace = ["toml", "semver", "relative-path", "serde-hashkey"]
doc = ["rust-embed", "handlebars", "pulldown-cmark", "syntect"]
cli = ["doc", "bincode", "atty", "tracing-subscriber", "anyhow/std", "clap", "webbrowser", "capture-io", "disable-io", "languageserver"]
cli = ["doc", "bincode", "atty", "tracing-subscriber", "anyhow/std", "clap", "webbrowser", "capture-io", "disable-io", "languageserver", "fmt"]
languageserver = ["lsp", "ropey", "percent-encoding", "url", "serde_json", "tokio", "workspace", "doc"]
capture-io = ["parking_lot"]
disable-io = []
fmt = []

[dependencies]
rune-macros = { version = "=0.12.3", path = "../rune-macros" }
Expand Down
8 changes: 5 additions & 3 deletions crates/rune/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ mod token;
pub(super) mod utils;
mod vis;

pub use self::attribute::Attribute;
pub use self::attribute::{AttrStyle, Attribute};
pub use self::block::Block;
pub use self::condition::Condition;
pub use self::expr::Expr;
Expand All @@ -179,7 +179,7 @@ pub use self::expr_match::{ExprMatch, ExprMatchBranch};
pub use self::expr_object::{ExprObject, FieldAssign, ObjectIdent, ObjectKey};
pub use self::expr_range::{ExprRange, ExprRangeLimits};
pub use self::expr_return::ExprReturn;
pub use self::expr_select::{ExprSelect, ExprSelectBranch};
pub use self::expr_select::{ExprSelect, ExprSelectBranch, ExprSelectPatBranch};
pub use self::expr_try::ExprTry;
pub use self::expr_tuple::ExprTuple;
pub use self::expr_unary::{ExprUnary, UnOp};
Expand Down Expand Up @@ -208,7 +208,9 @@ pub use self::lit_number::LitNumber;
pub use self::lit_str::LitStr;
pub use self::local::Local;
pub use self::macro_call::MacroCall;
pub use self::pat::{Pat, PatBinding, PatLit, PatObject, PatPath, PatTuple, PatVec};
pub use self::pat::{
Pat, PatBinding, PatIgnore, PatLit, PatObject, PatPath, PatRest, PatTuple, PatVec,
};
pub use self::path::{Path, PathKind, PathSegment, PathSegmentExpr};
pub use self::span::{ByteIndex, Span};
pub use self::spanned::{OptionSpanned, Spanned};
Expand Down
23 changes: 21 additions & 2 deletions crates/rune/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
mod benches;
mod check;
mod doc;
mod format;
mod languageserver;
mod loader;
mod run;
Expand Down Expand Up @@ -193,6 +194,8 @@ enum Command {
Bench(benches::Flags),
/// Run the designated script
Run(run::Flags),
/// Format the provided file
Fmt(format::Flags),
/// Run a language server.
LanguageServer(languageserver::Flags),
}
Expand All @@ -202,6 +205,7 @@ impl Command {
match self {
Command::Check(..) => {}
Command::Doc(..) => {}
Command::Fmt(..) => {}
Command::Test(..) => {
c.test = true;
}
Expand All @@ -219,6 +223,7 @@ impl Command {
match self {
Command::Check(..) => "Checking",
Command::Doc(..) => "Building documentation",
Command::Fmt(..) => "Formatting files",
Command::Test(..) => "Testing",
Command::Bench(..) => "Benchmarking",
Command::Run(..) => "Running",
Expand All @@ -230,6 +235,7 @@ impl Command {
match self {
Command::Check(args) => &args.shared,
Command::Doc(args) => &args.shared,
Command::Fmt(args) => &args.shared,
Command::Test(args) => &args.shared,
Command::Bench(args) => &args.shared,
Command::Run(args) => &args.shared,
Expand Down Expand Up @@ -388,8 +394,11 @@ impl Args {
options.test(true);
options.bytecode(false);
}
Command::Bench(_) | Command::Doc(..) | Command::Run(_) | Command::LanguageServer(_) => {
}
Command::Bench(_)
| Command::Doc(..)
| Command::Run(_)
| Command::LanguageServer(_)
| Command::Fmt(..) => {}
}

for option in &self.cmd.shared().compiler_options {
Expand Down Expand Up @@ -663,6 +672,16 @@ where
}
}
Command::Doc(flags) => return doc::run(io, entry, c, flags, options, entrys),
Command::Fmt(flags) => {
let mut paths = vec![];
for e in entrys {
for path in e.paths {
paths.push(path);
}
}

return format::run(io, &paths, flags);
}
Command::Test(flags) => {
for e in entrys {
for path in &e.paths {
Expand Down
101 changes: 101 additions & 0 deletions crates/rune/src/cli/format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use anyhow::{Context, Result};
use clap::Parser;
use std::io::Write;
use std::path::PathBuf;

use crate::cli::{ExitCode, Io, SharedFlags};
use crate::termcolor::WriteColor;
use crate::Source;

#[derive(Parser, Debug, Clone)]
pub(super) struct Flags {
/// Exit with a non-zero exit-code even for warnings
#[arg(long)]
warnings_are_errors: bool,

#[command(flatten)]
pub(super) shared: SharedFlags,

#[arg(long)]
check: bool,
}

pub(super) fn run(io: &mut Io<'_>, paths: &[PathBuf], flags: &Flags) -> Result<ExitCode> {
let mut red = crate::termcolor::ColorSpec::new();
red.set_fg(Some(crate::termcolor::Color::Red));

let mut green = crate::termcolor::ColorSpec::new();
green.set_fg(Some(crate::termcolor::Color::Green));

let mut yellow = crate::termcolor::ColorSpec::new();
yellow.set_fg(Some(crate::termcolor::Color::Yellow));

let mut succeeded = 0;
let mut failed = 0;
let mut unchanged = 0;

for path in paths {
let source =
Source::from_path(path).with_context(|| format!("reading file: {}", path.display()))?;

match crate::fmt::layout_source(&source) {
Ok(val) => {
if val == source.as_str() {
if !flags.check {
io.stdout.set_color(&yellow)?;
write!(io.stdout, "== ")?;
io.stdout.reset()?;
writeln!(io.stdout, "{}", path.display())?;
}

unchanged += 1;
} else {
succeeded += 1;
io.stdout.set_color(&green)?;
write!(io.stdout, "++ ")?;
io.stdout.reset()?;
writeln!(io.stdout, "{}", path.display())?;
if !flags.check {
std::fs::write(path, &val)?;
}
}
}
Err(err) => {
failed += 1;
io.stdout.set_color(&red)?;
write!(io.stdout, "!! ")?;
io.stdout.reset()?;
writeln!(io.stdout, "{}: {}", path.display(), err)?;
}
}
}

io.stdout.set_color(&yellow)?;
write!(io.stdout, "{}", unchanged)?;
io.stdout.reset()?;
writeln!(io.stdout, " unchanged")?;
io.stdout.set_color(&green)?;
write!(io.stdout, "{}", succeeded)?;
io.stdout.reset()?;
writeln!(io.stdout, " succeeded")?;
io.stdout.set_color(&red)?;
write!(io.stdout, "{}", failed)?;
io.stdout.reset()?;
writeln!(io.stdout, " failed")?;

if flags.check && succeeded > 0 {
io.stdout.set_color(&red)?;
write!(
io.stdout,
"Exiting with failure due to `--check` flag and unformatted files."
)?;
io.stdout.reset()?;
return Ok(ExitCode::Failure);
}

if failed > 0 {
return Ok(ExitCode::Failure);
}

Ok(ExitCode::Success)
}
34 changes: 34 additions & 0 deletions crates/rune/src/fmt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//! Helper to format Rune code.
mod comments;
mod error;
mod indent_writer;
mod printer;
mod whitespace;

use crate::ast;
use crate::parse::{Parse, Parser};
use crate::{Source, SourceId};

use self::error::FormattingError;
use self::printer::Printer;

/// Format the given contents.
pub fn layout_string(contents: String) -> Result<String, FormattingError> {
let s = Source::new("<memory>", contents);
layout_source(&s)
}

/// Format the given source.
pub fn layout_source(source: &Source) -> Result<String, FormattingError> {
let mut parser = Parser::new(source.as_str(), SourceId::new(0), true);

let ast = ast::File::parse(&mut parser)?;
let mut printer: Printer = Printer::new(source)?;

printer.visit_file(&ast)?;

let res = printer.commit().trim().to_owned() + "\n";

Ok(res)
}
118 changes: 118 additions & 0 deletions crates/rune/src/fmt/comments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//! Extract comments from source code.
#[cfg(test)]
mod tests;

use std::str::CharIndices;

use crate::ast::Span;

use super::error::FormattingError;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(super) enum CommentKind {
Line,
Block,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(super) struct Comment {
pub(super) kind: CommentKind,
pub(super) span: Span,
pub(super) on_new_line: bool,
}

pub(super) fn parse_comments(input: &str) -> Result<Vec<Comment>, FormattingError> {
let mut comments = Vec::new();

let mut chars = input.char_indices();

let mut in_string = false;
let mut in_char = false;
let mut in_template = false;
let mut on_new_line = true;

while let Some((idx, c)) = chars.next() {
match c {
'/' if !in_string && !in_char && !in_template => match chars.clone().next() {
Some((_, '/')) => {
let end = parse_line_comment(&mut chars);

if !input[idx..end].starts_with("///") && !input[idx..end].starts_with("//!") {
comments.push(Comment {
on_new_line,
kind: CommentKind::Line,
span: Span::new(idx, end),
});
}
}
Some((_, '*')) => {
let end = parse_block_comment(&mut chars).ok_or(FormattingError::Eof)?;

if !input[idx..end].starts_with("/**") && !input[idx..end].starts_with("/*!") {
comments.push(Comment {
on_new_line,
kind: CommentKind::Block,
span: Span::new(idx, end),
});
}
}
_ => {}
},
'"' => {
on_new_line = false;
if !in_char && !in_template {
in_string = !in_string;
}
}
'\'' => {
on_new_line = false;
if !in_string && !in_template {
in_char = !in_char;
}
}
'`' => {
on_new_line = false;
if !in_string && !in_char {
in_template = !in_template;
}
}
'\n' => {
on_new_line = true;
}
c if c.is_whitespace() => {}
_ => {
on_new_line = false;
}
}
}

Ok(comments)
}

fn parse_line_comment(chars: &mut CharIndices<'_>) -> usize {
let mut last_i = 0;

for (i, c) in chars.by_ref() {
match c {
'\n' => return i,
_ => {
last_i = i;
}
}
}

last_i + 1
}

fn parse_block_comment(chars: &mut CharIndices<'_>) -> Option<usize> {
while let Some((_, c)) = chars.next() {
if c == '*' {
if let Some((_, '/')) = chars.clone().next() {
return Some(chars.next()?.0);
}
}
}

None
}
Loading

0 comments on commit 802101c

Please sign in to comment.