-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: [experimental] add sorting for Rust derive traits (#23)
- Loading branch information
1 parent
8d47bea
commit e1a97f6
Showing
19 changed files
with
784 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Changelog | ||
All notable changes to this project will be documented in this file. | ||
|
||
This changelog follows the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) | ||
format and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
||
## [Unreleased] | ||
|
||
### Added | ||
- Generic keyword sorting functionality | ||
- Support for Bazel files | ||
- Support for `Cargo.toml` files | ||
|
||
### Experimental | ||
- Support for `.gitignore` files | ||
- Support for `CODEOWNERS` files | ||
- Sorting of Rust `#[derive(...)]` traits |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
#!/usr/bin/env python3 | ||
import os | ||
import re | ||
from collections import defaultdict | ||
|
||
def count_traits(directory): | ||
# Regex to match the derive traits | ||
derive_pattern = re.compile(r'#\[derive\((.*?)\)\]') | ||
|
||
trait_count = defaultdict(int) | ||
|
||
# Walk through the directory | ||
for root, _, files in os.walk(directory): | ||
for file in files: | ||
if file.endswith('.rs'): | ||
file_path = os.path.join(root, file) | ||
with open(file_path, 'r') as f: | ||
content = f.read() | ||
|
||
# Find all derive traits in the file | ||
derives = derive_pattern.findall(content) | ||
for derive in derives: | ||
traits = [trait.strip() for trait in derive.split(',')] | ||
for trait in traits: | ||
trait_count[trait] += 1 | ||
|
||
# Sort the traits by frequency in descending order | ||
sorted_traits = sorted(trait_count.items(), key=lambda x: x[1], reverse=True) | ||
|
||
# Print the sorted results | ||
for trait, count in sorted_traits: | ||
print(f'{trait}: {count}') | ||
|
||
# Example usage | ||
if __name__ == "__main__": | ||
directory = '.' | ||
count_traits(directory) | ||
|
||
''' | ||
Clone: 4572 | ||
PartialEq: 3753 | ||
Debug: 3527 | ||
Eq: 2225 | ||
Deserialize: 1594 | ||
Serialize: 1255 | ||
::prost::Message: 1185 | ||
Copy: 750 | ||
candid::Deserialize: 695 | ||
serde::Serialize: 694 | ||
candid::CandidType: 691 | ||
CandidType: 622 | ||
Hash: 549 | ||
comparable::Comparable: 546 | ||
Default: 376 | ||
PartialOrd: 312 | ||
Ord: 305 | ||
serde::Deserialize: 200 | ||
Parser: 154 | ||
::prost::Oneof: 132 | ||
Error: 60 | ||
ProposalMetadata: 51 | ||
thiserror::Error: 47 | ||
::prost::Enumeration: 43 | ||
EnumIter: 41 | ||
ZeroizeOnDrop: 41 | ||
Zeroize: 40 | ||
Decode: 40 | ||
Encode: 40 | ||
JsonSchema: 38 | ||
ValidateEq: 32 | ||
Args: 18 | ||
Template: 12 | ||
strum_macros::Display: 10 | ||
IntoStaticStr: 8 | ||
Arbitrary: 8 | ||
Display: 7 | ||
strum_macros::EnumIter: 7 | ||
EnumString: 6 | ||
Subcommand: 6 | ||
ExhaustiveSet: 6 | ||
ValueEnum: 5 | ||
std::hash::Hash: 5 | ||
AsRefStr: 5 | ||
std::fmt::Debug: 4 | ||
EnumCount: 4 | ||
Message: 4 | ||
ArgEnum: 4 | ||
VariantNames: 2 | ||
Request: 1 | ||
clap::ArgEnum: 1 | ||
EnumMessage: 1 | ||
Educe: 1 | ||
FromRepr: 1 | ||
: 1 | ||
strum_macros::EnumString: 1 | ||
clap::Args: 1 | ||
clap::Subcommand: 1 | ||
''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ pub mod bazel; | |
pub mod cargo_toml; | ||
pub mod generic; | ||
pub mod gitignore; | ||
pub mod rust_derive; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
use crate::Strategy; | ||
use once_cell::sync::Lazy; | ||
use regex::Regex; | ||
use std::io; | ||
|
||
use crate::is_ignore_block; | ||
|
||
static RE_DERIVE_BEGIN: Lazy<Regex> = Lazy::new(re_derive_begin); | ||
static RE_DERIVE_END: Lazy<Regex> = Lazy::new(re_derive_end); | ||
|
||
// These values count the number of characters and an extra '\n'. | ||
const STAY_ONE_LINE_LEN: usize = 97; | ||
const BREAK_INTO_MANY_LINES_LEN: usize = 101; | ||
|
||
pub(crate) fn process(lines: Vec<String>, strategy: Strategy) -> io::Result<Vec<String>> { | ||
let mut output_lines: Vec<String> = Vec::new(); | ||
let mut block = Vec::new(); | ||
let mut is_sorting_block = false; | ||
let mut is_ignore_block_prev_line = false; | ||
|
||
for line in lines { | ||
let mut is_derive_begin = false; | ||
if RE_DERIVE_BEGIN.is_match(&line) { | ||
if let Some(prev_line) = output_lines.last() { | ||
is_ignore_block_prev_line = is_ignore_block(&[prev_line.clone()]); | ||
} | ||
is_derive_begin = true; | ||
is_sorting_block = true; | ||
block.push(line.clone()); | ||
} | ||
if is_sorting_block && RE_DERIVE_END.is_match(&line) { | ||
if !is_derive_begin { | ||
block.push(line.clone()); | ||
} | ||
block = sort(block, is_ignore_block_prev_line, strategy); | ||
is_ignore_block_prev_line = false; | ||
is_sorting_block = false; | ||
output_lines.append(&mut block); | ||
} else if is_sorting_block { | ||
if !is_derive_begin { | ||
block.push(line); | ||
} | ||
} else { | ||
output_lines.push(line); | ||
} | ||
} | ||
|
||
if is_sorting_block { | ||
block = sort(block, is_ignore_block_prev_line, strategy); | ||
output_lines.append(&mut block); | ||
} | ||
|
||
Ok(output_lines) | ||
} | ||
|
||
fn sort(block: Vec<String>, is_ignore_block_prev_line: bool, strategy: Strategy) -> Vec<String> { | ||
if is_ignore_block_prev_line || is_ignore_block(&block) { | ||
return block; | ||
} | ||
let line: String = block | ||
.iter() | ||
.map(|line| line.trim_end_matches('\n')) | ||
.collect(); | ||
let line = format!("{}\n", line); | ||
let trimmed_line = line.trim(); | ||
|
||
let mut result = Vec::new(); | ||
// Check if the line contains a #[derive(...)] statement | ||
if let Some(derive_start) = trimmed_line.find("#[derive(") { | ||
if let Some(derive_end) = trimmed_line[derive_start..].find(")]") { | ||
let derive_content = &trimmed_line[derive_start + 9..derive_start + derive_end]; | ||
let mut traits: Vec<&str> = derive_content.split(',').map(str::trim).collect(); | ||
|
||
match strategy { | ||
Strategy::RustDeriveAlphabetical => { | ||
traits.sort_unstable(); | ||
} | ||
Strategy::RustDeriveCanonical => { | ||
traits = canonical_sort(traits); | ||
} | ||
_ => { | ||
return block; | ||
} | ||
} | ||
traits.retain(|t| !t.is_empty()); | ||
let sorted_traits = traits.join(", "); | ||
let new_derive = format!("#[derive({})]", sorted_traits); | ||
|
||
// Reconstruct the line with preserved whitespaces | ||
let prefix_whitespace = &line[..line.find(trimmed_line).unwrap_or(0)]; | ||
let suffix_whitespace = | ||
&line[line.rfind(trimmed_line).unwrap_or(line.len()) + trimmed_line.len()..]; | ||
|
||
let new_line = format!("{}{}{}", prefix_whitespace, new_derive, suffix_whitespace); | ||
if new_line.len() <= STAY_ONE_LINE_LEN { | ||
result.push(new_line); | ||
} else { | ||
let mid_line = format!("{} {},", prefix_whitespace, sorted_traits); | ||
if mid_line.len() <= BREAK_INTO_MANY_LINES_LEN { | ||
result.push(format!("{}#[derive(\n", prefix_whitespace)); | ||
result.push(format!("{}\n", mid_line)); | ||
result.push(format!("{})]\n", prefix_whitespace)); | ||
} else { | ||
result.push(format!("{}#[derive(\n", prefix_whitespace)); | ||
for x in traits { | ||
result.push(format!("{} {},\n", prefix_whitespace, x)); | ||
} | ||
result.push(format!("{})]\n", prefix_whitespace)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
result | ||
} | ||
|
||
fn canonical_sort(traits: Vec<&str>) -> Vec<&str> { | ||
// Define the canonical order of traits | ||
let canonical_order = [ | ||
"Copy", | ||
"Clone", | ||
"Eq", | ||
"PartialEq", | ||
"Ord", | ||
"PartialOrd", | ||
"Hash", | ||
"Debug", | ||
"Display", | ||
"Default", | ||
]; | ||
|
||
// Create a mapping from trait to its canonical index | ||
let canonical_index: std::collections::HashMap<_, _> = canonical_order | ||
.iter() | ||
.enumerate() | ||
.map(|(i, &trait_name)| (trait_name, i)) | ||
.collect(); | ||
|
||
// Sort traits by canonical index, and by trait name if indices are the same | ||
let mut sorted_traits = traits; | ||
sorted_traits.sort_by(|a, b| { | ||
let index_a = canonical_index.get(a).unwrap_or(&usize::MAX); | ||
let index_b = canonical_index.get(b).unwrap_or(&usize::MAX); | ||
(index_a, a).cmp(&(index_b, b)) | ||
}); | ||
|
||
sorted_traits | ||
} | ||
|
||
fn re_derive_begin() -> Regex { | ||
Regex::new(r"^\s*#\[derive\(").expect("Failed to build regex for rust derive begin") | ||
} | ||
|
||
fn re_derive_end() -> Regex { | ||
Regex::new(r"\)\]\s*$").expect("Failed to build regex for rust derive end") | ||
} |
Oops, something went wrong.