mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-16 20:53:57 +08:00
4c381aedbd
A chained Swift call accepted any same-named member unless its name was a standard-library one: vapor's `JSONDecoder().decode(...)` and even its own `URLQueryDecoder().decode(...)` went to `ClientRequest._URLQueryContainer .decode`, IceCubes' `JSONEncoder().encode(self)` to a filter's `encode`, realm's `Realm().create(...)` to a test object's `create`. When the link before the call is a constructor - `TypeName(...)` with flat arguments - the member must belong to that type or a supertype / protocol the project gives it; an outside type (JSONDecoder) keeps only the project's extensions of it. A capitalized C function (`RLMObjectBaseObjectSchema(obj)!`) constructs nothing and is left alone. vapor -63 +62 (`URLQueryDecoder::decode`, `URLEncodedFormParser::parse`), IceCubesApp -23 +1, realm-swift -19 +19 (`Realm::create`), Alamofire -1; Kingfisher unchanged. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
63 lines
2.0 KiB
TypeScript
63 lines
2.0 KiB
TypeScript
/**
|
||
* A Swift call on a just-constructed value — `URLQueryDecoder().decode(…)`,
|
||
* `JSONDecoder().decode(…)` — is a member of the type constructed (or of what
|
||
* it inherits), never some other type's method of that name: vapor's
|
||
* `JSONDecoder().decode` went to a request's private
|
||
* `_URLQueryContainer.decode`, and so did its own decoders' calls.
|
||
*/
|
||
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
|
||
import * as fs from 'fs';
|
||
import * as os from 'os';
|
||
import * as path from 'path';
|
||
import { CodeGraph } from '../src';
|
||
|
||
let root = '';
|
||
let cg: CodeGraph;
|
||
|
||
beforeAll(async () => {
|
||
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-swift-constructed-'));
|
||
const files: Record<string, string> = {
|
||
'Sources/App/URLQueryDecoder.swift': `struct URLQueryDecoder {
|
||
func decode<D>(_ type: D.Type) -> Int {
|
||
return 0
|
||
}
|
||
}
|
||
`,
|
||
'Sources/App/Request.swift': `struct Request {
|
||
struct _Container {
|
||
func decode<D>(_ type: D.Type) -> Int {
|
||
return 1
|
||
}
|
||
}
|
||
}
|
||
`,
|
||
'Sources/App/Use.swift': `import Foundation
|
||
|
||
func run(data: Data) throws -> Int {
|
||
let a = URLQueryDecoder().decode(Int.self)
|
||
let b = try JSONDecoder().decode(Int.self, from: data)
|
||
return a + b
|
||
}
|
||
`,
|
||
};
|
||
for (const [rel, content] of Object.entries(files)) {
|
||
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
|
||
fs.writeFileSync(path.join(root, rel), content);
|
||
}
|
||
cg = await CodeGraph.init(root, { index: true });
|
||
});
|
||
|
||
afterAll(() => {
|
||
cg?.close();
|
||
if (root) fs.rmSync(root, { recursive: true, force: true });
|
||
});
|
||
|
||
describe('Swift calls on a constructed value', () => {
|
||
it('reach the constructed type’s member, and no other type’s', () => {
|
||
const run = cg.getNodesInFile('Sources/App/Use.swift').find((n) => n.name === 'run')!;
|
||
const decodes = cg.getOutgoingEdges(run.id).filter((e) => e.kind === 'calls')
|
||
.map((e) => cg.getNode(e.target)!).filter((t) => t.name === 'decode').map((t) => t.qualifiedName);
|
||
expect(decodes).toEqual(['URLQueryDecoder::decode']);
|
||
});
|
||
});
|