From 60f7f7d0f359749285c684b1a5c4530a596ed949 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 28 Jul 2024 14:52:42 +1000 Subject: [PATCH] Refactoring for nullable (#1880) --- .../Yaml/StringArrayMapConverter.cs | 2 +- .../Definitions/ILanguageBlock.cs | 4 +- .../Definitions/IResource.cs | 8 +- .../Definitions/IResourceAnnotations.cs | 11 + .../Definitions/IResourceHelpInfo.cs | 30 +++ .../Definitions/IResourceLabels.cs | 18 ++ .../Definitions/IResourceMetadata.cs | 15 ++ src/PSRule.Types/Definitions/IResourceTags.cs | 26 +++ src/PSRule.Types/Definitions/ISourceExtent.cs | 4 +- src/PSRule.Types/Definitions/ISourceFile.cs | 42 ++++ .../Definitions/ResourceAnnotations.cs | 2 +- .../Definitions/ResourceFlags.cs | 0 .../Definitions/ResourceHelpInfo.cs} | 28 +-- .../Definitions/ResourceHelper.cs | 27 ++- .../Definitions/ResourceId.cs | 47 +--- .../Definitions/ResourceIdKind.cs | 35 +++ .../Definitions/ResourceKind.cs | 0 src/PSRule.Types/Definitions/SeverityLevel.cs | 30 +++ src/PSRule.Types/Definitions/SourceType.cs | 25 +++ .../Commands/ExportConventionCommand.cs | 24 +- src/PSRule/Commands/LanguageBlock.cs | 6 +- .../Commands/NewRuleDefinitionCommand.cs | 10 +- src/PSRule/Common/JsonConverters.cs | 2 +- src/PSRule/Common/JsonReaderExtensions.cs | 4 +- src/PSRule/Common/SeverityLevelExtensions.cs | 2 +- src/PSRule/Common/YamlConverters.cs | 206 +++++++++--------- src/PSRule/Configuration/PSRuleOption.cs | 1 + src/PSRule/Definitions/AnnotatedExtensions.cs | 13 ++ .../Definitions/Conventions/BaseConvention.cs | 6 +- .../Conventions/ScriptBlockConvention.cs | 30 +-- .../Expressions/ExpressionContext.cs | 5 +- .../Expressions/IExpressionContext.cs | 3 +- .../Definitions/Expressions/Primitives.cs | 4 +- src/PSRule/Definitions/IAnnotated.cs | 11 +- src/PSRule/Definitions/ICondition.cs | 21 -- src/PSRule/Definitions/IConditionResult.cs | 25 +++ .../Definitions/IDetailedRuleResultV2.cs | 37 ++++ src/PSRule/Definitions/IResultDetailV2.cs | 15 ++ src/PSRule/Definitions/IResultReasonV2.cs | 30 +++ src/PSRule/Definitions/IRuleResultV2.cs | 68 ------ src/PSRule/Definitions/ISpecDescriptor.cs | 19 ++ src/PSRule/Definitions/ISuppressionInfo.cs | 13 -- .../Definitions/ISuppressionInfoComparer.cs | 17 ++ src/PSRule/Definitions/InternalResource.cs | 4 +- src/PSRule/Definitions/Resource.cs | 2 +- src/PSRule/Definitions/ResourceBuilder.cs | 3 + src/PSRule/Definitions/ResourceLabels.cs | 11 +- src/PSRule/Definitions/ResourceMetadata.cs | 8 +- src/PSRule/Definitions/ResourceTags.cs | 22 +- src/PSRule/Definitions/Rules/IRuleSpec.cs | 37 ++++ src/PSRule/Definitions/Rules/IRuleV1.cs | 30 +++ src/PSRule/Definitions/Rules/Rule.cs | 181 --------------- src/PSRule/Definitions/Rules/RuleFilter.cs | 6 +- src/PSRule/Definitions/Rules/RuleV1.cs | 73 +++++++ src/PSRule/Definitions/Rules/RuleV1Spec.cs | 30 +++ src/PSRule/Definitions/Rules/RuleVisitor.cs | 4 +- src/PSRule/Definitions/Selectors/ISelector.cs | 9 + .../Definitions/Selectors/SelectorVisitor.cs | 9 +- src/PSRule/Definitions/SourceExtent.cs | 6 +- src/PSRule/Definitions/SpecAttribute.cs | 23 ++ src/PSRule/Definitions/SpecDescriptor.cs | 30 +++ src/PSRule/Definitions/SpecFactory.cs | 59 ----- .../SuppressionGroupVisitor.cs | 4 +- src/PSRule/Help/FormatOptions.cs | 18 ++ src/PSRule/Help/IHelpDocument.cs | 17 ++ src/PSRule/Help/Link.cs | 14 ++ src/PSRule/Help/Models.cs | 111 ---------- src/PSRule/Help/ResourceHelpDocument.cs | 27 +++ src/PSRule/Help/RuleDocument.cs | 28 +++ src/PSRule/Help/TextBlock.cs | 31 +++ src/PSRule/Host/HostHelper.cs | 5 +- src/PSRule/PSRule.csproj | 1 + src/PSRule/Pipeline/AssertPipelineBuilder.cs | 2 +- src/PSRule/Pipeline/DefaultPipelineResult.cs | 4 +- src/PSRule/Pipeline/Dependencies/LockFile.cs | 4 + src/PSRule/Pipeline/Emitters/YamlEmitter.cs | 4 + .../Formatters/AzurePipelinesFormatter.cs | 6 +- .../Formatters/GitHubActionsFormatter.cs | 6 +- src/PSRule/Pipeline/IPipeline.cs | 8 +- .../Pipeline/InvokePipelineBuilderBase.cs | 3 +- src/PSRule/Pipeline/InvokeResult.cs | 2 +- src/PSRule/Pipeline/OptionContextBuilder.cs | 4 +- src/PSRule/Pipeline/OptionScope.cs | 2 +- src/PSRule/Pipeline/PipelineContext.cs | 4 +- src/PSRule/Pipeline/Source.cs | 92 +------- src/PSRule/Pipeline/SourceFile.cs | 76 +++++++ src/PSRule/Pipeline/SourcePipelineBuilder.cs | 1 + src/PSRule/Rules/PowerShellCondition.cs | 6 +- src/PSRule/Rules/Rule.cs | 9 +- src/PSRule/Rules/RuleBlock.cs | 20 +- src/PSRule/Rules/RuleRecord.cs | 2 +- src/PSRule/Rules/RuleSummaryRecord.cs | 2 +- src/PSRule/Runtime/LanguageScope.cs | 9 +- src/PSRule/Runtime/LanguageScopeSet.cs | 4 +- .../ObjectPath/IPathExpressionContext.cs | 14 ++ ...ions.cs => ObjectPathEvaluateException.cs} | 0 .../Runtime/ObjectPath/PathExpression.cs | 32 --- .../ObjectPath/PathExpressionContext.cs | 26 +++ src/PSRule/Runtime/RunspaceContext.cs | 7 +- tests/PSRule.Tests/BaselineTests.cs | 1 + tests/PSRule.Tests/TestResourceName.cs | 16 +- 101 files changed, 1182 insertions(+), 913 deletions(-) rename src/{PSRule => PSRule.Types}/Definitions/ILanguageBlock.cs (87%) rename src/{PSRule => PSRule.Types}/Definitions/IResource.cs (87%) create mode 100644 src/PSRule.Types/Definitions/IResourceAnnotations.cs create mode 100644 src/PSRule.Types/Definitions/IResourceHelpInfo.cs create mode 100644 src/PSRule.Types/Definitions/IResourceLabels.cs create mode 100644 src/PSRule.Types/Definitions/IResourceMetadata.cs create mode 100644 src/PSRule.Types/Definitions/IResourceTags.cs create mode 100644 src/PSRule.Types/Definitions/ISourceFile.cs rename src/{PSRule => PSRule.Types}/Definitions/ResourceAnnotations.cs (89%) rename src/{PSRule => PSRule.Types}/Definitions/ResourceFlags.cs (100%) rename src/{PSRule/Definitions/IResourceHelpInfo.cs => PSRule.Types/Definitions/ResourceHelpInfo.cs} (57%) rename src/{PSRule => PSRule.Types}/Definitions/ResourceHelper.cs (75%) rename src/{PSRule => PSRule.Types}/Definitions/ResourceId.cs (84%) create mode 100644 src/PSRule.Types/Definitions/ResourceIdKind.cs rename src/{PSRule => PSRule.Types}/Definitions/ResourceKind.cs (100%) create mode 100644 src/PSRule.Types/Definitions/SeverityLevel.cs create mode 100644 src/PSRule.Types/Definitions/SourceType.cs create mode 100644 src/PSRule/Definitions/AnnotatedExtensions.cs create mode 100644 src/PSRule/Definitions/IConditionResult.cs create mode 100644 src/PSRule/Definitions/IDetailedRuleResultV2.cs create mode 100644 src/PSRule/Definitions/IResultDetailV2.cs create mode 100644 src/PSRule/Definitions/IResultReasonV2.cs create mode 100644 src/PSRule/Definitions/ISpecDescriptor.cs create mode 100644 src/PSRule/Definitions/ISuppressionInfoComparer.cs create mode 100644 src/PSRule/Definitions/Rules/IRuleSpec.cs create mode 100644 src/PSRule/Definitions/Rules/IRuleV1.cs delete mode 100644 src/PSRule/Definitions/Rules/Rule.cs create mode 100644 src/PSRule/Definitions/Rules/RuleV1.cs create mode 100644 src/PSRule/Definitions/Rules/RuleV1Spec.cs create mode 100644 src/PSRule/Definitions/Selectors/ISelector.cs create mode 100644 src/PSRule/Definitions/SpecAttribute.cs create mode 100644 src/PSRule/Definitions/SpecDescriptor.cs create mode 100644 src/PSRule/Help/FormatOptions.cs create mode 100644 src/PSRule/Help/IHelpDocument.cs create mode 100644 src/PSRule/Help/Link.cs delete mode 100644 src/PSRule/Help/Models.cs create mode 100644 src/PSRule/Help/ResourceHelpDocument.cs create mode 100644 src/PSRule/Help/RuleDocument.cs create mode 100644 src/PSRule/Help/TextBlock.cs create mode 100644 src/PSRule/Pipeline/SourceFile.cs create mode 100644 src/PSRule/Runtime/ObjectPath/IPathExpressionContext.cs rename src/PSRule/Runtime/ObjectPath/{Exceptions.cs => ObjectPathEvaluateException.cs} (100%) create mode 100644 src/PSRule/Runtime/ObjectPath/PathExpressionContext.cs diff --git a/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs b/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs index 8a323a573a..60220c53c7 100644 --- a/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs +++ b/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs @@ -45,7 +45,7 @@ bool IYamlTypeConverter.Accepts(Type type) } else if (parser.TryConsume(out scalar)) { - result[key] = new string[] { scalar.Value }; + result[key] = [scalar.Value]; } #pragma warning restore IDE0001 } diff --git a/src/PSRule/Definitions/ILanguageBlock.cs b/src/PSRule.Types/Definitions/ILanguageBlock.cs similarity index 87% rename from src/PSRule/Definitions/ILanguageBlock.cs rename to src/PSRule.Types/Definitions/ILanguageBlock.cs index a68b3f99d0..f22d833fc8 100644 --- a/src/PSRule/Definitions/ILanguageBlock.cs +++ b/src/PSRule.Types/Definitions/ILanguageBlock.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using PSRule.Pipeline; - namespace PSRule.Definitions; /// @@ -18,5 +16,5 @@ public interface ILanguageBlock /// /// The source location for the block. /// - SourceFile Source { get; } + ISourceFile Source { get; } } diff --git a/src/PSRule/Definitions/IResource.cs b/src/PSRule.Types/Definitions/IResource.cs similarity index 87% rename from src/PSRule/Definitions/IResource.cs rename to src/PSRule.Types/Definitions/IResource.cs index c058e1165f..b47a310a54 100644 --- a/src/PSRule/Definitions/IResource.cs +++ b/src/PSRule.Types/Definitions/IResource.cs @@ -24,24 +24,24 @@ public interface IResource : ILanguageBlock string Name { get; } /// - /// An optional reference identifer for the resource. + /// An optional reference identifier for the resource. /// ResourceId? Ref { get; } /// /// Any additional aliases for the resource. /// - ResourceId[] Alias { get; } + ResourceId[]? Alias { get; } /// /// Any resource tags. /// - ResourceTags Tags { get; } + IResourceTags? Tags { get; } /// /// Any taxonomy references. /// - ResourceLabels Labels { get; } + IResourceLabels? Labels { get; } /// /// Flags for the resource. diff --git a/src/PSRule.Types/Definitions/IResourceAnnotations.cs b/src/PSRule.Types/Definitions/IResourceAnnotations.cs new file mode 100644 index 0000000000..4acc58b8f1 --- /dev/null +++ b/src/PSRule.Types/Definitions/IResourceAnnotations.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// +/// +public interface IResourceAnnotations : IDictionary +{ +} diff --git a/src/PSRule.Types/Definitions/IResourceHelpInfo.cs b/src/PSRule.Types/Definitions/IResourceHelpInfo.cs new file mode 100644 index 0000000000..9d1da35d04 --- /dev/null +++ b/src/PSRule.Types/Definitions/IResourceHelpInfo.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// Metadata about a PSRule resource. +/// +public interface IResourceHelpInfo +{ + /// + /// The name of the resource. + /// + string Name { get; } + + /// + /// A display name of the resource if set. + /// + string DisplayName { get; } + + /// + /// A short description of the resource if set. + /// + InfoString Synopsis { get; } + + /// + /// A long description of the resource if set. + /// + InfoString Description { get; } +} diff --git a/src/PSRule.Types/Definitions/IResourceLabels.cs b/src/PSRule.Types/Definitions/IResourceLabels.cs new file mode 100644 index 0000000000..97fb01fe7c --- /dev/null +++ b/src/PSRule.Types/Definitions/IResourceLabels.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// +/// +public interface IResourceLabels : IDictionary +{ + /// + /// Check if the resource label matches. + /// + /// + /// + /// + bool Contains(string key, string[] value); +} diff --git a/src/PSRule.Types/Definitions/IResourceMetadata.cs b/src/PSRule.Types/Definitions/IResourceMetadata.cs new file mode 100644 index 0000000000..416891a163 --- /dev/null +++ b/src/PSRule.Types/Definitions/IResourceMetadata.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// Additional resource metadata. +/// +public interface IResourceMetadata +{ + /// + /// Annotations on the resource. + /// + public IResourceAnnotations Annotations { get;} +} diff --git a/src/PSRule.Types/Definitions/IResourceTags.cs b/src/PSRule.Types/Definitions/IResourceTags.cs new file mode 100644 index 0000000000..1690247e09 --- /dev/null +++ b/src/PSRule.Types/Definitions/IResourceTags.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; + +namespace PSRule.Definitions; + +/// +/// +/// +public interface IResourceTags : IDictionary +{ + /// + /// + /// + /// + Hashtable ToHashtable(); + + /// + /// Check if a specific resource tag exists. + /// + /// + /// + /// + bool Contains(object key, object value); +} diff --git a/src/PSRule.Types/Definitions/ISourceExtent.cs b/src/PSRule.Types/Definitions/ISourceExtent.cs index db91e639d7..1360b11787 100644 --- a/src/PSRule.Types/Definitions/ISourceExtent.cs +++ b/src/PSRule.Types/Definitions/ISourceExtent.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. namespace PSRule.Definitions; @@ -11,7 +11,7 @@ public interface ISourceExtent /// /// The source file path. /// - string File { get; } + ISourceFile File { get; } /// /// The first line of the expression. diff --git a/src/PSRule.Types/Definitions/ISourceFile.cs b/src/PSRule.Types/Definitions/ISourceFile.cs new file mode 100644 index 0000000000..5742822316 --- /dev/null +++ b/src/PSRule.Types/Definitions/ISourceFile.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// A source file containing resources that will be loaded and interpreted by PSRule. +/// +public interface ISourceFile +{ + /// + /// The file path to the source. + /// + string Path { get; } + + /// + /// The name of the module if the source was loaded from a module. + /// + string Module { get; } + + /// + /// The type of source file. + /// + SourceType Type { get; } + + /// + /// The base path to use for loading help content. + /// + string HelpPath { get; } + + /// + /// Determines if the source file exists. + /// + /// Returns true when the source file exists. + bool Exists(); + + /// + /// Determines if the source file is a dependency. + /// + /// Returns true when the source file is a dependency. + bool IsDependency(); +} diff --git a/src/PSRule/Definitions/ResourceAnnotations.cs b/src/PSRule.Types/Definitions/ResourceAnnotations.cs similarity index 89% rename from src/PSRule/Definitions/ResourceAnnotations.cs rename to src/PSRule.Types/Definitions/ResourceAnnotations.cs index 73431f32ca..717baee367 100644 --- a/src/PSRule/Definitions/ResourceAnnotations.cs +++ b/src/PSRule.Types/Definitions/ResourceAnnotations.cs @@ -6,7 +6,7 @@ namespace PSRule.Definitions; /// /// Additional resource annotations. /// -public sealed class ResourceAnnotations : Dictionary +public sealed class ResourceAnnotations : Dictionary, IResourceAnnotations { } diff --git a/src/PSRule/Definitions/ResourceFlags.cs b/src/PSRule.Types/Definitions/ResourceFlags.cs similarity index 100% rename from src/PSRule/Definitions/ResourceFlags.cs rename to src/PSRule.Types/Definitions/ResourceFlags.cs diff --git a/src/PSRule/Definitions/IResourceHelpInfo.cs b/src/PSRule.Types/Definitions/ResourceHelpInfo.cs similarity index 57% rename from src/PSRule/Definitions/IResourceHelpInfo.cs rename to src/PSRule.Types/Definitions/ResourceHelpInfo.cs index ed5cf5c3a0..0bab43fe5c 100644 --- a/src/PSRule/Definitions/IResourceHelpInfo.cs +++ b/src/PSRule.Types/Definitions/ResourceHelpInfo.cs @@ -1,36 +1,10 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Newtonsoft.Json; namespace PSRule.Definitions; -/// -/// Metadata about a PSRule resource. -/// -public interface IResourceHelpInfo -{ - /// - /// The name of the resource. - /// - string Name { get; } - - /// - /// A display name of the resource if set. - /// - string DisplayName { get; } - - /// - /// A short description of the resource if set. - /// - InfoString Synopsis { get; } - - /// - /// A long description of the resource if set. - /// - InfoString Description { get; } -} - internal sealed class ResourceHelpInfo : IResourceHelpInfo { internal ResourceHelpInfo(string name, string displayName, InfoString synopsis, InfoString description) diff --git a/src/PSRule/Definitions/ResourceHelper.cs b/src/PSRule.Types/Definitions/ResourceHelper.cs similarity index 75% rename from src/PSRule/Definitions/ResourceHelper.cs rename to src/PSRule.Types/Definitions/ResourceHelper.cs index 1fccf29e13..e8d780a697 100644 --- a/src/PSRule/Definitions/ResourceHelper.cs +++ b/src/PSRule.Types/Definitions/ResourceHelper.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using PSRule.Definitions.Rules; -using PSRule.Runtime; - namespace PSRule.Definitions; internal static class ResourceHelper @@ -12,24 +9,26 @@ internal static class ResourceHelper private const char SCOPE_SEPARATOR = '\\'; + internal const string STANDALONE_SCOPENAME = "."; + internal static string GetIdString(string scope, string name) { return name.IndexOf(SCOPE_SEPARATOR) >= 0 ? name : string.Concat( - LanguageScope.Normalize(scope), + NormalizeScope(scope), SCOPE_SEPARATOR, name ); } - internal static void ParseIdString(string defaultScope, string id, out string scope, out string name) + internal static void ParseIdString(string defaultScope, string id, out string? scope, out string? name) { ParseIdString(id, out scope, out name); - scope ??= LanguageScope.Normalize(defaultScope); + scope ??= NormalizeScope(defaultScope); } - internal static void ParseIdString(string id, out string scope, out string name) + internal static void ParseIdString(string id, out string? scope, out string? name) { scope = null; name = null; @@ -48,7 +47,7 @@ internal static void ParseIdString(string id, out string scope, out string name) /// An array of names. Qualified names (RuleIds) supplied are left intact. /// The of the . /// An array of RuleIds. - internal static ResourceId[] GetRuleId(string defaultScope, string[] name, ResourceIdKind kind) + internal static ResourceId[]? GetRuleId(string defaultScope, string[] name, ResourceIdKind kind) { if (name == null || name.Length == 0) return null; @@ -60,8 +59,9 @@ internal static ResourceId[] GetRuleId(string defaultScope, string[] name, Resou return (result.Length == 0) ? null : result; } - internal static ResourceId GetRuleId(string defaultScope, string name, ResourceIdKind kind) + internal static ResourceId GetRuleId(string? defaultScope, string name, ResourceIdKind kind) { + defaultScope ??= STANDALONE_SCOPENAME; return name.IndexOf(SCOPE_SEPARATOR) > 0 ? ResourceId.Parse(name, kind) : new ResourceId(defaultScope, name, kind); } @@ -70,7 +70,7 @@ internal static ResourceId GetRuleId(string defaultScope, string name, ResourceI return string.IsNullOrEmpty(name) ? null : new ResourceId(scope, name, kind); } - internal static bool IsObsolete(ResourceMetadata metadata) + internal static bool IsObsolete(IResourceMetadata metadata) { return metadata != null && metadata.Annotations != null && @@ -80,6 +80,11 @@ internal static bool IsObsolete(ResourceMetadata metadata) internal static SeverityLevel GetLevel(SeverityLevel? level) { - return !level.HasValue || level.Value == SeverityLevel.None ? RuleV1.DEFAULT_LEVEL : level.Value; + return !level.HasValue || level.Value == SeverityLevel.None ? SeverityLevel.Error : level.Value; + } + + internal static string NormalizeScope(string? scope) + { + return scope == null || string.IsNullOrEmpty(scope) ? STANDALONE_SCOPENAME : scope; } } diff --git a/src/PSRule/Definitions/ResourceId.cs b/src/PSRule.Types/Definitions/ResourceId.cs similarity index 84% rename from src/PSRule/Definitions/ResourceId.cs rename to src/PSRule.Types/Definitions/ResourceId.cs index b8b7cdfbab..593bb0c386 100644 --- a/src/PSRule/Definitions/ResourceId.cs +++ b/src/PSRule.Types/Definitions/ResourceId.cs @@ -2,41 +2,9 @@ // Licensed under the MIT License. using System.Diagnostics; -using PSRule.Runtime; namespace PSRule.Definitions; -/// -/// Additional information about the type of identifier if available. -/// -internal enum ResourceIdKind -{ - /// - /// Not specified. - /// - None = 0, - - /// - /// Unknown. - /// - Unknown = 1, - - /// - /// The identifier is a primary resource identifier. - /// - Id = 2, - - /// - /// The identifier is a opaque reference resource identifier. - /// - Ref = 3, - - /// - /// The identifier is an alias resource identifier. - /// - Alias = 4, -} - /// /// A unique identifier for a resource. /// @@ -57,7 +25,7 @@ private ResourceId(string id, string scope, string name, ResourceIdKind kind) } internal ResourceId(string scope, string name, ResourceIdKind kind) - : this(GetIdString(scope, name), LanguageScope.Normalize(scope), name, kind) { } + : this(GetIdString(scope, name), ResourceHelper.NormalizeScope(scope), name, kind) { } /// /// A string representation of the resource identifier. @@ -136,7 +104,7 @@ public bool Equals(string id) return !left.Equals(right); } - private static bool EqualOrNull(string x, string y) + private static bool EqualOrNull(string? x, string? y) { return x == null || y == null || StringComparer.OrdinalIgnoreCase.Equals(x, y); } @@ -149,7 +117,7 @@ private static int GetHashCode(string id) private static string GetIdString(string scope, string name) { return string.Concat( - LanguageScope.Normalize(scope), + ResourceHelper.NormalizeScope(scope), SCOPE_SEPARATOR, name ); @@ -157,20 +125,21 @@ private static string GetIdString(string scope, string name) internal static ResourceId Parse(string id, ResourceIdKind kind = ResourceIdKind.Unknown) { - return TryParse(id, kind, out var value) ? value.Value : default; + return TryParse(id, kind, out var value) && value != null ? value.Value : default; } private static bool TryParse(string id, ResourceIdKind kind, out ResourceId? value) { value = null; - if (string.IsNullOrEmpty(id) || !TryParse(id, out var scope, out var name)) + if (string.IsNullOrEmpty(id) || !TryParse(id, out var scope, out var name) || name == null) return false; + scope ??= ResourceHelper.STANDALONE_SCOPENAME; value = new ResourceId(id, scope, name, kind); return true; } - private static bool TryParse(string id, out string scope, out string name) + private static bool TryParse(string id, out string? scope, out string? name) { scope = null; name = null; @@ -251,7 +220,7 @@ public int GetHashCode(string obj) #endregion IEqualityComparer - private static bool EqualOrNull(string x, string y) + private static bool EqualOrNull(string? x, string? y) { return x == null || y == null || StringComparer.OrdinalIgnoreCase.Equals(x, y); } diff --git a/src/PSRule.Types/Definitions/ResourceIdKind.cs b/src/PSRule.Types/Definitions/ResourceIdKind.cs new file mode 100644 index 0000000000..980d2f0577 --- /dev/null +++ b/src/PSRule.Types/Definitions/ResourceIdKind.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// Additional information about the type of identifier if available. +/// +internal enum ResourceIdKind +{ + /// + /// Not specified. + /// + None = 0, + + /// + /// Unknown. + /// + Unknown = 1, + + /// + /// The identifier is a primary resource identifier. + /// + Id = 2, + + /// + /// The identifier is a opaque reference resource identifier. + /// + Ref = 3, + + /// + /// The identifier is an alias resource identifier. + /// + Alias = 4, +} diff --git a/src/PSRule/Definitions/ResourceKind.cs b/src/PSRule.Types/Definitions/ResourceKind.cs similarity index 100% rename from src/PSRule/Definitions/ResourceKind.cs rename to src/PSRule.Types/Definitions/ResourceKind.cs diff --git a/src/PSRule.Types/Definitions/SeverityLevel.cs b/src/PSRule.Types/Definitions/SeverityLevel.cs new file mode 100644 index 0000000000..a929f8e687 --- /dev/null +++ b/src/PSRule.Types/Definitions/SeverityLevel.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// If the rule fails, how serious is the result. +/// +public enum SeverityLevel +{ + /// + /// Severity is unset. + /// + None = 0, + + /// + /// A failure generates an error. + /// + Error = 1, + + /// + /// A failure generates a warning. + /// + Warning = 2, + + /// + /// A failure generate an informational message. + /// + Information = 3 +} diff --git a/src/PSRule.Types/Definitions/SourceType.cs b/src/PSRule.Types/Definitions/SourceType.cs new file mode 100644 index 0000000000..85d2b909cd --- /dev/null +++ b/src/PSRule.Types/Definitions/SourceType.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// The type of source file. +/// +public enum SourceType +{ + /// + /// PowerShell script file. + /// + Script = 1, + + /// + /// YAML file. + /// + Yaml = 2, + + /// + /// JSON or JSON with comments file. + /// + Json = 3 +} diff --git a/src/PSRule/Commands/ExportConventionCommand.cs b/src/PSRule/Commands/ExportConventionCommand.cs index 8be732463a..45bcea1329 100644 --- a/src/PSRule/Commands/ExportConventionCommand.cs +++ b/src/PSRule/Commands/ExportConventionCommand.cs @@ -8,6 +8,8 @@ namespace PSRule.Commands; +#nullable enable + [Cmdlet(VerbsData.Export, RuleLanguageNouns.Convention)] internal sealed class ExportConventionCommand : LanguageBlock { @@ -26,34 +28,34 @@ internal sealed class ExportConventionCommand : LanguageBlock /// [Parameter(Mandatory = false)] [ValidateNotNullOrEmpty()] - public ScriptBlock Initialize { get; set; } + public ScriptBlock? Initialize { get; set; } /// /// A script block to call once per object before being processed by any rule. /// [Parameter(Mandatory = false)] [ValidateNotNullOrEmpty()] - public ScriptBlock Begin { get; set; } + public ScriptBlock? Begin { get; set; } /// /// A script block to call once per object after rules are processed. /// [Parameter(Mandatory = false, Position = 1)] [ValidateNotNullOrEmpty()] - public ScriptBlock Process { get; set; } + public ScriptBlock? Process { get; set; } /// /// A script block to call once after all rules and all objects are processed. /// [Parameter(Mandatory = false)] [ValidateNotNullOrEmpty()] - public ScriptBlock End { get; set; } + public ScriptBlock? End { get; set; } /// /// An optional pre-condition before the convention is evaluated. /// [Parameter(Mandatory = false)] - public ScriptBlock If { get; set; } + public ScriptBlock? If { get; set; } protected override void ProcessRecord() { @@ -61,15 +63,17 @@ protected override void ProcessRecord() // throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordScriptScope, LanguageKeywords.Rule)); var context = RunspaceContext.CurrentThread; + if (context == null) return; + + var source = context!.Source!.File; var errorPreference = GetErrorActionPreference(); - var commentMetadata = GetCommentMetadata(MyInvocation.ScriptName, MyInvocation.ScriptLineNumber, MyInvocation.OffsetInLine); - var source = context.Source.File; + var commentMetadata = GetCommentMetadata(source, MyInvocation.ScriptLineNumber, MyInvocation.OffsetInLine); var metadata = new ResourceMetadata { Name = Name }; var extent = new SourceExtent( - file: source.Path, + file: source, line: MyInvocation.ScriptLineNumber, position: MyInvocation.OffsetInLine ); @@ -95,7 +99,7 @@ protected override void ProcessRecord() WriteObject(block); } - private LanguageScriptBlock ConventionBlock(RunspaceContext context, ScriptBlock block, RunspaceScope scope) + private LanguageScriptBlock? ConventionBlock(RunspaceContext context, ScriptBlock block, RunspaceScope scope) { if (block == null) return null; @@ -109,3 +113,5 @@ private LanguageScriptBlock ConventionBlock(RunspaceContext context, ScriptBlock return new LanguageScriptBlock(ps); } } + +#nullable restore diff --git a/src/PSRule/Commands/LanguageBlock.cs b/src/PSRule/Commands/LanguageBlock.cs index 6aa2bda456..54e36ed645 100644 --- a/src/PSRule/Commands/LanguageBlock.cs +++ b/src/PSRule/Commands/LanguageBlock.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections; @@ -17,9 +17,9 @@ internal abstract class LanguageBlock : PSCmdlet { private const string ErrorActionParameter = "ErrorAction"; - protected static CommentMetadata GetCommentMetadata(string path, int lineNumber, int offset) + protected static CommentMetadata GetCommentMetadata(ISourceFile file, int lineNumber, int offset) { - return HostHelper.GetCommentMeta(path, lineNumber - 2, offset); + return HostHelper.GetCommentMeta(file, lineNumber - 2, offset); } protected static ResourceTags GetTag(Hashtable hashtable) diff --git a/src/PSRule/Commands/NewRuleDefinitionCommand.cs b/src/PSRule/Commands/NewRuleDefinitionCommand.cs index d7bd556fc6..7114b71187 100644 --- a/src/PSRule/Commands/NewRuleDefinitionCommand.cs +++ b/src/PSRule/Commands/NewRuleDefinitionCommand.cs @@ -107,13 +107,13 @@ protected override void ProcessRecord() throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordSourceScope, LanguageKeywords.Rule)); var context = RunspaceContext.CurrentThread; + var source = context.Source.File; var errorPreference = GetErrorActionPreference(); - var metadata = GetCommentMetadata(MyInvocation.ScriptName, MyInvocation.ScriptLineNumber, MyInvocation.OffsetInLine); + var metadata = GetCommentMetadata(source, MyInvocation.ScriptLineNumber, MyInvocation.OffsetInLine); var level = ResourceHelper.GetLevel(Level); var tag = GetTag(Tag); - var source = context.Source.File; var extent = new SourceExtent( - file: source.Path, + file: source, line: MyInvocation.ScriptLineNumber, position: MyInvocation.OffsetInLine ); @@ -151,7 +151,7 @@ protected override void ProcessRecord() WriteObject(block); } - private PowerShellCondition GetCondition(RunspaceContext context, ResourceId id, SourceFile source, ActionPreference errorAction) + private PowerShellCondition GetCondition(RunspaceContext context, ResourceId id, ISourceFile source, ActionPreference errorAction) { var result = context.GetPowerShell(); result.AddCommand(new CmdletInfo(CmdletName, typeof(InvokeRuleBlockCommand))); @@ -176,7 +176,7 @@ private void CheckDependsOn() } } - private ResourceId[] GetScopedSelectors(SourceFile source) + private ResourceId[] GetScopedSelectors(ISourceFile source) { return ResourceHelper.GetRuleId(source.Module, With, ResourceIdKind.Unknown); } diff --git a/src/PSRule/Common/JsonConverters.cs b/src/PSRule/Common/JsonConverters.cs index 8fe1a5d7f5..9be93130a4 100644 --- a/src/PSRule/Common/JsonConverters.cs +++ b/src/PSRule/Common/JsonConverters.cs @@ -388,7 +388,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s private IResource MapResource(JsonReader reader, JsonSerializer serializer) { - reader.GetSourceExtent(RunspaceContext.CurrentThread.Source.File.Path, out var extent); + reader.GetSourceExtent(RunspaceContext.CurrentThread.Source.File, out var extent); reader.SkipComments(out _); if (reader.TokenType != JsonToken.StartObject || !reader.Read()) throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed); diff --git a/src/PSRule/Common/JsonReaderExtensions.cs b/src/PSRule/Common/JsonReaderExtensions.cs index 68fc1061f3..e731b7d543 100644 --- a/src/PSRule/Common/JsonReaderExtensions.cs +++ b/src/PSRule/Common/JsonReaderExtensions.cs @@ -23,10 +23,10 @@ public static bool TryLineInfo(this JsonReader reader, out int lineNumber, out i return true; } - public static bool GetSourceExtent(this JsonReader reader, string file, out ISourceExtent extent) + public static bool GetSourceExtent(this JsonReader reader, ISourceFile file, out ISourceExtent extent) { extent = null; - if (string.IsNullOrEmpty(file) || !TryLineInfo(reader, out var lineNumber, out var linePosition)) + if (file == null || !TryLineInfo(reader, out var lineNumber, out var linePosition)) return false; extent = new SourceExtent(file, lineNumber, linePosition); diff --git a/src/PSRule/Common/SeverityLevelExtensions.cs b/src/PSRule/Common/SeverityLevelExtensions.cs index b86cd4be91..6644a8d998 100644 --- a/src/PSRule/Common/SeverityLevelExtensions.cs +++ b/src/PSRule/Common/SeverityLevelExtensions.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using PSRule.Definitions.Rules; +using PSRule.Definitions; namespace PSRule; diff --git a/src/PSRule/Common/YamlConverters.cs b/src/PSRule/Common/YamlConverters.cs index c78b981cc2..9d549b8dc5 100644 --- a/src/PSRule/Common/YamlConverters.cs +++ b/src/PSRule/Common/YamlConverters.cs @@ -22,6 +22,8 @@ namespace PSRule; +#nullable enable + /// /// A YAML converter that allows short and full notation of suppression rules. /// @@ -32,30 +34,30 @@ public bool Accepts(Type type) return type == typeof(SuppressionRule); } - public object ReadYaml(IParser parser, Type type) + public object? ReadYaml(IParser parser, Type type) { var result = new SuppressionRule(); if (parser.TryConsume(out _)) { var targetNames = new List(); - while (parser.TryConsume(out Scalar scalar)) + while (parser.TryConsume(out var scalar) && scalar != null) targetNames.Add(scalar.Value); - result.TargetName = targetNames.ToArray(); + result.TargetName = [.. targetNames]; parser.MoveNext(); } else if (parser.TryConsume(out _)) { - while (parser.TryConsume(out Scalar scalar)) + while (parser.TryConsume(out var scalar) && scalar != null) { var name = scalar.Value; if (name == "targetName" && parser.TryConsume(out _)) { var targetNames = new List(); - while (parser.TryConsume(out Scalar item)) + while (parser.TryConsume(out var item) && item != null) targetNames.Add(item.Value); - result.TargetName = targetNames.ToArray(); + result.TargetName = [.. targetNames]; parser.MoveNext(); } } @@ -64,7 +66,7 @@ public object ReadYaml(IParser parser, Type type) return result; } - public void WriteYaml(IEmitter emitter, object value, Type type) + public void WriteYaml(IEmitter emitter, object? value, Type type) { throw new NotImplementedException(); } @@ -80,12 +82,12 @@ public bool Accepts(Type type) return type == typeof(FieldMap); } - public object ReadYaml(IParser parser, Type type) + public object? ReadYaml(IParser parser, Type type) { var result = new FieldMap(); if (parser.TryConsume(out _)) { - while (parser.TryConsume(out Scalar scalar)) + while (parser.TryConsume(out var scalar) && scalar != null) { var fieldName = scalar.Value; if (parser.TryConsume(out _)) @@ -93,10 +95,12 @@ public object ReadYaml(IParser parser, Type type) var fields = new List(); while (!parser.Accept(out _)) { - if (parser.TryConsume(out scalar)) +#pragma warning disable IDE0001 + if (parser.TryConsume(out scalar) && scalar != null) fields.Add(scalar.Value); +#pragma warning restore } - result.Set(fieldName, fields.ToArray()); + result.Set(fieldName, [.. fields]); parser.Require(); parser.MoveNext(); } @@ -107,7 +111,7 @@ public object ReadYaml(IParser parser, Type type) return result; } - public void WriteYaml(IEmitter emitter, object value, Type type) + public void WriteYaml(IEmitter emitter, object? value, Type type) { if (type == typeof(FieldMap) && value == null) { @@ -142,10 +146,10 @@ public bool Accepts(Type type) return type == typeof(PSObject); } - public object ReadYaml(IParser parser, Type type) + public object? ReadYaml(IParser parser, Type type) { // Handle empty objects - if (parser.TryConsume(out Scalar scalar)) + if (parser.TryConsume(out var scalar) && scalar != null) { return PSObject.AsPSObject(scalar.Value); } @@ -153,24 +157,26 @@ public object ReadYaml(IParser parser, Type type) var result = new PSObject(); if (parser.TryConsume(out _)) { - while (parser.TryConsume(out scalar)) +#pragma warning disable IDE0001 + while (parser.TryConsume(out scalar) && scalar != null) { var name = scalar.Value; var property = ReadNoteProperty(parser, name) ?? throw new NotImplementedException(); result.Properties.Add(property); } +#pragma warning restore parser.Require(); parser.MoveNext(); } return result; } - public void WriteYaml(IEmitter emitter, object value, Type type) + public void WriteYaml(IEmitter emitter, object? value, Type type) { Map(emitter, value); } - private PSNoteProperty ReadNoteProperty(IParser parser, string name) + private PSNoteProperty? ReadNoteProperty(IParser parser, string name) { if (parser.TryConsume(out _)) { @@ -181,7 +187,7 @@ private PSNoteProperty ReadNoteProperty(IParser parser, string name) { values.Add(PSObject.AsPSObject(ReadYaml(parser, typeof(PSObject)))); } - else if (parser.TryConsume(out Scalar scalar)) + else if (parser.TryConsume(out var scalar)) { values.Add(PSObject.AsPSObject(scalar.Value)); } @@ -194,7 +200,7 @@ private PSNoteProperty ReadNoteProperty(IParser parser, string name) { return new PSNoteProperty(name, ReadYaml(parser, typeof(PSObject))); } - else if (parser.TryConsume(out Scalar scalar)) + else if (parser.TryConsume(out var scalar)) { return new PSNoteProperty(name, scalar.Value); } @@ -204,8 +210,10 @@ private PSNoteProperty ReadNoteProperty(IParser parser, string name) internal abstract class MappingTypeConverter { - protected void Map(IEmitter emitter, object value) + protected void Map(IEmitter emitter, object? value) { + if (value is null) return; + emitter.Emit(new MappingStart()); foreach (var kv in GetKV(value)) { @@ -270,7 +278,7 @@ private static IEnumerable> GetKV(object value) /// internal sealed class PSObjectYamlTypeResolver : INodeTypeResolver { - public bool Resolve(NodeEvent nodeEvent, ref Type currentType) + public bool Resolve(NodeEvent? nodeEvent, ref Type currentType) { if (nodeEvent is SequenceStart) { @@ -288,7 +296,7 @@ public bool Resolve(NodeEvent nodeEvent, ref Type currentType) internal sealed class PSOptionYamlTypeResolver : INodeTypeResolver { - public bool Resolve(NodeEvent nodeEvent, ref Type currentType) + public bool Resolve(NodeEvent? nodeEvent, ref Type currentType) { if (currentType == typeof(object) && nodeEvent is SequenceStart) { @@ -302,18 +310,11 @@ public bool Resolve(NodeEvent nodeEvent, ref Type currentType) /// /// A YAML type inspector to order properties alphabetically /// -internal sealed class OrderedPropertiesTypeInspector : TypeInspectorSkeleton +internal sealed class OrderedPropertiesTypeInspector(ITypeInspector innerTypeDescriptor) : TypeInspectorSkeleton { - private readonly ITypeInspector _InnerTypeDescriptor; - - public OrderedPropertiesTypeInspector(ITypeInspector innerTypeDescriptor) - { - _InnerTypeDescriptor = innerTypeDescriptor; - } - - public override IEnumerable GetProperties(Type type, object container) + public override IEnumerable GetProperties(Type type, object? container) { - return _InnerTypeDescriptor + return innerTypeDescriptor .GetProperties(type, container) .OrderBy(prop => prop.Name); } @@ -333,7 +334,7 @@ public FieldYamlTypeInspector() _NamingConvention = CamelCaseNamingConvention.Instance; } - public override IEnumerable GetProperties(Type type, object container) + public override IEnumerable GetProperties(Type type, object? container) { return GetPropertyDescriptor(type: type); } @@ -390,7 +391,7 @@ public Field(FieldInfo fieldInfo, ITypeResolver typeResolver, INamingConvention public Type Type => _FieldInfo.FieldType; - public Type TypeOverride { get; set; } + public Type? TypeOverride { get; set; } public int Order { get; set; } @@ -398,12 +399,12 @@ public Field(FieldInfo fieldInfo, ITypeResolver typeResolver, INamingConvention public ScalarStyle ScalarStyle { get; set; } - public void Write(object target, object value) + public void Write(object target, object? value) { throw new NotImplementedException(); } - public T GetCustomAttribute() where T : Attribute + public T? GetCustomAttribute() where T : Attribute { return _FieldInfo.GetCustomAttributes(typeof(T), true).OfType().FirstOrDefault(); } @@ -434,7 +435,7 @@ public Property(PropertyInfo propertyInfo, ITypeResolver typeResolver, INamingCo public Type Type => _PropertyInfo.PropertyType; - public Type TypeOverride { get; set; } + public Type? TypeOverride { get; set; } public int Order { get; set; } @@ -442,12 +443,12 @@ public Property(PropertyInfo propertyInfo, ITypeResolver typeResolver, INamingCo public ScalarStyle ScalarStyle { get; set; } - public T GetCustomAttribute() where T : Attribute + public T? GetCustomAttribute() where T : Attribute { return _PropertyInfo.GetCustomAttributes(typeof(T), true).OfType().FirstOrDefault(); } - public void Write(object target, object value) + public void Write(object target, object? value) { throw new NotImplementedException(); } @@ -480,11 +481,11 @@ public ResourceNodeDeserializer(INodeDeserializer next) _Factory = new SpecFactory(); } - bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object value) + bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value) { if (typeof(ResourceObject).IsAssignableFrom(expectedType)) { - var comment = HostHelper.GetCommentMeta(RunspaceContext.CurrentThread.Source.File.Path, reader.Current.Start.Line - 2, reader.Current.Start.Column); + var comment = reader.Current == null || RunspaceContext.CurrentThread == null ? null : HostHelper.GetCommentMeta(RunspaceContext.CurrentThread.Source?.File, reader.Current.Start.Line - 2, reader.Current.Start.Column); var resource = MapResource(reader, nestedObjectDeserializer, comment); value = new ResourceObject(resource); return true; @@ -495,16 +496,16 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, CommentMetadata comment) + private IResource? MapResource(IParser reader, Func nestedObjectDeserializer, CommentMetadata? comment) { - IResource result = null; - string apiVersion = null; - string kind = null; - ResourceMetadata metadata = null; - if (reader.TryConsume(out var mappingStart)) + IResource? result = null; + string? apiVersion = null; + string? kind = null; + ResourceMetadata? metadata = null; + if (reader.TryConsume(out var mappingStart) && mappingStart != null) { - var extent = new SourceExtent(RunspaceContext.CurrentThread.Source.File.Path, mappingStart.Start.Line, mappingStart.Start.Column); - while (reader.TryConsume(out Scalar scalar)) + var extent = new SourceExtent(RunspaceContext.CurrentThread!.Source!.File, mappingStart.Start.Line, mappingStart.Start.Column); + while (reader.TryConsume(out var scalar) && scalar != null) { // Read apiVersion if (TryApiVersion(reader, scalar, out var apiVersionValue)) @@ -522,7 +523,7 @@ private IResource MapResource(IParser reader, Func nested metadata = metadataValue; } // Read spec - else if (kind != null && TrySpec(reader, scalar, apiVersion, kind, nestedObjectDeserializer, metadata, comment, extent, out var resource)) + else if (kind != null && apiVersion != null && TrySpec(reader, scalar, apiVersion, kind, nestedObjectDeserializer, metadata, comment, extent, out var resource)) { result = resource; } @@ -537,7 +538,7 @@ private IResource MapResource(IParser reader, Func nested return result; } - private static bool TryApiVersion(IParser reader, Scalar scalar, out string apiVersion) + private static bool TryApiVersion(IParser reader, Scalar scalar, out string? apiVersion) { apiVersion = null; if (scalar.Value == FIELD_APIVERSION) @@ -548,7 +549,7 @@ private static bool TryApiVersion(IParser reader, Scalar scalar, out string apiV return false; } - private static bool TryKind(IParser reader, Scalar scalar, out string kind) + private static bool TryKind(IParser reader, Scalar scalar, out string? kind) { kind = null; if (scalar.Value == FIELD_KIND) @@ -559,7 +560,7 @@ private static bool TryKind(IParser reader, Scalar scalar, out string kind) return false; } - private bool TryMetadata(IParser reader, Scalar scalar, Func nestedObjectDeserializer, out ResourceMetadata metadata) + private bool TryMetadata(IParser reader, Scalar scalar, Func nestedObjectDeserializer, out ResourceMetadata? metadata) { metadata = null; if (scalar.Value != FIELD_METADATA) @@ -567,22 +568,22 @@ private bool TryMetadata(IParser reader, Scalar scalar, Func nestedObjectDeserializer, ResourceMetadata metadata, CommentMetadata comment, ISourceExtent extent, out IResource spec) + private bool TrySpec(IParser reader, Scalar scalar, string apiVersion, string kind, Func nestedObjectDeserializer, ResourceMetadata? metadata, CommentMetadata? comment, ISourceExtent extent, out IResource? spec) { spec = null; return scalar.Value == FIELD_SPEC && TryResource(reader, apiVersion, kind, nestedObjectDeserializer, metadata, comment, extent, out spec); } - private bool TryResource(IParser reader, string apiVersion, string kind, Func nestedObjectDeserializer, ResourceMetadata metadata, CommentMetadata comment, ISourceExtent extent, out IResource spec) + private bool TryResource(IParser reader, string apiVersion, string kind, Func nestedObjectDeserializer, ResourceMetadata? metadata, CommentMetadata? comment, ISourceExtent extent, out IResource? spec) { spec = null; if (_Factory.TryDescriptor(apiVersion, kind, out var descriptor) && reader.Current is MappingStart) @@ -590,7 +591,7 @@ private bool TryResource(IParser reader, string apiVersion, string kind, Func nestedObjectDeserializer, out object value) + bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value) { if (typeof(LanguageExpression).IsAssignableFrom(expectedType)) { @@ -632,9 +633,9 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func /// Map an operator. /// - private LanguageOperator MapOperator(string type, LanguageExpression.PropertyBag properties, LanguageExpression subselector, IParser reader, Func nestedObjectDeserializer) + private LanguageOperator? MapOperator(string type, LanguageExpression.PropertyBag? properties, LanguageExpression? subselector, IParser reader, Func nestedObjectDeserializer) { - if (TryExpression(reader, type, properties, nestedObjectDeserializer, out LanguageOperator result)) + if (TryExpression(reader, type, properties, nestedObjectDeserializer, out LanguageOperator? result) && result != null) { // If and Not if (reader.TryConsume(out _)) @@ -660,11 +661,11 @@ private LanguageOperator MapOperator(string type, LanguageExpression.PropertyBag return result; } - private LanguageCondition MapCondition(string type, LanguageExpression.PropertyBag properties, IParser reader, Func nestedObjectDeserializer) + private LanguageCondition? MapCondition(string type, LanguageExpression.PropertyBag properties, IParser reader, Func nestedObjectDeserializer) { - if (TryExpression(reader, type, null, nestedObjectDeserializer, out LanguageCondition result)) + if (TryExpression(reader, type, null, nestedObjectDeserializer, out LanguageCondition? result) && result != null) { - while (!reader.Accept(out MappingEnd end)) + while (!reader.Accept(out var end) && end != null) { MapProperty(properties, reader, nestedObjectDeserializer, out _, out _); } @@ -673,12 +674,15 @@ private LanguageCondition MapCondition(string type, LanguageExpression.PropertyB return result; } - private LanguageExpression MapExpression(IParser reader, Func nestedObjectDeserializer) + private LanguageExpression? MapExpression(IParser reader, Func nestedObjectDeserializer) { - LanguageExpression result = null; + LanguageExpression? result = null; var properties = new LanguageExpression.PropertyBag(); MapProperty(properties, reader, nestedObjectDeserializer, out var key, out var subselector); - if (key != null && TryCondition(key)) + if (key == null) + return null; + + if (TryCondition(key)) { result = MapCondition(key, properties, reader, nestedObjectDeserializer); } @@ -692,7 +696,7 @@ private LanguageExpression MapExpression(IParser reader, Func nestedObjectDeserializer) + private ExpressionFnOuter MapFunction(string type, IParser reader, Func nestedObjectDeserializer) { _FunctionBuilder.Push(); - string name = null; + string? name = null; while (!(reader.Accept(out _) || reader.Accept(out _))) { - if (reader.TryConsume(out var s)) + if (reader.TryConsume(out var s) && s != null) { if (name != null) { @@ -718,24 +722,18 @@ private ExpressionFnOuter MapFunction(string type, IParser reader, Func(out _)) + else if (reader.TryConsume(out _) && name != null) { var child = MapFunction(name, reader, nestedObjectDeserializer); - if (name != null) - { - _FunctionBuilder.Add(name, child); - name = null; - } + _FunctionBuilder.Add(name, child); + name = null; reader.Consume(); } - else if (reader.TryConsume(out _)) + else if (reader.TryConsume(out _) && name != null) { var sequence = MapSequence(name, reader, nestedObjectDeserializer); - if (name != null) - { - _FunctionBuilder.Add(name, sequence); - name = null; - } + _FunctionBuilder.Add(name, sequence); + name = null; reader.Consume(); } } @@ -743,7 +741,7 @@ private ExpressionFnOuter MapFunction(string type, IParser reader, Func nestedObjectDeserializer) + private object[] MapSequence(string name, IParser reader, Func nestedObjectDeserializer) { var result = new List(); while (!reader.Accept(out _)) @@ -758,23 +756,25 @@ private object[] MapSequence(string name, IParser reader, Func(); } } - return result.ToArray(); + return [.. result]; } - private void MapProperty(LanguageExpression.PropertyBag properties, IParser reader, Func nestedObjectDeserializer, out string name, out LanguageExpression subselector) + private void MapProperty(LanguageExpression.PropertyBag properties, IParser reader, Func nestedObjectDeserializer, out string? name, out LanguageExpression? subselector) { name = null; subselector = null; - while (reader.TryConsume(out Scalar scalar)) + while (reader.TryConsume(out var scalar) && scalar != null) { var key = scalar.Value; if (TryCondition(key) || TryOperator(key)) name = key; - if (reader.TryConsume(out scalar)) +#pragma warning disable IDE0001 + if (reader.TryConsume(out scalar) && scalar != null) { properties[key] = scalar.Value; } +#pragma warning restore IDE0001 // value: else if (TryValue(key, reader, nestedObjectDeserializer, out var value)) { @@ -790,10 +790,12 @@ private void MapProperty(LanguageExpression.PropertyBag properties, IParser read var objects = new List(); while (!reader.TryConsume(out _)) { - if (reader.TryConsume(out scalar)) +#pragma warning disable IDE0001 + if (reader.TryConsume(out scalar) && scalar != null) { objects.Add(scalar.Value); } +#pragma warning restore IDE0001 } properties[key] = objects.ToArray(); } @@ -821,7 +823,7 @@ private bool TryCondition(string key) return _Factory.IsCondition(key); } - private bool TryValue(string key, IParser reader, Func nestedObjectDeserializer, out object value) + private bool TryValue(string key, IParser reader, Func nestedObjectDeserializer, out object? value) { value = null; if (key != "value") @@ -836,7 +838,7 @@ private bool TryValue(string key, IParser reader, Func ne return false; } - private bool TryFunction(IParser reader, Func nestedObjectDeserializer, out ExpressionFnOuter fn) + private bool TryFunction(IParser reader, Func nestedObjectDeserializer, out ExpressionFnOuter? fn) { fn = null; if (!IsFunction(reader)) @@ -852,15 +854,15 @@ private bool TryFunction(IParser reader, Func nestedObjec private static bool IsFunction(IParser reader) { - return reader.Accept(out var scalar) || scalar.Value == "$"; + return reader.Accept(out var scalar) || scalar?.Value == "$"; } - private bool TryExpression(IParser reader, string type, LanguageExpression.PropertyBag properties, Func nestedObjectDeserializer, out T expression) where T : LanguageExpression + private bool TryExpression(IParser reader, string type, LanguageExpression.PropertyBag? properties, Func nestedObjectDeserializer, out T? expression) where T : LanguageExpression { expression = null; if (_Factory.TryDescriptor(type, out var descriptor)) { - expression = (T)descriptor.CreateInstance(RunspaceContext.CurrentThread.Source.File, properties); + expression = (T)descriptor.CreateInstance(RunspaceContext.CurrentThread!.Source!.File, properties); return expression != null; } return false; @@ -871,7 +873,7 @@ internal sealed class PSObjectYamlDeserializer : INodeDeserializer { private readonly INodeDeserializer _Next; private readonly PSObjectYamlTypeConverter _Converter; - private readonly IFileInfo _SourceInfo; + private readonly IFileInfo? _SourceInfo; public PSObjectYamlDeserializer(INodeDeserializer next, IFileInfo sourceInfo) { @@ -886,7 +888,7 @@ public PSObjectYamlDeserializer(INodeDeserializer next) _Converter = new PSObjectYamlTypeConverter(); } - bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object value) + bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value) { if (expectedType == typeof(PSObject[]) && reader.Current is MappingStart) { @@ -923,8 +925,6 @@ public TargetObjectYamlDeserializer(INodeDeserializer next) _Converter = new PSObjectYamlTypeConverter(); } -#nullable enable - bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value) { value = null; @@ -976,8 +976,6 @@ private bool TryGetTargetObject(IParser reader, out TargetObject? value) } return false; } - -#nullable disable } internal sealed class InfoStringYamlTypeConverter : IYamlTypeConverter @@ -987,15 +985,17 @@ public bool Accepts(Type type) return type == typeof(InfoString); } - public object ReadYaml(IParser parser, Type type) + public object? ReadYaml(IParser parser, Type type) { return parser.TryConsume(out var scalar) && !string.IsNullOrEmpty(scalar.Value) ? new InfoString(scalar.Value) : new InfoString(); } - public void WriteYaml(IEmitter emitter, object value, Type type) + public void WriteYaml(IEmitter emitter, object? value, Type type) { - if (value is InfoString info && info.HasValue) + if (value is InfoString info && info.HasValue && info.Text != null) emitter.Emit(new Scalar(info.Text)); } } + +#nullable restore diff --git a/src/PSRule/Configuration/PSRuleOption.cs b/src/PSRule/Configuration/PSRuleOption.cs index e8ff680a0b..e442dc74b5 100644 --- a/src/PSRule/Configuration/PSRuleOption.cs +++ b/src/PSRule/Configuration/PSRuleOption.cs @@ -6,6 +6,7 @@ using System.Management.Automation; using Newtonsoft.Json; using PSRule.Converters.Yaml; +using PSRule.Definitions; using PSRule.Definitions.Baselines; using PSRule.Pipeline; using PSRule.Resources; diff --git a/src/PSRule/Definitions/AnnotatedExtensions.cs b/src/PSRule/Definitions/AnnotatedExtensions.cs new file mode 100644 index 0000000000..f0a3f9b8ed --- /dev/null +++ b/src/PSRule/Definitions/AnnotatedExtensions.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +internal static class AnnotatedExtensions +{ + internal static TAnnotation RequireAnnotation(this IAnnotated annotated) where TAnnotation : T, new() + { + var result = annotated.GetAnnotation(); + return result == null ? new TAnnotation() : result; + } +} diff --git a/src/PSRule/Definitions/Conventions/BaseConvention.cs b/src/PSRule/Definitions/Conventions/BaseConvention.cs index eebb65badd..27ababf42b 100644 --- a/src/PSRule/Definitions/Conventions/BaseConvention.cs +++ b/src/PSRule/Definitions/Conventions/BaseConvention.cs @@ -48,19 +48,19 @@ private bool MatchWildcard(string name) [DebuggerDisplay("{Id}")] internal abstract class BaseConvention : IConvention { - protected BaseConvention(SourceFile source, string name) + protected BaseConvention(ISourceFile source, string name) { Source = source; Name = name; Id = new ResourceId(Source.Module, name, ResourceIdKind.Id); } - public SourceFile Source { get; } + public ISourceFile Source { get; } public ResourceId Id { get; } /// - /// The name of the convetion. + /// The name of the convention. /// public string Name { get; } diff --git a/src/PSRule/Definitions/Conventions/ScriptBlockConvention.cs b/src/PSRule/Definitions/Conventions/ScriptBlockConvention.cs index bca7f83336..379659d127 100644 --- a/src/PSRule/Definitions/Conventions/ScriptBlockConvention.cs +++ b/src/PSRule/Definitions/Conventions/ScriptBlockConvention.cs @@ -8,23 +8,25 @@ namespace PSRule.Definitions.Conventions; +#nullable enable + internal sealed class ScriptBlockConvention : BaseConvention, IDisposable, IResource { - private readonly LanguageScriptBlock _Initialize; - private readonly LanguageScriptBlock _Begin; - private readonly LanguageScriptBlock _Process; - private readonly LanguageScriptBlock _End; + private readonly LanguageScriptBlock? _Initialize; + private readonly LanguageScriptBlock? _Begin; + private readonly LanguageScriptBlock? _Process; + private readonly LanguageScriptBlock? _End; private bool _Disposed; internal ScriptBlockConvention( - SourceFile source, + ISourceFile source, ResourceMetadata metadata, ResourceHelpInfo info, - LanguageScriptBlock begin, - LanguageScriptBlock initialize, - LanguageScriptBlock process, - LanguageScriptBlock end, + LanguageScriptBlock? begin, + LanguageScriptBlock? initialize, + LanguageScriptBlock? process, + LanguageScriptBlock? end, ActionPreference errorPreference, ResourceFlags flags, ISourceExtent extent) @@ -53,13 +55,13 @@ internal ScriptBlockConvention( ResourceId? IResource.Ref => null; // Not supported with conventions. - ResourceId[] IResource.Alias => null; + ResourceId[]? IResource.Alias => null; // Not supported with conventions. - ResourceTags IResource.Tags => null; + IResourceTags? IResource.Tags => null; // Not supported with conventions. - ResourceLabels IResource.Labels => null; + IResourceLabels? IResource.Labels => null; public override void Initialize(RunspaceContext context, IEnumerable input) { @@ -81,7 +83,7 @@ public override void End(RunspaceContext context, IEnumerable input) InvokeConventionBlock(context, Source, _End, input); } - private static void InvokeConventionBlock(RunspaceContext context, SourceFile source, LanguageScriptBlock block, IEnumerable input) + private static void InvokeConventionBlock(RunspaceContext context, ISourceFile source, LanguageScriptBlock? block, IEnumerable input) { if (block == null) return; @@ -124,3 +126,5 @@ public void Dispose() #endregion IDisposable } + +#nullable restore diff --git a/src/PSRule/Definitions/Expressions/ExpressionContext.cs b/src/PSRule/Definitions/Expressions/ExpressionContext.cs index 6ce9922358..4587373e8d 100644 --- a/src/PSRule/Definitions/Expressions/ExpressionContext.cs +++ b/src/PSRule/Definitions/Expressions/ExpressionContext.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Diagnostics; -using PSRule.Pipeline; using PSRule.Runtime; using PSRule.Runtime.ObjectPath; @@ -14,7 +13,7 @@ internal sealed class ExpressionContext : IExpressionContext, IBindingContext private List _Reason; - internal ExpressionContext(RunspaceContext context, SourceFile source, ResourceKind kind, object current) + internal ExpressionContext(RunspaceContext context, ISourceFile source, ResourceKind kind, object current) { Context = context; Source = source; @@ -24,7 +23,7 @@ internal ExpressionContext(RunspaceContext context, SourceFile source, ResourceK Current = current; } - public SourceFile Source { get; } + public ISourceFile Source { get; } public string LanguageScope { get; } diff --git a/src/PSRule/Definitions/Expressions/IExpressionContext.cs b/src/PSRule/Definitions/Expressions/IExpressionContext.cs index 8f68ef7e54..3d33ca4af7 100644 --- a/src/PSRule/Definitions/Expressions/IExpressionContext.cs +++ b/src/PSRule/Definitions/Expressions/IExpressionContext.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using PSRule.Pipeline; using PSRule.Runtime; namespace PSRule.Definitions.Expressions; @@ -10,7 +9,7 @@ internal interface IExpressionContext : IBindingContext { ResourceKind Kind { get; } - SourceFile Source { get; } + ISourceFile Source { get; } string LanguageScope { get; } diff --git a/src/PSRule/Definitions/Expressions/Primitives.cs b/src/PSRule/Definitions/Expressions/Primitives.cs index fc9cbaea93..4a5b2f9563 100644 --- a/src/PSRule/Definitions/Expressions/Primitives.cs +++ b/src/PSRule/Definitions/Expressions/Primitives.cs @@ -12,7 +12,7 @@ internal interface ILanguageExpressionDescriptor LanguageExpressionType Type { get; } - LanguageExpression CreateInstance(SourceFile source, LanguageExpression.PropertyBag properties); + LanguageExpression CreateInstance(ISourceFile source, LanguageExpression.PropertyBag properties); } internal sealed class LanguageExpresssionDescriptor : ILanguageExpressionDescriptor @@ -30,7 +30,7 @@ public LanguageExpresssionDescriptor(string name, LanguageExpressionType type, L public LanguageExpressionFn Fn { get; } - public LanguageExpression CreateInstance(SourceFile source, LanguageExpression.PropertyBag properties) + public LanguageExpression CreateInstance(ISourceFile source, LanguageExpression.PropertyBag properties) { if (Type == LanguageExpressionType.Operator) return new LanguageOperator(this, properties); diff --git a/src/PSRule/Definitions/IAnnotated.cs b/src/PSRule/Definitions/IAnnotated.cs index 569694bb29..715522fa88 100644 --- a/src/PSRule/Definitions/IAnnotated.cs +++ b/src/PSRule/Definitions/IAnnotated.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. namespace PSRule.Definitions; @@ -9,12 +9,3 @@ internal interface IAnnotated void SetAnnotation(TAnnotation annotation) where TAnnotation : T; } - -internal static class AnnotatedExtensions -{ - internal static TAnnotation RequireAnnotation(this IAnnotated annotated) where TAnnotation : T, new() - { - var result = annotated.GetAnnotation(); - return result == null ? new TAnnotation() : result; - } -} diff --git a/src/PSRule/Definitions/ICondition.cs b/src/PSRule/Definitions/ICondition.cs index 64d50c4808..ba5a1dc181 100644 --- a/src/PSRule/Definitions/ICondition.cs +++ b/src/PSRule/Definitions/ICondition.cs @@ -5,27 +5,6 @@ namespace PSRule.Definitions; -/// -/// A result from an language condition. -/// -public interface IConditionResult -{ - /// - /// Determine if the condition had errors. - /// - bool HadErrors { get; } - - /// - /// The number of sub-conditions that were evaluated. - /// - int Count { get; } - - /// - /// The number of sub-conditions that passed. - /// - int Pass { get; } -} - /// /// A language condition. /// diff --git a/src/PSRule/Definitions/IConditionResult.cs b/src/PSRule/Definitions/IConditionResult.cs new file mode 100644 index 0000000000..58263aea5f --- /dev/null +++ b/src/PSRule/Definitions/IConditionResult.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// A result from an language condition. +/// +public interface IConditionResult +{ + /// + /// Determine if the condition had errors. + /// + bool HadErrors { get; } + + /// + /// The number of sub-conditions that were evaluated. + /// + int Count { get; } + + /// + /// The number of sub-conditions that passed. + /// + int Pass { get; } +} diff --git a/src/PSRule/Definitions/IDetailedRuleResultV2.cs b/src/PSRule/Definitions/IDetailedRuleResultV2.cs new file mode 100644 index 0000000000..404c369c8c --- /dev/null +++ b/src/PSRule/Definitions/IDetailedRuleResultV2.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; + +namespace PSRule.Definitions; + +/// +/// Detailed rule records for PSRule v2. +/// +public interface IDetailedRuleResultV2 : IRuleResultV2 +{ + /// + /// Custom data set by the rule for this target object. + /// + Hashtable Data { get; } + + /// + /// Detailed information about the rule result. + /// + IResultDetailV2 Detail { get; } + + /// + /// A set of custom fields bound for the target object. + /// + Hashtable Field { get; } + + /// + /// The bound name of the target. + /// + string TargetName { get; } + + /// + /// The bound type of the target. + /// + string TargetType { get; } +} diff --git a/src/PSRule/Definitions/IResultDetailV2.cs b/src/PSRule/Definitions/IResultDetailV2.cs new file mode 100644 index 0000000000..e25b105655 --- /dev/null +++ b/src/PSRule/Definitions/IResultDetailV2.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// Detailed information about the rule result. +/// +public interface IResultDetailV2 +{ + /// + /// Any reasons for the result. + /// + IEnumerable Reason { get; } +} diff --git a/src/PSRule/Definitions/IResultReasonV2.cs b/src/PSRule/Definitions/IResultReasonV2.cs new file mode 100644 index 0000000000..2f0c29287e --- /dev/null +++ b/src/PSRule/Definitions/IResultReasonV2.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// A reason for the rule result. +/// +public interface IResultReasonV2 +{ + /// + /// The object path that failed. + /// + string Path { get; } + + /// + /// The object path including the path of the parent object. + /// + string FullPath { get; } + + /// + /// The reason message. + /// + string Message { get; } + + /// + /// Return a formatted reason string. + /// + string Format(); +} diff --git a/src/PSRule/Definitions/IRuleResultV2.cs b/src/PSRule/Definitions/IRuleResultV2.cs index 412e06216c..8962f576f2 100644 --- a/src/PSRule/Definitions/IRuleResultV2.cs +++ b/src/PSRule/Definitions/IRuleResultV2.cs @@ -42,71 +42,3 @@ public interface IRuleResultV2 : IResultRecord /// long Time { get; } } - -/// -/// Detailed rule records for PSRule v2. -/// -public interface IDetailedRuleResultV2 : IRuleResultV2 -{ - /// - /// Custom data set by the rule for this target object. - /// - Hashtable Data { get; } - - /// - /// Detailed information about the rule result. - /// - IResultDetailV2 Detail { get; } - - /// - /// A set of custom fields bound for the target object. - /// - Hashtable Field { get; } - - /// - /// The bound name of the target. - /// - string TargetName { get; } - - /// - /// The bound type of the target. - /// - string TargetType { get; } -} - -/// -/// Detailed information about the rule result. -/// -public interface IResultDetailV2 -{ - /// - /// Any reasons for the result. - /// - IEnumerable Reason { get; } -} - -/// -/// A reason for the rule result. -/// -public interface IResultReasonV2 -{ - /// - /// The object path that failed. - /// - string Path { get; } - - /// - /// The object path including the path of the parent object. - /// - string FullPath { get; } - - /// - /// The reason message. - /// - string Message { get; } - - /// - /// Return a formatted reason string. - /// - string Format(); -} diff --git a/src/PSRule/Definitions/ISpecDescriptor.cs b/src/PSRule/Definitions/ISpecDescriptor.cs new file mode 100644 index 0000000000..1cfe30017c --- /dev/null +++ b/src/PSRule/Definitions/ISpecDescriptor.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Annotations; + +namespace PSRule.Definitions; + +internal interface ISpecDescriptor +{ + string Name { get; } + + string ApiVersion { get; } + + string FullName { get; } + + Type SpecType { get; } + + IResource CreateInstance(ISourceFile source, ResourceMetadata metadata, CommentMetadata comment, ISourceExtent extent, object spec); +} diff --git a/src/PSRule/Definitions/ISuppressionInfo.cs b/src/PSRule/Definitions/ISuppressionInfo.cs index 95c3cb4a0b..9885dbf234 100644 --- a/src/PSRule/Definitions/ISuppressionInfo.cs +++ b/src/PSRule/Definitions/ISuppressionInfo.cs @@ -14,16 +14,3 @@ internal interface ISuppressionInfo int Count { get; } } - -internal sealed class ISuppressionInfoComparer : IEqualityComparer -{ - public bool Equals(ISuppressionInfo x, ISuppressionInfo y) - { - return object.Equals(x, null) || object.Equals(y, null) ? object.Equals(x, y) : x.Equals(y); - } - - public int GetHashCode(ISuppressionInfo obj) - { - return obj.GetHashCode(); - } -} diff --git a/src/PSRule/Definitions/ISuppressionInfoComparer.cs b/src/PSRule/Definitions/ISuppressionInfoComparer.cs new file mode 100644 index 0000000000..600138f2d9 --- /dev/null +++ b/src/PSRule/Definitions/ISuppressionInfoComparer.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +internal sealed class ISuppressionInfoComparer : IEqualityComparer +{ + public bool Equals(ISuppressionInfo x, ISuppressionInfo y) + { + return object.Equals(x, null) || object.Equals(y, null) ? object.Equals(x, y) : x.Equals(y); + } + + public int GetHashCode(ISuppressionInfo obj) + { + return obj.GetHashCode(); + } +} diff --git a/src/PSRule/Definitions/InternalResource.cs b/src/PSRule/Definitions/InternalResource.cs index 2e84cca7d4..681d66c6ba 100644 --- a/src/PSRule/Definitions/InternalResource.cs +++ b/src/PSRule/Definitions/InternalResource.cs @@ -40,9 +40,9 @@ private protected InternalResource(ResourceKind kind, string apiVersion, SourceF // Not supported with base resources. ResourceId[] IResource.Alias => null; - ResourceTags IResource.Tags => Metadata.Tags; + IResourceTags IResource.Tags => Metadata.Tags; - ResourceLabels IResource.Labels => Metadata.Labels; + IResourceLabels IResource.Labels => Metadata.Labels; ResourceFlags IResource.Flags => Flags; diff --git a/src/PSRule/Definitions/Resource.cs b/src/PSRule/Definitions/Resource.cs index 3bf556648a..13f06ec343 100644 --- a/src/PSRule/Definitions/Resource.cs +++ b/src/PSRule/Definitions/Resource.cs @@ -52,7 +52,7 @@ protected internal Resource(ResourceKind kind, string apiVersion, SourceFile sou /// The file path where the resource is defined. /// [YamlIgnore()] - public SourceFile Source { get; } + public ISourceFile Source { get; } /// /// Information about the resource. diff --git a/src/PSRule/Definitions/ResourceBuilder.cs b/src/PSRule/Definitions/ResourceBuilder.cs index e438574916..2b1e52f7e6 100644 --- a/src/PSRule/Definitions/ResourceBuilder.cs +++ b/src/PSRule/Definitions/ResourceBuilder.cs @@ -22,6 +22,9 @@ internal ResourceBuilder() _Deserializer = new DeserializerBuilder() .IgnoreUnmatchedProperties() .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeMapping() + .WithTypeMapping() + .WithTypeMapping() .WithTypeConverter(new FieldMapYamlTypeConverter()) .WithTypeConverter(new StringArrayMapConverter()) .WithTypeConverter(new StringArrayConverter()) diff --git a/src/PSRule/Definitions/ResourceLabels.cs b/src/PSRule/Definitions/ResourceLabels.cs index a92f0b8fb2..f9e4766d80 100644 --- a/src/PSRule/Definitions/ResourceLabels.cs +++ b/src/PSRule/Definitions/ResourceLabels.cs @@ -5,10 +5,12 @@ namespace PSRule.Definitions; +#nullable enable + /// /// Additional resource taxonomy references. /// -public sealed class ResourceLabels : Dictionary +public sealed class ResourceLabels : Dictionary, IResourceLabels { /// /// Create an empty set of resource labels. @@ -18,7 +20,7 @@ public ResourceLabels() : base(StringComparer.OrdinalIgnoreCase) { } /// /// Convert from a hashtable to resource labels. /// - internal static ResourceLabels FromHashtable(Hashtable hashtable) + internal static ResourceLabels? FromHashtable(Hashtable hashtable) { if (hashtable == null || hashtable.Count == 0) return null; @@ -33,7 +35,8 @@ internal static ResourceLabels FromHashtable(Hashtable hashtable) return annotations; } - internal bool Contains(string key, string[] value) + /// + public bool Contains(string key, string[] value) { if (!TryGetValue(key, out var actual)) return false; @@ -49,3 +52,5 @@ internal bool Contains(string key, string[] value) return false; } } + +#nullable enable diff --git a/src/PSRule/Definitions/ResourceMetadata.cs b/src/PSRule/Definitions/ResourceMetadata.cs index 5eb4b735c3..6a05277e3b 100644 --- a/src/PSRule/Definitions/ResourceMetadata.cs +++ b/src/PSRule/Definitions/ResourceMetadata.cs @@ -8,7 +8,7 @@ namespace PSRule.Definitions; /// /// Additional resource metadata. /// -public sealed class ResourceMetadata +public sealed class ResourceMetadata : IResourceMetadata { /// /// Create an empty set of metadata. @@ -49,19 +49,19 @@ public ResourceMetadata() /// Any resource annotations. /// [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] - public ResourceAnnotations Annotations { get; set; } + public IResourceAnnotations Annotations { get; set; } /// /// Any resource tags. /// [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] - public ResourceTags Tags { get; set; } + public IResourceTags Tags { get; set; } /// /// Any taxonomy references. /// [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] - public ResourceLabels Labels { get; set; } + public IResourceLabels Labels { get; set; } /// /// A URL to documentation for the resource. diff --git a/src/PSRule/Definitions/ResourceTags.cs b/src/PSRule/Definitions/ResourceTags.cs index f5c3787d07..8a240a72f2 100644 --- a/src/PSRule/Definitions/ResourceTags.cs +++ b/src/PSRule/Definitions/ResourceTags.cs @@ -7,12 +7,14 @@ namespace PSRule.Definitions; +#nullable enable + /// /// Additional resource tags. /// -public sealed class ResourceTags : Dictionary +public sealed class ResourceTags : Dictionary, IResourceTags { - private Hashtable _Hashtable; + private Hashtable? _Hashtable; /// /// Create an empty set of resource tags. @@ -24,7 +26,7 @@ public ResourceTags() : base(StringComparer.OrdinalIgnoreCase) { } /// Convert from a hashtable to resource tags. /// [DebuggerStepThrough] - internal static ResourceTags FromHashtable(Hashtable hashtable) + internal static ResourceTags? FromHashtable(Hashtable hashtable) { if (hashtable == null || hashtable.Count == 0) return null; @@ -40,7 +42,7 @@ internal static ResourceTags FromHashtable(Hashtable hashtable) /// Convert from a dictionary of string pairs to resource tags. /// [DebuggerStepThrough] - internal static ResourceTags FromDictionary(Dictionary dictionary) + internal static ResourceTags? FromDictionary(Dictionary dictionary) { if (dictionary == null) return null; @@ -62,17 +64,15 @@ public Hashtable ToHashtable() return _Hashtable; } - /// - /// Check if a specific resource tag exists. - /// - internal bool Contains(object key, object value) + /// + public bool Contains(object key, object value) { if (key == null || value == null || key is not string k || !ContainsKey(k)) return false; if (TryArray(value, out var values)) { - for (var i = 0; i < values.Length; i++) + for (var i = 0; values != null && i < values.Length; i++) { if (Comparer.Equals(values[i], this[k])) return true; @@ -83,7 +83,7 @@ internal bool Contains(object key, object value) return v == "*" || Comparer.Equals(v, this[k]); } - private static bool TryArray(object o, out string[] values) + private static bool TryArray(object o, out string[]? values) { values = null; if (o is string[] sArray) @@ -127,3 +127,5 @@ public string ToViewString() return sb.ToString(); } } + +#nullable restore diff --git a/src/PSRule/Definitions/Rules/IRuleSpec.cs b/src/PSRule/Definitions/Rules/IRuleSpec.cs new file mode 100644 index 0000000000..b46f544ade --- /dev/null +++ b/src/PSRule/Definitions/Rules/IRuleSpec.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Definitions.Expressions; + +namespace PSRule.Definitions.Rules; + +/// +/// A specification for a rule resource. +/// +internal interface IRuleSpec +{ + /// + /// The of the rule condition that will be evaluated. + /// + LanguageIf Condition { get; } + + /// + /// If the rule fails, how serious is the result. + /// + SeverityLevel? Level { get; } + + /// + /// An optional type pre-condition before the rule is evaluated. + /// + string[] Type { get; } + + /// + /// An optional selector pre-condition before the rule is evaluated. + /// + string[] With { get; } + + /// + /// An optional sub-selector pre-condition before the rule is evaluated. + /// + LanguageIf Where { get; } +} diff --git a/src/PSRule/Definitions/Rules/IRuleV1.cs b/src/PSRule/Definitions/Rules/IRuleV1.cs new file mode 100644 index 0000000000..0e4e198064 --- /dev/null +++ b/src/PSRule/Definitions/Rules/IRuleV1.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions.Rules; + +/// +/// A rule resource V1. +/// +public interface IRuleV1 : IResource, IDependencyTarget +{ + /// + /// If the rule fails, how serious is the result. + /// + SeverityLevel Level { get; } + + /// + /// A recommendation for the rule. + /// + InfoString Recommendation { get; } + + /// + /// A short description of the rule. + /// + string Synopsis { get; } + + /// + /// Any additional tags assigned to the rule. + /// + IResourceTags Tag { get; } +} diff --git a/src/PSRule/Definitions/Rules/Rule.cs b/src/PSRule/Definitions/Rules/Rule.cs deleted file mode 100644 index a3b56cd6d1..0000000000 --- a/src/PSRule/Definitions/Rules/Rule.cs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Newtonsoft.Json; -using PSRule.Definitions.Expressions; -using PSRule.Pipeline; -using YamlDotNet.Serialization; - -namespace PSRule.Definitions.Rules; - -/// -/// If the rule fails, how serious is the result. -/// -public enum SeverityLevel -{ - /// - /// Severity is unset. - /// - None = 0, - - /// - /// A failure generates an error. - /// - Error = 1, - - /// - /// A failure generates a warning. - /// - Warning = 2, - - /// - /// A failure generate an informational message. - /// - Information = 3 -} - -/// -/// A rule resource V1. -/// -public interface IRuleV1 : IResource, IDependencyTarget -{ - /// - /// If the rule fails, how serious is the result. - /// - SeverityLevel Level { get; } - - /// - /// A recommendation for the rule. - /// - InfoString Recommendation { get; } - - /// - /// A short description of the rule. - /// - string Synopsis { get; } - - /// - /// Any additional tags assigned to the rule. - /// - ResourceTags Tag { get; } -} - -/// -/// A specification for a rule resource. -/// -internal interface IRuleSpec -{ - /// - /// The of the rule condition that will be evaluated. - /// - LanguageIf Condition { get; } - - /// - /// If the rule fails, how serious is the result. - /// - SeverityLevel? Level { get; } - - /// - /// An optional type pre-condition before the rule is evaluated. - /// - string[] Type { get; } - - /// - /// An optional selector pre-condition before the rule is evaluated. - /// - string[] With { get; } - - /// - /// An optional sub-selector pre-condition before the rule is evaluated. - /// - LanguageIf Where { get; } -} - -/// -/// A rule resource V1. -/// -[Spec(Specs.V1, Specs.Rule)] -internal sealed class RuleV1 : InternalResource, IResource, IRuleV1 -{ - internal const SeverityLevel DEFAULT_LEVEL = SeverityLevel.Error; - - public RuleV1(string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, RuleV1Spec spec) - : base(ResourceKind.Rule, apiVersion, source, metadata, info, extent, spec) - { - Ref = ResourceHelper.GetIdNullable(source.Module, metadata.Ref, ResourceIdKind.Ref); - Alias = ResourceHelper.GetRuleId(source.Module, metadata.Alias, ResourceIdKind.Alias); - Level = ResourceHelper.GetLevel(spec.Level); - } - - /// - [JsonIgnore] - [YamlIgnore] - public ResourceId? Ref { get; } - - /// - [JsonIgnore] - [YamlIgnore] - public ResourceId[] Alias { get; } - - /// - /// If the rule fails, how serious is the result. - /// - [JsonIgnore] - [YamlIgnore] - public SeverityLevel Level { get; } - - /// - /// A human readable block of text, used to identify the purpose of the rule. - /// - [JsonIgnore] - [YamlIgnore] - public string Synopsis => Info.Synopsis.Text; - - /// - ResourceId? IDependencyTarget.Ref => Ref; - - /// - ResourceId[] IDependencyTarget.Alias => Alias; - - // Not supported with resource rules. - ResourceId[] IDependencyTarget.DependsOn => Array.Empty(); - - /// - bool IDependencyTarget.Dependency => Source.IsDependency(); - - /// - ResourceId? IResource.Ref => Ref; - - /// - ResourceId[] IResource.Alias => Alias; - - /// - ResourceTags IRuleV1.Tag => Metadata.Tags; - - /// - InfoString IRuleV1.Recommendation => InfoString.Create(Spec?.Recommend); -} - -/// -/// A specification for a V1 rule resource. -/// -internal sealed class RuleV1Spec : Spec, IRuleSpec -{ - /// - public LanguageIf Condition { get; set; } - - /// - public SeverityLevel? Level { get; set; } - - /// - public string Recommend { get; set; } - - /// - public string[] Type { get; set; } - - /// - public string[] With { get; set; } - - /// - public LanguageIf Where { get; set; } -} diff --git a/src/PSRule/Definitions/Rules/RuleFilter.cs b/src/PSRule/Definitions/Rules/RuleFilter.cs index cd208f6fe8..57d07f7954 100644 --- a/src/PSRule/Definitions/Rules/RuleFilter.cs +++ b/src/PSRule/Definitions/Rules/RuleFilter.cs @@ -71,7 +71,7 @@ private bool IsExcluded(IEnumerable ids) return false; } - private bool IsIncluded(IEnumerable ids, ResourceTags tag, ResourceLabels labels) + private bool IsIncluded(IEnumerable ids, IResourceTags tag, IResourceLabels labels) { foreach (var id in ids) { @@ -81,7 +81,7 @@ private bool IsIncluded(IEnumerable ids, ResourceTags tag, ResourceL return false; } - private bool TagEquals(ResourceTags tag) + private bool TagEquals(IResourceTags tag) { if (Tag == null) return true; @@ -97,7 +97,7 @@ private bool TagEquals(ResourceTags tag) return true; } - private bool LabelEquals(ResourceLabels labels) + private bool LabelEquals(IResourceLabels labels) { if (Labels == null) return true; diff --git a/src/PSRule/Definitions/Rules/RuleV1.cs b/src/PSRule/Definitions/Rules/RuleV1.cs new file mode 100644 index 0000000000..bc86f12cdf --- /dev/null +++ b/src/PSRule/Definitions/Rules/RuleV1.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Newtonsoft.Json; +using PSRule.Pipeline; +using YamlDotNet.Serialization; + +namespace PSRule.Definitions.Rules; + +/// +/// A rule resource V1. +/// +[Spec(Specs.V1, Specs.Rule)] +internal sealed class RuleV1 : InternalResource, IResource, IRuleV1 +{ + // internal const SeverityLevel DEFAULT_LEVEL = SeverityLevel.Error; + + public RuleV1(string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, RuleV1Spec spec) + : base(ResourceKind.Rule, apiVersion, source, metadata, info, extent, spec) + { + Ref = ResourceHelper.GetIdNullable(source.Module, metadata.Ref, ResourceIdKind.Ref); + Alias = ResourceHelper.GetRuleId(source.Module, metadata.Alias, ResourceIdKind.Alias); + Level = ResourceHelper.GetLevel(spec.Level); + } + + /// + [JsonIgnore] + [YamlIgnore] + public ResourceId? Ref { get; } + + /// + [JsonIgnore] + [YamlIgnore] + public ResourceId[] Alias { get; } + + /// + /// If the rule fails, how serious is the result. + /// + [JsonIgnore] + [YamlIgnore] + public SeverityLevel Level { get; } + + /// + /// A human readable block of text, used to identify the purpose of the rule. + /// + [JsonIgnore] + [YamlIgnore] + public string Synopsis => Info.Synopsis.Text; + + /// + ResourceId? IDependencyTarget.Ref => Ref; + + /// + ResourceId[] IDependencyTarget.Alias => Alias; + + // Not supported with resource rules. + ResourceId[] IDependencyTarget.DependsOn => Array.Empty(); + + /// + bool IDependencyTarget.Dependency => Source.IsDependency(); + + /// + ResourceId? IResource.Ref => Ref; + + /// + ResourceId[] IResource.Alias => Alias; + + /// + IResourceTags IRuleV1.Tag => Metadata.Tags; + + /// + InfoString IRuleV1.Recommendation => InfoString.Create(Spec?.Recommend); +} diff --git a/src/PSRule/Definitions/Rules/RuleV1Spec.cs b/src/PSRule/Definitions/Rules/RuleV1Spec.cs new file mode 100644 index 0000000000..a288fa0981 --- /dev/null +++ b/src/PSRule/Definitions/Rules/RuleV1Spec.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Definitions.Expressions; + +namespace PSRule.Definitions.Rules; + +/// +/// A specification for a V1 rule resource. +/// +internal sealed class RuleV1Spec : Spec, IRuleSpec +{ + /// + public LanguageIf Condition { get; set; } + + /// + public SeverityLevel? Level { get; set; } + + /// + public string Recommend { get; set; } + + /// + public string[] Type { get; set; } + + /// + public string[] With { get; set; } + + /// + public LanguageIf Where { get; set; } +} diff --git a/src/PSRule/Definitions/Rules/RuleVisitor.cs b/src/PSRule/Definitions/Rules/RuleVisitor.cs index 2912c91dbf..2b2e253c05 100644 --- a/src/PSRule/Definitions/Rules/RuleVisitor.cs +++ b/src/PSRule/Definitions/Rules/RuleVisitor.cs @@ -19,7 +19,7 @@ internal sealed class RuleVisitor : ICondition private readonly LanguageExpressionOuterFn _Condition; private readonly RunspaceContext _Context; - public RuleVisitor(RunspaceContext context, ResourceId id, SourceFile source, IRuleSpec spec) + public RuleVisitor(RunspaceContext context, ResourceId id, ISourceFile source, IRuleSpec spec) { _Context = context; ErrorAction = ActionPreference.Stop; @@ -36,7 +36,7 @@ public RuleVisitor(RunspaceContext context, ResourceId id, SourceFile source, IR public Guid InstanceId { get; } - public SourceFile Source { get; } + public ISourceFile Source { get; } public ResourceId Id { get; } diff --git a/src/PSRule/Definitions/Selectors/ISelector.cs b/src/PSRule/Definitions/Selectors/ISelector.cs new file mode 100644 index 0000000000..e2f362640d --- /dev/null +++ b/src/PSRule/Definitions/Selectors/ISelector.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions.Selectors; + +internal interface ISelector : ILanguageBlock +{ + +} diff --git a/src/PSRule/Definitions/Selectors/SelectorVisitor.cs b/src/PSRule/Definitions/Selectors/SelectorVisitor.cs index 63b6f2bfc0..251acc731e 100644 --- a/src/PSRule/Definitions/Selectors/SelectorVisitor.cs +++ b/src/PSRule/Definitions/Selectors/SelectorVisitor.cs @@ -9,18 +9,13 @@ namespace PSRule.Definitions.Selectors; -internal interface ISelector : ILanguageBlock -{ - -} - [DebuggerDisplay("Id: {Id}")] internal sealed class SelectorVisitor : ISelector { private readonly LanguageExpressionOuterFn _Fn; private readonly RunspaceContext _Context; - public SelectorVisitor(RunspaceContext context, ResourceId id, SourceFile source, LanguageIf expression) + public SelectorVisitor(RunspaceContext context, ResourceId id, ISourceFile source, LanguageIf expression) { _Context = context; Id = id; @@ -34,7 +29,7 @@ public SelectorVisitor(RunspaceContext context, ResourceId id, SourceFile source public ResourceId Id { get; } - public SourceFile Source { get; } + public ISourceFile Source { get; } public bool Match(object o) { diff --git a/src/PSRule/Definitions/SourceExtent.cs b/src/PSRule/Definitions/SourceExtent.cs index e48adadcc8..795a631369 100644 --- a/src/PSRule/Definitions/SourceExtent.cs +++ b/src/PSRule/Definitions/SourceExtent.cs @@ -5,21 +5,21 @@ namespace PSRule.Definitions; internal sealed class SourceExtent : ISourceExtent { - internal SourceExtent(string file, int? line) + internal SourceExtent(ISourceFile file, int? line) : this(file, line, null) { File = file; Line = line; } - internal SourceExtent(string file, int? line, int? position) + internal SourceExtent(ISourceFile file, int? line, int? position) { File = file; Line = line; Position = position; } - public string File { get; } + public ISourceFile File { get; } public int? Line { get; } diff --git a/src/PSRule/Definitions/SpecAttribute.cs b/src/PSRule/Definitions/SpecAttribute.cs new file mode 100644 index 0000000000..dec059e1d6 --- /dev/null +++ b/src/PSRule/Definitions/SpecAttribute.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +internal sealed class SpecAttribute : Attribute +{ + public SpecAttribute() + { + + } + + public SpecAttribute(string apiVersion, string kind) + { + ApiVersion = apiVersion; + Kind = kind; + } + + public string ApiVersion { get; } + + public string Kind { get; } +} diff --git a/src/PSRule/Definitions/SpecDescriptor.cs b/src/PSRule/Definitions/SpecDescriptor.cs new file mode 100644 index 0000000000..b3bc80b93d --- /dev/null +++ b/src/PSRule/Definitions/SpecDescriptor.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Annotations; + +namespace PSRule.Definitions; + +internal sealed class SpecDescriptor : ISpecDescriptor where T : Resource, IResource where TSpec : Spec, new() +{ + public SpecDescriptor(string apiVersion, string name) + { + ApiVersion = apiVersion; + Name = name; + FullName = Spec.GetFullName(apiVersion, name); + } + + public string Name { get; } + + public string ApiVersion { get; } + + public string FullName { get; } + + public Type SpecType => typeof(TSpec); + + public IResource CreateInstance(ISourceFile source, ResourceMetadata metadata, CommentMetadata comment, ISourceExtent extent, object spec) + { + var info = new ResourceHelpInfo(metadata.Name, metadata.DisplayName, new InfoString(comment?.Synopsis), InfoString.Create(metadata.Description)); + return (IResource)Activator.CreateInstance(typeof(T), ApiVersion, source, metadata, info, extent, spec); + } +} diff --git a/src/PSRule/Definitions/SpecFactory.cs b/src/PSRule/Definitions/SpecFactory.cs index 185eacd2bb..a91f48b8f5 100644 --- a/src/PSRule/Definitions/SpecFactory.cs +++ b/src/PSRule/Definitions/SpecFactory.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using PSRule.Annotations; -using PSRule.Pipeline; - namespace PSRule.Definitions; internal sealed class SpecFactory @@ -34,59 +31,3 @@ private void With(ISpecDescriptor descriptor) _Descriptors.Add(descriptor.FullName, descriptor); } } - -[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] -internal sealed class SpecAttribute : Attribute -{ - public SpecAttribute() - { - - } - - public SpecAttribute(string apiVersion, string kind) - { - ApiVersion = apiVersion; - Kind = kind; - } - - public string ApiVersion { get; } - - public string Kind { get; } -} - -internal sealed class SpecDescriptor : ISpecDescriptor where T : Resource, IResource where TSpec : Spec, new() -{ - public SpecDescriptor(string apiVersion, string name) - { - ApiVersion = apiVersion; - Name = name; - FullName = Spec.GetFullName(apiVersion, name); - } - - public string Name { get; } - - public string ApiVersion { get; } - - public string FullName { get; } - - public Type SpecType => typeof(TSpec); - - public IResource CreateInstance(SourceFile source, ResourceMetadata metadata, CommentMetadata comment, ISourceExtent extent, object spec) - { - var info = new ResourceHelpInfo(metadata.Name, metadata.DisplayName, new InfoString(comment?.Synopsis), InfoString.Create(metadata.Description)); - return (IResource)Activator.CreateInstance(typeof(T), ApiVersion, source, metadata, info, extent, spec); - } -} - -internal interface ISpecDescriptor -{ - string Name { get; } - - string ApiVersion { get; } - - string FullName { get; } - - Type SpecType { get; } - - IResource CreateInstance(SourceFile source, ResourceMetadata metadata, CommentMetadata comment, ISourceExtent extent, object spec); -} diff --git a/src/PSRule/Definitions/SuppressionGroups/SuppressionGroupVisitor.cs b/src/PSRule/Definitions/SuppressionGroups/SuppressionGroupVisitor.cs index 14b9ef2906..b294dd8ef0 100644 --- a/src/PSRule/Definitions/SuppressionGroups/SuppressionGroupVisitor.cs +++ b/src/PSRule/Definitions/SuppressionGroups/SuppressionGroupVisitor.cs @@ -14,7 +14,7 @@ internal sealed class SuppressionGroupVisitor private readonly SuppressionInfo _Info; private readonly RunspaceContext _Context; - public SuppressionGroupVisitor(RunspaceContext context, ResourceId id, SourceFile source, ISuppressionGroupV1Spec spec, IResourceHelpInfo info) + public SuppressionGroupVisitor(RunspaceContext context, ResourceId id, ISourceFile source, ISuppressionGroupV1Spec spec, IResourceHelpInfo info) { _Context = context; Id = id; @@ -70,7 +70,7 @@ internal void Hit() public ResourceId Id { get; } - public SourceFile Source { get; } + public ISourceFile Source { get; } public Guid InstanceId { get; } diff --git a/src/PSRule/Help/FormatOptions.cs b/src/PSRule/Help/FormatOptions.cs new file mode 100644 index 0000000000..87ed6b6ccf --- /dev/null +++ b/src/PSRule/Help/FormatOptions.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Help; + +/// +/// Define options that determine how markdown will be rendered. +/// +[Flags()] +internal enum FormatOptions +{ + None = 0, + + /// + /// Add a line break after headers. + /// + LineBreak = 1 +} diff --git a/src/PSRule/Help/IHelpDocument.cs b/src/PSRule/Help/IHelpDocument.cs new file mode 100644 index 0000000000..5d89c01990 --- /dev/null +++ b/src/PSRule/Help/IHelpDocument.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Definitions; + +namespace PSRule.Help; + +internal interface IHelpDocument +{ + string Name { get; } + + InfoString Synopsis { get; set; } + + InfoString Description { get; set; } + + Link[] Links { get; set; } +} diff --git a/src/PSRule/Help/Link.cs b/src/PSRule/Help/Link.cs new file mode 100644 index 0000000000..a2a4ccd9d8 --- /dev/null +++ b/src/PSRule/Help/Link.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Help; + +/// +/// YAML link. +/// +internal sealed class Link +{ + public string Name; + + public string Uri; +} diff --git a/src/PSRule/Help/Models.cs b/src/PSRule/Help/Models.cs deleted file mode 100644 index b4041edd19..0000000000 --- a/src/PSRule/Help/Models.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using PSRule.Definitions; - -namespace PSRule.Help; - -/// -/// Define options that determine how markdown will be rendered. -/// -[Flags()] -internal enum FormatOptions -{ - None = 0, - - /// - /// Add a line break after headers. - /// - LineBreak = 1 -} - -/// -/// Markdown text content. -/// -internal sealed class TextBlock -{ - /// - /// The text of the section body. - /// - public readonly string Text; - - /// - /// Additional options that determine how the section will be formated when rendering markdown. - /// - public readonly FormatOptions FormatOption; - - public TextBlock(string text, FormatOptions formatOption = FormatOptions.None) - { - Text = text; - FormatOption = formatOption; - } - - public override string ToString() - { - return Text; - } -} - -/// -/// YAML link. -/// -internal sealed class Link -{ - public string Name; - - public string Uri; -} - -internal interface IHelpDocument -{ - string Name { get; } - - InfoString Synopsis { get; set; } - - InfoString Description { get; set; } - - Link[] Links { get; set; } -} - -internal sealed class RuleDocument : IHelpDocument -{ - public RuleDocument(string name) - { - Name = name; - } - - public string Name { get; } - - public InfoString Synopsis { get; set; } - - public InfoString Description { get; set; } - - public TextBlock Notes { get; set; } - - public InfoString Recommendation { get; set; } - - public Link[] Links { get; set; } - - public ResourceTags Annotations { get; set; } -} - -internal sealed class ResourceHelpDocument : IHelpDocument -{ - public ResourceHelpDocument(string name) - { - Name = name; - } - - public string Name { get; } - - public InfoString Synopsis { get; set; } - - public InfoString Description { get; set; } - - public Link[] Links { get; set; } - - internal IResourceHelpInfo ToInfo() - { - return new ResourceHelpInfo(Name, Name, Synopsis, Description); - } -} diff --git a/src/PSRule/Help/ResourceHelpDocument.cs b/src/PSRule/Help/ResourceHelpDocument.cs new file mode 100644 index 0000000000..0d99d08dc7 --- /dev/null +++ b/src/PSRule/Help/ResourceHelpDocument.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Definitions; + +namespace PSRule.Help; + +internal sealed class ResourceHelpDocument : IHelpDocument +{ + public ResourceHelpDocument(string name) + { + Name = name; + } + + public string Name { get; } + + public InfoString Synopsis { get; set; } + + public InfoString Description { get; set; } + + public Link[] Links { get; set; } + + internal IResourceHelpInfo ToInfo() + { + return new ResourceHelpInfo(Name, Name, Synopsis, Description); + } +} diff --git a/src/PSRule/Help/RuleDocument.cs b/src/PSRule/Help/RuleDocument.cs new file mode 100644 index 0000000000..b5b310a20e --- /dev/null +++ b/src/PSRule/Help/RuleDocument.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Definitions; + +namespace PSRule.Help; + +internal sealed class RuleDocument : IHelpDocument +{ + public RuleDocument(string name) + { + Name = name; + } + + public string Name { get; } + + public InfoString Synopsis { get; set; } + + public InfoString Description { get; set; } + + public TextBlock Notes { get; set; } + + public InfoString Recommendation { get; set; } + + public Link[] Links { get; set; } + + public ResourceTags Annotations { get; set; } +} diff --git a/src/PSRule/Help/TextBlock.cs b/src/PSRule/Help/TextBlock.cs new file mode 100644 index 0000000000..adfc56f43a --- /dev/null +++ b/src/PSRule/Help/TextBlock.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Help; + +/// +/// Markdown text content. +/// +internal sealed class TextBlock +{ + /// + /// The text of the section body. + /// + public readonly string Text; + + /// + /// Additional options that determine how the section will be formated when rendering markdown. + /// + public readonly FormatOptions FormatOption; + + public TextBlock(string text, FormatOptions formatOption = FormatOptions.None) + { + Text = text; + FormatOption = formatOption; + } + + public override string ToString() + { + return Text; + } +} diff --git a/src/PSRule/Host/HostHelper.cs b/src/PSRule/Host/HostHelper.cs index 2fdb215b7a..8fa94597cf 100644 --- a/src/PSRule/Host/HostHelper.cs +++ b/src/PSRule/Host/HostHelper.cs @@ -102,7 +102,7 @@ internal static IEnumerable ImportResource(Source[] source, Runs /// /// Called from PowerShell to get additional metdata from a language block, such as comment help. /// - internal static CommentMetadata GetCommentMeta(string path, int lineNumber, int offset) + internal static CommentMetadata GetCommentMeta(ISourceFile file, int lineNumber, int offset) { var context = RunspaceContext.CurrentThread; if (lineNumber < 0 || RunspaceContext.CurrentThread.IsScope(RunspaceScope.None) || context.Source.SourceContentCache == null) @@ -251,6 +251,9 @@ private static ILanguageBlock[] GetYamlLanguageBlocks(Source[] sources, Runspace var d = new DeserializerBuilder() .IgnoreUnmatchedProperties() .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeMapping() + .WithTypeMapping() + .WithTypeMapping() .WithTypeConverter(new FieldMapYamlTypeConverter()) .WithTypeConverter(new StringArrayMapConverter()) .WithTypeConverter(new StringArrayConverter()) diff --git a/src/PSRule/PSRule.csproj b/src/PSRule/PSRule.csproj index 9db7c925ec..839187850d 100644 --- a/src/PSRule/PSRule.csproj +++ b/src/PSRule/PSRule.csproj @@ -8,6 +8,7 @@ true false README.md + diff --git a/src/PSRule/Pipeline/AssertPipelineBuilder.cs b/src/PSRule/Pipeline/AssertPipelineBuilder.cs index de0a7999a4..9e6a95892b 100644 --- a/src/PSRule/Pipeline/AssertPipelineBuilder.cs +++ b/src/PSRule/Pipeline/AssertPipelineBuilder.cs @@ -3,7 +3,7 @@ using System.Management.Automation; using PSRule.Configuration; -using PSRule.Definitions.Rules; +using PSRule.Definitions; using PSRule.Pipeline.Formatters; using PSRule.Pipeline.Output; using PSRule.Resources; diff --git a/src/PSRule/Pipeline/DefaultPipelineResult.cs b/src/PSRule/Pipeline/DefaultPipelineResult.cs index d2a4cfe324..49491d4f83 100644 --- a/src/PSRule/Pipeline/DefaultPipelineResult.cs +++ b/src/PSRule/Pipeline/DefaultPipelineResult.cs @@ -1,7 +1,7 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using PSRule.Definitions.Rules; +using PSRule.Definitions; using PSRule.Options; namespace PSRule.Pipeline; diff --git a/src/PSRule/Pipeline/Dependencies/LockFile.cs b/src/PSRule/Pipeline/Dependencies/LockFile.cs index 12464c59f1..892028bdc1 100644 --- a/src/PSRule/Pipeline/Dependencies/LockFile.cs +++ b/src/PSRule/Pipeline/Dependencies/LockFile.cs @@ -6,6 +6,8 @@ namespace PSRule.Pipeline.Dependencies; +#nullable enable + /// /// Define the structure for the PSRule lock file. /// By default, this file is ps-rule.lock.json. @@ -73,3 +75,5 @@ public void Write(string? path) File.WriteAllText(path, json, Encoding.UTF8); } } + +#nullable restore diff --git a/src/PSRule/Pipeline/Emitters/YamlEmitter.cs b/src/PSRule/Pipeline/Emitters/YamlEmitter.cs index d77cc9f737..ccf2fd8466 100644 --- a/src/PSRule/Pipeline/Emitters/YamlEmitter.cs +++ b/src/PSRule/Pipeline/Emitters/YamlEmitter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using PSRule.Data; +using PSRule.Definitions; using PSRule.Emitters; using PSRule.Runtime; using YamlDotNet.Core; @@ -114,6 +115,9 @@ private IDeserializer GetDeserializer() return new DeserializerBuilder() .IgnoreUnmatchedProperties() .WithTypeConverter(_TypeConverter) + .WithTypeMapping() + .WithTypeMapping() + .WithTypeMapping() .WithNodeDeserializer( inner => new TargetObjectYamlDeserializer(inner), s => s.InsteadOf()) diff --git a/src/PSRule/Pipeline/Formatters/AzurePipelinesFormatter.cs b/src/PSRule/Pipeline/Formatters/AzurePipelinesFormatter.cs index c6b1a17379..83847d78e2 100644 --- a/src/PSRule/Pipeline/Formatters/AzurePipelinesFormatter.cs +++ b/src/PSRule/Pipeline/Formatters/AzurePipelinesFormatter.cs @@ -50,13 +50,13 @@ protected override void FailDetail(RuleRecord record) { base.FailDetail(record); var message = GetFailMessage(record); - if (record.Level == Definitions.Rules.SeverityLevel.Error) + if (record.Level == Definitions.SeverityLevel.Error) Error(message); - if (record.Level == Definitions.Rules.SeverityLevel.Warning) + if (record.Level == Definitions.SeverityLevel.Warning) Warning(message); - if (record.Level != Definitions.Rules.SeverityLevel.Information) + if (record.Level != Definitions.SeverityLevel.Information) LineBreak(); } } diff --git a/src/PSRule/Pipeline/Formatters/GitHubActionsFormatter.cs b/src/PSRule/Pipeline/Formatters/GitHubActionsFormatter.cs index e1baf01fa9..9a95edcfe2 100644 --- a/src/PSRule/Pipeline/Formatters/GitHubActionsFormatter.cs +++ b/src/PSRule/Pipeline/Formatters/GitHubActionsFormatter.cs @@ -52,13 +52,13 @@ protected override void FailDetail(RuleRecord record) { base.FailDetail(record); var message = GetFailMessage(record); - if (record.Level == Definitions.Rules.SeverityLevel.Error) + if (record.Level == Definitions.SeverityLevel.Error) Error(message); - if (record.Level == Definitions.Rules.SeverityLevel.Warning) + if (record.Level == Definitions.SeverityLevel.Warning) Warning(message); - if (record.Level == Definitions.Rules.SeverityLevel.Information) + if (record.Level == Definitions.SeverityLevel.Information) Information(message); LineBreak(); diff --git a/src/PSRule/Pipeline/IPipeline.cs b/src/PSRule/Pipeline/IPipeline.cs index c0de633715..d16fb21662 100644 --- a/src/PSRule/Pipeline/IPipeline.cs +++ b/src/PSRule/Pipeline/IPipeline.cs @@ -1,10 +1,12 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Management.Automation; namespace PSRule.Pipeline; +#nullable enable + /// /// An instance of a PSRule pipeline. /// @@ -24,7 +26,7 @@ public interface IPipeline : IDisposable /// Process an object through the pipeline. Each object will be processed by rules that apply based on pre-conditions. /// /// The object to process. - void Process(PSObject sourceObject); + void Process(PSObject? sourceObject); /// /// Clean up and flush pipeline results. Call this method once after processing any objects through the pipeline. @@ -32,3 +34,5 @@ public interface IPipeline : IDisposable [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "Matches PowerShell pipeline.")] void End(); } + +#nullable restore diff --git a/src/PSRule/Pipeline/InvokePipelineBuilderBase.cs b/src/PSRule/Pipeline/InvokePipelineBuilderBase.cs index 3ec3ab1b16..fc35064dcb 100644 --- a/src/PSRule/Pipeline/InvokePipelineBuilderBase.cs +++ b/src/PSRule/Pipeline/InvokePipelineBuilderBase.cs @@ -1,7 +1,8 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using PSRule.Configuration; +using PSRule.Definitions; using PSRule.Host; using PSRule.Options; diff --git a/src/PSRule/Pipeline/InvokeResult.cs b/src/PSRule/Pipeline/InvokeResult.cs index a95a27b132..1abb4bce83 100644 --- a/src/PSRule/Pipeline/InvokeResult.cs +++ b/src/PSRule/Pipeline/InvokeResult.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using PSRule.Definitions.Rules; +using PSRule.Definitions; using PSRule.Rules; namespace PSRule.Pipeline; diff --git a/src/PSRule/Pipeline/OptionContextBuilder.cs b/src/PSRule/Pipeline/OptionContextBuilder.cs index c3cc8177f9..cb086c239c 100644 --- a/src/PSRule/Pipeline/OptionContextBuilder.cs +++ b/src/PSRule/Pipeline/OptionContextBuilder.cs @@ -52,7 +52,7 @@ internal OptionContextBuilder(PSRuleOption option, string[] include = null, Hash /// internal OptionContext Build(string languageScope) { - languageScope = LanguageScope.Normalize(languageScope); + languageScope = ResourceHelper.NormalizeScope(languageScope); var context = new OptionContext(); _Scopes.Sort(_Comparer); @@ -119,7 +119,7 @@ internal void ModuleConfig(string module, ModuleConfigV1Spec spec) private static bool ShouldCombine(string languageScope, OptionScope optionScope) { - return optionScope.LanguageScope == LanguageScope.STANDALONE_SCOPENAME || optionScope.LanguageScope == languageScope || optionScope.Type == ScopeType.Explicit; + return optionScope.LanguageScope == ResourceHelper.STANDALONE_SCOPENAME || optionScope.LanguageScope == languageScope || optionScope.Type == ScopeType.Explicit; } /// diff --git a/src/PSRule/Pipeline/OptionScope.cs b/src/PSRule/Pipeline/OptionScope.cs index 58abe492d4..8140081227 100644 --- a/src/PSRule/Pipeline/OptionScope.cs +++ b/src/PSRule/Pipeline/OptionScope.cs @@ -46,7 +46,7 @@ internal class OptionScope private OptionScope(ScopeType type, string languageScope) { Type = type; - LanguageScope = Runtime.LanguageScope.Normalize(languageScope); + LanguageScope = ResourceHelper.NormalizeScope(languageScope); } public Options.BaselineOption Baseline { get; set; } diff --git a/src/PSRule/Pipeline/PipelineContext.cs b/src/PSRule/Pipeline/PipelineContext.cs index 47899809ac..1e46fa7654 100644 --- a/src/PSRule/Pipeline/PipelineContext.cs +++ b/src/PSRule/Pipeline/PipelineContext.cs @@ -98,9 +98,9 @@ public static PipelineContext New(PSRuleOption option, IHostContext hostContext, internal sealed class SourceScope { - public readonly SourceFile File; + public readonly ISourceFile File; - public SourceScope(SourceFile source) + public SourceScope(ISourceFile source) { File = source; } diff --git a/src/PSRule/Pipeline/Source.cs b/src/PSRule/Pipeline/Source.cs index b115b09670..5d6b0fd913 100644 --- a/src/PSRule/Pipeline/Source.cs +++ b/src/PSRule/Pipeline/Source.cs @@ -2,100 +2,12 @@ // Licensed under the MIT License. using System.Collections; -using System.Diagnostics; using System.Management.Automation; -using Newtonsoft.Json; +using PSRule.Definitions; using PSRule.Runtime; -using YamlDotNet.Serialization; namespace PSRule.Pipeline; -/// -/// The type of source file. -/// -public enum SourceType -{ - /// - /// PowerShell script file. - /// - Script = 1, - - /// - /// YAML file. - /// - Yaml = 2, - - /// - /// JSON or JSON with comments file. - /// - Json = 3 -} - -/// -/// A source file containing resources that will be loaded and interpreted by PSRule. -/// -[DebuggerDisplay("{Type}: {Path}")] -public sealed class SourceFile -{ - private bool? _Exists; - - internal Source Source; - - /// - /// Create an instance of a PSRule source. - /// - /// The file path to the source. - /// The name of the module if the source was loaded from a module. - /// The type of source file. - /// The base path to use for loading help content. - public SourceFile(string path, string module, SourceType type, string helpPath) - { - Path = path; - Module = module; - Type = type; - HelpPath = helpPath; - } - - /// - /// The file path to the source. - /// - [JsonProperty(PropertyName = "path")] - public string Path { get; } - - /// - /// The name of the module if the source was loaded from a module. - /// - [JsonProperty(PropertyName = "moduleName")] - public string Module { get; } - - /// - /// The type of source file. - /// - [YamlIgnore] - [JsonIgnore] - public SourceType Type { get; } - - /// - /// The base path to use for loading help content. - /// - [YamlIgnore] - [JsonIgnore] - public string HelpPath { get; } - - internal bool Exists() - { - if (!_Exists.HasValue) - _Exists = File.Exists(Path); - - return _Exists.Value; - } - - internal bool IsDependency() - { - return Source.Dependency; - } -} - /// /// A PSRule source containing one or more source files. /// @@ -186,7 +98,7 @@ internal string Scope { get { - return LanguageScope.Normalize(Module?.Name); + return ResourceHelper.NormalizeScope(Module?.Name); } } diff --git a/src/PSRule/Pipeline/SourceFile.cs b/src/PSRule/Pipeline/SourceFile.cs new file mode 100644 index 0000000000..e20c355f32 --- /dev/null +++ b/src/PSRule/Pipeline/SourceFile.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; +using Newtonsoft.Json; +using PSRule.Definitions; +using YamlDotNet.Serialization; + +namespace PSRule.Pipeline; + +/// +/// A source file containing resources that will be loaded and interpreted by PSRule. +/// +[DebuggerDisplay("{Type}: {Path}")] +public sealed class SourceFile : ISourceFile +{ + private bool? _Exists; + + internal Source Source; + + /// + /// Create an instance of a PSRule source. + /// + /// The file path to the source. + /// The name of the module if the source was loaded from a module. + /// The type of source file. + /// The base path to use for loading help content. + public SourceFile(string path, string module, SourceType type, string helpPath) + { + Path = path; + Module = module; + Type = type; + HelpPath = helpPath; + } + + /// + /// The file path to the source. + /// + [JsonProperty(PropertyName = "path")] + public string Path { get; } + + /// + /// The name of the module if the source was loaded from a module. + /// + [JsonProperty(PropertyName = "moduleName")] + public string Module { get; } + + /// + /// The type of source file. + /// + [YamlIgnore] + [JsonIgnore] + public SourceType Type { get; } + + /// + /// The base path to use for loading help content. + /// + [YamlIgnore] + [JsonIgnore] + public string HelpPath { get; } + + /// + public bool Exists() + { + if (!_Exists.HasValue) + _Exists = File.Exists(Path); + + return _Exists.Value; + } + + /// + public bool IsDependency() + { + return Source.Dependency; + } +} diff --git a/src/PSRule/Pipeline/SourcePipelineBuilder.cs b/src/PSRule/Pipeline/SourcePipelineBuilder.cs index 13beee58c2..57f3eac540 100644 --- a/src/PSRule/Pipeline/SourcePipelineBuilder.cs +++ b/src/PSRule/Pipeline/SourcePipelineBuilder.cs @@ -5,6 +5,7 @@ using System.Management.Automation; using System.Reflection; using PSRule.Configuration; +using PSRule.Definitions; using PSRule.Options; using PSRule.Pipeline.Output; using PSRule.Resources; diff --git a/src/PSRule/Rules/PowerShellCondition.cs b/src/PSRule/Rules/PowerShellCondition.cs index 2752e2ccdb..bcf96ca91a 100644 --- a/src/PSRule/Rules/PowerShellCondition.cs +++ b/src/PSRule/Rules/PowerShellCondition.cs @@ -16,7 +16,7 @@ internal sealed class PowerShellCondition : ICondition private bool _Disposed; - internal PowerShellCondition(ResourceId id, SourceFile source, PowerShell condition, ActionPreference errorAction) + internal PowerShellCondition(ResourceId id, ISourceFile source, PowerShell condition, ActionPreference errorAction) { _Condition = condition; Id = id; @@ -26,7 +26,7 @@ internal PowerShellCondition(ResourceId id, SourceFile source, PowerShell condit public ResourceId Id { get; } - public SourceFile Source { get; } + public ISourceFile Source { get; } public ActionPreference ErrorAction { get; } @@ -47,7 +47,7 @@ public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); - System.GC.SuppressFinalize(this); + GC.SuppressFinalize(this); } public IConditionResult If() diff --git a/src/PSRule/Rules/Rule.cs b/src/PSRule/Rules/Rule.cs index 6007b33f12..417fdc35c7 100644 --- a/src/PSRule/Rules/Rule.cs +++ b/src/PSRule/Rules/Rule.cs @@ -6,7 +6,6 @@ using PSRule.Data; using PSRule.Definitions; using PSRule.Definitions.Rules; -using PSRule.Pipeline; using YamlDotNet.Serialization; namespace PSRule.Rules; @@ -46,7 +45,7 @@ public sealed class Rule : IDependencyTarget, ITargetInfo, IResource, IRuleV1 /// [JsonProperty(PropertyName = "tag")] [DefaultValue(null)] - public ResourceTags Tag { get; set; } + public IResourceTags Tag { get; set; } /// [JsonProperty(PropertyName = "info")] @@ -56,7 +55,7 @@ public sealed class Rule : IDependencyTarget, ITargetInfo, IResource, IRuleV1 /// [JsonProperty(PropertyName = "source")] [DefaultValue(null)] - public SourceFile Source { get; set; } + public ISourceFile Source { get; set; } /// /// Other rules that must completed successfully before calling this rule. @@ -74,7 +73,7 @@ public sealed class Rule : IDependencyTarget, ITargetInfo, IResource, IRuleV1 /// [JsonIgnore, YamlIgnore] - public ResourceLabels Labels { get; set; } + public IResourceLabels Labels { get; set; } string ITargetInfo.TargetName => Name; @@ -92,7 +91,7 @@ public sealed class Rule : IDependencyTarget, ITargetInfo, IResource, IRuleV1 IResourceHelpInfo IResource.Info => Info; - ResourceTags IResource.Tags => Tag; + IResourceTags IResource.Tags => Tag; InfoString IRuleV1.Recommendation => ((IRuleHelpInfoV2)Info)?.Recommendation; diff --git a/src/PSRule/Rules/RuleBlock.cs b/src/PSRule/Rules/RuleBlock.cs index 45e4bb49ef..0dc24a435d 100644 --- a/src/PSRule/Rules/RuleBlock.cs +++ b/src/PSRule/Rules/RuleBlock.cs @@ -12,6 +12,8 @@ namespace PSRule.Rules; +#nullable enable + internal delegate bool RulePrecondition(); internal delegate RuleConditionResult RuleCondition(); @@ -22,7 +24,7 @@ namespace PSRule.Rules; [DebuggerDisplay("{Id} @{Source.Path}")] internal sealed class RuleBlock : ILanguageBlock, IDependencyTarget, IDisposable, IResource, IRuleV1 { - internal RuleBlock(SourceFile source, ResourceId id, ResourceId? @ref, SeverityLevel level, RuleHelpInfo info, ICondition condition, ResourceTags tag, ResourceId[] alias, ResourceId[] dependsOn, Hashtable configuration, ISourceExtent extent, ResourceFlags flags, ResourceLabels labels) + internal RuleBlock(ISourceFile source, ResourceId id, ResourceId? @ref, SeverityLevel level, RuleHelpInfo info, ICondition condition, IResourceTags tag, ResourceId[] alias, ResourceId[] dependsOn, Hashtable configuration, ISourceExtent extent, ResourceFlags flags, IResourceLabels labels) { Source = source; Name = id.Name; @@ -52,7 +54,7 @@ internal RuleBlock(SourceFile source, ResourceId id, ResourceId? @ref, SeverityL public ResourceId? Ref { get; } /// - public ResourceId[] Alias { get; } + public ResourceId[]? Alias { get; } /// /// If the rule fails, how serious is the result. @@ -79,10 +81,10 @@ internal RuleBlock(SourceFile source, ResourceId id, ResourceId? @ref, SeverityL /// /// Tags assigned to block. Tags are additional metadata used to select rules to execute and identify results. /// - public readonly ResourceTags Tag; + public readonly IResourceTags Tag; /// - public ResourceLabels Labels { get; } + public IResourceLabels? Labels { get; } /// /// Configuration defaults for the rule definition. @@ -95,7 +97,7 @@ internal RuleBlock(SourceFile source, ResourceId id, ResourceId? @ref, SeverityL public readonly RuleHelpInfo Info; /// - public SourceFile Source { get; } + public ISourceFile Source { get; } /// public ISourceExtent Extent { get; } @@ -115,15 +117,15 @@ internal RuleBlock(SourceFile source, ResourceId id, ResourceId? @ref, SeverityL string IResource.Name => Name; - ResourceTags IResource.Tags => Tag; + IResourceTags? IResource.Tags => Tag; IResourceHelpInfo IResource.Info => Info; - ResourceTags IRuleV1.Tag => Tag; + IResourceTags IRuleV1.Tag => Tag; string IRuleV1.Synopsis => Info.Synopsis; - InfoString IRuleV1.Recommendation => ((IRuleHelpInfoV2)Info)?.Recommendation; + InfoString IRuleV1.Recommendation => ((IRuleHelpInfoV2)Info)!.Recommendation; #region IDisposable @@ -134,3 +136,5 @@ public void Dispose() #endregion IDisposable } + +#nullable restore diff --git a/src/PSRule/Rules/RuleRecord.cs b/src/PSRule/Rules/RuleRecord.cs index a6ed9e00fd..6a71cc53f9 100644 --- a/src/PSRule/Rules/RuleRecord.cs +++ b/src/PSRule/Rules/RuleRecord.cs @@ -25,7 +25,7 @@ public sealed class RuleRecord : IDetailedRuleResultV2 internal readonly ResultDetail _Detail; - internal RuleRecord(string runId, ResourceId ruleId, string @ref, TargetObject targetObject, string targetName, string targetType, ResourceTags tag, RuleHelpInfo info, Hashtable field, SeverityLevel level, ISourceExtent extent, RuleOutcome outcome = RuleOutcome.None, RuleOutcomeReason reason = RuleOutcomeReason.None) + internal RuleRecord(string runId, ResourceId ruleId, string @ref, TargetObject targetObject, string targetName, string targetType, IResourceTags tag, RuleHelpInfo info, Hashtable field, SeverityLevel level, ISourceExtent extent, RuleOutcome outcome = RuleOutcome.None, RuleOutcomeReason reason = RuleOutcomeReason.None) { _TargetObject = targetObject; RunId = runId; diff --git a/src/PSRule/Rules/RuleSummaryRecord.cs b/src/PSRule/Rules/RuleSummaryRecord.cs index 2ef34a4363..e64e307e4b 100644 --- a/src/PSRule/Rules/RuleSummaryRecord.cs +++ b/src/PSRule/Rules/RuleSummaryRecord.cs @@ -15,7 +15,7 @@ namespace PSRule.Rules; [DebuggerDisplay("{RuleId}, Outcome = {Outcome}")] public sealed class RuleSummaryRecord { - internal RuleSummaryRecord(string ruleId, string ruleName, ResourceTags tag, RuleHelpInfo info) + internal RuleSummaryRecord(string ruleId, string ruleName, IResourceTags tag, RuleHelpInfo info) { RuleId = ruleId; RuleName = ruleName; diff --git a/src/PSRule/Runtime/LanguageScope.cs b/src/PSRule/Runtime/LanguageScope.cs index 23fe412978..7a5bde8596 100644 --- a/src/PSRule/Runtime/LanguageScope.cs +++ b/src/PSRule/Runtime/LanguageScope.cs @@ -11,8 +11,6 @@ namespace PSRule.Runtime; [DebuggerDisplay("{Name}")] internal sealed class LanguageScope : ILanguageScope { - internal const string STANDALONE_SCOPENAME = "."; - private readonly RunspaceContext _Context; private IDictionary _Configuration; private readonly Dictionary _Service; @@ -23,7 +21,7 @@ internal sealed class LanguageScope : ILanguageScope public LanguageScope(RunspaceContext context, string name) { _Context = context; - Name = Normalize(name); + Name = ResourceHelper.NormalizeScope(name); //_Configuration = new Dictionary(); _Filter = new Dictionary(); _Service = new Dictionary(); @@ -144,11 +142,6 @@ public bool TryGetScope(object o, out string[] scope) return false; } - internal static string Normalize(string scope) - { - return string.IsNullOrEmpty(scope) ? STANDALONE_SCOPENAME : scope; - } - private void Dispose(bool disposing) { if (!_Disposed) diff --git a/src/PSRule/Runtime/LanguageScopeSet.cs b/src/PSRule/Runtime/LanguageScopeSet.cs index 1151764675..de64c44ee2 100644 --- a/src/PSRule/Runtime/LanguageScopeSet.cs +++ b/src/PSRule/Runtime/LanguageScopeSet.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using PSRule.Definitions; + namespace PSRule.Runtime; /// @@ -98,6 +100,6 @@ internal bool Import(string name, out ILanguageScope scope) private static string GetScopeName(string name) { - return LanguageScope.Normalize(name); + return ResourceHelper.NormalizeScope(name); } } diff --git a/src/PSRule/Runtime/ObjectPath/IPathExpressionContext.cs b/src/PSRule/Runtime/ObjectPath/IPathExpressionContext.cs new file mode 100644 index 0000000000..fc754f726a --- /dev/null +++ b/src/PSRule/Runtime/ObjectPath/IPathExpressionContext.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime.ObjectPath; + +/// +/// A context ojbect used using evaluating a path expression. +/// +internal interface IPathExpressionContext +{ + object Input { get; } + + bool CaseSensitive { get; } +} diff --git a/src/PSRule/Runtime/ObjectPath/Exceptions.cs b/src/PSRule/Runtime/ObjectPath/ObjectPathEvaluateException.cs similarity index 100% rename from src/PSRule/Runtime/ObjectPath/Exceptions.cs rename to src/PSRule/Runtime/ObjectPath/ObjectPathEvaluateException.cs diff --git a/src/PSRule/Runtime/ObjectPath/PathExpression.cs b/src/PSRule/Runtime/ObjectPath/PathExpression.cs index 851868c883..4c80a9dfdd 100644 --- a/src/PSRule/Runtime/ObjectPath/PathExpression.cs +++ b/src/PSRule/Runtime/ObjectPath/PathExpression.cs @@ -15,38 +15,6 @@ namespace PSRule.Runtime.ObjectPath; /// internal delegate bool PathExpressionFilterFn(IPathExpressionContext context, object input); -/// -/// A context ojbect used using evaluating a path expression. -/// -internal interface IPathExpressionContext -{ - object Input { get; } - - bool CaseSensitive { get; } -} - -/// -/// The default context object used using evaluating a path expression. -/// -internal sealed class PathExpressionContext : IPathExpressionContext -{ - public PathExpressionContext(object input, bool caseSensitive) - { - Input = input; - CaseSensitive = caseSensitive; - } - - /// - /// The original root object passed into the expression. - /// - public object Input { get; } - - /// - /// Determines if member name matching is case-sensitive. - /// - public bool CaseSensitive { get; } -} - /// /// A path expression using JSONPath inspired syntax. /// diff --git a/src/PSRule/Runtime/ObjectPath/PathExpressionContext.cs b/src/PSRule/Runtime/ObjectPath/PathExpressionContext.cs new file mode 100644 index 0000000000..dead15f9df --- /dev/null +++ b/src/PSRule/Runtime/ObjectPath/PathExpressionContext.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime.ObjectPath; + +/// +/// The default context object used using evaluating a path expression. +/// +internal sealed class PathExpressionContext : IPathExpressionContext +{ + public PathExpressionContext(object input, bool caseSensitive) + { + Input = input; + CaseSensitive = caseSensitive; + } + + /// + /// The original root object passed into the expression. + /// + public object Input { get; } + + /// + /// Determines if member name matching is case-sensitive. + /// + public bool CaseSensitive { get; } +} diff --git a/src/PSRule/Runtime/RunspaceContext.cs b/src/PSRule/Runtime/RunspaceContext.cs index 3786412118..5db8f4641b 100644 --- a/src/PSRule/Runtime/RunspaceContext.cs +++ b/src/PSRule/Runtime/RunspaceContext.cs @@ -544,7 +544,7 @@ private string GetLogPrefix() return _LogPrefix ?? string.Empty; } - internal void EnterLanguageScope(SourceFile file) + internal void EnterLanguageScope(ISourceFile file) { // TODO: Look at scope caching, and a scope stack. @@ -561,7 +561,7 @@ internal void EnterLanguageScope(SourceFile file) Source = new SourceScope(file); } - internal void ExitLanguageScope(SourceFile file) + internal void ExitLanguageScope(ISourceFile file) { // Look at scope poping and validation. @@ -754,6 +754,9 @@ public void Init(Source[] source) foreach (var resource in resources) { + if (resource == null) + continue; + EnterLanguageScope(resource.Source); try { diff --git a/tests/PSRule.Tests/BaselineTests.cs b/tests/PSRule.Tests/BaselineTests.cs index 190ac4579a..989b739db7 100644 --- a/tests/PSRule.Tests/BaselineTests.cs +++ b/tests/PSRule.Tests/BaselineTests.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PSRule.Configuration; +using PSRule.Definitions; using PSRule.Definitions.Baselines; using PSRule.Host; using PSRule.Pipeline; diff --git a/tests/PSRule.Tests/TestResourceName.cs b/tests/PSRule.Tests/TestResourceName.cs index c415477916..c03babcca6 100644 --- a/tests/PSRule.Tests/TestResourceName.cs +++ b/tests/PSRule.Tests/TestResourceName.cs @@ -18,9 +18,9 @@ public TestResourceName(ResourceId id, ResourceTags resourceTags = null, Resourc Labels = resourceLabels ?? new ResourceLabels(); } - public ResourceKind Kind => throw new System.NotImplementedException(); + public ResourceKind Kind => throw new NotImplementedException(); - public string ApiVersion => throw new System.NotImplementedException(); + public string ApiVersion => throw new NotImplementedException(); public string Name { get; } @@ -28,21 +28,21 @@ public TestResourceName(ResourceId id, ResourceTags resourceTags = null, Resourc public ResourceId[] Alias => Array.Empty(); - public ResourceTags Tags { get; } + public IResourceTags Tags { get; } - public ResourceLabels Labels { get; } + public IResourceLabels Labels { get; } public ResourceFlags Flags => ResourceFlags.None; - public ISourceExtent Extent => throw new System.NotImplementedException(); + public ISourceExtent Extent => throw new NotImplementedException(); - public IResourceHelpInfo Info => throw new System.NotImplementedException(); + public IResourceHelpInfo Info => throw new NotImplementedException(); public ResourceId Id { get; } - public string SourcePath => throw new System.NotImplementedException(); + public string SourcePath => throw new NotImplementedException(); public string Module { get; } - public SourceFile Source => throw new System.NotImplementedException(); + public ISourceFile Source => throw new NotImplementedException(); }