Swift in-class properties are extracted by a dedicated branch in TreeSitterExtractor.visitNode, not the generic nameField/variableTypes path swift.ts declares. That branch had a `!isComputed` gate that dropped computed properties entirely, so `codegraph query`/`codegraph_explore` returned "No results found" for them — including a SwiftUI view's `var body: some View`, the most important symbol in any SwiftUI app, and the heavily-read `var isCloudProxy: Bool` from the report. Stored properties were already fixed in #708 (v1.0.0); the reporter tested v0.9.9 and confirmed "still present on main" by inspecting swift.ts only, missing the dedicated branch — so only the computed-property half was real. - Computed properties now index as `property` nodes; the getter is walked via visitFunctionBody so its calls attribute to the property (a SwiftUI `body`'s subview tree becomes the property's callees — the render flow is traceable through it), not flattened onto the enclosing type. - Protocol property requirements (`var x: T { get }`) — a third never-indexed category — index as `property` too. - Routing the getter through visitFunctionBody also stops getter-local `let`/`var` declarations from being wrongly node-ified as struct fields (the generic child-walk used to do this): Alamofire property 0→348, field 618→588, idempotent. Stored/static behavior is unchanged. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
30dc303f4c
commit
b3f59c717a
@@ -1437,6 +1437,88 @@ protocol UploadConvertible: URLRequestConvertible {
|
||||
// UploadConvertible extends URLRequestConvertible
|
||||
expect(extendsRefs.find((r) => r.referenceName === 'URLRequestConvertible')).toBeDefined();
|
||||
});
|
||||
|
||||
it('indexes Swift properties so they are findable: computed → property, stored → field, static → constant/variable (#1020)', () => {
|
||||
const code = `
|
||||
struct ReproConfig {
|
||||
let reproStoredValue: Int
|
||||
var reproComputedFlag: Bool {
|
||||
reproStoredValue > 0
|
||||
}
|
||||
static let sharedLimit = 10
|
||||
static var sharedCount = 0
|
||||
func reproControlMethod() -> Bool {
|
||||
reproComputedFlag
|
||||
}
|
||||
}
|
||||
|
||||
final class ReproService {
|
||||
private let reproClassStored: String = "x"
|
||||
var reproClassComputed: Int { reproClassStored.count }
|
||||
}
|
||||
`;
|
||||
const result = extractFromSource('Repro.swift', code);
|
||||
const byName = (name: string) => result.nodes.find((n) => n.name === name);
|
||||
|
||||
// Computed properties are the regression this fix targets: before #1020 they
|
||||
// were dropped entirely, so search/explore returned nothing for them.
|
||||
expect(byName('reproComputedFlag')?.kind).toBe('property');
|
||||
expect(byName('reproClassComputed')?.kind).toBe('property');
|
||||
|
||||
// Stored instance properties stay `field` (fixed earlier in #708 — guard it).
|
||||
expect(byName('reproStoredValue')?.kind).toBe('field');
|
||||
expect(byName('reproClassStored')?.kind).toBe('field');
|
||||
|
||||
// `static let`/`static var` members remain shared constant/variable nodes.
|
||||
expect(byName('sharedLimit')?.kind).toBe('constant');
|
||||
expect(byName('sharedCount')?.kind).toBe('variable');
|
||||
|
||||
// The control method is unaffected.
|
||||
expect(byName('reproControlMethod')?.kind).toBe('method');
|
||||
});
|
||||
|
||||
it("attributes a computed property's getter calls to the property, not the type (SwiftUI body flow) (#1020)", () => {
|
||||
const code = `
|
||||
struct GreetingView {
|
||||
let name: String
|
||||
var body: some View {
|
||||
let prefix = "Hi"
|
||||
return VStack {
|
||||
Text(greeting(prefix))
|
||||
}
|
||||
}
|
||||
func greeting(_ p: String) -> String { p }
|
||||
}
|
||||
`;
|
||||
const result = extractFromSource('View.swift', code);
|
||||
const body = result.nodes.find((n) => n.kind === 'property' && n.name === 'body');
|
||||
expect(body).toBeDefined();
|
||||
|
||||
// The getter's call to greeting() must originate from `body` (so a SwiftUI
|
||||
// view's render flow is reachable through the property), not flatten onto the
|
||||
// enclosing struct.
|
||||
const callsFromBody = result.unresolvedReferences.filter(
|
||||
(r) => r.fromNodeId === body!.id && r.referenceKind === 'calls'
|
||||
);
|
||||
expect(callsFromBody.some((r) => r.referenceName === 'greeting')).toBe(true);
|
||||
|
||||
// The getter is walked as a body, so a local declared inside it is NOT
|
||||
// node-ified (locals are the data-flow frontier we leave uncovered). Before
|
||||
// this fix the generic walker treated such a local as a struct `field`.
|
||||
expect(result.nodes.find((n) => n.name === 'prefix')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('indexes a Swift protocol property requirement as a findable property (#1020)', () => {
|
||||
const code = `
|
||||
protocol Themable {
|
||||
var accentColor: Color { get }
|
||||
var title: String { get set }
|
||||
}
|
||||
`;
|
||||
const result = extractFromSource('Themable.swift', code);
|
||||
expect(result.nodes.find((n) => n.name === 'accentColor')?.kind).toBe('property');
|
||||
expect(result.nodes.find((n) => n.name === 'title')?.kind).toBe('property');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Kotlin Extraction', () => {
|
||||
|
||||
Reference in New Issue
Block a user