Skip to content

Lint: Enforce no-new-public-enums policy via SwiftLint custom rule#6778

Merged
ajpallares merged 10 commits into
mainfrom
pallares/ci-enforce-no-new-public-enums
May 29, 2026
Merged

Lint: Enforce no-new-public-enums policy via SwiftLint custom rule#6778
ajpallares merged 10 commits into
mainfrom
pallares/ci-enforce-no-new-public-enums

Conversation

@ajpallares

@ajpallares ajpallares commented May 13, 2026

Copy link
Copy Markdown
Member

Checklist

  • If applicable, unit tests
  • If applicable, create follow-up issues for purchases-android and hybrids

Motivation

Internal policy forbids new public enum declarations in the SDK's consumer-facing surface (incl. @_spi(Experimental)): adding a case is source-breaking for any consumer with an exhaustive switch. Parallels purchases-android#3503.

Description

New SwiftLint custom rule no_new_public_enums flags new public enums except those marked @_spi(Internal) on the same line. Pre-existing types are grandfathered in swiftlint-baseline.json.

To intentionally accept a new violation, regenerate the baseline:

swiftlint lint --write-baseline swiftlint-baseline.json

Note

Low Risk
Tooling and documentation only; no runtime SDK behavior changes.

Overview
Adds CI enforcement for the existing “no new public enum on consumer-facing API” policy via a new SwiftLint custom rule no_new_public_enums, scoped to Sources/, RevenueCatUI/, and AdMob adapter sources. The rule errors on public enum declarations unless the declaration is immediately preceded by @_spi(Internal) on the same line.

Wires swiftlint-baseline.json into .swiftlint.yml so all current violations are grandfathered; new matches fail lint until someone intentionally regenerates the baseline with swiftlint lint --write-baseline swiftlint-baseline.json.

AGENTS.md is updated to document the rule, baseline workflow, and the @_spi(Internal) exemption alongside the existing API guidance.

Reviewed by Cursor Bugbot for commit 02f835e. Bugbot is set up for automated code reviews on this repo. Configure here.

Per company policy, no new `enum` types may be added to consumer-facing
APIs (fully public + `@_spi(Experimental)`). This adds CI-level
enforcement so the policy stops being just a guideline.

Adds a `check_public_enums` Fastlane lane that consumes the
`.private.swiftinterface` files emitted alongside the public ones,
extracts every consumer-facing `enum` declaration, and diffs against an
allowlist baseline file per scheme. Internal SPI is intentionally
excluded.

Wired into both `check-api-changes-revenuecat` and
`check-api-changes-revenuecatui` CI jobs. Allowlists seeded from the
currently-committed swiftinterface files (45 entries for RevenueCat,
3 for RevenueCatUI).

To intentionally add a new consumer-facing enum (with API-council
approval), regenerate the allowlist:

  bundle exec fastlane ios check_public_enums scheme:RevenueCat regenerate:true

Co-authored-by: Cursor <cursoragent@cursor.com>
ajpallares and others added 4 commits May 15, 2026 19:44
Adds the `no_new_public_enums` custom SwiftLint rule that flags any new
`public enum` declared in the SDK's consumer-facing surface, including
`@_spi(Experimental)` APIs. Enums marked `@_spi(Internal)` on the same
line as the declaration are exempt because they are not part of the
consumer-facing surface.

Existing public enums are grandfathered via `swiftlint-baseline.json`,
which SwiftLint reads automatically through the `baseline:` key in
`.swiftlint.yml`. Refactors that remove a baselined entry continue to
pass; only NEW violations fail the build. New entries can be added by
regenerating the baseline with `swiftlint lint --write-baseline
swiftlint-baseline.json`, which requires an explicit, reviewable JSON
change.

This mirrors the approach taken on Android in
RevenueCat/purchases-android#3503, which adds a custom detekt rule
`ForbiddenPublicSealedClass` with the equivalent semantics for sealed
classes/interfaces.

Why: adding a case to an existing `public enum` is source-breaking for
any consumer with an exhaustive `switch`. The policy avoids new public
enums entirely; structs of static constants are the recommended
alternative.

`AGENTS.md` and the `Important Files` list are updated to document the
new policy and the baseline file.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ajpallares ajpallares changed the title CI: Enforce no-new-public-enums policy for consumer-facing APIs Lint: Enforce no-new-public-enums policy via SwiftLint custom rule May 29, 2026
ajpallares and others added 5 commits May 29, 2026 09:03
Drops the `@_spi(Internal)` suggestion (not a real alternative when a
public API is needed) and the baseline mention from the user-facing
message. Regenerates `swiftlint-baseline.json` since SwiftLint baselines
key off the violation message.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replaces the previous "lint everything except Tests/" approach with an
explicit `included:` list of the actual SDK source roots: Sources/,
RevenueCatUI/, and AdapterSDKs/RevenueCatAdMob/Sources/. Sample apps,
backend integration tests, dev tooling, and similar non-SDK Swift files
are no longer in scope for this rule.

Coverage of the existing 56 grandfathered violations is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@ajpallares ajpallares marked this pull request as ready for review May 29, 2026 08:56
@ajpallares ajpallares requested a review from a team as a code owner May 29, 2026 08:56

@rickvdl rickvdl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice one!

@ajpallares ajpallares merged commit 6e51ba5 into main May 29, 2026
18 of 20 checks passed
@ajpallares ajpallares deleted the pallares/ci-enforce-no-new-public-enums branch May 29, 2026 09:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants