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

Clarinet format #1609

Open
wants to merge 48 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
1578266
initial componet
Nov 4, 2024
c114c34
clarinet fmt boilerplate hooked up
Nov 5, 2024
02a7db6
refactor functions a bit
Nov 11, 2024
0cef41e
add basic formatter blocks
Nov 12, 2024
07bd0ec
fix build removing clarity-repl cargo flags
Nov 14, 2024
6d33c36
remove dep on clarity-repl
Nov 15, 2024
cb252fe
fix file path
Nov 18, 2024
29c4618
basic tests working
Nov 20, 2024
ec6d9e7
add comment handling and some max line length logic
Nov 21, 2024
a7d0adf
settings flags for max line and indentation
Nov 21, 2024
ffc88c9
remove max line length check for comments
Nov 21, 2024
b863acf
switch to use PSE and refactor the matchings
Nov 26, 2024
3c3dcbd
push settings into display_pse so we can attempt formatting Tuple
Dec 2, 2024
af8c35b
fix map/tuple formatting
Dec 2, 2024
b8e4451
add nested indentation
Dec 2, 2024
9017ce5
fix format_map
Dec 2, 2024
86016fd
fix match and let formatting
Dec 3, 2024
c6254c1
handle and/or
Dec 3, 2024
25005a2
golden test prettified
Dec 3, 2024
e2bb030
special casing on comments and previous checking
Dec 16, 2024
8bc8c15
update match formatting
Dec 17, 2024
057af87
cleanup spacing cruft
Dec 17, 2024
8b3ff87
fix boolean comments and a bit of pre/post newline logic
Dec 18, 2024
5736d15
add a couple golden files
Dec 18, 2024
dcc2f2c
fix traits spacing
Dec 18, 2024
feafe83
comments golden and fix simple lists
Dec 18, 2024
4d6d022
use manifest-path properly
Dec 19, 2024
972c49c
fix key value sugar
Dec 19, 2024
93b64a8
Merge branch 'main' into clarinet-format
Dec 19, 2024
3850034
use some peekable for inlining comments
Dec 20, 2024
76cf5e7
cleanup previous_expr unused code
Dec 20, 2024
83afc00
index-of case
Dec 20, 2024
22a8337
add metadata to golden test files
Dec 20, 2024
3596282
simplify tuple handling and add if-tests
Dec 23, 2024
ae2de4d
add clarity bitcoin example contract
Dec 29, 2024
fe4b18d
module out the helpers and ignored source code
tippenein Jan 1, 2025
3583f58
remove unused previous_expr
tippenein Jan 1, 2025
6ccbe92
use the ignored and helper mods
tippenein Jan 1, 2025
0f94882
fix the indentation nesting and if statements
Jan 15, 2025
20f3734
cleanup inner_content functions
Jan 22, 2025
c4b3be3
cleanup and change assumptions temporarily
Jan 23, 2025
15134c3
Merge branch 'main' into clarinet-format
Jan 27, 2025
eb81c54
fix list cons and boolean breaks with simplified line-break
Jan 29, 2025
3e6ebd9
make in-place OR dry-run required
Jan 31, 2025
34d3a93
fix up trailing parens for inner content
Feb 4, 2025
5cc4d88
--tabs flag
Feb 4, 2025
bd10613
update golden tests and fix match comments
Feb 4, 2025
285b7f3
Merge branch 'main' into clarinet-format
Feb 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"components/clarinet-cli",
"components/clarinet-deployments",
"components/clarinet-files",
"components/clarinet-format",
"components/clarinet-utils",
"components/clarinet-sdk-wasm",
"components/clarity-lsp",
Expand Down
1 change: 1 addition & 0 deletions components/clarinet-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ clarity_repl = { package = "clarity-repl", path = "../clarity-repl", features =
] }
clarinet-files = { path = "../clarinet-files", features = ["cli"] }
clarity-lsp = { path = "../clarity-lsp", features = ["cli"] }
clarinet-format = { path = "../clarinet-format" }
clarinet-deployments = { path = "../clarinet-deployments", features = ["cli"] }
hiro-system-kit = { path = "../hiro-system-kit" }
stacks-network = { path = "../stacks-network" }
Expand Down
105 changes: 104 additions & 1 deletion components/clarinet-cli/src/frontend/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use clarinet_files::{
get_manifest_location, FileLocation, NetworkManifest, ProjectManifest, ProjectManifestFile,
RequirementConfig,
};
use clarinet_format::formatter::{ClarityFormatter, Settings};
use clarity_repl::analysis::call_checker::ContractAnalysis;
use clarity_repl::clarity::vm::analysis::AnalysisDatabase;
use clarity_repl::clarity::vm::costs::LimitedCostTracker;
Expand All @@ -39,7 +40,7 @@ use clarity_repl::{analysis, repl, Terminal};
use stacks_network::{self, DevnetOrchestrator};
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::prelude::*;
use std::io::{self, prelude::*};
use std::{env, process};
use toml;

