From cb503bad3d51b6aa3fcad9b5dd389ba9d5ac462d Mon Sep 17 00:00:00 2001 From: Pedro Debevere Date: Fri, 10 Nov 2023 08:17:13 +0100 Subject: [PATCH] DCP224342 SLDisValidatorCommon extraction --- .gitattributes | 63 +++ .github/dependabot.yml | 15 + .../private-dataminer-cicd-nugetsolution.yml | 34 ++ .gitignore | 363 ++++++++++++++++++ Common/Common.csproj | 35 ++ Common/Comparers/IValidationResultComparer.cs | 58 +++ Common/Data/ProtocolInputData.cs | 97 +++++ Common/IReadOnlyListExtensions.cs | 38 ++ Common/Interfaces/ICSharpValidationResult.cs | 16 + Common/Interfaces/ICodeFixResult.cs | 20 + Common/Interfaces/ILineInfoProvider.cs | 31 ++ Common/Interfaces/IProtocolInputData.cs | 53 +++ .../IProtocolQActionHelperProvider.cs | 16 + .../IQActionCompilationModelProvider.cs | 22 ++ Common/Interfaces/ISeverityBubbleUpResult.cs | 13 + Common/Interfaces/ITestCategory.cs | 28 ++ Common/Interfaces/ITestDescription.cs | 34 ++ Common/Interfaces/IValidationResult.cs | 139 +++++++ Common/Interfaces/IValidator.cs | 44 +++ Common/LICENSE.txt | 18 + Common/Model/Category.cs | 89 +++++ Common/Model/Certainty.cs | 21 + Common/Model/CombinedState.cs | 33 ++ Common/Model/FixImpact.cs | 21 + Common/Model/GenericStatus.cs | 37 ++ Common/Model/ResultState.cs | 21 + Common/Model/Severity.cs | 37 ++ Common/Model/Source.cs | 21 + Common/Model/SuppressionState.cs | 21 + Common/Model/ValidatorSettings.cs | 14 + Common/Suppressions/CommentSuppression.cs | 179 +++++++++ .../Suppressions/CommentSuppressionManager.cs | 151 ++++++++ Common/Suppressions/ISuppression.cs | 34 ++ .../ISuppressionTokenWithReason.cs | 14 + .../NormalValidatorSuppressionToken.cs | 108 ++++++ .../PostponeValidatorSuppressionToken.cs | 120 ++++++ Common/Suppressions/SuppressionExtensions.cs | 16 + Common/Suppressions/SuppressionToken.cs | 198 ++++++++++ Common/Suppressions/SuppressionType.cs | 17 + .../VersionHistoryMajorChangeSuppression.cs | 55 +++ .../Suppressions/VersionHistorySuppression.cs | 95 +++++ .../VersionHistorySuppressionManager.cs | 156 ++++++++ .../Tools/QActionCompilationModelProvider.cs | 61 +++ Common/Tools/SimpleLineInfoProvider.cs | 154 ++++++++ Common/icon.png | Bin 0 -> 47711 bytes Skyline.DataMiner.CICD.Validators.sln | 25 ++ _NuGetItems/LICENSE.txt | 18 + _NuGetItems/icon.png | Bin 0 -> 47711 bytes 48 files changed, 2853 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/private-dataminer-cicd-nugetsolution.yml create mode 100644 .gitignore create mode 100644 Common/Common.csproj create mode 100644 Common/Comparers/IValidationResultComparer.cs create mode 100644 Common/Data/ProtocolInputData.cs create mode 100644 Common/IReadOnlyListExtensions.cs create mode 100644 Common/Interfaces/ICSharpValidationResult.cs create mode 100644 Common/Interfaces/ICodeFixResult.cs create mode 100644 Common/Interfaces/ILineInfoProvider.cs create mode 100644 Common/Interfaces/IProtocolInputData.cs create mode 100644 Common/Interfaces/IProtocolQActionHelperProvider.cs create mode 100644 Common/Interfaces/IQActionCompilationModelProvider.cs create mode 100644 Common/Interfaces/ISeverityBubbleUpResult.cs create mode 100644 Common/Interfaces/ITestCategory.cs create mode 100644 Common/Interfaces/ITestDescription.cs create mode 100644 Common/Interfaces/IValidationResult.cs create mode 100644 Common/Interfaces/IValidator.cs create mode 100644 Common/LICENSE.txt create mode 100644 Common/Model/Category.cs create mode 100644 Common/Model/Certainty.cs create mode 100644 Common/Model/CombinedState.cs create mode 100644 Common/Model/FixImpact.cs create mode 100644 Common/Model/GenericStatus.cs create mode 100644 Common/Model/ResultState.cs create mode 100644 Common/Model/Severity.cs create mode 100644 Common/Model/Source.cs create mode 100644 Common/Model/SuppressionState.cs create mode 100644 Common/Model/ValidatorSettings.cs create mode 100644 Common/Suppressions/CommentSuppression.cs create mode 100644 Common/Suppressions/CommentSuppressionManager.cs create mode 100644 Common/Suppressions/ISuppression.cs create mode 100644 Common/Suppressions/ISuppressionTokenWithReason.cs create mode 100644 Common/Suppressions/NormalValidatorSuppressionToken.cs create mode 100644 Common/Suppressions/PostponeValidatorSuppressionToken.cs create mode 100644 Common/Suppressions/SuppressionExtensions.cs create mode 100644 Common/Suppressions/SuppressionToken.cs create mode 100644 Common/Suppressions/SuppressionType.cs create mode 100644 Common/Suppressions/VersionHistoryMajorChangeSuppression.cs create mode 100644 Common/Suppressions/VersionHistorySuppression.cs create mode 100644 Common/Suppressions/VersionHistorySuppressionManager.cs create mode 100644 Common/Tools/QActionCompilationModelProvider.cs create mode 100644 Common/Tools/SimpleLineInfoProvider.cs create mode 100644 Common/icon.png create mode 100644 Skyline.DataMiner.CICD.Validators.sln create mode 100644 _NuGetItems/LICENSE.txt create mode 100644 _NuGetItems/icon.png diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..1ff0c423 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.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 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..172766da --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "nuget" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + groups: + mstest: + patterns: + - "MSTest.*" diff --git a/.github/workflows/private-dataminer-cicd-nugetsolution.yml b/.github/workflows/private-dataminer-cicd-nugetsolution.yml new file mode 100644 index 00000000..286e8db2 --- /dev/null +++ b/.github/workflows/private-dataminer-cicd-nugetsolution.yml @@ -0,0 +1,34 @@ +name: DataMiner CICD NuGet Solution + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events. + push: + branches: [] + tags: + - "[0-9]+.[0-9]+.[0-9]+.[0-9]+" + - "[0-9]+.[0-9]+.[0-9]+" + - "[0-9]+.[0-9]+.[0-9]+.[0-9]+-**" + - "[0-9]+.[0-9]+.[0-9]+-**" + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + +# Note: Tagging will push the nupackage to nuget.org using the provided NUGETAPIKEY. You can tag both a prerelease version (a.b.c-xxxx) or a stable release (a.b.c). + CICD: + uses: SkylineCommunications/_ReusableWorkflows/.github/workflows/NuGet Solution Master Workflow.yml@main + with: + referenceName: ${{ github.ref_name }} + runNumber: ${{ github.run_number }} + referenceType: ${{ github.ref_type }} + repository: ${{ github.repository }} + owner: ${{ github.repository_owner }} + sonarCloudProjectName: SkylineCommunications_Skyline.DataMiner.CICD.Validators + secrets: + sonarCloudToken: ${{ secrets.SONAR_TOKEN }} + pfx: ${{ secrets.PFX }} + pfxPassword: ${{ secrets.PFXPASSWORD }} + nugetApiKey: ${{ secrets.NUGETAPIKEY }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9491a2fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/Common/Common.csproj b/Common/Common.csproj new file mode 100644 index 00000000..bf3dd71c --- /dev/null +++ b/Common/Common.csproj @@ -0,0 +1,35 @@ + + + netstandard2.0 + Skyline.DataMiner.CICD.Validators.Common + Skyline.DataMiner.CICD.Validators.Common + True + True + SkylineCommunications + Skyline Communications + LICENSE.txt + True + icon.png + https://skyline.be/ + Skyline;DataMiner;CICD + Library containing common validator-related logic. + https://github.com/SkylineCommunications/Skyline.DataMiner.CICD.Validators.git + git + + + + + + + + + True + \ + + + True + \ + + + + diff --git a/Common/Comparers/IValidationResultComparer.cs b/Common/Comparers/IValidationResultComparer.cs new file mode 100644 index 00000000..47e4e93a --- /dev/null +++ b/Common/Comparers/IValidationResultComparer.cs @@ -0,0 +1,58 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Comparers +{ + using System; + using System.Collections.Generic; + + using Skyline.DataMiner.CICD.Models.Protocol.Read; + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + + /// + /// Validation result comparer. + /// + public class IValidationResultComparer : IEqualityComparer + { + + public static IValidationResultComparer Instance { get; } = new IValidationResultComparer(); + + public bool Equals(IValidationResult x, IValidationResult y) + { + if (x == null && y == null) + { + return true; + } + if (x == null || y == null) + { + return false; + } + + return x.Category == y.Category && + x.Severity == y.Severity && + String.Equals(x.FullId ?? Convert.ToString(x.ErrorId), y.FullId ?? Convert.ToString(y.ErrorId)) && + String.Equals(x.Description, y.Description) && + String.Equals(x.PositionNode.GetIdentifier(), y.PositionNode.GetIdentifier()) && + String.Equals(x.ReferenceNode.GetIdentifier(), y.ReferenceNode.GetIdentifier()) && + String.Equals(x.DveExport?.TablePid, y.DveExport?.TablePid); + } + + public int GetHashCode(IValidationResult obj) + { + int hash = 17; + + unchecked + { + if (obj != null) + { + hash = hash * 23 + obj.Category.GetHashCode(); + hash = hash * 23 + obj.Severity.GetHashCode(); + hash = hash * 23 + (obj.FullId ?? Convert.ToString(obj.ErrorId)).GetHashCode(); + hash = hash * 23 + obj.Description.GetHashCode(); + hash = hash * 23 + (obj.PositionNode != null ? obj.PositionNode.GetIdentifier().GetHashCode() : 0); + hash = hash * 23 + (obj.ReferenceNode != null ? obj.ReferenceNode.GetIdentifier().GetHashCode() : 0); + hash = hash * 23 + (obj.DveExport?.TablePid != null ? obj.DveExport.Value.GetHashCode() : 0); + } + } + + return hash; + } + } +} diff --git a/Common/Data/ProtocolInputData.cs b/Common/Data/ProtocolInputData.cs new file mode 100644 index 00000000..333e1e73 --- /dev/null +++ b/Common/Data/ProtocolInputData.cs @@ -0,0 +1,97 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Data +{ + using System; + + using Interfaces; + + using Skyline.DataMiner.CICD.Models.Protocol; + using Skyline.DataMiner.CICD.Models.Protocol.Read; + using Skyline.DataMiner.CICD.Models.Protocol.Read.Interfaces; + using Skyline.DataMiner.CICD.Parsers.Common.Xml; + using Skyline.DataMiner.CICD.Validators.Common.Tools; + + /// + /// Protocol input data. + /// + public class ProtocolInputData : IProtocolInputData + { + private readonly Lazy qactionCompilationModel; + + /// + /// Initializes a new instance of the class. + /// + /// The protocol model. + /// The document. + /// The protocol code. + /// The line info provider. + /// , , or are . + public ProtocolInputData(IProtocolModel model, XmlDocument document, string protocolCode, ILineInfoProvider lineInfoProvider) + { + Model = model ?? throw new ArgumentNullException(nameof(model)); + Document = document ?? throw new ArgumentNullException(nameof(document)); + Code = protocolCode ?? throw new ArgumentNullException(nameof(protocolCode)); + LineInfoProvider = lineInfoProvider ?? throw new ArgumentNullException(nameof(lineInfoProvider)); + } + + /// + /// Initializes a new instance of the class. + /// + /// The protocol model. + /// The document. + /// The protocol code. + /// The line info provider. + /// The QAction compilation model provider. + /// , , or are . + public ProtocolInputData(IProtocolModel model, XmlDocument document, string protocolCode, ILineInfoProvider lineInfo, IQActionCompilationModelProvider qactionCompilationModelProvider) + : this(model, document, protocolCode, lineInfo) + { + QActionCompilationModelProvider = qactionCompilationModelProvider; + qactionCompilationModel = new Lazy(() => CreateQActionCompilationModel(qactionCompilationModelProvider, model, document, protocolCode)); + } + + /// + /// Initializes a new instance of the class. + /// + /// The protocol code. + /// The QAction compilation model provider. + /// is . + public ProtocolInputData(string protocolCode, IQActionCompilationModelProvider qactionCompilationModelProvider) + { + Code = protocolCode ?? throw new ArgumentNullException(nameof(protocolCode)); + + var parser = new Parser(protocolCode); + Document = parser.Document; + Model = new ProtocolModel(Document); + LineInfoProvider = new SimpleLineInfoProvider(protocolCode); + + QActionCompilationModelProvider = qactionCompilationModelProvider; + qactionCompilationModel = new Lazy(() => CreateQActionCompilationModel(qactionCompilationModelProvider, Model, Document, protocolCode)); + } + + /// + public XmlDocument Document { get; } + + /// + public IProtocolModel Model { get; } + + /// + public string Code { get; } + + /// + public ILineInfoProvider LineInfoProvider { get; } + + /// + public IQActionCompilationModelProvider QActionCompilationModelProvider { get; } + + /// + public IProtocolModel MainProtocolModel => Model?.MainProtocolModel; + + /// + public bool IsExportedProtocol => MainProtocolModel != null; + + private static QActionCompilationModel CreateQActionCompilationModel(IQActionCompilationModelProvider provider, IProtocolModel model, XmlDocument document, string code) + { + return provider?.GetQActionCompilationModel(document, model, code); + } + } +} diff --git a/Common/IReadOnlyListExtensions.cs b/Common/IReadOnlyListExtensions.cs new file mode 100644 index 00000000..4b5e2646 --- /dev/null +++ b/Common/IReadOnlyListExtensions.cs @@ -0,0 +1,38 @@ +namespace Skyline.DataMiner.CICD.Validators.Common +{ + using System.Collections; + using System.Collections.Generic; + + /// + /// Defines extension methods for the interface. + /// + internal static class IReadOnlyListExtensions + { + public static int IndexOf(this IReadOnlyList source, T value) + { + if (source is IList list) + { + return list.IndexOf(value); + } + else if (source is IList listT) + { + return listT.IndexOf(value); + } + else + { + int i = 0; + foreach (T element in source) + { + if (Equals(element, value)) + { + return i; + } + + i++; + } + + return -1; + } + } + } +} diff --git a/Common/Interfaces/ICSharpValidationResult.cs b/Common/Interfaces/ICSharpValidationResult.cs new file mode 100644 index 00000000..adea0425 --- /dev/null +++ b/Common/Interfaces/ICSharpValidationResult.cs @@ -0,0 +1,16 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Interfaces +{ + using Microsoft.CodeAnalysis; + + /// + /// Represents a validator result related to C# code. + /// + public interface ICSharpValidationResult : IValidationResult + { + /// + /// Gets the location in source code. + /// + /// The location in source code. + Location CSharpLocation { get; } + } +} \ No newline at end of file diff --git a/Common/Interfaces/ICodeFixResult.cs b/Common/Interfaces/ICodeFixResult.cs new file mode 100644 index 00000000..50e70519 --- /dev/null +++ b/Common/Interfaces/ICodeFixResult.cs @@ -0,0 +1,20 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Interfaces +{ + /// + /// Represents the result of performing a code fix. + /// + public interface ICodeFixResult + { + /// + /// Gets a value indicating whether the application of the code fix succeeded. + /// + /// true if applying the code fix succeeded; otherwise, false. + bool Success { get; } + + /// + /// Gets the message related to application of the code fix. + /// + /// The message related to application of the code fix. + string Message { get; } + } +} diff --git a/Common/Interfaces/ILineInfoProvider.cs b/Common/Interfaces/ILineInfoProvider.cs new file mode 100644 index 00000000..e4e51706 --- /dev/null +++ b/Common/Interfaces/ILineInfoProvider.cs @@ -0,0 +1,31 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Interfaces +{ + /// + /// Represents a line info provider. + /// + public interface ILineInfoProvider + { + /// + /// Retrieves the line and column from the specified offset. + /// + /// The offset. + /// The line number. + /// The column index. + void GetLineAndColumn(int offset, out int lineNumber, out int columnIndex); + + /// + /// Retrieves the offset from the specified line number and column index. + /// + /// The line number. + /// The column index. + /// The offset. + int GetOffset(int lineNumber, int columnIndex); + + /// + /// Retrieves the first non white space offset starting from the specified line number. + /// + /// The line number. + /// The first non white space offset starting from the specified line number. + int? GetFirstNonWhitespaceOffset(int lineNumber); + } +} diff --git a/Common/Interfaces/IProtocolInputData.cs b/Common/Interfaces/IProtocolInputData.cs new file mode 100644 index 00000000..7966caf7 --- /dev/null +++ b/Common/Interfaces/IProtocolInputData.cs @@ -0,0 +1,53 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Interfaces +{ + using Skyline.DataMiner.CICD.Models.Protocol.Read.Interfaces; + using Skyline.DataMiner.CICD.Parsers.Common.Xml; + + /// + /// Protocol input data interface. + /// + public interface IProtocolInputData + { + /// + /// Gets the XML document. + /// + /// The XML document. + XmlDocument Document { get; } + + /// + /// Gets the protocol model. + /// + /// The protocol model. + IProtocolModel Model { get; } + + /// + /// Gets the protocol code. + /// + /// The protocol code. + string Code { get; } + + /// + /// Gets the line info provider. + /// + /// The line info provider. + ILineInfoProvider LineInfoProvider { get; } + + /// + /// Gets the QAction compilation model provider. + /// + /// The QAction compilation model provider. + IQActionCompilationModelProvider QActionCompilationModelProvider { get; } + + /// + /// If this is the model of an exported protocol, this property contains the model of the main protocol that was used to create the export. + /// + /// The model of the main protocol + IProtocolModel MainProtocolModel { get; } + + /// + /// Gets a value indicating whether this is the model of an exported protocol. + /// + /// true if this is the model of an exported protocol; otherwise, false. + bool IsExportedProtocol { get; } + } +} diff --git a/Common/Interfaces/IProtocolQActionHelperProvider.cs b/Common/Interfaces/IProtocolQActionHelperProvider.cs new file mode 100644 index 00000000..caf6226a --- /dev/null +++ b/Common/Interfaces/IProtocolQActionHelperProvider.cs @@ -0,0 +1,16 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Interfaces +{ + /// + /// Protocol QAction helper code provider interface; + /// + public interface IProtocolQActionHelperProvider + { + /// + /// Retrieves the protocol QAction helper code provider. + /// + /// The protocol XML code. + /// Value indicating whether errors during helper code generation should be ignored. + /// The protocol QAction helper code. + string GetProtocolQActionHelper(string protocolCode, bool ignoreErrors = false); + } +} diff --git a/Common/Interfaces/IQActionCompilationModelProvider.cs b/Common/Interfaces/IQActionCompilationModelProvider.cs new file mode 100644 index 00000000..e78ad5fa --- /dev/null +++ b/Common/Interfaces/IQActionCompilationModelProvider.cs @@ -0,0 +1,22 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Interfaces +{ + using Skyline.DataMiner.CICD.Models.Protocol; + using Skyline.DataMiner.CICD.Models.Protocol.Read.Interfaces; + using Skyline.DataMiner.CICD.Parsers.Common.Xml; + + /// + /// Represents a the QAction compilation model provider for a connector. + /// + public interface IQActionCompilationModelProvider + { + /// + /// Retrieves the QAction compilation model. + /// + /// The connector XML document. + /// The protocol model. + /// The code. + /// The QAction compilation model. + /// or is . + QActionCompilationModel GetQActionCompilationModel(XmlDocument document, IProtocolModel model, string code); + } +} diff --git a/Common/Interfaces/ISeverityBubbleUpResult.cs b/Common/Interfaces/ISeverityBubbleUpResult.cs new file mode 100644 index 00000000..2c6de19d --- /dev/null +++ b/Common/Interfaces/ISeverityBubbleUpResult.cs @@ -0,0 +1,13 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Interfaces +{ + /// + /// Severity bubble up result. + /// + public interface ISeverityBubbleUpResult + { + /// + /// Performs the severity bubble up. + /// + void DoSeverityBubbleUp(); + } +} \ No newline at end of file diff --git a/Common/Interfaces/ITestCategory.cs b/Common/Interfaces/ITestCategory.cs new file mode 100644 index 00000000..0d673bd3 --- /dev/null +++ b/Common/Interfaces/ITestCategory.cs @@ -0,0 +1,28 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Interfaces +{ + using System.Collections.Generic; + + /// + /// Represents a test category. + /// + public interface ITestCategory + { + /// + /// Gets the ID of the test category. + /// + /// The ID of the test category. + int Id { get; } + + /// + /// Gets the name of the test category. + /// + /// The name of the test category. + string Name { get; } + + /// + /// Gets the tests that belong to this test category. + /// + /// The tests that belong to this test category. + IList Tests { get; } + } +} \ No newline at end of file diff --git a/Common/Interfaces/ITestDescription.cs b/Common/Interfaces/ITestDescription.cs new file mode 100644 index 00000000..b913e313 --- /dev/null +++ b/Common/Interfaces/ITestDescription.cs @@ -0,0 +1,34 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Interfaces +{ + using Skyline.DataMiner.CICD.Validators.Common.Model; + + /// + /// Represents a test description. + /// + public interface ITestDescription + { + /// + /// Gets the test ID. + /// + /// The test ID. + uint Id { get; } + + /// + /// Gets the test name. + /// + /// The test name. + string Name { get; } + + /// + /// Gets the test category. + /// + /// The test category. + ITestCategory Category { get; } + + /// + /// Gets the result severity. + /// + /// The result severity. + Severity ResultSeverity { get; } + } +} diff --git a/Common/Interfaces/IValidationResult.cs b/Common/Interfaces/IValidationResult.cs new file mode 100644 index 00000000..3fbc6e19 --- /dev/null +++ b/Common/Interfaces/IValidationResult.cs @@ -0,0 +1,139 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Interfaces +{ + using System.Collections.Generic; + + using Skyline.DataMiner.CICD.Models.Protocol.Read; + using Skyline.DataMiner.CICD.Validators.Common.Model; + + /// + /// Represents a validator result. + /// + public interface IValidationResult + { + /// + /// Gets the sub results. + /// + /// The sub results. + List SubResults { get; } + + /// + /// Gets the check ID. + /// + /// The check ID. + uint CheckId { get; } + + /// + /// Gets the error ID. + /// + /// The error ID. + uint ErrorId { get; } + + /// + /// Gets the full ID. + /// + /// The full ID. + string FullId { get; } + + /// + /// Gets the category. + /// + /// The category. + Category Category { get; } + + /// + /// Gets the severity. + /// + /// The severity. + Severity Severity { get; } + + /// + /// Gets the certainty. + /// + /// The certainty. + Certainty Certainty { get; } + + /// + /// Gets the source. + /// + /// The source. + Source Source { get; } + + /// + /// Gets the fix impact. + /// + /// The fix impact. + FixImpact FixImpact { get; } + + /// + /// Gets the group description. + /// + /// The group description. + string GroupDescription { get; } + + /// + /// Gets the description. + /// + /// The description. + string Description { get; } + + /// + /// Gets information about how to fix the issue. + /// + /// Information about how to fix the issue. + string HowToFix { get; } + + /// + /// Gets example code. + /// + /// Example code. + string ExampleCode { get; } + + /// + /// Gets additional details. + /// + /// The additional details. + string Details { get; } + + /// + /// Gets the reference node. + /// + /// The reference node. + IReadable ReferenceNode { get; } + + /// + /// Gets a value indicating whether a code fix is available. + /// + /// true if a code fix is available; otherwise, false. + bool HasCodeFix { get; } + + /// + /// Gets the position. + /// + /// The position. + int Position { get; } + + /// + /// Gets the position of the node. + /// + /// The position of the node. + IReadable PositionNode { get; } + + /// + /// Gets the auto fix warnings. + /// + /// The auto fix warnings. + List<(string Message, bool AutoFixPopup)> AutoFixWarnings { get; } + + /// + /// Gets the DVE export info. + /// + /// The DVE export info. + (int TablePid, string Name)? DveExport { get; } + + + // Old stuff that is still needed... + int Line { get; } + string DescriptionFormat { get; } + object[] DescriptionParameters { get; } + } +} diff --git a/Common/Interfaces/IValidator.cs b/Common/Interfaces/IValidator.cs new file mode 100644 index 00000000..61997535 --- /dev/null +++ b/Common/Interfaces/IValidator.cs @@ -0,0 +1,44 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Interfaces +{ + using System.Collections.Generic; + using System.Threading; + + using Skyline.DataMiner.CICD.Parsers.Common.XmlEdit; + using Skyline.DataMiner.CICD.Validators.Common.Model; + + /// + /// Validator interface. + /// + public interface IValidator + { + /// + /// Runs the validator on the specified input. + /// + /// The input data. + /// Cancellation token. + /// The validator settings. + /// Indication of which tests should be executed. By default, all tests are executed. + /// The validator results. + IList RunValidate(IProtocolInputData input, CancellationToken cancellationToken, ValidatorSettings validatorSettings, ICollection<(Category category, uint checkId)> testsToExecute = null); + + /// + /// Performs a comparison between the two provided connector versions and returns detected major changes, if any. + /// + /// The data representing the new version. + /// The data representing the previous version. + /// Cancellation token. + /// The validator settings. + /// Detected major changes, if any. + IList RunCompare(IProtocolInputData newData, IProtocolInputData oldData, CancellationToken cancellationToken, ValidatorSettings validatorSettings); + + /// + /// Performs a code fix. + /// + /// The XML document. + /// The protocol model. + /// The validation result. + /// The validator settings. + /// The code fix result. + ICodeFixResult ExecuteCodeFix(XmlDocument document, Models.Protocol.Edit.Protocol protocol, IValidationResult result, ValidatorSettings validatorSettings); + } +} \ No newline at end of file diff --git a/Common/LICENSE.txt b/Common/LICENSE.txt new file mode 100644 index 00000000..92a62e8c --- /dev/null +++ b/Common/LICENSE.txt @@ -0,0 +1,18 @@ +SKYLINE LIBRARY LICENSE + +1. Applicability +The software in this repository (hereafter the “Software”) is owned by Skyline Communications (hereafter the “Skyline”). The terms of this license govern your use of the Software. If you do not agree with the terms of this license, you may not use or exploit the Software in any other manner. +2. Grant of rights +You may use the Software for the development, testing and validation of DataMiner packages only. +You may not use this Software in any other manner unless you have obtained Skyline’s prior written authorization to do so. +It is forbidden to create derivative works of the Software. +3. Intellectual property +Skyline owns the intellectual property rights vested in the Software. Skyline granting you access to the Software does not entail permission to utilize or otherwise manipulate the Software in contravention to this Library License. Skyline reserves the right to pursue legal action against you in case of breach of its intellectual property rights. +4. No warranty +Skyline provides the Software ‘as is’, without any warranty of any kind. +5. Limitation of liability +Within the maximum possible extent under the applicable laws, Skyline disclaims all liability for the Software. +6. Applicable laws and jurisdiction +This license shall be governed by the laws of Belgium. Any dispute shall be submitted to the exclusive jurisdiction of the competent courts of Gent, division Kortrijk, Belgium + + diff --git a/Common/Model/Category.cs b/Common/Model/Category.cs new file mode 100644 index 00000000..88d9af2c --- /dev/null +++ b/Common/Model/Category.cs @@ -0,0 +1,89 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Model +{ + /// + /// Represents the validator category. + /// + public enum Category + { + /// + /// Undefined. + /// + Undefined = 0, + /// + /// Protocol. + /// + Protocol = 1, + /// + /// Param. + /// + Param = 2, + /// + /// QAction. + /// + QAction = 3, + /// + /// Group. + /// + Group = 4, + /// + /// Trigger. + /// + Trigger = 5, + /// + /// Action. + /// + Action = 6, + /// + /// Timer. + /// + Timer = 7, + /// + /// HTTP. + /// + HTTP = 8, + /// + /// Pair. + /// + Pair = 9, + /// + /// Command. + /// + Command = 10, + /// + /// Response. + /// + Response = 11, + /// + /// Ports. + /// + Ports = 12, + /// + /// Relation. + /// + Relation = 13, + /// + /// Topology. + /// + Topology = 14, + /// + /// Chain. + /// + Chain = 15, + /// + /// ParameterGroup. + /// + ParameterGroup = 16, + /// + /// ExportRule. + /// + ExportRule = 17, + /// + /// TreeControl. + /// + TreeControl = 18, + /// + /// PortSettings. + /// + PortSettings = 19, + } +} diff --git a/Common/Model/Certainty.cs b/Common/Model/Certainty.cs new file mode 100644 index 00000000..2f791622 --- /dev/null +++ b/Common/Model/Certainty.cs @@ -0,0 +1,21 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Model +{ + /// + /// Represents the certainty of a detected issue. + /// + public enum Certainty + { + /// + /// Undefined. + /// + Undefined = -1, + /// + /// Certain. + /// + Certain = 100, + /// + /// Uncertain. + /// + Uncertain = 50, + } +} \ No newline at end of file diff --git a/Common/Model/CombinedState.cs b/Common/Model/CombinedState.cs new file mode 100644 index 00000000..db902266 --- /dev/null +++ b/Common/Model/CombinedState.cs @@ -0,0 +1,33 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Model +{ + /// + /// Represents the combined state. + /// + public enum CombinedState + { + /// + /// Undefined. + /// + Undefined, + /// + /// Active. + /// + Active, + /// + /// New. + /// + New, + /// + /// Solved. + /// + Solved, + /// + /// Suppressed. + /// + Suppressed, + /// + /// Postponed. + /// + Postponed, + } +} diff --git a/Common/Model/FixImpact.cs b/Common/Model/FixImpact.cs new file mode 100644 index 00000000..948d0e75 --- /dev/null +++ b/Common/Model/FixImpact.cs @@ -0,0 +1,21 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Model +{ + /// + /// Represents the impact of a fix. + /// + public enum FixImpact + { + /// + /// Undefined. + /// + Undefined, + /// + /// Non breaking. + /// + NonBreaking, + /// + /// Breaking. + /// + Breaking, + } +} \ No newline at end of file diff --git a/Common/Model/GenericStatus.cs b/Common/Model/GenericStatus.cs new file mode 100644 index 00000000..005c6157 --- /dev/null +++ b/Common/Model/GenericStatus.cs @@ -0,0 +1,37 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Model +{ + using System; + + /// + /// Represents the generic status. + /// + [Flags] + public enum GenericStatus + { + /// + /// None. + /// + None = 0, + /// + /// Missing. + /// + Missing = 1, + /// + /// Empty. + /// + Empty = 2, + /// + /// Untrimmed. + /// + Untrimmed = 4, + + /// + /// Invalid. + /// + /// When the ValueTag.Value is null this indicates the value could not be parsed to the model type (ex: Enum, Int32, etc.). + Invalid = 8, + + //Something = 16, + //Something = 32, + } +} \ No newline at end of file diff --git a/Common/Model/ResultState.cs b/Common/Model/ResultState.cs new file mode 100644 index 00000000..fe7adea0 --- /dev/null +++ b/Common/Model/ResultState.cs @@ -0,0 +1,21 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Model +{ + /// + /// Represents the result state. + /// + public enum ResultState + { + /// + /// Active. + /// + Active, + /// + /// New. + /// + New, + /// + /// Solved. + /// + Solved, + } +} diff --git a/Common/Model/Severity.cs b/Common/Model/Severity.cs new file mode 100644 index 00000000..85458621 --- /dev/null +++ b/Common/Model/Severity.cs @@ -0,0 +1,37 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Model +{ + /// + /// Represents a validator severity. + /// + public enum Severity + { + /// + /// Undefined. + /// + Undefined = -999, // Not to be used + /// + /// Information. + /// + Information = -1, + /// + /// Bubble up. + /// + BubbleUp = -100, // Default bubble up + /// + /// Warning. + /// + Warning = 0, + /// + /// Minor. + /// + Minor, + /// + /// Major. + /// + Major, + /// + /// Critical. + /// + Critical, + } +} \ No newline at end of file diff --git a/Common/Model/Source.cs b/Common/Model/Source.cs new file mode 100644 index 00000000..dc699905 --- /dev/null +++ b/Common/Model/Source.cs @@ -0,0 +1,21 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Model +{ + /// + /// Represents the source. + /// + public enum Source + { + /// + /// Undefined. + /// + Undefined = -1, + /// + /// Validator. + /// + Validator, + /// + /// Major change checker. + /// + MajorChangeChecker, + } +} \ No newline at end of file diff --git a/Common/Model/SuppressionState.cs b/Common/Model/SuppressionState.cs new file mode 100644 index 00000000..2e40c4ef --- /dev/null +++ b/Common/Model/SuppressionState.cs @@ -0,0 +1,21 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Model +{ + /// + /// Represents the suppression state. + /// + public enum SuppressionState + { + /// + /// Not suppressed. + /// + NotSuppressed, + /// + /// Suppressed. + /// + Suppressed, + /// + /// Postponed. + /// + Postponed, + } +} diff --git a/Common/Model/ValidatorSettings.cs b/Common/Model/ValidatorSettings.cs new file mode 100644 index 00000000..cfa87425 --- /dev/null +++ b/Common/Model/ValidatorSettings.cs @@ -0,0 +1,14 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Model +{ + /// + /// Represents the validator settings. + /// + public class ValidatorSettings + { + /// + /// Gets or sets the expected provider. + /// + /// The expected provider. + public string ExpectedProvider { get; set; } + } +} diff --git a/Common/Suppressions/CommentSuppression.cs b/Common/Suppressions/CommentSuppression.cs new file mode 100644 index 00000000..044321b2 --- /dev/null +++ b/Common/Suppressions/CommentSuppression.cs @@ -0,0 +1,179 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Suppressions +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Skyline.DataMiner.CICD.Parsers.Common.Xml; + + /// + /// Represents a suppression that results in a suppression comment. + /// + public class CommentSuppression + { + /// + /// Initializes a new instance of the class. + /// + /// The start token. + /// The end token. + /// or is . + public CommentSuppression(SuppressionToken startToken, SuppressionToken endToken) + { + StartToken = startToken ?? throw new ArgumentNullException(nameof(startToken)); + EndToken = endToken ?? throw new ArgumentNullException(nameof(endToken)); + + Start = StartToken.Position; + End = EndToken.Position; + } + + /// + /// Initializes a new instance of the class. + /// + /// The start token. + /// The end. + /// is . + public CommentSuppression(SuppressionToken startToken, int end) + { + StartToken = startToken ?? throw new ArgumentNullException(nameof(startToken)); + + Start = StartToken.Position; + End = end; + } + + /// + /// Gets the XML document. + /// + /// The XML document. + public XmlDocument Document => StartToken.Document; + + /// + /// Gets the start token. + /// + /// The start token. + public SuppressionToken StartToken { get; } + + /// + /// Gets the end token. + /// + /// The end token. + public SuppressionToken EndToken { get; } + + /// + /// Gets the start. + /// + /// The start. + public int Start { get; } + + /// + /// Gets the end. + /// + /// The end. + public int End { get; } + + /// + /// Gets the suppression type. + /// + /// The suppression type. + public SuppressionType Type => StartToken.Type; + + /// + /// Gets the code. + /// + /// The code. + public string Code => StartToken.Code; + + /// + /// Retrieves all comment-based suppressions from the specified XML document. + /// + /// The XML document. + /// Indicates whether only valid suppressions should be retrieved. + /// All comment-based suppressions from the specified XML document. + /// is . + public static IEnumerable GetAllSuppressions(XmlDocument document, bool onlyValid = true) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + var suppressions = GetAllSuppressionsInternal(document); + + if (onlyValid) + { + suppressions = suppressions.Where(s => s.StartToken.IsValid); + } + + return suppressions; + } + + private static IEnumerable GetAllSuppressionsInternal(XmlDocument document) + { + var openTokens = new Dictionary<(SuppressionType, string), Stack>(); + + var tokens = SuppressionToken.GetAllTokens(document); + foreach (var token in tokens.OrderBy(t => t.Position)) + { + if (!openTokens.TryGetValue((token.Type, token.Code), out var stack)) + { + stack = new Stack(); + openTokens.Add((token.Type, token.Code), stack); + } + + if (token.IsOpen) + { + // register open token + stack.Push(token); + } + else if (stack.Count > 0) + { + var openToken = stack.Pop(); + yield return new CommentSuppression(openToken, token); + } + else + { + // no open token found => ignore end token + } + } + + // handle unclosed suppression tokens + foreach (var stack in openTokens.Values) + { + foreach (var openToken in stack) + { + int endPos = TryGetSuppressionEndPosition(openToken) ?? document.LastCharOffset + 1; + yield return new CommentSuppression(openToken, endPos); + } + } + } + + private static int? TryGetSuppressionEndPosition(SuppressionToken token) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } + + var parent = token.CommentNode?.ParentNode; + if (parent == null) + { + return null; + } + + var index = parent.Children.IndexOf(token.CommentNode); + if (index < 0) + { + return null; + } + + var nextSiblingElement = parent.Children.Skip(index + 1).OfType().FirstOrDefault(); + if (nextSiblingElement != null) + { + return nextSiblingElement.LastCharOffset + 1; + } + else + { + return parent.LastCharOffset + 1; + } + } + } +} \ No newline at end of file diff --git a/Common/Suppressions/CommentSuppressionManager.cs b/Common/Suppressions/CommentSuppressionManager.cs new file mode 100644 index 00000000..c69bd052 --- /dev/null +++ b/Common/Suppressions/CommentSuppressionManager.cs @@ -0,0 +1,151 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Suppressions +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Skyline.DataMiner.CICD.Parsers.Common.Xml; + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + + /// + /// Suppression manager for comment-based suppressions. + /// + public class CommentSuppressionManager + { + private readonly XmlDocument _document; + private readonly ILineInfoProvider _lineInfoProvider; + + private readonly List _suppressions; + private readonly IDictionary> _dictSuppressions; + + /// + /// Initializes a new instance of the class. + /// + /// The XML document. + /// The line info provider. + /// or is . + public CommentSuppressionManager(XmlDocument document, ILineInfoProvider lineInfoProvider) + { + _document = document ?? throw new ArgumentNullException(nameof(document)); + _lineInfoProvider = lineInfoProvider ?? throw new ArgumentNullException(nameof(lineInfoProvider)); + + _suppressions = CommentSuppression.GetAllSuppressions(document).ToList(); + _dictSuppressions = _suppressions.GroupBy(s => s.Code).ToDictionary(x => x.Key, x => x.ToList()); + } + + /// + /// Gets the comment-based suppressions. + /// + public IReadOnlyCollection Suppressions => _suppressions; + + /// + /// Determines whether the specified validation result is suppressed. + /// + /// The validation result. + /// true if the specified validator result is suppressed; otherwise, false. + /// is . + public bool IsSuppressed(IValidationResult result) + { + if (result is null) + { + throw new ArgumentNullException(nameof(result)); + } + + return TryFindSuppression(result, out _); + } + + /// + /// Determines whether the all sub results of the specified validation result are suppressed. + /// + /// The validation result. + /// true if all the child results of the specified validator result are suppressed; otherwise, false. + /// is . + public bool AreAllChildrenSuppressed(IValidationResult result) + { + if(result == null) throw new ArgumentNullException(nameof(result)); + + if (result.SubResults == null || result.SubResults.Count == 0) + { + return false; + } + + foreach (var r in result.SubResults) + { + if (!IsSuppressed(r) && !AreAllChildrenSuppressed(r)) + { + return false; + } + } + + return true; + } + + /// + /// Adds the specified suppression. + /// + /// The suppression to add. + /// is . + public void AddSuppression(CommentSuppression suppression) + { + if (suppression is null) + { + throw new ArgumentNullException(nameof(suppression)); + } + + _suppressions.Add(suppression); + + if (!_dictSuppressions.TryGetValue(suppression.Code, out var list)) + { + list = new List(); + _dictSuppressions.Add(suppression.Code, list); + } + + list.Add(suppression); + } + + /// + /// Retrieves the suppression for the specified validator result. + /// + /// The validator result. + /// When this method returns, contains the suppression that suppresses this validation result if a suppression was found; otherwise, . + /// true if the specified validation result has been suppressed; otherwise, false. + /// is . + public bool TryFindSuppression(IValidationResult result, out CommentSuppression suppression) + { + if (result is null) + { + throw new ArgumentNullException(nameof(result)); + } + + var resultCode = string.IsNullOrWhiteSpace(result.FullId) ? Convert.ToString(result.ErrorId) : result.FullId; + + if (_dictSuppressions.TryGetValue(resultCode, out var suppressionsForResultCode)) + { + int pos = GetPosition(result); + suppression = suppressionsForResultCode.FirstOrDefault(s => pos >= s.Start && pos <= s.End); + return suppression != null; + } + + suppression = default; + return false; + } + + private int GetPosition(IValidationResult result) + { + if (result.Position > 0 && result.Line <= 0) + { + return result.Position; + } + + if (result.Position <= 0 && result.Line > 0) + { + int pos = _lineInfoProvider.GetOffset(result.Line - 1, 0); + pos += _lineInfoProvider.GetFirstNonWhitespaceOffset(result.Line - 1) ?? 0; + + return pos; + } + + return -1; + } + } +} \ No newline at end of file diff --git a/Common/Suppressions/ISuppression.cs b/Common/Suppressions/ISuppression.cs new file mode 100644 index 00000000..dd6f958c --- /dev/null +++ b/Common/Suppressions/ISuppression.cs @@ -0,0 +1,34 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Suppressions +{ + using Skyline.DataMiner.CICD.Models.Protocol.Enums; + + /// + /// Represents a suppression. + /// + public interface ISuppression + { + /// + /// Gets the location of the suppression. + /// + /// The location of the suppression. + string Location { get; } + + /// + /// Gets the result ID. + /// + /// The result ID. + string ResultId { get; } + + /// + /// Gets the reason. + /// + /// The reason. + string Reason { get; } + + /// + /// Gets the suppression type. + /// + /// The suppression type. + EnumSuppressionType Type { get; } + } +} \ No newline at end of file diff --git a/Common/Suppressions/ISuppressionTokenWithReason.cs b/Common/Suppressions/ISuppressionTokenWithReason.cs new file mode 100644 index 00000000..918259af --- /dev/null +++ b/Common/Suppressions/ISuppressionTokenWithReason.cs @@ -0,0 +1,14 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Suppressions +{ + /// + /// Represents a suppression token with a specified reason. + /// + public interface ISuppressionTokenWithReason + { + /// + /// Gets the reason. + /// + /// The reason. + string Reason { get; } + } +} \ No newline at end of file diff --git a/Common/Suppressions/NormalValidatorSuppressionToken.cs b/Common/Suppressions/NormalValidatorSuppressionToken.cs new file mode 100644 index 00000000..88e029ca --- /dev/null +++ b/Common/Suppressions/NormalValidatorSuppressionToken.cs @@ -0,0 +1,108 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Suppressions +{ + using System; + using System.Linq; + using System.Text.RegularExpressions; + + using Skyline.DataMiner.CICD.Parsers.Common.Xml; + + /// + /// Represents a normal (i.e. non-postponed) validator suppression token. + /// + public class NormalValidatorSuppressionToken : SuppressionToken, ISuppressionTokenWithReason + { + private static readonly Regex _regExtractSuppressValidator = new Regex(@"^(?[\/\\]?)SuppressValidator\s+(?[0-9\.]+)(\s+(?.+))?$", RegexOptions.Compiled | RegexOptions.Singleline); + + /// + /// Initializes a new instance of the class. + /// + /// The XML document. + /// The comment. + /// Indicates whether the token is a closing token. + /// The suppressed code. + /// The reason of suppression. + /// is . + public NormalValidatorSuppressionToken(XmlDocument document, XmlComment comment, bool isClose, string code, string reason) + : base(document, comment, SuppressionType.Normal, code, isClose) + { + Reason = reason ?? throw new ArgumentNullException(nameof(reason)); + } + + /// + /// Initializes a new instance of the class. + /// + /// The XML document. + /// The comment. + /// Indicates whether the token is a closing token. + /// The suppressed code. + /// The reason of suppression. + /// is . + public NormalValidatorSuppressionToken(XmlDocument document, int position, bool isClose, string code, string reason) + : base(document, position, SuppressionType.Normal, code, isClose) + { + Reason = reason ?? throw new ArgumentNullException(nameof(reason)); + } + + /// + /// Gets the suppression reason. + /// + /// The suppression reason. + public string Reason { get; } + + /// + /// Gets a value indicating whether the suppression reason is valid. + /// + /// true if the suppression reason is valid; otherwise; false. + /// The suppression reason is considered invalid if it or white space. + public override bool IsValid => !string.IsNullOrWhiteSpace(Reason); + + /// + /// Parses the specified XML comment and converts it into a normal suppression token. + /// + /// The XML document. + /// The XML comment representing a suppression. + /// When this method returns, contains the suppression token that corresponds with this XML comment, if it could be parsed; otherwise, . + /// true if the suppression comment could be parsed; otherwise; false. + public static bool TryParse(XmlDocument document, XmlComment comment, out NormalValidatorSuppressionToken token) + { + string text = comment.InnerText?.Trim() ?? ""; + + if (TryMatchRegex(_regExtractSuppressValidator, text, out var m)) + { + string code = m.Groups["code"].Value; + string reason = m.Groups["reason"].Value; + bool isClose = (new[] { "\\", "/" }).Contains(m.Groups["close"].Value); + + token = new NormalValidatorSuppressionToken(document, comment, isClose, code, reason); + return true; + } + + token = null; + return false; + } + + /// + /// Generates an XML comment pair containing the specified result code and suppression reason. + /// + /// The result code. + /// The reason. + /// The start comment. + /// The end comment. + /// or is or white space. + public static void CreateXmlComments(string resultCode, string reason, out string startComment, out string endComment) + { + if (string.IsNullOrWhiteSpace(resultCode)) + { + throw new ArgumentException(nameof(resultCode) + " cannot be empty", nameof(resultCode)); + } + + if (string.IsNullOrWhiteSpace(reason)) + { + throw new ArgumentException(nameof(reason) + " cannot be empty", nameof(resultCode)); + } + + startComment = $""; + endComment = $""; + } + } +} \ No newline at end of file diff --git a/Common/Suppressions/PostponeValidatorSuppressionToken.cs b/Common/Suppressions/PostponeValidatorSuppressionToken.cs new file mode 100644 index 00000000..874f48c3 --- /dev/null +++ b/Common/Suppressions/PostponeValidatorSuppressionToken.cs @@ -0,0 +1,120 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Suppressions +{ + using System; + using System.Linq; + using System.Text.RegularExpressions; + + using Skyline.DataMiner.CICD.Parsers.Common.Xml; + + /// + /// Represents a postponed suppression. + /// + public class PostponeValidatorSuppressionToken : SuppressionToken, ISuppressionTokenWithReason + { + private static readonly Regex _regExtractPostponeValidator = new Regex(@"^(?[\/\\]?)PostponeValidator\s+(?[0-9\.]+)(\s+DCP(?\d+))?(\s+(?.+))?$", RegexOptions.Compiled | RegexOptions.Singleline); + + /// + /// Initializes a new instance of the class. + /// + /// The XML document. + /// The comment. + /// Indicates whether the token is a closing token. + /// The suppressed code. + /// The DCP task in which the issue will be fixed. + /// The suppression reason. + /// is . + public PostponeValidatorSuppressionToken(XmlDocument document, XmlComment comment, bool isClose, string code, int dcpTask, string reason) + : base(document, comment, SuppressionType.Postpone, code, isClose) + { + DcpTask = dcpTask; + Reason = reason ?? throw new ArgumentNullException(nameof(reason)); + } + + /// + /// Initializes a new instance of the class. + /// + /// The XML document. + /// The position. + /// Indicates whether the token is a closing token. + /// The suppressed code. + /// The DCP task in which the issue will be fixed. + /// The suppression reason. + /// is . + public PostponeValidatorSuppressionToken(XmlDocument document, int position, bool isClose, string code, int dcpTask, string reason) + : base(document, position, SuppressionType.Postpone, code, isClose) + { + DcpTask = dcpTask; + Reason = reason ?? throw new ArgumentNullException(nameof(reason)); + } + + /// + /// Gets the DCP task. + /// + /// The DCP task. + public int DcpTask { get; } + + /// + /// Gets the reason. + /// + /// The reason. + public string Reason { get; } + + /// + /// Gets a value indicating whether the suppression reason is valid. + /// + /// true if the suppression reason is valid; otherwise; false. + /// The suppression reason is considered invalid if it or white space or the DCP task is not provided. + public override bool IsValid => DcpTask > 0 && !string.IsNullOrWhiteSpace(Reason); + + /// + /// Parses the specified XML comment and converts it into a postponed suppression token. + /// + /// The XML document. + /// The XML comment representing a suppression. + /// When this method returns, contains the suppression token that corresponds with this XML comment, if it could be parsed; otherwise, . + /// true if the suppression comment could be parsed; otherwise; false. + public static bool TryParse(XmlDocument document, XmlComment comment, out PostponeValidatorSuppressionToken token) + { + string text = comment.InnerText?.Trim() ?? ""; + + if (TryMatchRegex(_regExtractPostponeValidator, text, out var m)) + { + string code = m.Groups["code"].Value; + string reason = m.Groups["reason"].Value; + int.TryParse(m.Groups["task"].Value, out int dcpTask); + bool isClose = (new[] { "\\", "/" }).Contains(m.Groups["close"].Value); + + token = new PostponeValidatorSuppressionToken(document, comment, isClose, code, dcpTask, reason); + return true; + } + + token = null; + return false; + } + + /// + /// Generates an XML comment pair containing the specified result code DCP task ID and suppression reason. + /// + /// The result code. + /// The DCP task ID. + /// The reason. + /// The start comment. + /// The end comment. + /// or is or white space. + public static void CreateXmlComments(string resultCode, int dcpTaskId, string reason, out string startComment, out string endComment) + { + if (string.IsNullOrWhiteSpace(resultCode)) + { + throw new ArgumentException(nameof(resultCode) + " cannot be empty", nameof(resultCode)); + } + + if (string.IsNullOrWhiteSpace(reason)) + { + throw new ArgumentException(nameof(reason) + " cannot be empty", nameof(resultCode)); + } + + startComment = $""; + endComment = $""; + } + } +} \ No newline at end of file diff --git a/Common/Suppressions/SuppressionExtensions.cs b/Common/Suppressions/SuppressionExtensions.cs new file mode 100644 index 00000000..2eb5ff21 --- /dev/null +++ b/Common/Suppressions/SuppressionExtensions.cs @@ -0,0 +1,16 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Suppressions +{ + using Skyline.DataMiner.CICD.Models.Protocol.Read; + + internal static class SuppressionExtensions + { + public static bool IsValid( + this IVersionHistoryBranchesBranchSystemVersionsSystemVersionMajorVersionsMajorVersionMinorVersionsMinorVersionSuppressionsSuppression suppression) + { + return suppression.ResultId?.Value != null && + suppression.Location?.Value != null && + suppression.Type?.Value != null && + suppression.Reason?.Value != null; + } + } +} \ No newline at end of file diff --git a/Common/Suppressions/SuppressionToken.cs b/Common/Suppressions/SuppressionToken.cs new file mode 100644 index 00000000..8110688e --- /dev/null +++ b/Common/Suppressions/SuppressionToken.cs @@ -0,0 +1,198 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Suppressions +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text.RegularExpressions; + + using Skyline.DataMiner.CICD.Parsers.Common.Xml; + + /// + /// Represents a suppression token. + /// + public abstract class SuppressionToken + { + /// + /// Initializes a new instance of the class. + /// + /// The XML document. + /// The comment. + /// The suppression type. + /// The code. + /// Indicates whether this is a closing token. + /// of is . + protected SuppressionToken(XmlDocument document, XmlComment comment, SuppressionType type, string code, bool isClose) + { + Document = document ?? throw new ArgumentNullException(nameof(document)); + CommentNode = comment ?? throw new ArgumentNullException(nameof(comment)); + Position = comment.FirstCharOffset; + Type = type; + Code = code ?? throw new ArgumentNullException(nameof(code)); + IsClose = isClose; + } + + /// + /// Initializes a new instance of the class. + /// + /// The XML document. + /// The position. + /// The suppression type. + /// The code. + /// Indicates whether this is a closing token. + /// or is . + protected SuppressionToken(XmlDocument document, int position, SuppressionType type, string code, bool isClose) + { + Document = document ?? throw new ArgumentNullException(nameof(document)); + Position = position; + Type = type; + Code = code ?? throw new ArgumentNullException(nameof(code)); + IsClose = isClose; + } + + /// + /// Gets the XML document. + /// + /// The XML document. + public XmlDocument Document { get; } + + /// + /// Gets the comment node. + /// + /// The comment node. + public XmlComment CommentNode { get; } + + /// + /// Gets the suppression type. + /// + /// The suppression type. + public SuppressionType Type { get; } + + /// + /// Gets the code. + /// + /// The code. + public string Code { get; } + + /// + /// Gets a value indicating whether this is a closing token. + /// + /// true if this is a closing token; otherwise, false. + public bool IsClose { get; } + + /// + /// Gets the position. + /// + /// The position. + public int Position { get; } + + /// + /// Gets a value indicating whether this is an opening token. + /// + /// true if this is an opening token; otherwise, false. + public bool IsOpen => !IsClose; + + /// + /// Gets a value indicating whether this token is valid. + /// + /// true if this token is valid; otherwise, false. + public abstract bool IsValid { get; } + + /// + /// Retrieves all tokens of the specified XML document. + /// + /// The XML document. + /// All tokens of the specified XML document. + /// is . + public static IEnumerable GetAllTokens(XmlDocument document) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + var allComments = GetAllComments(document); + + foreach (var comment in allComments) + { + if (DetectToken(document, comment, out var token)) + { + yield return token; + } + } + } + + public static bool DetectToken(XmlDocument document, XmlComment comment, out SuppressionToken token) + { + if (NormalValidatorSuppressionToken.TryParse(document, comment, out var t1)) + { + token = t1; + return true; + } + + if (PostponeValidatorSuppressionToken.TryParse(document, comment, out var t2)) + { + token = t2; + return true; + } + + token = null; + return false; + } + + /// + /// Tries to match the specified text to the specified regular expression. + /// + /// The regular expression. + /// The text. + /// When this method returns, contains the match if the there was a match; otherwise, . + /// + protected static bool TryMatchRegex(Regex regex, string text, out Match match) + { + var m = regex.Match(text); + if (m.Success) + { + match = m; + return true; + } + else + { + match = null; + return false; + } + } + + private static ICollection GetAllComments(XmlDocument document) + { + var allComments = new List(); + + TreeWalker(document, x => x is XmlContainer c ? c.Children : Enumerable.Empty(), x => + { + if (x is XmlComment c) + { + allComments.Add(c); + } + }); + + return allComments; + } + + private static void TreeWalker(T root, Func> getChildren, Action processNode) where T : class + { + Queue nodes = new Queue(); + nodes.Enqueue(root); + + while (nodes.Count > 0) + { + T node = nodes.Dequeue(); + + processNode(node); + + var children = getChildren(node); + foreach (var c in children) + { + nodes.Enqueue(c); + } + } + } + } +} \ No newline at end of file diff --git a/Common/Suppressions/SuppressionType.cs b/Common/Suppressions/SuppressionType.cs new file mode 100644 index 00000000..6a29f87c --- /dev/null +++ b/Common/Suppressions/SuppressionType.cs @@ -0,0 +1,17 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Suppressions +{ + /// + /// Defines the suppression type. + /// + public enum SuppressionType + { + /// + /// Normal suppression. + /// + Normal, + /// + /// Postpone suppression. + /// + Postpone + } +} \ No newline at end of file diff --git a/Common/Suppressions/VersionHistoryMajorChangeSuppression.cs b/Common/Suppressions/VersionHistoryMajorChangeSuppression.cs new file mode 100644 index 00000000..e5391851 --- /dev/null +++ b/Common/Suppressions/VersionHistoryMajorChangeSuppression.cs @@ -0,0 +1,55 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Suppressions +{ + using Skyline.DataMiner.CICD.Models.Protocol.Edit; + using Skyline.DataMiner.CICD.Models.Protocol.Enums; + using Skyline.DataMiner.CICD.Models.Protocol.Read; + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + + /// + /// Represents a major change suppression that is included in the version history. + /// + public class VersionHistoryMajorChangeSuppression : VersionHistorySuppression + { + /// + /// Initializes a new instance of the class. + /// + /// The suppression. + public VersionHistoryMajorChangeSuppression(IVersionHistoryBranchesBranchSystemVersionsSystemVersionMajorVersionsMajorVersionMinorVersionsMinorVersionSuppressionsSuppression suppression) : base(suppression) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The node. + /// The result. + /// The reason. + public VersionHistoryMajorChangeSuppression(IReadable node, IValidationResult result, string reason) + { + Type = EnumSuppressionType.MajorChange; + + Reason = reason; + ResultId = result.FullId; + + Location = node.GetIdentifier(); + } + + /// + /// Generates an edit XML node from this suppression. + /// + /// The corresponding edit XML node. + public VersionHistoryBranchesBranchSystemVersionsSystemVersionMajorVersionsMajorVersionMinorVersionsMinorVersionSuppressionsSuppression ToEditXml() + { + var suppression = + new VersionHistoryBranchesBranchSystemVersionsSystemVersionMajorVersionsMajorVersionMinorVersionsMinorVersionSuppressionsSuppression + { + ResultId = ResultId, + Type = new VersionHistoryBranchesBranchSystemVersionsSystemVersionMajorVersionsMajorVersionMinorVersionsMinorVersionSuppressionsSuppressionType(Type), + Location = Location, + Reason = Reason + }; + + return suppression; + } + } +} \ No newline at end of file diff --git a/Common/Suppressions/VersionHistorySuppression.cs b/Common/Suppressions/VersionHistorySuppression.cs new file mode 100644 index 00000000..97f1d8d3 --- /dev/null +++ b/Common/Suppressions/VersionHistorySuppression.cs @@ -0,0 +1,95 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Suppressions +{ + using System; + + using Skyline.DataMiner.CICD.Models.Protocol.Enums; + using Skyline.DataMiner.CICD.Models.Protocol.Read; + + /// + /// Represents a suppression that is included in the version history. + /// + public abstract class VersionHistorySuppression : ISuppression + { + /// + /// Initializes a new instance of the class. + /// + protected VersionHistorySuppression() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Invalid location, result ID, reason or type. + protected VersionHistorySuppression(IVersionHistoryBranchesBranchSystemVersionsSystemVersionMajorVersionsMajorVersionMinorVersionsMinorVersionSuppressionsSuppression suppression) + { + if (suppression == null) + { + throw new ArgumentNullException(nameof(suppression)); + } + + Location = suppression.Location?.Value ?? throw new ArgumentException("Invalid location."); + ResultId = suppression.ResultId?.Value ?? throw new ArgumentException("Invalid result ID."); + Reason = suppression.Reason?.Value ?? throw new ArgumentException("Invalid reason."); + Type = suppression.Type?.Value.GetValueOrDefault() ?? throw new ArgumentException("Invalid type."); + } + + /// + /// Gets or sets the location. + /// + /// The location. + public string Location { get; protected set; } + + /// + /// Gets or sets the result ID. + /// + /// The result ID. + public string ResultId { get; protected set; } + + /// + /// Gets or sets the reason. + /// + /// The reason. + public string Reason { get; protected set; } + + /// + /// Get or sets the suppression type. + /// + /// The suppression type. + public EnumSuppressionType Type { get; protected set; } + + /// + /// Parses the specified version history based suppression XML edit node. + /// + /// The version history based suppression XML edit node. + /// When this method returns, contains the version history based suppression. + /// + /// is . + public static bool TryParse( + IVersionHistoryBranchesBranchSystemVersionsSystemVersionMajorVersionsMajorVersionMinorVersionsMinorVersionSuppressionsSuppression + versionSuppression, out VersionHistorySuppression suppression) + { + if (versionSuppression == null) + { + throw new ArgumentNullException(nameof(versionSuppression)); + } + + suppression = default; + + if (!versionSuppression.IsValid()) + { + return false; + } + + switch (versionSuppression.Type.Value) + { + case EnumSuppressionType.MajorChange: + suppression = new VersionHistoryMajorChangeSuppression(versionSuppression); + return true; + + default: + return false; + } + } + } +} \ No newline at end of file diff --git a/Common/Suppressions/VersionHistorySuppressionManager.cs b/Common/Suppressions/VersionHistorySuppressionManager.cs new file mode 100644 index 00000000..f5378ee5 --- /dev/null +++ b/Common/Suppressions/VersionHistorySuppressionManager.cs @@ -0,0 +1,156 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Suppressions +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Skyline.DataMiner.CICD.Models.Protocol.Read; + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + + /// + /// Suppression manager for version history based suppressions. + /// + public class VersionHistorySuppressionManager + { + private readonly IProtocol protocol; + private readonly IDictionary> suppressions = new Dictionary>(); + + /// + /// Initializes a new instance of the class. + /// + /// The protocol. + /// is . + public VersionHistorySuppressionManager(IProtocol protocol) + { + this.protocol = protocol ?? throw new ArgumentNullException(nameof(protocol)); + + GetAllSuppressions(); + } + + private void GetAllSuppressions() + { + if (protocol.VersionHistory == null) + { + // No history, no suppressions. + return; + } + + if (!protocol.VersionHistory.TryGetMinorVersion(protocol.Version?.Value, out var minor)) + { + // No version, no suppressions + return; + } + + if (minor?.Suppressions == null) + { + return; + } + + foreach (var minorSuppression in minor.Suppressions) + { + if (minorSuppression.ResultId?.Value == null) + { + // Invalid suppression (ResultId is used for the dictionary). TryParse will be false when stuff is missing. + continue; + } + + if (!suppressions.TryGetValue(minorSuppression.ResultId.Value, out List temp)) + { + temp = new List(); + suppressions.Add(minorSuppression.ResultId.Value, temp); + } + + if (VersionHistorySuppression.TryParse(minorSuppression, out VersionHistorySuppression s)) + { + temp.Add(s); + } + } + } + + /// + /// Determines whether the specified validator result is suppressed. + /// + /// The validator result. + /// true if the specified validator result is suppressed; otherwise, false. + /// is . + public bool IsSuppressed(IValidationResult result) + { + if (result is null) + { + throw new ArgumentNullException(nameof(result)); + } + + return TryFindSuppression(result, out _); + } + + /// + /// Determines whether all the sub results of the specified validation result are suppressed. + /// + /// The validation result. + /// true if all the child results of the specified validator result are suppressed; otherwise, false. + /// is . + public bool AreAllChildrenSuppressed(IValidationResult result) + { + if(result == null) throw new ArgumentNullException(nameof (result)); + + if (result.SubResults == null || result.SubResults.Count == 0) + { + return false; + } + + foreach (var r in result.SubResults) + { + if (!IsSuppressed(r) && !AreAllChildrenSuppressed(r)) + { + return false; + } + } + + return true; + } + + /// + /// Adds the specified suppression. + /// + /// The suppression to add. + /// is . + public void AddSuppression(VersionHistorySuppression suppression) + { + if(suppression == null) throw new ArgumentNullException(nameof(suppression)); + + if (!suppressions.TryGetValue(suppression.ResultId, out List temp)) + { + temp = new List(); + suppressions.Add(suppression.ResultId, temp); + } + + temp.Add(suppression); + } + + /// + /// Retrieves the suppression for the specified validator result. + /// + /// The validator result. + /// When this method returns, contains the suppression that suppresses this validation result if a suppression was found; otherwise, . + /// true if the specified validation result has been suppressed; otherwise, false. + public bool TryFindSuppression(IValidationResult result, out VersionHistorySuppression suppression) + { + suppression = default; + + if (result?.FullId == null) + { + // Old validator results. + return false; + } + + if (!suppressions.TryGetValue(result.FullId, out List suppressionsForResultCode)) + { + return false; + } + + string location = result.PositionNode.GetIdentifier(); + suppression = suppressionsForResultCode.FirstOrDefault(s => s.Location == location); + return suppression != null; + } + } +} \ No newline at end of file diff --git a/Common/Tools/QActionCompilationModelProvider.cs b/Common/Tools/QActionCompilationModelProvider.cs new file mode 100644 index 00000000..62e1b991 --- /dev/null +++ b/Common/Tools/QActionCompilationModelProvider.cs @@ -0,0 +1,61 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Tools +{ + using System; + + using Skyline.DataMiner.CICD.Parsers.Common.Xml; + + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + + using Skyline.DataMiner.CICD.Models.Protocol.Read.Interfaces; + + using Skyline.DataMiner.CICD.Models.Protocol; + using Skyline.DataMiner.CICD.Models.Common; + + /// + /// Represents a the QAction compilation model provider for a connector. + /// + public class QActionCompilationModelProvider : IQActionCompilationModelProvider + { + private readonly IAssemblyResolver _dllImportResolver; + private readonly IProtocolQActionHelperProvider _qactionHelperProvider; + + /// + /// Initializes a new instance of the class. + /// + /// The assembly resolver. + /// The QAction helper code provider. + /// or is . + public QActionCompilationModelProvider(IAssemblyResolver assemblyResolver, IProtocolQActionHelperProvider qactionHelperProvider) + { + _dllImportResolver = assemblyResolver ?? throw new ArgumentNullException(nameof(assemblyResolver)); + _qactionHelperProvider = qactionHelperProvider ?? throw new ArgumentNullException(nameof(qactionHelperProvider)); + } + + /// + /// Retrieves the QAction compilation model. + /// + /// The connector XML document. + /// The protocol model. + /// The code. + /// The QAction compilation model. + /// or is . + public QActionCompilationModel GetQActionCompilationModel(XmlDocument document, IProtocolModel model, string code) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + string qactionHelperSourceCode = _qactionHelperProvider?.GetProtocolQActionHelper(code, ignoreErrors: true); + + var solution = new QActionCompilationModel(qactionHelperSourceCode, model, _dllImportResolver); + + return solution; + } + } +} diff --git a/Common/Tools/SimpleLineInfoProvider.cs b/Common/Tools/SimpleLineInfoProvider.cs new file mode 100644 index 00000000..4ba2a9ab --- /dev/null +++ b/Common/Tools/SimpleLineInfoProvider.cs @@ -0,0 +1,154 @@ +namespace Skyline.DataMiner.CICD.Validators.Common.Tools +{ + using System; + using System.Collections.Generic; + + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + + /// + /// Represents a simple line info provider. + /// + public class SimpleLineInfoProvider : ILineInfoProvider + { + private readonly string _text; + private readonly Lazy> _lineBreaks; + + /// + /// Initializes a new instance of the class. + /// + /// The input text. + /// is . + public SimpleLineInfoProvider(string text) + { + _text = text ?? throw new ArgumentNullException(nameof(text)); + + _lineBreaks = new Lazy>(GetLinebreakPositions); + } + + /// + /// Gets the number of lines. + /// + /// The number of lines. + public int Lines => _lineBreaks.Value.Count + 1; + + /// + public void GetLineAndColumn(int offset, out int lineNumber, out int columnIndex) + { + if (offset < 0 || offset > _text.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + lineNumber = _lineBreaks.Value.BinarySearch(offset); + if (lineNumber < 0) + { + lineNumber = ~lineNumber; + } + + columnIndex = offset - GetOffset(lineNumber); + } + + /// + public int GetOffset(int lineNumber, int columnIndex) + { + if (lineNumber < 0 || lineNumber >= Lines) + { + throw new ArgumentOutOfRangeException(nameof(lineNumber)); + } + + int offset = GetOffset(lineNumber); + return offset + columnIndex; + } + + /// + public int? GetFirstNonWhitespaceOffset(int lineNumber) + { + if (lineNumber < 0 || lineNumber >= Lines) + { + throw new ArgumentOutOfRangeException(nameof(lineNumber)); + } + + string text = GetText(lineNumber); + + for (int i = 0; i < text.Length; i++) + { + if (!Char.IsWhiteSpace(text[i])) + { + return i; + } + } + + return null; + } + + /// + /// Gets the offset of the specified line. + /// + /// The line number. + /// The offset. + /// is invalid. + private int GetOffset(int lineNumber) + { + if (lineNumber < 0 || lineNumber >= Lines) + { + throw new ArgumentOutOfRangeException(nameof(lineNumber)); + } + + if (lineNumber == 0) + { + return 0; + } + + return _lineBreaks.Value[lineNumber - 1] + 1; + } + + /// + /// Retrieves the text of the specified line. + /// + /// + /// + /// + private string GetText(int lineNumber) + { + if (lineNumber < 0 || lineNumber >= Lines) + { + throw new ArgumentOutOfRangeException(nameof(lineNumber)); + } + + int start, end; + + if (lineNumber == 0) + { + start = 0; + end = _lineBreaks.Value.Count > 0 ? _lineBreaks.Value[0] : _text.Length; + } + else if (lineNumber == Lines - 1) + { + start = GetOffset(lineNumber); + end = _text.Length; + } + else + { + start = GetOffset(lineNumber); + end = GetOffset(lineNumber + 1); + } + + return _text.Substring(start, end - start); + } + + private List GetLinebreakPositions() + { + var lineBreaks = new List(); + + for (int i = 0; i < _text.Length; i++) + { + if (_text[i] == '\n') + { + lineBreaks.Add(i); + } + } + + return lineBreaks; + } + } +} diff --git a/Common/icon.png b/Common/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..982bbaad78f97237409293e0824b5b37dc3fc758 GIT binary patch literal 47711 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�RZ0jZLW1iR0Ce(cjoVc@ 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

