| | | 1 | | using System.Text.RegularExpressions; |
| | | 2 | | |
| | | 3 | | namespace AspxLint.Core.Rules; |
| | | 4 | | |
| | | 5 | | public sealed class Ws002MixedIndent : IRule |
| | | 6 | | { |
| | 205 | 7 | | public string Id => "WS-002"; |
| | 30 | 8 | | public string Name => "Indentation mixte (tabulations + espaces)"; |
| | 27 | 9 | | public Severity Severity => Severity.Warning; |
| | | 10 | | public string Description => |
| | 25 | 11 | | "Melanger tabulations et espaces dans l'indentation d'un meme fichier rend l'affichage variable selon l'editeur. |
| | 46 | 12 | | public bool HasFix => true; |
| | | 13 | | |
| | 5 | 14 | | private static readonly Regex IndentRegex = new(@"^[ \t]*", RegexOptions.Compiled); |
| | 5 | 15 | | private static readonly Regex LeadingTabRegex = new(@"^\t", RegexOptions.Compiled); |
| | 5 | 16 | | private static readonly Regex LeadingSpacesRegex = new(@"^ +", RegexOptions.Compiled); |
| | 5 | 17 | | private static readonly Regex IndentReplaceRegex = new(@"^[ \t]+", RegexOptions.Compiled); |
| | | 18 | | |
| | | 19 | | public IEnumerable<Issue> Detect(string content, string[] lines, RuleContext ctx) |
| | | 20 | | { |
| | | 21 | | bool hasTab = false, hasSpace = false; |
| | | 22 | | foreach (var line in lines) |
| | | 23 | | { |
| | | 24 | | var indent = IndentRegex.Match(line).Value; |
| | | 25 | | if (indent.Contains('\t')) hasTab = true; |
| | | 26 | | if (LeadingSpacesRegex.IsMatch(line)) hasSpace = true; |
| | | 27 | | } |
| | | 28 | | if (!(hasTab && hasSpace)) yield break; |
| | | 29 | | |
| | | 30 | | var emitted = false; |
| | | 31 | | for (int i = 0; i < lines.Length; i++) |
| | | 32 | | { |
| | | 33 | | var indent = IndentRegex.Match(lines[i]).Value; |
| | | 34 | | if (indent.Contains('\t') && indent.Contains(' ')) |
| | | 35 | | { |
| | | 36 | | emitted = true; |
| | | 37 | | var stripped = lines[i].TrimStart(); |
| | | 38 | | yield return new Issue(Id, Name, Severity, i + 1, 1, |
| | | 39 | | "⇥ + ⎵ " + (stripped.Length <= 50 ? stripped : stripped[..50]), |
| | | 40 | | "Cette ligne melange tabulations et espaces dans son indentation."); |
| | | 41 | | } |
| | | 42 | | } |
| | | 43 | | if (!emitted) |
| | | 44 | | { |
| | | 45 | | // Aucun melange intra-ligne, mais le fichier melange des lignes-tab et des lignes-espace. |
| | | 46 | | int firstTab = -1, firstSpace = -1; |
| | | 47 | | for (int i = 0; i < lines.Length; i++) |
| | | 48 | | { |
| | | 49 | | if (firstTab < 0 && LeadingTabRegex.IsMatch(lines[i])) firstTab = i; |
| | | 50 | | if (firstSpace < 0 && LeadingSpacesRegex.IsMatch(lines[i])) firstSpace = i; |
| | | 51 | | if (firstTab >= 0 && firstSpace >= 0) break; |
| | | 52 | | } |
| | | 53 | | if (firstTab >= 0) |
| | | 54 | | { |
| | | 55 | | var stripped = lines[firstTab].TrimStart(); |
| | | 56 | | yield return new Issue(Id, Name, Severity, firstTab + 1, 1, |
| | | 57 | | "⇥ " + (stripped.Length <= 50 ? stripped : stripped[..50]), |
| | | 58 | | $"Le fichier melange indentation par tabulation (ici) et par espaces (ligne {firstSpace + 1})."); |
| | | 59 | | } |
| | | 60 | | } |
| | | 61 | | } |
| | | 62 | | |
| | | 63 | | public string? Fix(string content, RuleContext ctx) |
| | | 64 | | { |
| | | 65 | | // Convertit toute tab d'indentation en 4 espaces — ne touche pas aux tabs au milieu d'une ligne. |
| | 21 | 66 | | var lines = content.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); |
| | 170 | 67 | | for (int i = 0; i < lines.Length; i++) |
| | | 68 | | { |
| | 64 | 69 | | var m = IndentReplaceRegex.Match(lines[i]); |
| | 64 | 70 | | if (!m.Success) continue; |
| | 16 | 71 | | var indent = m.Value.Replace("\t", " "); |
| | 16 | 72 | | lines[i] = indent + lines[i][m.Length..]; |
| | | 73 | | } |
| | 21 | 74 | | return string.Join("\n", lines); |
| | | 75 | | } |
| | | 76 | | } |