Skip to content

Commit 5a8599a

Browse files
committed
Improvements, refactoring and tests
1 parent 8d6c176 commit 5a8599a

255 files changed

Lines changed: 4603 additions & 1551 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.clinerules

Lines changed: 327 additions & 0 deletions
Large diffs are not rendered by default.

.github/copilot-instructions.md

Lines changed: 320 additions & 148 deletions
Large diffs are not rendered by default.

SourceGenerator-Review-Plan.md

Lines changed: 258 additions & 0 deletions
Large diffs are not rendered by default.

Thinktecture.Runtime.Extensions.slnx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
<File Path="LICENSE.md" />
1111
<File Path="README.md" />
1212
<File Path="CLAUDE.md" />
13+
<File Path="src\Thinktecture.Runtime.Extensions.SourceGenerator\AnalyzerReleases.Shipped.md" />
14+
<File Path="src\Thinktecture.Runtime.Extensions.SourceGenerator\AnalyzerReleases.Unshipped.md" />
1315
</Folder>
1416
<Folder Name="/assets/.github/">
1517
<File Path=".github\workflows\claude.yml" />

review/ArgumentName.cs.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Errors
2+
- default(ArgumentName) can cause NullReferenceException:
3+
- Equals(string other) calls Name.Equals(...), which throws if Name is null (default struct or null passed to Create).
4+
- GetHashCode() calls Name.GetHashCode(), which throws if Name is null.
5+
- ToString() returns Name; for a default instance this returns null, which is unexpected for ToString and can cause downstream issues.
6+
- Remediation: Enforce non-null invariant in the private constructor (ArgumentNullException.ThrowIfNull(name)) and/or make fields private with a validating factory. Additionally, harden methods against default by using Name is null checks (e.g., return false in Equals(string), return 0 in GetHashCode, return string.Empty in ToString) or prevent default by using a non-defaultable pattern.
7+
8+
Warnings
9+
- Inconsistent equality semantics:
10+
- Equals(string other) uses OrdinalIgnoreCase, while Equals(ArgumentName other) and operator== use case-sensitive string comparison and include RenderAsIs in equality. This inconsistency can lead to subtle bugs where ArgumentName.Equals("name") is true but new ArgumentName("name") != new ArgumentName("Name").
11+
- Remediation: Decide on a single comparison semantic. If case-insensitive is intended, update Equals(ArgumentName), operator==, and GetHashCode to use StringComparer.OrdinalIgnoreCase. If case-sensitive is intended, change Equals(string) to case-sensitive and/or remove the overload to avoid confusion.
12+
13+
- Public API surface leakage from SourceGenerator assembly:
14+
- Type is public and placed in the root Thinktecture namespace, with public readonly fields. This unnecessarily expands the public surface of the analyzer/generator assembly and risks name collisions with consumer code that references Thinktecture.* namespaces. Public fields also reduce versioning flexibility.
15+
- Remediation: Make the type internal (and ideally move to an internal sub-namespace used by the source generator). Consider making fields private and exposing read-only properties if external access is ever needed.
16+
17+
- Non-nullable field with defaultable struct:
18+
- Name is declared as non-nullable string but can be null in default(ArgumentName), violating the declared nullability contract and leading to the errors noted above. This can also produce misleading nullability analysis.
19+
- Remediation: Prevent default, or relax the field to string? and handle nulls, or enforce non-null invariant as described above.

