Skip to content

Commit 312a71a

Browse files
committed
Add test suite; rewrite comment processor
The comment processor is now even more cursed. Surely no one will find any regressions this time.
1 parent eabbf1a commit 312a71a

40 files changed

+532
-38
lines changed

cmd/version.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ var versionCmd = &cobra.Command{
1414
Use: "version",
1515
Short: "Print the version number of dockerfmt",
1616
Run: func(cmd *cobra.Command, args []string) {
17-
fmt.Println("dockerfmt 0.3.0")
17+
fmt.Println("dockerfmt 0.3.1")
1818
},
1919
}

dockerfmt_test.go

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
"testing"
9+
10+
"github.com/reteps/dockerfmt/lib"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestFormatter(t *testing.T) {
15+
// assert equality
16+
assert.Equal(t, 123, 123, "they should be equal")
17+
matchingFiles, err := filepath.Glob("tests/in/*.dockerfile")
18+
if err != nil {
19+
t.Fatalf("Failed to find test files: %v", err)
20+
}
21+
c := &lib.Config{
22+
IndentSize: 4,
23+
TrailingNewline: true,
24+
SpaceRedirects: false,
25+
}
26+
for _, fileName := range matchingFiles {
27+
t.Run(fileName, func(t *testing.T) {
28+
outFile := strings.Replace(fileName, "in", "out", 1)
29+
originalLines, err := lib.GetFileLines(fileName)
30+
if err != nil {
31+
t.Fatalf("Failed to read file %s: %v", fileName, err)
32+
}
33+
fmt.Printf("Comparing file %s with %s\n", fileName, outFile)
34+
formattedLines := lib.FormatFileLines(originalLines, c)
35+
36+
// Write outFile to directory
37+
err = os.WriteFile(outFile, []byte(formattedLines), 0644)
38+
if err != nil {
39+
t.Fatalf("Failed to write to file %s: %v", outFile, err)
40+
}
41+
42+
// Read outFile
43+
outLines, err := lib.GetFileLines(outFile)
44+
if err != nil {
45+
t.Fatalf("Failed to read file %s: %v", outFile, err)
46+
}
47+
// Compare outLines with formattedLines
48+
assert.Equal(t, strings.Join(outLines, ""), formattedLines, "Files should be equal")
49+
50+
})
51+
}
52+
}

go.mod

+4
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@ require (
66
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
77
github.com/moby/buildkit v0.20.2
88
github.com/spf13/cobra v1.9.1
9+
github.com/stretchr/testify v1.10.0
910
mvdan.cc/sh/v3 v3.11.0
1011
)
1112

1213
require (
1314
github.com/containerd/typeurl/v2 v2.2.3 // indirect
15+
github.com/davecgh/go-spew v1.1.1 // indirect
1416
github.com/gogo/protobuf v1.3.2 // indirect
1517
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1618
github.com/pkg/errors v0.9.1 // indirect
1719
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
20+
github.com/pmezard/go-difflib v1.0.0 // indirect
1821
github.com/spf13/pflag v1.0.6 // indirect
1922
google.golang.org/protobuf v1.35.2 // indirect
23+
gopkg.in/yaml.v3 v3.0.1 // indirect
2024
)

go.sum

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
6565
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
6666
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
6767
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
68+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
6869
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
6970
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
7071
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

js/format.wasm

390 Bytes
Binary file not shown.

js/package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@reteps/dockerfmt",
3-
"version": "0.3.0",
3+
"version": "0.3.1",
44
"type": "module",
55
"description": "",
66
"repository": "git+https://github.com/reteps/dockerfmt/tree/main/js",

lib/format.go

+75-18
Original file line numberDiff line numberDiff line change
@@ -229,11 +229,65 @@ func formatShell(content string, hereDoc bool, c *Config) string {
229229
}
230230

