mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-07 02:58:02 +00:00
fix(xaml): index escaped literal keys and verify COPY recovery
This commit is contained in:
parent
baca584645
commit
c3b7c4a9b4
4 changed files with 91 additions and 5 deletions
|
|
@ -68,10 +68,14 @@ export function extractXamlDeclarations(source: string): DocumentDeclaration[] {
|
|||
!DIRECTIVES.has(parts[1])
|
||||
)
|
||||
continue;
|
||||
if (typeof value !== 'string' || !value.trim() || /[{}&]/.test(value)) continue;
|
||||
if (typeof value !== 'string' || value.includes('&')) continue;
|
||||
// A leading {} escapes the entire remaining value, including further braces.
|
||||
const escaped = value.startsWith('{}');
|
||||
const name = escaped ? value.slice(2) : value;
|
||||
if (!name.trim() || (!escaped && /[{}]/.test(name))) continue;
|
||||
if (span?.startIndex === undefined || span.endIndex === undefined) continue;
|
||||
declarations.push({
|
||||
name: value,
|
||||
name,
|
||||
description: `${tag} x:${parts[1]} declaration`,
|
||||
startIndex: span.startIndex,
|
||||
startLine: lineAt(span.startIndex),
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ describe('XAML pipeline documents (#3202)', () => {
|
|||
fixtures.push(repo);
|
||||
await fs.writeFile(
|
||||
path.join(repo.dbPath, 'Home.XAML'),
|
||||
`<Page xmlns:x="${NS}" x:Class="App.Home">\n<Button x:Name="Save" Click="SaveClicked" />\n<Style x:Key="Accent" />\n</Page>`,
|
||||
`<Page xmlns:x="${NS}" x:Class="App.Home">\n<Button x:Name="Save" Click="SaveClicked" />\n<Style x:Key="Accent" />\n<Style x:Key="{}EscapedAccent" />\n<Style x:Key="{}{x:Type Button}" />\n<Style x:Key="{x:Type Button}" />\n</Page>`,
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(repo.dbPath, 'Broken.xaml'),
|
||||
|
|
@ -35,7 +35,9 @@ describe('XAML pipeline documents (#3202)', () => {
|
|||
expect(declarations.map((node) => node.properties.name).sort()).toEqual([
|
||||
'Accent',
|
||||
'App.Home',
|
||||
'EscapedAccent',
|
||||
'Save',
|
||||
'{x:Type Button}',
|
||||
]);
|
||||
expect(declarations.find((node) => node.properties.name === 'Save')?.properties.startLine).toBe(
|
||||
1,
|
||||
|
|
|
|||
|
|
@ -87,12 +87,21 @@ describe('persisted XAML declarations (#3202)', () => {
|
|||
'<Grid xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="BeforeFailure" />',
|
||||
);
|
||||
const options = { skipAgentsMd: true, registryName: 'copy-thread-failure-fixture' };
|
||||
let initialThreads = 0;
|
||||
const originalCopy = adapter.loadGraphToLbug;
|
||||
const copy = vi.spyOn(adapter, 'loadGraphToLbug').mockImplementationOnce(async (...args) => {
|
||||
const rows = await adapter.executeQuery("CALL current_setting('threads') RETURN *");
|
||||
initialThreads = Number(rows[0].threads);
|
||||
return originalCopy(...args);
|
||||
});
|
||||
await runFullAnalysis(repo.dbPath, options, { onProgress() {} });
|
||||
expect(initialThreads).toBeGreaterThan(0);
|
||||
copy.mockClear();
|
||||
const { storagePath } = getStoragePaths(repo.dbPath);
|
||||
const before = await loadMeta(storagePath);
|
||||
expect(before).not.toBeNull();
|
||||
const failure = new Error('Injected incremental COPY failure');
|
||||
const copy = vi.spyOn(adapter, 'loadGraphToLbug').mockImplementationOnce(async () => {
|
||||
copy.mockImplementationOnce(async () => {
|
||||
const rows = await adapter.executeQuery("CALL current_setting('threads') RETURN *");
|
||||
expect(Number(rows[0].threads)).toBe(1);
|
||||
throw failure;
|
||||
|
|
@ -109,6 +118,51 @@ describe('persisted XAML declarations (#3202)', () => {
|
|||
indexedAt: before?.indexedAt,
|
||||
fileHashes: before?.fileHashes,
|
||||
});
|
||||
copy.mockImplementationOnce(async (...args) => {
|
||||
const rows = await adapter.executeQuery("CALL current_setting('threads') RETURN *");
|
||||
expect(Number(rows[0].threads)).toBe(initialThreads);
|
||||
return originalCopy(...args);
|
||||
});
|
||||
await runFullAnalysis(repo.dbPath, options, { onProgress() {} });
|
||||
expect(copy).toHaveBeenCalledTimes(2);
|
||||
expect((await loadMeta(storagePath))?.incrementalInProgress).toBeUndefined();
|
||||
await initLbug(getStoragePaths(repo.dbPath).lbugPath);
|
||||
expect((await searchFTSFromLbug('AfterFailure')).results).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ filePath: 'CopyFailure.xaml' })]),
|
||||
);
|
||||
}, 180_000);
|
||||
|
||||
it('persists and searches escaped literal resource keys without the escape prefix', async () => {
|
||||
const repo = await setupMiniRepo();
|
||||
const home = await createTempDir();
|
||||
fixtures.push(repo, home);
|
||||
vi.stubEnv('GITNEXUS_HOME', home.dbPath);
|
||||
await fs.writeFile(
|
||||
path.join(repo.dbPath, 'Escaped.xaml'),
|
||||
'<Grid xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">' +
|
||||
'<Style x:Key="{}EscapedResource" />' +
|
||||
'<Style x:Key="{}{LiteralResource}" />' +
|
||||
'<Style x:Key="{x:Type Button}" /></Grid>',
|
||||
);
|
||||
await runFullAnalysis(
|
||||
repo.dbPath,
|
||||
{ skipAgentsMd: true, registryName: 'escaped-key-fixture' },
|
||||
{ onProgress() {} },
|
||||
);
|
||||
await initLbug(getStoragePaths(repo.dbPath).lbugPath);
|
||||
const rows = await executePrepared(
|
||||
'MATCH (n:Section) WHERE n.filePath = $file RETURN n.id AS id, n.name AS name ORDER BY name',
|
||||
{ file: 'Escaped.xaml' },
|
||||
);
|
||||
expect(rows).toEqual([
|
||||
{ id: expect.any(String), name: 'EscapedResource' },
|
||||
{ id: expect.any(String), name: '{LiteralResource}' },
|
||||
]);
|
||||
for (const [index, term] of ['EscapedResource', 'LiteralResource'].entries()) {
|
||||
expect((await searchFTSFromLbug(term)).results.flatMap((hit) => hit.nodeIds ?? [])).toContain(
|
||||
rows[index].id,
|
||||
);
|
||||
}
|
||||
}, 180_000);
|
||||
|
||||
it.each(['node-id', 'file-path'])(
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ describe('XAML document declarations (#3202)', () => {
|
|||
expect(
|
||||
extractXamlDeclarations(`<Grid xmlns:x="${NS}">
|
||||
<Button Content="{Binding User}" Click="Save" />
|
||||
<Style x:Key="{x:Type Button}" /><Style x:Key="{}escaped" />
|
||||
<Style x:Key="{x:Type Button}" />
|
||||
<Button x:Name="" /><Style x:Key="A&B" />
|
||||
<![CDATA[<Button x:Name="Fake" />]]>
|
||||
</Grid>`),
|
||||
|
|
@ -66,6 +66,32 @@ describe('XAML document declarations (#3202)', () => {
|
|||
expect(extractXamlDeclarations('<Button x:Name="Undeclared" />')).toEqual([]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['{}escaped', 'escaped'],
|
||||
['{}{x:Type Button}', '{x:Type Button}'],
|
||||
['{}{}nested', '{}nested'],
|
||||
['{}left{right}', 'left{right}'],
|
||||
])('indexes escaped literal resource key %s as %s', (value, name) => {
|
||||
const source = `<Style xmlns:q="${NS}" q:Key="${value}" />`;
|
||||
expect(extractXamlDeclarations(source)).toEqual([
|
||||
{
|
||||
name,
|
||||
description: 'Style x:Key declaration',
|
||||
startIndex: 0,
|
||||
startLine: 0,
|
||||
endLine: 0,
|
||||
level: 1,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it.each(['{}', '{} ', '{}A&B', '{}{key'])(
|
||||
'does not index empty or unresolved entity values after an escape: %s',
|
||||
(value) => {
|
||||
expect(extractXamlDeclarations(`<Style xmlns:x="${NS}" x:Key="${value}" />`)).toEqual([]);
|
||||
},
|
||||
);
|
||||
|
||||
it('keeps duplicate names distinct and preserves Unicode names', () => {
|
||||
const declarations = extractXamlDeclarations(
|
||||
`<Grid xmlns:x="${NS}"><Button x:Name="Save" /><Button x:Name="Save" /><Button x:Name="\u03a9" /></Grid>`,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue