Refactoring of language scopes (#2601)

This commit is contained in:
Bernie White 2024-11-12 14:41:52 +10:00 коммит произвёл GitHub
Родитель ea4a77c3ab
Коммит d2c419126c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
12 изменённых файлов: 122 добавлений и 68 удалений

Просмотреть файл

@ -32,8 +32,8 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
[#2552](https://github.com/microsoft/PSRule/issues/2552)
- Modules are automatically restored unless `--no-restore` is used with the `run` command.
- Engineering:
- Bump YamlDotNet to v16.1.3.
[#1874](https://github.com/microsoft/PSRule/pull/1874)
- Bump YamlDotNet to v16.2.0.
[#2596](https://github.com/microsoft/PSRule/pull/2596)
## v3.0.0-B0275 (pre-release)

Просмотреть файл

@ -104,6 +104,7 @@ nav:
- Index: concepts/cli/index.md
- run: concepts/cli/run.md
- module: concepts/cli/module.md
- restore: concepts/cli/restore.md
- Assertion methods: concepts/PSRule/en-US/about_PSRule_Assert.md
- Baselines: concepts/PSRule/en-US/about_PSRule_Baseline.md
- Badges: concepts/PSRule/en-US/about_PSRule_Badges.md

Просмотреть файл

@ -658,8 +658,7 @@ internal static class HostHelper
private static SuppressionGroupV1[] ToSuppressionGroupV1(IEnumerable<ILanguageBlock> blocks, RunspaceContext context)
{
if (blocks == null)
return Array.Empty<SuppressionGroupV1>();
if (blocks == null) return [];
// Index suppression groups by Id.
var results = new Dictionary<string, SuppressionGroupV1>(StringComparer.OrdinalIgnoreCase);
@ -682,13 +681,12 @@ internal static class HostHelper
context.ExitLanguageScope(block.Source);
}
}
return results.Values.ToArray();
return [.. results.Values];
}
private static ModuleConfigV1[] ToModuleConfigV1(IEnumerable<ILanguageBlock> blocks, RunspaceContext context)
{
if (blocks == null)
return Array.Empty<ModuleConfigV1>();
if (blocks == null) return [];
// Index configurations by Name.
var results = new Dictionary<string, ModuleConfigV1>(StringComparer.OrdinalIgnoreCase);
@ -766,39 +764,86 @@ internal static class HostHelper
private static bool Match(RunspaceContext context, RuleBlock resource)
{
var filter = context.LanguageScope.GetFilter(ResourceKind.Rule);
return filter == null || filter.Match(resource);
try
{
context.EnterLanguageScope(resource.Source);
var filter = context.LanguageScope.GetFilter(ResourceKind.Rule);
return filter == null || filter.Match(resource);
}
finally
{
context.ExitLanguageScope(resource.Source);
}
}
private static bool Match(RunspaceContext context, IRuleV1 resource)
{
context.EnterLanguageScope(resource.Source);
var filter = context.LanguageScope.GetFilter(ResourceKind.Rule);
return filter == null || filter.Match(resource);
try
{
context.EnterLanguageScope(resource.Source);
var filter = context.LanguageScope.GetFilter(ResourceKind.Rule);
return filter == null || filter.Match(resource);
}
finally
{
context.ExitLanguageScope(resource.Source);
}
}
private static bool Match(RunspaceContext context, Baseline resource)
{
var filter = context.LanguageScope.GetFilter(ResourceKind.Baseline);
return filter == null || filter.Match(resource);
try
{
context.EnterLanguageScope(resource.Source);
var filter = context.LanguageScope.GetFilter(ResourceKind.Baseline);
return filter == null || filter.Match(resource);
}
finally
{
context.ExitLanguageScope(resource.Source);
}
}
private static bool Match(RunspaceContext context, ScriptBlockConvention block)
{
var filter = context.LanguageScope.GetFilter(ResourceKind.Convention);
return filter == null || filter.Match(block);
try
{
context.EnterLanguageScope(block.Source);
var filter = context.LanguageScope.GetFilter(ResourceKind.Convention);
return filter == null || filter.Match(block);
}
finally
{
context.ExitLanguageScope(block.Source);
}
}
private static bool Match(RunspaceContext context, SelectorV1 resource)
{
var filter = context.LanguageScope.GetFilter(ResourceKind.Selector);
return filter == null || filter.Match(resource);
try
{
context.EnterLanguageScope(resource.Source);
var filter = context.LanguageScope.GetFilter(ResourceKind.Selector);
return filter == null || filter.Match(resource);
}
finally
{
context.ExitLanguageScope(resource.Source);
}
}
private static bool Match(RunspaceContext context, SuppressionGroupV1 suppressionGroup)
{
var filter = context.LanguageScope.GetFilter(ResourceKind.SuppressionGroup);
return filter == null || filter.Match(suppressionGroup);
try
{
context.EnterLanguageScope(suppressionGroup.Source);
var filter = context.LanguageScope.GetFilter(ResourceKind.SuppressionGroup);
return filter == null || filter.Match(suppressionGroup);
}
finally
{
//context.ExitLanguageScope(suppressionGroup.Source);
}
}
private static IConvention[] Sort(RunspaceContext context, IConvention[] conventions)

Просмотреть файл

@ -66,6 +66,11 @@ internal sealed class PipelineContext : IPipelineContext, IBindingContext
public IPipelineWriter Writer { get; }
/// <summary>
/// A collection of languages scopes for this pipeline.
/// </summary>
public ILanguageScopeCollection LanguageScopes { get; }
private PipelineContext(PSRuleOption option, IHostContext hostContext, PipelineInputStream reader, IPipelineWriter writer, OptionContextBuilder optionBuilder, IList<ResourceRef> unresolved)
{
_OptionBuilder = optionBuilder;
@ -87,6 +92,7 @@ internal sealed class PipelineContext : IPipelineContext, IBindingContext
RunId = Environment.GetRunId() ?? ObjectHashAlgorithm.GetDigest(Guid.NewGuid().ToByteArray());
RunTime = Stopwatch.StartNew();
_DefaultOptionContext = _OptionBuilder?.Build(null);
LanguageScopes = new LanguageScopeSet();
}
public static PipelineContext New(PSRuleOption option, IHostContext hostContext, PipelineInputStream reader, IPipelineWriter writer, OptionContextBuilder optionBuilder, IList<ResourceRef> unresolved)
@ -299,6 +305,7 @@ internal sealed class PipelineContext : IPipelineContext, IBindingContext
ObjectHashAlgorithm?.Dispose();
_Runspace?.Dispose();
_PathExpressionCache.Clear();
LanguageScopes.Dispose();
LocalizedDataCache.Clear();
ExpressionCache.Clear();
ContentCache.Clear();

Просмотреть файл

@ -25,7 +25,7 @@ internal interface ILanguageScope : IDisposable
/// <summary>
/// Get an ordered culture preference list which will be tries for finding help.
/// </summary>
string[] Culture { get; }
string[]? Culture { get; }
void Configure(OptionContext context);

Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Runtime;
internal interface ILanguageScopeCollection : IDisposable
{
IEnumerable<ILanguageScope> Get();
bool TryScope(string name, out ILanguageScope scope);
bool Import(string name);
}

Просмотреть файл

@ -24,6 +24,7 @@ internal sealed class LanguageScope : ILanguageScope
public LanguageScope(string name)
{
_Configuration = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
Name = ResourceHelper.NormalizeScope(name);
_Filter = [];
_Service = [];
@ -33,7 +34,7 @@ internal sealed class LanguageScope : ILanguageScope
public string Name { [DebuggerStepThrough] get; }
/// <inheritdoc/>
public string[] Culture { [DebuggerStepThrough] get; [DebuggerStepThrough] private set; }
public string[]? Culture { [DebuggerStepThrough] get; [DebuggerStepThrough] private set; }
public StringComparer GetBindingComparer() => _BindingComparer ?? StringComparer.OrdinalIgnoreCase;
@ -50,6 +51,7 @@ internal sealed class LanguageScope : ILanguageScope
if (context == null) throw new ArgumentNullException(nameof(context));
_Configuration = context.Configuration;
_Configuration ??= new Dictionary<string, object>();
WithFilter(context.RuleFilter);
WithFilter(context.ConventionFilter);
_BindingComparer = context.Binding.GetComparer();
@ -62,7 +64,7 @@ internal sealed class LanguageScope : ILanguageScope
/// <inheritdoc/>
public bool TryConfigurationValue(string key, out object? value)
{
value = null;
value = default;
return !string.IsNullOrEmpty(key) && _Configuration != null && _Configuration.TryGetValue(key, out value);
}
@ -113,8 +115,8 @@ internal sealed class LanguageScope : ILanguageScope
path = result.TargetTypePath;
return true;
}
type = null;
path = null;
type = default;
path = default;
return false;
}
@ -128,8 +130,8 @@ internal sealed class LanguageScope : ILanguageScope
path = result.TargetNamePath;
return true;
}
name = null;
path = null;
name = default;
path = default;
return false;
}

Просмотреть файл

@ -8,7 +8,7 @@ namespace PSRule.Runtime;
/// <summary>
/// A collection of <see cref="ILanguageScope"/>.
/// </summary>
internal sealed class LanguageScopeSet : IDisposable
internal sealed class LanguageScopeSet : ILanguageScopeCollection
{
private readonly Dictionary<string, ILanguageScope> _Scopes;
@ -18,15 +18,7 @@ internal sealed class LanguageScopeSet : IDisposable
public LanguageScopeSet()
{
_Scopes = new Dictionary<string, ILanguageScope>(StringComparer.OrdinalIgnoreCase);
Import(null, out _Current);
}
public ILanguageScope Current
{
get
{
return _Current;
}
Import(null);
}
#region IDisposable
@ -64,34 +56,22 @@ internal sealed class LanguageScopeSet : IDisposable
_Scopes.Add(languageScope.Name, languageScope);
}
internal IEnumerable<ILanguageScope> Get()
public IEnumerable<ILanguageScope> Get()
{
return _Scopes.Values;
}
/// <summary>
/// Switch to a specific language scope by name.
/// </summary>
/// <param name="name">The name of the language scope to switch to.</param>
internal void UseScope(string name)
{
if (!_Scopes.TryGetValue(GetScopeName(name), out var scope))
throw new Exception($"The specified scope '{name}' was not found.");
_Current = scope;
}
internal bool TryScope(string name, out ILanguageScope scope)
public bool TryScope(string name, out ILanguageScope scope)
{
return _Scopes.TryGetValue(GetScopeName(name), out scope);
}
internal bool Import(string name, out ILanguageScope scope)
public bool Import(string name)
{
if (_Scopes.TryGetValue(GetScopeName(name), out scope))
if (_Scopes.ContainsKey(GetScopeName(name)))
return false;
scope = new LanguageScope(name);
var scope = new LanguageScope(name);
Add(scope);
return true;
}

