mirror of
https://github.com/Tencent/teamai-cli.git
synced 2026-10-02 03:14:40 +08:00
fix(code-knowledge): keep a call's own name from counting as a shadow
Two follow-ups from review.
The closed rule collected every identifier in the enclosing scopes, and that
included the callee of a sibling call: in `func run() { work(); work() }` each
call read the other's `work` as evidence, so both cross-file resolutions were
suppressed. The name a call goes through is a use, not a binding. Arguments are
still walked, so a closure that does bind a name keeps counting.
`SWIFT_IDENTIFIER` also admitted ASCII only, so a parameter named `π` was
dropped from the set and a sibling `func π()` won the fallback. Swift
identifiers are not ASCII.
Both shapes now have cases; all four mutations the rule has to survive are
killed by the suite.
This commit is contained in:
@@ -485,4 +485,29 @@ describe('Swift module-scope resolution yields to enclosing bindings', () => {
|
||||
// parameter list is empty, which is all the old collector inspected.
|
||||
expect(result.edges.filter((e) => e.relation === 'REFERENCES')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does not let one call stand in for another in the same body', async () => {
|
||||
const { result } = await extractFiles([
|
||||
['Sources/App/Worker.swift', 'func work() -> Int { return 1 }\n'],
|
||||
['Sources/App/Twice.swift', 'func run() -> Int {\n work()\n work()\n return 0\n}\n'],
|
||||
]);
|
||||
|
||||
// Neither call binds `work`; each one only *uses* it. Counting the other
|
||||
// call's identifier as evidence would suppress both, so the over-collection
|
||||
// this rule accepts has a floor: a call's own name sits below it.
|
||||
const references = result.edges.filter((e) => e.relation === 'REFERENCES');
|
||||
expect(references).toHaveLength(2);
|
||||
for (const reference of references) expect(reference.to).toBe('Sources/App/Worker.swift');
|
||||
});
|
||||
|
||||
it('recognises a binding whose name is not ASCII', async () => {
|
||||
const { result } = await extractFiles([
|
||||
['Sources/App/Pi.swift', 'func π() -> Int { return 1 }\n'],
|
||||
['Sources/App/Runner.swift', 'func run(π: () -> Int) -> Int {\n return π()\n}\n'],
|
||||
]);
|
||||
|
||||
// Swift identifiers are not ASCII. A class that only admits [A-Za-z] drops
|
||||
// the parameter and lets the sibling `func π()` win the fallback.
|
||||
expect(result.edges.filter((e) => e.relation === 'REFERENCES')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -215,7 +215,9 @@ function namedChildrenOf(node: Node): Node[] {
|
||||
return node.namedChildren.filter((child): child is Node => child !== null);
|
||||
}
|
||||
|
||||
const SWIFT_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
||||
// Swift identifiers admit any Unicode letter, so an ASCII-only class would drop
|
||||
// a parameter named `π` and let a sibling `func π()` win the fallback.
|
||||
const SWIFT_IDENTIFIER = /^[\p{L}_][\p{L}\p{N}_]*$/u;
|
||||
|
||||
function addSwiftName(name: string, names: Set<string>): void {
|
||||
// `_` is the "no internal name" placeholder, not a binding.
|
||||
@@ -278,6 +280,20 @@ function swiftLocalBindingsAt(node: Node): string[] {
|
||||
if (current.type === "user_type") {
|
||||
return;
|
||||
}
|
||||
// The name a *call* goes through is a use, not a binding. Without this,
|
||||
// `work(); work()` has each call count the other one's identifier as
|
||||
// evidence, and both cross-file resolutions are suppressed. Arguments are
|
||||
// still walked, so a closure that does bind a name — `handler { work in
|
||||
// work() }` — keeps counting.
|
||||
if (current.type === "call_expression") {
|
||||
for (const child of namedChildrenOf(current)) {
|
||||
if (child.type === "navigation_expression" || child.type === "simple_identifier") {
|
||||
continue;
|
||||
}
|
||||
collect(child);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (const child of namedChildrenOf(current)) {
|
||||
collect(child);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user