Skip to content
Permalink
Browse files

feature: Add Roslyn Analyzer for Fody plugin (#2199)

  • Loading branch information...
arontsang authored and glennawatson committed Oct 12, 2019
1 parent c661691 commit e24fbdd739dd6986f2ff78facf66064bc96b1908
@@ -21,6 +21,7 @@ var packageWhitelist = new List<FilePath>
MakeAbsolute(File("./src/ReactiveUI.Events.XamEssentials/ReactiveUI.Events.XamEssentials.csproj")),
MakeAbsolute(File("./src/ReactiveUI.Events.XamForms/ReactiveUI.Events.XamForms.csproj")),
MakeAbsolute(File("./src/ReactiveUI.Fody/ReactiveUI.Fody.csproj")),
MakeAbsolute(File("./src/ReactiveUI.Fody.Analyzer/ReactiveUI.Fody.Analyzer.csproj")),
MakeAbsolute(File("./src/ReactiveUI.Fody.Helpers/ReactiveUI.Fody.Helpers.csproj")),
MakeAbsolute(File("./src/ReactiveUI.AndroidSupport/ReactiveUI.AndroidSupport.csproj")),
MakeAbsolute(File("./src/ReactiveUI.AndroidX/ReactiveUI.AndroidX.csproj")),
@@ -53,7 +54,8 @@ if (IsRunningOnWindows())
{
packageTestWhitelist.AddRange(new[]
{
MakeAbsolute(File("./src/ReactiveUI.Fody.Tests/ReactiveUI.Fody.Tests.csproj"))
MakeAbsolute(File("./src/ReactiveUI.Fody.Tests/ReactiveUI.Fody.Tests.csproj")),
MakeAbsolute(File("./src/ReactiveUI.Fody.Analyzer.Test/ReactiveUI.Fody.Analyzer.Test.csproj"))
});
}

@@ -0,0 +1,89 @@
// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Simplification;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace TestHelper
{
/// <summary>
/// Diagnostic Producer class with extra methods dealing with applying codefixes
/// All methods are static.
/// </summary>
public abstract partial class CodeFixVerifier : DiagnosticVerifier
{
/// <summary>
/// Apply the inputted CodeAction to the inputted document.
/// Meant to be used to apply codefixes.
/// </summary>
/// <param name="document">The Document to apply the fix on.</param>
/// <param name="codeAction">A CodeAction that will be applied to the Document.</param>
/// <returns>A Document with the changes from the CodeAction.</returns>
private static Document ApplyFix(Document document, CodeAction codeAction)
{
var operations = codeAction.GetOperationsAsync(CancellationToken.None).Result;
var solution = operations.OfType<ApplyChangesOperation>().Single().ChangedSolution;
return solution.GetDocument(document.Id);
}

/// <summary>
/// Compare two collections of Diagnostics,and return a list of any new diagnostics that appear only in the second collection.
/// 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,
/// this method may not necessarily return the new one.
/// </summary>
/// <param name="diagnostics">The Diagnostics that existed in the code before the CodeFix was applied.</param>
/// <param name="newDiagnostics">The Diagnostics that exist in the code after the CodeFix was applied.</param>
/// <returns>A list of Diagnostics that only surfaced in the code after the CodeFix was applied.</returns>
private static IEnumerable<Diagnostic> GetNewDiagnostics(IEnumerable<Diagnostic> diagnostics, IEnumerable<Diagnostic> newDiagnostics)
{
var oldArray = diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray();
var newArray = newDiagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray();

int oldIndex = 0;
int newIndex = 0;

while (newIndex < newArray.Length)
{
if (oldIndex < oldArray.Length && oldArray[oldIndex].Id == newArray[newIndex].Id)
{
++oldIndex;
++newIndex;
}
else
{
yield return newArray[newIndex++];
}
}
}

/// <summary>
/// Get the existing compiler diagnostics on the inputted document.
/// </summary>
/// <param name="document">The Document to run the compiler diagnostic analyzers on.</param>
/// <returns>The compiler diagnostics that were found in the code.</returns>
private static IEnumerable<Diagnostic> GetCompilerDiagnostics(Document document)
{
return document.GetSemanticModelAsync().Result.GetDiagnostics();
}

/// <summary>
/// Given a document, turn it into a string based on the syntax root.
/// </summary>
/// <param name="document">The Document to be converted to a string.</param>
/// <returns>A string containing the syntax of the Document after formatting.</returns>
private static string GetStringFromDocument(Document document)
{
var simplifiedDoc = Simplifier.ReduceAsync(document, Simplifier.Annotation).Result;
var root = simplifiedDoc.GetSyntaxRootAsync().Result;
root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace);
return root.GetText().ToString();
}
}
}
@@ -0,0 +1,88 @@
// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;

