Skip to content

Changes from Swift-App-Template#125

Merged
leogdion merged 4 commits into
v0.0.5from
swift-app-template
Jun 11, 2026
Merged

Changes from Swift-App-Template#125
leogdion merged 4 commits into
v0.0.5from
swift-app-template

Conversation

@leogdion

@leogdion leogdion commented Apr 2, 2026

Copy link
Copy Markdown
Member

No description provided.

@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c095c3a8-cdf2-442f-a2aa-9725483918ef

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch swift-app-template

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@leogdion leogdion marked this pull request as draft April 2, 2026 15:13
@claude

claude Bot commented Apr 2, 2026

Copy link
Copy Markdown

PR Review: Changes from Swift-App-Template

Thanks for the additions! This PR brings several useful features: access modifier support for Class, Function, and a new Initializer type; IfCanImport for conditional compilation blocks; and a buildAttributeArgumentExpr helper for proper string-literal attribute arguments. Overall the approach is sound and fits the existing patterns well. A few things worth addressing:


Bugs / Correctness

1. Initializer has no parameter support (significant limitation)

Initializer.swift hardcodes an empty parameter list:

parameters: FunctionParameterListSyntax([]),

This means every generated init will always be init(). Real-world initiailzers almost always have parameters. This should either be implemented before merging or explicitly noted as a known limitation with a follow-up task.

2. buildAttributeArgumentExpr string detection is fragile

