Skip to content

Generated code is hardcoded public — no way to scope tokens for use inside a redistributable package #16

Description

@rozd

Context

I'm embedding ThemeKit inside a Swift Package that is itself a library (an SDK consumed by third-party app developers), not a top-level app. The generated theme should stay an internal implementation detail of the SDK — it must not leak into, or collide with, the consuming app's own symbols.

Problem

Every generator hardcodes public, and the root type name is a fixed literal. E.g.:

EnvironmentThemeGenerator.swift:

nonisolated extension EnvironmentValues {
    @Entry public var theme: Theme = .default
}

ThemeStructGenerator.swift:

nonisolated public struct Theme: Sendable, Codable, Equatable { ... }

Same pattern in CategoryStructGenerator and ShapeStyleExtensionGenerator. ThemeConfig.GenerationConfig only exposes outputPath and shouldGeneratePreview — there's no way to opt into a lower access level or rename the root type via theme.json.

Why this matters for library authors

  1. Bare-name collision. Theme is a very generic public name. If the host app (or any other dependency in its graph) also declares a top-level Theme, every unqualified use of Theme anywhere the two are simultaneously visible becomes ambiguous — a compile break caused purely by importing the SDK.
  2. EnvironmentValues.theme collision. If the host app also uses ThemeKit for its own theming, it generates its own public var theme: Theme in its module. Two extensions now grant .theme on EnvironmentValues with different concrete return types — some call sites resolve fine via inference, others (implicit-type usage, generic contexts) become ambiguous or just confusing (autocomplete shows two unrelated Theme types).
  3. Unwanted API-surface leakage. Even without a literal collision, a library's internal styling tokens (.onSurface, .primaryColor, etc.) become part of its public interface just by having generated the theme — when the intent was almost always for the theme to be a private implementation detail.

Proposed fix

Add to GenerationConfig (theme.json's config section):

  • "accessLevel": "internal" | "public" (default "public" to preserve current behavior) — threaded through every generator (ThemeStructGenerator, EnvironmentThemeGenerator, CategoryStructGenerator, ShapeStyleExtensionGenerator, CopyWithGenerator, etc.) in place of the hardcoded public keyword.
  • Optionally, a "rootTypeName" override (default "Theme") so the generated root struct can be renamed to something collision-resistant (e.g. ConsentTheme) without hand-editing generated files.

Today the workaround is hand-editing every generated file after each run (rename Theme → something project-specific, drop public → default internal), which has to be redone on every regeneration. A config-level switch would make ThemeKit safe to embed in a redistributable package out of the box.

Happy to help sketch/PR this if useful — just wanted to raise it as an issue first since it touches several generator files.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions