< Summary

Information
Class: AspxLint.Core.Rules.Attr001MissingQuotes
Assembly: AspxLint.Core
File(s): D:\a\claude-aspx-lint\claude-aspx-lint\src\AspxLint.Core\Rules\Attr001MissingQuotes.cs
Line coverage
100%
Covered lines: 27
Uncovered lines: 0
Coverable lines: 27
Total lines: 72
Line coverage: 100%
Branch coverage
100%
Covered branches: 2
Total branches: 2
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Id()100%11100%
get_Name()100%11100%
get_Severity()100%11100%
get_Description()100%11100%
get_HasFix()100%11100%
.cctor()100%11100%
Fix(...)100%22100%
FixSegment(...)100%11100%

File(s)

D:\a\claude-aspx-lint\claude-aspx-lint\src\AspxLint.Core\Rules\Attr001MissingQuotes.cs

#LineLine coverage
 1using System.Text;
 2using System.Text.RegularExpressions;
 3
 4namespace AspxLint.Core.Rules;
 5
 6public sealed class Attr001MissingQuotes : IRule
 7{
 2098    public string Id => "ATTR-001";
 309    public string Name => "Attribut sans guillemets";
 2710    public Severity Severity => Severity.Warning;
 11    public string Description =>
 2512        "Les valeurs d'attributs doivent toujours etre entre guillemets doubles pour respecter XHTML. Sinon les caracter
 4613    public bool HasFix => true;
 14
 15    // attr=value — value est tout sauf espace, guillemet, < > = / (donc une vraie valeur "nue").
 516    private static readonly Regex AttrUnquoted = new(
 517        @"\s([a-zA-Z][a-zA-Z0-9\-:_]*)=([^\s""'<>=/]+)",
 518        RegexOptions.Compiled);
 19
 520    private static readonly Regex Comments = new(@"<!--|-->", RegexOptions.Compiled);
 521    private static readonly Regex Directive = new(@"<%@", RegexOptions.Compiled);
 22
 523    private static readonly Regex AspBlock = new(@"<%[\s\S]*?%>", RegexOptions.Compiled);
 524    private static readonly Regex TagWithAttrs = new(
 525        @"(<[a-zA-Z][a-zA-Z0-9:_\-]*\b)([^>]*)>",
 526        RegexOptions.Compiled);
 27
 28    public IEnumerable<Issue> Detect(string content, string[] lines, RuleContext ctx)
 29    {
 30        for (int i = 0; i < lines.Length; i++)
 31        {
 32            var line = lines[i];
 33            if (Comments.IsMatch(line)) continue;
 34            if (Directive.IsMatch(line)) continue;
 35
 36            foreach (Match m in AttrUnquoted.Matches(line))
 37            {
 38                // Verifie qu'on est bien a l'interieur d'une balise non encore fermee.
 39                var before = line[..m.Index];
 40                var lastOpen = before.LastIndexOf('<');
 41                var lastClose = before.LastIndexOf('>');
 42                if (lastOpen <= lastClose) continue;
 43
 44                yield return new Issue(Id, Name, Severity,
 45                    i + 1, m.Index + 1, m.Value.Trim(),
 46                    $"Entourer la valeur de guillemets : {m.Groups[1].Value}=\"{m.Groups[2].Value}\"");
 47            }
 48        }
 49    }
 50
 51    public string? Fix(string content, RuleContext ctx)
 52    {
 53        // Decoupe par bloc <% %> pour ne JAMAIS toucher au code serveur.
 2354        var sb = new StringBuilder(content.Length + 64);
 2355        int last = 0;
 7256        foreach (Match m in AspBlock.Matches(content))
 57        {
 1358            sb.Append(FixSegment(content[last..m.Index]));
 1359            sb.Append(m.Value);
 1360            last = m.Index + m.Length;
 61        }
 2362        sb.Append(FixSegment(content[last..]));
 2363        return sb.ToString();
 64    }
 65
 66    private static string FixSegment(string text) =>
 3667        TagWithAttrs.Replace(text, m =>
 3668            m.Groups[1].Value
 3669            + AttrUnquoted.Replace(m.Groups[2].Value,
 3670                mm => $" {mm.Groups[1].Value}=\"{mm.Groups[2].Value}\"")
 3671            + ">");
 72}