Skip to content

Commit 18db1ad

Browse files
authored
Fixes custom emitter loaded to late microsoft#2775 (microsoft#2776)
1 parent 4e04ee1 commit 18db1ad

28 files changed

+467
-127
lines changed

docs/changelog.md

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ What's changed since pre-release v3.0.0-B0416:
3333
[#2768](https://github.com/microsoft/PSRule/pull/2768)
3434
- Bump vscode engine to v1.97.0.
3535
[#2759](https://github.com/microsoft/PSRule/pull/2759)
36+
- Bug fix:
37+
- Fixed custom emitter loaded too late by @BernieWhite.
38+
[#2775](https://github.com/microsoft/PSRule/issues/2775)
3639

3740
## v3.0.0-B0416 (pre-release)
3841

docs/concepts/formats.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Custom emitters can be created by implementing the `PSRule.Emitters.IEmitter` in
3636
This custom type implementation will be loaded by PSRule and used to process the input object.
3737

3838
To use a custom emitter, it must be registered with PSRule as a service.
39-
This can be done by a convention within the `-Initialize` script block.
39+
This can be done by registering a runtime factory.
4040

4141
## Configuring formats
4242

src/PSRule.Types/Environment.cs

+12-8
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,22 @@
1111

1212
namespace PSRule;
1313

14+
#nullable enable
15+
1416
/// <summary>
1517
/// A helper for accessing environment and runtime variables.
1618
/// </summary>
1719
public static class Environment
1820
{
19-
private static readonly char[] STRINGARRAYMAP_ITEMSEPARATOR = new char[] { ',' };
20-
private static readonly char[] STRINGARRAY_SEPARATOR = new char[] { ';' };
21-
private static readonly char[] LINUX_PATH_ENV_SEPARATOR = new char[] { ':' };
22-
private static readonly char[] WINDOWS_PATH_ENV_SEPARATOR = new char[] { ';' };
21+
private static readonly char[] STRING_ARRAY_MAP_ITEM_SEPARATOR = [','];
22+
private static readonly char[] STRINGARRAY_SEPARATOR = [';'];
23+
private static readonly char[] LINUX_PATH_ENV_SEPARATOR = [':'];
24+
private static readonly char[] WINDOWS_PATH_ENV_SEPARATOR = [';'];
2325

2426
private const char BACKSLASH = '\\';
2527
private const char SLASH = '/';
2628

27-
private const char STRINGARRYAMAP_PAIRSEPARATOR = '=';
29+
private const char STRING_ARRAY_MAP_PAIR_SEPARATOR = '=';
2830
private const string PATH_ENV = "PATH";
2931
private const string DEFAULT_CREDENTIAL_USERNAME = "na";
3032
private const string TF_BUILD = "TF_BUILD";
@@ -127,7 +129,7 @@ public static string GetRootedPath(string? path, bool normalize = false, string?
127129
/// <remarks>
128130
/// A base path always includes a trailing <c>/</c>.
129131
/// </remarks>
130-
public static string GetRootedBasePath(string path, bool normalize = false, string? basePath = null)
132+
public static string GetRootedBasePath(string? path, bool normalize = false, string? basePath = null)
131133
{
132134
if (string.IsNullOrEmpty(path))
133135
path = string.Empty;
@@ -250,12 +252,12 @@ public static bool TryStringArrayMap(string key, out StringArrayMap? value)
250252
var map = new StringArrayMap();
251253
for (var i = 0; i < pairs.Length; i++)
252254
{
253-
var index = pairs[i].IndexOf(STRINGARRYAMAP_PAIRSEPARATOR);
255+
var index = pairs[i].IndexOf(STRING_ARRAY_MAP_PAIR_SEPARATOR);
254256
if (index < 1 || index + 1 >= pairs[i].Length) continue;
255257

256258
var left = pairs[i].Substring(0, index);
257259
var right = pairs[i].Substring(index + 1);
258-
var pair = right.Split(STRINGARRAYMAP_ITEMSEPARATOR, StringSplitOptions.RemoveEmptyEntries);
260+
var pair = right.Split(STRING_ARRAY_MAP_ITEM_SEPARATOR, StringSplitOptions.RemoveEmptyEntries);
259261
map[left] = pair;
260262
}
261263
value = map;
@@ -380,3 +382,5 @@ private static bool IsPathSeparator(char c)
380382
return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar || c == SLASH || c == BACKSLASH;
381383
}
382384
}
385+
386+
#nullable restore

src/PSRule.Types/Runtime/ILogger.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ public interface ILogger
2323
/// <param name="eventId">An event identifier for the diagnostic message.</param>
2424
/// <param name="state">Additional information that describes the diagnostic state to log.</param>
2525
/// <param name="exception">An optional exception which the diagnostic message is related to.</param>
26-
/// <param name="formatter">A function to format the diagnostic message for the outpuyt stream.</param>
26+
/// <param name="formatter">A function to format the diagnostic message for the outpuWt stream.</param>
2727
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter);
2828
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace PSRule.Runtime;
5+
6+
/// <summary>
7+
/// An interface for implementing a runtime factory.
8+
/// </summary>
9+
public interface IRuntimeFactory
10+
{
11+
/// <summary>
12+
/// Call the runtime factory to configure services.
13+
/// </summary>
14+
void Configure(IRuntimeFactoryContext context);
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace PSRule.Runtime;
5+
6+
/// <summary>
7+
/// A context for a runtime factory.
8+
/// </summary>
9+
public interface IRuntimeFactoryContext
10+
{
11+
/// <summary>
12+
/// Configure services for the runtime factory.
13+
/// </summary>
14+
void ConfigureServices(Action<IRuntimeServiceCollection> configure);
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace PSRule.Runtime;
5+
6+
/// <summary>
7+
/// Identifies for classes that construct runtime services.
8+
/// </summary>
9+
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
10+
public sealed class RuntimeFactoryAttribute : Attribute
11+
{
12+
13+
}

src/PSRule/Emitters/EmitterBuilder.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace PSRule.Emitters;
1818
internal sealed class EmitterBuilder
1919
{
2020
private static readonly EventId PSR0012 = new(12, "PSR0012");
21-
private static readonly EventId PSR0013 = new(12, "PSR0013");
21+
private static readonly EventId PSR0013 = new(13, "PSR0013");
2222

2323
private readonly ILanguageScopeSet? _LanguageScopeSet;
2424
private readonly IFormatOption _FormatOption;

src/PSRule/Pipeline/Formatters/AssertFormatterBase.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ private void Source(Source[] source)
264264
{
265265
if (source[i].Module != null && !list.Contains(source[i].Module.Name))
266266
{
267-
WriteLineFormat(FormatterStrings.ModuleVersion, source[i].Module.Name, source[i].Module.Version);
267+
WriteLineFormat(FormatterStrings.ModuleVersion, source[i].Module.Name, source[i].Module.FullVersion);
268268
list.Add(source[i].Module.Name);
269269
}
270270
}

src/PSRule/Pipeline/Output/JobSummaryWriter.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ private void Source()
8686
if (_Source[i].Module != null && !list.Contains(_Source[i].Module.Name))
8787
{
8888
var projectLink = string.IsNullOrEmpty(_Source[i].Module.ProjectUri) ? _Source[i].Module.Name : $"[{_Source[i].Module.Name}]({_Source[i].Module.ProjectUri})";
89-
WriteLine($"{projectLink} | v{_Source[i].Module.Version}");
89+
WriteLine($"{projectLink} | v{_Source[i].Module.FullVersion}");
9090
list.Add(_Source[i].Module.Name);
9191
}
9292
}

src/PSRule/Pipeline/Output/SarifBuilder.cs

+17-16
Original file line numberDiff line numberDiff line change
@@ -514,24 +514,25 @@ private Tool GetTool(Source[]? source)
514514
var result = new List<ToolComponent>();
515515
for (var i = 0; i < source.Length; i++)
516516
{
517-
if (source[i].Module != null && !_Extensions.ContainsKey(source[i].Module.Name))
517+
var module = source[i].Module;
518+
if (module == null || _Extensions.ContainsKey(module.Name))
519+
continue;
520+
521+
var extension = new ToolComponent
518522
{
519-
var extension = new ToolComponent
523+
Name = module.Name,
524+
Version = module.FullVersion,
525+
Guid = module.Guid,
526+
AssociatedComponent = new ToolComponentReference
520527
{
521-
Name = source[i].Module.Name,
522-
Version = source[i].Module.Version,
523-
Guid = source[i].Module.Guid,
524-
AssociatedComponent = new ToolComponentReference
525-
{
526-
Name = TOOL_NAME,
527-
},
528-
InformationUri = new Uri(source[i].Module.ProjectUri, UriKind.Absolute),
529-
Organization = source[i].Module.CompanyName,
530-
Rules = new List<ReportingDescriptor>(),
531-
};
532-
_Extensions.Add(extension.Name, extension);
533-
result.Add(extension);
534-
}
528+
Name = TOOL_NAME,
529+
},
530+
InformationUri = new Uri(module.ProjectUri, UriKind.Absolute),
531+
Organization = module.CompanyName,
532+
Rules = [],
533+
};
534+
_Extensions.Add(extension.Name, extension);
535+
result.Add(extension);
535536
}
536537
return result.Count > 0 ? result : null;
537538
}

src/PSRule/Pipeline/PipelineBuilderBase.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,9 @@ protected bool RequireModules()
132132
}
133133
for (var i = 0; Source != null && i < Source.Length; i++)
134134
{
135-
if (Source[i].Module != null && Option.Requires.TryGetValue(Source[i].Module.Name, out requiredVersion))
135+
if (Source[i].Module != null && Option.Requires.TryGetValue(Source[i].Module!.Name, out requiredVersion))
136136
{
137-
if (GuardModuleVersion(Source[i].Module.Name, Source[i].Module.Version, requiredVersion))
137+
if (GuardModuleVersion(Source[i].Module!.Name, Source[i].Module!.FullVersion, requiredVersion))
138138
result = false;
139139
}
140140
}

src/PSRule/Pipeline/Source.cs

+15-45
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
using System.Collections;
5-
using System.Management.Automation;
4+
using System.Reflection;
65
using PSRule.Definitions;
76

87
namespace PSRule.Pipeline;
98

9+
#nullable enable
10+
1011
/// <summary>
1112
/// A PSRule source containing one or more source files.
1213
/// </summary>
1314
public sealed class Source
1415
{
1516
internal bool Dependency;
1617

17-
internal readonly ModuleInfo Module;
18+
internal readonly ModuleInfo? Module;
1819

1920
internal Source(string path, SourceFile[] file)
2021
{
@@ -34,53 +35,20 @@ internal Source(ModuleInfo module, SourceFile[] file, bool dependency)
3435
SetSource();
3536
}
3637

37-
internal sealed class ModuleInfo
38+
internal sealed class ModuleInfo(string path, string name, string version, string? projectUri, string? guid, string? companyName, string? prerelease, Assembly[] assemblies)
3839
{
39-
private const string FIELD_PRERELEASE = "Prerelease";
40-
private const string FIELD_PSDATA = "PSData";
4140
private const string PRERELEASE_SEPARATOR = "-";
4241

43-
public readonly string Path;
44-
public readonly string Name;
45-
public readonly string Version;
46-
public readonly string ProjectUri;
47-
public readonly string Guid;
48-
public readonly string CompanyName;
42+
public readonly string Path = path;
43+
public readonly string Name = name;
44+
public readonly string Version = version;
45+
public readonly string FullVersion = string.IsNullOrEmpty(prerelease) ? version : string.Concat(version, PRERELEASE_SEPARATOR, prerelease);
4946

50-
public ModuleInfo(PSModuleInfo info)
51-
{
52-
Path = info.ModuleBase;
53-
Name = info.Name;
54-
Version = info.Version?.ToString();
55-
ProjectUri = info.ProjectUri?.ToString();
56-
Guid = info.Guid.ToString();
57-
CompanyName = info.CompanyName;
58-
if (TryPrivateData(info, FIELD_PSDATA, out var psData) && psData.ContainsKey(FIELD_PRERELEASE))
59-
Version = string.Concat(Version, PRERELEASE_SEPARATOR, psData[FIELD_PRERELEASE].ToString());
60-
}
47+
public readonly Assembly[] Assemblies = assemblies;
6148

62-
public ModuleInfo(string path, string name, string version, string projectUri, string guid, string companyName, string prerelease)
63-
{
64-
Path = path;
65-
Name = name;
66-
Version = version;
67-
ProjectUri = projectUri;
68-
Guid = guid;
69-
CompanyName = companyName;
70-
if (!string.IsNullOrEmpty(prerelease))
71-
Version = string.Concat(version, PRERELEASE_SEPARATOR, prerelease);
72-
}
73-
74-
private static bool TryPrivateData(PSModuleInfo info, string propertyName, out Hashtable value)
75-
{
76-
value = null;
77-
if (info.PrivateData is Hashtable privateData && privateData.ContainsKey(propertyName) && privateData[propertyName] is Hashtable data)
78-
{
79-
value = data;
80-
return true;
81-
}
82-
return false;
83-
}
49+
public readonly string? ProjectUri = projectUri;
50+
public readonly string? Guid = guid;
51+
public readonly string? CompanyName = companyName;
8452
}
8553

8654
/// <summary>
@@ -107,3 +75,5 @@ private void SetSource()
10775
File[i].Source = this;
10876
}
10977
}
78+
79+
#nullable restore

0 commit comments

Comments
 (0)