mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
fix(lua): address latest PR review findings
This commit is contained in:
parent
94fce75003
commit
a5611a3f86
8 changed files with 108 additions and 12 deletions
|
|
@ -11,8 +11,8 @@
|
|||
* ruby, rust, php, kotlin, swift, dart
|
||||
* - experimental: vue (embedded-language / SFC complexity),
|
||||
* cobol (regex-provider path),
|
||||
* lua (definition-only legacy DAG path; scope-resolution
|
||||
* hooks pending — Phase B)
|
||||
* lua (scope-resolution provider is active; broader
|
||||
* language-contract coverage is still experimental)
|
||||
* - quarantined: (none)
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export const EXTENSIONS = [
|
|||
];
|
||||
|
||||
/** Lua module extensions used only by Lua import resolvers. */
|
||||
export const LUA_EXTENSIONS = ['', '.lua', '/init.lua'] as const;
|
||||
export const LUA_EXTENSIONS = ['.lua', '/init.lua'] as const;
|
||||
|
||||
/**
|
||||
* Try to match a path (with extensions) against the known file set.
|
||||
|
|
|
|||
|
|
@ -19,14 +19,21 @@
|
|||
*/
|
||||
import type { CaptureMatch, ParsedImport } from 'gitnexus-shared';
|
||||
|
||||
function stripLuaString(s: string): string {
|
||||
const long = s.match(/^\[(=*)\[([\s\S]*)\]\1\]$/);
|
||||
return long ? long[2] : stripQuotes(s);
|
||||
}
|
||||
|
||||
function stripQuotes(s: string): string {
|
||||
return s.replace(/^["']|["']$/g, '');
|
||||
const quoted = s.match(/^(?:(["'])([\s\S]*)\1|\[(=*)\]([\s\S]*)\]\3\])$/);
|
||||
if (!quoted) return s;
|
||||
return quoted[2] ?? quoted[4] ?? '';
|
||||
}
|
||||
|
||||
export function interpretLuaImport(captures: CaptureMatch): ParsedImport | null {
|
||||
const source = captures['@import.source']?.text;
|
||||
if (source === undefined) return null;
|
||||
const targetRaw = stripQuotes(source);
|
||||
const targetRaw = stripLuaString(source);
|
||||
if (!targetRaw) return null;
|
||||
const localName = captures['@import.localName']?.text;
|
||||
if (localName) {
|
||||
|
|
|
|||
|
|
@ -155,6 +155,23 @@ export const inferCallForm = (callNode: SyntaxNode, nameNode: SyntaxNode): CallF
|
|||
return 'member';
|
||||
}
|
||||
|
||||
// Lua tree-sitter represents `obj:method()` / `obj.field()` as a `call`
|
||||
// whose `function` child is a `variable` carrying `table` plus `method` or
|
||||
// `field`; unlike the other grammars, the captured name is not wrapped in a
|
||||
// member-access node.
|
||||
if (callNode.type === 'call') {
|
||||
const callee = callNode.childForFieldName('function');
|
||||
const table = callNode.childForFieldName('table') ?? callee?.childForFieldName('table');
|
||||
const member =
|
||||
callNode.childForFieldName('method') ??
|
||||
callNode.childForFieldName('field') ??
|
||||
callee?.childForFieldName('method') ??
|
||||
callee?.childForFieldName('field');
|
||||
if (callee?.type === 'variable' && table && member) {
|
||||
return 'member';
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Scoped calls (Rust Foo::new(), C++ ns::func()): treat as free
|
||||
// The receiver is a type, not an instance — handled differently in Phase 3
|
||||
if (nameParent && SCOPED_CALL_NODE_TYPES.has(nameParent.type)) {
|
||||
|
|
@ -226,6 +243,14 @@ export const extractReceiverName = (nameNode: SyntaxNode): string | undefined =>
|
|||
receiver = parent.childForFieldName('receiver');
|
||||
}
|
||||
|
||||
// Lua: the receiver is the `table` field of the call's `function` variable.
|
||||
if (!receiver && callNode.type === 'call') {
|
||||
const callee = callNode.childForFieldName('function');
|
||||
if (callee?.type === 'variable') {
|
||||
receiver = callNode.childForFieldName('table') ?? callee.childForFieldName('table');
|
||||
}
|
||||
}
|
||||
|
||||
// PHP scoped_call_expression (parent::method(), self::method()):
|
||||
// nameNode's direct parent IS the scoped_call_expression (name is a direct child)
|
||||
if (
|
||||
|
|
@ -274,6 +299,12 @@ export const extractReceiverName = (nameNode: SyntaxNode): string | undefined =>
|
|||
|
||||
if (!receiver) return undefined;
|
||||
|
||||
// Lua colon calls wrap a simple receiver in a `variable` node.
|
||||
if (receiver.type === 'variable') {
|
||||
const name = receiver.childForFieldName('name');
|
||||
if (name?.type === 'identifier') return name.text;
|
||||
}
|
||||
|
||||
// Only capture simple identifiers — refuse complex expressions
|
||||
if (SIMPLE_RECEIVER_TYPES.has(receiver.type)) {
|
||||
return receiver.text;
|
||||
|
|
@ -328,6 +359,12 @@ export const extractReceiverNode = (nameNode: SyntaxNode): SyntaxNode | undefine
|
|||
receiver = parent.childForFieldName('receiver');
|
||||
}
|
||||
|
||||
// Lua: the receiver is the `table` field of the call's `function` variable.
|
||||
if (!receiver && callNode.type === 'call') {
|
||||
const callee = callNode.childForFieldName('function');
|
||||
if (callee?.type === 'variable') receiver = callNode.childForFieldName('table');
|
||||
}
|
||||
|
||||
if (
|
||||
!receiver &&
|
||||
(parent.type === 'scoped_call_expression' || callNode.type === 'scoped_call_expression')
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { SupportedLanguages, type BindingRef, type ScopeId } from 'gitnexus-shar
|
|||
import { emitLuaScopeCaptures } from '../../../src/core/ingestion/languages/lua/index.js';
|
||||
import { collectLuaCaptureSideChannel } from '../../../src/core/ingestion/languages/lua/capture-side-channel.js';
|
||||
import { luaScopeResolver } from '../../../src/core/ingestion/languages/lua/scope-resolver.js';
|
||||
import { interpretLuaImport } from '../../../src/core/ingestion/languages/lua/interpret.js';
|
||||
|
||||
function writeFixtureRepo(root: string, files: Record<string, string>): void {
|
||||
for (const [rel, content] of Object.entries(files)) {
|
||||
|
|
@ -56,6 +57,22 @@ describe('Lua scope resolver import extensions', () => {
|
|||
const files = new Set(['main.lua', 'foo.ts', 'foo.lua']);
|
||||
expect(luaScopeResolver.resolveImportTarget('foo', 'main.lua', files)).toBe('foo.lua');
|
||||
});
|
||||
|
||||
it('does not bind an extensionless collision ahead of the Lua module', () => {
|
||||
const files = new Set(['main.lua', 'foo', 'foo.lua']);
|
||||
expect(luaScopeResolver.resolveImportTarget('foo', 'main.lua', files)).toBe('foo.lua');
|
||||
});
|
||||
|
||||
it('unwraps quoted and long-bracket require sources', () => {
|
||||
const sources = ['"lib.util"', "'lib.util'", '[[lib.util]]', '[=[lib.util]=]'];
|
||||
for (const source of sources) {
|
||||
expect(
|
||||
interpretLuaImport({
|
||||
'@import.source': { text: source },
|
||||
} as never),
|
||||
).toEqual({ kind: 'wildcard', targetRaw: 'lib.util' });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -128,6 +145,23 @@ describe('Lua scope: bare require import', () => {
|
|||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
it('resolves a long-bracket require source in the graph', async () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lua-scope-long-require-'));
|
||||
try {
|
||||
writeFixtureRepo(tmpDir, {
|
||||
'lib/util.lua': 'return {}\n',
|
||||
'main.lua': 'require([[lib.util]])\n',
|
||||
});
|
||||
const result = await runPipelineFromRepo(tmpDir, () => {});
|
||||
const imports = getRelationships(result, 'IMPORTS').filter(
|
||||
(e) => e.sourceFilePath?.includes('main.lua') && e.targetFilePath?.includes('util.lua'),
|
||||
);
|
||||
expect(imports).toHaveLength(1);
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
}, 60000);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { getProvider } from '../../src/core/ingestion/languages/index.js';
|
|||
|
||||
// Vendored grammar — loaded from vendor/ by absolute path, never node_modules (#2111).
|
||||
const Kotlin = requireVendoredGrammar('tree-sitter-kotlin');
|
||||
const Lua = requireVendoredGrammar('tree-sitter-lua');
|
||||
|
||||
/**
|
||||
* Helper: parse code, run the language query, and return all @call captures
|
||||
|
|
@ -97,6 +98,26 @@ describe('inferCallForm', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Lua', () => {
|
||||
it('detects colon member calls', () => {
|
||||
parser.setLanguage(Lua);
|
||||
const captures = extractCallCaptures(parser, 'util:answer()', SupportedLanguages.Lua);
|
||||
const match = captures.find((c) => c.calledName === 'answer');
|
||||
expect(match).toBeDefined();
|
||||
expect(inferCallForm(match!.callNode, match!.nameNode)).toBe('member');
|
||||
expect(extractReceiverName(match!.nameNode)).toBe('util');
|
||||
});
|
||||
|
||||
it('detects dot member calls', () => {
|
||||
parser.setLanguage(Lua);
|
||||
const captures = extractCallCaptures(parser, 'util.answer()', SupportedLanguages.Lua);
|
||||
const match = captures.find((c) => c.calledName === 'answer');
|
||||
expect(match).toBeDefined();
|
||||
expect(inferCallForm(match!.callNode, match!.nameNode)).toBe('member');
|
||||
expect(extractReceiverName(match!.nameNode)).toBe('util');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Java', () => {
|
||||
it('detects free call (no object)', () => {
|
||||
parser.setLanguage(Java);
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ describe('isLanguageAvailable', () => {
|
|||
if (isGrammarRuntimeSkipped(SupportedLanguages.Lua)) {
|
||||
expect(available).toBe(false);
|
||||
} else {
|
||||
expect(typeof available).toBe('boolean');
|
||||
expect(available).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -203,13 +203,10 @@ describe('parser-loader ABI load-smoke (#1922)', () => {
|
|||
return;
|
||||
}
|
||||
|
||||
const crlf = String.fromCharCode(13, 10);
|
||||
const snippets = [
|
||||
String.raw`local value = "line\
|
||||
continued"
|
||||
`,
|
||||
String.raw`local value = 'line\
|
||||
continued'
|
||||
`,
|
||||
`local value = "line\\${crlf}continued"${crlf}`,
|
||||
`local value = 'line\\${crlf}continued'${crlf}`,
|
||||
];
|
||||
for (const snippet of snippets) {
|
||||
const parser = new Parser();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue