mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-02 01:37:32 +08:00
- A bare Lua call cannot reach a table's method (`function M.x`, `function M:x`) without its table: kong's 118 `error(...)` calls went to its response PDK's `_RESPONSE.error`, 111 to a mock's `error`, busted's `setup`/`teardown` to perf helpers' and factories'. - A bare call to a Lua global (`ipairs`, `error`, `require`, busted's `describe`/`it`/`setup`...) is Lua's unless the file defines its own function of that name: telescope's 120 `for _, v in ipairs(...)` went to a linked list's `ipairs` method, apisix's 36 `require(...)` to a wasm module's `_M.require`. - A function's own locals are not nodes, so the alias reader now reads `local x = ...` from source above the call within its function, and a member of a host global table (`kong.request.get_query`) resolves to the one method of a table named after its holder (`_REQUEST`). kong's `local clear_header = kong.service.request.clear_header` had resolved to the RESPONSE module's - now nothing, correctly. telescope -129, kong -516 +58 (indexing 4.7s -> 3.5s), apisix -57 +18, lua-resty-core -4, roact -3 +1. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
89 lines
2.4 KiB
TypeScript
89 lines
2.4 KiB
TypeScript
/**
|
||
* A bare Lua call cannot reach a table's method — `function M.error()` is
|
||
* `M.error(…)`, never `error(…)` — and a Lua global (`ipairs`, `error`,
|
||
* busted's `setup`) is Lua's unless the file defines its own. Telescope's
|
||
* 120 `for _, v in ipairs(…)` went to a linked list's `ipairs` method,
|
||
* kong's 118 `error(…)` to its response PDK's `error`. A function's own
|
||
* `local get_query = kong.request.get_query` alias still reaches the method
|
||
* of the table named after its holder.
|
||
*/
|
||
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-lua-builtins-'));
|
||
const files: Record<string, string> = {
|
||
'lua/app/linked_list.lua': `local LinkedList = {}
|
||
|
||
function LinkedList:ipairs()
|
||
return function() end
|
||
end
|
||
|
||
return LinkedList
|
||
`,
|
||
'lua/app/response.lua': `local _RESPONSE = {}
|
||
|
||
function _RESPONSE.error(status)
|
||
return status
|
||
end
|
||
|
||
return _RESPONSE
|
||
`,
|
||
'lua/app/request.lua': `local _REQUEST = {}
|
||
|
||
function _REQUEST.get_query()
|
||
return {}
|
||
end
|
||
|
||
return _REQUEST
|
||
`,
|
||
'lua/app/handler.lua': `local M = {}
|
||
|
||
function M.run(items)
|
||
for _, v in ipairs(items) do
|
||
if not v then
|
||
error("missing")
|
||
end
|
||
end
|
||
local get_query = kong.request.get_query
|
||
return get_query()
|
||
end
|
||
|
||
return M
|
||
`,
|
||
};
|
||
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 callsFrom = (file: string) => {
|
||
const ids = cg.getNodesInFile(file).map((n) => n.id);
|
||
return cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind === 'calls').map((e) => cg.getNode(e.target)!.qualifiedName).sort();
|
||
};
|
||
|
||
describe('bare Lua calls', () => {
|
||
it('are Lua’s globals, never a table’s method of that name', () => {
|
||
const calls = callsFrom('lua/app/handler.lua');
|
||
expect(calls).not.toContain('LinkedList::ipairs');
|
||
expect(calls).not.toContain('_RESPONSE::error');
|
||
});
|
||
|
||
it('still follow a function’s own alias of a host table’s member', () => {
|
||
expect(callsFrom('lua/app/handler.lua')).toContain('_REQUEST::get_query');
|
||
});
|
||
});
|