Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cli formatter #81

Merged
merged 17 commits into from Jun 17, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -8,4 +8,4 @@ target
Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk
**/*.rs.bk
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
@@ -1,3 +1,3 @@
[workspace]

members = ["core_lang", "forc", "sway-server", "test_suite"]
members = ["core_lang", "forc", "sway-server", "test_suite", "formatter"]
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
11 changes: 6 additions & 5 deletions forc/Cargo.toml
Expand Up @@ -9,17 +9,18 @@ version = "0.1.0"

[dependencies]
core_lang = {path = "../core_lang"}
fuel-tx = { git = "ssh://git@github.com/FuelLabs/fuel-tx.git" }
fuel-vm = { git = "ssh://git@github.com/FuelLabs/fuel-vm.git" }
formatter = {path = "../formatter"}
fuel-tx = {git = "ssh://git@github.com/FuelLabs/fuel-tx.git"}
fuel-vm = {git = "ssh://git@github.com/FuelLabs/fuel-vm.git"}
hex = "0.4.3"
line-col = "0.2"
#pest = "2.1"
pest = { git = "https://github.com/sezna/pest.git" , rev = "8aa58791f759daf4caee26e8560e862df5a6afb7" }
fuel-asm = {git = "ssh://git@github.com/FuelLabs/fuel-asm.git"}
pest = {git = "https://github.com/sezna/pest.git", rev = "8aa58791f759daf4caee26e8560e862df5a6afb7"}
serde = {version = "1.0", features = ["derive"]}
source-span = "2.4"
structopt = "0.3"
term-table = "1.3"
termcolor = "1.1"
toml = "0.5"
whoami = "1.1"
fuel-asm = { git = "ssh://git@github.com/FuelLabs/fuel-asm.git" }
term-table = "1.3"
16 changes: 16 additions & 0 deletions forc/src/cli/commands/format.rs
@@ -0,0 +1,16 @@
use structopt::{self, StructOpt};

use crate::ops::forc_fmt;

#[derive(Debug, StructOpt)]
pub struct Command {
#[structopt(short, long)]
pub check: bool,
}

pub(crate) fn exec(command: Command) -> Result<(), String> {
match forc_fmt::format(command) {
Err(e) => Err(e.message),
_ => Ok(()),
}
}
1 change: 1 addition & 0 deletions forc/src/cli/commands/mod.rs
Expand Up @@ -3,6 +3,7 @@ pub mod benchmark;
pub mod build;
pub mod coverage;
pub mod deploy;
pub mod format;
pub mod init;
pub mod mvprun;
pub mod parse_bytecode;
Expand Down
9 changes: 7 additions & 2 deletions forc/src/cli/mod.rs
Expand Up @@ -2,15 +2,16 @@ use structopt::StructOpt;

mod commands;
use self::commands::{
analysis, benchmark, build, coverage, deploy, init, mvprun, parse_bytecode, publish, serve,
test,
analysis, benchmark, build, coverage, deploy, format, init, mvprun, parse_bytecode, publish,
serve, test,
};

use analysis::Command as AnalysisCommand;
use benchmark::Command as BenchmarkCommand;
pub use build::Command as BuildCommand;
use coverage::Command as CoverageCommand;
use deploy::Command as DeployCommand;
pub use format::Command as FormatCommand;
use init::Command as InitCommand;
use mvprun::Command as MvprunCommand;
use parse_bytecode::Command as ParseBytecodeCommand;
Expand All @@ -32,6 +33,9 @@ enum Forc {
Benchmark(BenchmarkCommand),
Build(BuildCommand),
Coverage(CoverageCommand),

#[structopt(name = "fmt")]
Format(FormatCommand),
Deploy(DeployCommand),
Init(InitCommand),
Mvprun(MvprunCommand),
Expand All @@ -48,6 +52,7 @@ pub(crate) fn run_cli() -> Result<(), String> {
Forc::Benchmark(command) => benchmark::exec(command),
Forc::Build(command) => build::exec(command),
Forc::Coverage(command) => coverage::exec(command),
Forc::Format(command) => format::exec(command),
Forc::Deploy(command) => deploy::exec(command),
Forc::Init(command) => init::exec(command),
Forc::Mvprun(command) => mvprun::exec(command),
Expand Down
19 changes: 1 addition & 18 deletions forc/src/ops/forc_build.rs
@@ -1,4 +1,4 @@
use crate::cli::BuildCommand;
use crate::{cli::BuildCommand, utils::helpers::find_manifest_dir};
use line_col::LineColLookup;
use source_span::{
fmt::{Color, Formatter, Style},
Expand Down Expand Up @@ -74,23 +74,6 @@ pub fn build(command: BuildCommand) -> Result<Vec<u8>, String> {
Ok(main)
}

/// Continually go up in the file tree until a manifest (Forc.toml) is found.
fn find_manifest_dir(starter_path: &PathBuf) -> Option<PathBuf> {
let mut path = fs::canonicalize(starter_path.clone()).ok()?;
let empty_path = PathBuf::from("/");
while path != empty_path {
path.push(crate::utils::constants::MANIFEST_FILE_NAME);
if path.exists() {
path.pop();
return Some(path);
} else {
path.pop();
path.pop();
}
}
None
}

/// Takes a dependency and returns a namespace of exported things from that dependency
/// trait implementations are included as well
fn compile_dependency_lib<'source, 'manifest>(
Expand Down
123 changes: 123 additions & 0 deletions forc/src/ops/forc_fmt.rs
@@ -0,0 +1,123 @@
use crate::{
cli::FormatCommand,
utils::{constants::SWAY_EXTENSION, helpers::find_manifest_dir},
};
use formatter::get_formatted_data;
use std::{
ffi::OsStr,
fmt, fs, io,
path::{Path, PathBuf},
};

pub fn format(command: FormatCommand) -> Result<(), FormatError> {
let curr_dir = std::env::current_dir()?;

match find_manifest_dir(&curr_dir) {
Some(path) => {
let files = get_sway_files(path)?;
let mut errors_exist = false;

for file in files {
if let Ok(file_content) = fs::read_to_string(&file) {
match core_lang::parse(&file_content) {
core_lang::CompileResult::Ok {
value: _,
warnings: _,
errors: _,
} => {
if !command.check {
format_sway_file(&file_content, &file)?;
}
}
core_lang::CompileResult::Err { warnings, errors } => {
errors_exist = true;
if command.check {
eprintln!("{:?}", file);

for warning in warnings {
eprintln!("{}", warning.to_friendly_warning_string());
}

for error in errors {
eprintln!("{}", error.to_friendly_error_string());
}
}
}
}
}
}

if command.check {
if errors_exist {
std::process::exit(1);
} else {
std::process::exit(0);
}
}
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved

Ok(())
}
_ => Err("Manifest file does not exist".into()),
}
}

fn get_sway_files(path: PathBuf) -> Result<Vec<PathBuf>, FormatError> {
let mut files = vec![];
let mut dir_entries = vec![path];

while let Some(entry) = dir_entries.pop() {
for inner_entry in fs::read_dir(entry)? {
if let Ok(entry) = inner_entry {
let path = entry.path();
if path.is_dir() {
dir_entries.push(path);
} else {
if is_sway_file(&path) {
files.push(path)
}
}
}
}
}

Ok(files)
}

fn format_sway_file(file_content: &str, file: &PathBuf) -> Result<(), FormatError> {
// todo: get tab_size from Manifest file
let (_, formatted_content) = get_formatted_data(file_content, 4);
fs::write(file, formatted_content)?;

Ok(())
}

fn is_sway_file(file: &Path) -> bool {
let res = file.extension();
Some(OsStr::new(SWAY_EXTENSION)) == res
}

pub struct FormatError {
pub message: String,
}

impl fmt::Display for FormatError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}", self)
}
}

impl From<&str> for FormatError {
fn from(s: &str) -> Self {
FormatError {
message: s.to_string(),
}
}
}

impl From<io::Error> for FormatError {
fn from(e: io::Error) -> Self {
FormatError {
message: e.to_string(),
}
}
}
1 change: 1 addition & 0 deletions forc/src/ops/mod.rs
@@ -1,2 +1,3 @@
pub mod forc_build;
pub mod forc_fmt;
pub mod forc_init;
1 change: 1 addition & 0 deletions forc/src/utils/constants.rs
@@ -1 +1,2 @@
pub const MANIFEST_FILE_NAME: &'static str = "Forc.toml";
pub const SWAY_EXTENSION: &'static str = "sw";
18 changes: 18 additions & 0 deletions forc/src/utils/helpers.rs
@@ -0,0 +1,18 @@
use std::path::PathBuf;

// Continually go up in the file tree until a manifest (Forc.toml) is found.
pub fn find_manifest_dir(starter_path: &PathBuf) -> Option<PathBuf> {
let mut path = std::fs::canonicalize(starter_path.clone()).ok()?;
let empty_path = PathBuf::from("/");
while path != empty_path {
path.push(crate::utils::constants::MANIFEST_FILE_NAME);
if path.exists() {
path.pop();
return Some(path);
} else {
path.pop();
path.pop();
}
}
None
}
1 change: 1 addition & 0 deletions forc/src/utils/mod.rs
@@ -1,3 +1,4 @@
pub mod constants;
pub mod defaults;
pub mod helpers;
pub mod manifest;
7 changes: 7 additions & 0 deletions formatter/Cargo.toml
@@ -0,0 +1,7 @@
[package]
authors = ["leviathan88 <elvisdedic@outlook.com>"]
edition = "2018"
name = "formatter"
version = "0.1.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand Up @@ -3,8 +3,6 @@ use std::{
str::Chars,
};

use lspower::lsp::{Position, Range, TextEdit};

use super::parse_helpers::{clean_all_incoming_whitespace, is_comment};
use super::{
code_line::CodeLine,
Expand All @@ -30,27 +28,17 @@ impl CodeBuilder {
}
}

pub fn to_text_edit(&mut self, text_lines_count: usize) -> Vec<TextEdit> {
let line_end = std::cmp::max(self.edits.len(), text_lines_count) as u32;

pub fn get_final_edits(mut self) -> (usize, String) {
// add new line at the end if needed
if let Some(code_line) = self.edits.last() {
if !code_line.is_empty() {
self.edits.push(CodeLine::empty_line())
}
}

let main_edit = TextEdit {
range: Range::new(Position::new(0, 0), Position::new(line_end as u32, 0)),
new_text: self
.edits
.iter()
.map(|code_line| code_line.text.clone())
.collect::<Vec<String>>()
.join("\n"),
};
let num_of_lines = self.edits.len();

vec![main_edit]
(num_of_lines, self.to_string())
}

/// formats line of code and adds it to Vec<CodeLine>
Expand Down Expand Up @@ -131,6 +119,14 @@ impl CodeBuilder {
self.add_line(code_line);
}

fn to_string(&mut self) -> String {
self.edits
.iter()
.map(|code_line| code_line.text.clone())
.collect::<Vec<String>>()
.join("\n")
}

/// if previous line is not completed get it, otherwise start a new one
fn get_unfinished_code_line_or_new(&mut self) -> CodeLine {
match self.edits.last() {
Expand Down
14 changes: 14 additions & 0 deletions formatter/src/formatter.rs
@@ -0,0 +1,14 @@
use super::code_builder::CodeBuilder;

/// returns number of lines and formatted text
pub fn get_formatted_data(file: &str, tab_size: u32) -> (usize, String) {
let mut code_builder = CodeBuilder::new(tab_size);
let lines: Vec<&str> = file.split("\n").collect();

// todo: handle lengthy lines of code
for line in lines {
code_builder.format_and_add(line);
}

code_builder.get_final_edits()
}
6 changes: 6 additions & 0 deletions formatter/src/lib.rs
@@ -0,0 +1,6 @@
mod code_builder;
mod code_line;
mod formatter;
mod parse_helpers;

pub use crate::formatter::get_formatted_data;