Skip to content

Commit 6fd4aa7

Browse files
committed
feat: justifications for all
1 parent 8786bf1 commit 6fd4aa7

31 files changed

Lines changed: 375 additions & 108 deletions
Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,39 @@
11
---
22
sidebar_label: RMG096
3-
description: 'Mapperly analyzer diagnostic RMG096 — Ignored mapping is missing Justification'
3+
description: 'Mapperly analyzer diagnostic RMG096 — Ignored mapping is missing justification'
44
---
55

6-
# RMG096 — Ignored mapping is missing Justification
6+
# RMG096 — Ignored mapping is missing justification
77

8-
If Mapperly detects a mapping ignore without a Justification, `RMG096` is emitted.
8+
If Mapperly detects a mapping ignore without a non-whitespace `Justification`, `RMG096` is emitted.
99

1010
The following attributes may raise this diagnostic:
11-
11+
12+
- `MapperIgnore`
1213
- `MapperIgnoreSource`
1314
- `MapperIgnoreTarget`
1415
- `MapperIgnoreSourceValue`
1516
- `MapperIgnoreTargetValue`
1617

17-
To fix this, add a Justification to the attribute, for example:
18+
To fix this, add a `Justification` to the attribute, for example:
1819

1920
```csharp
20-
public partial class Mapper {
21-
[MapperIgnoreSource(nameof(SourceClass.IgnoredProperty), Justification = "This source member is not relevant for the target mapping")]
22-
public partial TargetClass Map(SourceClass source);
21+
[Mapper]
22+
public partial class Mapper
23+
{
24+
[MapperIgnoreSource(
25+
nameof(SourceClass.IgnoredProperty),
26+
Justification = "This source member is not relevant for the target mapping"
27+
)]
28+
public partial TargetClass Map(SourceClass source);
2329
}
2430

25-
class SourceClass {
26-
string IgnoredProperty { get; set; }
31+
class SourceClass
32+
{
33+
public string IgnoredProperty { get; set; }
2734
}
2835

29-
class TargetClass { }
30-
```
36+
class TargetClass
37+
{
38+
}
39+
```

src/Riok.Mapperly.Abstractions/MapperIgnoreAttribute.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,15 @@ namespace Riok.Mapperly.Abstractions;
1111
/// </remarks>
1212
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method)]
1313
[Conditional("MAPPERLY_ABSTRACTIONS_SCOPE_RUNTIME")]
14-
public sealed class MapperIgnoreAttribute : Attribute;
14+
public sealed class MapperIgnoreAttribute : Attribute
15+
{
16+
/// <summary>
17+
/// Gets or sets the justification for ignoring the member or method.
18+
/// This is only used for documentation purposes and does not have any effect on the mapping.
19+
/// </summary>
20+
/// <remarks>
21+
/// You can enforce the presence of justifications by setting the diagnostic severity of <c>RMG096</c> in your
22+
/// <c>.editorconfig</c> to any value other than <c>hidden</c>.
23+
/// </remarks>
24+
public string? Justification { get; set; }
25+
}

src/Riok.Mapperly.Abstractions/MapperIgnoreSourceAttribute.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ public MapperIgnoreSourceAttribute(string source)
2222
/// Gets the source property name which should be ignored from the mapping.
2323
/// </summary>
2424
public string Source { get; }
25-
25+
2626
/// <summary>
2727
/// Gets or sets the justification for ignoring the source property.
2828
/// This is only used for documentation purposes and does not have any effect on the mapping.
2929
/// </summary>
3030
/// <remarks>
31-
/// You can enforce the presence of Justifications by setting the diagnostic severity of <c>RMG096</c> in your
31+
/// You can enforce the presence of justifications by setting the diagnostic severity of <c>RMG096</c> in your
3232
/// <c>.editorconfig</c> to any value other than <c>hidden</c>.
3333
/// </remarks>
3434
public string? Justification { get; set; }

src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValueAttribute.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,14 @@ public MapperIgnoreSourceValueAttribute(object source)
2222
/// Gets the source enum value which should be ignored from the mapping.
2323
/// </summary>
2424
public Enum? SourceValue { get; }
25+
26+
/// <summary>
27+
/// Gets or sets the justification for ignoring the source enum value.
28+
/// This is only used for documentation purposes and does not have any effect on the mapping.
29+
/// </summary>
30+
/// <remarks>
31+
/// You can enforce the presence of justifications by setting the diagnostic severity of <c>RMG096</c> in your
32+
/// <c>.editorconfig</c> to any value other than <c>hidden</c>.
33+
/// </remarks>
34+
public string? Justification { get; set; }
2535
}

src/Riok.Mapperly.Abstractions/MapperIgnoreTargetAttribute.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,14 @@ public MapperIgnoreTargetAttribute(string target)
2222
/// Gets the target property name which should be ignored from the mapping.
2323
/// </summary>
2424
public string Target { get; }
25+
26+
/// <summary>
27+
/// Gets or sets the justification for ignoring the target property.
28+
/// This is only used for documentation purposes and does not have any effect on the mapping.
29+
/// </summary>
30+
/// <remarks>
31+
/// You can enforce the presence of justifications by setting the diagnostic severity of <c>RMG096</c> in your
32+
/// <c>.editorconfig</c> to any value other than <c>hidden</c>.
33+
/// </remarks>
34+
public string? Justification { get; set; }
2535
}

src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValueAttribute.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,14 @@ public MapperIgnoreTargetValueAttribute(object target)
2222
/// Gets the target enum value which should be ignored from the mapping.
2323
/// </summary>
2424
public Enum? TargetValue { get; }
25+
26+
/// <summary>
27+
/// Gets or sets the justification for ignoring the target enum value.
28+
/// This is only used for documentation purposes and does not have any effect on the mapping.
29+
/// </summary>
30+
/// <remarks>
31+
/// You can enforce the presence of justifications by setting the diagnostic severity of <c>RMG096</c> in your
32+
/// <c>.editorconfig</c> to any value other than <c>hidden</c>.
33+
/// </remarks>
34+
public string? Justification { get; set; }
2535
}

src/Riok.Mapperly/AnalyzerReleases.Shipped.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,5 +213,12 @@ RMG091 | Mapper | Error | Circular included mapping configuration detected
213213
RMG092 | Mapper | Error | Source type is not assignable to the included source type
214214
RMG093 | Mapper | Error | Target type is not assignable to the included target type
215215
RMG094 | Mapper | Error | Circular existing target mapping without setter detected
216+
217+
## Release 5.0
218+
219+
### New Rules
220+
221+
Rule ID | Category | Severity | Notes
222+
--------|----------|----------|-------
216223
RMG095 | Mapper | Warning | Invalid MSBuild configuration option
217-
RMG096 | Mapper | Hidden | A MapperIgnore* attribute does not specify a Justification
224+
RMG096 | Mapper | Hidden | A MapperIgnore* attribute does not specify a justification

src/Riok.Mapperly/Configuration/MapperConfigurationReader.cs

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -218,15 +218,13 @@ private MembersMappingConfiguration BuildMembersConfig(MappingConfigurationRefer
218218
return MapperConfiguration.Members;
219219

220220
var ignoreSourceMemberAttributes = _dataAccessor
221-
.Access<MapperIgnoreSourceAttribute>(configRef.Method)
222-
.WhereNotNullBy(static attr => attr.Source)
221+
.Access<MapperIgnoreSourceAttribute, MapperIgnoreMemberConfiguration>(configRef.Method)
223222
.ToList();
224-
var ignoredSourceMembers = ignoreSourceMemberAttributes.Select(x => x.Source).ToList();
223+
var ignoredSourceMembers = ignoreSourceMemberAttributes.Select(x => x.Value).WhereNotNull().ToList();
225224
var ignoreTargetMemberAttributes = _dataAccessor
226-
.Access<MapperIgnoreTargetAttribute>(configRef.Method)
227-
.WhereNotNullBy(static attr => attr.Target)
225+
.Access<MapperIgnoreTargetAttribute, MapperIgnoreMemberConfiguration>(configRef.Method)
228226
.ToList();
229-
var ignoredTargetMembers = ignoreTargetMemberAttributes.Select(x => x.Target).ToList();
227+
var ignoredTargetMembers = ignoreTargetMemberAttributes.Select(x => x.Value).WhereNotNull().ToList();
230228
var memberValueConfigurations = _dataAccessor.Access<MapValueAttribute, MemberValueMappingConfiguration>(configRef.Method).ToList();
231229
var memberConfigurations = _dataAccessor
232230
.Access<MapPropertyAttribute, MemberMappingConfiguration>(configRef.Method)
@@ -259,14 +257,8 @@ private MembersMappingConfiguration BuildMembersConfig(MappingConfigurationRefer
259257
return MapperConfiguration.Members;
260258
}
261259

262-
foreach (var missingIgnoreSourceJustificationAttribute in ignoreSourceMemberAttributes.Where(attr => attr.Justification is null))
263-
{
264-
_diagnostics.ReportDiagnostic(
265-
DiagnosticDescriptors.MapperIgnoreAttributeMissingJustification,
266-
configRef.Method,
267-
missingIgnoreSourceJustificationAttribute.Source
268-
);
269-
}
260+
ReportMissingJustificationDiagnostics(ignoreSourceMemberAttributes, static x => x.Value);
261+
ReportMissingJustificationDiagnostics(ignoreTargetMemberAttributes, static x => x.Value);
270262

271263
foreach (var invalidMemberConfig in memberValueConfigurations.Where(x => !x.IsValid))
272264
{
@@ -296,16 +288,19 @@ private EnumMappingConfiguration BuildEnumConfig(MappingConfigurationReference c
296288

297289
var configData = _dataAccessor.AccessFirstOrDefault<MapEnumAttribute, EnumConfiguration>(configRef.Method);
298290
var explicitMappings = _dataAccessor.Access<MapEnumValueAttribute, EnumValueMappingConfiguration>(configRef.Method).ToList();
299-
var ignoredSources = _dataAccessor
291+
var ignoredSourceValueConfigurations = _dataAccessor
300292
.Access<MapperIgnoreSourceValueAttribute, MapperIgnoreEnumValueConfiguration>(configRef.Method)
301-
.Select(x => x.Value)
302293
.ToList();
303-
var ignoredTargets = _dataAccessor
294+
var ignoredSources = ignoredSourceValueConfigurations.Select(x => x.Value).ToList();
295+
var ignoredTargetValueConfigurations = _dataAccessor
304296
.Access<MapperIgnoreTargetValueAttribute, MapperIgnoreEnumValueConfiguration>(configRef.Method)
305-
.Select(x => x.Value)
306297
.ToList();
298+
var ignoredTargets = ignoredTargetValueConfigurations.Select(x => x.Value).ToList();
307299
var requiredMapping = _dataAccessor.AccessFirstOrDefault<MapperRequiredMappingAttribute>(configRef.Method)?.RequiredMappingStrategy;
308300

301+
ReportMissingJustificationDiagnostics(ignoredSourceValueConfigurations, static x => x.Value.Name);
302+
ReportMissingJustificationDiagnostics(ignoredTargetValueConfigurations, static x => x.Value.Name);
303+
309304
// ignore the required mapping as the same attribute is used for other mapping types
310305
// e.g. object to object
311306
var hasEnumConfigs = configData != null || explicitMappings.Count > 0 || ignoredSources.Count > 0 || ignoredTargets.Count > 0;
@@ -326,4 +321,23 @@ private EnumMappingConfiguration BuildEnumConfig(MappingConfigurationReference c
326321
configData?.NamingStrategy ?? MapperConfiguration.Enum.NamingStrategy
327322
);
328323
}
324+
325+
private void ReportMissingJustificationDiagnostics<TConfiguration>(
326+
IEnumerable<TConfiguration> ignoreConfigurations,
327+
Func<TConfiguration, string?> ignoredNameAccessor
328+
)
329+
where TConfiguration : MapperIgnoreConfigurationBase
330+
{
331+
foreach (var ignoreConfiguration in ignoreConfigurations)
332+
{
333+
if (!string.IsNullOrWhiteSpace(ignoreConfiguration.Justification))
334+
continue;
335+
336+
var ignoredName = ignoredNameAccessor(ignoreConfiguration);
337+
if (ignoredName == null)
338+
continue;
339+
340+
_diagnostics.ReportDiagnostic(DiagnosticDescriptors.IgnoreMissingJustification, ignoreConfiguration.Location, ignoredName);
341+
}
342+
}
329343
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
namespace Riok.Mapperly.Configuration;
2+
3+
public record MapperIgnoreConfiguration : MapperIgnoreConfigurationBase;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Riok.Mapperly.Configuration;
2+
3+
public abstract record MapperIgnoreConfigurationBase : HasSyntaxReference
4+
{
5+
public string? Justification { get; set; }
6+
}

0 commit comments

Comments
 (0)