fix(xaml): index escaped literal keys and verify COPY recovery

This commit is contained in:
azizur100389 2026-09-16 11:52:30 +01:00
parent baca584645
commit c3b7c4a9b4
4 changed files with 91 additions and 5 deletions

View file

@ -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),

View file

@ -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,

View file

@ -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'])(

View file

@ -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&amp;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&amp;B', '{}&#123;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>`,