Editor/Projection/Tests/ScssNormalizer.cs
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Grains.RazorDesigner.Projection.Tests;
public static class ScssNormalizer
{
private static readonly HashSet<string> Shapes = new( StringComparer.Ordinal )
{
"width",
"height",
"flex-grow",
"flex-shrink",
"flex-basis",
"flex-direction",
"justify-content",
"align-items",
"gap",
"flex-wrap",
"padding",
"margin",
"min-width",
"max-width",
"min-height",
"max-height",
"font-family",
"font-size",
"font-weight",
"color",
"text-align",
"background-color",
"background-image",
"background-size",
"background-position",
"background-repeat",
"border-radius",
"border-width",
"border-color",
"box-shadow",
"opacity",
"cursor",
"overflow",
// CSS Tier 3 (grd-7t2z)
"position",
"top",
"left",
"right",
"bottom",
"font-style",
"text-transform",
"letter-spacing",
"line-height",
"z-index",
"pointer-events",
};
private static readonly Regex DeclarationPattern =
new( @"^\s*(?<prop>[\w-]+)\s*:\s*(?<val>.+?)\s*;?\s*$", RegexOptions.Compiled );
// Matches the start of a checkmark nested block.
private static readonly Regex CheckmarkOpen =
new( @"^\s*>\s*\.checkmark\s*\{", RegexOptions.Compiled );
private static readonly Regex PseudoOpen =
new( @"^\s*&:(?<sel>[\w-]+(?:\([^)]*\))?)\s*\{", RegexOptions.Compiled );
// Matches a closing brace.
private static readonly Regex ClosingBrace =
new( @"^\s*\}\s*$", RegexOptions.Compiled );
public static IReadOnlyList<string> Normalize( IReadOnlyList<string> lines )
{
var result = new List<string>( lines.Count );
int i = 0;
while ( i < lines.Count )
{
var line = lines[i];
// --- Checkmark nested block ---
if ( CheckmarkOpen.IsMatch( line ) )
{
result.Add( "> .checkmark {" );
i++;
// Consume inner lines until closing brace
while ( i < lines.Count )
{
var inner = lines[i];
if ( ClosingBrace.IsMatch( inner ) )
{
result.Add( "}" );
i++;
break;
}
// Inner declarations use the same whitelist
result.Add( NormalizeSingleLine( inner ) );
i++;
}
continue;
}
// --- Pseudo-class nested block (grd-74lj): &:hover { ... } ---
var pseudoMatch = PseudoOpen.Match( line );
if ( pseudoMatch.Success )
{
var sel = pseudoMatch.Groups["sel"].Value;
result.Add( $"&:{sel} {{" );
i++;
while ( i < lines.Count )
{
var inner = lines[i];
if ( ClosingBrace.IsMatch( inner ) )
{
result.Add( "}" );
i++;
break;
}
result.Add( NormalizeSingleLine( inner ) );
i++;
}
continue;
}
// --- Regular declaration ---
result.Add( NormalizeSingleLine( line ) );
i++;
}
return result;
}
private static string NormalizeSingleLine( string line )
{
var m = DeclarationPattern.Match( line );
if ( !m.Success )
{
throw new InvalidOperationException(
$"ScssNormalizer: unrecognized declaration '{line.Trim()}' — extend the whitelist in ScssNormalizer.Shapes" );
}
var prop = m.Groups["prop"].Value;
var val = m.Groups["val"].Value.Trim();
if ( !Shapes.Contains( prop ) )
{
throw new InvalidOperationException(
$"ScssNormalizer: unrecognized declaration '{line.Trim()}' — extend the whitelist in ScssNormalizer.Shapes" );
}
return $"{prop}: {val};";
}
}