Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 39 additions & 19 deletions Darling/Darling.Tests/AnalysisPassTokenThreadingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,29 +98,29 @@ public void NoStoreCallOnTheAnalysisPassRunsWithoutThePassToken()
{
var offenders = new List<string>();

foreach (var (file, lines) in AnalysisSources())
foreach (var (file, code, source) in AnalysisSources())
{
var enclosing = string.Empty;

for (var i = 0; i < lines.Length; i++)
for (var i = 0; i < code.Length; i++)
{
var declaration = s_memberDeclaration.Match(lines[i]);
var declaration = s_memberDeclaration.Match(code[i]);
if (declaration.Success)
{
enclosing = declaration.Groups["name"].Value;
}

if (!s_untokenedStoreCall.IsMatch(lines[i]))
if (!s_untokenedStoreCall.IsMatch(code[i]))
{
continue;
}

if (s_exempt.ContainsKey(enclosing) && DocBlockAbove(lines, IndexOfDeclaration(lines, i)).Contains(ExemptionMarker, StringComparison.Ordinal))
if (s_exempt.ContainsKey(enclosing) && DocBlockAbove(source, IndexOfDeclaration(code, i)).Contains(ExemptionMarker, StringComparison.Ordinal))
{
continue;
}

offenders.Add($"{file}:{i + 1} in {enclosing}(): {lines[i].Trim()}");
offenders.Add($"{file}:{i + 1} in {enclosing}(): {source[i].Trim()}");
}
}

Expand All @@ -144,25 +144,25 @@ public void EveryExemptionIsStillEarningItsPlace()
var untokenedBy = new Dictionary<string, int>(StringComparer.Ordinal);
var markedMethods = new HashSet<string>(StringComparer.Ordinal);

foreach (var (_, lines) in AnalysisSources())
foreach (var (_, code, source) in AnalysisSources())
{
var enclosing = string.Empty;
var enclosingAt = -1;

for (var i = 0; i < lines.Length; i++)
for (var i = 0; i < code.Length; i++)
{
var declaration = s_memberDeclaration.Match(lines[i]);
var declaration = s_memberDeclaration.Match(code[i]);
if (declaration.Success)
{
enclosing = declaration.Groups["name"].Value;
enclosingAt = i;
if (DocBlockAbove(lines, enclosingAt).Contains(ExemptionMarker, StringComparison.Ordinal))
if (DocBlockAbove(source, enclosingAt).Contains(ExemptionMarker, StringComparison.Ordinal))
{
markedMethods.Add(enclosing);
}
}

if (s_untokenedStoreCall.IsMatch(lines[i]) && enclosingAt >= 0)
if (s_untokenedStoreCall.IsMatch(code[i]) && enclosingAt >= 0)
{
untokenedBy[enclosing] = untokenedBy.TryGetValue(enclosing, out var n) ? n + 1 : 1;
}
Expand Down Expand Up @@ -194,18 +194,18 @@ public void EveryCatchOnTheFactCollectorLetsAnAbandonmentThrough()
@"^\s*catch\s*\(\s*Exception\s+ex\s*\)\s*when\s*\(\s*!AnalysisShutdown\.IsExpectedAbandon\(",
RegexOptions.Compiled | RegexOptions.CultureInvariant);

foreach (var (file, lines) in AnalysisSources())
foreach (var (file, code, source) in AnalysisSources())
{
if (!Path.GetFileName(file).StartsWith("PgFactCollector.", StringComparison.Ordinal))
{
continue;
}

for (var i = 0; i < lines.Length; i++)
for (var i = 0; i < code.Length; i++)
{
if (opensCatch.IsMatch(lines[i]) && !classified.IsMatch(lines[i]))
if (opensCatch.IsMatch(code[i]) && !classified.IsMatch(code[i]))
{
bare.Add($"{file}:{i + 1}: {lines[i].Trim()}");
bare.Add($"{file}:{i + 1}: {source[i].Trim()}");
}
}
}
Expand All @@ -219,7 +219,7 @@ public void EveryCatchOnTheFactCollectorLetsAnAbandonmentThrough()
straight to the pass, which is the same outcome by a shorter route. */
Assert.Equal(27, AnalysisSources()
.Where(s => Path.GetFileName(s.File).StartsWith("PgFactCollector.", StringComparison.Ordinal))
.SelectMany(s => s.Lines)
.SelectMany(s => s.Code)
.Count(line => classified.IsMatch(line)));
}

Expand Down Expand Up @@ -325,7 +325,11 @@ private static int IndexOfDeclaration(string[] lines, int callLine)
return 0;
}

/// <summary>The contiguous <c>///</c> block immediately above a declaration, as one string.</summary>
/// <summary>The contiguous <c>///</c> block immediately above a declaration, as one string. Reads the
/// file AS WRITTEN, not the walked code: the exemption marker lives in a comment, so stripping comments
/// is exactly what must not happen here. The <c>///</c> prefix is the right test for the same reason —
/// a doc comment is prefixed on every line by definition, which is what a <c>/* … */</c> body in this
/// codebase is not.</summary>
private static string DocBlockAbove(string[] lines, int declarationLine)
{
var doc = new List<string>();
Expand All @@ -337,15 +341,31 @@ private static string DocBlockAbove(string[] lines, int declarationLine)
return string.Join("\n", doc);
}

private static IEnumerable<(string File, string[] Lines)> AnalysisSources()
/// <summary>
/// Two views of each file, line-for-line aligned. <c>Code</c> is
/// <see cref="CSharpSourceWalker.StripCommentsAndStrings"/>'s output and is what every regex here reads,
/// so a commented-out call and a doc comment naming <c>ReadAsync()</c> are neither offenders nor
/// counted. <c>Source</c> is the file as written, and exists for the two things that are ABOUT the
/// prose: <see cref="DocBlockAbove"/>, which reads the exemption marker out of a doc comment, and the
/// offender messages, which should quote what someone will find when they open the file.
///
/// <para>The alignment is what makes one index serve both: the walk blanks comments and literal text to
/// spaces and leaves every newline in place, so line <c>i</c> is line <c>i</c> in either view.</para>
/// </summary>
private static IEnumerable<(string File, string[] Code, string[] Source)> AnalysisSources()
{
var files = Directory.GetFiles(AnalysisDirectory(), "*.cs", SearchOption.TopDirectoryOnly);
Assert.NotEmpty(files);

foreach (var file in files.OrderBy(f => f, StringComparer.Ordinal))
{
/* The working copy is CRLF; split on the LF so a line never carries a stray CR. */
yield return (Path.GetFileName(file), ReadSource(file).Replace("\r\n", "\n", StringComparison.Ordinal).Split('\n'));
var source = ReadSource(file).Replace("\r\n", "\n", StringComparison.Ordinal);

yield return (
Path.GetFileName(file),
CSharpSourceWalker.StripCommentsAndStrings(source).Split('\n'),
source.Split('\n'));
}
}

Expand Down
Loading
Loading