Skip to content

[ANE-2911] Swift Version syntax#1699

Open
tjugdev wants to merge 6 commits intomasterfrom
ane-2911-swift-version-syntax
Open

[ANE-2911] Swift Version syntax#1699
tjugdev wants to merge 6 commits intomasterfrom
ane-2911-swift-version-syntax

Conversation

@tjugdev
Copy link
Copy Markdown
Contributor

@tjugdev tjugdev commented Apr 22, 2026

Overview

Swift lets a version string be defined as either a string, or as an instance of Version (docs). The parser did not previously support this version syntax, and this PR adds support for it.

Note: Package.swift files are just Swift files, supporting the full Swift grammar. Since we're not going to write a full Swift interpreter, we're just choosing to support a reasonable subset of the Swift grammar in a limited context in order to parse most Package.swift files. For complicated Package.swift files, other strategies will be more suitable, such as parsing the JSON Package.resolved.

Acceptance criteria

Package.swift files using the Version(...) syntax for versions can be parsed.

Testing plan

  • Package.swift files linked in the ticket can now be analyzed
  • New tests added with the new syntax.

Risks

Metrics

References

  • ANE-2911: Handle Swift Version(...) syntax

Checklist

  • I added tests for this PR's change (or explained in the PR description why tests don't make sense).
  • If this PR introduced a user-visible change, I added documentation into docs/.
  • If this PR added docs, I added links as appropriate to the user manual's ToC in docs/README.ms and gave consideration to how discoverable or not my documentation is.
  • If this change is externally visible, I updated Changelog.md. If this PR did not mark a release, I added my changes into an ## Unreleased section at the top.
  • If I made changes to .fossa.yml or fossa-deps.{json.yml}, I updated docs/references/files/*.schema.json AND I have updated example files used by fossa init command. You may also need to update these if you have added/removed new dependency type (e.g. pip) or analysis target type (e.g. poetry).
  • If I made changes to a subcommand's options, I updated docs/references/subcommands/<subcommand>.md.

@tjugdev tjugdev requested a review from a team as a code owner April 22, 2026 21:55
@tjugdev tjugdev requested a review from csasarak April 22, 2026 21:55
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

Walkthrough

This pull request extends the Swift package manifest parser to recognize and handle version requirements expressed using SwiftPM's Version(...) constructor syntax, in addition to existing plain quoted version strings. The implementation introduces new SwiftVersion and SwiftVersionPart type definitions, updates the parseRequirement function to accept both quoted text and structured Version(...) constructor forms, and adds necessary parsing utilities. Accompanying changes include updated test fixtures and test data files that exercise the new version-constructor handling with prerelease identifiers and build metadata. Documentation is updated with a changelog entry describing the new functionality.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'ANE-2911 Swift Version syntax' clearly describes the main change: adding support for Swift's Version(...) syntax in package declarations.
Description check ✅ Passed The PR description covers the required template sections: Overview (what and why), Acceptance criteria (parsing Version(...) syntax), Testing plan (files and new tests), References (ANE-2911), and includes the checklist items.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Strategy/Swift/PackageSwift.hs`:
- Around line 98-140: The SwiftVersion type currently allows any number of
positional parts; change the model and parser to enforce exactly three numeric
components (major, minor, patch) and preserve prerelease/build lists: replace
the parts :: [Text] field with explicit major/minor/patch fields (or ensure
assembleParts validates exactly three Component values) and update
parseVersionConstructor/parseParts/parseVersionArgument/assembleParts to reject
inputs with fewer or more than three numeric components (returning a parse
failure) while still parsing PrereleaseIdentifiers and BuildMetadataIdentifiers
as before; also update toText (ToText SwiftVersion) to format from the three
numeric fields.
- Around line 120-127: parseVersionArgument currently drops unknown labeled keys
but leaves their values unconsumed, breaking later parsing; update the wildcard
branch so that when key is neither "prereleaseIdentifiers" nor
"buildMetadataIdentifiers" you explicitly parse-and-discard the corresponding
value before returning Nothing. Concretely, in parseVersionArgument replace `_
-> pure Nothing` with a branch that attempts to consume parseStringArray (for
bracketed arrays) or a single token/value (e.g. digits/quoted string/identifier
via the same lexeme/character parsers used elsewhere such as digitChar or
takeWhile1P) and ignore the result, then return pure Nothing; this ensures
parseVersionArgument (and the sepEndBy1 loop that calls it) will not leave the
value for the next iteration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: c176a8fb-a347-4b83-8d44-1302ab889b22

📥 Commits

Reviewing files that changed from the base of the PR and between 1f01ec7 and 9241e36.

📒 Files selected for processing (5)
  • Changelog.md
  • src/Strategy/Swift/PackageSwift.hs
  • test/Swift/PackageSwiftSpec.hs
  • test/Swift/testdata/Package.full.swift
  • test/Swift/testdata/Package.swift

Comment on lines +98 to +140
-- | Represents https://developer.apple.com/documentation/packagedescription/version
data SwiftVersion = SwiftVersion
{ parts :: [Text]
, prereleaseIdentifiers :: [Text]
, buildMetadataIdentifiers :: [Text]
}

instance ToText SwiftVersion where
toText SwiftVersion{..} = version <> prerelease <> build
where
version = (intercalate "." parts)
prerelease = if not $ null prereleaseIdentifiers then "-" <> (intercalate "." prereleaseIdentifiers) else ""
build = if not $ null buildMetadataIdentifiers then "+" <> (intercalate "." buildMetadataIdentifiers) else ""

data SwiftVersionPart = Component Text | PrereleaseIdentifiers [Text] | BuildMetadataIdentifiers [Text]

parseVersionConstructor :: Parser SwiftVersion
parseVersionConstructor = (symbol "Version") >> assembleParts <$> parseParts
where
parseParts :: Parser [SwiftVersionPart]
parseParts = betweenBrackets $ catMaybes <$> sepEndBy1 parseVersionArgument (symbol ",")

parseVersionArgument :: Parser (Maybe SwiftVersionPart)
parseVersionArgument = do
key <- (optional . try) (lexeme $ takeWhile1P (Just "package key") (`notElem` (":," :: String)) <* symbol ":")
case key of
Nothing -> (Just . Component) . toText <$> some digitChar
Just ("prereleaseIdentifiers") -> (Just . PrereleaseIdentifiers) <$> parseStringArray
Just ("buildMetadataIdentifiers") -> (Just . BuildMetadataIdentifiers) <$> parseStringArray
_ -> pure Nothing

parseStringArray :: Parser [Text]
parseStringArray = betweenSquareBrackets (sepEndBy parseQuotedText (symbol ","))

assembleParts :: [SwiftVersionPart] -> SwiftVersion
assembleParts =
foldl'
( \version part -> case part of
Component p -> version{parts = (parts version) ++ [p]}
PrereleaseIdentifiers p -> version{prereleaseIdentifiers = p}
BuildMetadataIdentifiers p -> version{buildMetadataIdentifiers = p}
)
(SwiftVersion{parts = [], prereleaseIdentifiers = [], buildMetadataIdentifiers = []})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Optional: tighten SwiftVersion invariants.

SwiftVersion models parts as an arbitrary [Text], but Swift's Version always has exactly three numeric components (major, minor, patch). The current shape lets the parser silently accept malformed calls like Version(1, 2) or Version(1, 2, 3, 4) and round-trip them through toText, producing versions that won't resolve downstream. Consider modeling it as SwiftVersion { major, minor, patch, prereleaseIdentifiers, buildMetadataIdentifiers } and enforcing the three positional components during parsing. Not blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Strategy/Swift/PackageSwift.hs` around lines 98 - 140, The SwiftVersion
type currently allows any number of positional parts; change the model and
parser to enforce exactly three numeric components (major, minor, patch) and
preserve prerelease/build lists: replace the parts :: [Text] field with explicit
major/minor/patch fields (or ensure assembleParts validates exactly three
Component values) and update
parseVersionConstructor/parseParts/parseVersionArgument/assembleParts to reject
inputs with fewer or more than three numeric components (returning a parse
failure) while still parsing PrereleaseIdentifiers and BuildMetadataIdentifiers
as before; also update toText (ToText SwiftVersion) to format from the three
numeric fields.

Comment thread src/Strategy/Swift/PackageSwift.hs Outdated
Comment on lines +120 to +127
parseVersionArgument :: Parser (Maybe SwiftVersionPart)
parseVersionArgument = do
key <- (optional . try) (lexeme $ takeWhile1P (Just "package key") (`notElem` (":," :: String)) <* symbol ":")
case key of
Nothing -> (Just . Component) . toText <$> some digitChar
Just ("prereleaseIdentifiers") -> (Just . PrereleaseIdentifiers) <$> parseStringArray
Just ("buildMetadataIdentifiers") -> (Just . BuildMetadataIdentifiers) <$> parseStringArray
_ -> pure Nothing
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unknown keyword arguments silently drop the value and will break parsing.

When parseVersionArgument matches a keyed argument whose name is neither prereleaseIdentifiers nor buildMetadataIdentifiers, the _ -> pure Nothing branch consumes the key: prefix but leaves the value unconsumed. The next iteration of sepEndBy1 will then attempt to parse the leftover value (e.g. a [...] array or quoted string) as a new positional argument and fail, even though from the user's perspective the Version(...) call is well-formed.

Today SwiftPM's Version initializer has a fixed signature (major, minor, patch, prereleaseIdentifiers, buildMetadataIdentifiers) so this isn't reachable in practice, but it makes the parser brittle against future additions and against any unexpected label. Consider consuming and discarding the value for unknown keys so parsing continues.

🔧 Suggested fix
     parseVersionArgument :: Parser (Maybe SwiftVersionPart)
     parseVersionArgument = do
       key <- (optional . try) (lexeme $ takeWhile1P (Just "package key") (`notElem` (":," :: String)) <* symbol ":")
       case key of
         Nothing -> (Just . Component) . toText <$> some digitChar
         Just ("prereleaseIdentifiers") -> (Just . PrereleaseIdentifiers) <$> parseStringArray
         Just ("buildMetadataIdentifiers") -> (Just . BuildMetadataIdentifiers) <$> parseStringArray
-        _ -> pure Nothing
+        _ -> Nothing <$ (parseStringArray $> () <|> void parseQuotedText <|> void (some digitChar))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Strategy/Swift/PackageSwift.hs` around lines 120 - 127,
parseVersionArgument currently drops unknown labeled keys but leaves their
values unconsumed, breaking later parsing; update the wildcard branch so that
when key is neither "prereleaseIdentifiers" nor "buildMetadataIdentifiers" you
explicitly parse-and-discard the corresponding value before returning Nothing.
Concretely, in parseVersionArgument replace `_ -> pure Nothing` with a branch
that attempts to consume parseStringArray (for bracketed arrays) or a single
token/value (e.g. digits/quoted string/identifier via the same lexeme/character
parsers used elsewhere such as digitChar or takeWhile1P) and ignore the result,
then return pure Nothing; this ensures parseVersionArgument (and the sepEndBy1
loop that calls it) will not leave the value for the next iteration.

@tjugdev tjugdev force-pushed the ane-2911-swift-version-syntax branch from f78d89a to a6ad085 Compare April 22, 2026 22:22
@tjugdev tjugdev force-pushed the ane-2911-swift-version-syntax branch from a6ad085 to ec8283f Compare April 23, 2026 15:21
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