Files and names
On this page
One primary declaration per file
Each file holds one primary declaration, named after it:
ServiceCredential lives in service_credential.jadpo. Route groups and
libraries are named declarations, so the same rule covers them.
Supporting types, failures and helpers that only that file uses live in it
too. A declaration any other file uses, apart from the file's own tests,
has a file of its own named after it. So every name a use line brings in
is in <folder>/<name>.jadpo (see Imports).
Everything in a file besides its primary declaration is used by the
primary declaration, directly or through one another. A declaration that
only the file's tests use, or that nothing uses, is dead code, reported as
layout.unused_declaration, as an unused shared declaration is. So a file
never collects helpers for something else. A declaration another file uses
is reported by layout.promotion_required, which moves it, not as unused.
The primary declaration is the one of the folder's kind named after the
file, or the first of that kind when the file is misnamed. In
database/seeds/ it is the seed. Test files, root files and loose files
have no primary declaration, and a library's functions keep their own
rule: a function nothing calls is unused.
While the primary declaration, or one it uses, writes a name that names nothing, perhaps a misspelling of the one it meant, nothing beside it is called unused, as with imports.
Order within a file
Definitions come before their use. After any opening comment and the use
lines, every declaration in a file comes before the first one that uses
it. So the primary declaration comes last, and you read it with the
constants and types it needs already in view.
When two declarations could go in either order:
- They go by kind: constants, then types, then failures, then helpers.
- Within a kind, they go in the order the primary declaration first reaches them. Reaching follows the primary's names in the order it first writes them, into each declaration before the next.
- Declarations that refer to each other keep their written order among themselves, and constants written together stay together.
- A declaration nothing reaches (reported as
layout.unused_declaration) goes after the reached ones of its kind, in written order. - A declaration still waits for what it uses, whatever its kind, so a
constant of type
Stockcomes afterStock.
jadpo fmt writes this order, and jadpo fmt --check fails on a file out
of it. A file with no primary declaration (a test file, a root file, a
loose file) keeps its written order.
Names are unique
A name must convert to its file name and back unchanged, so a name can't
have two capitals in a row: VatRate, not VATRate.
Names are unique across the whole project and across kinds, with one
exception. A private function or type needs a name unique only within
its domain, whether it has a file of its own or sits beside the
declaration that uses it. A private constant follows the same rule
(Constants). A failure or an email
(type X = Failure or EmailMessage) doesn't get the exception.
Everything else stays unique across the project:
- anything
public, and anything inshared/or the root files; - every other kind of declaration, whatever its visibility, since most have a name outside the code: entities (tables), failures (public error codes), events, actions, queries, routes, schedules, subscribers (stored deliveries and schedule state name them), services, fixtures and emails;
- a type an API's record names, private or not: a route's input or output, or a type reached through their fields, is an OpenAPI schema name;
- library names, which also qualify their functions (
rounding.half_even).
A name unique across the project is unique against the per-domain ones
too. A private Address in billing clashes with a public or shared
Address, while two private Address types in billing and shipping
don't. Two names folding to one file name (naming.folded_collision)
follow the same scope. So a search for a name finds one file, or one file
per domain for a private function or type.
Domain, library and standard-library names are unique among themselves, and local variables can't reuse them. Standard-library names are fixed per language version, so a new standard namespace never breaks an existing library.
A private name can be imported only inside its own domain, where it is
unique, and anything importable across domains is unique across the
project. So import.missing can always name the exact use line.
A domain is not a namespace: Customer means the same thing whichever
folder it appears in.
There is one exception to file names being unique. A type declared in the
same file as an action, query or function may fold to that callable's
name, as an input does: type CreateTodo = Input beside
action create_todo in actions/create_todo.jadpo. Only one file carries
the name. If the type is promoted to its own file, the rule applies again.
When two names clash
A clash is semantic.duplicate_declaration. It says whether the two are a
copy (use the existing one, or move it to shared/) or two things with
one name (rename one). It never invents a name, and it lists the names in
use that contain the same word. jadpo rename refuses a new name already
in use before editing anything.
An agent working in parallel with others merges main before pushing,
runs jadpo check (it builds nothing, so it is quick), fixes any clash,
then pushes. Generated CI runs jadpo check first, so a clash fails in
seconds (Tools for agents).
Tests and other files
Tests sit beside their subject as <name>.test.jadpo. Scenarios that span
several domains go in the root tests/. The kind of test (pure, action,
route, schedule) is known from what it calls, so it isn't a folder;
jadpo test --kind route runs one kind.
Files that aren't Jadpo (provider contracts, email templates, search mappings) sit beside the file that uses them, and that file must refer to them.
Not built yet: jadpo rename.
Markdown for agents: /docs/files-and-names.md.Built from Jadpo main at a707efb.