Expand Down Expand Up @@ -94,11 +95,35 @@ enum Command {
/// Get Clarity autocompletion and inline errors from your code editor (VSCode, vim, emacs, etc)
#[clap(name = "lsp", bin_name = "lsp")]
LSP,
/// Format clarity code files
#[clap(name = "format", aliases = &["fmt"], bin_name = "format")]
Formatter(Formatter),
/// Step by step debugging and breakpoints from your code editor (VSCode, vim, emacs, etc)
#[clap(name = "dap", bin_name = "dap")]
DAP,
}

#[derive(Parser, PartialEq, Clone, Debug)]
struct Formatter {
#[clap(long = "manifest-path", short = 'm')]
pub manifest_path: Option<String>,
/// If specified, format only this file
#[clap(long = "file", short = 'f')]
pub file: Option<String>,
#[clap(long = "max-line-length", short = 'l')]
pub max_line_length: Option<usize>,
#[clap(long = "indent", short = 'i', conflicts_with = "use_tabs")]
/// indentation size, e.g. 2
pub indentation: Option<usize>,
#[clap(long = "tabs", short = 't', conflicts_with = "indentation", action = clap::ArgAction::SetTrue)]
/// indentation size, e.g. 2
pub use_tabs: bool,
#[clap(long = "dry-run", conflicts_with = "in_place")]
pub dry_run: bool,
#[clap(long = "in-place", conflicts_with = "dry_run")]
pub in_place: bool,
}