Просмотреть файл

@ -65,10 +65,7 @@ internal sealed class RunspaceContext : IDisposable, ILogger, IScriptResourceDis
private readonly List<ResultReason> _Reason;
private readonly List<IConvention> _Conventions;
/// <summary>
/// A collection of languages scopes for this pipeline.
/// </summary>
private readonly LanguageScopeSet _LanguageScopes;
private ILanguageScope? _CurrentLanguageScope;
// Track whether Dispose has been called.
private bool _Disposed;
@ -91,7 +88,6 @@ internal sealed class RunspaceContext : IDisposable, ILogger, IScriptResourceDis
_RuleTimer = new Stopwatch();
_Reason = [];
_Conventions = [];
_LanguageScopes = new LanguageScopeSet();
_Scope = new Stack<RunspaceScope>();
}
@ -105,12 +101,12 @@ internal sealed class RunspaceContext : IDisposable, ILogger, IScriptResourceDis
internal SourceScope? Source { get; private set; }
internal ILanguageScope LanguageScope
internal ILanguageScope? LanguageScope
{
[DebuggerStepThrough]
get
{
return _LanguageScopes.Current;
return _CurrentLanguageScope;
}
}
@ -524,7 +520,10 @@ internal sealed class RunspaceContext : IDisposable, ILogger, IScriptResourceDis
if (!file.Exists())
throw new FileNotFoundException(PSRuleResources.ScriptNotFound, file.Path);
_LanguageScopes.UseScope(file.Module);
if (!Pipeline.LanguageScopes.TryScope(file.Module, out var scope))
throw new Exception("Language scope is unknown.");
_CurrentLanguageScope = scope;
if (TargetObject != null && LanguageScope != null)
Binding = LanguageScope.Bind(TargetObject);
@ -535,6 +534,7 @@ internal sealed class RunspaceContext : IDisposable, ILogger, IScriptResourceDis
public void ExitLanguageScope(ISourceFile file)
{
// Look at scope popping and validation.
_CurrentLanguageScope = null;
Source = null;
}
@ -645,6 +645,8 @@ internal sealed class RunspaceContext : IDisposable, ILogger, IScriptResourceDis
internal void AddService(string id, object service)
{
if (LanguageScope == null) throw new Exception("Can not call out of scope.");
ResourceHelper.ParseIdString(LanguageScope.Name, id, out var scopeName, out var name);
if (!StringComparer.OrdinalIgnoreCase.Equals(LanguageScope.Name, scopeName) || string.IsNullOrEmpty(name))
return;
@ -654,8 +656,10 @@ internal sealed class RunspaceContext : IDisposable, ILogger, IScriptResourceDis
internal object? GetService(string id)
{
if (LanguageScope == null) throw new Exception("Can not call out of scope.");
ResourceHelper.ParseIdString(LanguageScope.Name, id, out var scopeName, out var name);
return !_LanguageScopes.TryScope(scopeName, out var scope) || string.IsNullOrEmpty(name) ? null : scope.GetService(name!);
return !Pipeline.LanguageScopes.TryScope(scopeName, out var scope) || string.IsNullOrEmpty(name) ? null : scope.GetService(name!);
}
private void RunConventionInitialize()
@ -722,7 +726,7 @@ internal sealed class RunspaceContext : IDisposable, ILogger, IScriptResourceDis
foreach (var resource in resources.Where(r => r.Kind == ResourceKind.ModuleConfig).ToArray())
Pipeline.Import(this, resource);
foreach (var languageScope in _LanguageScopes.Get())
foreach (var languageScope in Pipeline.LanguageScopes.Get())
Pipeline.UpdateLanguageScope(languageScope);
foreach (var resource in resources)
@ -745,14 +749,14 @@ internal sealed class RunspaceContext : IDisposable, ILogger, IScriptResourceDis
foreach (var resource in resources.Where(r => r.Kind != ResourceKind.ModuleConfig).ToArray())
Pipeline.Import(this, resource);
foreach (var languageScope in _LanguageScopes.Get())
foreach (var languageScope in Pipeline.LanguageScopes.Get())
Pipeline.UpdateLanguageScope(languageScope);
}
private void InitLanguageScopes(Source[] source)
{
for (var i = 0; source != null && i < source.Length; i++)
_LanguageScopes.Import(source[i].Scope, out _);
Pipeline.LanguageScopes.Import(source[i].Scope);
}
public void Begin()
@ -788,7 +792,7 @@ internal sealed class RunspaceContext : IDisposable, ILogger, IScriptResourceDis
if (string.IsNullOrEmpty(Source?.File.HelpPath))
return null;
var cultures = LanguageScope.Culture;
var cultures = LanguageScope?.Culture;
if (!_RaisedUsingInvariantCulture && (cultures == null || cultures.Length == 0))
{
this.Throw(_InvariantCulture, PSRuleResources.UsingInvariantCulture);
@ -907,7 +911,6 @@ internal sealed class RunspaceContext : IDisposable, ILogger, IScriptResourceDis
if (_Conventions[i] is IDisposable d)
d.Dispose();
}
_LanguageScopes.Dispose();
}
_Disposed = true;
}

Просмотреть файл

@ -625,6 +625,7 @@ public sealed class FunctionTests
context.Init(s);
context.Begin();
context.PushScope(Runtime.RunspaceScope.Precondition);
context.EnterLanguageScope(s[0].File[0]);
context.EnterTargetObject(new TargetObject(targetObject));
return result;
}

Просмотреть файл

@ -95,6 +95,7 @@ public sealed class PSRuleOptionTests : BaseTests
var context = new Runtime.RunspaceContext(PipelineContext.New(option, null, null, new TestWriter(GetOption()), builder, null));
context.Init(null);
context.Begin();
context.EnterLanguageScope(GetSource()[0].File[0]);
return new Runtime.Configuration(context);
}

Просмотреть файл

@ -1873,6 +1873,7 @@ public sealed class SelectorTests : BaseTests
context.Init(source);
context.Begin();
var selector = HostHelper.GetSelectorForTests(source, context).ToArray().FirstOrDefault(s => s.Name == name);
context.EnterLanguageScope(selector.Source);
return new SelectorVisitor(context, selector.Id, selector.Source, selector.Spec.If);
}