mmi_`P@SQr+4e-cTVg2 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 literal 0 HcmV?d00001 diff --git a/Skyline.DataMiner.CICD.Validators.sln b/Skyline.DataMiner.CICD.Validators.sln new file mode 100644 index 00000000..c4989982 --- /dev/null +++ b/Skyline.DataMiner.CICD.Validators.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34221.43 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{6C83F73E-1130-46EF-BB3C-67573F987AFE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6C83F73E-1130-46EF-BB3C-67573F987AFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C83F73E-1130-46EF-BB3C-67573F987AFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C83F73E-1130-46EF-BB3C-67573F987AFE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C83F73E-1130-46EF-BB3C-67573F987AFE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {014D26A1-5C06-445C-85FF-4356501918FB} + EndGlobalSection +EndGlobal diff --git a/_NuGetItems/LICENSE.txt b/_NuGetItems/LICENSE.txt new file mode 100644 index 00000000..92a62e8c --- /dev/null +++ b/_NuGetItems/LICENSE.txt @@ -0,0 +1,18 @@ +SKYLINE LIBRARY LICENSE + +1. Applicability +The software in this repository (hereafter the “Software”) is owned by Skyline Communications (hereafter the “Skyline”). The terms of this license govern your use of the Software. If you do not agree with the terms of this license, you may not use or exploit the Software in any other manner. +2. Grant of rights +You may use the Software for the development, testing and validation of DataMiner packages only. +You may not use this Software in any other manner unless you have obtained Skyline’s prior written authorization to do so. +It is forbidden to create derivative works of the Software. +3. Intellectual property +Skyline owns the intellectual property rights vested in the Software. Skyline granting you access to the Software does not entail permission to utilize or otherwise manipulate the Software in contravention to this Library License. Skyline reserves the right to pursue legal action against you in case of breach of its intellectual property rights. +4. No warranty +Skyline provides the Software ‘as is’, without any warranty of any kind. +5. Limitation of liability +Within the maximum possible extent under the applicable laws, Skyline disclaims all liability for the Software. +6. Applicable laws and jurisdiction +This license shall be governed by the laws of Belgium. Any dispute shall be submitted to the exclusive jurisdiction of the competent courts of Gent, division Kortrijk, Belgium + + diff --git a/_NuGetItems/icon.png b/_NuGetItems/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..982bbaad78f97237409293e0824b5b37dc3fc758 GIT binary patch literal 47711 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�RZ0jZLW1iR0Ce(cjoVc@ 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

mmi_`P@SQr+4e-cTVg2 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 literal 0 HcmV?d00001