Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 72 additions & 1 deletion .github/workflows/PreCommitHooks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
- 'master'

jobs:
pre-commit:
pc-julia-formatter:
name: julia-formatter hook
runs-on: ubuntu-latest

Expand Down Expand Up @@ -44,3 +44,74 @@ jobs:

# Check that running the hook again succeeds.
pipx run pre-commit try-repo ../../../ julia-formatter --files main.jl --verbose


pc-jlfmt:
name: jlfmt hook
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6
with:
persist-credentials: false

- name: Check for graceful error if jlfmt is not installed
working-directory: .github/workflows/pre-commit
run: |
# At this point, jlfmt won't be on PATH since the GitHub runner
# doesn't automatically add Julia apps to PATH. This is intentional,
# since it lets us check that there is a nice error message if jlfmt
# is not on PATH.
output=$(pipx run pre-commit try-repo ../../../ jlfmt --files main.jl --verbose 2>&1 || true)
echo "$output" | grep -q "make sure that jlfmt is on your PATH" || { echo "Expected graceful error message when jlfmt was not found"; exit 1; }

- uses: julia-actions/setup-julia@v3
with:
version: '1'

- name: Install jlfmt app
# Need to explicitly add registry because of
# https://github.com/JuliaLang/Pkg.jl/issues/4360 which is not
# backported to 1.12 yet
Comment on lines +74 to +75

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we ask for a backport on the issue?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I had actually meant to investigate this in more depth because it seems like it has been backported: JuliaLang/Pkg.jl#4493 I'm not sure why the CI runner doesn't pick it up.

run:
julia -e 'using Pkg; Pkg.Registry.add("General"); Pkg.Apps.add(; path=pwd())'

- uses: actions/setup-python@v6
with:
python-version: '3.x'

- name: Test pre-commit hook
working-directory: .github/workflows/pre-commit
run: |
# Run pre-commit hook, check that it fails.
# pre-commit try-repo will fail if the hook makes changes
pipx run pre-commit try-repo ../../../ jlfmt --files main.jl --verbose && exit 1

# Check that the file was changed.
git diff --exit-code main.jl && exit 1

# Check that running the hook again succeeds.
pipx run pre-commit try-repo ../../../ jlfmt --files main.jl --verbose

- name: Test pre-commit hook with --jlfmt-path argument
working-directory: .github/workflows/pre-commit
run: |
# Move the jlfmt binary to somewhere else
mv ~/.julia/bin/jlfmt ~/.julia/bin/jlfmt-moved

# Reset the test file
git checkout main.jl
Comment thread
penelopeysm marked this conversation as resolved.

# Add --jlfmt-path arg to the existing jlfmt hook. pre-commit
# try-repo doesn't allow passing hook arguments via the command line,
# so we inject it directly into .pre-commit-hooks.yaml. A tad ugly.
sed -i '/^- id: jlfmt$/a\ args: ["--jlfmt-path=~/.julia/bin/jlfmt-moved"]' ../../../.pre-commit-hooks.yaml

# Run pre-commit hook, check that it fails.
pipx run pre-commit try-repo ../../../ jlfmt --files main.jl --verbose && exit 1

# Check that the file was changed.
git diff --exit-code main.jl && exit 1

# Check that running the hook again succeeds.
pipx run pre-commit try-repo ../../../ jlfmt --files main.jl --verbose
2 changes: 2 additions & 0 deletions .github/workflows/pre-commit/main.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# NOTE: This file should remain unformatted (as per JuliaFormatter's default style) as it is
# used to test the pre-commit hooks.
function f(x )
return x+1
end
14 changes: 12 additions & 2 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
- id: julia-formatter
name: "Julia Formatter"
entry: "julia -e 'import JuliaFormatter: format; format(ARGS)'"
entry: "julia --threads=auto -e 'import JuliaFormatter: format; format(ARGS)'"
pass_filenames: true
always_run: false
types: [file]
files: \.(jl|[jq]?md)$
files: \.(jl|md|jmd|qmd)$
language: "system"
description: "An opinionated code formatter for Julia. Plot twist - the opinion is your own."

- id: jlfmt
name: "JuliaFormatter (Pkg app)"
entry: bin/jlfmt-hook.sh
pass_filenames: true
always_run: false
types: [file]
files: \.(jl|md|jmd|qmd)$
language: "script"
description: "An opinionated code formatter for Julia, but now via a Pkg app. Plot twist - the opinion is your own."
13 changes: 13 additions & 0 deletions HISTORY_v2.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# v2.4.0

Added the `--threads=auto` option to the old `julia-formatter` pre-commit hook, which should speed up invocations of JuliaFormatter.

Added a new pre-commit hook which uses the `jlfmt` executable.
To use this, you will need to first install `jlfmt` with

```julia
] app add JuliaFormatter
```