review/DebugLogger.cs.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Review: Logging/DebugLogger.cs
2+
3+
Path: `src/Thinktecture.Runtime.Extensions.SourceGenerator/Logging/DebugLogger.cs`
4+
5+
## Summary
6+
`DebugLogger` is a sealed logger implementation that inherits `LoggerBase` and implements `ILogger`. It sets its minimum level to `LogLevel.Debug` via the base constructor and delegates Debug/Information/Error to `LoggerBase.Log`. `LogTrace` is intentionally a no-op.
7+
8+
## API and Behavior
9+
- Constructor: `DebugLogger(LoggerFactory, ILoggingSink, string source)` calls `base(..., LogLevel.Debug)` which establishes the effective minimum level for this logger instance.
10+
- `LogTrace(string message, TypeDeclarationSyntax type)`: empty method body — trace messages are ignored by this logger.
11+
- `LogDebug(...)` and `LogInformation(...)`: forward to `Log(LogLevel.Debug|Information, ...)` on `LoggerBase`.
12+
- Generic overloads for Debug/Information accept `T : INamespaceAndName` to include symbol identity context. The calls forward to base with the same constraints.
13+
- `LogError(...)`: forwards to `Log(LogLevel.Error, ...)` including the `Exception?`.
14+
15+
Interface compliance:
16+
- Implements all members of `ILogger`. Optional parameters on the interface are compile-time features and not required on the implementing method; the provided signatures are compatible.
17+
18+
## Potential Issues and Warnings
19+
- Behavior consistency: `LogTrace` is a no-op while other levels delegate to `LoggerBase.Log`. If the intent is to completely suppress trace logs for this logger type, this is correct. If the intent is to let the base decide (e.g., to allow sinks with independent level gating), consider delegating to `base.Log(LogLevel.Trace, ...)` for consistency.
20+
- Unused parameter: `type` in `LogTrace` is not used. Some analyzers (e.g., IDE0060) may flag “Remove unused parameter.” Because the signature is dictated by the interface, removal is not possible. If analyzer noise is a concern:
21+
- Add `_ = type;` to explicitly acknowledge the parameter; or
22+
- Call into `base.Log(LogLevel.Trace, ...)` as suggested above; or
23+
- Add a justification comment or suppression attribute as per project conventions.
24+
- Documentation: Public API lacks XML doc comments. If the project enforces documentation, consider adding short summaries clarifying that this logger deliberately suppresses trace logs.
25+
- Nullability: Signatures align with the interface (`TypeDeclarationSyntax?` for non-trace overloads; non-nullable for `LogTrace`). Passing through `null` where allowed is handled by `LoggerBase`.
26+
27+
## Recommendations
28+
- Decide on the intended semantics for trace in `DebugLogger` and either:
29+
- Keep as no-op and add a brief comment, e.g., “Debug logger intentionally ignores trace-level messages,” and optionally `_ = type;` to avoid IDE0060; or
30+
- Delegate to `base.Log(LogLevel.Trace, message, type)` for uniform handling (base will filter by level anyway).
31+
- Consider adding minimal XML docs to clarify intended filtering.
32+
- No functional errors detected.
33+
34+
## Verdict
35+
No errors. One minor warning around an unused parameter and a small consistency consideration for `LogTrace` handling. The implementation is otherwise correct and consistent with the logging design used across the project.

review/EnumExtensions.cs.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Issues found in src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/EnumExtensions.cs
2+
3+
## Errors
4+
5+
1) Invalid array cast for underlying values
6+
- Code: `public static readonly int[] Values = (int[])Enum.GetValues(typeof(T));`
7+
- Problem: `Enum.GetValues(typeof(T))` returns an array of the enum type (`T[]`), not an array of the underlying numeric type. Casting `T[]` to `int[]` always fails at runtime with InvalidCastException, regardless of the enum’s underlying type.
8+
- Impact: Any first access to `EnumHelper<T>` (e.g., calling `GetValidValue`) will throw.
9+
- Fix options:
10+
- Use `Enum.GetValuesAsUnderlyingType(typeof(T))` (returns an `Array` of the actual underlying numeric type), and adjust comparison logic accordingly.
11+
- If the API is intended to support only int-based enums, guard explicitly:
12+
```
13+
if (Enum.GetUnderlyingType(typeof(T)) != typeof(int)) throw new NotSupportedException(...);
14+
```
15+
and remove the invalid cast.
16+
17+
2) `GetValidValue<T>(this int value)` incompatible with non-int underlying enums
18+
- Code: `public static T? GetValidValue<T>(this int value) where T : struct, Enum`
19+
- Problems:
20+
- The parameter is `int`, but enums may use any integral underlying type (`sbyte, byte, short, ushort, int, uint, long, ulong`). For non-int underlying enums this can truncate or mis-compare values.
21+
- Combined with the broken `Values` array cast, the method is currently non-functional.
22+
- Impact: Incorrect behavior or exceptions for enums not using `int` as underlying type.
23+
- Fix options:
24+
- Generalize input type by comparing via `Convert.ToUInt64(value)` against enum values converted to `UInt64`, or
25+
- Use `Enum.GetValuesAsUnderlyingType` and compare using typed values, or
26+
- Restrict to int-based enums with a runtime check and document the limitation.
27+
28+
## Warnings
29+
30+
1) `IsValid<T>` does not account for [Flags] combinations
31+
- Code: `Array.IndexOf(EnumHelper<T>.Items, item) >= 0`
32+
- Problem: Returns `false` for valid bitwise combinations unless explicitly declared as named members.
33+
- Risk: Surprising semantics for flags enums; may be intended, but if general validity for flags is desired, this is too strict.
34+
- Suggestion: If flags support is needed, validate via bitwise decomposition against defined values or document current behavior clearly.
35+
36+
2) Redundant generic constraints
37+
- Code: `where T : struct, Enum` (in `GetValidValue`)
38+
- Problem: `Enum` constraint implies value type; adding `struct` is redundant.
39+
- Suggestion: Simplify to `where T : Enum` for consistency with `IsValid<T>`.

