mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-03 10:18:20 +08:00
A function nested in another function is reachable only from inside it; a Python name the enclosing function binds is that local's value; a pytest fixture is a test's parameter only in its own module or under its conftest. Unreachable same-named functions still count against a lone cross-file guess. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
122 lines
3.6 KiB
TypeScript
122 lines
3.6 KiB
TypeScript
/**
|
|
* A name passed as a value means what is in scope where it is written. A
|
|
* function nested in another function is in scope only in there — httpx's
|
|
* `self._build_auth(auth)` passes its parameter, not the `auth` a test
|
|
* defines inside `test_custom_auth`. A Python name the function around it
|
|
* binds is that local's — `auth_flow(self, request)` handing `request` on is
|
|
* not the package's `request()`. And a pytest fixture is a test's parameter
|
|
* only in its own module or under its `conftest.py`.
|
|
*/
|
|
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-py-fnvalue-'));
|
|
const files: Record<string, string> = {
|
|
'httpx/_client.py': `class Client:
|
|
@property
|
|
def auth(self):
|
|
return self._auth
|
|
|
|
def _build_auth(self, auth):
|
|
return auth
|
|
|
|
def set_auth(self, auth):
|
|
self._auth = self._build_auth(auth)
|
|
`,
|
|
'httpx/_api.py': `def request(method, url):
|
|
return method, url
|
|
`,
|
|
'httpx/_auth.py': `class Auth:
|
|
def auth_flow(self, request):
|
|
yield request
|
|
|
|
def sync_auth_flow(self, request):
|
|
flow = self.auth_flow(request)
|
|
return flow
|
|
|
|
|
|
class Recorder:
|
|
def request(self):
|
|
return None
|
|
`,
|
|
'tests/test_auth.py': `def test_custom_auth():
|
|
def auth(request):
|
|
return request
|
|
|
|
return register(auth)
|
|
|
|
|
|
def test_basic_auth():
|
|
auth = ("user", "pass")
|
|
return register(auth)
|
|
`,
|
|
'docs/example/test_order.py': `import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def func():
|
|
return 1
|
|
`,
|
|
'tests/test_coverage.py': `import pytest
|
|
|
|
|
|
def _run_both(func):
|
|
return func()
|
|
|
|
|
|
@pytest.fixture(
|
|
scope="module",
|
|
)
|
|
def recipe():
|
|
return {}
|
|
|
|
|
|
def test_recipe(recipe):
|
|
return use(recipe)
|
|
`,
|
|
};
|
|
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 });
|
|
});
|
|
|
|
const targetsFrom = (file: string, qualifiedName?: string) => {
|
|
const ids = cg.getNodesInFile(file).filter((n) => !qualifiedName || n.qualifiedName === qualifiedName).map((n) => n.id);
|
|
return cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind === 'references' || e.kind === 'calls')
|
|
.map((e) => cg.getNode(e.target)!).map((t) => `${t.filePath}:${t.qualifiedName}`);
|
|
};
|
|
|
|
describe('Python names passed as values', () => {
|
|
it('never reach a function nested in another function', () => {
|
|
expect(targetsFrom('httpx/_client.py')).not.toContain('tests/test_auth.py:test_custom_auth::auth');
|
|
expect(targetsFrom('tests/test_auth.py', 'test_basic_auth')).not.toContain('tests/test_auth.py:test_custom_auth::auth');
|
|
});
|
|
|
|
it('reach a nested function from inside its container', () => {
|
|
expect(targetsFrom('tests/test_auth.py', 'test_custom_auth')).toContain('tests/test_auth.py:test_custom_auth::auth');
|
|
});
|
|
|
|
it('are the local a parameter binds', () => {
|
|
expect(targetsFrom('httpx/_auth.py')).not.toContain('httpx/_api.py:request');
|
|
});
|
|
|
|
it('reach a fixture only from its own module', () => {
|
|
expect(targetsFrom('tests/test_coverage.py', '_run_both')).not.toContain('docs/example/test_order.py:func');
|
|
expect(targetsFrom('tests/test_coverage.py', 'test_recipe')).toContain('tests/test_coverage.py:recipe');
|
|
});
|
|
});
|