#[derive(Subcommand, PartialEq, Clone, Debug)]
enum Devnet {
/// Generate package of all required devnet artifacts
Expand Down Expand Up @@ -1180,6 +1205,33 @@ pub fn main() {
process::exit(1);
}
},
Command::Formatter(cmd) => {
let sources = get_sources_to_format(cmd.manifest_path, cmd.file);
let mut settings = Settings::default();

if let Some(max_line_length) = cmd.max_line_length {
settings.max_line_length = max_line_length;
}

if let Some(indentation) = cmd.indentation {
settings.indentation = clarinet_format::formatter::Indentation::Space(indentation);
}
if cmd.use_tabs {
settings.indentation = clarinet_format::formatter::Indentation::Tab;
}
let mut formatter = ClarityFormatter::new(settings);

for (file_path, source) in &sources {
let output = formatter.format(source);
if cmd.in_place {
let _ = overwrite_formatted(file_path, output);
} else if cmd.dry_run {
println!("{}", output);
} else {
eprintln!("required flags: in-place or dry-run");
}
}
}
Command::Devnet(subcommand) => match subcommand {
Devnet::Package(cmd) => {
let manifest = load_manifest_or_exit(cmd.manifest_path);
Expand All @@ -1193,6 +1245,57 @@ pub fn main() {
};
}

fn overwrite_formatted(file_path: &String, output: String) -> io::Result<()> {
let mut file = fs::File::create(file_path)?;

file.write_all(output.as_bytes())?;
Ok(())
}

fn from_code_source(src: ClarityCodeSource) -> String {
match src {
ClarityCodeSource::ContractOnDisk(path_buf) => {
path_buf.as_path().to_str().unwrap().to_owned()
}
_ => panic!("invalid code source"), // TODO
}
}
// look for files at the default code path (./contracts/) if
// cmd.manifest_path is not specified OR if cmd.file is not specified
fn get_sources_from_manifest(manifest_path: Option<String>) -> Vec<String> {
let manifest = load_manifest_or_warn(manifest_path);
match manifest {
Some(manifest_path) => {
let contracts = manifest_path.contracts.values().cloned();
contracts.map(|c| from_code_source(c.code_source)).collect()
}
None => {
// TODO this should probably just panic or fail gracefully because
// if the manifest isn't specified or found at the default location
// we can't do much
vec![]
}
}
}
fn get_sources_to_format(
manifest_path: Option<String>,
file: Option<String>,
) -> Vec<(String, String)> {
let files: Vec<String> = match file {
Some(file_name) => vec![format!("{}", file_name)],
None => get_sources_from_manifest(manifest_path),
};
// Map each file to its source code
files
.into_iter()
.map(|file_path| {
let source = fs::read_to_string(&file_path)
.unwrap_or_else(|_| "Failed to read file".to_string());
(file_path, source)
})
.collect()
}

fn get_manifest_location_or_exit(path: Option<String>) -> FileLocation {
match get_manifest_location(path) {
Some(manifest_location) => manifest_location,
Expand Down
31 changes: 31 additions & 0 deletions components/clarinet-format/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "clarinet-format"
version = "0.1.0"
edition = "2021"

[dependencies]
# clarity-repl = { path = "../clarity-repl" }
clarity = { workspace = true}

[dev-dependencies]
pretty_assertions = "1.3"

[features]
default = ["cli"]
cli = [
"clarity/canonical",
"clarity/developer-mode",
"clarity/devtools",
"clarity/log",
]
wasm = [
"clarity/wasm",
"clarity/developer-mode",
"clarity/devtools",
]


[lib]
name = "clarinet_format"
path = "src/lib.rs"
crate-type = ["lib"]
25 changes: 25 additions & 0 deletions components/clarinet-format/src/formatter/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use clarity::vm::representations::{PreSymbolicExpression, PreSymbolicExpressionType};

/// trim but leaves newlines preserved
pub fn t(input: &str) -> &str {
let start = input
.find(|c: char| !c.is_whitespace() || c == '\n')
.unwrap_or(0);

let end = input
.rfind(|c: char| !c.is_whitespace() || c == '\n')
.map(|pos| pos + 1)
.unwrap_or(0);

&input[start..end]
}
/// REMOVE: just grabs the 1st and rest from a PSE
pub fn name_and_args(
exprs: &[PreSymbolicExpression],
) -> Option<(&PreSymbolicExpression, &[PreSymbolicExpression])> {
if exprs.len() >= 2 {
Some((&exprs[1], &exprs[2..]))
} else {
None // Return None if there aren't enough items
}
}
83 changes: 83 additions & 0 deletions components/clarinet-format/src/formatter/ignored.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use clarity::vm::representations::{PreSymbolicExpression, PreSymbolicExpressionType};

use crate::formatter::helpers::t;

pub fn ignored_exprs(expr: &PreSymbolicExpression) -> String {
let mut output = String::new();
let mut current_line = 1;

let start_line = expr.span().start_line as usize;
let end_line = expr.span().end_line as usize;
let start_col = expr.span().start_column as usize;
let end_col = expr.span().end_column as usize;

// Add newlines if needed to reach the start line
while current_line < start_line {
output.push('\n');
current_line += 1;
}

// Handle single-line expressions
if start_line == end_line {
// Add padding spaces before the expression
output.extend(std::iter::repeat(' ').take(start_col - 1));
output.push_str(&display_pse_unformatted(expr));
} else {
// Handle multi-line expressions
let expr_str = display_pse_unformatted(expr);
let lines: Vec<&str> = expr_str.lines().collect();

// Print first line with proper indentation
output.extend(std::iter::repeat(' ').take(start_col - 1));
output.push_str(lines[0]);
output.push('\n');
current_line += 1;

// Print middle lines
for line in &lines[1..lines.len() - 1] {
output.push_str(line);
output.push('\n');
current_line += 1;
}

// Print last line
if let Some(last_line) = lines.last() {
output.extend(std::iter::repeat(' ').take(end_col - last_line.len()));
output.push_str(last_line);
}
}

output
}

fn display_pse_unformatted(pse: &PreSymbolicExpression) -> String {
match pse.pre_expr {
PreSymbolicExpressionType::Atom(ref value) => t(value.as_str()).to_string(),
PreSymbolicExpressionType::AtomValue(ref value) => value.to_string(),
PreSymbolicExpressionType::List(ref items) => {
format!("{:?}", items)
}
PreSymbolicExpressionType::Tuple(ref items) => {
format!("{:?}", items)
}
PreSymbolicExpressionType::SugaredContractIdentifier(ref name) => {
format!(".{}", name)
}
PreSymbolicExpressionType::SugaredFieldIdentifier(ref contract, ref field) => {
format!(".{}.{}", contract, field)
}
PreSymbolicExpressionType::FieldIdentifier(ref trait_id) => {
format!("'{}", trait_id)
}
PreSymbolicExpressionType::TraitReference(ref name) => {
println!("trait ref: {}", name);
name.to_string()
}
PreSymbolicExpressionType::Comment(ref text) => {
format!(";; {}", t(text))
}
PreSymbolicExpressionType::Placeholder(ref placeholder) => {
placeholder.to_string() // Placeholder is for if parsing fails
}
}
}
Loading
Loading