namespace TestHelper
{
/// <summary>
/// Struct that stores information about a Diagnostic appearing in a source.
/// </summary>
public struct DiagnosticResult
{
private IList<DiagnosticResultLocation> _locations;

/// <summary>
/// Gets or sets the locations of the Analysis Result.
/// </summary>
public IList<DiagnosticResultLocation> Locations
{
get
{
if (_locations == null)
{
_locations = Array.Empty<DiagnosticResultLocation>();
}

return _locations;
}

set
{
_locations = value;
}
}

/// <summary>
/// Gets or Sets Severity of the Analysis Result.
/// </summary>
public DiagnosticSeverity Severity { get; set; }

/// <summary>
/// Gets or sets the Id of the Analysis Result.
/// </summary>
public string Id { get; set; }

/// <summary>
/// Gets or sets the Analysis Result Message.
/// </summary>
public string Message { get; set; }

/// <summary>
/// Gets the Path of the source file that caused the Analysis Result.
/// </summary>
public string Path
{
get
{
return Locations.Count > 0 ? Locations[0].Path : string.Empty;
}
}

/// <summary>
/// Gets the line number of the source that caused the Analysis Result.
/// </summary>
public int Line
{
get
{
return Locations.Count > 0 ? Locations[0].Line : -1;
}
}

/// <summary>
/// Gets the column number of the source that caused the Analysis Result.
/// </summary>
public int Column
{
get
{
return Locations.Count > 0 ? Locations[0].Column : -1;
}
}
}
}
@@ -0,0 +1,104 @@
// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;

namespace TestHelper
{
/// <summary>
/// Location where the diagnostic appears, as determined by path, line number, and column number.
/// </summary>
public struct DiagnosticResultLocation
{
/// <summary>
/// Initializes a new instance of the <see cref="DiagnosticResultLocation"/> struct.
/// </summary>
/// <param name="path">The Source File where the issues exists.</param>
/// <param name="line">The line number of the result.</param>
/// <param name="column">The column of the Result.</param>
public DiagnosticResultLocation(string path, int line, int column)
{
if (line < -1)
{
throw new ArgumentOutOfRangeException(nameof(line), "line must be >= -1");
}

if (column < -1)
{
throw new ArgumentOutOfRangeException(nameof(column), "column must be >= -1");
}

Path = path;
Line = line;
Column = column;
}

/// <summary>
/// Gets Path of the source file, which has issues.
/// </summary>
public string Path { get; }

/// <summary>
/// Gets the line number of the issue.
/// </summary>
public int Line { get; }

/// <summary>
/// Gets the columns of the issue.
/// </summary>
public int Column { get; }

/// <summary>
/// Compares two DiagnosticResultLocation for Equality.
/// </summary>
/// <param name="left">Left.</param>
/// <param name="right">Right.</param>
/// <returns>Are Equal.</returns>
public static bool operator ==(DiagnosticResultLocation left, DiagnosticResultLocation right)
{
return left.Equals(right);
}

public static bool operator !=(DiagnosticResultLocation left, DiagnosticResultLocation right)
{
return !left.Equals(right);
}

/// <summary>
/// Compares two DiagnosticResultLocation for Equality.
/// </summary>
/// <param name="other">Other object to compare to.</param>
/// <returns>Are Equal.</returns>
public bool Equals(DiagnosticResultLocation other)
{
return string.Equals(Path, other.Path) && Line == other.Line && Column == other.Column;
}

/// <summary>
/// Compares two DiagnosticResultLocation for Equality.
/// </summary>
/// <param name="obj">Other object to compare to.</param>
/// <returns>Are Equal.</returns>
public override bool Equals(object obj)
{
return obj is DiagnosticResultLocation other && Equals(other);
}

/// <summary>
/// Gets HashCode.
/// </summary>
/// <returns>HashCode.</returns>
public override int GetHashCode()
{
unchecked
{
var hashCode = Path != null ? Path.GetHashCode() : 0;
hashCode = (hashCode * 397) ^ Line;
hashCode = (hashCode * 397) ^ Column;
return hashCode;
}
}
}
}

0 comments on commit e24fbdd

Please sign in to comment.
You can’t perform that action at this time.