mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-13 03:58:33 +00:00
fix(lua): respect module return exports
This commit is contained in:
parent
a64adab8e6
commit
bc69e75e34
2 changed files with 90 additions and 5 deletions
|
|
@ -240,18 +240,65 @@ export const swiftExportChecker: ExportChecker = (node, _name) => {
|
|||
/** Ruby: all top-level definitions are public (no export syntax). */
|
||||
export const rubyExportChecker: ExportChecker = (_node, _name) => true;
|
||||
|
||||
/** Lua: `function foo()` is global (reachable from other files via require/_G);
|
||||
* `local function foo()` is module-private. */
|
||||
export const luaExportChecker: ExportChecker = (node, _name) => {
|
||||
/**
|
||||
* Lua module exports are structural rather than keyword-based:
|
||||
* - `function foo()` is global and therefore visible across files;
|
||||
* - `local function foo()` is private unless the module returns it directly
|
||||
* or exposes it through a returned table (`return { foo = foo }`).
|
||||
*
|
||||
* The query passes the captured identifier node, not the declaration node, so
|
||||
* the declaration must be recovered through the parent chain. Only direct
|
||||
* module-level returns are considered; a return nested in another function
|
||||
* does not make a nested local part of the module API.
|
||||
*/
|
||||
export const luaExportChecker: ExportChecker = (node, name) => {
|
||||
let current: SyntaxNode | null = node;
|
||||
let declaration: SyntaxNode | null = null;
|
||||
while (current !== null) {
|
||||
if (current.type === 'local_function_definition_statement') return false;
|
||||
if (current.type === 'local_function_definition_statement') {
|
||||
declaration = current;
|
||||
break;
|
||||
}
|
||||
if (current.type === 'function_definition_statement') return true;
|
||||
current = current.parent;
|
||||
}
|
||||
return true;
|
||||
|
||||
if (declaration === null) return true;
|
||||
|
||||
const root = findLuaRoot(declaration);
|
||||
if (declaration.parent !== root) return false;
|
||||
return luaModuleReturnExposesName(root, name);
|
||||
};
|
||||
|
||||
function findLuaRoot(node: SyntaxNode): SyntaxNode {
|
||||
let root = node;
|
||||
while (root.parent !== null) root = root.parent;
|
||||
return root;
|
||||
}
|
||||
|
||||
function luaModuleReturnExposesName(root: SyntaxNode, name: string): boolean {
|
||||
for (const statement of root.namedChildren ?? []) {
|
||||
if (statement.type !== 'return_statement') continue;
|
||||
const expressionList = statement.namedChildren?.find(
|
||||
(child) => child.type === 'expression_list',
|
||||
);
|
||||
const expression = expressionList?.namedChildren?.[0];
|
||||
if (expression === undefined) continue;
|
||||
|
||||
const directName = expression.childForFieldName('name');
|
||||
if (directName?.type === 'identifier' && directName.text === name) return true;
|
||||
|
||||
if (expression.type !== 'table') continue;
|
||||
const fields = expression.namedChildren?.find((child) => child.type === 'field_list');
|
||||
for (const field of fields?.namedChildren ?? []) {
|
||||
if (field.type !== 'field') continue;
|
||||
const value = field.childForFieldName('value');
|
||||
const valueName = value?.childForFieldName('name');
|
||||
if (valueName?.type === 'identifier' && valueName.text === name) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/** Dart: public if no leading underscore (convention, same as Python). */
|
||||
export const dartExportChecker: ExportChecker = (_node, name) => !name.startsWith('_');
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import os from 'node:os';
|
|||
import {
|
||||
getRelationships,
|
||||
getNodesByLabel,
|
||||
getNodesByLabelFull,
|
||||
runPipelineFromRepo,
|
||||
type PipelineResult,
|
||||
} from './helpers.js';
|
||||
|
|
@ -158,6 +159,43 @@ local value = wrap(require("wrapped"))
|
|||
}, 60000);
|
||||
});
|
||||
|
||||
describe('Lua module export visibility', () => {
|
||||
it('exports local functions only when the module return value exposes them', async () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lua-scope-exports-'));
|
||||
try {
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'module.lua': `local function exposed() end
|
||||
local function hidden() end
|
||||
function global() end
|
||||
return { exposed = exposed }
|
||||
`,
|
||||
});
|
||||
const result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
const functions = getNodesByLabelFull(result, 'Function').filter((node) =>
|
||||
node.properties.filePath?.endsWith('module.lua'),
|
||||
);
|
||||
expect(functions).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: 'exposed',
|
||||
properties: expect.objectContaining({ isExported: true }),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: 'hidden',
|
||||
properties: expect.objectContaining({ isExported: false }),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: 'global',
|
||||
properties: expect.objectContaining({ isExported: true }),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
}, 60000);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// require("lib.util") + member call util.answer() across files
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue