Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Auditing the GitHub API using Analyzers #1

Closed
wants to merge 11 commits into from
Closed
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
17 changes: 17 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Auto detect text files and perform LF normalization
* text=auto

# Custom for Visual Studio
*.cs diff=csharp

# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
28 changes: 28 additions & 0 deletions Octokit.CodeAnalysis.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.24720.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octokit.CodeAnalysis", "Octokit.CodeAnalysis\Octokit.CodeAnalysis\Octokit.CodeAnalysis.csproj", "{3A78DC9C-62E2-4B9D-A87F-8414F2C74192}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octokit.CodeAnalysis.Test", "Octokit.CodeAnalysis\Octokit.CodeAnalysis.Test\Octokit.CodeAnalysis.Test.csproj", "{C1B5C04E-D78A-48C5-9919-C57C4537B79C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3A78DC9C-62E2-4B9D-A87F-8414F2C74192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3A78DC9C-62E2-4B9D-A87F-8414F2C74192}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3A78DC9C-62E2-4B9D-A87F-8414F2C74192}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A78DC9C-62E2-4B9D-A87F-8414F2C74192}.Release|Any CPU.Build.0 = Release|Any CPU
{C1B5C04E-D78A-48C5-9919-C57C4537B79C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C1B5C04E-D78A-48C5-9919-C57C4537B79C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C1B5C04E-D78A-48C5-9919-C57C4537B79C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C1B5C04E-D78A-48C5-9919-C57C4537B79C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using Microsoft.CodeAnalysis;
using System;

namespace TestHelper
{
/// <summary>
/// Location where the diagnostic appears, as determined by path, line number, and column number.
/// </summary>
public struct DiagnosticResultLocation
{
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(line), "column must be >= -1");
}

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

public string Path { get; }
public int Line { get; }
public int Column { get; }
}

/// <summary>
/// Struct that stores information about a Diagnostic appearing in a source
/// </summary>
public struct DiagnosticResult
{
private DiagnosticResultLocation[] locations;

public DiagnosticResultLocation[] Locations
{
get
{
if (this.locations == null)
{
this.locations = new DiagnosticResultLocation[] { };
}
return this.locations;
}

set
{
this.locations = value;
}
}

public DiagnosticSeverity Severity { get; set; }

public string Id { get; set; }

public string Message { get; set; }

public string Path
{
get
{
return this.Locations.Length > 0 ? this.Locations[0].Path : "";
}
}

public int Line
{
get
{
return this.Locations.Length > 0 ? this.Locations[0].Line : -1;
}
}

public int Column
{
get
{
return this.Locations.Length > 0 ? this.Locations[0].Column : -1;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace TestHelper
{
/// <summary>
/// Class for turning strings into documents and getting the diagnostics on them
/// All methods are static
/// </summary>
public abstract partial class DiagnosticVerifier
{
private static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
private static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location);
private static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location);
private static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location);

internal static string DefaultFilePathPrefix = "Test";
internal static string CSharpDefaultFileExt = "cs";
internal static string VisualBasicDefaultExt = "vb";
internal static string TestProjectName = "TestProject";

#region Get Diagnostics

/// <summary>
/// Given classes in the form of strings, their language, and an IDiagnosticAnlayzer to apply to it, return the diagnostics found in the string after converting it to a document.
/// </summary>
/// <param name="sources">Classes in the form of strings</param>
/// <param name="language">The language the source classes are in</param>
/// <param name="analyzer">The analyzer to be run on the sources</param>
/// <returns>An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location</returns>
private static Diagnostic[] GetSortedDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer)
{
return GetSortedDiagnosticsFromDocuments(analyzer, GetDocuments(sources, language));
}

/// <summary>
/// Given an analyzer and a document to apply it to, run the analyzer and gather an array of diagnostics found in it.
/// The returned diagnostics are then ordered by location in the source document.
/// </summary>
/// <param name="analyzer">The analyzer to run on the documents</param>
/// <param name="documents">The Documents that the analyzer will be run on</param>
/// <returns>An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location</returns>
protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents)
{
var projects = new HashSet<Project>();
foreach (var document in documents)
{
projects.Add(document.Project);
}

var diagnostics = new List<Diagnostic>();
foreach (var project in projects)
{
var compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer));
var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result;
foreach (var diag in diags)
{
if (diag.Location == Location.None || diag.Location.IsInMetadata)
{
diagnostics.Add(diag);
}
else
{
for (int i = 0; i < documents.Length; i++)
{
var document = documents[i];
var tree = document.GetSyntaxTreeAsync().Result;
if (tree == diag.Location.SourceTree)
{
diagnostics.Add(diag);
}
}
}
}
}

var results = SortDiagnostics(diagnostics);
diagnostics.Clear();
return results;
}

/// <summary>
/// Sort diagnostics by location in source document
/// </summary>
/// <param name="diagnostics">The list of Diagnostics to be sorted</param>
/// <returns>An IEnumerable containing the Diagnostics in order of Location</returns>
private static Diagnostic[] SortDiagnostics(IEnumerable<Diagnostic> diagnostics)
{
return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray();
}

#endregion

#region Set up compilation and documents
/// <summary>
/// Given an array of strings as sources and a language, turn them into a project and return the documents and spans of it.
/// </summary>
/// <param name="sources">Classes in the form of strings</param>
/// <param name="language">The language the source code is in</param>
/// <returns>A Tuple containing the Documents produced from the sources and their TextSpans if relevant</returns>
private static Document[] GetDocuments(string[] sources, string language)
{
if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic)
{
throw new ArgumentException("Unsupported Language");
}

var project = CreateProject(sources, language);
var documents = project.Documents.ToArray();

if (sources.Length != documents.Length)
{
throw new SystemException("Amount of sources did not match amount of Documents created");
}

return documents;
}

/// <summary>
/// Create a Document from a string through creating a project that contains it.
/// </summary>
/// <param name="source">Classes in the form of a string</param>
/// <param name="language">The language the source code is in</param>
/// <returns>A Document created from the source string</returns>
protected static Document CreateDocument(string source, string language = LanguageNames.CSharp)
{
return CreateProject(new[] { source }, language).Documents.First();
}

/// <summary>
/// Create a project using the inputted strings as sources.
/// </summary>
/// <param name="sources">Classes in the form of strings</param>
/// <param name="language">The language the source code is in</param>
/// <returns>A Project created out of the Documents created from the source strings</returns>
private static Project CreateProject(string[] sources, string language = LanguageNames.CSharp)
{
string fileNamePrefix = DefaultFilePathPrefix;
string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt;

var projectId = ProjectId.CreateNewId(debugName: TestProjectName);

var solution = new AdhocWorkspace()
.CurrentSolution
.AddProject(projectId, TestProjectName, TestProjectName, language)
.AddMetadataReference(projectId, CorlibReference)
.AddMetadataReference(projectId, SystemCoreReference)
.AddMetadataReference(projectId, CSharpSymbolsReference)
.AddMetadataReference(projectId, CodeAnalysisReference);

int count = 0;
foreach (var source in sources)
{
var newFileName = fileNamePrefix + count + "." + fileExt;
var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName);
solution = solution.AddDocument(documentId, newFileName, SourceText.From(source));
count++;
}
return solution.GetProject(projectId);
}
#endregion
}
}

Loading