5 minutes read
Swift
The swift detector reports Xcode pins and iOS app deployment targets that App Store Connect no longer accepts, as declared in .xcode-version and an application's project.pbxproj. It is stable: findings were checked against ground truth on a larger corpus, the precision floor in benchmarks/thresholds.json is enforced, and it is part of the documented product contract. The maturity rules are in Decision 0013.
What it reads
| File | Declaration |
|---|---|
.xcode-version |
One pinned Xcode release on a line by itself, such as 15.4 or 14.2.0 |
.swift-version |
One pinned Swift release on a line by itself, such as 5.9 or 5.9.2 |
Package.swift |
The swift-tools-version comment on line 1, and direct .package(url:…, from:…) / .package(url:…, exact:…) dependencies with a literal version string |
Package.resolved |
Each pin under pins with a non-empty identity and a state.version string (SwiftPM lockfile v2) |
*.xcodeproj/project.pbxproj |
IPHONEOS_DEPLOYMENT_TARGET on build configurations of application targets (com.apple.product-type.application) |
Blank lines and # comments in a toolchain file are ignored. A file with more than one pin, or any non-release token (latest, stable), produces nothing. Block and line comments and string literals are stripped before .package calls are read. path:, branch:, revision:, range / .upToNextMajor requirements, multi-line .package calls, platform minimums (.iOS(.v13) and friends), and interpolated or Version(…) values are ignored. Pins with only branch / revision, the legacy v1 object.pins shape, and non-version pins produce nothing. Frameworks, static libraries, extensions, and other product types are skipped. Project-level settings, $(…) references, and non-numeric deployment targets are ignored.
It walks the repository up to eight directories deep (at most 500 matching files) and does not follow symbolic links. It skips .git, node_modules, vendor, dist, build, target, .venv, venv, fixtures, __fixtures__, and testdata. A project.pbxproj is read only when it sits inside a directory whose name ends with .xcodeproj. Swift, SwiftPM, and Xcode are never run, and nothing is fetched.
What it reports
- An
.xcode-versionpin that App Store Connect no longer accepts. The bundled rules cover Xcode older than 15 (deadline 2024-04-29) and Xcode 15 through 25 (deadline 2026-04-28), citing App Store upcoming requirements. - An application target whose
IPHONEOS_DEPLOYMENT_TARGETis below iOS 13. The bundled rule cites the iOS and iPadOS minimum system requirements (deadline 2026-09-09). - A
Package.swiftexact:pin or aPackage.resolvedversion when a rule for that SwiftPM identity'sverscontains the version. Afrom:requirement is reported only by a rule that covers every version of the package. There are no bundled package rules yet. - A
.swift-versionorswift-tools-versionpin when a rule forpkg:generic/swiftcontains that version. There are no such bundled rules yet.
Each finding names the file and line, and carries its rule's source, replacement, deadline, migration guidance, and verification commands. A package finding whose rule lists usage matchers also lists the .swift files that import that module and the qualified calls those matchers name. The finding's impact counts those usages, and a migration packet lists the files. Toolchain and App Store findings point at the pin or deployment-target line only.
What it intentionally does not judge
- Xcode releases, Swift toolchain lines, iOS deployment targets, and SwiftPM packages with no rule in
registry/swift.json. A missing finding is not evidence that a version is current. - A
.xcode-versionor.swift-versionvalue oflatestorstable, a template, or a value that interpolates at runtime. - Platform minimums in
Package.swift(platforms: [.iOS(.v12)]and similar). Those declare a library's supported OS floor, not an App Store upload target. - Deployment targets on non-application Xcode targets (frameworks, libraries, extensions, tests), including
TVOS_DEPLOYMENT_TARGET. path:,branch:,revision:, range, and.upToNextMajor/.upToNextMinorpackage requirements; multi-line.packagecalls; and dynamic version expressions.- The legacy
Package.resolvedv1 shape. - Vulnerability advisories and license policy.
Known limits and false-positive boundaries
- An
.xcode-versionpin is reported whatever the project actually builds with. A stale pin that CI no longer uses still produces a finding. - The App Store Xcode rules apply to uploads to App Store Connect. A Swift package that never ships an app can still pin an old Xcode for local builds and be reported.
- The iOS deployment-target rule names only application targets. A shared framework that still declares an older minimum is not reported, even when an app links it.
- Package identity is the lowercased last path segment of the http(s) URL without
.git. Afile://or relative URL has no identity and never matches. A renamed repository matches only under the identity SwiftPM recorded. - Malformed input produces no declarations from the affected unit, never a partial result, and no error is raised. For
.xcode-version,.swift-version,Package.swift,Package.resolved, andproject.pbxprojthat unit is the whole file. A toolchain or package file over 1 MiB yields nothing; aproject.pbxprojover 16 MiB yields nothing. A single line over 4,096 characters is skipped. - The eight-level and 500-file limits are silent. Files beyond them, and any under
vendoror a skipped directory, are not scanned. - Source usage reads
.swiftfiles. Animport Module(including@testableand similar attributes) matches a rule whose module is that name. A call matches when the rule names the qualified form. Usage is not a Swift AST: it is regex-based matching, so unusual formatting can miss an import or call. - Runtime Objective-C lookup forms (
NSClassFromString,NSProtocolFromString,NSSelectorFromString,Selector(…),objc_getClass,sel_registerName,dlsym) are listed as unclassified, with their file and line. Offing does not guess which type they resolve to. - Usage is not read under
vendor,fixtures,__fixtures__, ortestdata. Comments and ordinary strings are not usages. A single source line over 4,096 characters is skipped for usage scanning, matching the declaration parsers.
Data
The rules are in registry/swift.json and ship in the data bundle. The importer swift-1 drafts Xcode upload-floor rules from Apple's App Store upcoming-requirements listing; see Lifecycle importers.
Validation
Eighty-five public repositories at pinned commits cover the detector. Six early entries were targeted (Formalist, Troop, xc, L10nLint, simperium-ios, and JohnSundell/Files). The rest were chosen blind by the rules in Validation, including a 2026-11-07 stable-promotion batch that walked GitHub code search for .xcode-version and older IPHONEOS_DEPLOYMENT_TARGET pins before Offing ran. Each expected finding was reviewed against the cited line at that commit. Precision and recall are measured on the full public corpus and recorded with the stable promotion in benchmarks/thresholds.json.