Skip to main content

dprint_plugin_dockerfile/
ast.rs

1// AST types for the dockerfile parser.
2//
3// These mirror the subset of the `dockerfile-parser` crate's public types that
4// the formatter relies on. They are produced by [`crate::parser`].
5
6/// A byte-index range into the original Dockerfile text.
7#[derive(PartialEq, Eq, Clone, Copy, Debug, Ord, PartialOrd)]
8pub struct Span {
9  pub start: usize,
10  pub end: usize,
11}
12
13impl Span {
14  pub fn new(start: usize, end: usize) -> Span {
15    Span { start, end }
16  }
17
18  /// Determines the 0-indexed line number and line-relative span of this span.
19  pub fn relative_span(&self, dockerfile: &Dockerfile) -> (usize, Span) {
20    let mut line_start_offset = 0;
21    let mut lines = 0;
22    for (i, c) in dockerfile.content.as_bytes().iter().enumerate() {
23      if i == self.start {
24        break;
25      }
26      if *c == b'\n' {
27        lines += 1;
28        line_start_offset = i + 1;
29      }
30    }
31
32    let start = self.start - line_start_offset;
33    let end = start + (self.end - self.start);
34    (lines, Span { start, end })
35  }
36}
37
38impl From<(usize, usize)> for Span {
39  fn from(tup: (usize, usize)) -> Span {
40    Span::new(tup.0, tup.1)
41  }
42}
43
44/// A parsed Dockerfile.
45#[derive(Debug, Clone, PartialEq)]
46pub struct Dockerfile {
47  /// The raw content of the Dockerfile.
48  pub content: String,
49  /// An ordered list of all parsed instructions.
50  pub instructions: Vec<Instruction>,
51  /// The line-continuation / escape character, from a `# escape=` directive
52  /// (`\` by default).
53  pub escape: char,
54}
55
56impl Dockerfile {
57  /// Parses a Dockerfile from a string.
58  pub fn parse(input: &str) -> Result<Dockerfile, monch::ParseErrorFailureError> {
59    crate::parser::parse(input)
60  }
61}
62
63/// A single Dockerfile instruction.
64#[derive(Debug, PartialEq, Eq, Clone)]
65pub enum Instruction {
66  From(FromInstruction),
67  Arg(ArgInstruction),
68  Label(LabelInstruction),
69  Run(RunInstruction),
70  Entrypoint(EntrypointInstruction),
71  Cmd(CmdInstruction),
72  Copy(CopyInstruction),
73  Env(EnvInstruction),
74  Shell(ShellInstruction),
75  Onbuild(OnbuildInstruction),
76  Healthcheck(HealthcheckInstruction),
77  Heredoc(HeredocInstruction),
78  Misc(MiscInstruction),
79  /// A line that could not be parsed as a known instruction. It is kept
80  /// verbatim so a single malformed line never fails formatting of the file.
81  Unknown(SpannedString),
82}
83
84impl Instruction {
85  pub fn span(&self) -> Span {
86    match self {
87      Instruction::From(i) => i.span,
88      Instruction::Arg(i) => i.span,
89      Instruction::Label(i) => i.span,
90      Instruction::Run(i) => i.span,
91      Instruction::Entrypoint(i) => i.span,
92      Instruction::Cmd(i) => i.span,
93      Instruction::Copy(i) => i.span,
94      Instruction::Env(i) => i.span,
95      Instruction::Shell(i) => i.span,
96      Instruction::Onbuild(i) => i.span,
97      Instruction::Healthcheck(i) => i.span,
98      Instruction::Heredoc(i) => i.span,
99      Instruction::Misc(i) => i.span,
100      Instruction::Unknown(i) => i.span,
101    }
102  }
103}
104
105/// A string with a character span.
106#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone)]
107pub struct SpannedString {
108  pub span: Span,
109  pub content: String,
110}
111
112/// A comment with a character span.
113#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone)]
114pub struct SpannedComment {
115  pub span: Span,
116  pub content: String,
117}
118
119/// A string array (ex. `["executable", "param1", "param2"]`).
120#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone)]
121pub struct StringArray {
122  pub span: Span,
123  pub elements: Vec<SpannedString>,
124}
125
126/// A component of a breakable string.
127#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone)]
128pub enum BreakableStringComponent {
129  String(SpannedString),
130  Comment(SpannedComment),
131}
132
133/// A Docker string that may be broken across several lines, separated by line
134/// continuations (`\\\n`), and possibly intermixed with comments.
135#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone)]
136pub struct BreakableString {
137  pub span: Span,
138  pub components: Vec<BreakableStringComponent>,
139}
140
141/// A string that is either in shell form or exec form (`["a", "b"]`).
142#[derive(Debug, PartialEq, Eq, Clone)]
143pub enum ShellOrExecExpr {
144  Shell(BreakableString),
145  Exec(StringArray),
146}
147
148/// A Dockerfile `FROM` instruction.
149#[derive(Debug, PartialEq, Eq, Clone)]
150pub struct FromInstruction {
151  pub span: Span,
152  pub flags: Vec<FromFlag>,
153  pub image: SpannedString,
154  pub alias: Option<SpannedString>,
155}
156
157/// A key/value pair passed to a `FROM` instruction as a flag.
158#[derive(Debug, PartialEq, Eq, Clone)]
159pub struct FromFlag {
160  pub span: Span,
161  pub name: SpannedString,
162  pub value: SpannedString,
163}
164
165/// A Dockerfile `ARG` instruction.
166#[derive(Debug, Clone, PartialEq, Eq)]
167pub struct ArgInstruction {
168  pub span: Span,
169  pub name: SpannedString,
170  pub value: Option<SpannedString>,
171}
172
173/// A Dockerfile `LABEL` instruction. A single instruction may set many labels.
174#[derive(Debug, PartialEq, Eq, Clone)]
175pub struct LabelInstruction {
176  pub span: Span,
177  pub labels: Vec<Label>,
178}
179
180/// A single label key/value pair.
181#[derive(Debug, PartialEq, Eq, Clone)]
182pub struct Label {
183  pub span: Span,
184  pub name: SpannedString,
185  pub value: SpannedString,
186}
187
188/// A Dockerfile `RUN` instruction.
189#[derive(Debug, PartialEq, Eq, Clone)]
190pub struct RunInstruction {
191  pub span: Span,
192  pub expr: ShellOrExecExpr,
193}
194
195/// A Dockerfile `ENTRYPOINT` instruction.
196#[derive(Debug, PartialEq, Eq, Clone)]
197pub struct EntrypointInstruction {
198  pub span: Span,
199  pub expr: ShellOrExecExpr,
200}
201
202/// A Dockerfile `CMD` instruction.
203#[derive(Debug, PartialEq, Eq, Clone)]
204pub struct CmdInstruction {
205  pub span: Span,
206  pub expr: ShellOrExecExpr,
207}
208
209/// A Dockerfile `COPY` instruction.
210#[derive(Debug, PartialEq, Eq, Clone)]
211pub struct CopyInstruction {
212  pub span: Span,
213  pub flags: Vec<CopyFlag>,
214  pub args: CopyArgs,
215}
216
217/// The argument portion of a `COPY` instruction: either space-separated paths
218/// or the JSON/exec array form (`COPY ["src", "dest"]`).
219#[derive(Debug, PartialEq, Eq, Clone)]
220pub enum CopyArgs {
221  Paths { sources: Vec<SpannedString>, destination: SpannedString },
222  Exec(StringArray),
223}
224
225/// A key/value pair passed to a `COPY` instruction as a flag.
226#[derive(Debug, PartialEq, Eq, Clone)]
227pub struct CopyFlag {
228  pub span: Span,
229  pub name: SpannedString,
230  pub value: SpannedString,
231}
232
233/// A Dockerfile `ENV` instruction.
234#[derive(Debug, PartialEq, Eq, Clone)]
235pub struct EnvInstruction {
236  pub span: Span,
237  pub vars: Vec<EnvVar>,
238}
239
240/// An environment variable key/value pair.
241#[derive(Debug, PartialEq, Eq, Clone)]
242pub struct EnvVar {
243  pub span: Span,
244  pub key: SpannedString,
245  pub value: BreakableString,
246}
247
248/// A Dockerfile `SHELL` instruction. Docker only permits the exec (JSON array)
249/// form, but the shell form is tolerated so malformed input still round-trips.
250#[derive(Debug, PartialEq, Eq, Clone)]
251pub struct ShellInstruction {
252  pub span: Span,
253  pub expr: ShellOrExecExpr,
254}
255
256/// A Dockerfile `ONBUILD` instruction, wrapping the instruction it triggers.
257#[derive(Debug, PartialEq, Eq, Clone)]
258pub struct OnbuildInstruction {
259  pub span: Span,
260  pub instruction: Box<Instruction>,
261}
262
263/// A Dockerfile `HEALTHCHECK` instruction.
264///
265/// Either `HEALTHCHECK [OPTIONS] CMD <command>` (with `cmd` set to the nested
266/// `CMD` instruction) or `HEALTHCHECK NONE` (with `cmd` being `None`).
267#[derive(Debug, PartialEq, Eq, Clone)]
268pub struct HealthcheckInstruction {
269  pub span: Span,
270  pub flags: Vec<HealthcheckFlag>,
271  pub cmd: Option<Box<Instruction>>,
272}
273
274/// A key/value option passed to a `HEALTHCHECK` instruction, e.g.
275/// `--interval=30s`.
276#[derive(Debug, PartialEq, Eq, Clone)]
277pub struct HealthcheckFlag {
278  pub span: Span,
279  pub name: SpannedString,
280  pub value: SpannedString,
281}
282
283/// An instruction that carries one or more [heredocs][heredoc], e.g.
284///
285/// ```dockerfile
286/// RUN <<EOF
287/// echo hello
288/// EOF
289/// ```
290///
291/// The first line is the wrapped `instruction` (parsed normally, so it still
292/// gets formatted); `body` is the verbatim text of the heredoc bodies and their
293/// closing delimiters, preserved exactly.
294///
295/// [heredoc]: https://docs.docker.com/engine/reference/builder/#here-documents
296#[derive(Debug, PartialEq, Eq, Clone)]
297pub struct HeredocInstruction {
298  pub span: Span,
299  pub instruction: Box<Instruction>,
300  pub body: String,
301}
302
303/// A miscellaneous (otherwise unsupported) Dockerfile instruction.
304///
305/// Includes valid-but-unparsed commands such as `EXPOSE`, `VOLUME`, `USER`,
306/// `WORKDIR`, `ONBUILD`, `STOPSIGNAL`, `HEALTHCHECK`, `SHELL`, `MAINTAINER`.
307#[derive(Debug, PartialEq, Eq, Clone)]
308pub struct MiscInstruction {
309  pub span: Span,
310  pub instruction: SpannedString,
311  pub arguments: BreakableString,
312}