Skip to content

Commit 71484d3

Browse files
committed
feat: initial implementation
1 parent 306cbb0 commit 71484d3

25 files changed

+731
-67
lines changed

.github/workflows/ci.yml

+4-3
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@ jobs:
7474
if: matrix.config.kind == 'test_release' && startsWith(github.ref, 'refs/tags/')
7575
run: cargo login ${{ secrets.CRATES_TOKEN }}
7676

77-
- name: Cargo publish
78-
if: matrix.config.kind == 'test_release' && startsWith(github.ref, 'refs/tags/')
79-
run: cargo publish
77+
# enable once the parser is published
78+
# - name: Cargo publish
79+
# if: matrix.config.kind == 'test_release' && startsWith(github.ref, 'refs/tags/')
80+
# run: cargo publish
8081

8182
# GITHUB RELEASE
8283
- name: Pre-release

Cargo.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "dprint-plugin-dockerfile"
3-
version = "0.0.1"
3+
version = "0.1.0"
44
authors = ["David Sherret <dsherret@gmail.com>"]
55
edition = "2018"
66
homepage = "https://github.com/dprint/dprint-plugin-dockerfile"
@@ -25,8 +25,8 @@ wasm = ["serde_json", "dprint-core/wasm"]
2525
tracing = ["dprint-core/tracing"]
2626

2727
[dependencies]
28-
dockerfile-parser = "0.7.1"
29-
dprint-core = { version = "0.44.0", features = ["formatting"] }
28+
dockerfile-parser = { git = "https://github.com/dsherret/dockerfile-parser-rs", branch = "span-for-nodes" }
29+
dprint-core = { version = "0.46.4", features = ["formatting"] }
3030
serde = { version = "1.0.88", features = ["derive"] }
3131
serde_json = { version = "1.0", optional = true }
3232

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
# dprint-plugin-dockerfile
22

