Files
codegraph/__tests__/lua-library-calls.test.ts
Colby MchenryandClaude Opus 5.5 be91a3120b fix(lua): a library call is never the one project method of that name (#2153)
The unique-name method guess no longer applies to a Lua/Luau call through a
standard or host library table (string, table, math, io, os, coroutine,
debug, utf8, package, bit, jit, ffi, vim, ngx, busted's assert/spy/stub/mock,
love) unless the project defines that function on the table itself (kong's
globalpatches define ngx.sleep), nor to a string method called on a value
(`s:find`, `s:match`, `s:gsub` …). Other untyped receivers keep the guess:
a word rule like Ruby's was tried and lost koreader's OO calls
(`self.ui:reloadDocument()` → ReaderUI).

A/B (edges removed / added): kong -2742, koreader -101, lazy.nvim -84,
telescope.nvim -47, Fusion -9, lua-resty-core -1.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:54:33 +00:00

79 lines
2.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* A Lua call through a standard or host library table (`string`, `table`,
* `vim`, `ngx`, busted's `assert` …), or a string method on a value
* (`s:find(…)`), is the library's — never the one project method that shares
* the name. kong's specs sent `assert.truthy(…)` to a condition helper 987
* times; telescope's `line:find(…)` went to its picker's `find`. A method the
* project defines on the table itself (kong patches `ngx.sleep`) keeps it.
*/
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-library-'));
const files: Record<string, string> = {
'spec/helpers/wait.lua': `local COND = {}
function COND:truthy(v)
return v
end
return COND
`,
'lua/picker.lua': `local Picker = {}
function Picker:find(text)
return text
end
return Picker
`,
'lua/patches.lua': `function ngx.sleep(s)
return s
end
`,
'lua/widget.lua': `local Widget = {}
function Widget:refresh()
return true
end
return Widget
`,
'spec/run_spec.lua': `local function run(line, w)
assert.truthy(line)
local at = line:find("x")
ngx.sleep(1)
w:refresh()
return at
end
return run
`,
};
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('Lua library calls', () => {
it('stay the library’s; a project method keeps a plain receiver and a patched library function', () => {
const ids = cg.getNodesInFile('spec/run_spec.lua').map((n) => n.id);
const targets = cg
.getOutgoingEdgesFrom(ids)
.filter((e) => e.kind === 'calls')
.map((e) => cg.getNode(e.target)!.qualifiedName)
.sort();
expect(targets).not.toContain('COND::truthy');
expect(targets).not.toContain('Picker::find');
expect(targets).toContain('Widget::refresh');
expect(targets.some((t) => t.endsWith('sleep'))).toBe(true);
});
});