Noir::CSharpLexer
Inherits Noir::MaskedLexer < Reference < Object
CSharpLexer is a hand-rolled structural lexer for C# source, modelled on
Noir::PhpLexer. The C# analyzers count {/}/(/) per line with no
string awareness (line.count('{') - line.count('}')), so a single } or
( inside a string literal truncates a method block (dropping callees) or
makes a signature run away (dropping parameters). This lexer masks every
non-code region in one linear pass so those counters can run over code only.
Handles the C# string zoo:
- regular
"…"backslash escapes - verbatim
@"…"""is an escaped quote,\is literal - interpolated
$"…{expr}…"{{/}}are literal braces; a"inside a{ }hole opens a nested string - combined
$@"…"/@$"…" - raw
"""…"""(and$"""…""") variable-length quote fence - char
'x'/'\}'/'"' - comments
//…and/* … */
Every literal is masked to spaces (newlines preserved, length unchanged), so
the structural helpers in Noir::MaskedLexer are plain depth counters over
@masked. The source is materialised once into an Array(Char) for O(1)
indexing, keeping the scan O(n) on multi-byte (e.g. CJK-commented) source.
Constructors
Instance methods
The source with comments only blanked to spaces — string and char literals are kept intact. Line count and every column index are preserved, so a caller can keep reporting the original line number while scanning text that a commented-out (or documentation-example) route can no longer reach.
masked_lines is the wrong tool for that job: it also blanks the string
literals a route extractor needs to read. ASP.NET Core's own source
carries /// app.MapGet("/from-route/{id}", …) inside <example> XML
docs, which a raw scan happily emits as an endpoint.
The masked source split into lines, parallel to source.lines. Strings,
comments and char literals are blanked, so the C# analyzers can run their
existing per-line line.count('{') / line.count('(') counters over
masked_lines[i] (structure) while emitting lines[i] (real text).