review/EnumerableExtension.cs.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
Issues found
2+
3+
1) Public API surface in SourceGenerator assembly
4+
- Severity: Warning
5+
- Details: The extension class and method are public. This assembly is an analyzer/source generator; exposing helper extensions publicly increases the surface area unintentionally for consumers of the analyzer package.
6+
- Recommendation: Make the class and method internal to keep the public API minimal.
7+
8+
Suggested change:
9+
------- PATCH
10+
- public static class EnumerableExtension
11+
+ internal static class EnumerableExtensions
12+
13+
- public static void Enumerate<T>(this IEnumerable<T> enumerable)
14+
+ internal static void Enumerate<T>(this IEnumerable<T> enumerable)
15+
+++++++ END
16+
17+
18+
2) Naming inconsistency: use plural "Extensions"
19+
- Severity: Warning
20+
- Details: Other helper types follow the "Extensions" plural naming convention (e.g., ReadOnlyCollectionExtensions). This class is named "EnumerableExtension" (singular), which is inconsistent with common .NET conventions and with the project naming.
21+
- Recommendation: Rename the class to EnumerableExtensions and the file to EnumerableExtensions.cs.
22+
23+
Suggested change:
24+
- File rename: src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/EnumerableExtension.cs → EnumerableExtensions.cs
25+
- Class rename:
26+
------- PATCH
27+
- public static class EnumerableExtension
28+
+ internal static class EnumerableExtensions
29+
+++++++ END
30+
31+
32+
3) Potential missing using for IEnumerable<T>
33+
- Severity: Warning (conditional)
34+
- Details: The file relies on IEnumerable<T> but has no explicit using System.Collections.Generic;. If implicit or global usings are disabled for this project, this will fail to compile.
35+
- Recommendation: Ensure a global using exists or add an explicit using.
36+
37+
Suggested change (if needed):
38+
------- PATCH
39+
+ using System.Collections.Generic;
40+
41+
namespace Thinktecture;
42+
+++++++ END
43+
44+
45+
Notes
46+
- Null-check for enumerable is not strictly required due to NRT and project guidance (nullable reference types enabled). If defensive checks are desired, add ArgumentNullException.ThrowIfNull(enumerable); at the start of the method.

