Skip to content

Commit cca51be

Browse files
committed
Fix indexing of Xcode resources in projects using file system folders. Closes #931
1 parent 5d5f49f commit cca51be

File tree

6 files changed

+99
-2
lines changed

6 files changed

+99
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- Exclude conditionally imported modules from unused import detection, as they may provide symbols for code that was not compiled.
1818
- Fix `--retain-assign-only-property-types` for properties with trailing comments.
1919
- Fix unused parameter analysis for parameters with name enclosed by backticks.
20+
- Fix indexing of Xcode resources (storyboards, etc.) in projects using file system folders.
2021

2122
## 3.2.0 (2025-06-27)
2223

Sources/XcodeSupport/XcodeTarget.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ public final class XcodeTarget {
2323
}
2424

2525
public func identifyFiles() throws {
26+
let sourceRoot = project.sourceRoot.lexicallyNormalized()
27+
let rootFileSystemFiles = try project.xcodeProject.pbxproj.fileSystemSynchronizedRootGroups.flatMapSet {
28+
if let stringPath = try $0.fullPath(sourceRoot: sourceRoot.string) {
29+
let path = FilePath(stringPath)
30+
return FilePath.glob(path.appending("**/*").string)
31+
}
32+
33+
return []
34+
}
35+
36+
try identifyFiles(kind: .xcDataModel, in: rootFileSystemFiles)
37+
try identifyFiles(kind: .xcMappingModel, in: rootFileSystemFiles)
38+
try identifyFiles(kind: .interfaceBuilder, in: rootFileSystemFiles)
39+
2640
let sourcesBuildPhases = project.xcodeProject.pbxproj.sourcesBuildPhases
2741
let resourcesBuildPhases = project.xcodeProject.pbxproj.resourcesBuildPhases
2842

@@ -42,7 +56,7 @@ public final class XcodeTarget {
4256
let targetPhases = buildPhases.filter { target.buildPhases.contains($0) }
4357
let sourceRoot = project.sourceRoot.lexicallyNormalized()
4458

45-
files[kind] = try targetPhases.flatMapSet {
59+
let foundFiles = try targetPhases.flatMapSet {
4660
try ($0.files ?? []).compactMapSet {
4761
if let stringPath = try $0.file?.fullPath(sourceRoot: sourceRoot.string) {
4862
let path = FilePath(stringPath)
@@ -54,6 +68,18 @@ public final class XcodeTarget {
5468
return nil
5569
}
5670
}
71+
files[kind, default: []].formUnion(foundFiles)
72+
}
73+
74+
private func identifyFiles(kind: ProjectFileKind, in paths: Set<FilePath>) throws {
75+
let foundFiles = paths.compactMapSet { path in
76+
if let ext = path.extension, kind.extensions.contains(ext.lowercased()) {
77+
return path
78+
}
79+
80+
return nil
81+
}
82+
files[kind, default: []].formUnion(foundFiles)
5783
}
5884

5985
private func identifyInfoPlistFiles() throws {

Tests/XcodeTests/UIKitProject/UIKitProject.xcodeproj/project.pbxproj

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 60;
6+
objectVersion = 70;
77
objects = {
88

99
/* Begin PBXBuildFile section */
@@ -13,6 +13,7 @@
1313
3C1FED1E2556D91D0001BD58 /* Target_With_Spaces.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1FED172556D91C0001BD58 /* Target_With_Spaces.framework */; };
1414
3C1FED1F2556D91D0001BD58 /* Target_With_Spaces.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1FED172556D91C0001BD58 /* Target_With_Spaces.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1515
3C1FED2B2556D9360001BD58 /* File With Spaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C1FED2A2556D9360001BD58 /* File With Spaces.swift */; };
16+
3C3057582EAE734C00D290DA /* XibViewController3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3057572EAE734800D290DA /* XibViewController3.swift */; };
1617
3C3ABC05256522AB00EAB309 /* XibViewController2Subclass.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3C3ABC04256522AB00EAB309 /* XibViewController2Subclass.xib */; };
1718
3C3ABC09256526F900EAB309 /* XibViewController2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3ABC08256526F900EAB309 /* XibViewController2.swift */; };
1819
3C5E4E58255C25D200BF728D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5E4E57255C25D200BF728D /* AppDelegate.swift */; };
@@ -100,6 +101,7 @@
100101
3C1FED172556D91C0001BD58 /* Target_With_Spaces.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Target_With_Spaces.framework; sourceTree = BUILT_PRODUCTS_DIR; };
101102
3C1FED1A2556D91D0001BD58 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
102103
3C1FED2A2556D9360001BD58 /* File With Spaces.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "File With Spaces.swift"; sourceTree = "<group>"; };
104+
3C3057572EAE734800D290DA /* XibViewController3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XibViewController3.swift; sourceTree = "<group>"; };
103105
3C3ABC04256522AB00EAB309 /* XibViewController2Subclass.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = XibViewController2Subclass.xib; sourceTree = "<group>"; };
104106
3C3ABC08256526F900EAB309 /* XibViewController2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XibViewController2.swift; sourceTree = "<group>"; };
105107
3C5E4E57255C25D200BF728D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@@ -129,6 +131,10 @@
129131
3CFFB5AA2AEF8FDE002EFB86 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
130132
/* End PBXFileReference section */
131133

