PureXML/third_party/rules-swift/CONVENTIONS.md

6.4 KiB

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/; 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), and the cross-cutting rules in 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) 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: 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 and core/git-discipline.md, and the project's 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.