-
-
Notifications
You must be signed in to change notification settings - Fork 216
feat: Justification Property in Ignore Attributes and Diagnostic #2176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c69ae6c
efb0c27
a899676
34ede99
4b76d16
8159352
8786bf1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| --- | ||
| sidebar_label: RMG096 | ||
| description: 'Mapperly analyzer diagnostic RMG096 — Ignored mapping is missing Justification' | ||
| --- | ||
|
|
||
| # RMG096 — Ignored mapping is missing Justification | ||
|
|
||
| If Mapperly detects a mapping ignore without a Justification, `RMG096` is emitted. | ||
|
|
||
| The following attributes may raise this diagnostic: | ||
|
|
||
| - `MapperIgnoreSource` | ||
| - `MapperIgnoreTarget` | ||
| - `MapperIgnoreSourceValue` | ||
| - `MapperIgnoreTargetValue` | ||
|
Comment on lines
+8
to
+15
|
||
|
|
||
| To fix this, add a Justification to the attribute, for example: | ||
|
|
||
| ```csharp | ||
| public partial class Mapper { | ||
| [MapperIgnoreSource(nameof(SourceClass.IgnoredProperty), Justification = "This source member is not relevant for the target mapping")] | ||
| public partial TargetClass Map(SourceClass source); | ||
| } | ||
|
|
||
| class SourceClass { | ||
| string IgnoredProperty { get; set; } | ||
| } | ||
|
Comment on lines
+24
to
+27
|
||
|
|
||
| class TargetClass { } | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -214,3 +214,4 @@ RMG092 | Mapper | Error | Source type is not assignable to the included source t | |
| RMG093 | Mapper | Error | Target type is not assignable to the included target type | ||
| RMG094 | Mapper | Error | Circular existing target mapping without setter detected | ||
| RMG095 | Mapper | Warning | Invalid MSBuild configuration option | ||
| RMG096 | Mapper | Hidden | A MapperIgnore* attribute does not specify a Justification | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -217,16 +217,16 @@ private MembersMappingConfiguration BuildMembersConfig(MappingConfigurationRefer | |
| if (configRef.Method == null) | ||
| return MapperConfiguration.Members; | ||
|
|
||
| var ignoredSourceMembers = _dataAccessor | ||
| var ignoreSourceMemberAttributes = _dataAccessor | ||
| .Access<MapperIgnoreSourceAttribute>(configRef.Method) | ||
| .Select(x => x.Source) | ||
| .WhereNotNull() | ||
| .WhereNotNullBy(static attr => attr.Source) | ||
| .ToList(); | ||
| var ignoredTargetMembers = _dataAccessor | ||
| var ignoredSourceMembers = ignoreSourceMemberAttributes.Select(x => x.Source).ToList(); | ||
| var ignoreTargetMemberAttributes = _dataAccessor | ||
| .Access<MapperIgnoreTargetAttribute>(configRef.Method) | ||
| .Select(x => x.Target) | ||
| .WhereNotNull() | ||
| .WhereNotNullBy(static attr => attr.Target) | ||
| .ToList(); | ||
| var ignoredTargetMembers = ignoreTargetMemberAttributes.Select(x => x.Target).ToList(); | ||
| var memberValueConfigurations = _dataAccessor.Access<MapValueAttribute, MemberValueMappingConfiguration>(configRef.Method).ToList(); | ||
| var memberConfigurations = _dataAccessor | ||
| .Access<MapPropertyAttribute, MemberMappingConfiguration>(configRef.Method) | ||
|
|
@@ -259,6 +259,15 @@ private MembersMappingConfiguration BuildMembersConfig(MappingConfigurationRefer | |
| return MapperConfiguration.Members; | ||
| } | ||
|
|
||
| foreach (var missingIgnoreSourceJustificationAttribute in ignoreSourceMemberAttributes.Where(attr => attr.Justification is null)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When adding support for the other attributes I'd move this into its own method and call it just next to resolving the attributes. |
||
| { | ||
| _diagnostics.ReportDiagnostic( | ||
| DiagnosticDescriptors.MapperIgnoreAttributeMissingJustification, | ||
| configRef.Method, | ||
| missingIgnoreSourceJustificationAttribute.Source | ||
| ); | ||
|
Comment on lines
+262
to
+268
|
||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a help link for the added docs, see other diagnostics on how to do it. |
||
|
|
||
| foreach (var invalidMemberConfig in memberValueConfigurations.Where(x => !x.IsValid)) | ||
| { | ||
| _diagnostics.ReportDiagnostic(DiagnosticDescriptors.InvalidMapValueAttributeUsage, invalidMemberConfig.Location); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -808,6 +808,15 @@ public static class DiagnosticDescriptors | |
| true | ||
| ); | ||
|
|
||
| public static readonly DiagnosticDescriptor MapperIgnoreAttributeMissingJustification = new( | ||
| "RMG096", | ||
| "Ignored mapping is missing Justification", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: I'd lowercase the |
||
| "The ignored mapping of {0} does not specify a Justification, consider adding one for documentation purposes", | ||
| DiagnosticCategories.Mapper, | ||
| DiagnosticSeverity.Hidden, | ||
| true | ||
| ); | ||
|
|
||
| private static string BuildHelpUri(string id) | ||
| { | ||
| #if ENV_NEXT | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,14 @@ public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> enumerable) | |
| #nullable restore | ||
| } | ||
|
|
||
| public static IEnumerable<TItem> WhereNotNullBy<TItem, TSelector>(this IEnumerable<TItem> enumerable, Func<TItem, TSelector?> selector) | ||
| where TSelector : notnull | ||
| { | ||
| #nullable disable | ||
| return enumerable.Where(x => selector(x) != null); | ||
| #nullable restore | ||
| } | ||
|
|
||
|
Comment on lines
+21
to
+28
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the benefit of this new extension method? The
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for your review @latonz! Got to be honest, I don't know why the old one ( Now I'm guessing it has to do with different Roslyn versions or the like, in which case I don't know a good approach other than iterating the list twice..
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It may just be a leftover of a refactoring… |
||
| public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> enumerable) | ||
| { | ||
| using var enumerator = enumerable.GetEnumerator(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| using System.Collections.Immutable; | ||
| using Microsoft.CodeAnalysis; | ||
| using Riok.Mapperly.Diagnostics; | ||
|
|
||
| namespace Riok.Mapperly.Tests.DocumentationDiagnostics; | ||
|
|
||
| public class MapperIgnoreJustificationTests | ||
| { | ||
| private TestHelperOptions TestHelperOptions => | ||
| TestHelperOptions.Default with | ||
| { | ||
| IgnoredDiagnostics = TestHelperOptions | ||
| .DefaultIgnoredDiagnostics.Except([DiagnosticDescriptors.MapperIgnoreAttributeMissingJustification]) | ||
| .ToImmutableHashSet(), | ||
| AllowedDiagnosticSeverities = new HashSet<DiagnosticSeverity>() { DiagnosticSeverity.Hidden }, | ||
| }; | ||
|
Comment on lines
+9
to
+16
|
||
|
|
||
| [Fact] | ||
| public void MapperIgnoreSourceShouldNotReportAttributeWithJustification() | ||
| { | ||
| var source = TestSourceBuilder.MapperWithBodyAndTypes( | ||
| "[MapperIgnoreSource(nameof(A.Value), Justification = \"Justification\")] partial B Map(A source);", | ||
| """ | ||
| class A | ||
| { | ||
| public int Value { get; set; } | ||
| } | ||
| """, | ||
| """ | ||
| class B { } | ||
| """ | ||
| ); | ||
|
|
||
| TestHelper.GenerateMapper(source, TestHelperOptions).Should().HaveAssertedAllDiagnostics(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void MapperIgnoreSourceShouldReportMissingJustification() | ||
| { | ||
| var source = TestSourceBuilder.MapperWithBodyAndTypes( | ||
| "[MapperIgnoreSource(nameof(A.Value))] partial B Map(A source);", | ||
| """ | ||
| class A | ||
| { | ||
| public int Value { get; set; } | ||
| } | ||
| """, | ||
| """ | ||
| class B { } | ||
| """ | ||
| ); | ||
|
|
||
| TestHelper | ||
| .GenerateMapper(source, TestHelperOptions) | ||
| .Should() | ||
| .HaveDiagnostic( | ||
| DiagnosticDescriptors.MapperIgnoreAttributeMissingJustification, | ||
| "The ignored mapping of Value does not specify a Justification, consider adding one for documentation purposes" | ||
| ) | ||
| .HaveAssertedAllDiagnostics(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -228,7 +228,7 @@ public Task WithBaseTypeMapPropertyAndSeparateMethodShouldWork() | |
| [MapProperty(nameof(A.BaseValueA), nameof(B.BaseValueB)] | ||
| public partial B Map(A src); | ||
|
|
||
| [MapperIgnoreSource(nameof(A.BaseValueA)] | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I stumbled across this while going through the failing snapshot tests:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the way to go would be disabling the new diagnostic for all tests except where we explicitly want to test it. I think you should be able to control editorsettings configs in the tests using
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops, my comment does not really answer your question but a question you raised in the PR description. It sometimes still works, even if the syntax tree is not parsed 100% correct as the parsed parts may still be enough fot mapperly to work🤷 |
||
| [MapperIgnoreSource(nameof(A.BaseValueA), Justification = "BaseValueA is ignored for testing purposes")] | ||
| [MapperIgnoreTarget(nameof(B.BaseValueB)] | ||
|
Comment on lines
228
to
232
|
||
| public partial BSubType1 Map(ASubType1 src); | ||
| """, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,13 +19,18 @@ public record TestHelperOptions( | |
| IReadOnlyDictionary<string, string>? AnalyzerConfigOptions = null | ||
| ) | ||
| { | ||
| public static readonly IReadOnlySet<DiagnosticDescriptor> DefaultIgnoredDiagnostics = new HashSet<DiagnosticDescriptor> | ||
| { | ||
| // ignore NoMemberMappings as a lot of tests use this for simplicity | ||
| DiagnosticDescriptors.NoMemberMappings, | ||
| // ignore missing Justification properties unless specifically tested for, | ||
| // in which case the diagnostic is removed before using the options. | ||
| DiagnosticDescriptors.MapperIgnoreAttributeMissingJustification, | ||
| }; | ||
|
Comment on lines
+22
to
+29
|
||
|
|
||
| public static readonly TestHelperOptions Default = new( | ||
| AllowedDiagnosticSeverities: new HashSet<DiagnosticSeverity>(), | ||
| IgnoredDiagnostics: new HashSet<DiagnosticDescriptor> | ||
| { | ||
| // ignore NoMemberMappings as a lot of tests use this for simplicity | ||
| DiagnosticDescriptors.NoMemberMappings, | ||
| } | ||
| IgnoredDiagnostics: DefaultIgnoredDiagnostics | ||
| ); | ||
|
|
||
| public static readonly TestHelperOptions DisabledNullable = Default with { NullableOption = NullableContextOptions.Disable }; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: lowercase
justificationThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same at several locations in this document.