134+
/* Begin PBXFileSystemSynchronizedRootGroup section */
135+
3C3057542EAE72DB00D290DA /* FileSystemFolder */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = FileSystemFolder; sourceTree = "<group>"; };
136+
/* End PBXFileSystemSynchronizedRootGroup section */
137+
132138
/* Begin PBXFrameworksBuildPhase section */
133139
3C1A70AE256BBAB300E07E4A /* Frameworks */ = {
134140
isa = PBXFrameworksBuildPhase;
@@ -236,6 +242,8 @@
236242
3C84965C255405AD00900DA9 /* UIKitProject */ = {
237243
isa = PBXGroup;
238244
children = (
245+
3C3057572EAE734800D290DA /* XibViewController3.swift */,
246+
3C3057542EAE72DB00D290DA /* FileSystemFolder */,
239247
3C849666255405B000900DA9 /* Info.plist */,
240248
3C9B06C325542F2500E45614 /* Launch Screen.storyboard */,
241249
3C9B06E625547C3800E45614 /* StoryboardViewController.storyboard */,
@@ -378,6 +386,9 @@
378386
3C1FED1D2556D91D0001BD58 /* PBXTargetDependency */,
379387
3C1A70B7256BBAB300E07E4A /* PBXTargetDependency */,
380388
);
389+
fileSystemSynchronizedGroups = (
390+
3C3057542EAE72DB00D290DA /* FileSystemFolder */,
391+
);
381392
name = UIKitProject;
382393
packageProductDependencies = (
383394
73AF86CC2968A93900BED352 /* LocalPackageTarget */,
@@ -537,6 +548,7 @@
537548
3CD46111256C034C00856DAC /* MultiTargetStruct.swift in Sources */,
538549
3CE3F7CC2685DEFB0047231C /* OldModel.xcdatamodeld in Sources */,
539550
3C3ABC09256526F900EAB309 /* XibViewController2.swift in Sources */,
551+
3C3057582EAE734C00D290DA /* XibViewController3.swift in Sources */,
540552
3C9B06EA25547CEC00E45614 /* StoryboardViewController.swift in Sources */,
541553
3CE3F7D02685E07C0047231C /* CustomEntityMigrationPolicy.swift in Sources */,
542554
3C9EFBA82684E1B70071E973 /* Model.xcdatamodeld in Sources */,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+
<device id="retina6_1" orientation="portrait" appearance="light"/>
4+
<dependencies>
5+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
6+
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
7+
<capability name="System colors in document resources" minToolsVersion="11.0"/>
8+
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9+
</dependencies>
10+
<objects>
11+
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="XibViewController3" customModule="UIKitProject" customModuleProvider="target">
12+
<connections>
13+
<outlet property="button" destination="OO3-gz-PhK" id="63X-9t-63W"/>
14+
<outlet property="view" destination="iN0-l3-epB" id="qyc-wr-FpV"/>
15+
</connections>
16+
</placeholder>
17+
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
18+
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="XibView" customModule="UIKitProject" customModuleProvider="target">
19+
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
20+
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
21+
<subviews>
22+
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="OO3-gz-PhK">
23+
<rect key="frame" x="184" y="439" width="46" height="30"/>
24+
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
25+
<state key="normal" title="Button"/>
26+
<connections>
27+
<action selector="click:" destination="-1" eventType="touchUpInside" id="jLb-bl-k6b"/>
28+
</connections>
29+
</button>
30+
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="XibViewController3" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ega-I7-Zu8">
31+
<rect key="frame" x="139" y="410" width="136" height="21"/>
32+
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
33+
<fontDescription key="fontDescription" type="system" pointSize="17"/>
34+
<nil key="textColor"/>
35+
<nil key="highlightedColor"/>
36+
</label>
37+
</subviews>
38+
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
39+
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
40+
<point key="canvasLocation" x="142" y="109"/>
41+
</view>
42+
</objects>
43+
<resources>
44+
<systemColor name="systemBackgroundColor">
45+
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
46+
</systemColor>
47+
</resources>
48+
</document>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import UIKit
2+
3+
class XibViewController3: UIViewController {
4+
@IBOutlet weak var button: UIButton!
5+
@IBAction func clickFromSubclass(_ sender: Any) {}
6+
}

Tests/XcodeTests/UIKitProjectTest.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ final class UIKitProjectTest: XcodeSourceGraphTestCase {
3535
}
3636
}
3737

38+
func testRetainsXibReferencedClassFromFileSystemFolder() {
39+
assertReferenced(.class("XibViewController3"))
40+
}
41+
3842
func testRetainsInspectablePropertyInExtension() {
3943
assertReferenced(.extensionClass("UIView")) {
4044
self.assertReferenced(.varInstance("customBorderColor"))

0 commit comments

Comments
 (0)