Please see [the docs](https://juliaeditorsupport.github.io/JuliaFormatter.jl/stable/integrations/) for more information.

# v2.3.3

Fixed a bug with alignment of multiline strings when the first line contains characters whose display width is not equal to the number of bytes.
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "JuliaFormatter"
uuid = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
version = "2.3.3"
version = "2.4.0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment about package versioning vs hook versioning. Here I think the bump might be justified because of a docs change, but in general it depends whether you consider the hook to be "part of" the Julia package, which is debatable. For example, it won't be installed when people do Pkg.add("JuliaFormatter").

@penelopeysm penelopeysm May 26, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, thanks for the suggestions! There are indeed a few good options to decouple semver of the package from semver of the hook.

But also, I don't really envision that there would be much need to update the hooks much in the future, so my inclination right now is that it's not hugely important to sort it out, and we can continue to live in a regime where hook behaviour is in practice guaranteed by package semver but not officially documented. I think if there is a need to make a breaking change in the future then we can revisit this design?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I think if I were to change this design, I would probably use the tag prefixes, that seems like the most elegant way to solve it. I'd also be interested in finding out how that interacts with things like Dependabot, but that'd be a minor detail.)

authors = ["Dominique Luna <dluna132@gmail.com> and contributors"]

[deps]
Expand Down
6 changes: 0 additions & 6 deletions bin/hook.jl

This file was deleted.

68 changes: 68 additions & 0 deletions bin/jlfmt-hook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env bash

# Wrapper for the jlfmt executable, which is used in the jlfmt pre-commit hook.
#
# Resolution order for jlfmt:
#
# 1. --jlfmt-path=<path> (explicit override)
# 2. jlfmt on PATH
# 3. $JULIA_DEPOT_PATH/bin (each entry, colon-separated)
# 4. ~/.julia/bin (default depot)
#
# Gracefully errors if jlfmt can't be found.

jlfmt=""

# Parse --jlfmt-path argument and pass the rest through.
args=()
for arg in "$@"; do
case "$arg" in
--jlfmt-path=*) jlfmt="${arg#--jlfmt-path=}" ;;
*) args+=("$arg") ;;
esac
done

if [ -n "$jlfmt" ]; then
# Path was explicitly specified. Expand tildes and check if it exists
jlfmt="${jlfmt/#\~/$HOME}"
if ! command -v "$jlfmt" &>/dev/null; then
echo "Error: The executable '$jlfmt' (passed via --jlfmt-path) was not found."
exit 1
fi
else
# Not specified, we'll have to search for it ourselves.
# Step 1: Check PATH
if command -v jlfmt &>/dev/null; then
jlfmt="jlfmt"
# Step 2: Check JULIA_DEPOT_PATH
elif [ -n "$JULIA_DEPOT_PATH" ]; then
IFS=: read -ra depots <<< "$JULIA_DEPOT_PATH"
for depot in "${depots[@]}"; do
depot="${depot/#\~/$HOME}"
if [ -x "$depot/bin/jlfmt" ]; then
jlfmt="$depot/bin/jlfmt"
break
fi
done
fi
# Step 3: Check ~/.julia/bin (default fallback: cf. Julia's
# `Base.init_depot_path()` implementation). In principle, we would like to
# run `julia -e 'println(DEPOT_PATH)'`, but that takes too long, so is not
# a viable option for pre-commit.
if [ -z "$jlfmt" ] && [ -x "${HOME}/.julia/bin/jlfmt" ]; then
jlfmt="${HOME}/.julia/bin/jlfmt"
fi
# Error
if [ -z "$jlfmt" ]; then
echo "ERROR: 'jlfmt' not found."
echo "Install it with: julia -e 'import Pkg; Pkg.Apps.add(\"JuliaFormatter\")'"
echo "Then make sure that jlfmt is on your PATH, or tell pre-commit its location with:"
echo ""
echo " args: [\"--jlfmt-path=/path/to/jlfmt\"]"
echo ""
echo "See https://juliaeditorsupport.github.io/JuliaFormatter.jl/stable/integrations/ for details."
exit 1
fi
fi

exec "$jlfmt" --threads=auto -- --inplace "${args[@]}"
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ makedocs(;
"SciML Style" => "sciml_style.md",
"Configuration File" => "config.md",
"Command Line Interface" => "cli.md",
"Integrations" => "integrations.md",
"API Reference" => "api.md",
],
warnonly = true,
Expand Down
16 changes: 11 additions & 5 deletions docs/src/cli.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
# Command Line Interface
# [Command Line Interface](@id cli)
Comment thread
penelopeysm marked this conversation as resolved.

JuliaFormatter provides a command-line executable `jlfmt` for formatting Julia source code.
This is a [Pkg app](https://pkgdocs.julialang.org/v1/apps/), and therefore requires Julia v1.12 or later.

## Installation

Install using Julia's app manager:
The app can be installed using Julia's app manager:

```julia
pkg> app add JuliaFormatter
# Install the latest available version
import Pkg; Pkg.Apps.add("JuliaFormatter")

# Or a specific version. Note that the version must be >= v2.2.0 since that is
# when the `jlfmt` app was introduced.
import Pkg; Pkg.Apps.add(; name = "JuliaFormatter", version = v"2.3.0")
```

This makes the `jlfmt` command available in your `PATH`.
This should create a new binary, called `jlfmt`, inside the Julia depot's `bin` directory (usually `~/.julia/bin`; but you can check with the `DEPOT_PATH` variable in Julia).

Alternatively, invoke directly without installation:
Alternatively, you can invoke the app directly without installation:

```bash
julia -m JuliaFormatter [<options>] <path>...
Expand Down
Loading
Loading