Skip to content

Commit 8d545a6

Browse files
committed
feat: formatter poc
1 parent 6adfbe8 commit 8d545a6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+4210
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/result*
2+
/target/
3+
/tarpaulin*

Cargo.toml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[dependencies]
2+
clap = "3"
3+
rnix = "0.10"
4+
rowan = "0.15"
5+
walkdir = "2"
6+
7+
[dev-dependencies]
8+
indoc = "*"
9+
10+
[package]
11+
authors = ["Kevin Amado <[email protected]>"]
12+
description = "The uncompromising Nix formatter"
13+
edition = "2021"
14+
name = "alejandra"
15+
repository = "https://github.com/kamadorueda/alejandra"
16+
version = "0.1.0"

flake.nix

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
{
2+
inputs =
3+
{
4+
flakeUtils.url = "github:numtide/flake-utils";
5+
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
6+
};
7+
outputs =
8+
inputs:
9+
inputs.flakeUtils.lib.eachDefaultSystem
10+
(
11+
system:
12+
let
13+
nixpkgs = import inputs.nixpkgs { inherit system; };
14+
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
15+
in
16+
{
17+
checks =
18+
{
19+
defaultPackage = inputs.self.defaultPackage.${ system };
20+
inherit (inputs.self.packages.${ system }) nixpkgsFormatted;
21+
};
22+
defaultApp =
23+
{
24+
type = "app";
25+
program =
26+
"${ inputs.self.defaultPackage.${ system } }/bin/alejandra";
27+
};
28+
defaultPackage =
29+
nixpkgs.rustPlatform.buildRustPackage
30+
{
31+
pname = cargoToml.package.name;
32+
version = cargoToml.package.version;
33+
src = inputs.self.sourceInfo;
34+
cargoLock.lockFile = ./Cargo.lock;
35+
NIX_BUILD_CORES = 0;
36+
meta =
37+
{
38+
description = inputs.self.description;
39+
homepage = "https://github.com/kamadorueda/alejandra";
40+
license = nixpkgs.lib.licenses.mit;
41+
maintainers = [ nixpkgs.lib.maintainers.kamadorueda ];
42+
};
43+
};
44+
devShell =
45+
nixpkgs.mkShell
46+
{
47+
packages = [ nixpkgs.cargo-tarpaulin nixpkgs.rustup ];
48+
shellHook =
49+
''
50+
rustup toolchain install nightly
51+
'';
52+
};
53+
packages =
54+
{
55+
nixpkgsFormatted =
56+
nixpkgs.stdenv.mkDerivation
57+
{
58+
name = "nixpkgs-formatted";
59+
builder =
60+
builtins.toFile
61+
"builder.sh"
62+
''
63+
source $stdenv/setup
64+
65+
cp -rT $nixpkgs $out
66+
chmod -R +w $out
67+
68+
alejandra $out
69+
70+
git diff --no-index $nixpkgs $out > $diff || true
71+
'';
72+
buildInputs =
73+
[ inputs.self.defaultPackage.${ system } nixpkgs.git ];
74+
nixpkgs = inputs.nixpkgs.sourceInfo.outPath;
75+
NIX_BUILD_CORES = 0;
76+
outputs = [ "diff" "out" ];
77+
};
78+
};
79+
}
80+
);
81+
}

src/builder.rs

