From 70756466a573b7b622607d721d3d0d36b878c99b Mon Sep 17 00:00:00 2001 From: Sergey Rogatnev Date: Sun, 24 May 2026 11:30:45 +0700 Subject: [PATCH 1/4] Support Ignore() method: ForMember(it => it.Prop, opt => opt.Ignore()) --- ...oMapperNavigationActionAvailabilityTest.cs | 2 + .../data/Navigation/TestIgnoreProperty.cs | 22 ++++++ .../Navigation/AutoMapperNavigationAction.cs | 3 + .../Registrations/AutoMapperMapping.cs | 7 +- .../AutoMapperRegistrationFinder.cs | 67 ++++++++++++++++++- 5 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/dotnet/ReSharperPlugin.AutoMapper.FindUsage.Tests/test/data/Navigation/TestIgnoreProperty.cs diff --git a/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage.Tests/Navigation/AutoMapperNavigationActionAvailabilityTest.cs b/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage.Tests/Navigation/AutoMapperNavigationActionAvailabilityTest.cs index 1c6ffe8..05c4812 100644 --- a/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage.Tests/Navigation/AutoMapperNavigationActionAvailabilityTest.cs +++ b/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage.Tests/Navigation/AutoMapperNavigationActionAvailabilityTest.cs @@ -25,6 +25,8 @@ public class AutoMapperNavigationActionAvailabilityTest [Test] public void TestConfigurationExpression() => DoNamedTest(); + [Test] public void TestIgnoreProperty() => DoNamedTest(); + [Test] public void TestNotAvailableOnGetter() => DoNamedTest(); [Test] public void TestNotAvailableWithoutMapping() => DoNamedTest(); diff --git a/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage.Tests/test/data/Navigation/TestIgnoreProperty.cs b/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage.Tests/test/data/Navigation/TestIgnoreProperty.cs new file mode 100644 index 0000000..51f2958 --- /dev/null +++ b/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage.Tests/test/data/Navigation/TestIgnoreProperty.cs @@ -0,0 +1,22 @@ +using AutoMapper; + +namespace TestProject; + +public class SourceDto +{ + public string Name { get; set; } +} + +public class DestinationDto +{ + public string Name { get; set{off}; } +} + +public class TestProfile : Profile +{ + public TestProfile() + { + CreateMap() + .ForMember(it => it.Name, exp => exp.Ignore()); + } +} diff --git a/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage/Navigation/AutoMapperNavigationAction.cs b/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage/Navigation/AutoMapperNavigationAction.cs index 2e6c216..c07f7e9 100644 --- a/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage/Navigation/AutoMapperNavigationAction.cs +++ b/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage/Navigation/AutoMapperNavigationAction.cs @@ -53,6 +53,9 @@ private IEnumerable CreateBulbItemsInternal() foreach (var mapping in mappings) { + if (mapping.IgnoredProperties.Contains(property.ShortName)) + continue; + var otherType = mapping.Source; if (otherType == null) continue; diff --git a/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage/Registrations/AutoMapperMapping.cs b/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage/Registrations/AutoMapperMapping.cs index eac77fe..5c2524f 100644 --- a/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage/Registrations/AutoMapperMapping.cs +++ b/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage/Registrations/AutoMapperMapping.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Collections.Immutable; using JetBrains.ReSharper.Psi; using JetBrains.ReSharper.Psi.Tree; @@ -8,11 +10,14 @@ public sealed class AutoMapperMapping public IType Source { get; } public IType Destination { get; } public ITreeNode Registration { get; } + public ISet IgnoredProperties { get; } - public AutoMapperMapping(IType source, IType destination, ITreeNode registration) + public AutoMapperMapping(IType source, IType destination, ITreeNode registration, + ISet ignoredProperties = null) { Source = source; Destination = destination; Registration = registration; + IgnoredProperties = ignoredProperties ?? ImmutableHashSet.Empty; } } \ No newline at end of file diff --git a/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage/Registrations/AutoMapperRegistrationFinder.cs b/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage/Registrations/AutoMapperRegistrationFinder.cs index 40f61ef..c150b93 100644 --- a/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage/Registrations/AutoMapperRegistrationFinder.cs +++ b/src/dotnet/ReSharperPlugin.AutoMapper.FindUsage/Registrations/AutoMapperRegistrationFinder.cs @@ -98,7 +98,8 @@ public FindExecution Merge(IReference result) { if (IsTargetType(tDest, _targetType)) { - var mapping = new AutoMapperMapping(tSource, tDest, invocation); + var ignoredProperties = GetIgnoredProperties(invocation); + var mapping = new AutoMapperMapping(tSource, tDest, invocation, ignoredProperties); _results.Add(mapping); } @@ -171,4 +172,68 @@ private static bool HasReverseMap(IInvocationExpression invocation) return false; } + + private static ISet GetIgnoredProperties(IInvocationExpression invocation) + { + var ignoredProperties = new HashSet(); + + var current = invocation.Parent; + while (current != null) + { + if (current is IInvocationExpression forMemberInvocation && + forMemberInvocation.InvokedExpression is IReferenceExpression { Reference: var reference } && + reference.GetName() == "ForMember") + { + if (IsIgnore(forMemberInvocation)) + { + var propertyName = GetPropertyName(forMemberInvocation); + if (propertyName != null) + { + ignoredProperties.Add(propertyName); + } + } + } + + if (current is IExpressionStatement) break; + current = current.Parent; + } + + return ignoredProperties; + } + + private static bool IsIgnore(IInvocationExpression forMemberInvocation) + { + // ForMember(it => it.Prop, opt => opt.Ignore()) + if (forMemberInvocation.Arguments.Count < 2) return false; + + var optArg = forMemberInvocation.Arguments[1].Expression; + if (optArg is ILambdaExpression lambda) + { + var body = lambda.BodyExpression; + // Handle opt => opt.Ignore() + if (body is IInvocationExpression + { + InvokedExpression: IReferenceExpression { Reference: var reference } + } && reference.GetName() == "Ignore") + { + return true; + } + } + + return false; + } + + private static string GetPropertyName(IInvocationExpression forMemberInvocation) + { + // ForMember(it => it.Prop, ...) + if (forMemberInvocation.Arguments.Count < 1) return null; + + var propArg = forMemberInvocation.Arguments[0].Expression; + if (propArg is ILambdaExpression { BodyExpression: IReferenceExpression refExp }) + { + return refExp.Reference.GetName(); + } + + return null; + } } \ No newline at end of file From f28339063d236d47cccb276ea4712e1d4af0a165 Mon Sep 17 00:00:00 2001 From: Sergey Rogatnev Date: Sun, 24 May 2026 15:05:20 +0700 Subject: [PATCH 2/4] Update CHANGELOG.md --- CHANGELOG.md | 14 +++++++++++++- README.md | 5 +++-- build.gradle.kts | 17 +++++++++-------- gradle.properties | 2 +- src/rider/main/resources/META-INF/plugin.xml | 2 +- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 957ff50..ced012e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## 0.1.0 - 2026-05-22 +## [0.1.2] - 2026-05-24 +### Added +- Support for `Ignore()` method for properties. Navigation to the source property is not suggested if the destination property is marked as ignored in the AutoMapper configuration. + +## [0.1.1] - 2026-05-24 +### Changed +- Minor changes. + +## [0.1.0] - 2026-05-22 ### Added - Navigation from DTO property to source Model property via `Alt+Enter` (on `set`/`init` accessors). - Support for `CreateMap` configurations. @@ -12,3 +20,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Grouping of mappings by type when multiple mappings are found for the same property. - Automatic use of full type names in the menu when multiple mappings are present. - Integration with ReSharper 2025.3 SDK. + +[0.1.2]: https://github.com/Backs/AutoMapper.FindUsage/compare/v0.1.1...v0.1.2 +[0.1.1]: https://github.com/Backs/AutoMapper.FindUsage/compare/v0.1.0...v0.1.1 +[0.1.0]: https://github.com/Backs/AutoMapper.FindUsage/releases/tag/v0.1.0 diff --git a/README.md b/README.md index b7de93a..2fe3fdf 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # AutoMapper.FindUsage for Rider and ReSharper -[![Rider](https://img.shields.io/jetbrains/plugin/v/31907.svg?label=Rider&colorB=0A7BBB&style=for-the-badge&logo=rider)](https://plugins.jetbrains.com/plugin/31907) -[![ReSharper](https://img.shields.io/jetbrains/plugin/v/31907.svg?label=ReSharper&colorB=0A7BBB&style=for-the-badge&logo=resharper)](https://plugins.jetbrains.com/plugin/31907) +[![Build and Publish](https://github.com/Backs/AutoMapper.FindUsage/actions/workflows/build.yml/badge.svg)](https://github.com/Backs/AutoMapper.FindUsage/actions/workflows/build.yml) +[![Rider](https://img.shields.io/jetbrains/plugin/v/31907.svg?label=Rider&colorB=0A7BBB&style=flat&logo=rider)](https://plugins.jetbrains.com/plugin/31907) +[![ReSharper](https://img.shields.io/jetbrains/plugin/v/31908.svg?label=ReSharper&colorB=0A7BBB&style=flat&logo=resharper)](https://plugins.jetbrains.com/plugin/31908) **AutoMapper.FindUsage** is a plugin for JetBrains Rider and ReSharper that provides seamless navigation between DTOs and Models based on your AutoMapper configurations. diff --git a/build.gradle.kts b/build.gradle.kts index 3055480..70d1936 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -120,10 +120,12 @@ tasks.buildPlugin { // TODO: See also org.jetbrains.changelog: https://github.com/JetBrains/gradle-changelog-plugin val changelogText = file("${rootDir}/CHANGELOG.md").readText() - val changelogMatches = Regex("(?s)(-.+?)(?=##|$)").findAll(changelogText) - val changeNotes = changelogMatches.map { - it.groups[1]!!.value.replace("(?s)- ".toRegex(), "\u2022 ").replace("`", "").replace(",", "%2C").replace(";", "%3B") - }.take(1).joinToString() + val latestSection = changelogText.split(Regex("(?m)^## \\[")).drop(1).firstOrNull() ?: "" + val notesContent = latestSection.lines().drop(1).joinToString("\n").trim() + + val changeNotes = Regex("(?m)^- (.*)").findAll(notesContent).map { + it.groups[1]!!.value.replace("`", "").replace(",", "%2C").replace(";", "%3B") + }.joinToString("\u2022 ", prefix = "\u2022 ") val executable: String by setBuildTool.get().extra val arguments = (setBuildTool.get().extra["args"] as List).toMutableList() @@ -158,11 +160,10 @@ tasks.runIde { tasks.patchPluginXml { // TODO: See also org.jetbrains.changelog: https://github.com/JetBrains/gradle-changelog-plugin val changelogText = file("${rootDir}/CHANGELOG.md").readText() - val changelogMatches = Regex("(?s)(-.+?)(?=##|\$)").findAll(changelogText) + val latestSection = changelogText.split(Regex("(?m)^## \\[")).drop(1).firstOrNull() ?: "" + val notesContent = latestSection.lines().drop(1).joinToString("\n").trim() - changeNotes.set(changelogMatches.map { - it.groups[1]!!.value.replace("(?s)\r?\n".toRegex(), "
\n") - }.take(1).joinToString()) + changeNotes.set(notesContent.replace("(?s)\r?\n".toRegex(), "
\n")) } tasks.prepareSandbox { diff --git a/gradle.properties b/gradle.properties index 3c6619c..f580a59 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ DotnetPluginId=ReSharperPlugin.AutoMapper.FindUsage DotnetSolution=ReSharperPlugin.AutoMapper.FindUsage.sln RiderPluginId=me.rogatnev.automapper.findusage -PluginVersion=0.1.1 +PluginVersion=0.1.2 BuildConfiguration=Debug diff --git a/src/rider/main/resources/META-INF/plugin.xml b/src/rider/main/resources/META-INF/plugin.xml index 3ff4f63..220987f 100644 --- a/src/rider/main/resources/META-INF/plugin.xml +++ b/src/rider/main/resources/META-INF/plugin.xml @@ -1,7 +1,7 @@ me.rogatnev.automapper.findusage AutoMapper.FindUsage - 0.1.1 + 0.1.2 Sergey Rogatnev com.intellij.modules.rider From 3c9c0d58430e5d6ebc585e6174a464f1d6c258fd Mon Sep 17 00:00:00 2001 From: Sergey Rogatnev Date: Tue, 26 May 2026 19:25:43 +0700 Subject: [PATCH 3/4] Fix README.md --- README.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/README.md b/README.md index 2fe3fdf..4488168 100644 --- a/README.md +++ b/README.md @@ -39,15 +39,6 @@ CreateMap().ReverseMap(); You can install the plugin directly from the [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/31907-automapper-findusage). -Alternatively, you can build it from source: - -1. Clone the repository. -2. Build the solution: - ```bash - dotnet build - ``` -3. Use the instructions in the [Development](#development) section to run and test it. - ## Development To test the plugin in a sandbox environment: From bea64b91b5c0f3052b6e979fb4042002066ddd57 Mon Sep 17 00:00:00 2001 From: Sergey Rogatnev Date: Tue, 26 May 2026 19:32:26 +0700 Subject: [PATCH 4/4] fix build.yml --- .github/workflows/build.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f324f51..6c89b92 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,12 +1,13 @@ name: Build and Publish on: - push: - branches: [ master, publish ] - tags: [ 'v*' ] - pull_request: - branches: [ master ] workflow_dispatch: + inputs: + publish: + description: 'Publish the plugin to JetBrains Marketplace' + required: false + type: boolean + default: false jobs: build: @@ -37,7 +38,7 @@ jobs: run: ./gradlew testDotNet - name: Publish Plugin - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + if: github.event_name == 'workflow_dispatch' && inputs.publish == true shell: pwsh env: PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}