diff --git a/src/Chocolatey.Language.Server/DiagnosticsHandler.cs b/src/Chocolatey.Language.Server/DiagnosticsHandler.cs index f74e63c2..a2a30b61 100644 --- a/src/Chocolatey.Language.Server/DiagnosticsHandler.cs +++ b/src/Chocolatey.Language.Server/DiagnosticsHandler.cs @@ -9,7 +9,6 @@ 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 { @@ -17,17 +16,22 @@ public class DiagnosticsHandler { private readonly ILanguageServer _router; private readonly BufferManager _bufferManager; - private static readonly IReadOnlyCollection TemplatedValues = new [] - { - "__replace", - "space_separated", - "tag1" - }; + private IList _rules = new List(); public DiagnosticsHandler(ILanguageServer router, BufferManager bufferManager) { _router = router; _bufferManager = bufferManager; + + var typeLocator = new TypeLocator(); + foreach (var nuspecRule in typeLocator.GetTypesThatInheritOrImplement().OrEmptyListIfNull()) + { + var rule = Activator.CreateInstance(nuspecRule) as INuSpecRule; + if (rule != null) + { + _rules.Add(rule); + } + } } public void PublishDiagnostics(Uri uri, Buffer buffer) @@ -37,8 +41,10 @@ public void PublishDiagnostics(Uri uri, Buffer buffer) var textPositions = new TextPositions(text); var diagnostics = new List(); - 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 { @@ -46,82 +52,5 @@ public void PublishDiagnostics(Uri uri, Buffer buffer) Diagnostics = diagnostics }); } - - /// - /// Handler to validate that no templated values remain in the nuspec. - /// - /// Package validator requirement for templated values. - private IEnumerable NuspecDoesNotContainTemplatedValuesRequirement(XmlDocumentSyntax syntaxTree, TextPositions textPositions) - { - foreach (var node in syntaxTree.DescendantNodesAndSelf().OfType()) - { - 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 - }; - } - } - - /// - /// Handler to validate the length of description in the package metadata. - /// - /// Package validator requirement for description. - /// Package validator maximum length requirement for description. - /// Package validator minimum length guideline for description. - private IEnumerable NuspecDescriptionLengthValidation(XmlDocumentSyntax syntaxTree, TextPositions textPositions) - { - var descriptionElement = syntaxTree.DescendantNodes().OfType().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 - }; - } - } } } diff --git a/src/Chocolatey.Language.Server/IEnumerableExtensions.cs b/src/Chocolatey.Language.Server/IEnumerableExtensions.cs new file mode 100644 index 00000000..617d6028 --- /dev/null +++ b/src/Chocolatey.Language.Server/IEnumerableExtensions.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Chocolatey.Language.Server +{ + /// + /// Extensions for IEnumerable + /// + public static class EnumerableExtensions + { + /// + /// Safe for each, returns an empty Enumerable if the list to iterate is null. + /// + /// Generic type. + /// The source. + /// + /// Source if not null; otherwise Enumerable.Empty<> + /// + public static IEnumerable OrEmptyListIfNull(this IEnumerable source) + { + return source ?? Enumerable.Empty(); + } + } +} diff --git a/src/Chocolatey.Language.Server/INuSpecRule.cs b/src/Chocolatey.Language.Server/INuSpecRule.cs new file mode 100644 index 00000000..d190b6a0 --- /dev/null +++ b/src/Chocolatey.Language.Server/INuSpecRule.cs @@ -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 Validate(XmlDocumentSyntax syntaxTree, TextPositions textPositions); + } +} diff --git a/src/Chocolatey.Language.Server/ITypeLocator.cs b/src/Chocolatey.Language.Server/ITypeLocator.cs new file mode 100644 index 00000000..d7721749 --- /dev/null +++ b/src/Chocolatey.Language.Server/ITypeLocator.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Chocolatey.Language.Server +{ + public interface ITypeLocator + { + IEnumerable GetTypesThatInheritOrImplement(); + IEnumerable GetTypesThatInheritOrImplement(params Assembly[] Assemblies); + } +} diff --git a/src/Chocolatey.Language.Server/TypeLocator.cs b/src/Chocolatey.Language.Server/TypeLocator.cs new file mode 100644 index 00000000..38f81af4 --- /dev/null +++ b/src/Chocolatey.Language.Server/TypeLocator.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Chocolatey.Language.Server +{ + public class TypeLocator : ITypeLocator + { + public IEnumerable GetTypesThatInheritOrImplement() + { + return GetTypesThatInheritOrImplement(GetType().Assembly); + } + + public IEnumerable GetTypesThatInheritOrImplement(params Assembly[] assemblies) + { + var list = new List(); + + 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; + } + } +} diff --git a/src/Chocolatey.Language.Server/Validations/DescriptionLengthValidation.cs b/src/Chocolatey.Language.Server/Validations/DescriptionLengthValidation.cs new file mode 100644 index 00000000..ad489ba9 --- /dev/null +++ b/src/Chocolatey.Language.Server/Validations/DescriptionLengthValidation.cs @@ -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 +{ + /// + /// Handler to validate the length of description in the package metadata. + /// + /// Package validator requirement for description. + /// Package validator maximum length requirement for description. + /// Package validator minimum length guideline for description. + public class DescriptionLengthValidation : INuSpecRule + { + public IEnumerable Validate(XmlDocumentSyntax syntaxTree, TextPositions textPositions) + { + var descriptionElement = syntaxTree.DescendantNodes().OfType().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 + }; + } + } + } +} diff --git a/src/Chocolatey.Language.Server/Validations/DoesNotContainTemplatedVaules.cs b/src/Chocolatey.Language.Server/Validations/DoesNotContainTemplatedVaules.cs new file mode 100644 index 00000000..96a61ada --- /dev/null +++ b/src/Chocolatey.Language.Server/Validations/DoesNotContainTemplatedVaules.cs @@ -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 +{ + /// + /// Handler to validate that no templated values remain in the nuspec. + /// + /// Package validator requirement for templated values. + public class DoesNotContainTemplatedValues : INuSpecRule + { + private static readonly IReadOnlyCollection TemplatedValues = new [] + { + "__replace", + "space_separated", + "tag1" + }; + + public IEnumerable Validate(XmlDocumentSyntax syntaxTree, TextPositions textPositions) + { + foreach (var node in syntaxTree.DescendantNodesAndSelf().OfType()) + { + 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 + }; + } + } + } +}