Run all linting in Docker via Dockerfile.lint (closes #30) #31

Open
clawbot wants to merge 20 commits from next into main
2 changed files with 45 additions and 7 deletions
Showing only changes of commit ffc817522e - Show all commits
+8 -7
View File
@@ -129,13 +129,14 @@ invocation graph, so a second pass cannot creep back in unnoticed.
`script/fmt-check` remains as a standalone entrypoint for asking the formatting `script/fmt-check` remains as a standalone entrypoint for asking the formatting
question on its own, without docker and without the rest of lint. It is not the question on its own, without docker and without the rest of lint. It is not the
last formatting path on the host — `script/fmt` writes with the host prettier, last formatting path on the host — `script/fmt` writes with the host prettier,
and `package.json` exposes `fmt` and `fmt-check` as direct prettier calls — it is and `package.json` exposes `fmt` and `fmt-check` as direct prettier calls — it
the one of them that the check graph could plausibly have reached, and nothing is the one of them that the check graph could plausibly have reached, and
reaches it: neither `script/check` nor `script/precommit` calls it, so it never nothing reaches it: neither `script/check` nor `script/precommit` calls it, so
contributes to their verdicts. Its own verdict cannot drift from the container's: prettier is pinned it never contributes to their verdicts. Its own verdict cannot drift from the
to an exact version, installed from `yarn.lock` under `--frozen-lockfile` in container's: prettier is pinned to an exact version, installed from `yarn.lock`
both places, and reads `.gitignore` as its default ignore file — which is why under `--frozen-lockfile` in both places, and reads `.gitignore` as its default
`.dockerignore` deliberately keeps `.gitignore` in the build context. ignore file — which is why `.dockerignore` deliberately keeps `.gitignore` in
the build context.
Lint happens in exactly one place in that graph, which constrains the rest of Lint happens in exactly one place in that graph, which constrains the rest of
the build. `script/check` calls `script/lint`, so `make check` cannot run inside the build. `script/check` calls `script/lint`, so `make check` cannot run inside
+37
View File
@@ -1094,6 +1094,43 @@ describe("the resolver reads what the shell would run", () => {
]); ]);
}); });
// The false green this file exists to prevent, in the one shape it still
// had: `script/lint` chaining a second `docker build` after the lint image
// built prettier twice and counted once, because a single `test` and a
// single `exec` over the line produced exactly one edge and resolved it to
// the first file named. That is the same failure as the original duplicate
// pass — two invocations, one verdict — reached through the walker instead
// of through the repo.
it("follows both docker builds when one line chains two", () => {
expect(
edgesOf(
'docker build --build-arg LINT_EPOCH="$(date +%s)" -f Dockerfile.lint . && docker build -f Dockerfile.extra .',
),
).toEqual(["docker:Dockerfile.lint", "docker:Dockerfile.extra"]);
});
// The other half: a flag search over the whole line reads a *later*
// command's `-f` as the build's own. A bare `docker build .` followed by
// `cp -f` resolved to `Dockerfile.lint`, which both counts an invocation
// that never happens and drops the `Dockerfile` edge — the half of the CI
// graph the second entrypoint exists to cover. Each separator that bounds
// an invocation is pinned, since the bounding is a split rather than a
// shell parse and dropping one of them would be silent.
it("does not read a later command's -f as the build's", () => {
expect(
edgesOf("docker build . && cp -f Dockerfile.lint /tmp/x"),
).toEqual(["docker:Dockerfile"]);
expect(
edgesOf("docker build . || cp -f Dockerfile.lint /tmp/x"),
).toEqual(["docker:Dockerfile"]);
expect(
edgesOf("docker build . ; cp -f Dockerfile.lint /tmp/x"),
).toEqual(["docker:Dockerfile"]);
expect(edgesOf("docker build . | tee -f Dockerfile.lint")).toEqual([
"docker:Dockerfile",
]);
});
// This exact-equality assertion is also the block-scalar guard, which is // This exact-equality assertion is also the block-scalar guard, which is
// not obvious from its name: a `run: |` step resolves to the bare `|`, // not obvious from its name: a `run: |` step resolves to the bare `|`,
// which reaches nothing, so the prettier count would stay 1 no matter what // which reaches nothing, so the prettier count would stay 1 no matter what