review/ErrorLogger.cs.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Review: Logging/ErrorLogger.cs
2+
3+
Path: `src/Thinktecture.Runtime.Extensions.SourceGenerator/Logging/ErrorLogger.cs`
4+
5+
## Summary
6+
`ErrorLogger` is a sealed logger inheriting `LoggerBase` and implementing `ILogger`. It sets its minimum level to `LogLevel.Error`. It intentionally ignores Trace, Debug, and Information (no-ops) while delegating Error to `LoggerBase.Log`.
7+
8+
## API and Behavior
9+
- Constructor: `ErrorLogger(LoggerFactory, ILoggingSink, string source)` calls `base(..., LogLevel.Error)`, setting the min level to Error.
10+
- `LogTrace(string message, TypeDeclarationSyntax type)`: no-op — trace suppressed.
11+
- `LogDebug(...)` (generic and non-generic): no-op — debug suppressed.
12+
- `LogInformation(...)` (generic and non-generic): no-op — information suppressed.
13+
- `LogError(...)`: delegates to `Log(LogLevel.Error, ...)` including `Exception?`.
14+
15+
Interface compliance:
16+
- Implements all `ILogger` members. Optional parameters on the interface are not required on the implementation signatures. Members coming from `LoggerBase` (e.g., `IsEnabled`, `Log(LogLevel, string)`) satisfy the interface.
17+
18+
## Potential Issues and Warnings
19+
- Unused parameters (analyzers like IDE0060):
20+
- `type` in `LogTrace`
21+
- `type`, `factory` in non-generic `LogDebug`
22+
- `type`, `namespaceAndName`, `factory` in generic `LogDebug`
23+
- `type`, `factory` in non-generic `LogInformation`
24+
- `type`, `namespaceAndName`, `factory` in generic `LogInformation`
25+
Options:
26+
- Add `_ = param;` for each; or
27+
- Delegate to `base.Log(LogLevel.Trace|Debug|Information, ...)` and let base perform level gating; or
28+
- Suppress with justification per project conventions.
29+
- Consistency:
30+
- Other logger variants either delegate suppressed levels to base (e.g., `DebugLogger` for Debug/Info) or no-op. If uniform routing through the base (for consistent formatting/enrichment and future-proofing) is desired, delegate and rely on base’s level filtering. If strict suppression is desired, keep no-ops and document intent.
31+
- Documentation:
32+
- No XML docs. If the project enforces documentation, add summaries clarifying suppression of Trace/Debug/Information.
33+
34+
## Nullability
35+
- Matches `ILogger`: non-nullable `TypeDeclarationSyntax` for Trace; nullable for other overloads. Delegation for Error should handle nulls in `LoggerBase`.
36+
37+
## Recommendations
38+
- Either keep no-ops and add comments plus `_ =` assignments to silence analyzers, or delegate suppressed levels to `base.Log(...)` for consistency.
39+
- Optionally add XML docs describing behavior.
40+
41+
## Verdict
42+
No functional errors. Minor warnings regarding unused parameters and a design choice on suppression vs delegation for Trace/Debug/Information.

review/FieldSymbolExtensions.cs.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Issues found in src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/FieldSymbolExtensions.cs
2+
3+
1) Warning: Implicit dependency on global usings for Roslyn types
4+
- Problem: The file references SyntaxToken, IFieldSymbol, and IPropertySymbol without a local using Microsoft.CodeAnalysis;. Only using Microsoft.CodeAnalysis.CSharp.Syntax; is present, which does not import SyntaxToken nor the symbol interfaces.
5+
- Impact: Compilation relies on a global using elsewhere. If that global using is removed or not included in certain build contexts (e.g., tests, analyzers, or consumers embedding sources), this file will fail to compile.
6+
- Recommendation: Add an explicit using to remove the hidden dependency.
7+
8+
Suggested change:
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp.Syntax;
11+
12+
namespace Thinktecture;
13+
...
14+
15+
2) Warning: Backing field detection may be broader than intended
16+
- Problem: IsPropertyBackingField checks only field.AssociatedSymbol is IPropertySymbol. This is generally correct for compiler-generated backing fields, but if the intention is to strictly detect compiler-generated fields, it should also verify field.IsImplicitlyDeclared to avoid any edge cases where a field might be associated but not implicitly generated.
17+
- Impact: Potential false positives in rare/edge cases if non-implicit fields with an associated property symbol appear (uncommon, but tightening the check is safer).
18+
- Recommendation: Narrow the check if strictness is desired.
19+
20+
Suggested change:
21+
public static bool IsPropertyBackingField(this IFieldSymbol field)
22+
{
23+
return field.IsImplicitlyDeclared && field.AssociatedSymbol is IPropertySymbol;
24+
}

0 commit comments

Comments
 (0)