PureXML/third_party/rules-swift/CONVENTIONS.md

128 lines
6.4 KiB
Markdown

# Tiledown Conventions
The Swift coding conventions for Tiledown. They keep the codebase consistent,
testable, and portable. Read this before opening a PR. When in doubt, read the
surrounding files and match what is already there: consistency with existing code
outranks personal preference.
These are the **Swift domain** conventions: the language- and platform-specific
craft. They sit on top of a vendored **core spine** of cross-cutting engineering
discipline (the optimal-path ethic, proof and verification, testing discipline,
systematic debugging, commits, and git process). That spine lives in
[`core/`](core/); read it first, the Swift rules build on it and do not
repeat it.
This page is the overview. The full per-area Swift rules live at the repository
root (index at [`README.md`](README.md)), and the cross-cutting rules in
[`core/`](core/).
## Language
Swift for everything. The single exception is JavaScript, and only where it is
intrinsic to the output: client-side tiles (Mermaid, charts, forms, polls) emit
HTML and JS that run in the visitor's browser. JS is never used for build logic or
tooling.
## Engineering principles
1. **Optimal over fast.** Respect existing code and idioms. Clarify ambiguity
before coding rather than assuming requirements. When a real trade-off exists,
surface two or three options instead of guessing.
2. **Progressive architecture.** Start with the simplest thing that works. A
protocol is earned by a real cross-target seam or by multiple implementations;
do not wrap a lone same-target concrete type preemptively. Generalise only when
a pattern has actually emerged.
3. **Make impossible states unrepresentable.** Use exhaustive enums with
associated values. Never force-unwrap (`!`, `try!`) in shipping code. Errors
carry both a human-readable reason and an actionable recovery path.
4. **Testable by design.** Inject every collaborator through the initialiser. Test
behaviour through the public API, not implementation details.
5. **Profile, then optimise.** Value semantics by default. Pick the right data
structure first. Optimise only with a profile in hand.
## Dependency injection
- **No singletons.** No `static let shared`, no process-wide config reached
through static accessors. Every dependency appears at the `init` site so
coupling is visible, testable, and removable.
- **Every external collaborator goes through `init`.** Not method parameters at
the call site, not static fallbacks, not environment lookup. Pure free functions
that compute from their arguments are fine.
- **Cross-module seams are protocols, not concrete imports.** A library module
does not reach into another module's concrete types. The executable (the
composition root) is the only place that wires concretes together.
- **No closure typealiases for named cross-module contracts.** Use a named
protocol. Closures as ordinary method parameters (`onProgress:`) are fine.
## Namespacing and file layout
- **Every public implementation type lives under an `enum`/`struct` namespace
that mirrors its folder.** The only top-level public contracts are protocols
and supporting value/null/preview types in the foundation-only
`SharedProtocols` target, where the module itself is the namespace. Reading
`Module.Sub.Leaf` should tell you where every other type lives and what it does.
- **Namespace anchors are caseless `enum`.** Use `struct` only when the type is
also a value. Never `class` for a namespace. Shared mutable state is an `actor`
or an injected value, never a `class`.
- **Drop redundant context.** Under `Availability`, the error is `Availability.Error`,
not `AvailabilityError`.
- **Concrete types are declared via extensions** on the leaf namespace.
Conformances may be separate extensions on the qualified path.
- **One non-private type per file.** Private helper types may co-locate when they
exist only to support the main type.
- **File naming.** A file declaring `extension Foo.Bar { public struct X }` is
named `Foo.Bar.X.swift` (dots, matching the qualified name). Anchor files
contain only the namespace declaration, no implementation.
## Concurrency
- Swift 6 strict concurrency is on. Types crossing concurrency boundaries are
`Sendable`; prove it, do not silence it with `@unchecked` unless you have a
documented reason.
- Shared mutable state goes in an `actor`. UI-affine state is `@MainActor`.
- Use structured concurrency. No arbitrary `Task.sleep` to paper over ordering.
## Cross-platform
Tiledown builds and runs on Apple platforms and Linux, with Windows where explicitly noted. The TileKit engine itself targets macOS and Linux; the broader cross-platform discipline (and the CI matrix in [cross-platform.md](cross-platform.md)) extends to Windows for code that opts into it.
- Guard platform-divergent code and abstract platform-specific dependencies behind
a protocol seam: one implementation per platform, wired by the composition root.
Subprocess use is allowed.
- Prefer pure-Swift implementations over Foundation-only conveniences in the core.
- On the server side, prefer cross-platform libraries (for example AsyncHTTPClient
over URLSession, swift-log over os.log).
## Testing
- Use the **Swift Testing** framework for unit and integration tests: `@Test`,
`@Suite`, `#expect`. Use XCTest only where the platform harness requires it,
notably XCUITest, or while migrating an existing suite that cannot move in one
change.
- One behaviour per test. Descriptive names. Deterministic data. No live network
or filesystem dependence; inject test doubles.
- One test target per source target, named `<SourceTarget>Tests`.
- Use parameterised tests (`@Test(arguments:)`) for families of similar cases.
## Verification before "done"
The discipline lives in [`core/verification.md`](core/verification.md): no
claim of done, fixed, or passing without fresh command output in the same response.
For Swift, match the command to the claim:
| Claim | Command |
|---|---|
| Build succeeds | `swift build` |
| Tests pass | `swift test` (cite the count, e.g. `42 / 42`) |
| Bug fixed | the test that reproduced it, now passing |
"Looks good" and "should pass" are not evidence.
## Commits, branches, PRs
See [`core/commits.md`](core/commits.md) and
[`core/git-discipline.md`](core/git-discipline.md), and the project's
[CONTRIBUTING.md](CONTRIBUTING.md). In short: Conventional Commits, one focused
change per PR, a `CHANGELOG.md` entry for any change touching shipping source, and
no AI attribution or em dashes in any committed text.