231231
if !hereDoc {
232+
// Here lies some cursed magic. Be careful.
233+
232234
// Replace comments with a subshell evaluation -- they won't be run so we can do this.
233235
content = StripWhitespace(content, true)
236+
lineComment := regexp.MustCompile(`(\n\s*)(#.*)`)
237+
content = lineComment.ReplaceAllString(content, "$1`$2#`\\")
238+
239+
/*
240+
```
241+
foo \
242+
`#comment#`\
243+
&& bar
244+
```
245+
246+
```
247+
foo && \
248+
`#comment#` \
249+
bar
250+
```
251+
*/
252+
253+
commentContinuation := regexp.MustCompile(`(\\(?:\s*` + "`#.*#`" + `\\){1,}\s*)&&`)
254+
content = commentContinuation.ReplaceAllString(content, "&&$1")
255+
234256
// log.Printf("Content0: %s\n", content)
235-
re := regexp.MustCompile(`(\\\n\s+)((?:\s*#.*){1,})`)
236-
content = re.ReplaceAllString(content, `'dummynode';$1$( $2`+"\n"+`)\`)
257+
lines := strings.SplitAfter(content, "\n")
258+
/**
259+
if the next line is not a comment, and we didn't start with a continuation, don't add the `&&`.
260+
*/
261+
inContinutation := false
262+
for i := range lines {
263+
lineTrim := strings.Trim(lines[i], " \t\\\n")
264+
// fmt.Printf("LineTrim: %s\n", lineTrim)
265+
nextLine := ""
266+
isComment := false
267+
nextLineIsComment := false
268+
if i+1 < len(lines) {
269+
nextLine = strings.Trim(lines[i+1], " \t\\\n")
270+
}
271+
if len(nextLine) >= 2 && nextLine[:2] == "`#" {
272+
nextLineIsComment = true
273+
}
274+
if len(lineTrim) >= 2 && lineTrim[:2] == "`#" {
275+
isComment = true
276+
}
277+
278+
// fmt.Printf("isComment: %v, nextLineIsComment: %v, inContinutation: %v\n", isComment, nextLineIsComment, inContinutation)
279+
if isComment && (inContinutation || nextLineIsComment) {
280+
lines[i] = strings.Replace(lines[i], "#`\\", "#`&&\\", 1)
281+
}
282+
283+
if len(lineTrim) >= 2 && !isComment && lineTrim[len(lineTrim)-2:] == "&&" {
284+
inContinutation = true
285+
} else if !isComment {
286+
inContinutation = false
287+
}
288+
}
289+
290+
content = strings.Join(lines, "")
237291
}
238292

