Commit 691584ab authored by Nathan Bean's avatar Nathan Bean
Browse files

Added new in member analyzer

parent 90481eab
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading.Tasks;
using VerifyCS = CodeAnalyzers.Test.CSharpAnalyzerVerifier<
KSU.CS.Pendant.CodeAnalysis.DesignAnalyzers.NewInMemberAnalyzer>;
namespace KSU.CS.Pendant.CodeAnalysis.Test
{
[TestClass]
public class NewInMemberAnalyzerUnitTest
{
[TestMethod]
public async Task NewNotFoundInMethod()
{
var test = @"
namespace ConsoleApplication1
{
class BaseClass
{
public virtual void Foo() { }
}
class DerivedClass : BaseClass
{
public override void Foo() { }
}
}
";
await VerifyCS.VerifyAnalyzerAsync(test);
}
[TestMethod]
public async Task NewFoundInMethod()
{
var test = @"
namespace ConsoleApplication1
{
class BaseClass
{
public void Foo() { }
}
class DerivedClass : BaseClass
{
{|#0:public new void Foo() { }|}
}
}
";
var expected = VerifyCS.Diagnostic("FAL0002").WithLocation(0).WithArguments("Foo");
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}
[TestMethod]
public async Task NewNotFoundInProperty()
{
var test = @"
namespace ConsoleApplication1
{
class BaseClass
{
public virtual int Foo { get { return 4; } }
}
class DerivedClass : BaseClass
{
public override int Foo { get { return 5; } }
}
}
";
await VerifyCS.VerifyAnalyzerAsync(test);
}
[TestMethod]
public async Task NewFoundInProperty()
{
var test = @"
namespace ConsoleApplication1
{
class BaseClass
{
public int Foo { get { return 4; } }
}
class DerivedClass : BaseClass
{
{|#0:public new int Foo { get { return 5; } }|}
}
}
";
var expected = VerifyCS.Diagnostic("FAL0003").WithLocation(0).WithArguments("Foo");
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}
}
}
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using CodeAnalyzers;
namespace KSU.CS.Pendant.CodeAnalysis.DesignAnalyzers
{
/// <summary>
/// A Code analyzer that checks for methods and properties declared with the 'new' keyword, i.e.:
///
/// <code>
/// public class BaseClass
/// {
/// public int Foo => 3;
/// }
/// public class DerivedClass
/// {
/// public new int Foo => 5;
/// }
/// </code>
///
/// This should almost always be replaced with virtual/override keywords in
/// the base and derived classes
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class NewInMemberAnalyzer : DiagnosticAnalyzer
{
#region Metadata
// Use of new keyword in method metadata
public const string NewInMethodDiagnosticId = "FAL0002";
private static readonly LocalizableString NewInMethodTitle = new LocalizableResourceString(nameof(Resources.NewInMethodAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString NewInMethodMessageFormat = new LocalizableResourceString(nameof(Resources.NewInMethodAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString NewInMethodDescription = new LocalizableResourceString(nameof(Resources.NewInMethodAnalyzerDescription), Resources.ResourceManager, typeof(Resources));
private const string NewInMethodCategory = "Design";
private static readonly DiagnosticDescriptor NewInMethodRule = new DiagnosticDescriptor(NewInMethodDiagnosticId, NewInMethodTitle, NewInMethodMessageFormat, NewInMethodCategory, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: NewInMethodDescription);
// Use of new keyword in property metadata
public const string NewInPropertyDiagnosticId = "FAL0003";
private static readonly LocalizableString NewInPropertyTitle = new LocalizableResourceString(nameof(Resources.NewInPropertyAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString NewInPropertyMessageFormat = new LocalizableResourceString(nameof(Resources.NewInPropertyAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString NewInPropertyDescription = new LocalizableResourceString(nameof(Resources.NewInPropertyAnalyzerDescription), Resources.ResourceManager, typeof(Resources));
private const string NewInPropertyCategory = "Design";
private static readonly DiagnosticDescriptor NewInPropertyRule = new DiagnosticDescriptor(NewInPropertyDiagnosticId, NewInPropertyTitle, NewInPropertyMessageFormat, NewInPropertyCategory, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: NewInPropertyDescription);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(NewInMethodRule, NewInPropertyRule); } }
#endregion
/// <summary>
/// Initializes the NewInMethodAnalyzer
/// </summary>
/// <param name="context">The analysis context</param>
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzePropertyNode, SyntaxKind.PropertyDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeMethodNode, SyntaxKind.MethodDeclaration);
}
/// <summary>
/// Analyzes a MethodDeclarationSyntax Node for use of the 'new' keyword
/// </summary>
/// <param name="context">The context of the MethodDeclarationSyntax Node</param>
private static void AnalyzeMethodNode(SyntaxNodeAnalysisContext context)
{
var declaration = context.Node as MethodDeclarationSyntax;
var modifiers = declaration.Modifiers;
if (modifiers.Any(m => m.IsKind(SyntaxKind.NewKeyword)))
{
// Create the diagnostic
var primaryLocation = declaration.GetLocation();
var diagnostic = Diagnostic.Create(
NewInMethodRule,
primaryLocation,
new string[] { declaration.Identifier.ValueText }
);
context.ReportDiagnostic(diagnostic);
}
}
/// <summary>
/// Analyzes a PropertyDeclarationSyntax Node for use of the 'new' keyword
/// </summary>
/// <param name="context">The context of the PropertyDeclarationSyntax Node</param>
private static void AnalyzePropertyNode(SyntaxNodeAnalysisContext context)
{
var declaration = context.Node as PropertyDeclarationSyntax;
var modifiers = declaration.Modifiers;
if (modifiers.Any(m => m.IsKind(SyntaxKind.NewKeyword)))
{
// Create the diagnostic
var primaryLocation = declaration.GetLocation();
var diagnostic = Diagnostic.Create(
NewInPropertyRule,
primaryLocation,
new string[] { declaration.Identifier.ValueText }
);
context.ReportDiagnostic(diagnostic);
}
}
}
}
......@@ -519,6 +519,60 @@ namespace CodeAnalyzers {
}
}
/// <summary>
/// Looks up a localized string similar to The use of the &apos;new&apos; keyword hides inherited method - &apos;override&apos; paired with &apos;virtual&apos; is more appropriate in most cases..
/// </summary>
internal static string NewInMethodAnalyzerDescription {
get {
return ResourceManager.GetString("NewInMethodAnalyzerDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The method &apos;{0}&apos; is declared &apos;new&apos;, when &apos;override&apos; paired with &apos;virtual&apos; is more appropriate in most cases..
/// </summary>
internal static string NewInMethodAnalyzerMessageFormat {
get {
return ResourceManager.GetString("NewInMethodAnalyzerMessageFormat", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Using &apos;new&apos; keyword hides inherited method..
/// </summary>
internal static string NewInMethodAnalyzerTitle {
get {
return ResourceManager.GetString("NewInMethodAnalyzerTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The use of the &apos;new&apos; keyword hides inherited property - &apos;override&apos; paired with &apos;virtual&apos; is more appropriate in most cases..
/// </summary>
internal static string NewInPropertyAnalyzerDescription {
get {
return ResourceManager.GetString("NewInPropertyAnalyzerDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The property &apos;{0}&apos; is declared &apos;new&apos;, when &apos;override&apos; paired with &apos;virtual&apos; is more appropriate in most cases..
/// </summary>
internal static string NewInPropertyAnalyzerMessageFormat {
get {
return ResourceManager.GetString("NewInPropertyAnalyzerMessageFormat", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Using &apos;new&apos; keyword hides inherited property..
/// </summary>
internal static string NewInPropertyAnalyzerTitle {
get {
return ResourceManager.GetString("NewInPropertyAnalyzerTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Properties should not reference themselves, doing so will cause infinite recursion..
/// </summary>
......
......@@ -273,6 +273,24 @@
<data name="NAM16AnalyzerTitle" xml:space="preserve">
<value>Local variables should be named using Camel Case</value>
</data>
<data name="NewInMethodAnalyzerDescription" xml:space="preserve">
<value>The use of the 'new' keyword hides inherited method - 'override' paired with 'virtual' is more appropriate in most cases.</value>
</data>
<data name="NewInMethodAnalyzerMessageFormat" xml:space="preserve">
<value>The method '{0}' is declared 'new', when 'override' paired with 'virtual' is more appropriate in most cases.</value>
</data>
<data name="NewInMethodAnalyzerTitle" xml:space="preserve">
<value>Using 'new' keyword hides inherited method.</value>
</data>
<data name="NewInPropertyAnalyzerDescription" xml:space="preserve">
<value>The use of the 'new' keyword hides inherited property - 'override' paired with 'virtual' is more appropriate in most cases.</value>
</data>
<data name="NewInPropertyAnalyzerMessageFormat" xml:space="preserve">
<value>The property '{0}' is declared 'new', when 'override' paired with 'virtual' is more appropriate in most cases.</value>
</data>
<data name="NewInPropertyAnalyzerTitle" xml:space="preserve">
<value>Using 'new' keyword hides inherited property.</value>
</data>
<data name="PropertySelfReferenceAnalyzerDescription" xml:space="preserve">
<value>Properties should not reference themselves, doing so will cause infinite recursion.</value>
</data>
......@@ -336,7 +354,6 @@
<data name="XMLC06AnalyzerTitle" xml:space="preserve">
<value>Property is missing summary XML comment tag</value>
</data>
<data name="XMLC07AnalyzerDescription" xml:space="preserve">
<value>Method declaration should be proceeded by a XML comment containing a summary tag describing the method</value>
</data>
......@@ -346,7 +363,6 @@
<data name="XMLC07AnalyzerTitle" xml:space="preserve">
<value>Method is missing summary XML comment tag</value>
</data>
<data name="XMLC08AnalyzerDescription" xml:space="preserve">
<value>Method declaration should be proceeded by a XML comment containing a param tag describing the parameters</value>
</data>
......@@ -356,7 +372,6 @@
<data name="XMLC08AnalyzerTitle" xml:space="preserve">
<value>Method is missing param XML comment tag</value>
</data>
<data name="XMLC09AnalyzerDescription" xml:space="preserve">
<value>Method declaration should be proceeded by a XML comment containing a returns tag describing the return value</value>
</data>
......@@ -366,7 +381,6 @@
<data name="XMLC09AnalyzerTitle" xml:space="preserve">
<value>Method is missing returns XML comment tag</value>
</data>
<data name="XMLC10AnalyzerDescription" xml:space="preserve">
<value>The XML comment is malformed and cannot be parsed</value>
</data>
......
......@@ -107,6 +107,7 @@ namespace KSU.CS.Pendant.Server
new Claim(ClaimTypes.Email, user.Email)
};
if (user.IsStudent) userClaims.Add(new Claim(ClaimTypes.Role, "Student"));
if (user.IsTeachingAssistant) userClaims.Add(new Claim(ClaimTypes.Role, "Teaching Assistant"));
//if(user.IsStaff) userClaims.Add(new Claim(ClaimTypes.Role, "Staff"));
if (user.IsFaculty) userClaims.Add(new Claim(ClaimTypes.Role, "Faculty"));
if (user.IsAdmin) userClaims.Add(new Claim(ClaimTypes.Role, "Admin"));
......
......@@ -59,7 +59,23 @@ namespace KSU.CS.Pendant.Server.Controllers
_context.Update(user);
await _context.SaveChangesAsync();
return View("Index");
return RedirectToAction("Index");
}
/// <summary>
/// Deletes the user
/// </summary>
/// <returns>A HTML redirect</returns>
[Authorize(Policy = "AdminOnly")]
public async Task<IActionResult> Destroy(int id)
{
var user = await _context.Users.FindAsync(id);
if (user is null) return StatusCode(404);
_context.Users.Remove(user);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
}
}
......@@ -12,6 +12,7 @@ using KSU.CS.Pendant.Server.Models;
using KSU.CS.Pendant.Server.Helpers;
using Microsoft.EntityFrameworkCore;
using KSU.CS.Pendant.Server.Services;
using System.Text.Json;
namespace KSU.CS.Pendant.Server.Controllers
{
......@@ -46,7 +47,7 @@ namespace KSU.CS.Pendant.Server.Controllers
.FirstAsync(i => i.ID == int.Parse(User.FindFirst("ID").Value));
// Only show students thier own validation attempts
if(!(user.IsAdmin || user.IsFaculty)) return View(user.ValidationAttempts);
if (!(user.IsAdmin || user.IsFaculty || user.IsTeachingAssistant)) return View(user.ValidationAttempts);
// load ALL validation attempts
var attempts = await _context.ValidationAttempts
......@@ -63,7 +64,7 @@ namespace KSU.CS.Pendant.Server.Controllers
/// </summary>
/// <param name="id"></param>
/// <returns>The details of the validation, or a 404 if it is not found or the user is not authorized</returns>
public async Task<IActionResult> Details(int id)
public async Task<IActionResult> Details(int id, [FromQuery]string format)
{
var attempt = await _context.ValidationAttempts
.Include(va => va.Assignment)
......@@ -80,7 +81,16 @@ namespace KSU.CS.Pendant.Server.Controllers
.FirstAsync(i => i.ID == int.Parse(User.FindFirst("ID").Value));
// Make sure the user is authorized to see this validation
if (!(user.IsAdmin || user.IsFaculty) && attempt.User.ID != int.Parse(User.FindFirst("ID").Value)) return StatusCode(404);
if (!(user.IsAdmin || user.IsFaculty || user.IsTeachingAssistant) && attempt.User.ID != int.Parse(User.FindFirst("ID").Value)) return StatusCode(404);
// Return json if the user requests
if (format == "json") return Json(new
{
design_issues = attempt.DesignIssues.Select(i => i.Message.ToString()),
functional_issues = attempt.FunctionalIssues.Select(i => i.Message.ToString()),
structural_issues = attempt.StructuralIssues.Select(i => i.Message.ToString()),
style_issues = attempt.StyleIssues.Select(i => i.Message.ToString())
}, new JsonSerializerOptions { WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping});
return View(attempt);
}
......
......@@ -37,6 +37,7 @@ namespace KSU.CS.Pendant.Server
.Build();
options.AddPolicy("AdminOnly", policy => policy.RequireClaim(System.Security.Claims.ClaimTypes.Role, "Admin"));
options.AddPolicy("FacultyOnly", policy => policy.RequireClaim(System.Security.Claims.ClaimTypes.Role, "Faculty", "Admin"));
options.AddPolicy("TeachingAssistantOnly", policy => policy.RequireClaim(System.Security.Claims.ClaimTypes.Role, "Teaching Assistant"));
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
......
......@@ -9,9 +9,10 @@
<th>@Html.DisplayNameFor(model => model.FirstName)</th>
<th>@Html.DisplayNameFor(model => model.LastName)</th>
<th>@Html.DisplayNameFor(model => model.EID)</th>
<th>@Html.DisplayNameFor(model => model.IsStudent)</th>
<th>@Html.DisplayNameFor(model => model.IsFaculty)</th>
<th>@Html.DisplayNameFor(model => model.IsAdmin)</th>
<th>Student</th>
<th>TA</th>
<th>Faculty</th>
<th>Admin</th>
<th></th>
</tr>
@foreach (var item in Model)
......@@ -21,6 +22,7 @@
<td>@Html.DisplayFor(modelItem => item.LastName)</td>
<td>@Html.DisplayFor(modelItem => item.EID)</td>
<td>@Html.DisplayFor(modelItem => item.IsStudent)</td>
<td>@Html.DisplayFor(modelItem => item.IsTeachingAssistant)</td>
<td>@Html.DisplayFor(modelItem => item.IsFaculty)</td>
<td>@Html.DisplayFor(modelItem => item.IsAdmin)</td>
<td>
......
......@@ -8,14 +8,15 @@
<div class="col-md-6">
<form asp-action="Update" enctype="application/x-www-form-urlencoded">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="ID" />
<div class="form-group">
<label asp-for="FirstName" class="control-label"></label>
<input asp-for="FirstName" class="form-control" readonly/>
<input asp-for="FirstName" class="form-control" readonly />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName" class="control-label"></label>
<input asp-for="LastName" class="form-control" readonly/>
<input asp-for="LastName" class="form-control" readonly />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<div class="form-group">
......@@ -23,6 +24,11 @@
<input asp-for="IsStudent" class="form-control" />
<span asp-validation-for="IsStudent" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="IsTeachingAssistant" class="control-label"></label>
<input asp-for="IsTeachingAssistant" class="form-control" />
<span asp-validation-for="IsTeachingAssistant" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="IsFaculty" class="control-label"></label>
<input asp-for="IsFaculty" class="form-control" />
......
......@@ -40,7 +40,8 @@
@Html.DisplayFor(modelItem => item.IssueCount) Found
</td>
<td>
<a asp-action="Details" asp-route-id="@item.ID">Details</a>
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Details" asp-route-id="@item.ID" asp-route-format="json">JSON</a>
</td>
</tr>
}
......
......@@ -20,12 +20,14 @@ namespace KSU.CS.ProgramVerifier
{
_designAnalyzers = new DiagnosticAnalyzer[]
{
new PropertySelfReferenceAnalyzer()
new PropertySelfReferenceAnalyzer(),
new NewInMemberAnalyzer()
}.ToImmutableArray();
_styleAnalyzers = new DiagnosticAnalyzer[]
{
new NamingConventionsAnalyzer()
new NamingConventionsAnalyzer(),
new CommentsAnalyzer()
}.ToImmutableArray();
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment