Skip to content

Commit

Permalink
(GH-70) Refactored to implement validations from interface
Browse files Browse the repository at this point in the history
- That way, all validations can be loaded at once
- Based on conversation in GitHub issue
  • Loading branch information
gep13 committed Feb 13, 2019
1 parent 703134c commit 5f7f9f1
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 86 deletions.
101 changes: 15 additions & 86 deletions src/Chocolatey.Language.Server/DiagnosticsHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,29 @@
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
using Buffer = Microsoft.Language.Xml.Buffer;
using DiagnosticSeverity = OmniSharp.Extensions.LanguageServer.Protocol.Models.DiagnosticSeverity;

namespace Chocolatey.Language.Server
{
public class DiagnosticsHandler
{
private readonly ILanguageServer _router;
private readonly BufferManager _bufferManager;
private static readonly IReadOnlyCollection<string> TemplatedValues = new []
{
"__replace",
"space_separated",
"tag1"
};
private IList<INuSpecRule> _rules = new List<INuSpecRule>();

public DiagnosticsHandler(ILanguageServer router, BufferManager bufferManager)
{
_router = router;
_bufferManager = bufferManager;

var typeLocator = new TypeLocator();
foreach (var nuspecRule in typeLocator.GetTypesThatInheritOrImplement<INuSpecRule>().OrEmptyListIfNull())
{
var rule = Activator.CreateInstance(nuspecRule) as INuSpecRule;
if (rule != null)
{
_rules.Add(rule);
}
}
}

public void PublishDiagnostics(Uri uri, Buffer buffer)
Expand All @@ -37,91 +41,16 @@ public void PublishDiagnostics(Uri uri, Buffer buffer)
var textPositions = new TextPositions(text);
var diagnostics = new List<Diagnostic>();

diagnostics.AddRange(NuspecDoesNotContainTemplatedValuesRequirement(syntaxTree, textPositions));
diagnostics.AddRange(NuspecDescriptionLengthValidation(syntaxTree, textPositions));
foreach (var rule in _rules.OrEmptyListIfNull())
{
diagnostics.AddRange(rule.Validate(syntaxTree, textPositions));
}

_router.Document.PublishDiagnostics(new PublishDiagnosticsParams
{
Uri = uri,
Diagnostics = diagnostics
});
}

/// <summary>
/// Handler to validate that no templated values remain in the nuspec.
/// </summary>
/// <seealso href="https://github.com/chocolatey/package-validator/blob/master/src/chocolatey.package.validator/infrastructure.app/rules/NuspecDoesNotContainTemplatedValuesRequirement.cs">Package validator requirement for templated values.</seealso>
private IEnumerable<Diagnostic> NuspecDoesNotContainTemplatedValuesRequirement(XmlDocumentSyntax syntaxTree, TextPositions textPositions)
{
foreach (var node in syntaxTree.DescendantNodesAndSelf().OfType<XmlTextSyntax>())
{
if (!TemplatedValues.Any(x => node.Value.Contains(x, StringComparison.OrdinalIgnoreCase)))
{
continue;
}

var range = textPositions.GetRange(node.Start, node.End);

yield return new Diagnostic {
Message = "Templated value which should be removed",
Severity = DiagnosticSeverity.Error,
Range = range
};
}
}

/// <summary>
/// Handler to validate the length of description in the package metadata.
/// </summary>
/// <seealso href="https://github.com/chocolatey/package-validator/blob/master/src/chocolatey.package.validator/infrastructure.app/rules/DescriptionRequirement.cs">Package validator requirement for description.</seealso>
/// <seealso href="https://github.com/chocolatey/package-validator/blob/master/src/chocolatey.package.validator/infrastructure.app/rules/DescriptionWordCountMaximum4000Requirement.cs">Package validator maximum length requirement for description.</seealso>
/// <seealso href="https://github.com/chocolatey/package-validator/blob/master/src/chocolatey.package.validator/infrastructure.app/rules/DescriptionWordCountMinimum30Guideline.cs">Package validator minimum length guideline for description.</seealso>
private IEnumerable<Diagnostic> NuspecDescriptionLengthValidation(XmlDocumentSyntax syntaxTree, TextPositions textPositions)
{
var descriptionElement = syntaxTree.DescendantNodes().OfType<XmlElementSyntax>().FirstOrDefault(x => string.Equals(x.Name, "description", StringComparison.OrdinalIgnoreCase));

if (descriptionElement == null)
{
yield return new Diagnostic {
Message = "Description is required. See https://github.com/chocolatey/package-validator/wiki/DescriptionNotEmpty",
Severity = DiagnosticSeverity.Error,
Range = textPositions.GetRange(0, syntaxTree.End)
};
yield break;
}

var descriptionLength = descriptionElement.GetContentValue().Trim().Length;

if (descriptionLength == 0)
{
var range = textPositions.GetRange(descriptionElement.StartTag.End, descriptionElement.EndTag.Start);

yield return new Diagnostic {
Message = "Description is required. See https://github.com/chocolatey/package-validator/wiki/DescriptionNotEmpty",
Severity = DiagnosticSeverity.Error,
Range = range
};
}
else if (descriptionLength <= 30)
{
var range = textPositions.GetRange(descriptionElement.StartTag.End, descriptionElement.EndTag.Start);

yield return new Diagnostic {
Message = "Description should be sufficient to explain the software. See https://github.com/chocolatey/package-validator/wiki/DescriptionCharacterCountMinimum",
Severity = DiagnosticSeverity.Warning,
Range = range
};
}
else if (descriptionLength > 4000)
{
var range = textPositions.GetRange(descriptionElement.StartTag.End, descriptionElement.EndTag.Start);

yield return new Diagnostic {
Message = "Description should not exceed 4000 characters. See https://github.com/chocolatey/package-validator/wiki/DescriptionCharacterCountMaximum",
Severity = DiagnosticSeverity.Error,
Range = range
};
}
}
}
}
24 changes: 24 additions & 0 deletions src/Chocolatey.Language.Server/IEnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.Linq;

namespace Chocolatey.Language.Server
{
/// <summary>
/// Extensions for IEnumerable
/// </summary>
public static class EnumerableExtensions
{
/// <summary>
/// Safe for each, returns an empty Enumerable if the list to iterate is null.
/// </summary>
/// <typeparam name="T">Generic type.</typeparam>
/// <param name="source">The source.</param>
/// <returns>
/// Source if not null; otherwise Enumerable.Empty&lt;<see cref="T" />&gt;
/// </returns>
public static IEnumerable<T> OrEmptyListIfNull<T>(this IEnumerable<T> source)
{
return source ?? Enumerable.Empty<T>();
}
}
}
11 changes: 11 additions & 0 deletions src/Chocolatey.Language.Server/INuSpecRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Collections.Generic;
using Microsoft.Language.Xml;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;

namespace Chocolatey.Language.Server
{
public interface INuSpecRule
{
IEnumerable<Diagnostic> Validate(XmlDocumentSyntax syntaxTree, TextPositions textPositions);
}
}
12 changes: 12 additions & 0 deletions src/Chocolatey.Language.Server/ITypeLocator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Reflection;

namespace Chocolatey.Language.Server
{
public interface ITypeLocator
{
IEnumerable<Type> GetTypesThatInheritOrImplement<T>();
IEnumerable<Type> GetTypesThatInheritOrImplement<T>(params Assembly[] Assemblies);
}
}
36 changes: 36 additions & 0 deletions src/Chocolatey.Language.Server/TypeLocator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Reflection;

namespace Chocolatey.Language.Server
{
public class TypeLocator : ITypeLocator
{
public IEnumerable<Type> GetTypesThatInheritOrImplement<T>()
{
return GetTypesThatInheritOrImplement<T>(GetType().Assembly);
}

public IEnumerable<Type> GetTypesThatInheritOrImplement<T>(params Assembly[] assemblies)
{
var list = new List<Type>();

if (assemblies == null || assemblies.Length == 0)
{
throw new ApplicationException("TypeLocator cannot locate types without assemblies");
}

foreach (var assembly in assemblies.OrEmptyListIfNull())
{
foreach (var t in assembly.GetTypes())
{
if (!typeof(T).IsAssignableFrom(t) || t.IsInterface || t.IsAbstract) continue;

list.Add(t);
}
}

return list;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Language.Xml;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using DiagnosticSeverity = OmniSharp.Extensions.LanguageServer.Protocol.Models.DiagnosticSeverity;

namespace Chocolatey.Language.Server.Validations
{
/// <summary>
/// Handler to validate the length of description in the package metadata.
/// </summary>
/// <seealso href="https://github.com/chocolatey/package-validator/blob/master/src/chocolatey.package.validator/infrastructure.app/rules/DescriptionRequirement.cs">Package validator requirement for description.</seealso>
/// <seealso href="https://github.com/chocolatey/package-validator/blob/master/src/chocolatey.package.validator/infrastructure.app/rules/DescriptionWordCountMaximum4000Requirement.cs">Package validator maximum length requirement for description.</seealso>
/// <seealso href="https://github.com/chocolatey/package-validator/blob/master/src/chocolatey.package.validator/infrastructure.app/rules/DescriptionWordCountMinimum30Guideline.cs">Package validator minimum length guideline for description.</seealso>
public class DescriptionLengthValidation : INuSpecRule
{
public IEnumerable<Diagnostic> Validate(XmlDocumentSyntax syntaxTree, TextPositions textPositions)
{
var descriptionElement = syntaxTree.DescendantNodes().OfType<XmlElementSyntax>().FirstOrDefault(x => string.Equals(x.Name, "description", StringComparison.OrdinalIgnoreCase));

if (descriptionElement == null)
{
yield return new Diagnostic {
Message = "Description is required. See https://github.com/chocolatey/package-validator/wiki/DescriptionNotEmpty",
Severity = DiagnosticSeverity.Error,
Range = textPositions.GetRange(0, syntaxTree.End)
};
yield break;
}

var descriptionLength = descriptionElement.GetContentValue().Trim().Length;

if (descriptionLength == 0)
{
var range = textPositions.GetRange(descriptionElement.StartTag.End, descriptionElement.EndTag.Start);

yield return new Diagnostic {
Message = "Description is required. See https://github.com/chocolatey/package-validator/wiki/DescriptionNotEmpty",
Severity = DiagnosticSeverity.Error,
Range = range
};
}
else if (descriptionLength <= 30)
{
var range = textPositions.GetRange(descriptionElement.StartTag.End, descriptionElement.EndTag.Start);

yield return new Diagnostic {
Message = "Description should be sufficient to explain the software. See https://github.com/chocolatey/package-validator/wiki/DescriptionCharacterCountMinimum",
Severity = DiagnosticSeverity.Warning,
Range = range
};
}
else if (descriptionLength > 4000)
{
var range = textPositions.GetRange(descriptionElement.StartTag.End, descriptionElement.EndTag.Start);

yield return new Diagnostic {
Message = "Description should not exceed 4000 characters. See https://github.com/chocolatey/package-validator/wiki/DescriptionCharacterCountMaximum",
Severity = DiagnosticSeverity.Error,
Range = range
};
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Language.Xml;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using DiagnosticSeverity = OmniSharp.Extensions.LanguageServer.Protocol.Models.DiagnosticSeverity;

namespace Chocolatey.Language.Server.Validations
{
/// <summary>
/// Handler to validate that no templated values remain in the nuspec.
/// </summary>
/// <seealso href="https://github.com/chocolatey/package-validator/blob/master/src/chocolatey.package.validator/infrastructure.app/rules/NuspecDoesNotContainTemplatedValuesRequirement.cs">Package validator requirement for templated values.</seealso>
public class DoesNotContainTemplatedValues : INuSpecRule
{
private static readonly IReadOnlyCollection<string> TemplatedValues = new []
{
"__replace",
"space_separated",
"tag1"
};

public IEnumerable<Diagnostic> Validate(XmlDocumentSyntax syntaxTree, TextPositions textPositions)
{
foreach (var node in syntaxTree.DescendantNodesAndSelf().OfType<XmlTextSyntax>())
{
if (!TemplatedValues.Any(x => node.Value.Contains(x, StringComparison.OrdinalIgnoreCase)))
{
continue;
}

var range = textPositions.GetRange(node.Start, node.End);

yield return new Diagnostic {
Message = "Templated value which should be removed",
Severity = DiagnosticSeverity.Error,
Range = range
};
}
}
}
}

0 comments on commit 5f7f9f1

Please sign in to comment.