mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-08 22:22:52 +00:00
* fix: bind Razor ViewComponent names to in-repo classes
Index Component.InvokeAsync("Name") and in-repo ViewComponent("Name")
as CALLS to workspace ViewComponent classes so impact sees real callers
instead of an empty graph. SDK types stay unresolved.
Fixes #2991
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(autofix): apply prettier + eslint fixes via /autofix command
* fix: scan Razor and C# ViewComponent names without regex holes
Use string-aware lexers so combined Name= aliases, code-block calls,
this/base helpers, and escaped @@ markup match ASP.NET instead of
emitting false or missing CALLS.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(autofix): apply prettier + eslint fixes via /autofix command
* perf: skip Razor scans without ViewComponent tokens
Preserve the lexer correctness fixes while avoiding per-character work for
the common view that cannot contain a supported invocation.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test: gate Razor ViewComponent extractor scaling in CI
Wire mixed-corpus tripwire + GITNEXUS_BENCH loader/scaling checks into the dedicated ci-tests benchmarks job so the #2991 lexer cannot regress without a wall-clock gate.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: read Razor views through one file handle
CodeQL js/file-system-race: the size gate stat'd the path and the read
re-resolved it, so a template swapped in between could be read past the
size ceiling. Both now go through the same handle.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
127 lines
4.4 KiB
TypeScript
127 lines
4.4 KiB
TypeScript
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
getRelationships,
|
|
runPipelineFromRepo,
|
|
writeFixtureRepo,
|
|
type PipelineResult,
|
|
} from './resolvers/helpers.js';
|
|
|
|
describe('C# Razor ViewComponent conventions', () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-csharp-razor-vc-'));
|
|
let result: PipelineResult;
|
|
|
|
beforeAll(() => vi.stubEnv('GITNEXUS_WORKER_READY_TIMEOUT_MS', '60000'));
|
|
|
|
beforeAll(async () => {
|
|
writeFixtureRepo(root, {
|
|
'Components/SessionSummaryBarViewComponent.cs': `
|
|
namespace Demo.Components;
|
|
public class SessionSummaryBarViewComponent : ViewComponent
|
|
{
|
|
public object Invoke() => new object();
|
|
}
|
|
`,
|
|
'Components/MenuViewComponent.cs': `
|
|
namespace Demo.Components;
|
|
[ApiController, ViewComponent(Name = "AccountMenu")]
|
|
public class MenuViewComponent : ViewComponent
|
|
{
|
|
public object Invoke() => new object();
|
|
}
|
|
`,
|
|
'One/DuplicateViewComponent.cs': `
|
|
namespace Demo.One;
|
|
public class DuplicateViewComponent : ViewComponent {}
|
|
`,
|
|
'Two/DuplicateViewComponent.cs': `
|
|
namespace Demo.Two;
|
|
public class DuplicateViewComponent : ViewComponent {}
|
|
`,
|
|
'Views/Home/Index.cshtml': `
|
|
@await Component.InvokeAsync("SessionSummaryBar", new { id = 1 })
|
|
<vc:account-menu />
|
|
@{
|
|
await Component.InvokeAsync("SessionSummaryBar");
|
|
}
|
|
@@await Component.InvokeAsync("SessionSummaryBar")
|
|
<!-- @await Component.InvokeAsync("AccountMenu") -->
|
|
`,
|
|
'Views/Shared/Alias.cshtml': `@await Component.InvokeAsync("AccountMenu")`,
|
|
'Views/Shared/Ambiguous.cshtml': `@await Component.InvokeAsync("Duplicate")`,
|
|
'Views/Shared/Commented.cshtml': `
|
|
@* @await Component.InvokeAsync("SessionSummaryBar") *@
|
|
`,
|
|
'Views/Shared/Suffix.cshtml': `@await Component.InvokeAsync("Menu")`,
|
|
'Controllers/HomeController.cs': `
|
|
using Microsoft.AspNetCore.Mvc;
|
|
namespace Demo.Controllers;
|
|
public class HomeController : Controller
|
|
{
|
|
public IViewComponentResult Widget() => ViewComponent("SessionSummaryBar");
|
|
public IViewComponentResult FromBase() => base.ViewComponent("SessionSummaryBar");
|
|
public IViewComponentResult FromThis() => this.ViewComponent("SessionSummaryBar");
|
|
}
|
|
`,
|
|
});
|
|
result = await runPipelineFromRepo(root, () => {}, { skipGraphPhases: true });
|
|
}, 120000);
|
|
|
|
afterAll(() => {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
it('emits File-to-Class CALLS for literal and tag-helper invocations', () => {
|
|
const calls = getRelationships(result, 'CALLS').filter(
|
|
(edge) => edge.rel.reason === 'aspnet-razor-view-component',
|
|
);
|
|
|
|
expect(
|
|
calls
|
|
.map((edge) => ({
|
|
source: edge.sourceFilePath,
|
|
target: edge.target,
|
|
targetLabel: edge.targetLabel,
|
|
}))
|
|
.sort((a, b) => `${a.source}:${a.target}`.localeCompare(`${b.source}:${b.target}`)),
|
|
).toEqual([
|
|
{
|
|
source: 'Controllers/HomeController.cs',
|
|
target: 'SessionSummaryBarViewComponent',
|
|
targetLabel: 'Class',
|
|
},
|
|
{
|
|
source: 'Views/Home/Index.cshtml',
|
|
target: 'MenuViewComponent',
|
|
targetLabel: 'Class',
|
|
},
|
|
{
|
|
source: 'Views/Home/Index.cshtml',
|
|
target: 'SessionSummaryBarViewComponent',
|
|
targetLabel: 'Class',
|
|
},
|
|
{
|
|
source: 'Views/Shared/Alias.cshtml',
|
|
target: 'MenuViewComponent',
|
|
targetLabel: 'Class',
|
|
},
|
|
]);
|
|
expect(calls.some((edge) => edge.target === 'ViewComponent')).toBe(false);
|
|
expect(calls.some((edge) => edge.target === 'InvokeAsync')).toBe(false);
|
|
expect(calls.some((edge) => edge.sourceFilePath === 'Components/MenuViewComponent.cs')).toBe(
|
|
false,
|
|
);
|
|
});
|
|
|
|
it('fails closed for ambiguous names, Razor comments, and replaced suffixes', () => {
|
|
const razorSources = getRelationships(result, 'CALLS')
|
|
.filter((edge) => edge.rel.reason === 'aspnet-razor-view-component')
|
|
.map((edge) => edge.sourceFilePath);
|
|
|
|
expect(razorSources).not.toContain('Views/Shared/Ambiguous.cshtml');
|
|
expect(razorSources).not.toContain('Views/Shared/Commented.cshtml');
|
|
expect(razorSources).not.toContain('Views/Shared/Suffix.cshtml');
|
|
});
|
|
});
|