239293
// Now that we have a valid bash-style command, we can format it with shfmt
@@ -242,25 +296,28 @@ func formatShell(content string, hereDoc bool, c *Config) string {
242296
// log.Printf("Content2: %s\n", content)
243297

244298
if !hereDoc {
245-
content = regexp.MustCompile(`\$\(\s+(#[\w\W]*?)\s+\) \\`).ReplaceAllString(content, "$1")
246-
// log.Printf("Content3: %s\n", content)
247-
content = strings.ReplaceAll(content, "'dummynode' ", "")
248-
content = strings.ReplaceAll(content, "'dummynode'", "")
249-
content = regexp.MustCompile(`(\s*#.*)`).ReplaceAllString(content, "$1")
250-
// log.Printf("Content4: %s\n", content)
251-
content = regexp.MustCompile("(?m)^ *(#.*)").ReplaceAllString(content, strings.Repeat(" ", int(c.IndentSize))+"$1")
252-
253-
// Add backslashes if needed
299+
reBacktickComment := regexp.MustCompile(`([ \t]*)(?:&& )?` + "`(#.*)#` " + `\\`)
300+
content = reBacktickComment.ReplaceAllString(content, "$1$2")
301+
302+
// Fixup the comment indentation
254303
lines := strings.SplitAfter(content, "\n")
255-
for i := 0; i < len(lines); i++ {
256-
line := lines[i]
257-
if len(line) > 0 && line[len(line)-2] != '\\' && line[len(line)-1] == '\n' {
258-
// Check if the next line is empty and if the current line is not a comment
259-
if i+1 < len(lines) && strings.TrimSpace(lines[i+1]) != "" && strings.TrimSpace(line)[0] != '#' {
260-
line = strings.TrimRight(line, " \n") + " \\\n"
304+
prevIsComment := false
305+
prevCommentSpacing := ""
306+
for i := range lines {
307+
lineTrim := strings.TrimLeft(lines[i], " \t")
308+
// fmt.Printf("LineTrim: %s, %v\n", lineTrim, prevIsComment)
309+
if len(lineTrim) >= 1 && lineTrim[0] == '#' {
310+
lineParts := strings.SplitN(lines[i], "#", 2)
311+
312+
if prevIsComment {
313+
lines[i] = prevCommentSpacing + "#" + lineParts[1]
314+
} else {
315+
prevCommentSpacing = lineParts[0]
261316
}
317+
prevIsComment = true
318+
} else {
319+
prevIsComment = false
262320
}
263-
lines[i] = line
264321
}
265322
content = strings.Join(lines, "")
266323

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

tests/moretests.dockerfile renamed to tests/in/moretests.dockerfile

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ RUN if [[ x$LATEST_NPM = xtrue ]]; then yarn global add npm@latest; fi
2727

2828
FROM ubuntu
2929

30-
RUN (cd out && ls)
30+
RUN (cd out && ls)
31+
32+
RUN ls ; ls
File renamed without changes.
File renamed without changes.
File renamed without changes.

tests/in/run2.dockerfile

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
RUN apt-get update \
2+
# Comment here
3+
&& apt-get install -y --no-install-recommends \
4+
# Another comment here
5+
man-db unminimize \
6+
# Multiline comment here
7+
# Here
8+
gosu curl git htop less nano unzip vim wget zip && \
9+
yes | unminimize && \
10+
apt-get clean && \
11+
rm -rf /var/lib/apt/lists/* \
12+
&& find /tmp -not -path /tmp -delete
13+
14+
RUN apt-get update && \
15+
# Run 'unminimize' to add docs
16+
apt-get install -y --no-install-recommends man-db unminimize \
17+
&& yes | unminimize \
18+
&& apt-get install -y --no-install-recommends \
19+
# Reverse proxy workaround for PrairieLearn:
20+
nginx \
21+
gettext \
22+
gosu \
23+
fonts-dejavu \
24+
# Utilities for convenience debugging this container:
25+
less htop vim nano silversearcher-ag zip unzip git cmake curl wget sqlite3 && \
26+
# Test:
27+
gosu nobody true && \
28+
# Cleanup:
29+
apt-get clean \
30+
&& rm -rf /var/lib/apt/lists/* \
31+
&& find /tmp -not -path /tmp -delete
32+
33+
RUN apt-get update && \
34+
apt-get install -y --no-install-recommends man-db unminimize \
35+
&& yes | unminimize \
36+
&& apt-get install -y --no-install-recommends \
37+
nginx \
38+
gettext \
39+
gosu \
40+
fonts-dejavu \
41+
less htop vim nano silversearcher-ag zip unzip git cmake curl wget sqlite3 && \
42+
gosu nobody true && \
43+
apt-get clean \
44+
&& rm -rf /var/lib/apt/lists/* \
45+
&& find /tmp -not -path /tmp -delete

tests/in/run3.dockerfile

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
RUN apt-get update \
2+
# Comment here
3+
&& apt-get install -y --no-install-recommends \
4+
# Another comment here
5+
man-db unminimize \
6+
# Multiline comment here
7+
# Here
8+
# And here
9+
gosu curl git htop less nano unzip vim wget zip && \
10+
yes | unminimize && \
11+
apt-get clean && \
12+
rm -rf /var/lib/apt/lists/* \
13+
&& find /tmp -not -path /tmp -delete

tests/in/shell.dockerfile

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# https://github.com/jessfraz/dockfmt/issues/2
2+
# set up PrairieLearn and run migrations to initialize the DB
3+
RUN chmod +x /PrairieLearn/scripts/init.sh \
4+
&& mkdir /course{,{2..9}} \
5+
&& mkdir -p /workspace_{main,host}_zips \
6+
&& mkdir -p /jobs \
7+
# Here is a comment in the middle of my command -- `docker run -it --rm`
8+
&& /PrairieLearn/scripts/start_postgres.sh \
9+
&& cd /PrairieLearn \
10+
&& make build \
11+
# Here is a multiline comment in my command
12+
# The parser has to handle this case, and strip out the
13+
# $() chars surrounding
14+
&& node apps/prairielearn/dist/server.js --migrate-and-exit \
15+
&& su postgres -c "createuser -s root" \
16+
&& /PrairieLearn/scripts/start_postgres.sh stop \
17+
&& /PrairieLearn/scripts/gen_ssl.sh \
18+
&& git config --global user.email "[email protected]" \
19+
&& git config --global user.name "Dev User" \
20+
&& git config --global safe.directory '*'
21+
22+
RUN chmod +x /PrairieLearn/scripts/init.sh \
23+
&& mkdir /course{,{2..9}} \
24+
&& mkdir -p /workspace_{main,host}_zips \
25+
&& mkdir -p /jobs && \
26+
# Here is a comment in the middle of my command -- `docker run -it --rm`
27+
/PrairieLearn/scripts/start_postgres.sh \
28+
&& cd /PrairieLearn && \
29+
make build && \
30+
# Here is a multiline comment in my command
31+
# The parser has to handle this case, and strip out the
32+
# $() chars surrounding
33+
node apps/prairielearn/dist/server.js --migrate-and-exit \
34+
&& su postgres -c "createuser -s root" \
35+
&& /PrairieLearn/scripts/start_postgres.sh stop \
36+
&& /PrairieLearn/scripts/gen_ssl.sh \
37+
&& git config --global user.email "[email protected]" \
38+
&& git config --global user.name "Dev User" \
39+
&& git config --global safe.directory '*'
40+
41+
healthcheck --interval=5m --timeout=3s \
42+
CMD curl -f http://localhost/ || exit 1
43+
CMD /PrairieLearn/scripts/init.sh

tests/whitespace.dockerfile renamed to tests/in/whitespace.dockerfile

+9-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,12 @@ RUN yarn --frozen-lockfile && \
1111
# 最终的应用
1212
FROM abiosoft/caddy
1313
COPY --from=builder /app/packages/ufc-host-app/build /srv
14-
EXPOSE 2015
14+
EXPOSE 2015
15+
16+
FROM foobar
17+
RUN ls
18+
LABEL foo=bar
19+
HEALTHCHECK NONE
20+
CMD ls
21+
COPY . .
22+
ADD . .

tests/out/comment.dockerfile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# https://github.com/jessfraz/dockfmt/issues/12
2+
FROM scratch
3+
# a comment

tests/out/env.dockerfile

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# https://github.com/un-ts/prettier/issues/398
2+
ENV a=1 \
3+
b=2 \
4+
# comment
5+
c=3 \
6+
d=4 \
7+
# comment
8+
e=5
9+
10+
ENV MY_VAR=my-value
11+
ENV MY_VAR=my-value2 \
12+
c=4
13+
ENV MY_VAR=my-value3

tests/out/flags.dockerfile

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# https://github.com/jessfraz/dockfmt/issues/23
2+
FROM --platform=linux/arm64 debian
3+
4+
RUN --network=host apt-get install vim
5+
RUN --security echo "test"
6+
COPY --chown=my-user:my-group --chmod=644 ./config.conf /data/config.conf
7+
COPY --link ./another-file /data/linked-file
8+
ADD --keep-git-dir ./ /data/src

0 commit comments

Comments
 (0)