[codex] Fix generated asset catalog symbol detection#98
[codex] Fix generated asset catalog symbol detection#98
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes FengNiao’s detection of Xcode-generated Swift asset catalog symbols, especially for folder-organized (nested) asset catalogs and asset names containing dots, and adds regression coverage via fixtures/tests.
Changes:
- Add a fixture-backed regression test project for nested asset-catalog symbols,
ImageResourceaccess, and dotted asset names. - Improve Swift member-access parsing to recognize nested member-access patterns and avoid matching custom type prefixes that merely end with asset-type names.
- Update generated asset symbol mapping to derive Swift symbol chains from real
.xcassetspaths while keeping Objective-C symbol support.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/Fixtures/GeneratedAssetCatalogSymbol/ViewController.swift | Adds Swift fixture source referencing nested and ImageResource symbols. |
| Tests/Fixtures/GeneratedAssetCatalogSymbol/Assets.xcassets/Contents.json | Adds top-level asset catalog metadata for the fixture. |
| Tests/Fixtures/GeneratedAssetCatalogSymbol/Assets.xcassets/unused.imageset/Contents.json | Adds an unused imageset fixture resource. |
| Tests/Fixtures/GeneratedAssetCatalogSymbol/Assets.xcassets/empty.icon.imageset/Contents.json | Adds a dotted-name imageset fixture resource. |
| Tests/Fixtures/GeneratedAssetCatalogSymbol/Assets.xcassets/Symbols/plug.imageset/Contents.json | Adds a nested-folder imageset fixture resource. |
| Tests/Fixtures/GeneratedAssetCatalogSymbol/Assets.xcassets/Icons/Settings/logo.imageset/Contents.json | Adds a deeper nested-folder imageset fixture resource. |
| Tests/FengNiaoKitTests/StringExtensionsTests.swift | Adds tests asserting dot-separated names convert to generated symbol keys correctly. |
| Tests/FengNiaoKitTests/SearchRuleTests.swift | Adds tests for nested Swift member-access symbol parsing and ignoring custom type prefixes. |
| Tests/FengNiaoKitTests/GeneratedAssetSymbolTests.swift | Adds an end-to-end regression test ensuring nested-folder assets aren’t misclassified as unused. |
| Sources/FengNiaoKit/FileSearchRule.swift | Updates Swift member-access search logic to extract nested symbol chains in select syntaxes. |
| Sources/FengNiaoKit/FengNiao.swift | Changes generated-symbol usage detection to map from actual asset catalog paths and subtract used paths from unused results. |
| Sources/FengNiaoKit/Extensions.swift | Treats . as a word boundary in camel-casing and introduces a path-component helper for symbol generation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| result.insert(".\(identifier)") | ||
| excludedRanges.append(match.range) | ||
| } | ||
|
|
There was a problem hiding this comment.
SwiftMemberAccessSearchRule currently extracts full nested symbols only for Type( .a.b.c ) and ImageResource.a.b.c. If nested symbols are used directly via type inference (e.g. let r: ImageResource = .Icons.Settings.logo or passed as resource: .Icons.Settings.logo), the fallback regex will only capture the first component (.Icons) and miss the full chain, which can cause used assets to be reported as unused. Consider adding a dedicated pattern that captures dot-prefixed chains (e.g. .A.B.c) regardless of surrounding call syntax, while keeping the negative-lookbehind to avoid regular property access.
| let dotPrefixedNestedPattern = #"(?<![A-Za-z0-9_])\.\s*([A-Za-z0-9_]+(?:\s*\.\s*[A-Za-z0-9_]+)+)"# | |
| let dotPrefixedNestedReg = try! NSRegularExpression(pattern: dotPrefixedNestedPattern, options: []) | |
| let dotPrefixedNestedMatches = dotPrefixedNestedReg.matches(in: content, options: [], range: content.fullRange) | |
| for match in dotPrefixedNestedMatches { | |
| let identifierRange = match.range(at: 1) | |
| guard identifierRange.location != NSNotFound else { continue } | |
| let identifier = nsstring.substring(with: identifierRange) | |
| .replacingOccurrences(of: #"\s*\.\s*"#, with: ".", options: .regularExpression) | |
| result.insert(".\(identifier)") | |
| excludedRanges.append(match.range) | |
| } |
| @Test("Swift member access rule applies to nested asset catalog symbols") | ||
| func swiftMemberAccessRuleAppliesToNestedAssetCatalogSymbols() { | ||
| let searcher = SwiftMemberAccessSearchRule() | ||
| let content = """ | ||
| let image = Image(.Icons.Settings.logo) | ||
| let resource = ImageResource.Symbols.plug | ||
| let direct = Image(ImageResource.emptyIcon) | ||
| """ | ||
| let result = searcher.search(in: content) | ||
| let expected: Set<String> = [ | ||
| ".Icons.Settings.logo", | ||
| ".Symbols.plug", | ||
| ".emptyIcon", | ||
| ] | ||
| #expect(result == expected) | ||
| } |
There was a problem hiding this comment.
The new nested-symbol search test only covers Image(.Icons.Settings.logo) and ImageResource.Symbols.plug/Image(ImageResource.emptyIcon). To prevent regressions for the common type-inference form, consider adding cases like let r: ImageResource = .Icons.Settings.logo and/or UIImage(resource: .Icons.Settings.logo) (or the equivalent API used by your generated symbols). These cases currently aren’t exercised and are where the regex can easily fall back to capturing only the first component.
Summary
ImageResourceaccess, and asset names containing dotsRoot Cause
FengNiao only matched generated Swift asset symbols against the leaf resource key. That worked for simple symbols like
.icFlag, but not for asset catalogs organized in folders where Xcode generates chained symbols such as.Icons.Settings.logo. It also treated dotted asset names likeempty.iconas literal dots instead of Xcode-style camelCase boundaries.Validation
swift test --filter FengNiaoKitTests