+253
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
#[derive(PartialEq)]
2+
pub enum Step {
3+
Comment(String),
4+
Dedent,
5+
Format(rnix::SyntaxElement),
6+
FormatWider(rnix::SyntaxElement),
7+
Indent,
8+
NewLine,
9+
Pad,
10+
Token(rnix::SyntaxKind, String),
11+
Whitespace,
12+
}
13+
14+
#[derive(Clone)]
15+
pub struct BuildCtx {
16+
pub config: crate::config::Config,
17+
pub force_wide: bool,
18+
pub indentation: usize,
19+
pub pos_new: crate::position::Position,
20+
pub pos_old: crate::position::Position,
21+
}
22+
23+
impl BuildCtx {
24+
pub fn new(
25+
config: crate::config::Config,
26+
force_wide: bool,
27+
pos_new: crate::position::Position,
28+
pos_old: crate::position::Position,
29+
) -> BuildCtx {
30+
BuildCtx { config, force_wide, indentation: 0, pos_new, pos_old }
31+
}
32+
}
33+
34+
pub fn build(
35+
config: &crate::config::Config,
36+
element: rnix::SyntaxElement,
37+
force_wide: bool,
38+
path: &str,
39+
) -> Option<rowan::GreenNode> {
40+
let mut builder = rowan::GreenNodeBuilder::new();
41+
let mut build_ctx = BuildCtx::new(
42+
config.clone(),
43+
force_wide,
44+
crate::position::Position::new(),
45+
crate::position::Position::new(),
46+
);
47+
48+
build_step(
49+
&mut builder,
50+
&mut build_ctx,
51+
path,
52+
&crate::builder::Step::Format(element),
53+
);
54+
55+
if build_ctx.force_wide && build_ctx.pos_new.line > 1 {
56+
None
57+
} else {
58+
Some(builder.finish())
59+
}
60+
}
61+
62+
fn build_step(
63+
builder: &mut rowan::GreenNodeBuilder,
64+
build_ctx: &mut BuildCtx,
65+
path: &str,
66+
step: &crate::builder::Step,
67+
) {
68+
if build_ctx.force_wide && build_ctx.pos_new.line > 1 {
69+
return;
70+
}
71+
72+
match step {
73+
crate::builder::Step::Comment(text) => {
74+
add_token(
75+
builder,
76+
build_ctx,
77+
rnix::SyntaxKind::TOKEN_COMMENT,
78+
text,
79+
);
80+
}
81+
crate::builder::Step::Dedent => {
82+
build_ctx.indentation -= 1;
83+
}
84+
crate::builder::Step::Format(element) => {
85+
format(builder, build_ctx, element, path);
86+
}
87+
crate::builder::Step::FormatWider(element) => {
88+
format_wider(builder, build_ctx, element, path);
89+
}
90+
crate::builder::Step::Indent => {
91+
build_ctx.indentation += 1;
92+
}
93+
crate::builder::Step::NewLine => {
94+
add_token(
95+
builder,
96+
build_ctx,
97+
rnix::SyntaxKind::TOKEN_WHITESPACE,
98+
"\n",
99+
);
100+
}
101+
crate::builder::Step::Pad => {
102+
if build_ctx.indentation > 0 {
103+
add_token(
104+
builder,
105+
build_ctx,
106+
rnix::SyntaxKind::TOKEN_COMMA,
107+
&format!("{0:<1$}", "", 2 * build_ctx.indentation),
108+
);
109+
}
110+
}
111+
crate::builder::Step::Token(kind, text) => {
112+
add_token(builder, build_ctx, *kind, &text);
113+
}
114+
crate::builder::Step::Whitespace => {
115+
add_token(
116+
builder,
117+
build_ctx,
118+
rnix::SyntaxKind::TOKEN_WHITESPACE,
119+
" ",
120+
);
121+
}
122+
}
123+
}
124+
125+
fn add_token(
126+
builder: &mut rowan::GreenNodeBuilder,
127+
build_ctx: &mut BuildCtx,
128+
kind: rnix::SyntaxKind,
129+
text: &str,
130+
) {
131+
builder.token(rowan::SyntaxKind(kind as u16), text);
132+
build_ctx.pos_new.update(text);
133+
}
134+
135+
fn format(
136+
builder: &mut rowan::GreenNodeBuilder,
137+
build_ctx: &mut BuildCtx,
138+
element: &rnix::SyntaxElement,
139+
path: &str,
140+
) {
141+
let kind = element.kind();
142+
143+
match element {
144+
rnix::SyntaxElement::Node(node) => {
145+
builder.start_node(rowan::SyntaxKind(kind as u16));
146+
147+
let rule = match kind {
148+
rnix::SyntaxKind::NODE_APPLY => crate::rules::apply::rule,
149+
rnix::SyntaxKind::NODE_ASSERT => crate::rules::assert::rule,
150+
rnix::SyntaxKind::NODE_ATTR_SET => crate::rules::attr_set::rule,
151+
rnix::SyntaxKind::NODE_BIN_OP => crate::rules::bin_op::rule,
152+
rnix::SyntaxKind::NODE_DYNAMIC => crate::rules::dynamic::rule,
153+
rnix::SyntaxKind::NODE_ERROR => {
154+
eprintln!("Warning: found an error node at: {}", path);
155+
crate::rules::default
156+
}
157+
rnix::SyntaxKind::NODE_IDENT => crate::rules::default,
158+
rnix::SyntaxKind::NODE_IF_ELSE => crate::rules::if_else::rule,
159+
rnix::SyntaxKind::NODE_INHERIT => crate::rules::inherit::rule,
160+
rnix::SyntaxKind::NODE_INHERIT_FROM => {
161+
crate::rules::inherit_from::rule
162+
}
163+
rnix::SyntaxKind::NODE_KEY => crate::rules::default,
164+
rnix::SyntaxKind::NODE_KEY_VALUE => {
165+
crate::rules::key_value::rule
166+
}
167+
rnix::SyntaxKind::NODE_LAMBDA => crate::rules::lambda::rule,
168+
rnix::SyntaxKind::NODE_LET_IN => crate::rules::let_in::rule,
169+
rnix::SyntaxKind::NODE_LIST => crate::rules::list::rule,
170+
rnix::SyntaxKind::NODE_LITERAL => crate::rules::default,
171+
rnix::SyntaxKind::NODE_LEGACY_LET => {
172+
eprintln!(
173+
"Warning: found a `legacy let` expression at: {}",
174+
path
175+
);
176+
crate::rules::default
177+
}
178+
rnix::SyntaxKind::NODE_OR_DEFAULT => {
179+
crate::rules::or_default::rule
180+
}
181+
rnix::SyntaxKind::NODE_PAREN => crate::rules::paren::rule,
182+
rnix::SyntaxKind::NODE_PAT_BIND => crate::rules::pat_bind::rule,
183+
rnix::SyntaxKind::NODE_PATTERN => crate::rules::pattern::rule,
184+
rnix::SyntaxKind::NODE_PAT_ENTRY => {
185+
crate::rules::pat_entry::rule
186+
}
187+
rnix::SyntaxKind::NODE_PATH_WITH_INTERPOL => {
188+
crate::rules::default
189+
}
190+
rnix::SyntaxKind::NODE_ROOT => crate::rules::root::rule,
191+
rnix::SyntaxKind::NODE_SELECT => crate::rules::select::rule,
192+
rnix::SyntaxKind::NODE_STRING => crate::rules::default,
193+
rnix::SyntaxKind::NODE_STRING_INTERPOL => {
194+
crate::rules::string_interpol::rule
195+
}
196+
rnix::SyntaxKind::NODE_UNARY_OP => crate::rules::default,
197+
rnix::SyntaxKind::NODE_WITH => crate::rules::with::rule,
198+
kind => {
199+
panic!("Missing rule for {:?} at: {}", kind, path);
200+
}
201+
};
202+
203+
for step in rule(build_ctx, node) {
204+
build_step(builder, build_ctx, path, &step);
205+
}
206+
207+
builder.finish_node();
208+
}
209+
rnix::SyntaxElement::Token(token) => {
210+
add_token(builder, build_ctx, kind, token.text());
211+
build_ctx.pos_old.update(token.text());
212+
}
213+
}
214+
}
215+
216+
fn format_wider(
217+
builder: &mut rowan::GreenNodeBuilder,
218+
build_ctx: &mut BuildCtx,
219+
element: &rnix::SyntaxElement,
220+
path: &str,
221+
) {
222+
match element {
223+
rnix::SyntaxElement::Node(node) => {
224+
let maybe_green_node = build(
225+
&build_ctx.config.with_layout(crate::config::Layout::Wide),
226+
node.clone().into(),
227+
true,
228+
path,
229+
);
230+
231+
let layout = match maybe_green_node {
232+
Some(finished) => {
233+
if build_ctx.pos_new.column
234+
+ finished.to_string().chars().count()
235+
> build_ctx.config.max_width()
236+
{
237+
crate::config::Layout::Tall
238+
} else {
239+
crate::config::Layout::Wide
240+
}
241+
}
242+
None => crate::config::Layout::Tall,
243+
};
244+
245+
let mut build_ctx_clone = build_ctx.clone();
246+
build_ctx_clone.config = build_ctx.config.with_layout(layout);
247+
format(builder, &mut build_ctx_clone, element, path);
248+
}
249+
rnix::SyntaxElement::Token(_) => {
250+
format(builder, build_ctx, element, path);
251+
}
252+
};
253+
}

0 commit comments

Comments
 (0)