Code quality¶
Four checks keep the code consistent. The CI (.github/workflows/ci.yml) runs them on
every pull request. All four, together with the documentation check docs, are required
for merging into develop.
| Check | Tool | Command |
|---|---|---|
format |
Spotless with palantir-java-format | ./mvnw spotless:check |
lint |
Error Prone and javac warnings |
./mvnw -Plint -DskipTests test-compile (JDK 21) |
tests |
JUnit tests and the packaged jar test | ./mvnw verify |
python |
ruff and ty on the Python helpers and examples, self-checks of the scripts | see Python |
Formatting¶
The Java code is formatted with Spotless and
palantir-java-format. Spotless also
removes unused imports, orders the imports, trims trailing whitespace, ends every file
with a newline, and sorts pom.xml.
./mvnw -B -q spotless:apply # format the code
./mvnw -B -q spotless:check # check the formatting, as the CI does
IDE formatters produce different results, so run spotless:apply before every commit.
A git pre-commit hook does this for you. It formats the staged Java files and stages the
result. It stops if a staged file also has unstaged changes, because staging the formatted
file would stage those changes too. Save it as .git/hooks/pre-commit and make it
executable with chmod +x .git/hooks/pre-commit:
#!/bin/sh
# format the staged Java files with spotless and stage the result
FILES=$(git diff --cached --name-only --diff-filter=ACM -- '*.java')
[ -z "$FILES" ] && exit 0
# re-staging a file that also has unstaged changes would stage those too
PARTIAL=$(git diff --name-only -- $FILES)
if [ -n "$PARTIAL" ]; then
echo "pre-commit: stage or stash the unstaged changes first:" >&2
echo "$PARTIAL" >&2
exit 1
fi
PATTERNS=$(printf '.*%s,' $FILES)
./mvnw -q spotless:apply -DspotlessFiles="${PATTERNS%,}" || exit 1
git add -- $FILES
Error Prone¶
The Maven profile lint compiles the code with Error Prone,
with all javac lint warnings except processing and serial, and fails on every
warning (-Werror). Error Prone needs JDK 21 or newer to run. The code still compiles
for Java 17. The JVM options that Error Prone needs are in .mvn/jvm.config.
Fix the warning instead of suppressing it. If a suppression is needed, use
@SuppressWarnings on the smallest possible scope, with a comment that gives the reason.
Python¶
The Python helpers, the documentation scripts in scripts/ and the test model tools in
tools/pycysbml, are linted and formatted with ruff and
type checked with ty, configured in ruff.toml and
ty.toml. Both tools, and the dependencies of the helpers, come from the uv project in
tools/, so run them through it from the repository root:
uv run --project tools ruff check # lint
uv run --project tools ruff format # format the code
uv run --project tools ruff format --check # check the formatting, as the CI does
uv run --project tools ty check # type check
The CI also runs the self-checks of the scripts, python scripts/release_notes.py --check
and python scripts/update_jsbml.py --check, through the same project.
The Python examples of the automation commands in examples/python are their own uv
project (with py4cytoscape). ruff checks them with the configuration above; ty runs
through their project, with its configuration in examples/python/pyproject.toml:
ty reports every diagnostic as an error, including a value of an untyped library or of
Any that flows into an annotated variable or return. Narrow such a value with a check
at the boundary, instead of suppressing the diagnostic.
Tests¶
Every change needs tests that cover it. Tests that need the network carry
@Tag("network"), long running model suites carry @Tag("models"); both are excluded
by default. A change of the import changes the golden snapshots, which are regenerated
and reviewed as described in Testing.
./mvnw -B -q verify prints no warnings; a new warning in the test output needs a look,
see Test logging.
Conventions¶
- Use the constants in
org.cy3sbml.SBMLfor node types, edge types and column names, never string literals. - Access the SBML document of a network only through
SBMLManager. - Create new objects in
CyActivatorand pass them to the classes that need them. The code has no static singletons. - Pass the Cytoscape services that an action or task needs through
ServiceAdapter.