3-
WIP - Future home of a dockerfile formatter.
3+
Dockerfile code formatter plugin for [dprint](https://dprint.dev).
4+
5+
This uses [dockerfile-parser-rs](https://github.com/dsherret/dockerfile-parser-rs) to parse a dockerfile.
6+
7+
See [releases](https://github.com/dprint/dprint-plugin-dockerfile/releases/) for usage.

deployment/schema.json

-20
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,6 @@
33
"$id": "https://plugins.dprint.dev/schemas/dockerfile-0.0.0.json",
44
"type": "object",
55
"definitions": {
6-
"useTabs": {
7-
"description": "Whether to use tabs (true) or spaces (false).",
8-
"type": "boolean",
9-
"default": false,
10-
"oneOf": [{
11-
"const": true,
12-
"description": ""
13-
}, {
14-
"const": false,
15-
"description": ""
16-
}]
17-
},
186
"newLineKind": {
197
"description": "The kind of newline to use.",
208
"type": "string",
@@ -44,14 +32,6 @@
4432
"default": 120,
4533
"type": "number"
4634
},
47-
"indentWidth": {
48-
"description": "The number of characters for an indent.",
49-
"default": 2,
50-
"type": "number"
51-
},
52-
"useTabs": {
53-
"$ref": "#/definitions/useTabs"
54-
},
5535
"newLineKind": {
5636
"$ref": "#/definitions/newLineKind"
5737
}

src/configuration/builder.rs

+6-25
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl ConfigurationBuilder {
3333
if let Some(global_config) = &self.global_config {
3434
resolve_config(self.config.clone(), global_config).config
3535
} else {
36-
let global_config = resolve_global_config(HashMap::new()).config;
36+
let global_config = resolve_global_config(HashMap::new(), &Default::default()).config;
3737
resolve_config(self.config.clone(), &global_config).config
3838
}
3939
}
@@ -50,20 +50,6 @@ impl ConfigurationBuilder {
5050
self.insert("lineWidth", (value as i32).into())
5151
}
5252

53-
/// Whether to use tabs (true) or spaces (false).
54-
///
55-
/// Default: `false`
56-
pub fn use_tabs(&mut self, value: bool) -> &mut Self {
57-
self.insert("useTabs", value.into())
58-
}
59-
60-
/// The number of columns for an indent.
61-
///
62-
/// Default: `2`
63-
pub fn indent_width(&mut self, value: u8) -> &mut Self {
64-
self.insert("indentWidth", (value as i32).into())
65-
}
66-
6753
/// The kind of newline to use.
6854
/// Default: `NewLineKind::LineFeed`
6955
pub fn new_line_kind(&mut self, value: NewLineKind) -> &mut Self {
@@ -91,15 +77,11 @@ mod tests {
9177
#[test]
9278
fn check_all_values_set() {
9379
let mut config = ConfigurationBuilder::new();
94-
config
95-
.new_line_kind(NewLineKind::CarriageReturnLineFeed)
96-
.line_width(90)
97-
.use_tabs(true)
98-
.indent_width(4);
80+
config.new_line_kind(NewLineKind::CarriageReturnLineFeed).line_width(90);
9981

10082
let inner_config = config.get_inner_config();
101-
assert_eq!(inner_config.len(), 4);
102-
let diagnostics = resolve_config(inner_config, &resolve_global_config(HashMap::new()).config).diagnostics;
83+
assert_eq!(inner_config.len(), 2);
84+
let diagnostics = resolve_config(inner_config, &resolve_global_config(HashMap::new(), &Default::default()).config).diagnostics;
10385
assert_eq!(diagnostics.len(), 0);
10486
}
10587

@@ -109,7 +91,7 @@ mod tests {
10991
global_config.insert(String::from("lineWidth"), 90.into());
11092
global_config.insert(String::from("newLineKind"), "crlf".into());
11193
global_config.insert(String::from("useTabs"), true.into());
112-
let global_config = resolve_global_config(global_config).config;
94+
let global_config = resolve_global_config(global_config, &Default::default()).config;
11395
let mut config_builder = ConfigurationBuilder::new();
11496
let config = config_builder.global_config(global_config).build();
11597
assert_eq!(config.line_width, 90);
@@ -118,10 +100,9 @@ mod tests {
118100

119101
#[test]
120102
fn use_defaults_when_global_not_set() {
121-
let global_config = resolve_global_config(HashMap::new()).config;
103+
let global_config = resolve_global_config(HashMap::new(), &Default::default()).config;
122104
let mut config_builder = ConfigurationBuilder::new();
123105
let config = config_builder.global_config(global_config).build();
124-
assert_eq!(config.indent_width, 2); // this is different
125106
assert_eq!(config.new_line_kind == NewLineKind::LineFeed, true);
126107
}
127108
}

src/configuration/configuration.rs

-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,5 @@ use serde::{Deserialize, Serialize};
55
#[serde(rename_all = "camelCase")]
66
pub struct Configuration {
77
pub line_width: u32,
8-
pub use_tabs: bool,
9-
pub indent_width: u8,
108
pub new_line_kind: NewLineKind,
119
}

src/configuration/resolve_config.rs

+3-10
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ use dprint_core::configuration::*;
77
///
88
/// ```
99
/// use std::collections::HashMap;
10-
/// use dprint_core::configuration::{resolve_global_config};
11-
/// use dprint_plugin_dockerfile::configuration::{resolve_config};
10+
/// use dprint_core::configuration::resolve_global_config;
11+
/// use dprint_plugin_dockerfile::configuration::resolve_config;
1212
///
1313
/// let config_map = HashMap::new(); // get a collection of key value pairs from somewhere
14-
/// let global_config_result = resolve_global_config(config_map);
14+
/// let global_config_result = resolve_global_config(config_map, &Default::default());
1515
///
1616
/// // check global_config_result.diagnostics here...
1717
///
@@ -34,13 +34,6 @@ pub fn resolve_config(config: ConfigKeyMap, global_config: &GlobalConfiguration)
3434
global_config.line_width.unwrap_or(DEFAULT_GLOBAL_CONFIGURATION.line_width),
3535
&mut diagnostics,
3636
),
37-
use_tabs: get_value(
38-
&mut config,
39-
"useTabs",
40-
global_config.use_tabs.unwrap_or(DEFAULT_GLOBAL_CONFIGURATION.use_tabs),
41-
&mut diagnostics,
42-
),
43-
indent_width: get_value(&mut config, "indentWidth", global_config.indent_width.unwrap_or(2), &mut diagnostics),
4437
new_line_kind: get_value(
4538
&mut config,
4639
"newLineKind",

src/format_text.rs

+31-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,37 @@
1+
use dockerfile_parser::Dockerfile;
2+
use dprint_core::configuration::resolve_new_line_kind;
3+
use dprint_core::formatting::PrintOptions;
14
use dprint_core::types::ErrBox;
25
use std::path::Path;
36

47
use crate::configuration::Configuration;
8+
use crate::parser::parse_items;
59

6-
pub fn format_text(_file_path: &Path, text: &str, _config: &Configuration) -> Result<String, ErrBox> {
7-
// todo :)
8-
Ok(text.to_string())
10+
pub fn format_text(_file_path: &Path, text: &str, config: &Configuration) -> Result<String, ErrBox> {
11+
let node = parse_node(text)?;
12+
13+
Ok(dprint_core::formatting::format(
14+
|| parse_items(&node, text, config),
15+
config_to_print_options(text, config),
16+
))
17+
}
18+
19+
#[cfg(feature = "tracing")]
20+
pub fn trace_file(_file_path: &Path, text: &str, config: &Configuration) -> dprint_core::formatting::TracingResult {
21+
let node = parse_node(text).unwrap();
22+
23+
dprint_core::formatting::trace_printing(|| parse_items(node, text, config), config_to_print_options(text, config))
24+
}
25+
26+
fn parse_node(text: &str) -> Result<Dockerfile, ErrBox> {
27+
Ok(Dockerfile::parse(text)?)
28+
}
29+
30+
fn config_to_print_options(text: &str, config: &Configuration) -> PrintOptions {
31+
PrintOptions {
32+
indent_width: 1,
33+
max_width: config.line_width,
34+
use_tabs: false,
35+
new_line_text: resolve_new_line_kind(text, config.new_line_kind),
36+
}
937
}

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod configuration;
22
mod format_text;
3+
mod parser;
34

45
pub use format_text::format_text;
56

src/parser/context.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use std::collections::HashSet;
2+
3+
use super::helpers::Node;
4+
use crate::configuration::Configuration;
5+
6+
pub struct Context<'a> {
7+
pub config: &'a Configuration,
8+
pub text: &'a str,
9+
pub handled_comments: HashSet<usize>,
10+
pub current_node: Option<Node<'a>>,
11+
}
12+
13+
impl<'a> Context<'a> {
14+
pub fn new(text: &'a str, config: &'a Configuration) -> Self {
15+
Self {
16+
config,
17+
text,
18+
handled_comments: HashSet::new(),
19+
current_node: None,
20+
}
21+
}
22+
}

src/parser/helpers.rs

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use dockerfile_parser::*;
2+
3+
macro_rules! create_node_ref {
4+
($($variant_name:ident($node_name:ident),)*) => {
5+
#[derive(Clone, Copy)]
6+
pub enum Node<'a> {
7+
$(
8+
$variant_name(&'a $node_name),
9+
)*
10+
}
11+
12+
$(
13+
impl<'a> From<&'a $node_name> for Node<'a> {
14+
fn from(instruction: &'a $node_name) -> Node<'a> {
15+
Node::$variant_name(instruction)
16+
}
17+
}
18+
)*
19+
}
20+
}
21+
22+
create_node_ref!(
23+
Arg(ArgInstruction),
24+
Cmd(CmdInstruction),
25+
Copy(CopyInstruction),
26+
CopyFlag(CopyFlag),
27+
From(FromInstruction),
28+
Label(LabelInstruction),
29+
LabelLabel(Label),
30+
Run(RunInstruction),
31+
Entrypoint(EntrypointInstruction),
32+
Env(EnvInstruction),
33+
EnvVar(EnvVar),
34+
Misc(MiscInstruction),
35+
String(SpannedString),
36+
BreakableString(BreakableString),
37+
StringArray(StringArray),
38+
Comment(Comment),
39+
);
40+
41+
impl<'a> Node<'a> {
42+
#[allow(dead_code)]
43+
pub fn span(&self) -> Span {
44+
use Node::*;
45+
match self {
46+
From(node) => node.span,
47+
Arg(node) => node.span,
48+
Label(node) => node.span,
49+
LabelLabel(node) => node.span,
50+
Run(node) => node.span,
51+
Entrypoint(node) => node.span,
52+
Cmd(node) => node.span,
53+
Copy(node) => node.span,
54+
CopyFlag(node) => node.span,
55+
Env(node) => node.span,
56+
EnvVar(node) => node.span,
57+
Misc(node) => node.span,
58+
String(node) => node.span,
59+
BreakableString(node) => node.span,
60+
StringArray(node) => node.span,
61+
Comment(node) => node.span,
62+
}
63+
}
64+
}
65+
66+
pub struct Comment {
67+
pub span: Span,
68+
pub text: String,
69+
}
70+
71+
pub fn parse_comments(text: &str, offset: usize) -> Vec<Comment> {
72+
let mut comments = Vec::new();
73+
let mut char_iterator = text.char_indices();
74+
let mut in_start_comment_context = true;
75+
76+
while let Some((i, c)) = char_iterator.next() {
77+
// leading whitespace is supported but discouraged
78+
if in_start_comment_context && c.is_whitespace() {
79+
continue;
80+
}
81+
82+
if in_start_comment_context && matches!(c, '#') {
83+
let start_index = i;
84+
let mut end_index = i;
85+
while let Some((i, c)) = char_iterator.next() {
86+
if c == '\n' {
87+
break;
88+
}
89+
end_index = i + c.len_utf8();
90+
}
91+
comments.push(Comment {
92+
span: Span::new(offset + start_index, offset + end_index),
93+
text: text[start_index + 1..end_index].to_string(),
94+
});
95+
in_start_comment_context = true;
96+
} else {
97+
in_start_comment_context = false;
98+
}
99+
}
100+
101+
comments
102+
}
103+
104+
impl<'a> From<&'a Instruction> for Node<'a> {
105+
fn from(instruction: &'a Instruction) -> Node<'a> {
106+
use Instruction::*;
107+
match instruction {
108+
From(node) => node.into(),
109+
Arg(node) => node.into(),
110+
Label(node) => node.into(),
111+
Run(node) => node.into(),
112+
Entrypoint(node) => node.into(),
113+
Cmd(node) => node.into(),
114+
Copy(node) => node.into(),
115+
Env(node) => node.into(),
116+
Misc(node) => node.into(),
117+
}
118+
}
119+
}

src/parser/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod context;
2+
mod helpers;
3+
mod parse;
4+
5+
pub use parse::*;

0 commit comments

Comments
 (0)