Skip to content

Commit e24fbdd

Browse files
arontsangglennawatson
authored andcommitted
feature: Add Roslyn Analyzer for Fody plugin (#2199)
1 parent c661691 commit e24fbdd

File tree

11 files changed

+1163
-2
lines changed

11 files changed

+1163
-2
lines changed

build.cake

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ var packageWhitelist = new List<FilePath>
2121
MakeAbsolute(File("./src/ReactiveUI.Events.XamEssentials/ReactiveUI.Events.XamEssentials.csproj")),
2222
MakeAbsolute(File("./src/ReactiveUI.Events.XamForms/ReactiveUI.Events.XamForms.csproj")),
2323
MakeAbsolute(File("./src/ReactiveUI.Fody/ReactiveUI.Fody.csproj")),
24+
MakeAbsolute(File("./src/ReactiveUI.Fody.Analyzer/ReactiveUI.Fody.Analyzer.csproj")),
2425
MakeAbsolute(File("./src/ReactiveUI.Fody.Helpers/ReactiveUI.Fody.Helpers.csproj")),
2526
MakeAbsolute(File("./src/ReactiveUI.AndroidSupport/ReactiveUI.AndroidSupport.csproj")),
2627
MakeAbsolute(File("./src/ReactiveUI.AndroidX/ReactiveUI.AndroidX.csproj")),
@@ -53,7 +54,8 @@ if (IsRunningOnWindows())
5354
{
5455
packageTestWhitelist.AddRange(new[]
5556
{
56-
MakeAbsolute(File("./src/ReactiveUI.Fody.Tests/ReactiveUI.Fody.Tests.csproj"))
57+
MakeAbsolute(File("./src/ReactiveUI.Fody.Tests/ReactiveUI.Fody.Tests.csproj")),
58+
MakeAbsolute(File("./src/ReactiveUI.Fody.Analyzer.Test/ReactiveUI.Fody.Analyzer.Test.csproj"))
5759
});
5860
}
5961

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CodeActions;
8+
using Microsoft.CodeAnalysis.Formatting;
9+
using Microsoft.CodeAnalysis.Simplification;
10+
using System.Collections.Generic;
11+
using System.Linq;
12+
using System.Threading;
13+
14+
namespace TestHelper
15+
{
16+
/// <summary>
17+
/// Diagnostic Producer class with extra methods dealing with applying codefixes
18+
/// All methods are static.
19+
/// </summary>
20+
public abstract partial class CodeFixVerifier : DiagnosticVerifier
21+
{
22+
/// <summary>
23+
/// Apply the inputted CodeAction to the inputted document.
24+
/// Meant to be used to apply codefixes.
25+
/// </summary>
26+
/// <param name="document">The Document to apply the fix on.</param>
27+
/// <param name="codeAction">A CodeAction that will be applied to the Document.</param>
28+
/// <returns>A Document with the changes from the CodeAction.</returns>
29+
private static Document ApplyFix(Document document, CodeAction codeAction)
30+
{
31+
var operations = codeAction.GetOperationsAsync(CancellationToken.None).Result;
32+
var solution = operations.OfType<ApplyChangesOperation>().Single().ChangedSolution;
33+
return solution.GetDocument(document.Id);
34+
}
35+
36+
/// <summary>
37+
/// Compare two collections of Diagnostics,and return a list of any new diagnostics that appear only in the second collection.
38+
/// Note: Considers Diagnostics to be the same if they have the same Ids. In the case of multiple diagnostics with the same Id in a row,
39+
/// this method may not necessarily return the new one.
40+
/// </summary>
41+
/// <param name="diagnostics">The Diagnostics that existed in the code before the CodeFix was applied.</param>
42+
/// <param name="newDiagnostics">The Diagnostics that exist in the code after the CodeFix was applied.</param>
43+
/// <returns>A list of Diagnostics that only surfaced in the code after the CodeFix was applied.</returns>
44+
private static IEnumerable<Diagnostic> GetNewDiagnostics(IEnumerable<Diagnostic> diagnostics, IEnumerable<Diagnostic> newDiagnostics)
45+
{
46+
var oldArray = diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray();
47+
var newArray = newDiagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray();
48+
49+
int oldIndex = 0;
50+
int newIndex = 0;
51+
52+
while (newIndex < newArray.Length)
53+
{
54+
if (oldIndex < oldArray.Length && oldArray[oldIndex].Id == newArray[newIndex].Id)
55+
{
56+
++oldIndex;
57+
++newIndex;
58+
}
59+
else
60+
{
61+
yield return newArray[newIndex++];
62+
}
63+
}
64+
}
65+
66+
/// <summary>
67+
/// Get the existing compiler diagnostics on the inputted document.
68+
/// </summary>
69+
/// <param name="document">The Document to run the compiler diagnostic analyzers on.</param>
70+
/// <returns>The compiler diagnostics that were found in the code.</returns>
71+
private static IEnumerable<Diagnostic> GetCompilerDiagnostics(Document document)
72+
{
73+
return document.GetSemanticModelAsync().Result.GetDiagnostics();
74+
}
75+
76+
/// <summary>
77+
/// Given a document, turn it into a string based on the syntax root.
78+
/// </summary>
79+
/// <param name="document">The Document to be converted to a string.</param>
80+
/// <returns>A string containing the syntax of the Document after formatting.</returns>
81+
private static string GetStringFromDocument(Document document)
82+
{
83+
var simplifiedDoc = Simplifier.ReduceAsync(document, Simplifier.Annotation).Result;
84+
var root = simplifiedDoc.GetSyntaxRootAsync().Result;
85+
root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace);
86+
return root.GetText().ToString();
87+
}
88+
}
89+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using System;
7+
using System.Collections.Generic;
8+
using Microsoft.CodeAnalysis;
9+
10+
namespace TestHelper
11+
{
12+
/// <summary>
13+
/// Struct that stores information about a Diagnostic appearing in a source.
14+
/// </summary>
15+
public struct DiagnosticResult
16+
{
17+
private IList<DiagnosticResultLocation> _locations;
18+
19+
/// <summary>
20+
/// Gets or sets the locations of the Analysis Result.
21+
/// </summary>
22+
public IList<DiagnosticResultLocation> Locations
23+
{
24+
get
25+
{
26+
if (_locations == null)
27+
{
28+
_locations = Array.Empty<DiagnosticResultLocation>();
29+
}
30+
31+
return _locations;
32+
}
33+
34+
set
35+
{
36+
_locations = value;
37+
}
38+
}
39+
40+
/// <summary>
41+
/// Gets or Sets Severity of the Analysis Result.
42+
/// </summary>
43+
public DiagnosticSeverity Severity { get; set; }
44+
45+
/// <summary>
46+
/// Gets or sets the Id of the Analysis Result.
47+
/// </summary>
48+
public string Id { get; set; }
49+
50+
/// <summary>
51+
/// Gets or sets the Analysis Result Message.
52+
/// </summary>
53+
public string Message { get; set; }
54+
55+
/// <summary>
56+
/// Gets the Path of the source file that caused the Analysis Result.
57+
/// </summary>
58+
public string Path
59+
{
60+
get
61+
{
62+
return Locations.Count > 0 ? Locations[0].Path : string.Empty;
63+
}
64+
}
65+
66+
/// <summary>
67+
/// Gets the line number of the source that caused the Analysis Result.
68+
/// </summary>
69+
public int Line
70+
{
71+
get
72+
{
73+
return Locations.Count > 0 ? Locations[0].Line : -1;
74+
}
75+
}
76+
77+
/// <summary>
78+
/// Gets the column number of the source that caused the Analysis Result.
79+
/// </summary>
80+
public int Column
81+
{
82+
get
83+
{
84+
return Locations.Count > 0 ? Locations[0].Column : -1;
85+
}
86+
}
87+
}
88+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using System;
7+
8+
namespace TestHelper
9+
{
10+
/// <summary>
11+
/// Location where the diagnostic appears, as determined by path, line number, and column number.
12+
/// </summary>
13+
public struct DiagnosticResultLocation
14+
{
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="DiagnosticResultLocation"/> struct.
17+
/// </summary>
18+
/// <param name="path">The Source File where the issues exists.</param>
19+
/// <param name="line">The line number of the result.</param>
20+
/// <param name="column">The column of the Result.</param>
21+
public DiagnosticResultLocation(string path, int line, int column)
22+
{
23+
if (line < -1)
24+
{
25+
throw new ArgumentOutOfRangeException(nameof(line), "line must be >= -1");
26+
}
27+
28+
if (column < -1)
29+
{
30+
throw new ArgumentOutOfRangeException(nameof(column), "column must be >= -1");
31+
}
32+
33+
Path = path;
34+
Line = line;
35+
Column = column;
36+
}
37+
38+
/// <summary>
39+
/// Gets Path of the source file, which has issues.
40+
/// </summary>
41+
public string Path { get; }
42+
43+
/// <summary>
44+
/// Gets the line number of the issue.
45+
/// </summary>
46+
public int Line { get; }
47+
48+
/// <summary>
49+
/// Gets the columns of the issue.
50+
/// </summary>
51+
public int Column { get; }
52+
53+
/// <summary>
54+
/// Compares two DiagnosticResultLocation for Equality.
55+
/// </summary>
56+
/// <param name="left">Left.</param>
57+
/// <param name="right">Right.</param>
58+
/// <returns>Are Equal.</returns>
59+
public static bool operator ==(DiagnosticResultLocation left, DiagnosticResultLocation right)
60+
{
61+
return left.Equals(right);
62+
}
63+
64+
public static bool operator !=(DiagnosticResultLocation left, DiagnosticResultLocation right)
65+
{
66+
return !left.Equals(right);
67+
}
68+
69+
/// <summary>
70+
/// Compares two DiagnosticResultLocation for Equality.
71+
/// </summary>
72+
/// <param name="other">Other object to compare to.</param>
73+
/// <returns>Are Equal.</returns>
74+
public bool Equals(DiagnosticResultLocation other)
75+
{
76+
return string.Equals(Path, other.Path) && Line == other.Line && Column == other.Column;
77+
}
78+
79+
/// <summary>
80+
/// Compares two DiagnosticResultLocation for Equality.
81+
/// </summary>
82+
/// <param name="obj">Other object to compare to.</param>
83+
/// <returns>Are Equal.</returns>
84+
public override bool Equals(object obj)
85+
{
86+
return obj is DiagnosticResultLocation other && Equals(other);
87+
}
88+
89+
/// <summary>
90+
/// Gets HashCode.
91+
/// </summary>
92+
/// <returns>HashCode.</returns>
93+
public override int GetHashCode()
94+
{
95+
unchecked
96+
{
97+
var hashCode = Path != null ? Path.GetHashCode() : 0;
98+
hashCode = (hashCode * 397) ^ Line;
99+
hashCode = (hashCode * 397) ^ Column;
100+
return hashCode;
101+
}
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)