if argument.hasPrefix("\"") && argument.hasSuffix("\"") && argument.count >= 2 {
    let content = String(argument.dropFirst().dropLast())

This heuristic fails on:

  • "hello"world" — treated as a string literal with content hello"world
  • "\"" — content becomes \" rather than the escaped quote character
  • Strings that are exactly "\"" (a string with one double-quote char)

A more robust approach would be to use String's initializer from a Substring after checking that both the first and last chars are " and the inner content doesn't contain unescaped quotes, or alternatively accept typed arguments (enum AttributeArgument { case identifier(String); case string(String) }).

3. Force unwrap in testPublicFinalClass

let publicRange = generated.range(of: "public")!
let finalRange = generated.range(of: "final")!

Force unwrapping in tests causes a crash rather than a clean test failure. Prefer:

let publicRange = try #require(generated.range(of: "public"))
let finalRange = try #require(generated.range(of: "final"))

Code Quality

4. open + final combination is not guarded

Class now allows .access(.open).final(), which produces open final class — invalid Swift (the compiler rejects it since open is the opposite of final). Consider either adding a runtime assertion or documenting this as a caller responsibility.

5. Inconsistent trivia fix for Import vs other declarations

Import.swift correctly uses .space (so @testable import gets the right spacing), while all other declarations use .newline. This is intentional and correct, but the asymmetry is easy to get wrong for future contributors. A brief comment explaining why Import differs would help.

6. Non-deterministic test assertions in AttributeTests

#expect(generated.contains("@Suite(\"App Model\")") || generated.contains("@Suite( \"App Model\")"))

The || suggests the output format isn't stable. Tests should assert one exact form; if there's a formatting choice, make it deterministic in buildAttributeArgumentExpr.


Missing Coverage

7. IfCanImport has no tests

The new IfCanImport type has zero test coverage. At minimum, a test asserting that:

IfCanImport("Foundation") { Import("Foundation") }

produces:

#if canImport(Foundation)
import Foundation
#endif

would prevent regressions.

8. No test for async + throws Initializer

testThrowingInit and testAsyncInit exist, but there's no test for .async().throwing() combined on an Initializer, which exercises the effectSpecifiers path more fully.


Minor

  • IfCanImport.swift and Initializer.swift use public import SwiftSyntax while AttributeArgument.swift uses import SwiftSyntax. Given neither exposes SwiftSyntax types in their public API, the public qualifier is unnecessary on the imports in those files (though following existing codebase convention is fine if that's the pattern elsewhere).
  • Function.throwing() just calls \throws`()` — the backtick call site is fine internally, but the alias is a good ergonomics addition for callers.

Summary

Severity Issue
🔴 Major Initializer missing parameter support
🟠 Medium buildAttributeArgumentExpr fragile for edge cases
🟠 Medium No tests for IfCanImport
🟡 Minor Force unwrap in testPublicFinalClass
🟡 Minor open + final combination not guarded
🟡 Minor Non-deterministic `

The access modifier work and the throwing() alias are clean. The biggest concern before merging is the parameterless Initializer — if it's intentional (MVP), it should be tracked explicitly.

@codecov

codecov Bot commented Apr 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.37037% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.20%. Comparing base (9f30391) to head (d77a56f).

Files with missing lines Patch % Lines
Sources/SyntaxKit/Declarations/PoundIf.swift 84.31% 8 Missing ⚠️
Sources/SyntaxKit/Declarations/Enum.swift 0.00% 3 Missing ⚠️
Sources/SyntaxKit/Declarations/Extension.swift 0.00% 3 Missing ⚠️
...ces/SyntaxKit/Declarations/PoundIf+Rendering.swift 94.91% 3 Missing ⚠️
Sources/SyntaxKit/Declarations/Protocol.swift 0.00% 3 Missing ⚠️
Sources/SyntaxKit/Declarations/Import.swift 33.33% 2 Missing ⚠️
...urces/SyntaxKit/Declarations/InitializerDecl.swift 97.14% 2 Missing ⚠️
...ces/SyntaxKit/Declarations/PoundIf+Condition.swift 81.81% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           v0.0.5     #125      +/-   ##
==========================================
+ Coverage   78.54%   80.20%   +1.66%     
==========================================
  Files         147      153       +6     
  Lines        4978     5209     +231     
==========================================
+ Hits         3910     4178     +268     
+ Misses       1068     1031      -37     
Flag Coverage Δ
spm 79.96% <90.37%> (+1.79%) ⬆️
swift-6.1 79.98% <90.37%> (+1.79%) ⬆️
swift-6.2 80.09% <90.37%> (+1.70%) ⬆️
swift-6.3 80.08% <90.37%> (+1.68%) ⬆️
ubuntu 80.19% <90.37%> (+1.66%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread Sources/SyntaxKit/Declarations/Initializer.swift Outdated
Comment thread Sources/SyntaxKit/Declarations/IfCanImport.swift Outdated
Base automatically changed from v0.0.4 to main April 6, 2026 18:57
@leogdion leogdion changed the base branch from main to v0.0.5 June 10, 2026 23:22
leogdion and others added 2 commits June 10, 2026 20:58
feat: Swift App Template v1.0.0-beta.1 (#11)

- Copy and restructure MonthBar infrastructure into template layout
- Replace all hardcoded values with `{{TEMPLATE_*}}` placeholder tokens
- Add `Packages/TemplateGenerator` — Swift executable that generates app skeleton files using SyntaxKit
- Add `Scripts/setup.sh` — interactive/automated setup with 19 token substitutions, Swift file generation, CI activation, full-stack mode, cleanup, and git reset
- Add `README.md`, `README.template.md`, and `CLAUDE.template.md` template documentation
- Move `workflows/app.yml` → `.github/app.workflow.yml` so GitHub skips parsing it until `setup.sh` activates it
- Add `.github/workflows/validate-template.yml` — CI that confirms placeholder tokens haven't been replaced on the template repo
- Replace placeholder-specific icon assets with a generic gradient placeholder
- Sync `_reference/MonthBar` to latest upstream
- Add `_reference/AtLeast` — iOS/watchOS companion app reference with multi-platform Fastlane and dynamic CI matrix
- Extract `private_lane :setup_api_key` in Fastfile; add `sync_build_number`, `sync_last_release`, `upload_metadata`, `upload_privacy_details`, `submit_for_review` lanes
- Add `pull_request` trigger, concurrency group, and dynamic matrix to CI workflow
- Split `build-macos` into always-run and full-matrix jobs; gate Windows/Android on cross-platform condition
- Add conditional server test jobs wrapped in `FULL_STACK_ONLY` markers for `setup.sh` processing
- Bump actions: `checkout@v6`, `swift-build@v1.5.2`, `swift-coverage-action@v5`, `codecov-action@v6`, `mise-action@v4`
- Prefix all Makefile `fastlane` calls with `mise exec --`; add screenshot targets
- Expand CLAUDE.md Commands and Code Signing sections with new Fastlane lanes and make targets
- Add automation guide TODO tracker linked to issues #28#50
- Fix `bundle install` and git section in `setup.sh`; harden with `printf -v`, required-field validation, and `swift` availability check
- Replace `keywords.txt` with lorem ipsum placeholder to force users to update before App Store submission
- Bump Dockerfile base image from `swift:6.0-jammy` to `swift:6.3-noble`
- Add `client` to `openapi-generator-config.yaml` generate list
- Remove LFS exclusion section from `.gitattributes`

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Resolve all swift-format and swiftlint findings in the V1.0.0 code and
make Scripts/lint.sh self-recover when mise.toml is untrusted.

- lint.sh: trust + install via mise before sourcing its env, so the
  mise-managed tools (swiftlint, periphery) land on PATH instead of
  silently no-op'ing on a fresh/untrusted mise.toml.
- IfCanImport: add doc comment on `syntax`, order instance properties
  before the initializer, and flatten nested calls to satisfy
  multiline_arguments_brackets.
- Class: split modifier methods into Class+Modifiers.swift (mirroring
  Function/Function+Modifiers.swift) to clear file_length; stored
  properties widened private -> internal to match.
- ClassTests / AttributeTests: split into parent @suite namespace enums
  with per-category child suites to clear file_length; replace force
  unwraps with try #require.

swift build + 468 tests pass; lint.sh completes clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@leogdion leogdion force-pushed the swift-app-template branch from d680855 to 1fd6c9e Compare June 11, 2026 01:19
Comment thread Scripts/lint.sh
Resolve all three review comments on PR #125.

- Rename Initializer -> InitializerDecl (mirrors InitializerDeclSyntax)
  to disambiguate from the existing Init expression DSL. Updates the
  matching test struct and call sites.
- Replace IfCanImport with a generic PoundIf #if/#elseif/#else/#endif
  block (split across PoundIf.swift, PoundIf+Condition.swift, and
  PoundIf+Rendering.swift). PoundIf.Condition covers canImport, flag,
  os, arch, targetEnvironment, swift, compiler, hasFeature, hasAttribute,
  and boolean combinators (and/or/not). Also accepts raw String and
  any CodeBlock conditions plus .elseif / .else modifiers. Delivers the
  core of #140 under the PoundIf name; see issue #140 for the SwiftSyntax
  pound taxonomy and the boundary against MacroExp (#141).
- New PoundIfTests covers each helper case, raw/CodeBlock conditions,
  elseif/else chaining, and the former IfCanImport shape (17 tests).
- Revert the mise-trust/install block added to Scripts/lint.sh in
  1fd6c9e per review request.

swift build + 485 tests pass; swift-format and swiftlint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@leogdion leogdion marked this pull request as ready for review June 11, 2026 18:00
…& params

- Convert global buildAttributeArgumentExpr(from:) to an ExprSyntax
  initializer (ExprSyntax(attributeArgument:)), satisfying the
  "no global functions" convention; update all 8 call sites and rename
  the file to ExprSyntax+AttributeArgument.swift.
- InitializerDecl: render `init() async throws` with single spacing
  (drop redundant async trailing space) and use throwsClause instead of
  the deprecated throwsSpecifier.
- InitializerDecl: support parameters via a @ParameterBuilderResult
  overload, reusing FunctionParameterSyntax.create.
- Add regression tests for async-throws spacing and init parameters.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jun 11, 2026

Copy link
Copy Markdown

Code Review — PR #125: Changes from Swift-App-Template

Overview: This PR adds InitializerDecl (a new init declaration builder), PoundIf (full #if / #elseif / #else / #endif support with typed conditions), ExprSyntax+AttributeArgument (fix for string-literal attribute arguments), access-modifier support on Class and Function, a .throwing() alias on Function, and a significant test-file refactor that promotes flat test structs to namespaced enum suites.


✅ What's Working Well

  • PoundIf condition model is thorough — the indirect enum Condition covers all Swift conditional-compilation grammar variants and the three-form ConditionForm (typed helper / raw string / arbitrary CodeBlock) gives callers a clean escape hatch without leaking internals.
  • ExprSyntax+AttributeArgument cleanly fixes the @Suite(App Model) vs @Suite("App Model") regression; the prefix/suffix quote detection is simple and the test assertions (positive and negative) are solid.
  • .throwing() alias on Function is a pragmatic UX improvement that avoids the need to write .\`throws\`() at every call site.
  • Test-namespace refactor — converting struct ClassTests / struct AttributeTests into enum ClassTests / enum AttributeTests and splitting by + file is a clean, scalable pattern.
  • Attribute trailing-trivia fixes (.newline / .space added consistently across Class, Enum, Extension, Import, Protocol, Struct, Function, Variable) are a coherent batch; the @testable import space fix is verified by testImportWithTestableAttribute.

🐛 Bugs / Correctness

1. PoundIf.parseExpression returns nil silently → can emit bare #if
PoundIf+Rendering.swift:parseExpression(_:) returns nil if the source string contains no expression statement. makeClause then emits IfConfigClauseSyntax with no condition, producing syntactically invalid #if\n...#endif. This most likely surfaces with the .raw("") escape hatch or a malformed CodeBlock condition.

// Suggestion: assert or log in debug builds
private static func parseExpression(_ source: String) -> ExprSyntax? {
    let file = Parser.parse(source: source)
    for item in file.statements {
        if let expr = item.item.as(ExprSyntax.self) { return expr }
    }
    // assertionFailure("PoundIf: could not parse condition: \(source)")
    return nil
}

2. renderCombinator default falls through to ""
The default: arm in renderCombinator is unreachable in practice (leaf cases are handled by renderLeaf), but returns "" rather than trapping. A silent empty string in a condition is hard to debug.

// Prefer:
default:
    assertionFailure("Unexpected leaf condition in renderCombinator")
    return ""

3. InitializerDecl body compactMap silently drops items
Items that don't cast to DeclSyntax, ExprSyntax, or StmtSyntax are silently dropped. In practice this shouldn't happen with well-formed CodeBlock implementations, but a missing-conversion is easy to miss during development.


⚠️ Design / API Concerns

4. privateinternal on Class stored properties

// Class.swift
-  private let name: String
+  internal let name: String

This widens the module-internal API surface without explanation. If these are internal only to enable @testable import SyntaxKit in tests, the narrower fix is to keep them private and use a test-only computed accessor or fileprivate extension. As-is, any code inside the module can now mutate accessModifier, isFinal, etc. directly, bypassing the copy-on-write builder pattern.

5. ExprSyntax(attributeArgument:) string detection is fragile at the edges
argument.hasPrefix("\"") && argument.hasSuffix("\"") && argument.count >= 2 will accept "\"" (a lone escaped double-quote) and will strip the surrounding quotes from multi-escape strings like "\"foo\"", yielding content \"foo\" (with literal backslashes). For v0 this is likely fine, but it should be documented in the function comment, and a argument.count >= 2 guard currently allows "" (an empty double-quote pair) which is valid but worth noting.

6. Misleading test name: testClassWithMultipleInheritance
In ClassTests+Declarations.swift, this test only passes one type ("Vehicle"). Either rename to testClassWithInheritance (it duplicates the existing test of that name, which is now also moved) or expand it to test multiple conformances.


🔧 Minor Style / Convention

7. import Foundation in test files where it's unused
Several new test files (AttributeTests+Arguments.swift, ClassTests+Declarations.swift, ClassTests+Modifiers.swift, InitializerDeclTests.swift, PoundIfTests.swift) import Foundation even though they don't use any Foundation types. Periphery or SwiftLint unused-import rules will flag these.

8. Function.throwing() implementation uses implicit return

public func throwing() -> Self {
    `throws`()
}

This is valid Swift but the lack of an explicit return is easy to miss on first read. Either add return or add a one-line comment indicating it's an intentional alias (CLAUDE.md says to omit comments unless the WHY is non-obvious — the doc comment on the method already covers it, so this is minor).

9. Missing init? (failable initializer) support in InitializerDecl
Not a bug, but worth tracking. Callers generating decodable types or factory inits will encounter this soon. Consider adding a .failable() modifier that switches InitializerDeclSyntax.optionalMark to .postfixQuestionMarkToken().

10. @Suite on test file with internal access
ImportTests is declared internal struct ImportTests without a @Suite(...) decorator, while the refactored suites use @Suite("..."). For consistency, consider adding @Suite("Import") (Swift Testing will otherwise use the default type name).


📊 Test Coverage

New feature Test file Notes
InitializerDecl InitializerDeclTests.swift Good: empty, public, async, throws, params, defaults, async+throws ordering
PoundIf PoundIfTests.swift Good: all condition cases + elseif + else chains
Class.access() ClassTests+Modifiers.swift Good: ordering assertion (public before final)
ExprSyntax(attributeArgument:) AttributeTests+Arguments.swift Good: regression tests for both quoted and unquoted
Function.access() / .throwing() FunctionTests.swift Adequate
Import trivia fix ImportTests.swift Good: positive + negative assertions

No integration-level tests covering PoundIf nested inside a type declaration (e.g., #if canImport(SwiftUI) inside a Struct body) — worth adding one to catch trivia edge cases at type scope.


Overall: The PR is in good shape. The PoundIf implementation is well-structured, the attribute-argument fix is correct, and the test refactor improves maintainability. The main items to address before merging are the silent nil condition path (#1), the unexplained private → internal widening (#4), and the duplicate / misleading test name (#6).

@leogdion leogdion merged commit 646021d into v0.0.5 Jun 11, 2026
42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant