mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-07 02:58:02 +00:00
fix(lua): address review findings
This commit is contained in:
parent
cc060f7ab1
commit
16f3641ca5
11 changed files with 99 additions and 20 deletions
|
|
@ -66,7 +66,9 @@ const LUA_SCOPE_QUERY = `
|
|||
;; receiver-linkable: collectNamespaceTargets registers X -> target file,
|
||||
;; and the member call resolves via Case 1 (namespace).
|
||||
;; 2. bare require("a.b.c") (side-effect, no binding) — emits wildcard;
|
||||
;; the IMPORTS edge still materializes but no receiver is bound.
|
||||
;; the IMPORTS edge still materializes but no receiver is bound. The
|
||||
;; statement-level patterns below only accept direct chunk/block children,
|
||||
;; so a bound require is not matched a second time through its nested call.
|
||||
(local_variable_declaration
|
||||
(variable_list (variable name: (identifier) @import.localName))
|
||||
(expression_list
|
||||
|
|
@ -75,10 +77,17 @@ const LUA_SCOPE_QUERY = `
|
|||
arguments: (argument_list (expression_list (string) @import.source)))
|
||||
(#eq? @_req "require"))) @import.statement
|
||||
|
||||
(call
|
||||
function: (variable name: (identifier) @_req)
|
||||
arguments: (argument_list (expression_list (string) @import.source))
|
||||
(#eq? @_req "require")) @import.statement
|
||||
(chunk
|
||||
(call
|
||||
function: (variable name: (identifier) @_req)
|
||||
arguments: (argument_list (expression_list (string) @import.source))
|
||||
(#eq? @_req "require")) @import.statement)
|
||||
|
||||
(block
|
||||
(call
|
||||
function: (variable name: (identifier) @_req)
|
||||
arguments: (argument_list (expression_list (string) @import.source))
|
||||
(#eq? @_req "require")) @import.statement)
|
||||
|
||||
;; ── References — free calls: foo() ───────────────────────────────────────────
|
||||
;; Also matches the require call — harmless: require has no def, so it stays an
|
||||
|
|
|
|||
|
|
@ -57,8 +57,10 @@ const luaScopeResolver: ScopeResolver = {
|
|||
_cachedIndex = buildSuffixIndex(list, list);
|
||||
_cachedSet = allFilePaths;
|
||||
}
|
||||
const list = [...allFilePaths];
|
||||
return suffixResolve(parts, list, list, _cachedIndex ?? undefined);
|
||||
// Once the suffix index exists, suffixResolve only consults the index;
|
||||
// passing a spread copy here would still traverse the entire workspace on
|
||||
// every require() despite the cache (#2909 contract).
|
||||
return suffixResolve(parts, [], [], _cachedIndex ?? undefined);
|
||||
},
|
||||
|
||||
// Lua: default local-first-then-imports merge (no language-specific precedence).
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ const loadCache = new Map<string, LoadResult>();
|
|||
const logged = new Set<string>();
|
||||
|
||||
/**
|
||||
* Runtime opt-out for genuinely-optional grammars (Swift/Dart/Kotlin).
|
||||
* Runtime opt-out for genuinely-optional grammars (Swift/Dart/Kotlin/Lua).
|
||||
*
|
||||
* `GITNEXUS_SKIP_OPTIONAL_GRAMMARS` has historically been an *install-time*
|
||||
* env only — the postinstall build scripts read it to skip building the
|
||||
|
|
@ -231,7 +231,7 @@ const logged = new Set<string>();
|
|||
* Accepts `1` / `true` / `all` / `*` (every skippable grammar), or a
|
||||
* comma-separated list of language ids and/or package names
|
||||
* (e.g. `swift,tree-sitter-dart`). Only grammars flagged `userSkippable` (the
|
||||
* genuinely-optional swift/dart/kotlin) can be skipped — required dependencies
|
||||
* genuinely-optional swift/dart/kotlin/lua) can be skipped — required dependencies
|
||||
* routed through the optional machinery for ABI safety (C) carry no
|
||||
* `userSkippable` and are never skippable here.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -44,8 +44,10 @@ export const vendoredGrammarDir = (packageName: string): string =>
|
|||
* Load a vendored tree-sitter grammar by its absolute path under `vendor/`.
|
||||
*
|
||||
* GitNexus vendors six grammars (c/dart/proto/swift/kotlin/lua) inside its own
|
||||
* package under `vendor/`, each shipping committed per-platform prebuilds. They
|
||||
* are deliberately NOT npm dependencies and must NEVER be copied into
|
||||
* package under `vendor/`. Grammars load a matching committed prebuild when
|
||||
* available; Lua is source-built by the grammar workflow/postinstall because
|
||||
* it does not currently ship committed prebuilds. They are deliberately NOT
|
||||
* npm dependencies and must NEVER be copied into
|
||||
* `node_modules`: an undeclared package under `node_modules` is "extraneous" to
|
||||
* every subsequent `npm`/`npx` arborist reify, which prunes or relocates it.
|
||||
* That is the root cause of #2111 / #1728 — on Windows the relocation throws
|
||||
|
|
|
|||
|
|
@ -63,10 +63,10 @@ run()
|
|||
|
||||
it('emits IMPORTS edge from main.lua to lib/util.lua', () => {
|
||||
const imports = getRelationships(result, 'IMPORTS');
|
||||
const imp = imports.find(
|
||||
const utilImports = imports.filter(
|
||||
(e) => e.sourceFilePath?.includes('main.lua') && e.targetFilePath?.includes('util.lua'),
|
||||
);
|
||||
expect(imp).toBeDefined();
|
||||
expect(utilImports).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('resolves run -> util.answer() as CALLS edge to util.lua', () => {
|
||||
|
|
@ -83,6 +83,25 @@ run()
|
|||
});
|
||||
});
|
||||
|
||||
describe('Lua scope: bare require import', () => {
|
||||
it('emits one IMPORTS edge for an unbound side-effect require', async () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lua-scope-bare-import-'));
|
||||
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);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// middleclass: class("Name", Parent) → EXTENDS + HAS_METHOD across files
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { isLanguageAvailable, loadLanguage } from '../../src/core/tree-sitter/parser-loader.js';
|
||||
import {
|
||||
isGrammarRuntimeSkipped,
|
||||
isLanguageAvailable,
|
||||
loadLanguage,
|
||||
} from '../../src/core/tree-sitter/parser-loader.js';
|
||||
import { SupportedLanguages } from '../../src/config/supported-languages.js';
|
||||
|
||||
describe('isLanguageAvailable', () => {
|
||||
|
|
@ -23,8 +27,13 @@ describe('isLanguageAvailable', () => {
|
|||
expect(isLanguageAvailable(SupportedLanguages.Swift)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for Lua in the default install (vendored grammar)', () => {
|
||||
expect(isLanguageAvailable(SupportedLanguages.Lua)).toBe(true);
|
||||
it('reports Lua availability according to the optional grammar state', () => {
|
||||
const available = isLanguageAvailable(SupportedLanguages.Lua);
|
||||
if (isGrammarRuntimeSkipped(SupportedLanguages.Lua)) {
|
||||
expect(available).toBe(false);
|
||||
} else {
|
||||
expect(typeof available).toBe('boolean');
|
||||
}
|
||||
});
|
||||
|
||||
it('handles Kotlin based on optional dependency availability', () => {
|
||||
|
|
|
|||
|
|
@ -168,4 +168,15 @@ describe('parser-loader ABI load-smoke (#1922)', () => {
|
|||
expect(tree.rootNode.type).toBe(testCase.rootType);
|
||||
});
|
||||
}
|
||||
|
||||
it('repeatedly creates and parses Lua parsers without stale scanner state', () => {
|
||||
const grammar = getLanguageGrammar(SupportedLanguages.Lua);
|
||||
for (let i = 0; i < 16; i += 1) {
|
||||
const parser = new Parser();
|
||||
parser.setLanguage(grammar as Parameters<Parser['setLanguage']>[0]);
|
||||
const snippet = i % 2 === 0 ? 'local x = "lua"\n' : '-- comment\nlocal x = [[lua]]\n';
|
||||
const tree = parser.parse(snippet);
|
||||
expect(tree.rootNode.type).toBe('chunk');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { SupportedLanguages } from '../../src/config/supported-languages.js';
|
|||
*
|
||||
* `GITNEXUS_SKIP_OPTIONAL_GRAMMARS` used to be an install-time-only env (the
|
||||
* postinstall build scripts read it). `parser-loader` now also honors it at
|
||||
* analyze time: when set, genuinely-optional grammars (swift/dart/kotlin)
|
||||
* analyze time: when set, genuinely-optional grammars (swift/dart/kotlin/lua)
|
||||
* report unavailable so the ingestion pipeline skips their files, mirroring a
|
||||
* genuinely-absent binding. Grammars that are required `dependencies` routed
|
||||
* through the optional machinery for ABI safety (C — `severity: 'error'`) are
|
||||
|
|
@ -39,6 +39,7 @@ describe('parser-loader GITNEXUS_SKIP_OPTIONAL_GRAMMARS runtime gate', () => {
|
|||
expect(isLanguageAvailable(SupportedLanguages.Swift)).toBe(false);
|
||||
expect(isLanguageAvailable(SupportedLanguages.Dart)).toBe(false);
|
||||
expect(isLanguageAvailable(SupportedLanguages.Kotlin)).toBe(false);
|
||||
expect(isLanguageAvailable(SupportedLanguages.Lua)).toBe(false);
|
||||
});
|
||||
|
||||
it('skip=all/true/* also skip every optional grammar', async () => {
|
||||
|
|
@ -47,6 +48,7 @@ describe('parser-loader GITNEXUS_SKIP_OPTIONAL_GRAMMARS runtime gate', () => {
|
|||
expect(isLanguageAvailable(SupportedLanguages.Swift), `value=${v}`).toBe(false);
|
||||
expect(isLanguageAvailable(SupportedLanguages.Dart), `value=${v}`).toBe(false);
|
||||
expect(isLanguageAvailable(SupportedLanguages.Kotlin), `value=${v}`).toBe(false);
|
||||
expect(isLanguageAvailable(SupportedLanguages.Lua), `value=${v}`).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -312,6 +312,17 @@ const CASES: ReadonlyMap<SupportedLanguages, ConformanceCase> = new Map([
|
|||
reachesDecoy: 'package:app/http.dart',
|
||||
},
|
||||
],
|
||||
[
|
||||
SupportedLanguages.Lua,
|
||||
{
|
||||
files: ['vendor/ghost.lua', 'lib/main.lua'],
|
||||
fromFile: 'lib/main.lua',
|
||||
resolutionConfig: undefined,
|
||||
external: 'external.ghost',
|
||||
decoy: 'vendor/ghost.lua',
|
||||
reachesDecoy: 'vendor.ghost',
|
||||
},
|
||||
],
|
||||
[
|
||||
SupportedLanguages.Swift,
|
||||
{
|
||||
|
|
@ -376,6 +387,7 @@ const KNOWN_GAPS: ReadonlyMap<SupportedLanguages, string> = new Map<SupportedLan
|
|||
[SupportedLanguages.Ruby, '`rails/generators` -> `lib/generators.rb`'],
|
||||
[SupportedLanguages.PHP, '`Vendor\\Ghost\\Missing` -> `lib/Legacy/Missing.php`'],
|
||||
[SupportedLanguages.Dart, '`package:http/http.dart` -> `lib/http.dart`'],
|
||||
[SupportedLanguages.Lua, '`external.ghost` -> `vendor/ghost.lua`'],
|
||||
[SupportedLanguages.Swift, '`Foundation` -> `Sources/Foundation/Thing.swift`'],
|
||||
[SupportedLanguages.C, '`stdio.h` -> `src/stdio.h`'],
|
||||
[SupportedLanguages.CPlusPlus, '`cstdio.h` -> `src/cstdio.h`'],
|
||||
|
|
|
|||
|
|
@ -528,6 +528,19 @@ const FIXTURES: ReadonlyMap<SupportedLanguages, ImportTargetFixture> = new Map<
|
|||
minimumParsedFileReads: 0,
|
||||
},
|
||||
],
|
||||
[
|
||||
SupportedLanguages.Lua,
|
||||
{
|
||||
files: ['lib/models.lua', 'lib/main.lua'],
|
||||
fromFile: 'lib/main.lua',
|
||||
resolutionConfig: undefined,
|
||||
missTarget: (i) => `vendor${i}.ghost`,
|
||||
hitTarget: 'lib.models',
|
||||
parsedImport: IGNORES_CONTEXT,
|
||||
minimumScans: 1,
|
||||
minimumParsedFileReads: 0,
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ struct ScannerState
|
|||
|
||||
void *tree_sitter_lua_external_scanner_create()
|
||||
{
|
||||
return malloc(sizeof(struct ScannerState));
|
||||
return calloc(1, sizeof(struct ScannerState));
|
||||
}
|
||||
|
||||
void tree_sitter_lua_external_scanner_destroy(void *payload)
|
||||
|
|
@ -278,12 +278,12 @@ bool tree_sitter_lua_external_scanner_scan(void *payload, TSLexer *lexer, const
|
|||
|
||||
if (valid_symbols[STRING_START])
|
||||
{
|
||||
// try to match a short single-quoted string's start (")
|
||||
// try to match a short single-quoted string's start (')
|
||||
if (consume_if(lexer, SQ_STRING_DELIMITER))
|
||||
{
|
||||
state->started = SHORT_SQ_STRING;
|
||||
}
|
||||
// try to match a short double-quoted string's start (')
|
||||
// try to match a short double-quoted string's start (")
|
||||
else if (consume_if(lexer, DQ_STRING_DELIMITER))
|
||||
{
|
||||
state->started = SHORT_DQ_STRING;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue