Compare commits
11 Commits
Author | SHA1 | Date |
---|---|---|
Wvader | 9a719a0226 | |
Wvader | b44f5b2fe5 | |
Wvader | 1e266bfd3f | |
Wvader | 55f735006e | |
Wvader | 771adf22ef | |
Wvader | 2bcc521bfa | |
Wvader | 808f353cac | |
Wvader | 9d1e064c3a | |
Wvader | eb32ef8e94 | |
Wvader | e314387772 | |
Wvader | 4d73897b4e |
144
.editorconfig
144
.editorconfig
|
@ -1,72 +1,72 @@
|
||||||
|
|
||||||
[*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,fs,fsi,fsscript,fsx,hlsl,hlsli,hlslinc,master,ml,mli,nuspec,paml,razor,resw,resx,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}]
|
[*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,fs,fsi,fsscript,fsx,hlsl,hlsli,hlslinc,master,ml,mli,nuspec,paml,razor,resw,resx,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
tab_width = 4
|
tab_width = 4
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
|
|
||||||
# Microsoft .NET properties
|
# Microsoft .NET properties
|
||||||
csharp_new_line_before_members_in_object_initializers = false
|
csharp_new_line_before_members_in_object_initializers = false
|
||||||
csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
|
csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
|
||||||
csharp_space_after_cast = false
|
csharp_space_after_cast = false
|
||||||
csharp_style_var_elsewhere = true:suggestion
|
csharp_style_var_elsewhere = true:suggestion
|
||||||
csharp_style_var_for_built_in_types = true:suggestion
|
csharp_style_var_for_built_in_types = true:suggestion
|
||||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||||
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
|
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
|
||||||
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none
|
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none
|
||||||
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
|
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
|
||||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||||
dotnet_style_qualification_for_event = false:suggestion
|
dotnet_style_qualification_for_event = false:suggestion
|
||||||
dotnet_style_qualification_for_field = false:suggestion
|
dotnet_style_qualification_for_field = false:suggestion
|
||||||
dotnet_style_qualification_for_method = false:suggestion
|
dotnet_style_qualification_for_method = false:suggestion
|
||||||
dotnet_style_qualification_for_property = false:suggestion
|
dotnet_style_qualification_for_property = false:suggestion
|
||||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||||
|
|
||||||
# ReSharper properties
|
# ReSharper properties
|
||||||
resharper_autodetect_indent_settings = true
|
resharper_autodetect_indent_settings = true
|
||||||
resharper_blank_lines_after_control_transfer_statements = 1
|
resharper_blank_lines_after_control_transfer_statements = 1
|
||||||
resharper_blank_lines_after_multiline_statements = 1
|
resharper_blank_lines_after_multiline_statements = 1
|
||||||
resharper_blank_lines_around_block_case_section = 1
|
resharper_blank_lines_around_block_case_section = 1
|
||||||
resharper_blank_lines_around_multiline_case_section = 1
|
resharper_blank_lines_around_multiline_case_section = 1
|
||||||
resharper_blank_lines_around_single_line_auto_property = 1
|
resharper_blank_lines_around_single_line_auto_property = 1
|
||||||
resharper_blank_lines_around_single_line_local_method = 1
|
resharper_blank_lines_around_single_line_local_method = 1
|
||||||
resharper_blank_lines_around_single_line_property = 1
|
resharper_blank_lines_around_single_line_property = 1
|
||||||
resharper_braces_for_for = required
|
resharper_braces_for_for = required
|
||||||
resharper_braces_for_foreach = required
|
resharper_braces_for_foreach = required
|
||||||
resharper_braces_for_ifelse = required
|
resharper_braces_for_ifelse = required
|
||||||
resharper_braces_for_while = required
|
resharper_braces_for_while = required
|
||||||
resharper_csharp_blank_lines_around_single_line_invocable = 1
|
resharper_csharp_blank_lines_around_single_line_invocable = 1
|
||||||
resharper_csharp_empty_block_style = together_same_line
|
resharper_csharp_empty_block_style = together_same_line
|
||||||
resharper_csharp_keep_blank_lines_in_code = 1
|
resharper_csharp_keep_blank_lines_in_code = 1
|
||||||
resharper_csharp_keep_blank_lines_in_declarations = 1
|
resharper_csharp_keep_blank_lines_in_declarations = 1
|
||||||
resharper_csharp_max_line_length = 180
|
resharper_csharp_max_line_length = 180
|
||||||
resharper_csharp_wrap_lines = false
|
resharper_csharp_wrap_lines = false
|
||||||
resharper_local_function_body = expression_body
|
resharper_local_function_body = expression_body
|
||||||
resharper_method_or_operator_body = expression_body
|
resharper_method_or_operator_body = expression_body
|
||||||
resharper_place_accessorholder_attribute_on_same_line = false
|
resharper_place_accessorholder_attribute_on_same_line = false
|
||||||
resharper_place_field_attribute_on_same_line = false
|
resharper_place_field_attribute_on_same_line = false
|
||||||
resharper_space_after_cast = false
|
resharper_space_after_cast = false
|
||||||
resharper_space_within_single_line_array_initializer_braces = true
|
resharper_space_within_single_line_array_initializer_braces = true
|
||||||
resharper_use_indent_from_vs = false
|
resharper_use_indent_from_vs = false
|
||||||
resharper_xmldoc_indent_text = ZeroIndent
|
resharper_xmldoc_indent_text = ZeroIndent
|
||||||
|
|
||||||
# ReSharper inspection severities
|
# ReSharper inspection severities
|
||||||
resharper_arguments_style_literal_highlighting = none
|
resharper_arguments_style_literal_highlighting = none
|
||||||
resharper_arguments_style_named_expression_highlighting = none
|
resharper_arguments_style_named_expression_highlighting = none
|
||||||
resharper_arguments_style_other_highlighting = none
|
resharper_arguments_style_other_highlighting = none
|
||||||
resharper_arrange_redundant_parentheses_highlighting = hint
|
resharper_arrange_redundant_parentheses_highlighting = hint
|
||||||
resharper_arrange_this_qualifier_highlighting = hint
|
resharper_arrange_this_qualifier_highlighting = hint
|
||||||
resharper_arrange_type_member_modifiers_highlighting = hint
|
resharper_arrange_type_member_modifiers_highlighting = hint
|
||||||
resharper_arrange_type_modifiers_highlighting = hint
|
resharper_arrange_type_modifiers_highlighting = hint
|
||||||
resharper_built_in_type_reference_style_for_member_access_highlighting = hint
|
resharper_built_in_type_reference_style_for_member_access_highlighting = hint
|
||||||
resharper_built_in_type_reference_style_highlighting = hint
|
resharper_built_in_type_reference_style_highlighting = hint
|
||||||
resharper_class_never_instantiated_global_highlighting = none
|
resharper_class_never_instantiated_global_highlighting = none
|
||||||
resharper_redundant_base_qualifier_highlighting = warning
|
resharper_redundant_base_qualifier_highlighting = warning
|
||||||
resharper_suggest_var_or_type_built_in_types_highlighting = hint
|
resharper_suggest_var_or_type_built_in_types_highlighting = hint
|
||||||
resharper_suggest_var_or_type_elsewhere_highlighting = hint
|
resharper_suggest_var_or_type_elsewhere_highlighting = hint
|
||||||
resharper_suggest_var_or_type_simple_types_highlighting = hint
|
resharper_suggest_var_or_type_simple_types_highlighting = hint
|
||||||
resharper_web_config_module_not_resolved_highlighting = warning
|
resharper_web_config_module_not_resolved_highlighting = warning
|
||||||
resharper_web_config_type_not_resolved_highlighting = warning
|
resharper_web_config_type_not_resolved_highlighting = warning
|
||||||
resharper_web_config_wrong_module_highlighting = warning
|
resharper_web_config_wrong_module_highlighting = warning
|
||||||
|
|
|
@ -1,28 +1,28 @@
|
||||||
name: Build and Test
|
name: Build and Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # avoid shallow clone so nbgv can do its work.
|
fetch-depth: 0 # avoid shallow clone so nbgv can do its work.
|
||||||
- uses: dotnet/nbgv@v0.4.0
|
- uses: dotnet/nbgv@v0.4.0
|
||||||
with:
|
with:
|
||||||
setAllVars: true
|
setAllVars: true
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
dotnet-version: 5.0.101
|
dotnet-version: 5.0.101
|
||||||
- name: Restore dependencies
|
- name: Restore dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
- name: Build
|
- name: Build
|
||||||
run: dotnet build --no-restore
|
run: dotnet build --no-restore
|
||||||
- name: Test
|
- name: Test
|
||||||
run: dotnet test --no-build --verbosity normal
|
run: dotnet test --no-build --verbosity normal
|
||||||
|
|
|
@ -1,37 +1,37 @@
|
||||||
name: Publish Packages
|
name: Publish Packages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # avoid shallow clone so nbgv can do its work.
|
fetch-depth: 0 # avoid shallow clone so nbgv can do its work.
|
||||||
- uses: dotnet/nbgv@v0.4.0
|
- uses: dotnet/nbgv@v0.4.0
|
||||||
with:
|
with:
|
||||||
setAllVars: true
|
setAllVars: true
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
dotnet-version: 5.0.101
|
dotnet-version: 5.0.101
|
||||||
- name: Restore dependencies
|
- name: Restore dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
- name: Build
|
- name: Build
|
||||||
run: dotnet build --configuration Release --no-restore /p:PublicRelease=true
|
run: dotnet build --configuration Release --no-restore /p:PublicRelease=true
|
||||||
- name: Test
|
- name: Test
|
||||||
run: dotnet test --configuration Release --no-build --verbosity normal
|
run: dotnet test --configuration Release --no-build --verbosity normal
|
||||||
- name: Publish MapTo
|
- name: Publish MapTo
|
||||||
uses: brandedoutcast/publish-nuget@v2.5.5
|
uses: brandedoutcast/publish-nuget@v2.5.5
|
||||||
with:
|
with:
|
||||||
PROJECT_FILE_PATH: src/BlueWest.MapTo/BlueWest.MapTo.csproj
|
PROJECT_FILE_PATH: src/MapTo/MapTo.csproj
|
||||||
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
|
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
|
||||||
NUGET_SOURCE: https://api.nuget.org
|
NUGET_SOURCE: https://api.nuget.org
|
||||||
TAG_COMMIT: false
|
TAG_COMMIT: false
|
||||||
INCLUDE_SYMBOLS: true
|
INCLUDE_SYMBOLS: true
|
||||||
VERSION_STATIC: ${{env.NBGV_SemVer1}}
|
VERSION_STATIC: ${{env.NBGV_SemVer1}}
|
||||||
|
|
|
@ -1,442 +1,442 @@
|
||||||
## Ignore Visual Studio temporary files, build results, and
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
## files generated by popular Visual Studio add-ons.
|
## files generated by popular Visual Studio add-ons.
|
||||||
##
|
##
|
||||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
|
|
||||||
# User-specific files
|
# User-specific files
|
||||||
*.rsuser
|
*.rsuser
|
||||||
*.suo
|
*.suo
|
||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
*.userprefs
|
*.userprefs
|
||||||
|
|
||||||
# Mono auto generated files
|
# Mono auto generated files
|
||||||
mono_crash.*
|
mono_crash.*
|
||||||
|
|
||||||
# Build results
|
# Build results
|
||||||
[Dd]ebug/
|
[Dd]ebug/
|
||||||
[Dd]ebugPublic/
|
[Dd]ebugPublic/
|
||||||
[Rr]elease/
|
[Rr]elease/
|
||||||
[Rr]eleases/
|
[Rr]eleases/
|
||||||
x64/
|
x64/
|
||||||
x86/
|
x86/
|
||||||
[Aa][Rr][Mm]/
|
[Aa][Rr][Mm]/
|
||||||
[Aa][Rr][Mm]64/
|
[Aa][Rr][Mm]64/
|
||||||
bld/
|
bld/
|
||||||
[Bb]in/
|
[Bb]in/
|
||||||
[Oo]bj/
|
[Oo]bj/
|
||||||
[Ll]og/
|
[Ll]og/
|
||||||
[Ll]ogs/
|
[Ll]ogs/
|
||||||
|
|
||||||
# Visual Studio 2015/2017 cache/options directory
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
.vs/
|
.vs/
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
#wwwroot/
|
#wwwroot/
|
||||||
|
|
||||||
# Visual Studio 2017 auto generated files
|
# Visual Studio 2017 auto generated files
|
||||||
Generated\ Files/
|
Generated\ Files/
|
||||||
|
|
||||||
# MSTest test Results
|
# MSTest test Results
|
||||||
[Tt]est[Rr]esult*/
|
[Tt]est[Rr]esult*/
|
||||||
[Bb]uild[Ll]og.*
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
# NUnit
|
# NUnit
|
||||||
*.VisualState.xml
|
*.VisualState.xml
|
||||||
TestResult.xml
|
TestResult.xml
|
||||||
nunit-*.xml
|
nunit-*.xml
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
# Build Results of an ATL Project
|
||||||
[Dd]ebugPS/
|
[Dd]ebugPS/
|
||||||
[Rr]eleasePS/
|
[Rr]eleasePS/
|
||||||
dlldata.c
|
dlldata.c
|
||||||
|
|
||||||
# Benchmark Results
|
# Benchmark Results
|
||||||
BenchmarkDotNet.Artifacts/
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
# .NET Core
|
# .NET Core
|
||||||
project.lock.json
|
project.lock.json
|
||||||
project.fragment.lock.json
|
project.fragment.lock.json
|
||||||
artifacts/
|
artifacts/
|
||||||
|
|
||||||
# StyleCop
|
# StyleCop
|
||||||
StyleCopReport.xml
|
StyleCopReport.xml
|
||||||
|
|
||||||
# Files built by Visual Studio
|
# Files built by Visual Studio
|
||||||
*_i.c
|
*_i.c
|
||||||
*_p.c
|
*_p.c
|
||||||
*_h.h
|
*_h.h
|
||||||
*.ilk
|
*.ilk
|
||||||
*.meta
|
*.meta
|
||||||
*.obj
|
*.obj
|
||||||
*.iobj
|
*.iobj
|
||||||
*.pch
|
*.pch
|
||||||
*.pdb
|
*.pdb
|
||||||
*.ipdb
|
*.ipdb
|
||||||
*.pgc
|
*.pgc
|
||||||
*.pgd
|
*.pgd
|
||||||
*.rsp
|
*.rsp
|
||||||
*.sbr
|
*.sbr
|
||||||
*.tlb
|
*.tlb
|
||||||
*.tli
|
*.tli
|
||||||
*.tlh
|
*.tlh
|
||||||
*.tmp
|
*.tmp
|
||||||
*.tmp_proj
|
*.tmp_proj
|
||||||
*_wpftmp.csproj
|
*_wpftmp.csproj
|
||||||
*.log
|
*.log
|
||||||
*.vspscc
|
*.vspscc
|
||||||
*.vssscc
|
*.vssscc
|
||||||
.builds
|
.builds
|
||||||
*.pidb
|
*.pidb
|
||||||
*.svclog
|
*.svclog
|
||||||
*.scc
|
*.scc
|
||||||
|
|
||||||
# Chutzpah Test files
|
# Chutzpah Test files
|
||||||
_Chutzpah*
|
_Chutzpah*
|
||||||
|
|
||||||
# Visual C++ cache files
|
# Visual C++ cache files
|
||||||
ipch/
|
ipch/
|
||||||
*.aps
|
*.aps
|
||||||
*.ncb
|
*.ncb
|
||||||
*.opendb
|
*.opendb
|
||||||
*.opensdf
|
*.opensdf
|
||||||
*.sdf
|
*.sdf
|
||||||
*.cachefile
|
*.cachefile
|
||||||
*.VC.db
|
*.VC.db
|
||||||
*.VC.VC.opendb
|
*.VC.VC.opendb
|
||||||
|
|
||||||
# Visual Studio profiler
|
# Visual Studio profiler
|
||||||
*.psess
|
*.psess
|
||||||
*.vsp
|
*.vsp
|
||||||
*.vspx
|
*.vspx
|
||||||
*.sap
|
*.sap
|
||||||
|
|
||||||
# Visual Studio Trace Files
|
# Visual Studio Trace Files
|
||||||
*.e2e
|
*.e2e
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
# TFS 2012 Local Workspace
|
||||||
$tf/
|
$tf/
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
# Guidance Automation Toolkit
|
||||||
*.gpState
|
*.gpState
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
# ReSharper is a .NET coding add-in
|
||||||
_ReSharper*/
|
_ReSharper*/
|
||||||
*.[Rr]e[Ss]harper
|
*.[Rr]e[Ss]harper
|
||||||
*.DotSettings.user
|
*.DotSettings.user
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
# TeamCity is a build add-in
|
||||||
_TeamCity*
|
_TeamCity*
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
# DotCover is a Code Coverage Tool
|
||||||
*.dotCover
|
*.dotCover
|
||||||
|
|
||||||
# AxoCover is a Code Coverage Tool
|
# AxoCover is a Code Coverage Tool
|
||||||
.axoCover/*
|
.axoCover/*
|
||||||
!.axoCover/settings.json
|
!.axoCover/settings.json
|
||||||
|
|
||||||
# Coverlet is a free, cross platform Code Coverage Tool
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
coverage*[.json, .xml, .info]
|
coverage*[.json, .xml, .info]
|
||||||
|
|
||||||
# Visual Studio code coverage results
|
# Visual Studio code coverage results
|
||||||
*.coverage
|
*.coverage
|
||||||
*.coveragexml
|
*.coveragexml
|
||||||
|
|
||||||
# NCrunch
|
# NCrunch
|
||||||
_NCrunch_*
|
_NCrunch_*
|
||||||
.*crunch*.local.xml
|
.*crunch*.local.xml
|
||||||
nCrunchTemp_*
|
nCrunchTemp_*
|
||||||
|
|
||||||
# MightyMoose
|
# MightyMoose
|
||||||
*.mm.*
|
*.mm.*
|
||||||
AutoTest.Net/
|
AutoTest.Net/
|
||||||
|
|
||||||
# Web workbench (sass)
|
# Web workbench (sass)
|
||||||
.sass-cache/
|
.sass-cache/
|
||||||
|
|
||||||
# Installshield output folder
|
# Installshield output folder
|
||||||
[Ee]xpress/
|
[Ee]xpress/
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
# DocProject is a documentation generator add-in
|
||||||
DocProject/buildhelp/
|
DocProject/buildhelp/
|
||||||
DocProject/Help/*.HxT
|
DocProject/Help/*.HxT
|
||||||
DocProject/Help/*.HxC
|
DocProject/Help/*.HxC
|
||||||
DocProject/Help/*.hhc
|
DocProject/Help/*.hhc
|
||||||
DocProject/Help/*.hhk
|
DocProject/Help/*.hhk
|
||||||
DocProject/Help/*.hhp
|
DocProject/Help/*.hhp
|
||||||
DocProject/Help/Html2
|
DocProject/Help/Html2
|
||||||
DocProject/Help/html
|
DocProject/Help/html
|
||||||
|
|
||||||
# Click-Once directory
|
# Click-Once directory
|
||||||
publish/
|
publish/
|
||||||
|
|
||||||
# Publish Web Output
|
# Publish Web Output
|
||||||
*.[Pp]ublish.xml
|
*.[Pp]ublish.xml
|
||||||
*.azurePubxml
|
*.azurePubxml
|
||||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
*.pubxml
|
*.pubxml
|
||||||
*.publishproj
|
*.publishproj
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
# in these scripts will be unencrypted
|
# in these scripts will be unencrypted
|
||||||
PublishScripts/
|
PublishScripts/
|
||||||
|
|
||||||
# NuGet Packages
|
# NuGet Packages
|
||||||
*.nupkg
|
*.nupkg
|
||||||
# NuGet Symbol Packages
|
# NuGet Symbol Packages
|
||||||
*.snupkg
|
*.snupkg
|
||||||
# The packages folder can be ignored because of Package Restore
|
# The packages folder can be ignored because of Package Restore
|
||||||
**/[Pp]ackages/*
|
**/[Pp]ackages/*
|
||||||
# except build/, which is used as an MSBuild target.
|
# except build/, which is used as an MSBuild target.
|
||||||
!**/[Pp]ackages/build/
|
!**/[Pp]ackages/build/
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
#!**/[Pp]ackages/repositories.config
|
#!**/[Pp]ackages/repositories.config
|
||||||
# NuGet v3's project.json files produces more ignorable files
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
*.nuget.props
|
*.nuget.props
|
||||||
*.nuget.targets
|
*.nuget.targets
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
# Microsoft Azure Build Output
|
||||||
csx/
|
csx/
|
||||||
*.build.csdef
|
*.build.csdef
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
# Microsoft Azure Emulator
|
||||||
ecf/
|
ecf/
|
||||||
rcf/
|
rcf/
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
# Windows Store app package directories and files
|
||||||
AppPackages/
|
AppPackages/
|
||||||
BundleArtifacts/
|
BundleArtifacts/
|
||||||
Package.StoreAssociation.xml
|
Package.StoreAssociation.xml
|
||||||
_pkginfo.txt
|
_pkginfo.txt
|
||||||
*.appx
|
*.appx
|
||||||
*.appxbundle
|
*.appxbundle
|
||||||
*.appxupload
|
*.appxupload
|
||||||
|
|
||||||
# Visual Studio cache files
|
# Visual Studio cache files
|
||||||
# files ending in .cache can be ignored
|
# files ending in .cache can be ignored
|
||||||
*.[Cc]ache
|
*.[Cc]ache
|
||||||
# but keep track of directories ending in .cache
|
# but keep track of directories ending in .cache
|
||||||
!?*.[Cc]ache/
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
# Others
|
# Others
|
||||||
ClientBin/
|
ClientBin/
|
||||||
~$*
|
~$*
|
||||||
*~
|
*~
|
||||||
*.dbmdl
|
*.dbmdl
|
||||||
*.dbproj.schemaview
|
*.dbproj.schemaview
|
||||||
*.jfm
|
*.jfm
|
||||||
*.pfx
|
*.pfx
|
||||||
*.publishsettings
|
*.publishsettings
|
||||||
orleans.codegen.cs
|
orleans.codegen.cs
|
||||||
|
|
||||||
# Including strong name files can present a security risk
|
# Including strong name files can present a security risk
|
||||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
#*.snk
|
#*.snk
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
#bower_components/
|
#bower_components/
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
# RIA/Silverlight projects
|
||||||
Generated_Code/
|
Generated_Code/
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
# Backup & report files from converting an old project file
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
# because we have git ;-)
|
# because we have git ;-)
|
||||||
_UpgradeReport_Files/
|
_UpgradeReport_Files/
|
||||||
Backup*/
|
Backup*/
|
||||||
UpgradeLog*.XML
|
UpgradeLog*.XML
|
||||||
UpgradeLog*.htm
|
UpgradeLog*.htm
|
||||||
ServiceFabricBackup/
|
ServiceFabricBackup/
|
||||||
*.rptproj.bak
|
*.rptproj.bak
|
||||||
|
|
||||||
# SQL Server files
|
# SQL Server files
|
||||||
*.mdf
|
*.mdf
|
||||||
*.ldf
|
*.ldf
|
||||||
*.ndf
|
*.ndf
|
||||||
|
|
||||||
# Business Intelligence projects
|
# Business Intelligence projects
|
||||||
*.rdl.data
|
*.rdl.data
|
||||||
*.bim.layout
|
*.bim.layout
|
||||||
*.bim_*.settings
|
*.bim_*.settings
|
||||||
*.rptproj.rsuser
|
*.rptproj.rsuser
|
||||||
*- [Bb]ackup.rdl
|
*- [Bb]ackup.rdl
|
||||||
*- [Bb]ackup ([0-9]).rdl
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
# Microsoft Fakes
|
# Microsoft Fakes
|
||||||
FakesAssemblies/
|
FakesAssemblies/
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
# GhostDoc plugin setting file
|
||||||
*.GhostDoc.xml
|
*.GhostDoc.xml
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
# Node.js Tools for Visual Studio
|
||||||
.ntvs_analysis.dat
|
.ntvs_analysis.dat
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
# Visual Studio 6 build log
|
||||||
*.plg
|
*.plg
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
# Visual Studio 6 workspace options file
|
||||||
*.opt
|
*.opt
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
*.vbw
|
*.vbw
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
# Visual Studio LightSwitch build output
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
**/*.Server/GeneratedArtifacts
|
**/*.Server/GeneratedArtifacts
|
||||||
**/*.Server/ModelManifest.xml
|
**/*.Server/ModelManifest.xml
|
||||||
_Pvt_Extensions
|
_Pvt_Extensions
|
||||||
|
|
||||||
# Paket dependency manager
|
# Paket dependency manager
|
||||||
.paket/paket.exe
|
.paket/paket.exe
|
||||||
paket-files/
|
paket-files/
|
||||||
|
|
||||||
# FAKE - F# Make
|
# FAKE - F# Make
|
||||||
.fake/
|
.fake/
|
||||||
|
|
||||||
# CodeRush personal settings
|
# CodeRush personal settings
|
||||||
.cr/personal
|
.cr/personal
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
# Python Tools for Visual Studio (PTVS)
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
# Cake - Uncomment if you are using it
|
# Cake - Uncomment if you are using it
|
||||||
# tools/**
|
# tools/**
|
||||||
# !tools/packages.config
|
# !tools/packages.config
|
||||||
|
|
||||||
# Tabs Studio
|
# Tabs Studio
|
||||||
*.tss
|
*.tss
|
||||||
|
|
||||||
# Telerik's JustMock configuration file
|
# Telerik's JustMock configuration file
|
||||||
*.jmconfig
|
*.jmconfig
|
||||||
|
|
||||||
# BizTalk build output
|
# BizTalk build output
|
||||||
*.btp.cs
|
*.btp.cs
|
||||||
*.btm.cs
|
*.btm.cs
|
||||||
*.odx.cs
|
*.odx.cs
|
||||||
*.xsd.cs
|
*.xsd.cs
|
||||||
|
|
||||||
# OpenCover UI analysis results
|
# OpenCover UI analysis results
|
||||||
OpenCover/
|
OpenCover/
|
||||||
|
|
||||||
# Azure Stream Analytics local run output
|
# Azure Stream Analytics local run output
|
||||||
ASALocalRun/
|
ASALocalRun/
|
||||||
|
|
||||||
# MSBuild Binary and Structured Log
|
# MSBuild Binary and Structured Log
|
||||||
*.binlog
|
*.binlog
|
||||||
|
|
||||||
# NVidia Nsight GPU debugger configuration file
|
# NVidia Nsight GPU debugger configuration file
|
||||||
*.nvuser
|
*.nvuser
|
||||||
|
|
||||||
# MFractors (Xamarin productivity tool) working folder
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
.mfractor/
|
.mfractor/
|
||||||
|
|
||||||
# Local History for Visual Studio
|
# Local History for Visual Studio
|
||||||
.localhistory/
|
.localhistory/
|
||||||
|
|
||||||
# BeatPulse healthcheck temp database
|
# BeatPulse healthcheck temp database
|
||||||
healthchecksdb
|
healthchecksdb
|
||||||
|
|
||||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
MigrationBackup/
|
MigrationBackup/
|
||||||
|
|
||||||
# Ionide (cross platform F# VS Code tools) working folder
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
.ionide/
|
.ionide/
|
||||||
|
|
||||||
##
|
##
|
||||||
## Visual studio for Mac
|
## Visual studio for Mac
|
||||||
##
|
##
|
||||||
|
|
||||||
|
|
||||||
# globs
|
# globs
|
||||||
Makefile.in
|
Makefile.in
|
||||||
*.userprefs
|
*.userprefs
|
||||||
*.usertasks
|
*.usertasks
|
||||||
config.make
|
config.make
|
||||||
config.status
|
config.status
|
||||||
aclocal.m4
|
aclocal.m4
|
||||||
install-sh
|
install-sh
|
||||||
autom4te.cache/
|
autom4te.cache/
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
tarballs/
|
tarballs/
|
||||||
test-results/
|
test-results/
|
||||||
|
|
||||||
# Mac bundle stuff
|
# Mac bundle stuff
|
||||||
*.dmg
|
*.dmg
|
||||||
*.app
|
*.app
|
||||||
|
|
||||||
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||||
# General
|
# General
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.AppleDouble
|
.AppleDouble
|
||||||
.LSOverride
|
.LSOverride
|
||||||
|
|
||||||
# Icon must end with two \r
|
# Icon must end with two \r
|
||||||
Icon
|
Icon
|
||||||
|
|
||||||
|
|
||||||
# Thumbnails
|
# Thumbnails
|
||||||
._*
|
._*
|
||||||
|
|
||||||
# Files that might appear in the root of a volume
|
# Files that might appear in the root of a volume
|
||||||
.DocumentRevisions-V100
|
.DocumentRevisions-V100
|
||||||
.fseventsd
|
.fseventsd
|
||||||
.Spotlight-V100
|
.Spotlight-V100
|
||||||
.TemporaryItems
|
.TemporaryItems
|
||||||
.Trashes
|
.Trashes
|
||||||
.VolumeIcon.icns
|
.VolumeIcon.icns
|
||||||
.com.apple.timemachine.donotpresent
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
# Directories potentially created on remote AFP share
|
# Directories potentially created on remote AFP share
|
||||||
.AppleDB
|
.AppleDB
|
||||||
.AppleDesktop
|
.AppleDesktop
|
||||||
Network Trash Folder
|
Network Trash Folder
|
||||||
Temporary Items
|
Temporary Items
|
||||||
.apdisk
|
.apdisk
|
||||||
|
|
||||||
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||||
# Windows thumbnail cache files
|
# Windows thumbnail cache files
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
ehthumbs.db
|
ehthumbs.db
|
||||||
ehthumbs_vista.db
|
ehthumbs_vista.db
|
||||||
|
|
||||||
# Dump file
|
# Dump file
|
||||||
*.stackdump
|
*.stackdump
|
||||||
|
|
||||||
# Folder config file
|
# Folder config file
|
||||||
[Dd]esktop.ini
|
[Dd]esktop.ini
|
||||||
|
|
||||||
# Recycle Bin used on file shares
|
# Recycle Bin used on file shares
|
||||||
$RECYCLE.BIN/
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
# Windows Installer files
|
# Windows Installer files
|
||||||
*.cab
|
*.cab
|
||||||
*.msi
|
*.msi
|
||||||
*.msix
|
*.msix
|
||||||
*.msm
|
*.msm
|
||||||
*.msp
|
*.msp
|
||||||
|
|
||||||
# Windows shortcuts
|
# Windows shortcuts
|
||||||
*.lnk
|
*.lnk
|
||||||
|
|
||||||
# JetBrains Rider
|
# JetBrains Rider
|
||||||
.idea/
|
.idea/
|
||||||
*.sln.iml
|
*.sln.iml
|
||||||
|
|
||||||
##
|
##
|
||||||
## Visual Studio Code
|
## Visual Studio Code
|
||||||
##
|
##
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
40
LICENSE
40
LICENSE
|
@ -1,21 +1,21 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2020 Mohammadreza Taikandi
|
Copyright (c) 2020 Mohammadreza Taikandi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The above copyright notice and this permission notice shall be included in all
|
||||||
copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
68
MapTo.sln
68
MapTo.sln
|
@ -1,34 +1,34 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTo", "src\BlueWest.MapTo\BlueWest.MapTo.csproj", "{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTo", "src\MapTo\MapTo.csproj", "{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapToTests", "test\MapTo.Tests\MapTo.Tests.csproj", "{797DA57B-AC7E-468B-8799-44C5A574C0E3}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapToTests", "test\MapTo.Tests\MapTo.Tests.csproj", "{797DA57B-AC7E-468B-8799-44C5A574C0E3}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConsoleApp", "test\TestConsoleApp\TestConsoleApp.csproj", "{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConsoleApp", "test\TestConsoleApp\TestConsoleApp.csproj", "{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTo.Integration.Tests", "test\MapTo.Integration.Tests\MapTo.Integration.Tests.csproj", "{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTo.Integration.Tests", "test\MapTo.Integration.Tests\MapTo.Integration.Tests.csproj", "{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Release|Any CPU.Build.0 = Release|Any CPU
|
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{797DA57B-AC7E-468B-8799-44C5A574C0E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{797DA57B-AC7E-468B-8799-44C5A574C0E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{797DA57B-AC7E-468B-8799-44C5A574C0E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{797DA57B-AC7E-468B-8799-44C5A574C0E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{797DA57B-AC7E-468B-8799-44C5A574C0E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{797DA57B-AC7E-468B-8799-44C5A574C0E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{797DA57B-AC7E-468B-8799-44C5A574C0E3}.Release|Any CPU.Build.0 = Release|Any CPU
|
{797DA57B-AC7E-468B-8799-44C5A574C0E3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Release|Any CPU.Build.0 = Release|Any CPU
|
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Release|Any CPU.Build.0 = Release|Any CPU
|
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mapto/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mapto/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=paramref/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=paramref/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Shouldly/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Shouldly/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=typeparam/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=typeparam/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Usings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Usings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
180
README.md
180
README.md
|
@ -1,91 +1,91 @@
|
||||||
# MapTo
|
# MapTo
|
||||||
[![Nuget](https://img.shields.io/nuget/v/mapto?logo=nuget)](https://www.nuget.org/packages/MapTo/)
|
[![Nuget](https://img.shields.io/nuget/v/mapto?logo=nuget)](https://www.nuget.org/packages/MapTo/)
|
||||||
![Publish Packages](https://github.com/mrtaikandi/MapTo/workflows/Publish%20Packages/badge.svg)
|
![Publish Packages](https://github.com/mrtaikandi/MapTo/workflows/Publish%20Packages/badge.svg)
|
||||||
|
|
||||||
A convention based object to object mapper using [Roslyn source generator](https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.md).
|
A convention based object to object mapper using [Roslyn source generator](https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.md).
|
||||||
|
|
||||||
MapTo is a library to programmatically generate the necessary code to map one object to another during compile-time, eliminating the need to use reflection to map objects and make it much faster in runtime. It provides compile-time safety checks and ease of use by leveraging extension methods.
|
MapTo is a library to programmatically generate the necessary code to map one object to another during compile-time, eliminating the need to use reflection to map objects and make it much faster in runtime. It provides compile-time safety checks and ease of use by leveraging extension methods.
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
```
|
```
|
||||||
dotnet add package MapTo --prerelease
|
dotnet add package MapTo --prerelease
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
MapTo relies on a set of attributes to instruct it on how to generate the mappings. To start, declare the destination class as `partial` and annotate it with `MapFrom` attribute. As its name implies, `MapFrom` attribute tells the library what the source class you want to map from is.
|
MapTo relies on a set of attributes to instruct it on how to generate the mappings. To start, declare the destination class as `partial` and annotate it with `MapFrom` attribute. As its name implies, `MapFrom` attribute tells the library what the source class you want to map from is.
|
||||||
|
|
||||||
```c#
|
```c#
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
|
||||||
namespace App.ViewModels
|
namespace App.ViewModels
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(App.Data.Models.User))]
|
[MapFrom(typeof(App.Data.Models.User))]
|
||||||
public partial class UserViewModel
|
public partial class UserViewModel
|
||||||
{
|
{
|
||||||
public string FirstName { get; }
|
public string FirstName { get; }
|
||||||
|
|
||||||
public string LastName { get; }
|
public string LastName { get; }
|
||||||
|
|
||||||
[IgnoreProperty]
|
[IgnoreProperty]
|
||||||
public string FullName { get; set; }
|
public string FullName { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To get an instance of `UserViewModel` from the `User` class, you can use any of the following methods:
|
To get an instance of `UserViewModel` from the `User` class, you can use any of the following methods:
|
||||||
|
|
||||||
```c#
|
```c#
|
||||||
var user = new User(id: 10) { FirstName = "John", LastName = "Doe" };
|
var user = new User(id: 10) { FirstName = "John", LastName = "Doe" };
|
||||||
|
|
||||||
var vm = user.ToUserViewModel(); // A generated extension method for User class.
|
var vm = user.ToUserViewModel(); // A generated extension method for User class.
|
||||||
|
|
||||||
// OR
|
// OR
|
||||||
vm = new UserViewModel(user); // A generated contructor.
|
vm = new UserViewModel(user); // A generated contructor.
|
||||||
|
|
||||||
// OR
|
// OR
|
||||||
vm = UserViewModel.From(user); // A generated factory method.
|
vm = UserViewModel.From(user); // A generated factory method.
|
||||||
```
|
```
|
||||||
|
|
||||||
> Please refer to [sample console app](https://github.com/mrtaikandi/MapTo/tree/master/test/TestConsoleApp) for a more complete example.
|
> Please refer to [sample console app](https://github.com/mrtaikandi/MapTo/tree/master/test/TestConsoleApp) for a more complete example.
|
||||||
|
|
||||||
## Available Attributes
|
## Available Attributes
|
||||||
### IgnoreProperty
|
### IgnoreProperty
|
||||||
By default, MapTo will include all properties with the same name (case-sensitive), whether read-only or not, in the mapping unless annotating them with the `IgnoreProperty` attribute.
|
By default, MapTo will include all properties with the same name (case-sensitive), whether read-only or not, in the mapping unless annotating them with the `IgnoreProperty` attribute.
|
||||||
```c#
|
```c#
|
||||||
[IgnoreProperty]
|
[IgnoreProperty]
|
||||||
public string FullName { get; set; }
|
public string FullName { get; set; }
|
||||||
```
|
```
|
||||||
|
|
||||||
### MapProperty
|
### MapProperty
|
||||||
This attribute gives you more control over the way the annotated property should get mapped. For instance, if the annotated property should use a property in the source class with a different name.
|
This attribute gives you more control over the way the annotated property should get mapped. For instance, if the annotated property should use a property in the source class with a different name.
|
||||||
|
|
||||||
```c#
|
```c#
|
||||||
[MapProperty(SourcePropertyName = "Id")]
|
[MapProperty(SourcePropertyName = "Id")]
|
||||||
public int Key { get; set; }
|
public int Key { get; set; }
|
||||||
```
|
```
|
||||||
|
|
||||||
### MapTypeConverter
|
### MapTypeConverter
|
||||||
A compilation error gets raised by default if the source and destination properties types are not implicitly convertible, but to convert the incompatible source type to the desired destination type, `MapTypeConverter` can be used.
|
A compilation error gets raised by default if the source and destination properties types are not implicitly convertible, but to convert the incompatible source type to the desired destination type, `MapTypeConverter` can be used.
|
||||||
|
|
||||||
This attribute will accept a type that implements `ITypeConverter<in TSource, out TDestination>` interface.
|
This attribute will accept a type that implements `ITypeConverter<in TSource, out TDestination>` interface.
|
||||||
|
|
||||||
```c#
|
```c#
|
||||||
[MapFrom(typeof(User))]
|
[MapFrom(typeof(User))]
|
||||||
public partial class UserViewModel
|
public partial class UserViewModel
|
||||||
{
|
{
|
||||||
public DateTimeOffset RegisteredAt { get; set; }
|
public DateTimeOffset RegisteredAt { get; set; }
|
||||||
|
|
||||||
[IgnoreProperty]
|
[IgnoreProperty]
|
||||||
public ProfileViewModel Profile { get; set; }
|
public ProfileViewModel Profile { get; set; }
|
||||||
|
|
||||||
[MapTypeConverter(typeof(IdConverter))]
|
[MapTypeConverter(typeof(IdConverter))]
|
||||||
[MapProperty(SourcePropertyName = nameof(User.Id))]
|
[MapProperty(SourcePropertyName = nameof(User.Id))]
|
||||||
public string Key { get; }
|
public string Key { get; }
|
||||||
|
|
||||||
private class IdConverter : ITypeConverter<int, string>
|
private class IdConverter : ITypeConverter<int, string>
|
||||||
{
|
{
|
||||||
public string Convert(int source, object[] converterParameters) => $"{source:X}";
|
public string Convert(int source, object[] converterParameters) => $"{source:X}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
|
@ -1,67 +0,0 @@
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
using MapTo.Extensions;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
||||||
|
|
||||||
namespace MapTo
|
|
||||||
{
|
|
||||||
internal class ClassMappingContext : MappingContext
|
|
||||||
{
|
|
||||||
internal ClassMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
|
||||||
: base(compilation, sourceGenerationOptions, typeSyntax) { }
|
|
||||||
|
|
||||||
protected override ImmutableArray<MappedMember> GetSourceMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
|
||||||
{
|
|
||||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IFieldSymbol>().ToArray();
|
|
||||||
|
|
||||||
return typeSymbol
|
|
||||||
.GetAllMembers()
|
|
||||||
.OfType<IFieldSymbol>()
|
|
||||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
|
||||||
.Select(property => MapField(sourceTypeSymbol, sourceProperties, property))
|
|
||||||
.Where(mappedProperty => mappedProperty is not null)
|
|
||||||
.ToImmutableArray()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ImmutableArray<MappedMember> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass)
|
|
||||||
{
|
|
||||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
|
||||||
|
|
||||||
return typeSymbol
|
|
||||||
.GetAllMembers()
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
|
||||||
.Select(property => MapProperty(sourceTypeSymbol, sourceProperties, property))
|
|
||||||
.Where(mappedProperty => mappedProperty is not null)
|
|
||||||
.ToImmutableArray()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ImmutableArray<MappedMember> GetTypeMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
|
||||||
{
|
|
||||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IFieldSymbol>().ToArray();
|
|
||||||
|
|
||||||
return sourceTypeSymbol
|
|
||||||
.GetAllMembers()
|
|
||||||
.OfType<IFieldSymbol>()
|
|
||||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
|
||||||
.Select(property => MapFieldSimple(typeSymbol, property))
|
|
||||||
.Where(mappedProperty => mappedProperty is not null)
|
|
||||||
.ToImmutableArray()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ImmutableArray<MappedMember> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass)
|
|
||||||
{
|
|
||||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
|
||||||
|
|
||||||
return sourceTypeSymbol
|
|
||||||
.GetAllMembers()
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
|
||||||
.Select(property => MapPropertySimple(typeSymbol, property))
|
|
||||||
.Where(mappedProperty => mappedProperty is not null)
|
|
||||||
.ToImmutableArray()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
using MapTo.Sources;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MapTo.Extensions
|
|
||||||
{
|
|
||||||
internal static class CommonExtensions
|
|
||||||
{
|
|
||||||
internal static SourceBuilder WriteComment(this SourceBuilder builder, string comment = "")
|
|
||||||
{
|
|
||||||
return builder.WriteLine($"// {comment}");
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static SourceBuilder WriteCommentArray(this SourceBuilder builder, IEnumerable<object> enumerable, string name = "")
|
|
||||||
{
|
|
||||||
builder.WriteComment($"Printing Array: {name}");
|
|
||||||
foreach (var o in enumerable)
|
|
||||||
{
|
|
||||||
if (o != null)
|
|
||||||
{
|
|
||||||
builder.WriteComment($" {o.ToString()}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
builder.WriteComment($"End printing Array: {name}");
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static SourceBuilder WriteModelInfo(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine()
|
|
||||||
.WriteComment($" IsTypeUpdatable {model.IsTypeUpdatable}")
|
|
||||||
.WriteComment($" HasMappedBaseClass {model.HasMappedBaseClass.ToString()}")
|
|
||||||
.WriteComment($" Namespace {model.Namespace}")
|
|
||||||
.WriteComment($" Options {model.Options.ToString()}")
|
|
||||||
.WriteComment($" Type {model.Type}")
|
|
||||||
.WriteComment($" TypeIdentifierName {model.TypeIdentifierName}")
|
|
||||||
.WriteComment($" SourceNamespace {targetSourceType.SourceNamespace}")
|
|
||||||
.WriteComment($" SourceTypeFullName {targetSourceType.SourceTypeFullName}")
|
|
||||||
.WriteComment($" SourceTypeIdentifierName {targetSourceType.SourceTypeIdentifierName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static SourceBuilder WriteMappedProperties(this SourceBuilder builder, System.Collections.Immutable.ImmutableArray<MappedMember> mappedProperties)
|
|
||||||
{
|
|
||||||
foreach (var item in mappedProperties)
|
|
||||||
{
|
|
||||||
string str = "";
|
|
||||||
|
|
||||||
if (item.NamedTypeSymbol != null)
|
|
||||||
foreach (var named in item.NamedTypeSymbol?.TypeArguments)
|
|
||||||
{
|
|
||||||
str += $"typeToString: {named.ToString()} ";
|
|
||||||
bool? containedTypeIsJsonEXtension = named?.HasAttribute(MappingContext.JsonExtensionAttributeSymbol);
|
|
||||||
str += $"typeArgumentTypeIsJsonExtensioN: {containedTypeIsJsonEXtension.ToString()}";
|
|
||||||
}
|
|
||||||
|
|
||||||
builder .WriteComment($" Name {item.Name}")
|
|
||||||
.WriteComment($" Type {item.Type}")
|
|
||||||
.WriteComment($" MappedSourcePropertyTypeName {item.MappedSourcePropertyTypeName}")
|
|
||||||
.WriteComment($" IsEnumerable {item.IsEnumerable}")
|
|
||||||
.WriteComment($" FullyQualifiedType {item.FullyQualifiedType}")
|
|
||||||
.WriteComment($" EnumerableTypeArgument {item.EnumerableTypeArgument}")
|
|
||||||
.WriteComment($" SourcePropertyName {item.SourcePropertyName}")
|
|
||||||
.WriteComment($" TypeSymbol {item.FullyQualifiedType.ToString()}")
|
|
||||||
.WriteComment($" isReadOnly {item.isReadOnly.ToString()}")
|
|
||||||
.WriteComment($" isEnumerable {item.isEnumerable.ToString()}")
|
|
||||||
.WriteComment($" INamedTypeSymbol {item.NamedTypeSymbol?.ToString()}")
|
|
||||||
.WriteComment($" INamedTypeSymbolTypeArguments {str}")
|
|
||||||
|
|
||||||
.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,379 +0,0 @@
|
||||||
using MapTo.Sources;
|
|
||||||
using static MapTo.Sources.Constants;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
#pragma warning disable CS8602
|
|
||||||
|
|
||||||
namespace MapTo.Extensions
|
|
||||||
{
|
|
||||||
internal static class CommonSource
|
|
||||||
{
|
|
||||||
internal static SourceCode GenerateStructOrClass(this MappingModel model, string structOrClass)
|
|
||||||
{
|
|
||||||
const bool writeDebugInfo = false;
|
|
||||||
|
|
||||||
List<string> constructorHeaders = new List<string>();
|
|
||||||
|
|
||||||
using var builder = new SourceBuilder()
|
|
||||||
.WriteLine(GeneratedFilesHeader)
|
|
||||||
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
|
|
||||||
.WriteUsings(model.Usings)
|
|
||||||
.WriteLine()
|
|
||||||
// Namespace declaration
|
|
||||||
.WriteLine($"namespace {model.Namespace}")
|
|
||||||
.WriteOpeningBracket();
|
|
||||||
|
|
||||||
builder
|
|
||||||
// Class declaration
|
|
||||||
.WriteLine($"partial {structOrClass} {model.TypeIdentifierName}")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteLine()
|
|
||||||
// Class body
|
|
||||||
.GeneratePublicEmptyConstructor(model, ref constructorHeaders, true)
|
|
||||||
.GeneratePublicConstructor(model, ref constructorHeaders)
|
|
||||||
.GeneratePublicConstructor(model, ref constructorHeaders, true);
|
|
||||||
|
|
||||||
|
|
||||||
if (model.IsTypeUpdatable) builder.GenerateUpdateMethod(model);
|
|
||||||
if (model.IsJsonExtension) builder.WriteToJsonMethod(model);
|
|
||||||
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLine()
|
|
||||||
// End class declaration
|
|
||||||
.WriteClosingBracket()
|
|
||||||
.WriteLine()
|
|
||||||
// End namespace declaration
|
|
||||||
.WriteClosingBracket();
|
|
||||||
|
|
||||||
return new(builder.ToString(), $"{model.Namespace}.{model.TypeIdentifierName}.g.cs");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GeneratePublicConstructor(this SourceBuilder builder, MappingModel model, ref List<string> constructorHeaders, bool filterNonMapped = false)
|
|
||||||
{
|
|
||||||
const string mappingContextParameterName = "context";
|
|
||||||
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
var baseConstructor = /*model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" :*/ string.Empty;
|
|
||||||
var stringBuilder = new StringBuilder();
|
|
||||||
var otherProperties = new List<MappedMember>();
|
|
||||||
|
|
||||||
foreach (var property in targetSourceType.TypeProperties)
|
|
||||||
{
|
|
||||||
if (!targetSourceType.SourceProperties.IsMappedProperty(property) && !filterNonMapped)
|
|
||||||
{
|
|
||||||
stringBuilder.Append(", ");
|
|
||||||
stringBuilder.Append($"{property.FullyQualifiedType} {property.SourcePropertyName.ToCamelCase()}");
|
|
||||||
otherProperties.Add(property);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
foreach (var property in targetSourceType.TypeFields)
|
|
||||||
{
|
|
||||||
if (!targetSourceType.SourceFields.IsMappedProperty(property) && !filterNonMapped)
|
|
||||||
{
|
|
||||||
stringBuilder.Append(", ");
|
|
||||||
stringBuilder.Append($"{property.FullyQualifiedType} {property.SourcePropertyName.ToCamelCase()}");
|
|
||||||
otherProperties.Add(property);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var readOnlyPropertiesArguments = stringBuilder.ToString();
|
|
||||||
|
|
||||||
var constructorHeader =
|
|
||||||
$"public {model.TypeIdentifierName}({targetSourceType.SourceType} {sourceClassParameterName}{readOnlyPropertiesArguments}){baseConstructor}";
|
|
||||||
|
|
||||||
|
|
||||||
bool hasAlreadyConstructor = false;
|
|
||||||
|
|
||||||
foreach (var header in constructorHeaders)
|
|
||||||
{
|
|
||||||
if(constructorHeader.Contains(header)) hasAlreadyConstructor = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAlreadyConstructor) continue;
|
|
||||||
|
|
||||||
constructorHeaders.Add(constructorHeader);
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLine(constructorHeader)
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteAssignmentMethod(model, filterNonMapped ? null : otherProperties.ToArray().ToImmutableArray(), sourceClassParameterName, mappingContextParameterName, filterNonMapped);
|
|
||||||
|
|
||||||
builder.WriteClosingBracket()
|
|
||||||
.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
// End constructor declaration
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GeneratePublicEmptyConstructor(this SourceBuilder builder, MappingModel model, ref List<string> constructorHeaders, bool filterNonMapped = false)
|
|
||||||
{
|
|
||||||
const string mappingContextParameterName = "context";
|
|
||||||
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
var baseConstructor = /*model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" :*/ string.Empty;
|
|
||||||
|
|
||||||
var constructorHeader =
|
|
||||||
$"public {model.TypeIdentifierName}(){baseConstructor}";
|
|
||||||
|
|
||||||
|
|
||||||
bool hasAlreadyConstructor = false;
|
|
||||||
|
|
||||||
foreach (var header in constructorHeaders)
|
|
||||||
{
|
|
||||||
if(constructorHeader.Contains(header)) hasAlreadyConstructor = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAlreadyConstructor) continue;
|
|
||||||
|
|
||||||
constructorHeaders.Add(constructorHeader);
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLine(constructorHeader)
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteClosingBracket()
|
|
||||||
.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
// End constructor declaration
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsMappedProperty(this System.Collections.Immutable.ImmutableArray<MappedMember> properties, MappedMember property)
|
|
||||||
{
|
|
||||||
|
|
||||||
foreach (var prop in properties)
|
|
||||||
{
|
|
||||||
if (prop.Name == property.Name) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder WriteToJsonMethod(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine($"public string ToJson()")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteLine("var stringBuilder = new System.Text.StringBuilder();")
|
|
||||||
.WriteLine(GetStringBuilderAppendNoInterpolation("{"));
|
|
||||||
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
foreach (var property in targetSourceType.TypeProperties)
|
|
||||||
{
|
|
||||||
if (!property.isEnumerable)
|
|
||||||
HandlePropertyEnumerable(builder, property);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder = WriteJsonField(builder, property);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach (var property in targetSourceType.TypeFields)
|
|
||||||
{
|
|
||||||
if (!property.isEnumerable)
|
|
||||||
HandleFieldEnumerable(builder, property);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.WriteLine(GetStringBuilderAppend($"\\\"{property.Name.ToCamelCase()}\\\" : [{GetJsonArrayValue(property, ref builder)}],"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.WriteLine(GetStringBuilderAppendNoInterpolation("}"));
|
|
||||||
builder.WriteLine("return stringBuilder.ToString();");
|
|
||||||
builder.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder WriteJsonField(SourceBuilder builder, MappedMember property)
|
|
||||||
{
|
|
||||||
builder.WriteLine(
|
|
||||||
GetStringBuilderAppend(
|
|
||||||
$"\\\"{property.Name.ToCamelCase()}\\\" : [{GetJsonArrayValue(property, ref builder)}],"));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HandleEnumerable(SourceBuilder builder, MappedMember property)
|
|
||||||
{
|
|
||||||
var symbol = property.ActualSymbol as IPropertySymbol;
|
|
||||||
#pragma warning disable CS8602
|
|
||||||
builder.WriteCommentArray(symbol.Parameters, nameof(symbol.Parameters));
|
|
||||||
#pragma warning restore CS8602
|
|
||||||
builder.WriteCommentArray(symbol.TypeCustomModifiers, nameof(symbol.TypeCustomModifiers));
|
|
||||||
|
|
||||||
builder.WriteComment($"Is enumerable {(property.ActualSymbol as IPropertySymbol).Parameters}");
|
|
||||||
builder.WriteLine(
|
|
||||||
GetStringBuilderAppend($"\\\"{property.Name.ToCamelCase()}\\\" : {GetJsonValue(property, builder)},"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void HandleFieldEnumerable(SourceBuilder builder, MappedMember property)
|
|
||||||
{
|
|
||||||
HandleEnumerable(builder, property);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HandlePropertyEnumerable(SourceBuilder builder, MappedMember property)
|
|
||||||
{
|
|
||||||
HandleEnumerable(builder, property);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetJsonArrayValue(MappedMember member, ref SourceBuilder builder)
|
|
||||||
{
|
|
||||||
if (member.isEnumerable)
|
|
||||||
{
|
|
||||||
// get underlying type (check if is a json extension)
|
|
||||||
|
|
||||||
builder.WriteLine("var arrStrBuilder = new StringBuilder();");
|
|
||||||
|
|
||||||
foreach (var named in member.NamedTypeSymbol?.TypeArguments!)
|
|
||||||
{
|
|
||||||
bool? containedTypeIsJsonEXtension = named?.HasAttribute(MappingContext.JsonExtensionAttributeSymbol);
|
|
||||||
if (!containedTypeIsJsonEXtension.HasValue) continue;
|
|
||||||
builder.WriteLine($"foreach (var v in {member.SourcePropertyName.ToString()})");
|
|
||||||
builder.WriteOpeningBracket();
|
|
||||||
builder.WriteLine("arrStrBuilder.Append(v.ToJson());");
|
|
||||||
builder.WriteLine("arrStrBuilder.Append(\", \");");
|
|
||||||
builder.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
builder.WriteLine("arrStrBuilder.Remove(arrStrBuilder.Length -1, 1);");
|
|
||||||
}
|
|
||||||
|
|
||||||
return "{arrStrBuilder.ToString()}";
|
|
||||||
}
|
|
||||||
private static string GetJsonValue(MappedMember member, SourceBuilder builder)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (member.FullyQualifiedType == "string") return $"\\\"{{{member.SourcePropertyName}}}\\\"";
|
|
||||||
if (member.FullyQualifiedType is "int" or "double" or "float" or "long") return $"{{{member.SourcePropertyName}}}";
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetStringBuilderAppend(string stringToAppend)
|
|
||||||
{
|
|
||||||
return $"stringBuilder.Append($\"{stringToAppend}\");";
|
|
||||||
}
|
|
||||||
private static string GetStringBuilderAppendNoInterpolation(string stringToAppend)
|
|
||||||
{
|
|
||||||
return $"stringBuilder.Append(\"{stringToAppend}\");";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder WriteAssignmentMethod(this SourceBuilder builder, MappingModel model, System.Collections.Immutable.ImmutableArray<MappedMember>? otherProperties,
|
|
||||||
string? sourceClassParameterName, string mappingContextParameterName, bool fromUpdate)
|
|
||||||
{
|
|
||||||
|
|
||||||
List<MappedMember> _addedMembers = new List<MappedMember>();
|
|
||||||
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
foreach (var property in targetSourceType.SourceProperties)
|
|
||||||
{
|
|
||||||
if (property.isReadOnly && fromUpdate) continue;
|
|
||||||
if(_addedMembers.Contains(property)) continue;
|
|
||||||
|
|
||||||
|
|
||||||
builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};");
|
|
||||||
_addedMembers.Add(property);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var property in targetSourceType.SourceFields)
|
|
||||||
{
|
|
||||||
if (property.isReadOnly && fromUpdate) continue;
|
|
||||||
if(_addedMembers.Contains(property)) continue;
|
|
||||||
|
|
||||||
builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};");
|
|
||||||
_addedMembers.Add(property);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherProperties == null) return builder;
|
|
||||||
|
|
||||||
foreach (var property in otherProperties)
|
|
||||||
{
|
|
||||||
if(_addedMembers.Contains(property)) continue;
|
|
||||||
|
|
||||||
builder.WriteLine(property.MappedSourcePropertyTypeName is null
|
|
||||||
? $"{property.Name} = {property.SourcePropertyName.ToCamelCase()};"
|
|
||||||
: "");
|
|
||||||
_addedMembers.Add(property);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateUpdateMethod(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
|
|
||||||
builder
|
|
||||||
.GenerateUpdaterMethodsXmlDocs(model, sourceClassParameterName, targetSourceType)
|
|
||||||
.WriteLine($"public void Update({targetSourceType.SourceType} {sourceClassParameterName})")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteAssignmentMethod(model, null, sourceClassParameterName, "context", true)
|
|
||||||
.WriteClosingBracket()
|
|
||||||
.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateUpdaterMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName,
|
|
||||||
MappedSourceType mappedSourceType)
|
|
||||||
{
|
|
||||||
if (!model.Options.GenerateXmlDocument)
|
|
||||||
{
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
builder
|
|
||||||
.WriteLine("/// <summary>")
|
|
||||||
.WriteLine($"/// Updates <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
|
|
||||||
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
|
||||||
.WriteLine("/// </summary>")
|
|
||||||
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{mappedSourceType.SourceType}\"/> to use as source.</param>");
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateEnumerableJsonSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
|
||||||
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static string ToJson(this IEnumerable<{targetSourceType.SourceType}{model.Options.NullableReferenceSyntax}> {sourceClassParameterName}List)")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
|
|
||||||
.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,630 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using MapTo.Extensions;
|
|
||||||
using MapTo.Sources;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
||||||
#pragma warning disable CS8602
|
|
||||||
|
|
||||||
namespace MapTo
|
|
||||||
{
|
|
||||||
internal static class MappingContextExtensions
|
|
||||||
{
|
|
||||||
internal static ImmutableArray<MappedMember> GetReadOnlyMappedProperties(this ImmutableArray<MappedMember> mappedProperties) => mappedProperties.Where(p => p.isReadOnly).ToImmutableArray()!;
|
|
||||||
internal static ImmutableArray<MappedMember> GetWritableMappedProperties(this ImmutableArray<MappedMember> mappedProperties) => mappedProperties.Where(p => !p.isReadOnly).ToImmutableArray()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal abstract class MappingContext
|
|
||||||
{
|
|
||||||
private readonly List<SymbolDisplayPart> _ignoredNamespaces;
|
|
||||||
|
|
||||||
protected MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
|
||||||
{
|
|
||||||
_ignoredNamespaces = new();
|
|
||||||
Diagnostics = ImmutableArray<Diagnostic>.Empty;
|
|
||||||
Usings = ImmutableArray.Create("System", Constants.RootNamespace);
|
|
||||||
SourceGenerationOptions = sourceGenerationOptions;
|
|
||||||
TypeSyntax = typeSyntax;
|
|
||||||
Compilation = compilation;
|
|
||||||
|
|
||||||
IgnoreMemberAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(IgnoreMemberAttributeSource.FullyQualifiedName);
|
|
||||||
MapTypeConverterAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapTypeConverterAttributeSource.FullyQualifiedName);
|
|
||||||
TypeConverterInterfaceTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(ITypeConverterSource.FullyQualifiedName);
|
|
||||||
MapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName);
|
|
||||||
MapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.FullyQualifiedName);
|
|
||||||
UseUpdateAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(UseUpdateAttributeSource.FullyQualifiedName);
|
|
||||||
JsonExtensionAttributeSymbol = compilation.GetTypeByMetadataNameOrThrow(JsonExtensionAttributeSource.FullyQualifiedName);
|
|
||||||
MappingContextTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MappingContextSource.FullyQualifiedName);
|
|
||||||
|
|
||||||
AddUsingIfRequired(sourceGenerationOptions.SupportNullableStaticAnalysis, "System.Diagnostics.CodeAnalysis");
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImmutableArray<Diagnostic> Diagnostics { get; private set; }
|
|
||||||
|
|
||||||
public MappingModel? Model { get; private set; }
|
|
||||||
|
|
||||||
protected Compilation Compilation { get; }
|
|
||||||
|
|
||||||
protected INamedTypeSymbol IgnoreMemberAttributeTypeSymbol { get; }
|
|
||||||
|
|
||||||
protected INamedTypeSymbol MapFromAttributeTypeSymbol { get; }
|
|
||||||
|
|
||||||
protected INamedTypeSymbol UseUpdateAttributeTypeSymbol { get; }
|
|
||||||
|
|
||||||
public static INamedTypeSymbol JsonExtensionAttributeSymbol { get; set; } = null!;
|
|
||||||
|
|
||||||
protected INamedTypeSymbol MappingContextTypeSymbol { get; }
|
|
||||||
|
|
||||||
protected INamedTypeSymbol MapPropertyAttributeTypeSymbol { get; }
|
|
||||||
|
|
||||||
protected INamedTypeSymbol MapTypeConverterAttributeTypeSymbol { get; }
|
|
||||||
|
|
||||||
protected SourceGenerationOptions SourceGenerationOptions { get; }
|
|
||||||
|
|
||||||
protected INamedTypeSymbol TypeConverterInterfaceTypeSymbol { get; }
|
|
||||||
|
|
||||||
protected TypeDeclarationSyntax TypeSyntax { get; }
|
|
||||||
|
|
||||||
protected ImmutableArray<string> Usings { get; private set; }
|
|
||||||
|
|
||||||
public static MappingContext Create(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
|
||||||
{
|
|
||||||
|
|
||||||
MappingContext context = typeSyntax switch
|
|
||||||
{
|
|
||||||
StructDeclarationSyntax => new StructMappingContext(compilation, sourceGenerationOptions, typeSyntax),
|
|
||||||
ClassDeclarationSyntax => new ClassMappingContext(compilation, sourceGenerationOptions, typeSyntax),
|
|
||||||
RecordDeclarationSyntax => new RecordMappingContext(compilation, sourceGenerationOptions, typeSyntax),
|
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Model = context.CreateMappingModel();
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void AddDiagnostic(Diagnostic diagnostic)
|
|
||||||
{
|
|
||||||
Diagnostics = Diagnostics.Add(diagnostic);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void AddUsingIfRequired(ISymbol? namedTypeSymbol) =>
|
|
||||||
AddUsingIfRequired(namedTypeSymbol?.ContainingNamespace.IsGlobalNamespace == false, namedTypeSymbol?.ContainingNamespace);
|
|
||||||
|
|
||||||
protected void AddUsingIfRequired(bool condition, INamespaceSymbol? ns) =>
|
|
||||||
AddUsingIfRequired(condition && ns is not null && !_ignoredNamespaces.Contains(ns.ToDisplayParts().First()), ns?.ToDisplayString());
|
|
||||||
|
|
||||||
protected void AddUsingIfRequired(bool condition, string? ns)
|
|
||||||
{
|
|
||||||
if (ns is not null && condition && ns != TypeSyntax.GetNamespace() && !Usings.Contains(ns))
|
|
||||||
{
|
|
||||||
Usings = Usings.Add(ns);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected IPropertySymbol? FindSourceProperty(IEnumerable<IPropertySymbol> sourceProperties, ISymbol property)
|
|
||||||
{
|
|
||||||
var propertyName = property
|
|
||||||
.GetAttribute(MapPropertyAttributeTypeSymbol)
|
|
||||||
?.NamedArguments
|
|
||||||
.FirstOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName)
|
|
||||||
.Value.Value as string ?? property.Name;
|
|
||||||
|
|
||||||
return sourceProperties.FirstOrDefault(p => p.Name == propertyName);
|
|
||||||
}
|
|
||||||
protected IFieldSymbol? FindSourceField(IEnumerable<IFieldSymbol> sourceProperties, ISymbol property)
|
|
||||||
{
|
|
||||||
var propertyName = property
|
|
||||||
.GetAttribute(MapPropertyAttributeTypeSymbol)
|
|
||||||
?.NamedArguments
|
|
||||||
.FirstOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName)
|
|
||||||
.Value.Value as string ?? property.Name;
|
|
||||||
|
|
||||||
return sourceProperties.FirstOrDefault(p => p.Name == propertyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract ImmutableArray<MappedMember> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
|
|
||||||
protected abstract ImmutableArray<MappedMember> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
|
|
||||||
|
|
||||||
|
|
||||||
protected abstract ImmutableArray<MappedMember> GetSourceMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
|
|
||||||
protected abstract ImmutableArray<MappedMember> GetTypeMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
|
|
||||||
|
|
||||||
|
|
||||||
protected ImmutableArray<INamedTypeSymbol> GetSourceTypeSymbol(TypeDeclarationSyntax typeDeclarationSyntax, SemanticModel? semanticModel = null)
|
|
||||||
{
|
|
||||||
var attributeData = typeDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName);
|
|
||||||
var sourceSymbol = GetSourceTypeSymbol(attributeData, semanticModel);
|
|
||||||
return sourceSymbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need two possible InamedTypeSymbol
|
|
||||||
protected ImmutableArray<INamedTypeSymbol> GetSourceTypeSymbol(SyntaxNode? attributeSyntax, SemanticModel? semanticModel = null)
|
|
||||||
{
|
|
||||||
if (attributeSyntax is null)
|
|
||||||
{
|
|
||||||
return new ImmutableArray<INamedTypeSymbol>(){};
|
|
||||||
}
|
|
||||||
|
|
||||||
semanticModel ??= Compilation.GetSemanticModel(attributeSyntax.SyntaxTree);
|
|
||||||
var descendentNodes = attributeSyntax
|
|
||||||
.DescendantNodes();
|
|
||||||
|
|
||||||
var sourceTypeExpressionSyntax = descendentNodes
|
|
||||||
.OfType<TypeOfExpressionSyntax>()
|
|
||||||
.ToImmutableArray();
|
|
||||||
|
|
||||||
// cast
|
|
||||||
var resultList = new List<INamedTypeSymbol>();
|
|
||||||
for (int i = 0; i < sourceTypeExpressionSyntax.Length; i++)
|
|
||||||
{
|
|
||||||
var sourceTypeExpression = sourceTypeExpressionSyntax[i];
|
|
||||||
if (semanticModel.GetTypeInfo(sourceTypeExpression.Type).Type is INamedTypeSymbol namedTypeSymbol)
|
|
||||||
{
|
|
||||||
resultList.Add(namedTypeSymbol);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultList.ToImmutableArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool IsTypeInheritFromMappedBaseClass(SemanticModel semanticModel)
|
|
||||||
{
|
|
||||||
return TypeSyntax.BaseList is not null && TypeSyntax.BaseList.Types
|
|
||||||
.Select(t => semanticModel.GetTypeInfo(t.Type).Type)
|
|
||||||
.Any(t => t?.GetAttribute(MapFromAttributeTypeSymbol) != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool IsTypeUpdatable()
|
|
||||||
{
|
|
||||||
return TypeSyntax.GetAttribute("UseUpdate") != null;
|
|
||||||
}
|
|
||||||
protected bool HasJsonExtension()
|
|
||||||
{
|
|
||||||
return TypeSyntax.GetAttribute("JsonExtension") != null;
|
|
||||||
}
|
|
||||||
protected virtual MappedMember? MapProperty(ISymbol sourceTypeSymbol, IReadOnlyCollection<IPropertySymbol> sourceProperties, ISymbol property)
|
|
||||||
{
|
|
||||||
var sourceProperty = FindSourceProperty(sourceProperties, property);
|
|
||||||
if (sourceProperty is null || !property.TryGetTypeSymbol(out var propertyType))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
string? converterFullyQualifiedName = null;
|
|
||||||
var converterParameters = ImmutableArray<string>.Empty;
|
|
||||||
ITypeSymbol? mappedSourcePropertyType = null;
|
|
||||||
ITypeSymbol? enumerableTypeArgumentType = null;
|
|
||||||
|
|
||||||
if (!Compilation.HasCompatibleTypes(sourceProperty, property))
|
|
||||||
{
|
|
||||||
if (!TryGetMapTypeConverterForProperty(property, sourceProperty, out converterFullyQualifiedName, out converterParameters) &&
|
|
||||||
!TryGetNestedObjectMappings(property, out mappedSourcePropertyType, out enumerableTypeArgumentType))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AddUsingIfRequired(propertyType);
|
|
||||||
AddUsingIfRequired(enumerableTypeArgumentType);
|
|
||||||
AddUsingIfRequired(mappedSourcePropertyType);
|
|
||||||
|
|
||||||
INamedTypeSymbol? namedType;
|
|
||||||
var isEnumerable = IsEnumerable(property, out namedType);
|
|
||||||
|
|
||||||
|
|
||||||
return new MappedMember(
|
|
||||||
property.Name,
|
|
||||||
property.GetTypeSymbol().ToString(),
|
|
||||||
ToQualifiedDisplayName(propertyType) ?? propertyType.Name,
|
|
||||||
converterFullyQualifiedName,
|
|
||||||
converterParameters.ToImmutableArray(),
|
|
||||||
sourceProperty.Name,
|
|
||||||
ToQualifiedDisplayName(mappedSourcePropertyType),
|
|
||||||
ToQualifiedDisplayName(enumerableTypeArgumentType),
|
|
||||||
property,
|
|
||||||
namedType,
|
|
||||||
isEnumerable,
|
|
||||||
(property as IPropertySymbol).IsReadOnly);
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual MappedMember? MapField(ISymbol sourceTypeSymbol, IReadOnlyCollection<IFieldSymbol> sourceProperties, ISymbol property)
|
|
||||||
{
|
|
||||||
var sourceProperty = FindSourceField(sourceProperties, property);
|
|
||||||
if (sourceProperty is null || !property.TryGetTypeSymbol(out var propertyType))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (property is IFieldSymbol symbol)
|
|
||||||
{
|
|
||||||
if (symbol.AssociatedSymbol != null) return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? converterFullyQualifiedName = null;
|
|
||||||
var converterParameters = ImmutableArray<string>.Empty;
|
|
||||||
ITypeSymbol? mappedSourcePropertyType = null;
|
|
||||||
ITypeSymbol? enumerableTypeArgumentType = null;
|
|
||||||
|
|
||||||
if (!Compilation.HasCompatibleTypes(sourceProperty, property))
|
|
||||||
{
|
|
||||||
if (!TryGetMapTypeConverterForField(property, sourceProperty, out converterFullyQualifiedName, out converterParameters) &&
|
|
||||||
!TryGetNestedObjectMappings(property, out mappedSourcePropertyType, out enumerableTypeArgumentType))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AddUsingIfRequired(propertyType);
|
|
||||||
AddUsingIfRequired(enumerableTypeArgumentType);
|
|
||||||
AddUsingIfRequired(mappedSourcePropertyType);
|
|
||||||
|
|
||||||
|
|
||||||
INamedTypeSymbol? namedType;
|
|
||||||
var isEnumerable = IsEnumerable(property, out namedType);
|
|
||||||
|
|
||||||
return new MappedMember(
|
|
||||||
property.Name,
|
|
||||||
property.GetTypeSymbol()?.ToString() ?? throw new InvalidOperationException(),
|
|
||||||
ToQualifiedDisplayName(propertyType) ?? propertyType.Name,
|
|
||||||
converterFullyQualifiedName,
|
|
||||||
converterParameters.ToImmutableArray(),
|
|
||||||
sourceProperty.Name,
|
|
||||||
ToQualifiedDisplayName(mappedSourcePropertyType),
|
|
||||||
ToQualifiedDisplayName(enumerableTypeArgumentType),
|
|
||||||
property,
|
|
||||||
namedType,
|
|
||||||
isEnumerable,
|
|
||||||
(property as IFieldSymbol).IsReadOnly);
|
|
||||||
;
|
|
||||||
}
|
|
||||||
protected virtual MappedMember? MapPropertySimple(ISymbol sourceTypeSymbol, ISymbol property)
|
|
||||||
{
|
|
||||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
string? converterFullyQualifiedName = null;
|
|
||||||
var converterParameters = ImmutableArray<string>.Empty;
|
|
||||||
ITypeSymbol? mappedSourcePropertyType = null;
|
|
||||||
ITypeSymbol? enumerableTypeArgumentType = null;
|
|
||||||
|
|
||||||
|
|
||||||
AddUsingIfRequired(propertyType);
|
|
||||||
AddUsingIfRequired(enumerableTypeArgumentType);
|
|
||||||
AddUsingIfRequired(mappedSourcePropertyType);
|
|
||||||
|
|
||||||
INamedTypeSymbol? namedType;
|
|
||||||
var isEnumerable = IsEnumerable(property, out namedType);
|
|
||||||
|
|
||||||
return new MappedMember(
|
|
||||||
property.Name,
|
|
||||||
property.GetTypeSymbol().ToString(),
|
|
||||||
ToQualifiedDisplayName(propertyType) ?? propertyType.Name,
|
|
||||||
converterFullyQualifiedName,
|
|
||||||
converterParameters.ToImmutableArray(),
|
|
||||||
property.Name,
|
|
||||||
ToQualifiedDisplayName(mappedSourcePropertyType),
|
|
||||||
ToQualifiedDisplayName(enumerableTypeArgumentType),
|
|
||||||
property,
|
|
||||||
namedType,
|
|
||||||
isEnumerable,
|
|
||||||
#pragma warning disable CS8602
|
|
||||||
(property as IPropertySymbol).IsReadOnly);
|
|
||||||
#pragma warning restore CS8602
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual MappedMember? MapFieldSimple(ISymbol sourceTypeSymbol, ISymbol property)
|
|
||||||
{
|
|
||||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(property is IFieldSymbol symbol)
|
|
||||||
{
|
|
||||||
if (symbol.AssociatedSymbol != null) return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
string? converterFullyQualifiedName = null;
|
|
||||||
var converterParameters = ImmutableArray<string>.Empty;
|
|
||||||
ITypeSymbol? mappedSourcePropertyType = null;
|
|
||||||
ITypeSymbol? enumerableTypeArgumentType = null;
|
|
||||||
|
|
||||||
|
|
||||||
AddUsingIfRequired(propertyType);
|
|
||||||
AddUsingIfRequired(enumerableTypeArgumentType);
|
|
||||||
AddUsingIfRequired(mappedSourcePropertyType);
|
|
||||||
|
|
||||||
INamedTypeSymbol? namedType;
|
|
||||||
var isEnumerable = IsEnumerable(property, out namedType);
|
|
||||||
|
|
||||||
|
|
||||||
return new MappedMember(
|
|
||||||
property.Name,
|
|
||||||
property.GetTypeSymbol().ToString(),
|
|
||||||
ToQualifiedDisplayName(propertyType) ?? propertyType.Name,
|
|
||||||
converterFullyQualifiedName,
|
|
||||||
converterParameters.ToImmutableArray(),
|
|
||||||
property.Name,
|
|
||||||
ToQualifiedDisplayName(mappedSourcePropertyType),
|
|
||||||
ToQualifiedDisplayName(enumerableTypeArgumentType),
|
|
||||||
property,
|
|
||||||
namedType,
|
|
||||||
isEnumerable,
|
|
||||||
(property as IFieldSymbol).IsReadOnly);
|
|
||||||
;
|
|
||||||
}
|
|
||||||
protected bool TryGetMapTypeConverterForProperty(ISymbol property, IPropertySymbol sourceProperty, out string? converterFullyQualifiedName,
|
|
||||||
out ImmutableArray<string> converterParameters)
|
|
||||||
{
|
|
||||||
converterFullyQualifiedName = null;
|
|
||||||
converterParameters = ImmutableArray<string>.Empty;
|
|
||||||
|
|
||||||
if (!Diagnostics.IsEmpty())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var typeConverterAttribute = property.GetAttribute(MapTypeConverterAttributeTypeSymbol);
|
|
||||||
if (typeConverterAttribute?.ConstructorArguments.First().Value is not INamedTypeSymbol converterTypeSymbol)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var baseInterface = GetTypeConverterBaseInterfaceForProperty(converterTypeSymbol, property, sourceProperty);
|
|
||||||
if (baseInterface is null)
|
|
||||||
{
|
|
||||||
AddDiagnostic(DiagnosticsFactory.InvalidTypeConverterGenericTypesError(property, sourceProperty));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
converterFullyQualifiedName = converterTypeSymbol.ToDisplayString();
|
|
||||||
converterParameters = GetTypeConverterParameters(typeConverterAttribute);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
protected bool TryGetMapTypeConverterForField(ISymbol property, IFieldSymbol sourceProperty, out string? converterFullyQualifiedName,
|
|
||||||
out ImmutableArray<string> converterParameters)
|
|
||||||
{
|
|
||||||
converterFullyQualifiedName = null;
|
|
||||||
converterParameters = ImmutableArray<string>.Empty;
|
|
||||||
|
|
||||||
if (!Diagnostics.IsEmpty())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var typeConverterAttribute = property.GetAttribute(MapTypeConverterAttributeTypeSymbol);
|
|
||||||
if (typeConverterAttribute?.ConstructorArguments.First().Value is not INamedTypeSymbol converterTypeSymbol)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var baseInterface = GetTypeConverterBaseInterfaceForField(converterTypeSymbol, property, sourceProperty);
|
|
||||||
if (baseInterface is null)
|
|
||||||
{
|
|
||||||
//AddDiagnostic(DiagnosticsFactory.InvalidTypeConverterGenericTypesError(property, null));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
converterFullyQualifiedName = converterTypeSymbol.ToDisplayString();
|
|
||||||
converterParameters = GetTypeConverterParameters(typeConverterAttribute);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
protected bool TryGetNestedObjectMappings(ISymbol property, out ITypeSymbol? mappedSourcePropertyType, out ITypeSymbol? enumerableTypeArgument)
|
|
||||||
{
|
|
||||||
mappedSourcePropertyType = null;
|
|
||||||
enumerableTypeArgument = null;
|
|
||||||
|
|
||||||
if (!Diagnostics.IsEmpty())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
|
||||||
{
|
|
||||||
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyTypeFoundError(property));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mapFromAttribute = propertyType.GetAttribute(MapFromAttributeTypeSymbol);
|
|
||||||
if (mapFromAttribute is null &&
|
|
||||||
propertyType is INamedTypeSymbol namedTypeSymbol &&
|
|
||||||
!propertyType.IsPrimitiveType() &&
|
|
||||||
(Compilation.IsGenericEnumerable(propertyType) || propertyType.AllInterfaces.Any(i => Compilation.IsGenericEnumerable(i))))
|
|
||||||
{
|
|
||||||
enumerableTypeArgument = namedTypeSymbol.TypeArguments.First();
|
|
||||||
mapFromAttribute = enumerableTypeArgument.GetAttribute(MapFromAttributeTypeSymbol);
|
|
||||||
}
|
|
||||||
|
|
||||||
mappedSourcePropertyType = mapFromAttribute?.ConstructorArguments.First().Value as INamedTypeSymbol;
|
|
||||||
|
|
||||||
if (mappedSourcePropertyType is null && enumerableTypeArgument is null)
|
|
||||||
{
|
|
||||||
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyTypeFoundError(property));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Diagnostics.IsEmpty();
|
|
||||||
}
|
|
||||||
protected bool IsEnumerable(ISymbol property, out INamedTypeSymbol? namedTypeSymbolResult)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
|
||||||
{
|
|
||||||
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyTypeFoundError(property));
|
|
||||||
namedTypeSymbolResult = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
propertyType is INamedTypeSymbol namedTypeSymbol &&
|
|
||||||
!propertyType.IsPrimitiveType() &&
|
|
||||||
(Compilation.IsGenericEnumerable(propertyType) || propertyType.AllInterfaces.Any(i => Compilation.IsGenericEnumerable(i))))
|
|
||||||
{
|
|
||||||
namedTypeSymbolResult = namedTypeSymbol;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
namedTypeSymbolResult = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
private static ImmutableArray<string> GetTypeConverterParameters(AttributeData typeConverterAttribute)
|
|
||||||
{
|
|
||||||
var converterParameter = typeConverterAttribute.ConstructorArguments.Skip(1).FirstOrDefault();
|
|
||||||
return converterParameter.IsNull
|
|
||||||
? ImmutableArray<string>.Empty
|
|
||||||
: converterParameter.Values.Where(v => v.Value is not null).Select(v => v.Value!.ToSourceCodeString()).ToImmutableArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private MappingModel? CreateMappingModel()
|
|
||||||
{
|
|
||||||
var semanticModel = Compilation.GetSemanticModel(TypeSyntax.SyntaxTree);
|
|
||||||
if (semanticModel.GetDeclaredSymbol(TypeSyntax) is not INamedTypeSymbol typeSymbol)
|
|
||||||
{
|
|
||||||
AddDiagnostic(DiagnosticsFactory.TypeNotFoundError(TypeSyntax.GetLocation(), TypeSyntax.Identifier.ValueText));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can have 2 sources...
|
|
||||||
|
|
||||||
var sourceTypeSymbols = GetSourceTypeSymbol(TypeSyntax, semanticModel);
|
|
||||||
|
|
||||||
|
|
||||||
// lets pick one for now, and then think what to do with the second one
|
|
||||||
if (sourceTypeSymbols.IsDefaultOrEmpty)
|
|
||||||
{
|
|
||||||
AddDiagnostic(DiagnosticsFactory.MapFromAttributeNotFoundError(TypeSyntax.GetLocation()));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var typeIdentifierName = TypeSyntax.GetIdentifierName();
|
|
||||||
var isTypeInheritFromMappedBaseClass = IsTypeInheritFromMappedBaseClass(semanticModel);
|
|
||||||
var isTypeUpdatable = true; //IsTypeUpdatable();
|
|
||||||
var hasJsonExtension = false; // HasJsonExtension();
|
|
||||||
|
|
||||||
List<MappedSourceType> mappedSourceTypes = new List<MappedSourceType>();
|
|
||||||
|
|
||||||
foreach (var sourceTypeSymbol in sourceTypeSymbols)
|
|
||||||
{
|
|
||||||
_ignoredNamespaces.Add(sourceTypeSymbol.ContainingNamespace.ToDisplayParts().First());
|
|
||||||
var sourceTypeIdentifierName = sourceTypeSymbol.Name;
|
|
||||||
var shouldGenerateSecondaryConstructor = ShouldGenerateSecondaryConstructor(semanticModel, sourceTypeSymbol);
|
|
||||||
var mappedProperties = GetSourceMappedProperties(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
|
|
||||||
var mappedFields = GetSourceMappedFields(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
|
|
||||||
AddUsingIfRequired(mappedProperties.Any(p => p.IsEnumerable), "System.Linq");
|
|
||||||
var allProperties = GetTypeMappedProperties(sourceTypeSymbol, typeSymbol , isTypeInheritFromMappedBaseClass);
|
|
||||||
var allFields = GetTypeMappedFields(sourceTypeSymbol, typeSymbol, isTypeInheritFromMappedBaseClass);
|
|
||||||
|
|
||||||
mappedSourceTypes.Add(new MappedSourceType(
|
|
||||||
sourceTypeSymbol.ContainingNamespace.ToDisplayString(),
|
|
||||||
sourceTypeIdentifierName,
|
|
||||||
sourceTypeSymbol.ToDisplayString(),
|
|
||||||
mappedProperties, mappedFields, allProperties, allFields, shouldGenerateSecondaryConstructor));
|
|
||||||
}
|
|
||||||
|
|
||||||
//var sourceTypeSymbol = sourceTypeSymbols[0];
|
|
||||||
|
|
||||||
// Pick first one to avoid errors. TODO: Make possible to use different source types
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*if (!mappedProperties.Any())
|
|
||||||
{
|
|
||||||
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyFoundError(TypeSyntax.GetLocation(), typeSymbol, sourceTypeSymbol));
|
|
||||||
return null;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return new MappingModel(
|
|
||||||
SourceGenerationOptions,
|
|
||||||
TypeSyntax.GetNamespace(),
|
|
||||||
TypeSyntax.Modifiers,
|
|
||||||
TypeSyntax.Keyword.Text,
|
|
||||||
typeIdentifierName,
|
|
||||||
isTypeUpdatable,
|
|
||||||
hasJsonExtension,
|
|
||||||
mappedSourceTypes.ToImmutableArray(),
|
|
||||||
isTypeInheritFromMappedBaseClass,
|
|
||||||
Usings);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private INamedTypeSymbol? GetTypeConverterBaseInterfaceForProperty(ITypeSymbol converterTypeSymbol, ISymbol property, IPropertySymbol sourceProperty)
|
|
||||||
{
|
|
||||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return converterTypeSymbol.AllInterfaces
|
|
||||||
.FirstOrDefault(i =>
|
|
||||||
i.TypeArguments.Length == 2 &&
|
|
||||||
SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, TypeConverterInterfaceTypeSymbol) &&
|
|
||||||
SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) &&
|
|
||||||
SymbolEqualityComparer.Default.Equals(propertyType, i.TypeArguments[1]));
|
|
||||||
}
|
|
||||||
private INamedTypeSymbol? GetTypeConverterBaseInterfaceForField(ITypeSymbol converterTypeSymbol, ISymbol property, IFieldSymbol sourceProperty)
|
|
||||||
{
|
|
||||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return converterTypeSymbol.AllInterfaces
|
|
||||||
.FirstOrDefault(i =>
|
|
||||||
i.TypeArguments.Length == 2 &&
|
|
||||||
SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, TypeConverterInterfaceTypeSymbol) &&
|
|
||||||
SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) &&
|
|
||||||
SymbolEqualityComparer.Default.Equals(propertyType, i.TypeArguments[1]));
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ShouldGenerateSecondaryConstructor(SemanticModel semanticModel, ISymbol sourceTypeSymbol)
|
|
||||||
{
|
|
||||||
var constructorSyntax = TypeSyntax.DescendantNodes()
|
|
||||||
.OfType<ConstructorDeclarationSyntax>()
|
|
||||||
.FirstOrDefault(c =>
|
|
||||||
c.ParameterList.Parameters.Count == 1 &&
|
|
||||||
SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(c.ParameterList.Parameters.Single().Type!).ConvertedType, sourceTypeSymbol));
|
|
||||||
|
|
||||||
if (constructorSyntax is null)
|
|
||||||
{
|
|
||||||
// Secondary constructor is not defined.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (constructorSyntax.Initializer?.ArgumentList.Arguments is not { Count: 2 } arguments ||
|
|
||||||
!SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(arguments[0].Expression).ConvertedType, MappingContextTypeSymbol) ||
|
|
||||||
!SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(arguments[1].Expression).ConvertedType, sourceTypeSymbol))
|
|
||||||
{
|
|
||||||
AddDiagnostic(DiagnosticsFactory.MissingConstructorArgument(constructorSyntax));
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string? ToQualifiedDisplayName(ISymbol? symbol)
|
|
||||||
{
|
|
||||||
if (symbol is null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var containingNamespace = TypeSyntax.GetNamespace();
|
|
||||||
var symbolNamespace = symbol.ContainingNamespace.ToDisplayString();
|
|
||||||
return containingNamespace != symbolNamespace && _ignoredNamespaces.Contains(symbol.ContainingNamespace.ToDisplayParts().First())
|
|
||||||
? symbol.ToDisplayString()
|
|
||||||
: symbol.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,222 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using MapTo.Extensions;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
|
||||||
|
|
||||||
namespace MapTo
|
|
||||||
{
|
|
||||||
internal enum AccessModifier
|
|
||||||
{
|
|
||||||
Public,
|
|
||||||
Internal,
|
|
||||||
Private
|
|
||||||
}
|
|
||||||
|
|
||||||
internal enum NullStaticAnalysisState
|
|
||||||
{
|
|
||||||
Default,
|
|
||||||
Enabled,
|
|
||||||
Disabled
|
|
||||||
}
|
|
||||||
|
|
||||||
internal record SourceCode(string Text, string HintName);
|
|
||||||
|
|
||||||
internal record MappedMember(
|
|
||||||
string Name,
|
|
||||||
string FullyQualifiedType,
|
|
||||||
string Type,
|
|
||||||
string? TypeConverter,
|
|
||||||
ImmutableArray<string> TypeConverterParameters,
|
|
||||||
string SourcePropertyName,
|
|
||||||
string? MappedSourcePropertyTypeName,
|
|
||||||
string? EnumerableTypeArgument,
|
|
||||||
ISymbol ActualSymbol,
|
|
||||||
INamedTypeSymbol? NamedTypeSymbol,
|
|
||||||
bool isEnumerable,
|
|
||||||
bool isReadOnly)
|
|
||||||
{
|
|
||||||
public bool IsEnumerable => EnumerableTypeArgument is not null;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal record MappedSourceType
|
|
||||||
(
|
|
||||||
string SourceNamespace,
|
|
||||||
string SourceTypeIdentifierName,
|
|
||||||
string SourceTypeFullName,
|
|
||||||
ImmutableArray<MappedMember> SourceProperties,
|
|
||||||
ImmutableArray<MappedMember> SourceFields,
|
|
||||||
ImmutableArray<MappedMember> TypeProperties,
|
|
||||||
ImmutableArray<MappedMember> TypeFields,
|
|
||||||
bool GenerateSecondaryConstructor
|
|
||||||
)
|
|
||||||
{
|
|
||||||
public string SourceType => SourceTypeFullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal record MappingModel(
|
|
||||||
SourceGenerationOptions Options,
|
|
||||||
string? Namespace,
|
|
||||||
SyntaxTokenList Modifiers,
|
|
||||||
string Type,
|
|
||||||
string TypeIdentifierName,
|
|
||||||
bool IsTypeUpdatable,
|
|
||||||
bool IsJsonExtension,
|
|
||||||
ImmutableArray<MappedSourceType> MappedSourceTypes,
|
|
||||||
bool HasMappedBaseClass,
|
|
||||||
ImmutableArray<string> Usings
|
|
||||||
);
|
|
||||||
|
|
||||||
internal interface IEfMethodsModel { }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
internal record EfMethodsModel(
|
|
||||||
SourceGenerationOptions Options,
|
|
||||||
string Namespace,
|
|
||||||
string ContextTypeName,
|
|
||||||
string ContextFullType,
|
|
||||||
ImmutableArray<EfEntityDataModel> MethodsModels,
|
|
||||||
ImmutableArray<string> Usings
|
|
||||||
);
|
|
||||||
|
|
||||||
internal class EfEntityDataModel
|
|
||||||
{
|
|
||||||
public string PropertyName { get; set; }
|
|
||||||
public string EntityTypeFullName { get; set; }
|
|
||||||
public string EntityTypeIdentifierName { get; set; }
|
|
||||||
|
|
||||||
public EfEntityDataModel(string propertyName, string entityTypeFullName, string entityTypeIdentifierName)
|
|
||||||
{
|
|
||||||
PropertyName = propertyName;
|
|
||||||
EntityTypeFullName = entityTypeFullName;
|
|
||||||
EntityTypeIdentifierName = entityTypeIdentifierName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal class EfAddMethodsModel : EfEntityDataModel
|
|
||||||
{
|
|
||||||
public string CreateTypeFullName { get; set; }
|
|
||||||
public string CreateTypeIdentifierName { get; set; }
|
|
||||||
public string ReturnTypeFullName { get; set; }
|
|
||||||
public string ReturnTypeIdentifierName { get; set; }
|
|
||||||
|
|
||||||
public EfAddMethodsModel(EfEntityDataModel entity, string createTypeFullName,
|
|
||||||
string createTypeIdentifierName,
|
|
||||||
string returnTypeFullName,
|
|
||||||
string returnTypeIdentifierName) : base(entity.PropertyName, entity.EntityTypeFullName, entity.EntityTypeIdentifierName)
|
|
||||||
{
|
|
||||||
CreateTypeFullName = createTypeFullName;
|
|
||||||
CreateTypeIdentifierName = createTypeIdentifierName;
|
|
||||||
ReturnTypeIdentifierName = returnTypeIdentifierName;
|
|
||||||
ReturnTypeFullName = returnTypeFullName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class EfGetOneByModel : EfEntityDataModel
|
|
||||||
{
|
|
||||||
public string ByParamPropertyName { get; set; }
|
|
||||||
public string ByParamFullTypeName { get; set; }
|
|
||||||
public string ByParamTypeName { get; set; }
|
|
||||||
public string ReturnTypeIdentifierName { get; set; }
|
|
||||||
public string ReturnTypeFullName { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
public EfGetOneByModel(EfEntityDataModel entity, string byParamPropertyName,
|
|
||||||
string byParamFullTypeName,
|
|
||||||
string returnTypeFullName,
|
|
||||||
string returnTypeIdentifierName) : base(entity.PropertyName, entity.EntityTypeFullName, entity.EntityTypeIdentifierName)
|
|
||||||
{
|
|
||||||
ByParamPropertyName = byParamPropertyName;
|
|
||||||
ByParamFullTypeName = byParamFullTypeName;
|
|
||||||
ReturnTypeIdentifierName = returnTypeIdentifierName;
|
|
||||||
ReturnTypeFullName = returnTypeFullName;
|
|
||||||
ByParamTypeName = returnTypeFullName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class EfGetOneWithModel : EfEntityDataModel
|
|
||||||
{
|
|
||||||
public string ReturnTypeIdentifierName { get; set; }
|
|
||||||
public string ReturnTypeFullName { get; set; }
|
|
||||||
|
|
||||||
public EfGetOneWithModel(EfEntityDataModel entity, string returnTypeFullName,
|
|
||||||
string returnTypeIdentifierName) : base(entity.PropertyName, entity.EntityTypeFullName, entity.EntityTypeIdentifierName)
|
|
||||||
{
|
|
||||||
ReturnTypeIdentifierName = returnTypeIdentifierName;
|
|
||||||
ReturnTypeFullName = returnTypeFullName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class EfGetManyModel : EfEntityDataModel
|
|
||||||
{
|
|
||||||
public string ReturnTypeIdentifierName { get; set; }
|
|
||||||
public string ReturnTypeFullName { get; set; }
|
|
||||||
|
|
||||||
public EfGetManyModel(EfEntityDataModel entity, string returnTypeFullName,
|
|
||||||
string returnTypeIdentifierName) : base(entity.PropertyName, entity.EntityTypeFullName, entity.EntityTypeIdentifierName)
|
|
||||||
{
|
|
||||||
ReturnTypeIdentifierName = returnTypeIdentifierName;
|
|
||||||
ReturnTypeFullName = returnTypeFullName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal class EfUpdateMethodsModel : EfEntityDataModel
|
|
||||||
{
|
|
||||||
public string UpdateTypeFullName;
|
|
||||||
public string UpdateTypeIdentifierName;
|
|
||||||
public string ReturnTypeFullName;
|
|
||||||
public string ReturnTypeIdentifierName;
|
|
||||||
public string KeyPropertyName;
|
|
||||||
public string KeyFullTypeName;
|
|
||||||
public EfUpdateMethodsModel(EfEntityDataModel entity,
|
|
||||||
string updateTypeFullName,
|
|
||||||
string updateTypeIdentifierName,
|
|
||||||
string returnTypeFullName,
|
|
||||||
string returnTypeIdentifierName,
|
|
||||||
string keyPropertyName,
|
|
||||||
string keyFullTypeName) : base(entity.PropertyName, entity.EntityTypeFullName, entity.EntityTypeIdentifierName)
|
|
||||||
{
|
|
||||||
UpdateTypeFullName = updateTypeFullName;
|
|
||||||
UpdateTypeIdentifierName = updateTypeIdentifierName;
|
|
||||||
ReturnTypeFullName= returnTypeFullName;
|
|
||||||
ReturnTypeIdentifierName = returnTypeIdentifierName;
|
|
||||||
KeyPropertyName = keyPropertyName;
|
|
||||||
KeyFullTypeName = keyFullTypeName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal record SourceGenerationOptions(
|
|
||||||
AccessModifier ConstructorAccessModifier,
|
|
||||||
AccessModifier GeneratedMethodsAccessModifier,
|
|
||||||
bool GenerateXmlDocument,
|
|
||||||
bool SupportNullableReferenceTypes,
|
|
||||||
bool SupportNullableStaticAnalysis)
|
|
||||||
{
|
|
||||||
internal static SourceGenerationOptions From(GeneratorExecutionContext context)
|
|
||||||
{
|
|
||||||
const string allowNullAttributeName = "System.Diagnostics.CodeAnalysis.AllowNullAttribute";
|
|
||||||
var supportNullableStaticAnalysis = context.GetBuildGlobalOption(propertyName: nameof(SupportNullableStaticAnalysis), NullStaticAnalysisState.Default);
|
|
||||||
var supportNullableReferenceTypes = context.Compilation.Options.NullableContextOptions is NullableContextOptions.Warnings or NullableContextOptions.Enable;
|
|
||||||
|
|
||||||
return new(
|
|
||||||
ConstructorAccessModifier: context.GetBuildGlobalOption(propertyName: nameof(ConstructorAccessModifier), AccessModifier.Public),
|
|
||||||
GeneratedMethodsAccessModifier: context.GetBuildGlobalOption(propertyName: nameof(GeneratedMethodsAccessModifier), AccessModifier.Public),
|
|
||||||
GenerateXmlDocument: context.GetBuildGlobalOption(propertyName: nameof(GenerateXmlDocument), true),
|
|
||||||
SupportNullableReferenceTypes: supportNullableReferenceTypes,
|
|
||||||
SupportNullableStaticAnalysis: supportNullableStaticAnalysis switch
|
|
||||||
{
|
|
||||||
NullStaticAnalysisState.Enabled => true,
|
|
||||||
NullStaticAnalysisState.Disabled => false,
|
|
||||||
_ => context.Compilation is CSharpCompilation { LanguageVersion: >= LanguageVersion.CSharp8 } cs && cs.TypeByMetadataNameExists(allowNullAttributeName)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string NullableReferenceSyntax => SupportNullableReferenceTypes ? "?" : string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
using static MapTo.Sources.Constants;
|
|
||||||
|
|
||||||
namespace MapTo.Sources
|
|
||||||
{
|
|
||||||
internal static class DictionaryToListAttributeSource
|
|
||||||
{
|
|
||||||
internal const string AttributeName = "DictionaryToList";
|
|
||||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
|
||||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
|
||||||
internal const string SourceMemberNameFieldOrPropertyName = "SourcePropertyName";
|
|
||||||
|
|
||||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
|
||||||
{
|
|
||||||
using var builder = new SourceBuilder()
|
|
||||||
.WriteLine(GeneratedFilesHeader)
|
|
||||||
.WriteNullableContextOptionIf(options.SupportNullableReferenceTypes)
|
|
||||||
.WriteLine()
|
|
||||||
.WriteLine("using System;")
|
|
||||||
.WriteLine()
|
|
||||||
.WriteLine($"namespace {RootNamespace}")
|
|
||||||
.WriteOpeningBracket();
|
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine("/// <summary>")
|
|
||||||
.WriteLine("/// Specifies the mapping behavior of the annotated property.")
|
|
||||||
.WriteLine("/// </summary>")
|
|
||||||
.WriteLine("/// <remarks>")
|
|
||||||
.WriteLine($"/// {AttributeClassName} has a number of uses:")
|
|
||||||
.WriteLine("/// <list type=\"bullet\">")
|
|
||||||
.WriteLine("/// <item><description>By default properties with same name will get mapped. This attribute allows the names to be different.</description></item>")
|
|
||||||
.WriteLine("/// <item><description>Indicates that a property should be mapped when member serialization is set to opt-in.</description></item>")
|
|
||||||
.WriteLine("/// </list>")
|
|
||||||
.WriteLine("/// </remarks>");
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]")
|
|
||||||
.WriteLine($"public sealed class {AttributeClassName} : Attribute")
|
|
||||||
.WriteOpeningBracket();
|
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine("/// <summary>")
|
|
||||||
.WriteLine("/// Gets or sets the property name of the object to mapping from.")
|
|
||||||
.WriteLine("/// </summary>");
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLine($"public string{options.NullableReferenceSyntax} {SourceMemberNameFieldOrPropertyName} {{ get; set; }}")
|
|
||||||
.WriteClosingBracket() // class
|
|
||||||
.WriteClosingBracket(); // namespace
|
|
||||||
|
|
||||||
|
|
||||||
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
using static MapTo.Sources.Constants;
|
|
||||||
|
|
||||||
namespace MapTo.Sources
|
|
||||||
{
|
|
||||||
internal static class JsonExtensionAttributeSource
|
|
||||||
{
|
|
||||||
internal const string AttributeName = "JsonExtension";
|
|
||||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
|
||||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
|
||||||
|
|
||||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
|
||||||
{
|
|
||||||
using var builder = new SourceBuilder()
|
|
||||||
.WriteLine(GeneratedFilesHeader)
|
|
||||||
.WriteLine("using System;")
|
|
||||||
.WriteLine()
|
|
||||||
.WriteLine($"namespace {RootNamespace}")
|
|
||||||
.WriteOpeningBracket();
|
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine("/// <summary>")
|
|
||||||
.WriteLine("/// Specifies that the annotated class has a json extension.")
|
|
||||||
.WriteLine("/// </summary>");
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLine("[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]")
|
|
||||||
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
|
|
||||||
.WriteOpeningBracket();
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteClosingBracket() // class
|
|
||||||
.WriteClosingBracket(); // namespace
|
|
||||||
|
|
||||||
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
using MapTo.Extensions;
|
|
||||||
using System.Text;
|
|
||||||
using static MapTo.Sources.Constants;
|
|
||||||
|
|
||||||
namespace MapTo.Sources
|
|
||||||
{
|
|
||||||
internal static class MapClassSource
|
|
||||||
{
|
|
||||||
internal static SourceCode Generate(MappingModel model)
|
|
||||||
{
|
|
||||||
return model.GenerateStructOrClass("class");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,235 +0,0 @@
|
||||||
using System;
|
|
||||||
using MapTo.Extensions;
|
|
||||||
using static MapTo.Sources.Constants;
|
|
||||||
|
|
||||||
namespace MapTo.Sources
|
|
||||||
{
|
|
||||||
internal static class MapRecordSource
|
|
||||||
{
|
|
||||||
internal static SourceCode Generate(MappingModel model)
|
|
||||||
{
|
|
||||||
using var builder = new SourceBuilder()
|
|
||||||
.WriteLine(GeneratedFilesHeader)
|
|
||||||
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
|
|
||||||
.WriteUsings(model.Usings)
|
|
||||||
.WriteLine()
|
|
||||||
|
|
||||||
// Namespace declaration
|
|
||||||
.WriteLine($"namespace {model.Namespace}")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
|
|
||||||
// Class declaration
|
|
||||||
.WriteLine($"partial record {model.TypeIdentifierName}")
|
|
||||||
.WriteOpeningBracket();
|
|
||||||
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
if (targetSourceType.GenerateSecondaryConstructor)
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.GenerateSecondaryConstructor(model)
|
|
||||||
.WriteLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Class body
|
|
||||||
|
|
||||||
|
|
||||||
builder
|
|
||||||
.GeneratePrivateConstructor(model)
|
|
||||||
|
|
||||||
.WriteLine()
|
|
||||||
.GenerateFactoryMethod(model)
|
|
||||||
|
|
||||||
// End class declaration
|
|
||||||
.WriteClosingBracket()
|
|
||||||
.WriteLine()
|
|
||||||
|
|
||||||
// Extension class declaration
|
|
||||||
.GenerateSourceTypeExtensionClass(model)
|
|
||||||
|
|
||||||
|
|
||||||
// End namespace declaration
|
|
||||||
.WriteClosingBracket();
|
|
||||||
|
|
||||||
return new(builder.ToString(), $"{model.Namespace}.{model.TypeIdentifierName}.g.cs");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateSecondaryConstructor(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
// grab first data from array
|
|
||||||
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
if (model.Options.GenerateXmlDocument)
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine("/// <summary>")
|
|
||||||
.WriteLine($"/// Initializes a new instance of the <see cref=\"{model.TypeIdentifierName}\"/> class")
|
|
||||||
.WriteLine($"/// using the property values from the specified <paramref name=\"{sourceClassParameterName}\"/>.")
|
|
||||||
.WriteLine("/// </summary>")
|
|
||||||
.WriteLine($"/// <exception cref=\"ArgumentNullException\">{sourceClassParameterName} is null</exception>");
|
|
||||||
}
|
|
||||||
builder .WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({targetSourceType.SourceType} {sourceClassParameterName})")
|
|
||||||
.WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GeneratePrivateConstructor(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
const string mappingContextParameterName = "context";
|
|
||||||
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
builder
|
|
||||||
.WriteLine(
|
|
||||||
$"private protected {model.TypeIdentifierName}({MappingContextSource.ClassName} {mappingContextParameterName}, {targetSourceType.SourceType} {sourceClassParameterName})")
|
|
||||||
.Indent()
|
|
||||||
.Write(": this(").WriteProperties(model, sourceClassParameterName, mappingContextParameterName)
|
|
||||||
.WriteLine(")")
|
|
||||||
.Unindent()
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteLine($"if ({mappingContextParameterName} == null) throw new ArgumentNullException(nameof({mappingContextParameterName}));")
|
|
||||||
.WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));")
|
|
||||||
.WriteLine()
|
|
||||||
.WriteLine($"{mappingContextParameterName}.{MappingContextSource.RegisterMethodName}({sourceClassParameterName}, this);")
|
|
||||||
.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
// End constructor declaration
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder WriteProperties(this SourceBuilder builder, MappingModel model, string sourceClassParameterName,
|
|
||||||
string mappingContextParameterName)
|
|
||||||
{
|
|
||||||
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < targetSourceType.SourceProperties.Length; i++)
|
|
||||||
{
|
|
||||||
var property = targetSourceType.SourceProperties[i];
|
|
||||||
if (property.TypeConverter is null)
|
|
||||||
{
|
|
||||||
if (property.IsEnumerable)
|
|
||||||
{
|
|
||||||
builder.Write(
|
|
||||||
$"{property.Name}: {sourceClassParameterName}.{property.SourcePropertyName}.Select({mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.EnumerableTypeArgument}>).ToList()");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.Write(property.MappedSourcePropertyTypeName is null
|
|
||||||
? $"{property.Name}: {sourceClassParameterName}.{property.SourcePropertyName}"
|
|
||||||
: $"{property.Name}: {mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.Type}>({sourceClassParameterName}.{property.SourcePropertyName})");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var parameters = property.TypeConverterParameters.IsEmpty
|
|
||||||
? "null"
|
|
||||||
: $"new object[] {{ {string.Join(", ", property.TypeConverterParameters)} }}";
|
|
||||||
|
|
||||||
builder.Write(
|
|
||||||
$"{property.Name}: new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters})");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i < targetSourceType.SourceProperties.Length - 1)
|
|
||||||
{
|
|
||||||
builder.Write(", ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateFactoryMethod(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
|
|
||||||
builder
|
|
||||||
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
|
||||||
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
|
||||||
.WriteLine(
|
|
||||||
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} From({targetSourceType.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteLine(
|
|
||||||
$"return {sourceClassParameterName} == null ? null : {MappingContextSource.ClassName}.{MappingContextSource.FactoryMethodName}<{targetSourceType.SourceType}, {model.TypeIdentifierName}>({sourceClassParameterName});")
|
|
||||||
.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateConvertorMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
|
||||||
{
|
|
||||||
if (!model.Options.GenerateXmlDocument)
|
|
||||||
{
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine("/// <summary>")
|
|
||||||
.WriteLine($"/// Creates a new instance of <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
|
|
||||||
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
|
||||||
.WriteLine("/// </summary>")
|
|
||||||
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{targetSourceType.SourceType}\"/> to use as source.</param>")
|
|
||||||
.WriteLine(
|
|
||||||
$"/// <returns>A new instance of <see cred=\"{model.TypeIdentifierName}\"/> -or- <c>null</c> if <paramref name=\"{sourceClassParameterName}\"/> is <c>null</c>.</returns>");
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateSourceTypeExtensionClass(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine(
|
|
||||||
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {targetSourceType.SourceTypeIdentifierName}To{model.TypeIdentifierName}Extensions")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.GenerateSourceTypeExtensionMethod(model)
|
|
||||||
.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
|
|
||||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
|
|
||||||
builder
|
|
||||||
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
|
||||||
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
|
||||||
.WriteLine(
|
|
||||||
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} To{model.TypeIdentifierName}(this {targetSourceType.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
|
|
||||||
.WriteClosingBracket();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
using MapTo.Extensions;
|
|
||||||
using static MapTo.Sources.Constants;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MapTo.Sources
|
|
||||||
{
|
|
||||||
internal static class MapStructSource
|
|
||||||
{
|
|
||||||
internal static SourceCode Generate(MappingModel model)
|
|
||||||
{
|
|
||||||
return model.GenerateStructOrClass("struct");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
using static MapTo.Sources.Constants;
|
|
||||||
|
|
||||||
namespace MapTo.Sources
|
|
||||||
{
|
|
||||||
internal static class UseUpdateAttributeSource
|
|
||||||
{
|
|
||||||
internal const string AttributeName = "UseUpdate";
|
|
||||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
|
||||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
|
||||||
|
|
||||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
|
||||||
{
|
|
||||||
using var builder = new SourceBuilder()
|
|
||||||
.WriteLine(GeneratedFilesHeader)
|
|
||||||
.WriteLine("using System;")
|
|
||||||
.WriteLine()
|
|
||||||
.WriteLine($"namespace {RootNamespace}")
|
|
||||||
.WriteOpeningBracket();
|
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine("/// <summary>")
|
|
||||||
.WriteLine("/// Specifies that the annotated class can be updatable.")
|
|
||||||
.WriteLine("/// </summary>");
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLine("[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]")
|
|
||||||
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
|
|
||||||
.WriteOpeningBracket();
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteClosingBracket() // class
|
|
||||||
.WriteClosingBracket(); // namespace
|
|
||||||
|
|
||||||
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
using MapTo.Extensions;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
||||||
|
|
||||||
namespace MapTo
|
|
||||||
{
|
|
||||||
internal class StructMappingContext : MappingContext
|
|
||||||
{
|
|
||||||
internal StructMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
|
||||||
: base(compilation, sourceGenerationOptions, typeSyntax) { }
|
|
||||||
|
|
||||||
protected override ImmutableArray<MappedMember> GetSourceMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
|
||||||
{
|
|
||||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IFieldSymbol>().ToArray();
|
|
||||||
|
|
||||||
return typeSymbol
|
|
||||||
.GetAllMembers()
|
|
||||||
.OfType<IFieldSymbol>()
|
|
||||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
|
||||||
.Select(property => MapField(sourceTypeSymbol, sourceProperties, property))
|
|
||||||
.Where(mappedProperty => mappedProperty is not null)
|
|
||||||
.ToImmutableArray()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ImmutableArray<MappedMember> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass)
|
|
||||||
{
|
|
||||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
|
||||||
|
|
||||||
return typeSymbol
|
|
||||||
.GetAllMembers()
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
|
||||||
.Select(property => MapProperty(sourceTypeSymbol, sourceProperties, property))
|
|
||||||
.Where(mappedProperty => mappedProperty is not null)
|
|
||||||
.ToImmutableArray()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ImmutableArray<MappedMember> GetTypeMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
|
||||||
{
|
|
||||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IFieldSymbol>().ToArray();
|
|
||||||
|
|
||||||
return sourceTypeSymbol
|
|
||||||
.GetAllMembers()
|
|
||||||
.OfType<IFieldSymbol>()
|
|
||||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
|
||||||
.Select(property => MapFieldSimple(typeSymbol, property))
|
|
||||||
.Where(mappedProperty => mappedProperty is not null)
|
|
||||||
.ToImmutableArray()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ImmutableArray<MappedMember> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass)
|
|
||||||
{
|
|
||||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
|
||||||
|
|
||||||
return sourceTypeSymbol
|
|
||||||
.GetAllMembers()
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
|
||||||
.Select(property => MapPropertySimple(typeSymbol, property))
|
|
||||||
.Where(mappedProperty => mappedProperty is not null)
|
|
||||||
.ToImmutableArray()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using MapTo.Extensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
|
namespace MapTo
|
||||||
|
{
|
||||||
|
internal class ClassMappingContext : MappingContext
|
||||||
|
{
|
||||||
|
internal ClassMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
||||||
|
: base(compilation, sourceGenerationOptions, typeSyntax) { }
|
||||||
|
|
||||||
|
protected override ImmutableArray<MappedProperty> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||||
|
{
|
||||||
|
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||||
|
|
||||||
|
return typeSymbol
|
||||||
|
.GetAllMembers(!isInheritFromMappedBaseClass)
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol))
|
||||||
|
.Select(property => MapProperty(sourceTypeSymbol, sourceProperties, property))
|
||||||
|
.Where(mappedProperty => mappedProperty is not null)
|
||||||
|
.ToImmutableArray()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ImmutableArray<MappedProperty> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||||
|
{
|
||||||
|
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||||
|
|
||||||
|
return typeSymbol
|
||||||
|
.GetAllMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol))
|
||||||
|
.Select(property => MapProperty(typeSymbol, sourceProperties, property))
|
||||||
|
.Where(mappedProperty => mappedProperty is not null)
|
||||||
|
.ToImmutableArray()!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,16 @@
|
||||||
// ReSharper disable UnusedType.Global
|
// ReSharper disable UnusedType.Global
|
||||||
// ReSharper disable CheckNamespace
|
// ReSharper disable CheckNamespace
|
||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace System.Runtime.CompilerServices
|
namespace System.Runtime.CompilerServices
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reserved to be used by the compiler for tracking metadata.
|
/// Reserved to be used by the compiler for tracking metadata.
|
||||||
/// This class should not be used by developers in source code.
|
/// This class should not be used by developers in source code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
internal static class IsExternalInit { }
|
internal static class IsExternalInit { }
|
||||||
}
|
}
|
|
@ -1,178 +1,178 @@
|
||||||
// ReSharper disable CheckNamespace
|
// ReSharper disable CheckNamespace
|
||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
#if NETSTANDARD2_0
|
#if NETSTANDARD2_0
|
||||||
namespace System.Diagnostics.CodeAnalysis
|
namespace System.Diagnostics.CodeAnalysis
|
||||||
{
|
{
|
||||||
// These attributes already shipped with .NET Core 3.1 in System.Runtime
|
// These attributes already shipped with .NET Core 3.1 in System.Runtime
|
||||||
#if !NETCOREAPP3_0 && !NETCOREAPP3_1 && !NETSTANDARD2_1
|
#if !NETCOREAPP3_0 && !NETCOREAPP3_1 && !NETSTANDARD2_1
|
||||||
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
|
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
|
||||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property)]
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property)]
|
||||||
internal sealed class AllowNullAttribute : Attribute { }
|
internal sealed class AllowNullAttribute : Attribute { }
|
||||||
|
|
||||||
/// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
|
/// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
|
||||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property)]
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property)]
|
||||||
internal sealed class DisallowNullAttribute : Attribute { }
|
internal sealed class DisallowNullAttribute : Attribute { }
|
||||||
|
|
||||||
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
|
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
|
||||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]
|
||||||
internal sealed class MaybeNullAttribute : Attribute { }
|
internal sealed class MaybeNullAttribute : Attribute { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input
|
/// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input
|
||||||
/// argument was not null when the call returns.
|
/// argument was not null when the call returns.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]
|
||||||
internal sealed class NotNullAttribute : Attribute { }
|
internal sealed class NotNullAttribute : Attribute { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies that when a method returns <see cref="ReturnValue" />, the parameter may be null even if the
|
/// Specifies that when a method returns <see cref="ReturnValue" />, the parameter may be null even if the
|
||||||
/// corresponding type disallows it.
|
/// corresponding type disallows it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Parameter)]
|
[AttributeUsage(AttributeTargets.Parameter)]
|
||||||
internal sealed class MaybeNullWhenAttribute : Attribute
|
internal sealed class MaybeNullWhenAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
||||||
/// <param name="returnValue">
|
/// <param name="returnValue">
|
||||||
/// The return value condition. If the method returns this value, the associated parameter may be null.
|
/// The return value condition. If the method returns this value, the associated parameter may be null.
|
||||||
/// </param>
|
/// </param>
|
||||||
public MaybeNullWhenAttribute(bool returnValue)
|
public MaybeNullWhenAttribute(bool returnValue)
|
||||||
{
|
{
|
||||||
ReturnValue = returnValue;
|
ReturnValue = returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Gets the return value condition.</summary>
|
/// <summary>Gets the return value condition.</summary>
|
||||||
public bool ReturnValue { get; }
|
public bool ReturnValue { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies that when a method returns <see cref="ReturnValue" />, the parameter will not be null even if the
|
/// Specifies that when a method returns <see cref="ReturnValue" />, the parameter will not be null even if the
|
||||||
/// corresponding type allows it.
|
/// corresponding type allows it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Parameter)]
|
[AttributeUsage(AttributeTargets.Parameter)]
|
||||||
internal sealed class NotNullWhenAttribute : Attribute
|
internal sealed class NotNullWhenAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
||||||
/// <param name="returnValue">
|
/// <param name="returnValue">
|
||||||
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
||||||
/// </param>
|
/// </param>
|
||||||
public NotNullWhenAttribute(bool returnValue)
|
public NotNullWhenAttribute(bool returnValue)
|
||||||
{
|
{
|
||||||
ReturnValue = returnValue;
|
ReturnValue = returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Gets the return value condition.</summary>
|
/// <summary>Gets the return value condition.</summary>
|
||||||
public bool ReturnValue { get; }
|
public bool ReturnValue { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
|
/// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
|
||||||
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)]
|
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)]
|
||||||
internal sealed class NotNullIfNotNullAttribute : Attribute
|
internal sealed class NotNullIfNotNullAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>Initializes the attribute with the associated parameter name.</summary>
|
/// <summary>Initializes the attribute with the associated parameter name.</summary>
|
||||||
/// <param name="parameterName">
|
/// <param name="parameterName">
|
||||||
/// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
|
/// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
|
||||||
/// </param>
|
/// </param>
|
||||||
public NotNullIfNotNullAttribute(string parameterName)
|
public NotNullIfNotNullAttribute(string parameterName)
|
||||||
{
|
{
|
||||||
ParameterName = parameterName;
|
ParameterName = parameterName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Gets the associated parameter name.</summary>
|
/// <summary>Gets the associated parameter name.</summary>
|
||||||
public string ParameterName { get; }
|
public string ParameterName { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Applied to a method that will never return under any circumstance.</summary>
|
/// <summary>Applied to a method that will never return under any circumstance.</summary>
|
||||||
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
||||||
internal sealed class DoesNotReturnAttribute : Attribute { }
|
internal sealed class DoesNotReturnAttribute : Attribute { }
|
||||||
|
|
||||||
/// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
|
/// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
|
||||||
[AttributeUsage(AttributeTargets.Parameter)]
|
[AttributeUsage(AttributeTargets.Parameter)]
|
||||||
internal sealed class DoesNotReturnIfAttribute : Attribute
|
internal sealed class DoesNotReturnIfAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>Initializes the attribute with the specified parameter value.</summary>
|
/// <summary>Initializes the attribute with the specified parameter value.</summary>
|
||||||
/// <param name="parameterValue">
|
/// <param name="parameterValue">
|
||||||
/// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
|
/// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
|
||||||
/// the associated parameter matches this value.
|
/// the associated parameter matches this value.
|
||||||
/// </param>
|
/// </param>
|
||||||
public DoesNotReturnIfAttribute(bool parameterValue)
|
public DoesNotReturnIfAttribute(bool parameterValue)
|
||||||
{
|
{
|
||||||
ParameterValue = parameterValue;
|
ParameterValue = parameterValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Gets the condition parameter value.</summary>
|
/// <summary>Gets the condition parameter value.</summary>
|
||||||
public bool ParameterValue { get; }
|
public bool ParameterValue { get; }
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies that the method or property will ensure that the listed field and property members have not-null
|
/// Specifies that the method or property will ensure that the listed field and property members have not-null
|
||||||
/// values.
|
/// values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
|
||||||
internal sealed class MemberNotNullAttribute : Attribute
|
internal sealed class MemberNotNullAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>Initializes the attribute with a field or property member.</summary>
|
/// <summary>Initializes the attribute with a field or property member.</summary>
|
||||||
/// <param name="member">
|
/// <param name="member">
|
||||||
/// The field or property member that is promised to be not-null.
|
/// The field or property member that is promised to be not-null.
|
||||||
/// </param>
|
/// </param>
|
||||||
public MemberNotNullAttribute(string member)
|
public MemberNotNullAttribute(string member)
|
||||||
{
|
{
|
||||||
Members = new[] { member };
|
Members = new[] { member };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Initializes the attribute with the list of field and property members.</summary>
|
/// <summary>Initializes the attribute with the list of field and property members.</summary>
|
||||||
/// <param name="members">
|
/// <param name="members">
|
||||||
/// The list of field and property members that are promised to be not-null.
|
/// The list of field and property members that are promised to be not-null.
|
||||||
/// </param>
|
/// </param>
|
||||||
public MemberNotNullAttribute(params string[] members)
|
public MemberNotNullAttribute(params string[] members)
|
||||||
{
|
{
|
||||||
Members = members;
|
Members = members;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Gets field or property member names.</summary>
|
/// <summary>Gets field or property member names.</summary>
|
||||||
public string[] Members { get; }
|
public string[] Members { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies that the method or property will ensure that the listed field and property members have not-null
|
/// Specifies that the method or property will ensure that the listed field and property members have not-null
|
||||||
/// values when returning with the specified return value condition.
|
/// values when returning with the specified return value condition.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
|
||||||
internal sealed class MemberNotNullWhenAttribute : Attribute
|
internal sealed class MemberNotNullWhenAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>Initializes the attribute with the specified return value condition and a field or property member.</summary>
|
/// <summary>Initializes the attribute with the specified return value condition and a field or property member.</summary>
|
||||||
/// <param name="returnValue">
|
/// <param name="returnValue">
|
||||||
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="member">
|
/// <param name="member">
|
||||||
/// The field or property member that is promised to be not-null.
|
/// The field or property member that is promised to be not-null.
|
||||||
/// </param>
|
/// </param>
|
||||||
public MemberNotNullWhenAttribute(bool returnValue, string member)
|
public MemberNotNullWhenAttribute(bool returnValue, string member)
|
||||||
{
|
{
|
||||||
ReturnValue = returnValue;
|
ReturnValue = returnValue;
|
||||||
Members = new[] { member };
|
Members = new[] { member };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Initializes the attribute with the specified return value condition and list of field and property members.</summary>
|
/// <summary>Initializes the attribute with the specified return value condition and list of field and property members.</summary>
|
||||||
/// <param name="returnValue">
|
/// <param name="returnValue">
|
||||||
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="members">
|
/// <param name="members">
|
||||||
/// The list of field and property members that are promised to be not-null.
|
/// The list of field and property members that are promised to be not-null.
|
||||||
/// </param>
|
/// </param>
|
||||||
public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
|
public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
|
||||||
{
|
{
|
||||||
ReturnValue = returnValue;
|
ReturnValue = returnValue;
|
||||||
Members = members;
|
Members = members;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Gets field or property member names.</summary>
|
/// <summary>Gets field or property member names.</summary>
|
||||||
public string[] Members { get; }
|
public string[] Members { get; }
|
||||||
|
|
||||||
/// <summary>Gets the return value condition.</summary>
|
/// <summary>Gets the return value condition.</summary>
|
||||||
public bool ReturnValue { get; }
|
public bool ReturnValue { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
|
@ -1,42 +1,42 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MapTo.Extensions;
|
using MapTo.Extensions;
|
||||||
using MapTo.Sources;
|
using MapTo.Sources;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using static MapTo.Sources.Constants;
|
using static MapTo.Sources.Constants;
|
||||||
|
|
||||||
namespace MapTo
|
namespace MapTo
|
||||||
{
|
{
|
||||||
internal static class DiagnosticsFactory
|
internal static class DiagnosticsFactory
|
||||||
{
|
{
|
||||||
private const string UsageCategory = "Usage";
|
private const string UsageCategory = "Usage";
|
||||||
private const string CodePrefix = "MT";
|
private const string CodePrefix = "MT";
|
||||||
private const string ErrorId = CodePrefix + "0";
|
private const string ErrorId = CodePrefix + "0";
|
||||||
private const string InfoId = CodePrefix + "1";
|
private const string InfoId = CodePrefix + "1";
|
||||||
private const string WarningId = CodePrefix + "2";
|
private const string WarningId = CodePrefix + "2";
|
||||||
|
|
||||||
internal static Diagnostic TypeNotFoundError(Location location, string syntaxName) =>
|
internal static Diagnostic TypeNotFoundError(Location location, string syntaxName) =>
|
||||||
Create($"{ErrorId}010", location, $"Unable to find '{syntaxName}' type.");
|
Create($"{ErrorId}010", location, $"Unable to find '{syntaxName}' type.");
|
||||||
|
|
||||||
internal static Diagnostic MapFromAttributeNotFoundError(Location location) =>
|
internal static Diagnostic MapFromAttributeNotFoundError(Location location) =>
|
||||||
Create($"{ErrorId}020", location, $"Unable to find {MapFromAttributeSource.AttributeName} type.");
|
Create($"{ErrorId}020", location, $"Unable to find {MapFromAttributeSource.AttributeName} type.");
|
||||||
|
|
||||||
internal static Diagnostic NoMatchingPropertyFoundError(Location location, INamedTypeSymbol classType, INamedTypeSymbol sourceType) =>
|
internal static Diagnostic NoMatchingPropertyFoundError(Location location, INamedTypeSymbol classType, INamedTypeSymbol sourceType) =>
|
||||||
Create($"{ErrorId}030", location, $"No matching properties found between '{classType.ToDisplayString()}' and '{sourceType.ToDisplayString()}' types.");
|
Create($"{ErrorId}030", location, $"No matching properties found between '{classType.ToDisplayString()}' and '{sourceType.ToDisplayString()}' types.");
|
||||||
|
|
||||||
internal static Diagnostic NoMatchingPropertyTypeFoundError(ISymbol property) =>
|
internal static Diagnostic NoMatchingPropertyTypeFoundError(ISymbol property) =>
|
||||||
Create($"{ErrorId}031", property.Locations.FirstOrDefault(), $"Cannot create a map for '{property.ToDisplayString()}' property because source and destination types are not implicitly convertible. Consider using '{MapTypeConverterAttributeSource.FullyQualifiedName}' to provide a type converter or ignore the property using '{IgnoreMemberAttributeSource.FullyQualifiedName}'.");
|
Create($"{ErrorId}031", property.Locations.FirstOrDefault(), $"Cannot create a map for '{property.ToDisplayString()}' property because source and destination types are not implicitly convertible. Consider using '{MapTypeConverterAttributeSource.FullyQualifiedName}' to provide a type converter or ignore the property using '{IgnorePropertyAttributeSource.FullyQualifiedName}'.");
|
||||||
|
|
||||||
internal static Diagnostic InvalidTypeConverterGenericTypesError(ISymbol property, IPropertySymbol sourceProperty) =>
|
internal static Diagnostic InvalidTypeConverterGenericTypesError(ISymbol property, IPropertySymbol sourceProperty) =>
|
||||||
Create($"{ErrorId}032", property.Locations.FirstOrDefault(), $"Cannot map '{property.ToDisplayString()}' property because the annotated converter does not implement '{RootNamespace}.{ITypeConverterSource.InterfaceName}<{sourceProperty.Type.ToDisplayString()}, {property.GetTypeSymbol()?.ToDisplayString()}>'.");
|
Create($"{ErrorId}032", property.Locations.FirstOrDefault(), $"Cannot map '{property.ToDisplayString()}' property because the annotated converter does not implement '{RootNamespace}.{ITypeConverterSource.InterfaceName}<{sourceProperty.Type.ToDisplayString()}, {property.GetTypeSymbol()?.ToDisplayString()}>'.");
|
||||||
|
|
||||||
internal static Diagnostic ConfigurationParseError(string error) =>
|
internal static Diagnostic ConfigurationParseError(string error) =>
|
||||||
Create($"{ErrorId}040", Location.None, error);
|
Create($"{ErrorId}040", Location.None, error);
|
||||||
|
|
||||||
internal static Diagnostic MissingConstructorArgument(ConstructorDeclarationSyntax constructorSyntax) =>
|
internal static Diagnostic MissingConstructorArgument(ConstructorDeclarationSyntax constructorSyntax) =>
|
||||||
Create($"{ErrorId}050", constructorSyntax.GetLocation(), "There are no argument given that corresponds to the required formal parameter.");
|
Create($"{ErrorId}050", constructorSyntax.GetLocation(), "There are no argument given that corresponds to the required formal parameter.");
|
||||||
|
|
||||||
private static Diagnostic Create(string id, Location? location, string message, DiagnosticSeverity severity = DiagnosticSeverity.Error) =>
|
private static Diagnostic Create(string id, Location? location, string message, DiagnosticSeverity severity = DiagnosticSeverity.Error) =>
|
||||||
Diagnostic.Create(new DiagnosticDescriptor(id, string.Empty, message, UsageCategory, severity, true), location ?? Location.None);
|
Diagnostic.Create(new DiagnosticDescriptor(id, string.Empty, message, UsageCategory, severity, true), location ?? Location.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
using MapTo.Sources;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MapTo.Extensions
|
||||||
|
{
|
||||||
|
internal static class CommonExtensions
|
||||||
|
{
|
||||||
|
internal static SourceBuilder WriteComment(this SourceBuilder builder, string comment)
|
||||||
|
{
|
||||||
|
return builder.WriteLine($"// {comment}");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static SourceBuilder WriteMappedProperties(this SourceBuilder builder, System.Collections.Immutable.ImmutableArray<MappedProperty> mappedProperties)
|
||||||
|
{
|
||||||
|
foreach (var item in mappedProperties)
|
||||||
|
{
|
||||||
|
builder.WriteComment($"Name: {item.Name}");
|
||||||
|
builder.WriteComment($"Type: {item.Type}");
|
||||||
|
builder.WriteComment($"MappedSourcePropertyTypeName: {item.MappedSourcePropertyTypeName}");
|
||||||
|
builder.WriteComment($"IsEnumerable: {item.IsEnumerable}");
|
||||||
|
builder.WriteComment($"SourcePropertyName: {item.SourcePropertyName}");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace MapTo.Extensions
|
namespace MapTo.Extensions
|
||||||
{
|
{
|
||||||
internal static class EnumExtensions
|
internal static class EnumExtensions
|
||||||
{
|
{
|
||||||
internal static string ToLowercaseString(this Enum member) => member.ToString().ToLower();
|
internal static string ToLowercaseString(this Enum member) => member.ToString().ToLower();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,19 +1,19 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace MapTo.Extensions
|
namespace MapTo.Extensions
|
||||||
{
|
{
|
||||||
internal static class EnumerableExtensions
|
internal static class EnumerableExtensions
|
||||||
{
|
{
|
||||||
internal static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
|
internal static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
|
||||||
{
|
{
|
||||||
foreach (var item in enumerable)
|
foreach (var item in enumerable)
|
||||||
{
|
{
|
||||||
action(item);
|
action(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool IsEmpty<T>(this IEnumerable<T> enumerable) => !enumerable.Any();
|
internal static bool IsEmpty<T>(this IEnumerable<T> enumerable) => !enumerable.Any();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,51 +1,51 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
using Microsoft.CodeAnalysis.Text;
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
namespace MapTo.Extensions
|
namespace MapTo.Extensions
|
||||||
{
|
{
|
||||||
internal static class GeneratorExecutionContextExtensions
|
internal static class GeneratorExecutionContextExtensions
|
||||||
{
|
{
|
||||||
private const string PropertyNameSuffix = "MapTo_";
|
private const string PropertyNameSuffix = "MapTo_";
|
||||||
|
|
||||||
internal static T GetBuildGlobalOption<T>(this GeneratorExecutionContext context, string propertyName, T defaultValue = default!) where T : notnull
|
internal static T GetBuildGlobalOption<T>(this GeneratorExecutionContext context, string propertyName, T defaultValue = default!) where T : notnull
|
||||||
{
|
{
|
||||||
if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(GetBuildPropertyName(propertyName), out var optionValue) || string.IsNullOrWhiteSpace(optionValue))
|
if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(GetBuildPropertyName(propertyName), out var optionValue) || string.IsNullOrWhiteSpace(optionValue))
|
||||||
{
|
{
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var type = typeof(T);
|
var type = typeof(T);
|
||||||
|
|
||||||
if (!type.IsEnum)
|
if (!type.IsEnum)
|
||||||
{
|
{
|
||||||
return (T)Convert.ChangeType(optionValue, type);
|
return (T)Convert.ChangeType(optionValue, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return (T)Enum.Parse(type, optionValue, true);
|
return (T)Enum.Parse(type, optionValue, true);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
context.ReportDiagnostic(DiagnosticsFactory.ConfigurationParseError($"'{optionValue}' is not a valid value for {PropertyNameSuffix}{propertyName} property."));
|
context.ReportDiagnostic(DiagnosticsFactory.ConfigurationParseError($"'{optionValue}' is not a valid value for {PropertyNameSuffix}{propertyName} property."));
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string GetBuildPropertyName(string propertyName) => $"build_property.{PropertyNameSuffix}{propertyName}";
|
internal static string GetBuildPropertyName(string propertyName) => $"build_property.{PropertyNameSuffix}{propertyName}";
|
||||||
|
|
||||||
internal static Compilation AddSource(this Compilation compilation, ref GeneratorExecutionContext context, SourceCode sourceCode)
|
internal static Compilation AddSource(this Compilation compilation, ref GeneratorExecutionContext context, SourceCode sourceCode)
|
||||||
{
|
{
|
||||||
var sourceText = SourceText.From(sourceCode.Text, Encoding.UTF8);
|
var sourceText = SourceText.From(sourceCode.Text, Encoding.UTF8);
|
||||||
context.AddSource(sourceCode.HintName, sourceText);
|
context.AddSource(sourceCode.HintName, sourceText);
|
||||||
|
|
||||||
// NB: https://github.com/dotnet/roslyn/issues/49753
|
// NB: https://github.com/dotnet/roslyn/issues/49753
|
||||||
// To be replaced after above issue is resolved.
|
// To be replaced after above issue is resolved.
|
||||||
var options = (CSharpParseOptions)((CSharpCompilation)compilation).SyntaxTrees[0].Options;
|
var options = (CSharpParseOptions)((CSharpCompilation)compilation).SyntaxTrees[0].Options;
|
||||||
return compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(sourceText, options));
|
return compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(sourceText, options));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,136 +1,131 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
namespace MapTo.Extensions
|
namespace MapTo.Extensions
|
||||||
{
|
{
|
||||||
internal static class RoslynExtensions
|
internal static class RoslynExtensions
|
||||||
{
|
{
|
||||||
public static IEnumerable<ITypeSymbol> GetBaseTypesAndThis(this ITypeSymbol type)
|
public static IEnumerable<ITypeSymbol> GetBaseTypesAndThis(this ITypeSymbol type)
|
||||||
{
|
{
|
||||||
var current = type;
|
var current = type;
|
||||||
while (current != null)
|
while (current != null)
|
||||||
{
|
{
|
||||||
yield return current;
|
yield return current;
|
||||||
|
|
||||||
current = current.BaseType;
|
current = current.BaseType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<ISymbol> GetAllMembers(this ITypeSymbol type, bool includeBaseTypeMembers = true)
|
public static IEnumerable<ISymbol> GetAllMembers(this ITypeSymbol type, bool includeBaseTypeMembers = true)
|
||||||
{
|
{
|
||||||
return includeBaseTypeMembers
|
return includeBaseTypeMembers
|
||||||
? type.GetBaseTypesAndThis().SelectMany(t => t.GetMembers())
|
? type.GetBaseTypesAndThis().SelectMany(t => t.GetMembers())
|
||||||
: type.GetMembers();
|
: type.GetMembers();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CompilationUnitSyntax GetCompilationUnit(this SyntaxNode syntaxNode) => syntaxNode.Ancestors().OfType<CompilationUnitSyntax>().Single();
|
public static CompilationUnitSyntax GetCompilationUnit(this SyntaxNode syntaxNode) => syntaxNode.Ancestors().OfType<CompilationUnitSyntax>().Single();
|
||||||
|
|
||||||
public static string GetIdentifierName(this TypeDeclarationSyntax typeSyntax) => typeSyntax.Identifier.Text;
|
public static string GetIdentifierName(this TypeDeclarationSyntax typeSyntax) => typeSyntax.Identifier.Text;
|
||||||
|
|
||||||
public static AttributeSyntax? GetAttribute(this MemberDeclarationSyntax typeDeclarationSyntax, string attributeName)
|
public static AttributeSyntax? GetAttribute(this TypeDeclarationSyntax typeDeclarationSyntax, string attributeName)
|
||||||
{
|
{
|
||||||
var attributeLists = typeDeclarationSyntax.AttributeLists;
|
return typeDeclarationSyntax.AttributeLists
|
||||||
var selection = attributeLists
|
.SelectMany(al => al.Attributes)
|
||||||
.SelectMany(al => al.Attributes)
|
.SingleOrDefault(a =>
|
||||||
.FirstOrDefault(x => x.Name.ToString().Contains(attributeName));
|
(a.Name as IdentifierNameSyntax)?.Identifier.ValueText == attributeName ||
|
||||||
|
((a.Name as QualifiedNameSyntax)?.Right as IdentifierNameSyntax)?.Identifier.ValueText == attributeName);
|
||||||
return selection;
|
}
|
||||||
}
|
|
||||||
|
public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) =>
|
||||||
public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) =>
|
symbol.GetAttributes().Any(a => a.AttributeClass?.Equals(attributeSymbol, SymbolEqualityComparer.Default) == true);
|
||||||
symbol.GetAttributes().Any(a => a.AttributeClass?.Equals(attributeSymbol, SymbolEqualityComparer.Default) == true);
|
|
||||||
|
public static IEnumerable<AttributeData> GetAttributes(this ISymbol symbol, ITypeSymbol attributeSymbol) =>
|
||||||
public static IEnumerable<AttributeData> GetAttributes(this ISymbol symbol, ITypeSymbol attributeSymbol) =>
|
symbol.GetAttributes().Where(a => a.AttributeClass?.Equals(attributeSymbol, SymbolEqualityComparer.Default) == true);
|
||||||
symbol.GetAttributes().Where(a => a.AttributeClass?.Equals(attributeSymbol, SymbolEqualityComparer.Default) == true);
|
|
||||||
|
public static AttributeData? GetAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) =>
|
||||||
public static AttributeData? GetAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) =>
|
symbol.GetAttributes(attributeSymbol).FirstOrDefault();
|
||||||
symbol.GetAttributes(attributeSymbol).FirstOrDefault();
|
|
||||||
|
public static string? GetNamespace(this TypeDeclarationSyntax typeDeclarationSyntax) => typeDeclarationSyntax
|
||||||
public static string? GetNamespace(this MemberDeclarationSyntax typeDeclarationSyntax) => typeDeclarationSyntax
|
.Ancestors()
|
||||||
.Ancestors()
|
.OfType<NamespaceDeclarationSyntax>()
|
||||||
.OfType<NamespaceDeclarationSyntax>()
|
.FirstOrDefault()
|
||||||
.FirstOrDefault()
|
?.Name
|
||||||
?.Name
|
.ToString();
|
||||||
.ToString();
|
|
||||||
|
public static bool HasCompatibleTypes(this Compilation compilation, ISymbol source, ISymbol destination) =>
|
||||||
public static bool HasCompatibleTypes(this Compilation compilation, ISymbol source, ISymbol destination) =>
|
source.TryGetTypeSymbol(out var sourceType) && destination.TryGetTypeSymbol(out var destinationType) &&
|
||||||
source.TryGetTypeSymbol(out var sourceType) && destination.TryGetTypeSymbol(out var destinationType) &&
|
(SymbolEqualityComparer.Default.Equals(destinationType, sourceType) || compilation.HasImplicitConversion(sourceType, destinationType));
|
||||||
(SymbolEqualityComparer.Default.Equals(destinationType, sourceType) || compilation.HasImplicitConversion(sourceType, destinationType));
|
|
||||||
|
public static bool TryGetTypeSymbol(this ISymbol symbol, [NotNullWhen(true)] out ITypeSymbol? typeSymbol)
|
||||||
public static bool TryGetTypeSymbol(this ISymbol symbol, [NotNullWhen(true)] out ITypeSymbol? typeSymbol)
|
{
|
||||||
{
|
switch (symbol)
|
||||||
switch (symbol)
|
{
|
||||||
{
|
case IPropertySymbol propertySymbol:
|
||||||
case IPropertySymbol propertySymbol:
|
typeSymbol = propertySymbol.Type;
|
||||||
typeSymbol = propertySymbol.Type;
|
return true;
|
||||||
return true;
|
|
||||||
|
case IParameterSymbol parameterSymbol:
|
||||||
case IFieldSymbol fieldSymbol:
|
typeSymbol = parameterSymbol.Type;
|
||||||
typeSymbol = fieldSymbol.Type;
|
return true;
|
||||||
return true;
|
|
||||||
|
default:
|
||||||
case IParameterSymbol parameterSymbol:
|
typeSymbol = null;
|
||||||
typeSymbol = parameterSymbol.Type;
|
return false;
|
||||||
return true;
|
}
|
||||||
|
}
|
||||||
default:
|
|
||||||
typeSymbol = null;
|
public static ITypeSymbol? GetTypeSymbol(this ISymbol symbol) => symbol.TryGetTypeSymbol(out var typeSymbol) ? typeSymbol : null;
|
||||||
return false;
|
|
||||||
}
|
public static IPropertySymbol? FindProperty(this IEnumerable<IPropertySymbol> properties, IPropertySymbol targetProperty)
|
||||||
}
|
{
|
||||||
|
return properties.SingleOrDefault(p =>
|
||||||
public static ITypeSymbol? GetTypeSymbol(this ISymbol symbol) => symbol.TryGetTypeSymbol(out var typeSymbol) ? typeSymbol : null;
|
p.Name == targetProperty.Name &&
|
||||||
|
(p.NullableAnnotation != NullableAnnotation.Annotated ||
|
||||||
public static IPropertySymbol? FindProperty(this IEnumerable<IPropertySymbol> properties, IPropertySymbol targetProperty)
|
p.NullableAnnotation == NullableAnnotation.Annotated &&
|
||||||
{
|
targetProperty.NullableAnnotation == NullableAnnotation.Annotated));
|
||||||
return properties.FirstOrDefault(p =>
|
}
|
||||||
p.Name == targetProperty.Name &&
|
|
||||||
(p.NullableAnnotation != NullableAnnotation.Annotated ||
|
public static INamedTypeSymbol GetTypeByMetadataNameOrThrow(this Compilation compilation, string fullyQualifiedMetadataName) =>
|
||||||
p.NullableAnnotation == NullableAnnotation.Annotated &&
|
compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) ?? throw new TypeLoadException($"Unable to find '{fullyQualifiedMetadataName}' type.");
|
||||||
targetProperty.NullableAnnotation == NullableAnnotation.Annotated));
|
|
||||||
}
|
public static bool IsGenericEnumerable(this Compilation compilation, ITypeSymbol typeSymbol) =>
|
||||||
|
typeSymbol is INamedTypeSymbol { IsGenericType: true } &&
|
||||||
public static INamedTypeSymbol GetTypeByMetadataNameOrThrow(this Compilation compilation, string fullyQualifiedMetadataName) =>
|
compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T).Equals(typeSymbol.OriginalDefinition, SymbolEqualityComparer.Default);
|
||||||
compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) ?? throw new TypeLoadException($"Unable to find '{fullyQualifiedMetadataName}' type.");
|
|
||||||
|
public static bool IsArray(this Compilation compilation, ITypeSymbol typeSymbol) => typeSymbol is IArrayTypeSymbol;
|
||||||
public static bool IsGenericEnumerable(this Compilation compilation, ITypeSymbol typeSymbol) =>
|
|
||||||
typeSymbol is INamedTypeSymbol { IsGenericType: true } &&
|
public static bool IsPrimitiveType(this ITypeSymbol type) => type.SpecialType is
|
||||||
compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T).Equals(typeSymbol.OriginalDefinition, SymbolEqualityComparer.Default);
|
SpecialType.System_String or
|
||||||
|
SpecialType.System_Boolean or
|
||||||
public static bool IsArray(this Compilation compilation, ITypeSymbol typeSymbol) => typeSymbol is IArrayTypeSymbol;
|
SpecialType.System_SByte or
|
||||||
|
SpecialType.System_Int16 or
|
||||||
public static bool IsPrimitiveType(this ITypeSymbol type) => type.SpecialType is
|
SpecialType.System_Int32 or
|
||||||
SpecialType.System_String or
|
SpecialType.System_Int64 or
|
||||||
SpecialType.System_Boolean or
|
SpecialType.System_Byte or
|
||||||
SpecialType.System_SByte or
|
SpecialType.System_UInt16 or
|
||||||
SpecialType.System_Int16 or
|
SpecialType.System_UInt32 or
|
||||||
SpecialType.System_Int32 or
|
SpecialType.System_UInt64 or
|
||||||
SpecialType.System_Int64 or
|
SpecialType.System_Single or
|
||||||
SpecialType.System_Byte or
|
SpecialType.System_Double or
|
||||||
SpecialType.System_UInt16 or
|
SpecialType.System_Char or
|
||||||
SpecialType.System_UInt32 or
|
SpecialType.System_Object;
|
||||||
SpecialType.System_UInt64 or
|
|
||||||
SpecialType.System_Single or
|
public static SyntaxNode? GetSyntaxNode(this ISymbol symbol) =>
|
||||||
SpecialType.System_Double or
|
symbol.Locations.FirstOrDefault() is { } location ? location.SourceTree?.GetRoot().FindNode(location.SourceSpan) : null;
|
||||||
SpecialType.System_Char or
|
|
||||||
SpecialType.System_Object;
|
public static IEnumerable<INamedTypeSymbol> GetTypesByMetadataName(this Compilation compilation, string typeMetadataName)
|
||||||
|
{
|
||||||
public static SyntaxNode? GetSyntaxNode(this ISymbol symbol) =>
|
return compilation.References
|
||||||
symbol.Locations.FirstOrDefault() is { } location ? location.SourceTree?.GetRoot().FindNode(location.SourceSpan) : null;
|
.Select(compilation.GetAssemblyOrModuleSymbol)
|
||||||
|
.OfType<IAssemblySymbol>()
|
||||||
public static IEnumerable<INamedTypeSymbol> GetTypesByMetadataName(this Compilation compilation, string typeMetadataName)
|
.Select(assemblySymbol => assemblySymbol.GetTypeByMetadataName(typeMetadataName))
|
||||||
{
|
.Where(t => t != null)!;
|
||||||
return compilation.References
|
}
|
||||||
.Select(compilation.GetAssemblyOrModuleSymbol)
|
|
||||||
.OfType<IAssemblySymbol>()
|
public static bool TypeByMetadataNameExists(this Compilation compilation, string typeMetadataName) => GetTypesByMetadataName(compilation, typeMetadataName).Any();
|
||||||
.Select(assemblySymbol => assemblySymbol.GetTypeByMetadataName(typeMetadataName))
|
}
|
||||||
.Where(t => t != null)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TypeByMetadataNameExists(this Compilation compilation, string typeMetadataName) => GetTypesByMetadataName(compilation, typeMetadataName).Any();
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,30 +1,30 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace MapTo.Extensions
|
namespace MapTo.Extensions
|
||||||
{
|
{
|
||||||
internal static class StringBuilderExtensions
|
internal static class StringBuilderExtensions
|
||||||
{
|
{
|
||||||
public static StringBuilder PadLeft(this StringBuilder builder, int width)
|
public static StringBuilder PadLeft(this StringBuilder builder, int width)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < width; i++)
|
for (var i = 0; i < width; i++)
|
||||||
{
|
{
|
||||||
builder.Append(" ");
|
builder.Append(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static StringBuilder AppendOpeningBracket(this StringBuilder builder, int indent = 0) => builder.AppendLine().PadLeft(indent).AppendFormat("{{{0}", Environment.NewLine);
|
internal static StringBuilder AppendOpeningBracket(this StringBuilder builder, int indent = 0) => builder.AppendLine().PadLeft(indent).AppendFormat("{{{0}", Environment.NewLine);
|
||||||
|
|
||||||
internal static StringBuilder AppendClosingBracket(this StringBuilder builder, int indent = 0, bool padNewLine = true)
|
internal static StringBuilder AppendClosingBracket(this StringBuilder builder, int indent = 0, bool padNewLine = true)
|
||||||
{
|
{
|
||||||
if (padNewLine)
|
if (padNewLine)
|
||||||
{
|
{
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.PadLeft(indent).Append("}");
|
return builder.PadLeft(indent).Append("}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MapTo.Extensions
|
namespace MapTo.Extensions
|
||||||
{
|
{
|
||||||
internal static class StringExtensions
|
internal static class StringExtensions
|
||||||
{
|
{
|
||||||
public static string ToCamelCase(this string value) => string.IsNullOrWhiteSpace(value) ? value : $"{char.ToLower(value[0])}{value.Substring(1)}";
|
public static string ToCamelCase(this string value) => string.IsNullOrWhiteSpace(value) ? value : $"{char.ToLower(value[0])}{value.Substring(1)}";
|
||||||
|
|
||||||
public static string ToSourceCodeString(this object? value) => value switch
|
public static string ToSourceCodeString(this object? value) => value switch
|
||||||
{
|
{
|
||||||
null => "null",
|
null => "null",
|
||||||
string strValue => $"\"{strValue}\"",
|
string strValue => $"\"{strValue}\"",
|
||||||
char charValue => $"'{charValue}'",
|
char charValue => $"'{charValue}'",
|
||||||
_ => value.ToString()
|
_ => value.ToString()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,50 +1,51 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<LangVersion>9</LangVersion>
|
<LangVersion>9</LangVersion>
|
||||||
<Description>An object to object mapping generator using Roslyn source generator.</Description>
|
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<AssemblyName>MapTo</AssemblyName>
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<Description>An object to object mapping generator using Roslyn source generator.</Description>
|
||||||
<PackageId>MapTo</PackageId>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<PackageProjectUrl>https://github.com/mrtaikandi/mapto</PackageProjectUrl>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<PackageVersion>$(Version)</PackageVersion>
|
<PackageId>MapTo</PackageId>
|
||||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
<RepositoryUrl>https://github.com/mrtaikandi/mapto</RepositoryUrl>
|
<PackageProjectUrl>https://github.com/mrtaikandi/mapto</PackageProjectUrl>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||||
<RootNamespace>MapTo</RootNamespace>
|
<PackageVersion>$(Version)</PackageVersion>
|
||||||
</PropertyGroup>
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
|
<RepositoryUrl>https://github.com/mrtaikandi/mapto</RepositoryUrl>
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<DocumentationFile>bin\Release\MapTo.xml</DocumentationFile>
|
<RootNamespace>MapTo</RootNamespace>
|
||||||
<Optimize>false</Optimize>
|
</PropertyGroup>
|
||||||
</PropertyGroup>
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
<ItemGroup>
|
<DocumentationFile>bin\Release\MapTo.xml</DocumentationFile>
|
||||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
</PropertyGroup>
|
||||||
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
|
|
||||||
</AssemblyAttribute>
|
<ItemGroup>
|
||||||
</ItemGroup>
|
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||||
|
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
|
||||||
<ItemGroup>
|
</AssemblyAttribute>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
|
</ItemGroup>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<ItemGroup>
|
||||||
</PackageReference>
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2">
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<PackageReference Update="Nerdbank.GitVersioning">
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<Version>3.5.109</Version>
|
</PackageReference>
|
||||||
</PackageReference>
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
<None Include="..\..\LICENSE" Pack="true" PackagePath="" Visible="false" />
|
||||||
<None Include="MapTo.props" Pack="true" PackagePath="build" Visible="false" />
|
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||||
</ItemGroup>
|
<None Include="MapTo.props" Pack="true" PackagePath="build" Visible="false" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="bin\Release\netstandard2.0" />
|
<ItemGroup>
|
||||||
</ItemGroup>
|
<Folder Include="bin\Release\netstandard2.0" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
|
||||||
|
</Project>
|
|
@ -1,5 +1,5 @@
|
||||||
<Project>
|
<Project>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<CompilerVisibleProperty Include="MapTo_ConstructorAccessModifier" />
|
<CompilerVisibleProperty Include="MapTo_ConstructorAccessModifier" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -1,81 +1,74 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Linq;
|
||||||
using System.Linq;
|
using MapTo.Extensions;
|
||||||
using System.Threading;
|
using MapTo.Sources;
|
||||||
using MapTo.Extensions;
|
using Microsoft.CodeAnalysis;
|
||||||
using MapTo.Sources;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
namespace MapTo
|
||||||
|
{
|
||||||
namespace MapTo
|
/// <summary>
|
||||||
{
|
/// MapTo source generator.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// MapTo source generator.
|
[Generator]
|
||||||
/// </summary>
|
public class MapToGenerator : ISourceGenerator
|
||||||
[Generator]
|
{
|
||||||
public class MapToGenerator : ISourceGenerator
|
/// <inheritdoc />
|
||||||
{
|
public void Initialize(GeneratorInitializationContext context)
|
||||||
/// <inheritdoc />
|
{
|
||||||
public void Initialize(GeneratorInitializationContext context)
|
context.RegisterForSyntaxNotifications(() => new MapToSyntaxReceiver());
|
||||||
{
|
}
|
||||||
|
|
||||||
context.RegisterForSyntaxNotifications(() => new MapToSyntaxReceiver());
|
/// <inheritdoc />
|
||||||
|
public void Execute(GeneratorExecutionContext context)
|
||||||
}
|
{
|
||||||
|
try
|
||||||
/// <inheritdoc />
|
{
|
||||||
public void Execute(GeneratorExecutionContext context)
|
var options = SourceGenerationOptions.From(context);
|
||||||
{
|
|
||||||
try
|
var compilation = context.Compilation
|
||||||
{
|
.AddSource(ref context, MapFromAttributeSource.Generate(options))
|
||||||
var options = SourceGenerationOptions.From(context);
|
.AddSource(ref context, IgnorePropertyAttributeSource.Generate(options))
|
||||||
|
.AddSource(ref context, ITypeConverterSource.Generate(options))
|
||||||
var compilation = context.Compilation
|
.AddSource(ref context, MapTypeConverterAttributeSource.Generate(options))
|
||||||
.AddSource(ref context, UseUpdateAttributeSource.Generate(options))
|
.AddSource(ref context, MapPropertyAttributeSource.Generate(options))
|
||||||
.AddSource(ref context, JsonExtensionAttributeSource.Generate(options))
|
.AddSource(ref context, MappingContextSource.Generate(options));
|
||||||
.AddSource(ref context, MapFromAttributeSource.Generate(options))
|
|
||||||
.AddSource(ref context, IgnoreMemberAttributeSource.Generate(options))
|
if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateTypes.Any())
|
||||||
.AddSource(ref context, ITypeConverterSource.Generate(options))
|
{
|
||||||
.AddSource(ref context, MapTypeConverterAttributeSource.Generate(options))
|
AddGeneratedMappingsClasses(context, compilation, receiver.CandidateTypes, options);
|
||||||
.AddSource(ref context, MapPropertyAttributeSource.Generate(options))
|
}
|
||||||
.AddSource(ref context, MappingContextSource.Generate(options));
|
}
|
||||||
|
catch (Exception ex)
|
||||||
if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateTypes.Any())
|
{
|
||||||
{
|
Console.WriteLine(ex);
|
||||||
AddGeneratedMappingsClasses(context, compilation, receiver.CandidateTypes, options);
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
private static void AddGeneratedMappingsClasses(GeneratorExecutionContext context, Compilation compilation, IEnumerable<TypeDeclarationSyntax> candidateTypes, SourceGenerationOptions options)
|
||||||
Console.WriteLine(ex);
|
{
|
||||||
throw;
|
foreach (var typeDeclarationSyntax in candidateTypes)
|
||||||
}
|
{
|
||||||
}
|
var mappingContext = MappingContext.Create(compilation, options, typeDeclarationSyntax);
|
||||||
|
mappingContext.Diagnostics.ForEach(context.ReportDiagnostic);
|
||||||
private static void AddGeneratedMappingsClasses(GeneratorExecutionContext context, Compilation compilation, IEnumerable<TypeDeclarationSyntax> candidateTypes, SourceGenerationOptions options)
|
|
||||||
{
|
if (mappingContext.Model is null)
|
||||||
foreach (var typeDeclarationSyntax in candidateTypes)
|
{
|
||||||
{
|
continue;
|
||||||
var mappingContext = MappingContext.Create(compilation, options, typeDeclarationSyntax);
|
}
|
||||||
|
|
||||||
mappingContext.Diagnostics.ForEach(context.ReportDiagnostic);
|
var (source, hintName) = typeDeclarationSyntax switch
|
||||||
|
{
|
||||||
if (mappingContext.Model is null)
|
StructDeclarationSyntax => MapStructSource.Generate(mappingContext.Model),
|
||||||
{
|
ClassDeclarationSyntax => MapClassSource.Generate(mappingContext.Model),
|
||||||
continue;
|
RecordDeclarationSyntax => MapRecordSource.Generate(mappingContext.Model),
|
||||||
}
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
};
|
||||||
var (source, hintName) = typeDeclarationSyntax switch
|
|
||||||
{
|
context.AddSource(hintName, source);
|
||||||
StructDeclarationSyntax => MapStructSource.Generate(mappingContext.Model),
|
}
|
||||||
ClassDeclarationSyntax => MapClassSource.Generate(mappingContext.Model),
|
}
|
||||||
RecordDeclarationSyntax => MapRecordSource.Generate(mappingContext.Model),
|
}
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
|
||||||
};
|
|
||||||
|
|
||||||
context.AddSource(hintName, source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,39 +1,39 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MapTo.Sources;
|
using MapTo.Sources;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
namespace MapTo
|
namespace MapTo
|
||||||
{
|
{
|
||||||
internal class MapToSyntaxReceiver : ISyntaxReceiver
|
internal class MapToSyntaxReceiver : ISyntaxReceiver
|
||||||
{
|
{
|
||||||
public List<TypeDeclarationSyntax> CandidateTypes { get; } = new();
|
public List<TypeDeclarationSyntax> CandidateTypes { get; } = new();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||||
{
|
{
|
||||||
if (syntaxNode is not TypeDeclarationSyntax { AttributeLists: { Count: >= 1 } attributes } typeDeclarationSyntax)
|
if (syntaxNode is not TypeDeclarationSyntax { AttributeLists: { Count: >= 1 } attributes } typeDeclarationSyntax)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var attributeSyntax = attributes
|
var attributeSyntax = attributes
|
||||||
.SelectMany(a => a.Attributes)
|
.SelectMany(a => a.Attributes)
|
||||||
.FirstOrDefault(a => a.Name is
|
.SingleOrDefault(a => a.Name is
|
||||||
IdentifierNameSyntax { Identifier: { ValueText: MapFromAttributeSource.AttributeName } } // For: [MapFrom]
|
IdentifierNameSyntax { Identifier: { ValueText: MapFromAttributeSource.AttributeName } } // For: [MapFrom]
|
||||||
or
|
or
|
||||||
QualifiedNameSyntax // For: [MapTo.MapFrom]
|
QualifiedNameSyntax // For: [MapTo.MapFrom]
|
||||||
{
|
{
|
||||||
Left: IdentifierNameSyntax { Identifier: { ValueText: Constants.RootNamespace } },
|
Left: IdentifierNameSyntax { Identifier: { ValueText: Constants.RootNamespace } },
|
||||||
Right: IdentifierNameSyntax { Identifier: { ValueText: MapFromAttributeSource.AttributeName } }
|
Right: IdentifierNameSyntax { Identifier: { ValueText: MapFromAttributeSource.AttributeName } }
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (attributeSyntax is not null)
|
if (attributeSyntax is not null)
|
||||||
{
|
{
|
||||||
CandidateTypes.Add(typeDeclarationSyntax);
|
CandidateTypes.Add(typeDeclarationSyntax);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,384 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using MapTo.Extensions;
|
||||||
|
using MapTo.Sources;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
|
namespace MapTo
|
||||||
|
{
|
||||||
|
internal static class MappingContextExtensions
|
||||||
|
{
|
||||||
|
internal static ImmutableArray<MappedProperty> GetReadOnlyMappedProperties(this ImmutableArray<MappedProperty> mappedProperties) => mappedProperties.Where(p => p.isReadOnly).ToImmutableArray()!;
|
||||||
|
internal static ImmutableArray<MappedProperty> GetWritableMappedProperties(this ImmutableArray<MappedProperty> mappedProperties) => mappedProperties.Where(p => !p.isReadOnly).ToImmutableArray()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal abstract class MappingContext
|
||||||
|
{
|
||||||
|
private readonly List<SymbolDisplayPart> _ignoredNamespaces;
|
||||||
|
|
||||||
|
protected MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
||||||
|
{
|
||||||
|
_ignoredNamespaces = new();
|
||||||
|
Diagnostics = ImmutableArray<Diagnostic>.Empty;
|
||||||
|
Usings = ImmutableArray.Create("System", Constants.RootNamespace);
|
||||||
|
SourceGenerationOptions = sourceGenerationOptions;
|
||||||
|
TypeSyntax = typeSyntax;
|
||||||
|
Compilation = compilation;
|
||||||
|
|
||||||
|
IgnorePropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(IgnorePropertyAttributeSource.FullyQualifiedName);
|
||||||
|
MapTypeConverterAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapTypeConverterAttributeSource.FullyQualifiedName);
|
||||||
|
TypeConverterInterfaceTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(ITypeConverterSource.FullyQualifiedName);
|
||||||
|
MapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName);
|
||||||
|
MapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.FullyQualifiedName);
|
||||||
|
MappingContextTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MappingContextSource.FullyQualifiedName);
|
||||||
|
|
||||||
|
AddUsingIfRequired(sourceGenerationOptions.SupportNullableStaticAnalysis, "System.Diagnostics.CodeAnalysis");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableArray<Diagnostic> Diagnostics { get; private set; }
|
||||||
|
|
||||||
|
public MappingModel? Model { get; private set; }
|
||||||
|
|
||||||
|
protected Compilation Compilation { get; }
|
||||||
|
|
||||||
|
protected INamedTypeSymbol IgnorePropertyAttributeTypeSymbol { get; }
|
||||||
|
|
||||||
|
protected INamedTypeSymbol MapFromAttributeTypeSymbol { get; }
|
||||||
|
|
||||||
|
protected INamedTypeSymbol MappingContextTypeSymbol { get; }
|
||||||
|
|
||||||
|
protected INamedTypeSymbol MapPropertyAttributeTypeSymbol { get; }
|
||||||
|
|
||||||
|
protected INamedTypeSymbol MapTypeConverterAttributeTypeSymbol { get; }
|
||||||
|
|
||||||
|
protected SourceGenerationOptions SourceGenerationOptions { get; }
|
||||||
|
|
||||||
|
protected INamedTypeSymbol TypeConverterInterfaceTypeSymbol { get; }
|
||||||
|
|
||||||
|
protected TypeDeclarationSyntax TypeSyntax { get; }
|
||||||
|
|
||||||
|
protected ImmutableArray<string> Usings { get; private set; }
|
||||||
|
|
||||||
|
public static MappingContext Create(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
||||||
|
{
|
||||||
|
MappingContext context = typeSyntax switch
|
||||||
|
{
|
||||||
|
StructDeclarationSyntax => new StructMappingContext(compilation, sourceGenerationOptions, typeSyntax),
|
||||||
|
ClassDeclarationSyntax => new ClassMappingContext(compilation, sourceGenerationOptions, typeSyntax),
|
||||||
|
RecordDeclarationSyntax => new RecordMappingContext(compilation, sourceGenerationOptions, typeSyntax),
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Model = context.CreateMappingModel();
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void AddDiagnostic(Diagnostic diagnostic)
|
||||||
|
{
|
||||||
|
Diagnostics = Diagnostics.Add(diagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void AddUsingIfRequired(ISymbol? namedTypeSymbol) =>
|
||||||
|
AddUsingIfRequired(namedTypeSymbol?.ContainingNamespace.IsGlobalNamespace == false, namedTypeSymbol?.ContainingNamespace);
|
||||||
|
|
||||||
|
protected void AddUsingIfRequired(bool condition, INamespaceSymbol? ns) =>
|
||||||
|
AddUsingIfRequired(condition && ns is not null && !_ignoredNamespaces.Contains(ns.ToDisplayParts().First()), ns?.ToDisplayString());
|
||||||
|
|
||||||
|
protected void AddUsingIfRequired(bool condition, string? ns)
|
||||||
|
{
|
||||||
|
if (ns is not null && condition && ns != TypeSyntax.GetNamespace() && !Usings.Contains(ns))
|
||||||
|
{
|
||||||
|
Usings = Usings.Add(ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IPropertySymbol? FindSourceProperty(IEnumerable<IPropertySymbol> sourceProperties, ISymbol property)
|
||||||
|
{
|
||||||
|
var propertyName = property
|
||||||
|
.GetAttribute(MapPropertyAttributeTypeSymbol)
|
||||||
|
?.NamedArguments
|
||||||
|
.SingleOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName)
|
||||||
|
.Value.Value as string ?? property.Name;
|
||||||
|
|
||||||
|
return sourceProperties.SingleOrDefault(p => p.Name == propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract ImmutableArray<MappedProperty> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
|
||||||
|
protected abstract ImmutableArray<MappedProperty> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
|
||||||
|
|
||||||
|
protected INamedTypeSymbol? GetSourceTypeSymbol(TypeDeclarationSyntax typeDeclarationSyntax, SemanticModel? semanticModel = null) =>
|
||||||
|
GetSourceTypeSymbol(typeDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName), semanticModel);
|
||||||
|
|
||||||
|
protected INamedTypeSymbol? GetSourceTypeSymbol(SyntaxNode? attributeSyntax, SemanticModel? semanticModel = null)
|
||||||
|
{
|
||||||
|
if (attributeSyntax is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
semanticModel ??= Compilation.GetSemanticModel(attributeSyntax.SyntaxTree);
|
||||||
|
var sourceTypeExpressionSyntax = attributeSyntax
|
||||||
|
.DescendantNodes()
|
||||||
|
.OfType<TypeOfExpressionSyntax>()
|
||||||
|
.SingleOrDefault();
|
||||||
|
|
||||||
|
return sourceTypeExpressionSyntax is not null ? semanticModel.GetTypeInfo(sourceTypeExpressionSyntax.Type).Type as INamedTypeSymbol : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool IsTypeInheritFromMappedBaseClass(SemanticModel semanticModel)
|
||||||
|
{
|
||||||
|
return TypeSyntax.BaseList is not null && TypeSyntax.BaseList.Types
|
||||||
|
.Select(t => semanticModel.GetTypeInfo(t.Type).Type)
|
||||||
|
.Any(t => t?.GetAttribute(MapFromAttributeTypeSymbol) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual MappedProperty? MapProperty(ISymbol sourceTypeSymbol, IReadOnlyCollection<IPropertySymbol> sourceProperties, ISymbol property)
|
||||||
|
{
|
||||||
|
var sourceProperty = FindSourceProperty(sourceProperties, property);
|
||||||
|
if (sourceProperty is null || !property.TryGetTypeSymbol(out var propertyType))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string? converterFullyQualifiedName = null;
|
||||||
|
var converterParameters = ImmutableArray<string>.Empty;
|
||||||
|
ITypeSymbol? mappedSourcePropertyType = null;
|
||||||
|
ITypeSymbol? enumerableTypeArgumentType = null;
|
||||||
|
|
||||||
|
if (!Compilation.HasCompatibleTypes(sourceProperty, property))
|
||||||
|
{
|
||||||
|
if (!TryGetMapTypeConverter(property, sourceProperty, out converterFullyQualifiedName, out converterParameters) &&
|
||||||
|
!TryGetNestedObjectMappings(property, out mappedSourcePropertyType, out enumerableTypeArgumentType))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddUsingIfRequired(propertyType);
|
||||||
|
AddUsingIfRequired(enumerableTypeArgumentType);
|
||||||
|
AddUsingIfRequired(mappedSourcePropertyType);
|
||||||
|
|
||||||
|
return new MappedProperty(
|
||||||
|
property.Name,
|
||||||
|
ToQualifiedDisplayName(propertyType) ?? propertyType.Name,
|
||||||
|
converterFullyQualifiedName,
|
||||||
|
converterParameters.ToImmutableArray(),
|
||||||
|
sourceProperty.Name,
|
||||||
|
ToQualifiedDisplayName(mappedSourcePropertyType),
|
||||||
|
ToQualifiedDisplayName(enumerableTypeArgumentType),
|
||||||
|
(property as IPropertySymbol).IsReadOnly);
|
||||||
|
;
|
||||||
|
}
|
||||||
|
protected virtual MappedProperty? MapPropertySimple(ISymbol sourceTypeSymbol, ISymbol property)
|
||||||
|
{
|
||||||
|
if (!property.TryGetTypeSymbol(out var propertyType))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string? converterFullyQualifiedName = null;
|
||||||
|
var converterParameters = ImmutableArray<string>.Empty;
|
||||||
|
ITypeSymbol? mappedSourcePropertyType = null;
|
||||||
|
ITypeSymbol? enumerableTypeArgumentType = null;
|
||||||
|
|
||||||
|
|
||||||
|
AddUsingIfRequired(propertyType);
|
||||||
|
AddUsingIfRequired(enumerableTypeArgumentType);
|
||||||
|
AddUsingIfRequired(mappedSourcePropertyType);
|
||||||
|
|
||||||
|
return new MappedProperty(
|
||||||
|
property.Name,
|
||||||
|
ToQualifiedDisplayName(propertyType) ?? propertyType.Name,
|
||||||
|
converterFullyQualifiedName,
|
||||||
|
converterParameters.ToImmutableArray(),
|
||||||
|
property.Name,
|
||||||
|
ToQualifiedDisplayName(mappedSourcePropertyType),
|
||||||
|
ToQualifiedDisplayName(enumerableTypeArgumentType),
|
||||||
|
(property as IPropertySymbol).IsReadOnly);
|
||||||
|
;
|
||||||
|
}
|
||||||
|
protected bool TryGetMapTypeConverter(ISymbol property, IPropertySymbol sourceProperty, out string? converterFullyQualifiedName,
|
||||||
|
out ImmutableArray<string> converterParameters)
|
||||||
|
{
|
||||||
|
converterFullyQualifiedName = null;
|
||||||
|
converterParameters = ImmutableArray<string>.Empty;
|
||||||
|
|
||||||
|
if (!Diagnostics.IsEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeConverterAttribute = property.GetAttribute(MapTypeConverterAttributeTypeSymbol);
|
||||||
|
if (typeConverterAttribute?.ConstructorArguments.First().Value is not INamedTypeSymbol converterTypeSymbol)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseInterface = GetTypeConverterBaseInterface(converterTypeSymbol, property, sourceProperty);
|
||||||
|
if (baseInterface is null)
|
||||||
|
{
|
||||||
|
AddDiagnostic(DiagnosticsFactory.InvalidTypeConverterGenericTypesError(property, sourceProperty));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
converterFullyQualifiedName = converterTypeSymbol.ToDisplayString();
|
||||||
|
converterParameters = GetTypeConverterParameters(typeConverterAttribute);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool TryGetNestedObjectMappings(ISymbol property, out ITypeSymbol? mappedSourcePropertyType, out ITypeSymbol? enumerableTypeArgument)
|
||||||
|
{
|
||||||
|
mappedSourcePropertyType = null;
|
||||||
|
enumerableTypeArgument = null;
|
||||||
|
|
||||||
|
if (!Diagnostics.IsEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!property.TryGetTypeSymbol(out var propertyType))
|
||||||
|
{
|
||||||
|
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyTypeFoundError(property));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mapFromAttribute = propertyType.GetAttribute(MapFromAttributeTypeSymbol);
|
||||||
|
if (mapFromAttribute is null &&
|
||||||
|
propertyType is INamedTypeSymbol namedTypeSymbol &&
|
||||||
|
!propertyType.IsPrimitiveType() &&
|
||||||
|
(Compilation.IsGenericEnumerable(propertyType) || propertyType.AllInterfaces.Any(i => Compilation.IsGenericEnumerable(i))))
|
||||||
|
{
|
||||||
|
enumerableTypeArgument = namedTypeSymbol.TypeArguments.First();
|
||||||
|
mapFromAttribute = enumerableTypeArgument.GetAttribute(MapFromAttributeTypeSymbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedSourcePropertyType = mapFromAttribute?.ConstructorArguments.First().Value as INamedTypeSymbol;
|
||||||
|
|
||||||
|
if (mappedSourcePropertyType is null && enumerableTypeArgument is null)
|
||||||
|
{
|
||||||
|
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyTypeFoundError(property));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Diagnostics.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ImmutableArray<string> GetTypeConverterParameters(AttributeData typeConverterAttribute)
|
||||||
|
{
|
||||||
|
var converterParameter = typeConverterAttribute.ConstructorArguments.Skip(1).FirstOrDefault();
|
||||||
|
return converterParameter.IsNull
|
||||||
|
? ImmutableArray<string>.Empty
|
||||||
|
: converterParameter.Values.Where(v => v.Value is not null).Select(v => v.Value!.ToSourceCodeString()).ToImmutableArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MappingModel? CreateMappingModel()
|
||||||
|
{
|
||||||
|
var semanticModel = Compilation.GetSemanticModel(TypeSyntax.SyntaxTree);
|
||||||
|
if (semanticModel.GetDeclaredSymbol(TypeSyntax) is not INamedTypeSymbol typeSymbol)
|
||||||
|
{
|
||||||
|
AddDiagnostic(DiagnosticsFactory.TypeNotFoundError(TypeSyntax.GetLocation(), TypeSyntax.Identifier.ValueText));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceTypeSymbol = GetSourceTypeSymbol(TypeSyntax, semanticModel);
|
||||||
|
if (sourceTypeSymbol is null)
|
||||||
|
{
|
||||||
|
AddDiagnostic(DiagnosticsFactory.MapFromAttributeNotFoundError(TypeSyntax.GetLocation()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ignoredNamespaces.Add(sourceTypeSymbol.ContainingNamespace.ToDisplayParts().First());
|
||||||
|
|
||||||
|
var typeIdentifierName = TypeSyntax.GetIdentifierName();
|
||||||
|
var sourceTypeIdentifierName = sourceTypeSymbol.Name;
|
||||||
|
var isTypeInheritFromMappedBaseClass = IsTypeInheritFromMappedBaseClass(semanticModel);
|
||||||
|
var shouldGenerateSecondaryConstructor = ShouldGenerateSecondaryConstructor(semanticModel, sourceTypeSymbol);
|
||||||
|
|
||||||
|
var mappedProperties = GetSourceMappedProperties(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
|
||||||
|
if (!mappedProperties.Any())
|
||||||
|
{
|
||||||
|
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyFoundError(TypeSyntax.GetLocation(), typeSymbol, sourceTypeSymbol));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddUsingIfRequired(mappedProperties.Any(p => p.IsEnumerable), "System.Linq");
|
||||||
|
|
||||||
|
var allProperties = GetTypeMappedProperties(sourceTypeSymbol, typeSymbol , isTypeInheritFromMappedBaseClass);
|
||||||
|
|
||||||
|
return new MappingModel(
|
||||||
|
SourceGenerationOptions,
|
||||||
|
TypeSyntax.GetNamespace(),
|
||||||
|
TypeSyntax.Modifiers,
|
||||||
|
TypeSyntax.Keyword.Text,
|
||||||
|
typeIdentifierName,
|
||||||
|
sourceTypeSymbol.ContainingNamespace.ToDisplayString(),
|
||||||
|
sourceTypeIdentifierName,
|
||||||
|
sourceTypeSymbol.ToDisplayString(),
|
||||||
|
mappedProperties,
|
||||||
|
allProperties,
|
||||||
|
isTypeInheritFromMappedBaseClass,
|
||||||
|
Usings,
|
||||||
|
shouldGenerateSecondaryConstructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private INamedTypeSymbol? GetTypeConverterBaseInterface(ITypeSymbol converterTypeSymbol, ISymbol property, IPropertySymbol sourceProperty)
|
||||||
|
{
|
||||||
|
if (!property.TryGetTypeSymbol(out var propertyType))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return converterTypeSymbol.AllInterfaces
|
||||||
|
.SingleOrDefault(i =>
|
||||||
|
i.TypeArguments.Length == 2 &&
|
||||||
|
SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, TypeConverterInterfaceTypeSymbol) &&
|
||||||
|
SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) &&
|
||||||
|
SymbolEqualityComparer.Default.Equals(propertyType, i.TypeArguments[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldGenerateSecondaryConstructor(SemanticModel semanticModel, ISymbol sourceTypeSymbol)
|
||||||
|
{
|
||||||
|
var constructorSyntax = TypeSyntax.DescendantNodes()
|
||||||
|
.OfType<ConstructorDeclarationSyntax>()
|
||||||
|
.SingleOrDefault(c =>
|
||||||
|
c.ParameterList.Parameters.Count == 1 &&
|
||||||
|
SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(c.ParameterList.Parameters.Single().Type!).ConvertedType, sourceTypeSymbol));
|
||||||
|
|
||||||
|
if (constructorSyntax is null)
|
||||||
|
{
|
||||||
|
// Secondary constructor is not defined.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (constructorSyntax.Initializer?.ArgumentList.Arguments is not { Count: 2 } arguments ||
|
||||||
|
!SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(arguments[0].Expression).ConvertedType, MappingContextTypeSymbol) ||
|
||||||
|
!SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(arguments[1].Expression).ConvertedType, sourceTypeSymbol))
|
||||||
|
{
|
||||||
|
AddDiagnostic(DiagnosticsFactory.MissingConstructorArgument(constructorSyntax));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? ToQualifiedDisplayName(ISymbol? symbol)
|
||||||
|
{
|
||||||
|
if (symbol is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var containingNamespace = TypeSyntax.GetNamespace();
|
||||||
|
var symbolNamespace = symbol.ContainingNamespace.ToDisplayString();
|
||||||
|
return containingNamespace != symbolNamespace && _ignoredNamespaces.Contains(symbol.ContainingNamespace.ToDisplayParts().First())
|
||||||
|
? symbol.ToDisplayString()
|
||||||
|
: symbol.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using MapTo.Extensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
|
||||||
|
namespace MapTo
|
||||||
|
{
|
||||||
|
internal enum AccessModifier
|
||||||
|
{
|
||||||
|
Public,
|
||||||
|
Internal,
|
||||||
|
Private
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum NullStaticAnalysisState
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
Enabled,
|
||||||
|
Disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
internal record SourceCode(string Text, string HintName);
|
||||||
|
|
||||||
|
internal record MappedProperty(
|
||||||
|
string Name,
|
||||||
|
string Type,
|
||||||
|
string? TypeConverter,
|
||||||
|
ImmutableArray<string> TypeConverterParameters,
|
||||||
|
string SourcePropertyName,
|
||||||
|
string? MappedSourcePropertyTypeName,
|
||||||
|
string? EnumerableTypeArgument,
|
||||||
|
bool isReadOnly)
|
||||||
|
{
|
||||||
|
public bool IsEnumerable => EnumerableTypeArgument is not null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal record MappingModel (
|
||||||
|
SourceGenerationOptions Options,
|
||||||
|
string? Namespace,
|
||||||
|
SyntaxTokenList Modifiers,
|
||||||
|
string Type,
|
||||||
|
string TypeIdentifierName,
|
||||||
|
string SourceNamespace,
|
||||||
|
string SourceTypeIdentifierName,
|
||||||
|
string SourceTypeFullName,
|
||||||
|
ImmutableArray<MappedProperty> SourceProperties,
|
||||||
|
ImmutableArray<MappedProperty> TypeProperties,
|
||||||
|
bool HasMappedBaseClass,
|
||||||
|
ImmutableArray<string> Usings,
|
||||||
|
bool GenerateSecondaryConstructor
|
||||||
|
)
|
||||||
|
{
|
||||||
|
public string SourceType => SourceTypeFullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal record SourceGenerationOptions(
|
||||||
|
AccessModifier ConstructorAccessModifier,
|
||||||
|
AccessModifier GeneratedMethodsAccessModifier,
|
||||||
|
bool GenerateXmlDocument,
|
||||||
|
bool SupportNullableReferenceTypes,
|
||||||
|
bool SupportNullableStaticAnalysis)
|
||||||
|
{
|
||||||
|
internal static SourceGenerationOptions From(GeneratorExecutionContext context)
|
||||||
|
{
|
||||||
|
const string allowNullAttributeName = "System.Diagnostics.CodeAnalysis.AllowNullAttribute";
|
||||||
|
var supportNullableStaticAnalysis = context.GetBuildGlobalOption(propertyName: nameof(SupportNullableStaticAnalysis), NullStaticAnalysisState.Default);
|
||||||
|
var supportNullableReferenceTypes = context.Compilation.Options.NullableContextOptions is NullableContextOptions.Warnings or NullableContextOptions.Enable;
|
||||||
|
|
||||||
|
return new(
|
||||||
|
ConstructorAccessModifier: context.GetBuildGlobalOption(propertyName: nameof(ConstructorAccessModifier), AccessModifier.Public),
|
||||||
|
GeneratedMethodsAccessModifier: context.GetBuildGlobalOption(propertyName: nameof(GeneratedMethodsAccessModifier), AccessModifier.Public),
|
||||||
|
GenerateXmlDocument: context.GetBuildGlobalOption(propertyName: nameof(GenerateXmlDocument), true),
|
||||||
|
SupportNullableReferenceTypes: supportNullableReferenceTypes,
|
||||||
|
SupportNullableStaticAnalysis: supportNullableStaticAnalysis switch
|
||||||
|
{
|
||||||
|
NullStaticAnalysisState.Enabled => true,
|
||||||
|
NullStaticAnalysisState.Disabled => false,
|
||||||
|
_ => context.Compilation is CSharpCompilation { LanguageVersion: >= LanguageVersion.CSharp8 } cs && cs.TypeByMetadataNameExists(allowNullAttributeName)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string NullableReferenceSyntax => SupportNullableReferenceTypes ? "?" : string.Empty;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,52 +1,42 @@
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MapTo.Extensions;
|
using MapTo.Extensions;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
namespace MapTo
|
namespace MapTo
|
||||||
{
|
{
|
||||||
internal class RecordMappingContext : MappingContext
|
internal class RecordMappingContext : MappingContext
|
||||||
{
|
{
|
||||||
internal RecordMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
internal RecordMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
||||||
: base(compilation, sourceGenerationOptions, typeSyntax) { }
|
: base(compilation, sourceGenerationOptions, typeSyntax) { }
|
||||||
|
|
||||||
protected override ImmutableArray<MappedMember> GetSourceMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
protected override ImmutableArray<MappedProperty> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||||
{
|
{
|
||||||
throw new System.NotImplementedException();
|
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||||
}
|
return typeSymbol.GetMembers()
|
||||||
|
.OfType<IMethodSymbol>()
|
||||||
protected override ImmutableArray<MappedMember> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
.OrderByDescending(s => s.Parameters.Length)
|
||||||
{
|
.First(s => s.Name == ".ctor")
|
||||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
.Parameters
|
||||||
return typeSymbol.GetMembers()
|
.Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol))
|
||||||
.OfType<IMethodSymbol>()
|
.Select(property => MapProperty(sourceTypeSymbol, sourceProperties, property))
|
||||||
.OrderByDescending(s => s.Parameters.Length)
|
.Where(mappedProperty => mappedProperty is not null)
|
||||||
.First(s => s.Name == ".ctor")
|
.ToImmutableArray()!;
|
||||||
.Parameters
|
}
|
||||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
|
||||||
.Select(property => MapProperty(sourceTypeSymbol, sourceProperties, property))
|
protected override ImmutableArray<MappedProperty> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||||
.Where(mappedProperty => mappedProperty is not null)
|
{
|
||||||
.ToImmutableArray()!;
|
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||||
}
|
return typeSymbol.GetMembers()
|
||||||
|
.OfType<IMethodSymbol>()
|
||||||
protected override ImmutableArray<MappedMember> GetTypeMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
.OrderByDescending(s => s.Parameters.Length)
|
||||||
{
|
.First(s => s.Name == ".ctor")
|
||||||
throw new System.NotImplementedException();
|
.Parameters
|
||||||
}
|
.Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol))
|
||||||
|
.Select(property => MapProperty(typeSymbol, sourceProperties, property))
|
||||||
protected override ImmutableArray<MappedMember> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
.Where(mappedProperty => mappedProperty is not null)
|
||||||
{
|
.ToImmutableArray()!;
|
||||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
}
|
||||||
return typeSymbol.GetMembers()
|
}
|
||||||
.OfType<IMethodSymbol>()
|
|
||||||
.OrderByDescending(s => s.Parameters.Length)
|
|
||||||
.First(s => s.Name == ".ctor")
|
|
||||||
.Parameters
|
|
||||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
|
||||||
.Select(property => MapProperty(typeSymbol, sourceProperties, property))
|
|
||||||
.Where(mappedProperty => mappedProperty is not null)
|
|
||||||
.ToImmutableArray()!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
namespace MapTo.Sources
|
namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
internal class Constants
|
internal class Constants
|
||||||
{
|
{
|
||||||
internal const string RootNamespace = "MapTo";
|
internal const string RootNamespace = "MapTo";
|
||||||
internal const string GeneratedFilesHeader = "// <auto-generated />";
|
internal const string GeneratedFilesHeader = "// <auto-generated />";
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,58 +1,58 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using static MapTo.Sources.Constants;
|
using static MapTo.Sources.Constants;
|
||||||
|
|
||||||
namespace MapTo.Sources
|
namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
internal static class ITypeConverterSource
|
internal static class ITypeConverterSource
|
||||||
{
|
{
|
||||||
internal const string InterfaceName = "ITypeConverter";
|
internal const string InterfaceName = "ITypeConverter";
|
||||||
internal const string FullyQualifiedName = RootNamespace + "." + InterfaceName + "`2";
|
internal const string FullyQualifiedName = RootNamespace + "." + InterfaceName + "`2";
|
||||||
|
|
||||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||||
{
|
{
|
||||||
using var builder = new SourceBuilder()
|
using var builder = new SourceBuilder()
|
||||||
.WriteLine(GeneratedFilesHeader)
|
.WriteLine(GeneratedFilesHeader)
|
||||||
.WriteNullableContextOptionIf(options.SupportNullableReferenceTypes)
|
.WriteNullableContextOptionIf(options.SupportNullableReferenceTypes)
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine($"namespace {RootNamespace}")
|
.WriteLine($"namespace {RootNamespace}")
|
||||||
.WriteOpeningBracket();
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
if (options.GenerateXmlDocument)
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine("/// <summary>")
|
.WriteLine("/// <summary>")
|
||||||
.WriteLine("/// Converts the value of <typeparamref name=\"TSource\"/> to <typeparamref name=\"TDestination\"/>.")
|
.WriteLine("/// Converts the value of <typeparamref name=\"TSource\"/> to <typeparamref name=\"TDestination\"/>.")
|
||||||
.WriteLine("/// </summary>")
|
.WriteLine("/// </summary>")
|
||||||
.WriteLine("/// <typeparam name=\"TSource\">The type to convert from.</typeparam>")
|
.WriteLine("/// <typeparam name=\"TSource\">The type to convert from.</typeparam>")
|
||||||
.WriteLine("/// <typeparam name=\"TDestination\">The type to convert to.</typeparam>");
|
.WriteLine("/// <typeparam name=\"TDestination\">The type to convert to.</typeparam>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine($"public interface {InterfaceName}<in TSource, out TDestination>")
|
.WriteLine($"public interface {InterfaceName}<in TSource, out TDestination>")
|
||||||
.WriteOpeningBracket();
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
if (options.GenerateXmlDocument)
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine("/// <summary>")
|
.WriteLine("/// <summary>")
|
||||||
.WriteLine("/// Converts the value of <paramref name=\"source\"/> object to <typeparamref name=\"TDestination\"/>.")
|
.WriteLine("/// Converts the value of <paramref name=\"source\"/> object to <typeparamref name=\"TDestination\"/>.")
|
||||||
.WriteLine("/// </summary>")
|
.WriteLine("/// </summary>")
|
||||||
.WriteLine("/// <param name=\"source\">The <see cref=\"TSource\"/> to convert.</param>")
|
.WriteLine("/// <param name=\"source\">The <see cref=\"TSource\"/> to convert.</param>")
|
||||||
.WriteLine($"/// <param name=\"converterParameters\">The parameter list passed to the <see cref=\"{MapTypeConverterAttributeSource.AttributeClassName}\"/></param>")
|
.WriteLine($"/// <param name=\"converterParameters\">The parameter list passed to the <see cref=\"{MapTypeConverterAttributeSource.AttributeClassName}\"/></param>")
|
||||||
.WriteLine("/// <returns><typeparamref name=\"TDestination\"/> object.</returns>");
|
.WriteLine("/// <returns><typeparamref name=\"TDestination\"/> object.</returns>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine($"TDestination Convert(TSource source, object[]{options.NullableReferenceSyntax} converterParameters);")
|
.WriteLine($"TDestination Convert(TSource source, object[]{options.NullableReferenceSyntax} converterParameters);")
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteClosingBracket();
|
.WriteClosingBracket();
|
||||||
|
|
||||||
return new(builder.ToString(), $"{InterfaceName}.g.cs");
|
return new(builder.ToString(), $"{InterfaceName}.g.cs");
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string GetFullyQualifiedName(ITypeSymbol sourceType, ITypeSymbol destinationType) =>
|
internal static string GetFullyQualifiedName(ITypeSymbol sourceType, ITypeSymbol destinationType) =>
|
||||||
$"{RootNamespace}.{InterfaceName}<{sourceType.ToDisplayString()}, {destinationType.ToDisplayString()}>";
|
$"{RootNamespace}.{InterfaceName}<{sourceType.ToDisplayString()}, {destinationType.ToDisplayString()}>";
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,36 +1,36 @@
|
||||||
using static MapTo.Sources.Constants;
|
using static MapTo.Sources.Constants;
|
||||||
|
|
||||||
namespace MapTo.Sources
|
namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
internal static class IgnoreMemberAttributeSource
|
internal static class IgnorePropertyAttributeSource
|
||||||
{
|
{
|
||||||
internal const string AttributeName = "IgnoreMemberMapTo";
|
internal const string AttributeName = "IgnoreProperty";
|
||||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||||
|
|
||||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||||
{
|
{
|
||||||
var builder = new SourceBuilder()
|
var builder = new SourceBuilder()
|
||||||
.WriteLine(GeneratedFilesHeader)
|
.WriteLine(GeneratedFilesHeader)
|
||||||
.WriteLine("using System;")
|
.WriteLine("using System;")
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine($"namespace {RootNamespace}")
|
.WriteLine($"namespace {RootNamespace}")
|
||||||
.WriteOpeningBracket();
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
if (options.GenerateXmlDocument)
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine("/// <summary>")
|
.WriteLine("/// <summary>")
|
||||||
.WriteLine("/// Specifies that the annotated property should be excluded.")
|
.WriteLine("/// Specifies that the annotated property should be excluded.")
|
||||||
.WriteLine("/// </summary>");
|
.WriteLine("/// </summary>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)]")
|
.WriteLine("[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]")
|
||||||
.WriteLine($"public sealed class {AttributeClassName} : Attribute {{ }}")
|
.WriteLine($"public sealed class {AttributeClassName} : Attribute {{ }}")
|
||||||
.WriteClosingBracket();
|
.WriteClosingBracket();
|
||||||
|
|
||||||
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,188 @@
|
||||||
|
using MapTo.Extensions;
|
||||||
|
using static MapTo.Sources.Constants;
|
||||||
|
|
||||||
|
namespace MapTo.Sources
|
||||||
|
{
|
||||||
|
internal static class MapClassSource
|
||||||
|
{
|
||||||
|
internal static SourceCode Generate(MappingModel model)
|
||||||
|
{
|
||||||
|
using var builder = new SourceBuilder()
|
||||||
|
.WriteLine(GeneratedFilesHeader)
|
||||||
|
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
|
||||||
|
.WriteUsings(model.Usings)
|
||||||
|
.WriteLine()
|
||||||
|
|
||||||
|
// Namespace declaration
|
||||||
|
.WriteLine($"namespace {model.Namespace}")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
|
||||||
|
// Class declaration
|
||||||
|
.WriteLine($"partial class {model.TypeIdentifierName}")
|
||||||
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
|
builder
|
||||||
|
.GeneratePublicConstructor(model)
|
||||||
|
.WriteLine();
|
||||||
|
|
||||||
|
if(!AllPropertiesReadOnly(model))
|
||||||
|
{
|
||||||
|
builder.GenerateUpdateMethod(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder
|
||||||
|
.WriteClosingBracket()
|
||||||
|
.WriteLine()
|
||||||
|
|
||||||
|
.WriteClosingBracket();
|
||||||
|
|
||||||
|
return new(builder.ToString(), $"{model.Namespace}.{model.TypeIdentifierName}.g.cs");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder GenerateSecondaryConstructor(this SourceBuilder builder, MappingModel model)
|
||||||
|
{
|
||||||
|
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||||
|
|
||||||
|
if (model.Options.GenerateXmlDocument)
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.WriteLine("/// <summary>")
|
||||||
|
.WriteLine($"/// Initializes a new instance of the <see cref=\"{model.TypeIdentifierName}\"/> class")
|
||||||
|
.WriteLine($"/// using the property values from the specified <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||||
|
.WriteLine("/// </summary>")
|
||||||
|
.WriteLine($"/// <exception cref=\"ArgumentNullException\">{sourceClassParameterName} is null</exception>");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName})")
|
||||||
|
.WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static SourceBuilder GeneratePublicConstructor(this SourceBuilder builder, MappingModel model)
|
||||||
|
{
|
||||||
|
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||||
|
const string mappingContextParameterName = "context";
|
||||||
|
|
||||||
|
var baseConstructor = /*model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" :*/ string.Empty;
|
||||||
|
|
||||||
|
builder
|
||||||
|
.WriteLine($"public {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName}){baseConstructor}")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
//.WriteLine($"if ({mappingContextParameterName} == null) throw new ArgumentNullException(nameof({mappingContextParameterName}));")
|
||||||
|
//.WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));")
|
||||||
|
//.WriteLine()
|
||||||
|
//.WriteLine($"{mappingContextParameterName}.{MappingContextSource.RegisterMethodName}({sourceClassParameterName}, this);")
|
||||||
|
|
||||||
|
.WriteProperties( model.SourceProperties, sourceClassParameterName, mappingContextParameterName, false);
|
||||||
|
|
||||||
|
// End constructor declaration
|
||||||
|
return builder.WriteClosingBracket();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder WriteProperties(this SourceBuilder builder, System.Collections.Immutable.ImmutableArray<MappedProperty> properties,
|
||||||
|
string? sourceClassParameterName, string mappingContextParameterName, bool fromUpdate)
|
||||||
|
{
|
||||||
|
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
if (property.isReadOnly && fromUpdate) continue;
|
||||||
|
|
||||||
|
if (property.TypeConverter is null)
|
||||||
|
{
|
||||||
|
if (property.IsEnumerable)
|
||||||
|
{
|
||||||
|
builder.WriteLine(
|
||||||
|
$"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.Select({mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.EnumerableTypeArgument}>).ToList();");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.WriteLine(property.MappedSourcePropertyTypeName is null
|
||||||
|
? $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};"
|
||||||
|
: "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var parameters = property.TypeConverterParameters.IsEmpty
|
||||||
|
? "null"
|
||||||
|
: $"new object[] {{ {string.Join(", ", property.TypeConverterParameters)} }}";
|
||||||
|
|
||||||
|
builder.WriteLine(
|
||||||
|
$"{property.Name} = new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters});");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return builder;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool AllPropertiesReadOnly(MappingModel model)
|
||||||
|
{
|
||||||
|
foreach (var property in model.SourceProperties)
|
||||||
|
{
|
||||||
|
if (!property.isReadOnly) return false;
|
||||||
|
}
|
||||||
|
return true ;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder GenerateUpdateMethod(this SourceBuilder builder, MappingModel model)
|
||||||
|
{
|
||||||
|
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||||
|
|
||||||
|
builder
|
||||||
|
.GenerateUpdaterMethodsXmlDocs(model, sourceClassParameterName)
|
||||||
|
.WriteLine($"public void Update({model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteProperties( model.SourceProperties.GetWritableMappedProperties(), sourceClassParameterName,"context", true )
|
||||||
|
.WriteClosingBracket();
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder GenerateConvertorMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
||||||
|
{
|
||||||
|
if (!model.Options.GenerateXmlDocument)
|
||||||
|
{
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.WriteLine("/// <summary>")
|
||||||
|
.WriteLine($"/// Creates a new instance of <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
|
||||||
|
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||||
|
.WriteLine("/// </summary>")
|
||||||
|
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{model.SourceType}\"/> to use as source.</param>")
|
||||||
|
.WriteLine($"/// <returns>A new instance of <see cred=\"{model.TypeIdentifierName}\"/> -or- <c>null</c> if <paramref name=\"{sourceClassParameterName}\"/> is <c>null</c>.</returns>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder GenerateUpdaterMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
||||||
|
{
|
||||||
|
if (!model.Options.GenerateXmlDocument)
|
||||||
|
{
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.WriteLine("/// <summary>")
|
||||||
|
.WriteLine($"/// Updates <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
|
||||||
|
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||||
|
.WriteLine("/// </summary>")
|
||||||
|
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{model.SourceType}\"/> to use as source.</param>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder GenerateSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
|
||||||
|
{
|
||||||
|
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
||||||
|
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
||||||
|
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} To{model.TypeIdentifierName}(this {model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
|
||||||
|
.WriteClosingBracket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,72 +1,65 @@
|
||||||
using static MapTo.Sources.Constants;
|
using static MapTo.Sources.Constants;
|
||||||
|
|
||||||
namespace MapTo.Sources
|
namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
internal static class MapFromAttributeSource
|
internal static class MapFromAttributeSource
|
||||||
{
|
{
|
||||||
internal const string AttributeName = "MapFrom";
|
internal const string AttributeName = "MapFrom";
|
||||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||||
|
|
||||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||||
{
|
{
|
||||||
using var builder = new SourceBuilder()
|
using var builder = new SourceBuilder()
|
||||||
.WriteLine(GeneratedFilesHeader)
|
.WriteLine(GeneratedFilesHeader)
|
||||||
.WriteLine("using System;")
|
.WriteLine("using System;")
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine($"namespace {RootNamespace}")
|
.WriteLine($"namespace {RootNamespace}")
|
||||||
.WriteOpeningBracket();
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
if (options.GenerateXmlDocument)
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine("/// <summary>")
|
.WriteLine("/// <summary>")
|
||||||
.WriteLine("/// Specifies that the annotated class can be mapped from the provided <see cref=\"SourceType\"/>.")
|
.WriteLine("/// Specifies that the annotated class can be mapped from the provided <see cref=\"SourceType\"/>.")
|
||||||
.WriteLine("/// </summary>");
|
.WriteLine("/// </summary>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine("[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]")
|
.WriteLine("[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]")
|
||||||
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
|
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
|
||||||
.WriteOpeningBracket();
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
if (options.GenerateXmlDocument)
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine("/// <summary>")
|
.WriteLine("/// <summary>")
|
||||||
.WriteLine($"/// Initializes a new instance of the <see cref=\"{AttributeName}Attribute\"/> class with the specified <paramref name=\"sourceType\"/>.")
|
.WriteLine($"/// Initializes a new instance of the <see cref=\"{AttributeName}Attribute\"/> class with the specified <paramref name=\"sourceType\"/>.")
|
||||||
.WriteLine("/// </summary>")
|
.WriteLine("/// </summary>")
|
||||||
.WriteLine("/// <param name=\"sourceType\">The type of to map from.</param>");
|
.WriteLine("/// <param name=\"sourceType\">The type of to map from.</param>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine($"public {AttributeName}Attribute(Type sourceType)")
|
.WriteLine($"public {AttributeName}Attribute(Type sourceType)")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine("SourceType = new [] { sourceType };")
|
.WriteLine("SourceType = sourceType;")
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteLine();
|
.WriteLine();
|
||||||
|
|
||||||
builder
|
if (options.GenerateXmlDocument)
|
||||||
.WriteLine($"public {AttributeName}Attribute(Type[] sourceType)")
|
{
|
||||||
.WriteOpeningBracket()
|
builder
|
||||||
.WriteLine("SourceType = sourceType;")
|
.WriteLine("/// <summary>")
|
||||||
.WriteClosingBracket()
|
.WriteLine("/// Gets the type to map from.")
|
||||||
.WriteLine();
|
.WriteLine("/// </summary>");
|
||||||
|
}
|
||||||
if (options.GenerateXmlDocument)
|
|
||||||
{
|
builder
|
||||||
builder
|
.WriteLine("public Type SourceType { get; }")
|
||||||
.WriteLine("/// <summary>")
|
.WriteClosingBracket() // class
|
||||||
.WriteLine("/// Gets the type to map from.")
|
.WriteClosingBracket(); // namespace
|
||||||
.WriteLine("/// </summary>");
|
|
||||||
}
|
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
|
||||||
|
}
|
||||||
builder
|
}
|
||||||
.WriteLine("public Type[] SourceType { get; }")
|
|
||||||
.WriteClosingBracket() // class
|
|
||||||
.WriteClosingBracket(); // namespace
|
|
||||||
|
|
||||||
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,61 +1,60 @@
|
||||||
using static MapTo.Sources.Constants;
|
using static MapTo.Sources.Constants;
|
||||||
|
|
||||||
namespace MapTo.Sources
|
namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
internal static class MapPropertyAttributeSource
|
internal static class MapPropertyAttributeSource
|
||||||
{
|
{
|
||||||
internal const string AttributeName = "MapProperty";
|
internal const string AttributeName = "MapProperty";
|
||||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||||
internal const string SourcePropertyNamePropertyName = "SourcePropertyName";
|
internal const string SourcePropertyNamePropertyName = "SourcePropertyName";
|
||||||
|
|
||||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||||
{
|
{
|
||||||
using var builder = new SourceBuilder()
|
using var builder = new SourceBuilder()
|
||||||
.WriteLine(GeneratedFilesHeader)
|
.WriteLine(GeneratedFilesHeader)
|
||||||
.WriteNullableContextOptionIf(options.SupportNullableReferenceTypes)
|
.WriteNullableContextOptionIf(options.SupportNullableReferenceTypes)
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine("using System;")
|
.WriteLine("using System;")
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine($"namespace {RootNamespace}")
|
.WriteLine($"namespace {RootNamespace}")
|
||||||
.WriteOpeningBracket();
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
if (options.GenerateXmlDocument)
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine()
|
.WriteLine("/// <summary>")
|
||||||
.WriteLine("/// <summary>")
|
.WriteLine("/// Specifies the mapping behavior of the annotated property.")
|
||||||
.WriteLine("/// Specifies the mapping behavior of the annotated property.")
|
.WriteLine("/// </summary>")
|
||||||
.WriteLine("/// </summary>")
|
.WriteLine("/// <remarks>")
|
||||||
.WriteLine("/// <remarks>")
|
.WriteLine($"/// {AttributeClassName} has a number of uses:")
|
||||||
.WriteLine($"/// {AttributeClassName} has a number of uses:")
|
.WriteLine("/// <list type=\"bullet\">")
|
||||||
.WriteLine("/// <list type=\"bullet\">")
|
.WriteLine("/// <item><description>By default properties with same name will get mapped. This attribute allows the names to be different.</description></item>")
|
||||||
.WriteLine("/// <item><description>By default properties with same name will get mapped. This attribute allows the names to be different.</description></item>")
|
.WriteLine("/// <item><description>Indicates that a property should be mapped when member serialization is set to opt-in.</description></item>")
|
||||||
.WriteLine("/// <item><description>Indicates that a property should be mapped when member serialization is set to opt-in.</description></item>")
|
.WriteLine("/// </list>")
|
||||||
.WriteLine("/// </list>")
|
.WriteLine("/// </remarks>");
|
||||||
.WriteLine("/// </remarks>");
|
}
|
||||||
}
|
|
||||||
|
builder
|
||||||
builder
|
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)]")
|
||||||
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)]")
|
.WriteLine($"public sealed class {AttributeClassName} : Attribute")
|
||||||
.WriteLine($"public sealed class {AttributeClassName} : Attribute")
|
.WriteOpeningBracket();
|
||||||
.WriteOpeningBracket();
|
|
||||||
|
if (options.GenerateXmlDocument)
|
||||||
if (options.GenerateXmlDocument)
|
{
|
||||||
{
|
builder
|
||||||
builder
|
.WriteLine("/// <summary>")
|
||||||
.WriteLine("/// <summary>")
|
.WriteLine("/// Gets or sets the property name of the object to mapping from.")
|
||||||
.WriteLine("/// Gets or sets the property name of the object to mapping from.")
|
.WriteLine("/// </summary>");
|
||||||
.WriteLine("/// </summary>");
|
}
|
||||||
}
|
|
||||||
|
builder
|
||||||
builder
|
.WriteLine($"public string{options.NullableReferenceSyntax} {SourcePropertyNamePropertyName} {{ get; set; }}")
|
||||||
.WriteLine($"public string{options.NullableReferenceSyntax} {SourcePropertyNamePropertyName} {{ get; set; }}")
|
.WriteClosingBracket() // class
|
||||||
.WriteClosingBracket() // class
|
.WriteClosingBracket(); // namespace
|
||||||
.WriteClosingBracket(); // namespace
|
|
||||||
|
|
||||||
|
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
||||||
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
using System;
|
||||||
|
using MapTo.Extensions;
|
||||||
|
using static MapTo.Sources.Constants;
|
||||||
|
|
||||||
|
namespace MapTo.Sources
|
||||||
|
{
|
||||||
|
internal static class MapRecordSource
|
||||||
|
{
|
||||||
|
internal static SourceCode Generate(MappingModel model)
|
||||||
|
{
|
||||||
|
using var builder = new SourceBuilder()
|
||||||
|
.WriteLine(GeneratedFilesHeader)
|
||||||
|
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
|
||||||
|
.WriteUsings(model.Usings)
|
||||||
|
.WriteLine()
|
||||||
|
|
||||||
|
// Namespace declaration
|
||||||
|
.WriteLine($"namespace {model.Namespace}")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
|
||||||
|
// Class declaration
|
||||||
|
.WriteLine($"partial record {model.TypeIdentifierName}")
|
||||||
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
|
// Class body
|
||||||
|
if (model.GenerateSecondaryConstructor)
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.GenerateSecondaryConstructor(model)
|
||||||
|
.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
builder
|
||||||
|
.GeneratePrivateConstructor(model)
|
||||||
|
|
||||||
|
.WriteLine()
|
||||||
|
.GenerateFactoryMethod(model)
|
||||||
|
|
||||||
|
// End class declaration
|
||||||
|
.WriteClosingBracket()
|
||||||
|
.WriteLine()
|
||||||
|
|
||||||
|
// Extension class declaration
|
||||||
|
.GenerateSourceTypeExtensionClass(model)
|
||||||
|
|
||||||
|
|
||||||
|
// End namespace declaration
|
||||||
|
.WriteClosingBracket();
|
||||||
|
|
||||||
|
return new(builder.ToString(), $"{model.Namespace}.{model.TypeIdentifierName}.g.cs");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder GenerateSecondaryConstructor(this SourceBuilder builder, MappingModel model)
|
||||||
|
{
|
||||||
|
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||||
|
|
||||||
|
if (model.Options.GenerateXmlDocument)
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.WriteLine("/// <summary>")
|
||||||
|
.WriteLine($"/// Initializes a new instance of the <see cref=\"{model.TypeIdentifierName}\"/> class")
|
||||||
|
.WriteLine($"/// using the property values from the specified <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||||
|
.WriteLine("/// </summary>")
|
||||||
|
.WriteLine($"/// <exception cref=\"ArgumentNullException\">{sourceClassParameterName} is null</exception>");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName})")
|
||||||
|
.WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder GeneratePrivateConstructor(this SourceBuilder builder, MappingModel model)
|
||||||
|
{
|
||||||
|
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||||
|
const string mappingContextParameterName = "context";
|
||||||
|
|
||||||
|
builder
|
||||||
|
.WriteLine($"private protected {model.TypeIdentifierName}({MappingContextSource.ClassName} {mappingContextParameterName}, {model.SourceType} {sourceClassParameterName})")
|
||||||
|
.Indent()
|
||||||
|
.Write(": this(").
|
||||||
|
|
||||||
|
WriteProperties(model, sourceClassParameterName, mappingContextParameterName)
|
||||||
|
|
||||||
|
.WriteLine(")")
|
||||||
|
.Unindent()
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine($"if ({mappingContextParameterName} == null) throw new ArgumentNullException(nameof({mappingContextParameterName}));")
|
||||||
|
.WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));")
|
||||||
|
.WriteLine()
|
||||||
|
.WriteLine($"{mappingContextParameterName}.{MappingContextSource.RegisterMethodName}({sourceClassParameterName}, this);");
|
||||||
|
|
||||||
|
// End constructor declaration
|
||||||
|
return builder.WriteClosingBracket();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder WriteProperties(this SourceBuilder builder, MappingModel model, string sourceClassParameterName,
|
||||||
|
string mappingContextParameterName)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < model.SourceProperties.Length; i++)
|
||||||
|
{
|
||||||
|
var property = model.SourceProperties[i];
|
||||||
|
if (property.TypeConverter is null)
|
||||||
|
{
|
||||||
|
if (property.IsEnumerable)
|
||||||
|
{
|
||||||
|
builder.Write(
|
||||||
|
$"{property.Name}: {sourceClassParameterName}.{property.SourcePropertyName}.Select({mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.EnumerableTypeArgument}>).ToList()");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.Write(property.MappedSourcePropertyTypeName is null
|
||||||
|
? $"{property.Name}: {sourceClassParameterName}.{property.SourcePropertyName}"
|
||||||
|
: $"{property.Name}: {mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.Type}>({sourceClassParameterName}.{property.SourcePropertyName})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var parameters = property.TypeConverterParameters.IsEmpty
|
||||||
|
? "null"
|
||||||
|
: $"new object[] {{ {string.Join(", ", property.TypeConverterParameters)} }}";
|
||||||
|
|
||||||
|
builder.Write(
|
||||||
|
$"{property.Name}: new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters})");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < model.SourceProperties.Length - 1)
|
||||||
|
{
|
||||||
|
builder.Write(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder GenerateFactoryMethod(this SourceBuilder builder, MappingModel model)
|
||||||
|
{
|
||||||
|
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
||||||
|
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
||||||
|
.WriteLine(
|
||||||
|
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} From({model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine(
|
||||||
|
$"return {sourceClassParameterName} == null ? null : {MappingContextSource.ClassName}.{MappingContextSource.FactoryMethodName}<{model.SourceType}, {model.TypeIdentifierName}>({sourceClassParameterName});")
|
||||||
|
.WriteClosingBracket();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder GenerateConvertorMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
||||||
|
{
|
||||||
|
if (!model.Options.GenerateXmlDocument)
|
||||||
|
{
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.WriteLine("/// <summary>")
|
||||||
|
.WriteLine($"/// Creates a new instance of <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
|
||||||
|
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||||
|
.WriteLine("/// </summary>")
|
||||||
|
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{model.SourceType}\"/> to use as source.</param>")
|
||||||
|
.WriteLine(
|
||||||
|
$"/// <returns>A new instance of <see cred=\"{model.TypeIdentifierName}\"/> -or- <c>null</c> if <paramref name=\"{sourceClassParameterName}\"/> is <c>null</c>.</returns>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder GenerateSourceTypeExtensionClass(this SourceBuilder builder, MappingModel model)
|
||||||
|
{
|
||||||
|
return builder
|
||||||
|
.WriteLine(
|
||||||
|
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {model.SourceTypeIdentifierName}To{model.TypeIdentifierName}Extensions")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.GenerateSourceTypeExtensionMethod(model)
|
||||||
|
.WriteClosingBracket();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder GenerateSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
|
||||||
|
{
|
||||||
|
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
||||||
|
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
||||||
|
.WriteLine(
|
||||||
|
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} To{model.TypeIdentifierName}(this {model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
|
||||||
|
.WriteClosingBracket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
using MapTo.Extensions;
|
||||||
|
using static MapTo.Sources.Constants;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MapTo.Sources
|
||||||
|
{
|
||||||
|
internal static class MapStructSource
|
||||||
|
{
|
||||||
|
internal static SourceCode Generate(MappingModel model)
|
||||||
|
{
|
||||||
|
using var builder = new SourceBuilder()
|
||||||
|
.WriteLine(GeneratedFilesHeader)
|
||||||
|
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
|
||||||
|
.WriteUsings(model.Usings)
|
||||||
|
.WriteLine()
|
||||||
|
|
||||||
|
// Namespace declaration
|
||||||
|
.WriteLine($"namespace {model.Namespace}")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
|
||||||
|
// Class declaration
|
||||||
|
.WriteLine($"partial struct {model.TypeIdentifierName}")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine()
|
||||||
|
|
||||||
|
// Class body
|
||||||
|
.GeneratePublicConstructor(model);
|
||||||
|
|
||||||
|
if (!AllPropertiesAreReadOnly(model))
|
||||||
|
{
|
||||||
|
builder.GenerateUpdateMethod(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder
|
||||||
|
.WriteLine()
|
||||||
|
// End class declaration
|
||||||
|
.WriteClosingBracket()
|
||||||
|
.WriteLine()
|
||||||
|
|
||||||
|
// End namespace declaration
|
||||||
|
.WriteClosingBracket();
|
||||||
|
|
||||||
|
return new(builder.ToString(), $"{model.Namespace}.{model.TypeIdentifierName}.g.cs");
|
||||||
|
}
|
||||||
|
private static bool AllPropertiesAreReadOnly(MappingModel model)
|
||||||
|
{
|
||||||
|
foreach (var property in model.SourceProperties)
|
||||||
|
{
|
||||||
|
if (!property.isReadOnly) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static SourceBuilder GenerateSecondaryConstructor(this SourceBuilder builder, MappingModel model)
|
||||||
|
{
|
||||||
|
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||||
|
|
||||||
|
if (model.Options.GenerateXmlDocument)
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.WriteLine("/// <summary>")
|
||||||
|
.WriteLine($"/// Initializes a new instance of the <see cref=\"{model.TypeIdentifierName}\"/> struct")
|
||||||
|
.WriteLine($"/// using the property values from the specified <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||||
|
.WriteLine("/// </summary>")
|
||||||
|
.WriteLine($"/// <exception cref=\"ArgumentNullException\">{sourceClassParameterName} is null</exception>");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName})")
|
||||||
|
.WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static SourceBuilder GeneratePublicConstructor(this SourceBuilder builder, MappingModel model)
|
||||||
|
{
|
||||||
|
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||||
|
const string mappingContextParameterName = "context";
|
||||||
|
|
||||||
|
var baseConstructor = /*model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" :*/ string.Empty;
|
||||||
|
|
||||||
|
var readOnlyProperties = model.TypeProperties.GetReadOnlyMappedProperties();
|
||||||
|
|
||||||
|
var readOnlyFields = "";
|
||||||
|
|
||||||
|
for (int i = 0; i < readOnlyProperties.Length; i++)
|
||||||
|
{
|
||||||
|
var property = readOnlyProperties[i];
|
||||||
|
readOnlyFields += $"{property.Type} {property.SourcePropertyName.ToCamelCase()}";
|
||||||
|
if (i != readOnlyProperties.Length - 1) readOnlyFields += " ,";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
builder
|
||||||
|
.WriteLine($"public {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName}{(string.IsNullOrEmpty(readOnlyFields) ? "" : $", {readOnlyFields}")}){baseConstructor}")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.TryWriteProperties(model.SourceProperties, readOnlyProperties, sourceClassParameterName, mappingContextParameterName, false);
|
||||||
|
|
||||||
|
// End constructor declaration
|
||||||
|
return builder.WriteClosingBracket();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder TryWriteProperties(this SourceBuilder builder, System.Collections.Immutable.ImmutableArray<MappedProperty> properties, System.Collections.Immutable.ImmutableArray<MappedProperty>? otherProperties,
|
||||||
|
string? sourceClassParameterName, string mappingContextParameterName, bool fromUpdate)
|
||||||
|
{
|
||||||
|
if (fromUpdate)
|
||||||
|
{
|
||||||
|
properties = properties.GetWritableMappedProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
if (property.isReadOnly && fromUpdate) continue;
|
||||||
|
|
||||||
|
if (property.TypeConverter is null)
|
||||||
|
{
|
||||||
|
if (property.IsEnumerable)
|
||||||
|
{
|
||||||
|
builder.WriteLine(
|
||||||
|
$"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.Select({mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.EnumerableTypeArgument}>).ToList();");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.WriteLine(property.MappedSourcePropertyTypeName is null
|
||||||
|
? $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};"
|
||||||
|
: "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var parameters = property.TypeConverterParameters.IsEmpty
|
||||||
|
? "null"
|
||||||
|
: $"new object[] {{ {string.Join(", ", property.TypeConverterParameters)} }}";
|
||||||
|
|
||||||
|
builder.WriteLine(
|
||||||
|
$"{property.Name} = new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters});");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (otherProperties == null) return builder;
|
||||||
|
|
||||||
|
foreach (var property in otherProperties)
|
||||||
|
{
|
||||||
|
|
||||||
|
builder.WriteLine(property.MappedSourcePropertyTypeName is null
|
||||||
|
? $"{property.Name} = {property.SourcePropertyName.ToCamelCase()};"
|
||||||
|
: "");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static SourceBuilder GenerateUpdateMethod(this SourceBuilder builder, MappingModel model)
|
||||||
|
{
|
||||||
|
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||||
|
|
||||||
|
builder
|
||||||
|
.GenerateUpdaterMethodsXmlDocs(model, sourceClassParameterName)
|
||||||
|
.WriteLine($"public void Update({model.SourceType} {sourceClassParameterName})")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.TryWriteProperties(model.SourceProperties, null, sourceClassParameterName, "context", true)
|
||||||
|
.WriteClosingBracket();
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder GenerateUpdaterMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
||||||
|
{
|
||||||
|
if (!model.Options.GenerateXmlDocument)
|
||||||
|
{
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.WriteLine("/// <summary>")
|
||||||
|
.WriteLine($"/// Updates <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
|
||||||
|
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||||
|
.WriteLine("/// </summary>")
|
||||||
|
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{model.SourceType}\"/> to use as source.</param>");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,84 +1,84 @@
|
||||||
using System;
|
using System;
|
||||||
using static MapTo.Sources.Constants;
|
using static MapTo.Sources.Constants;
|
||||||
|
|
||||||
namespace MapTo.Sources
|
namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
internal static class MapTypeConverterAttributeSource
|
internal static class MapTypeConverterAttributeSource
|
||||||
{
|
{
|
||||||
internal const string AttributeName = "MapTypeConverter";
|
internal const string AttributeName = "MapTypeConverter";
|
||||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||||
internal const string ConverterPropertyName = "Converter";
|
internal const string ConverterPropertyName = "Converter";
|
||||||
internal const string ConverterParametersPropertyName = "ConverterParameters";
|
internal const string ConverterParametersPropertyName = "ConverterParameters";
|
||||||
|
|
||||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||||
{
|
{
|
||||||
using var builder = new SourceBuilder()
|
using var builder = new SourceBuilder()
|
||||||
.WriteLine(GeneratedFilesHeader)
|
.WriteLine(GeneratedFilesHeader)
|
||||||
.WriteNullableContextOptionIf(options.SupportNullableReferenceTypes)
|
.WriteNullableContextOptionIf(options.SupportNullableReferenceTypes)
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine("using System;")
|
.WriteLine("using System;")
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine($"namespace {RootNamespace}")
|
.WriteLine($"namespace {RootNamespace}")
|
||||||
.WriteOpeningBracket();
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
if (options.GenerateXmlDocument)
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine("/// <summary>")
|
.WriteLine("/// <summary>")
|
||||||
.WriteLine("/// Specifies what type to use as a converter for the property this attribute is bound to.")
|
.WriteLine("/// Specifies what type to use as a converter for the property this attribute is bound to.")
|
||||||
.WriteLine("/// </summary>");
|
.WriteLine("/// </summary>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]")
|
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]")
|
||||||
.WriteLine($"public sealed class {AttributeClassName} : Attribute")
|
.WriteLine($"public sealed class {AttributeClassName} : Attribute")
|
||||||
.WriteOpeningBracket();
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
if (options.GenerateXmlDocument)
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine("/// <summary>")
|
.WriteLine("/// <summary>")
|
||||||
.WriteLine($"/// Initializes a new instance of <see cref=\"{AttributeClassName}\"/>.")
|
.WriteLine($"/// Initializes a new instance of <see cref=\"{AttributeClassName}\"/>.")
|
||||||
.WriteLine("/// </summary>")
|
.WriteLine("/// </summary>")
|
||||||
.WriteLine($"/// <param name=\"converter\">The <see cref=\"{ITypeConverterSource.InterfaceName}{{TSource,TDestination}}\" /> to be used to convert the source type.</param>")
|
.WriteLine($"/// <param name=\"converter\">The <see cref=\"{ITypeConverterSource.InterfaceName}{{TSource,TDestination}}\" /> to be used to convert the source type.</param>")
|
||||||
.WriteLine("/// <param name=\"converterParameters\">The list of parameters to pass to the <paramref name=\"converter\"/> during the type conversion.</param>");
|
.WriteLine("/// <param name=\"converterParameters\">The list of parameters to pass to the <paramref name=\"converter\"/> during the type conversion.</param>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine($"public {AttributeClassName}(Type converter, object[]{options.NullableReferenceSyntax} converterParameters = null)")
|
.WriteLine($"public {AttributeClassName}(Type converter, object[]{options.NullableReferenceSyntax} converterParameters = null)")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine($"{ConverterPropertyName} = converter;")
|
.WriteLine($"{ConverterPropertyName} = converter;")
|
||||||
.WriteLine($"{ConverterParametersPropertyName} = converterParameters;")
|
.WriteLine($"{ConverterParametersPropertyName} = converterParameters;")
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteLine();
|
.WriteLine();
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
if (options.GenerateXmlDocument)
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine("/// <summary>")
|
.WriteLine("/// <summary>")
|
||||||
.WriteLine($"/// Gets or sets the <see cref=\"{ITypeConverterSource.InterfaceName}{{TSource,TDestination}}\" /> to be used to convert the source type.")
|
.WriteLine($"/// Gets or sets the <see cref=\"{ITypeConverterSource.InterfaceName}{{TSource,TDestination}}\" /> to be used to convert the source type.")
|
||||||
.WriteLine("/// </summary>");
|
.WriteLine("/// </summary>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine($"public Type {ConverterPropertyName} {{ get; }}")
|
.WriteLine($"public Type {ConverterPropertyName} {{ get; }}")
|
||||||
.WriteLine();
|
.WriteLine();
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
if (options.GenerateXmlDocument)
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine("/// <summary>")
|
.WriteLine("/// <summary>")
|
||||||
.WriteLine($"/// Gets the list of parameters to pass to the <see cref=\"{ConverterPropertyName}\"/> during the type conversion.")
|
.WriteLine($"/// Gets the list of parameters to pass to the <see cref=\"{ConverterPropertyName}\"/> during the type conversion.")
|
||||||
.WriteLine("/// </summary>");
|
.WriteLine("/// </summary>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine($"public object[]{options.NullableReferenceSyntax} {ConverterParametersPropertyName} {{ get; }}")
|
.WriteLine($"public object[]{options.NullableReferenceSyntax} {ConverterParametersPropertyName} {{ get; }}")
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteClosingBracket();
|
.WriteClosingBracket();
|
||||||
|
|
||||||
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,115 +1,115 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using static MapTo.Sources.Constants;
|
using static MapTo.Sources.Constants;
|
||||||
|
|
||||||
namespace MapTo.Sources
|
namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
internal static class MappingContextSource
|
internal static class MappingContextSource
|
||||||
{
|
{
|
||||||
internal const string ClassName = "MappingContext";
|
internal const string ClassName = "MappingContext";
|
||||||
internal const string FullyQualifiedName = RootNamespace + "." + ClassName;
|
internal const string FullyQualifiedName = RootNamespace + "." + ClassName;
|
||||||
internal const string FactoryMethodName = "Create";
|
internal const string FactoryMethodName = "Create";
|
||||||
internal const string RegisterMethodName = "Register";
|
internal const string RegisterMethodName = "Register";
|
||||||
internal const string MapMethodName = "MapFromWithContext";
|
internal const string MapMethodName = "MapFromWithContext";
|
||||||
|
|
||||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||||
{
|
{
|
||||||
var usings = new List<string> { "System", "System.Collections.Generic", "System.Reflection" };
|
var usings = new List<string> { "System", "System.Collections.Generic", "System.Reflection" };
|
||||||
|
|
||||||
using var builder = new SourceBuilder()
|
using var builder = new SourceBuilder()
|
||||||
.WriteLine(GeneratedFilesHeader)
|
.WriteLine(GeneratedFilesHeader)
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteUsings(usings)
|
.WriteUsings(usings)
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
|
|
||||||
// Namespace declaration
|
// Namespace declaration
|
||||||
.WriteLine($"namespace {RootNamespace}")
|
.WriteLine($"namespace {RootNamespace}")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
|
|
||||||
// Class declaration
|
// Class declaration
|
||||||
.WriteLine($"internal sealed class {ClassName}")
|
.WriteLine($"internal sealed class {ClassName}")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
|
|
||||||
.WriteLine("private readonly Dictionary<object, object> _cache;")
|
.WriteLine("private readonly Dictionary<object, object> _cache;")
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
.WriteLine($"internal {ClassName}()")
|
.WriteLine($"internal {ClassName}()")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine("_cache = new Dictionary<object, object>(1);")
|
.WriteLine("_cache = new Dictionary<object, object>(1);")
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
|
|
||||||
// Factory
|
// Factory
|
||||||
.WriteLine($"internal static TMapped {FactoryMethodName}<TOriginal, TMapped>(TOriginal original)")
|
.WriteLine($"internal static TMapped {FactoryMethodName}<TOriginal, TMapped>(TOriginal original)")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine("if (original == null) throw new ArgumentNullException(nameof(original));")
|
.WriteLine("if (original == null) throw new ArgumentNullException(nameof(original));")
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine("var context = new MappingContext();")
|
.WriteLine("var context = new MappingContext();")
|
||||||
.WriteLine("var mapped = context.MapFromWithContext<TOriginal, TMapped>(original);")
|
.WriteLine("var mapped = context.MapFromWithContext<TOriginal, TMapped>(original);")
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine("if (mapped == null)")
|
.WriteLine("if (mapped == null)")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine("throw new InvalidOperationException();")
|
.WriteLine("throw new InvalidOperationException();")
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine("return mapped;")
|
.WriteLine("return mapped;")
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
|
|
||||||
// MapFromWithContext method
|
// MapFromWithContext method
|
||||||
.WriteLine($"internal TMapped MapFromWithContext<TOriginal, TMapped>(TOriginal original)")
|
.WriteLine($"internal TMapped MapFromWithContext<TOriginal, TMapped>(TOriginal original)")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine("if (original == null)")
|
.WriteLine("if (original == null)")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine("return default(TMapped);")
|
.WriteLine("return default(TMapped);")
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine("if (!TryGetValue<TOriginal, TMapped>(original, out var mapped))")
|
.WriteLine("if (!TryGetValue<TOriginal, TMapped>(original, out var mapped))")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine("var instance = Activator.CreateInstance(typeof(TMapped), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { this, original }, null);")
|
.WriteLine("var instance = Activator.CreateInstance(typeof(TMapped), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { this, original }, null);")
|
||||||
.WriteLine("if (instance != null)")
|
.WriteLine("if (instance != null)")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine("mapped = (TMapped)instance;")
|
.WriteLine("mapped = (TMapped)instance;")
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine("return mapped;")
|
.WriteLine("return mapped;")
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
|
|
||||||
// Register method
|
// Register method
|
||||||
.WriteLine("internal void Register<TOriginal, TMapped>(TOriginal original, TMapped mapped)")
|
.WriteLine("internal void Register<TOriginal, TMapped>(TOriginal original, TMapped mapped)")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine("if (original == null) throw new ArgumentNullException(nameof(original));")
|
.WriteLine("if (original == null) throw new ArgumentNullException(nameof(original));")
|
||||||
.WriteLine("if (mapped == null) throw new ArgumentNullException(nameof(mapped));")
|
.WriteLine("if (mapped == null) throw new ArgumentNullException(nameof(mapped));")
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine("if (!_cache.ContainsKey(original))")
|
.WriteLine("if (!_cache.ContainsKey(original))")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine("_cache.Add(original, mapped);")
|
.WriteLine("_cache.Add(original, mapped);")
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
|
|
||||||
// TryGetValue method
|
// TryGetValue method
|
||||||
.WriteLine("private bool TryGetValue<TOriginal, TMapped>(TOriginal original, out TMapped mapped)")
|
.WriteLine("private bool TryGetValue<TOriginal, TMapped>(TOriginal original, out TMapped mapped)")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine("if (original != null && _cache.TryGetValue(original, out var value))")
|
.WriteLine("if (original != null && _cache.TryGetValue(original, out var value))")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine("mapped = (TMapped)value;")
|
.WriteLine("mapped = (TMapped)value;")
|
||||||
.WriteLine("return true;")
|
.WriteLine("return true;")
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine("mapped = default(TMapped);")
|
.WriteLine("mapped = default(TMapped);")
|
||||||
.WriteLine("return false;")
|
.WriteLine("return false;")
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
|
|
||||||
// End class declaration
|
// End class declaration
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
|
|
||||||
// End namespace declaration
|
// End namespace declaration
|
||||||
.WriteClosingBracket();
|
.WriteClosingBracket();
|
||||||
|
|
||||||
return new(builder.ToString(), $"{ClassName}.g.cs");
|
return new(builder.ToString(), $"{ClassName}.g.cs");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,36 +1,36 @@
|
||||||
using static MapTo.Sources.Constants;
|
using static MapTo.Sources.Constants;
|
||||||
|
|
||||||
namespace MapTo.Sources
|
namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
internal static class ReadOnlyPropertyAttributeSource
|
internal static class ReadOnlyPropertyAttributeSource
|
||||||
{
|
{
|
||||||
internal const string AttributeName = "ReadOnlyProperty";
|
internal const string AttributeName = "ReadOnlyProperty";
|
||||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||||
|
|
||||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||||
{
|
{
|
||||||
var builder = new SourceBuilder()
|
var builder = new SourceBuilder()
|
||||||
.WriteLine(GeneratedFilesHeader)
|
.WriteLine(GeneratedFilesHeader)
|
||||||
.WriteLine("using System;")
|
.WriteLine("using System;")
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine($"namespace {RootNamespace}")
|
.WriteLine($"namespace {RootNamespace}")
|
||||||
.WriteOpeningBracket();
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
if (options.GenerateXmlDocument)
|
if (options.GenerateXmlDocument)
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine("/// <summary>")
|
.WriteLine("/// <summary>")
|
||||||
.WriteLine("/// Specifies that the annotated property should be excluded.")
|
.WriteLine("/// Specifies that the annotated property should be excluded.")
|
||||||
.WriteLine("/// </summary>");
|
.WriteLine("/// </summary>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine("[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]")
|
.WriteLine("[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]")
|
||||||
.WriteLine($"public sealed class {AttributeClassName} : Attribute {{ }}")
|
.WriteLine($"public sealed class {AttributeClassName} : Attribute {{ }}")
|
||||||
.WriteClosingBracket();
|
.WriteClosingBracket();
|
||||||
|
|
||||||
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,107 +1,107 @@
|
||||||
using System;
|
using System;
|
||||||
using System.CodeDom.Compiler;
|
using System.CodeDom.Compiler;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace MapTo.Sources
|
namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
internal sealed class SourceBuilder : IDisposable
|
internal sealed class SourceBuilder : IDisposable
|
||||||
{
|
{
|
||||||
private readonly StringWriter _writer;
|
private readonly StringWriter _writer;
|
||||||
private readonly IndentedTextWriter _indentedWriter;
|
private readonly IndentedTextWriter _indentedWriter;
|
||||||
|
|
||||||
public SourceBuilder()
|
public SourceBuilder()
|
||||||
{
|
{
|
||||||
_writer = new StringWriter();
|
_writer = new StringWriter();
|
||||||
_indentedWriter = new IndentedTextWriter(_writer, new string(' ', 4));
|
_indentedWriter = new IndentedTextWriter(_writer, new string(' ', 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_writer.Dispose();
|
_writer.Dispose();
|
||||||
_indentedWriter.Dispose();
|
_indentedWriter.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceBuilder WriteLine(string? value = null)
|
public SourceBuilder WriteLine(string? value = null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
{
|
{
|
||||||
_indentedWriter.WriteLineNoTabs(string.Empty);
|
_indentedWriter.WriteLineNoTabs(string.Empty);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_indentedWriter.WriteLine(value);
|
_indentedWriter.WriteLine(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceBuilder Write(string? value = null)
|
public SourceBuilder Write(string? value = null)
|
||||||
{
|
{
|
||||||
_indentedWriter.Write(value);
|
_indentedWriter.Write(value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceBuilder WriteLineIf(bool condition, string? value)
|
public SourceBuilder WriteLineIf(bool condition, string? value)
|
||||||
{
|
{
|
||||||
if (condition)
|
if (condition)
|
||||||
{
|
{
|
||||||
WriteLine(value);
|
WriteLine(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceBuilder WriteNullableContextOptionIf(bool enabled) => WriteLineIf(enabled, "#nullable enable");
|
public SourceBuilder WriteNullableContextOptionIf(bool enabled) => WriteLineIf(enabled, "#nullable enable");
|
||||||
|
|
||||||
public SourceBuilder WriteOpeningBracket()
|
public SourceBuilder WriteOpeningBracket()
|
||||||
{
|
{
|
||||||
_indentedWriter.WriteLine("{");
|
_indentedWriter.WriteLine("{");
|
||||||
_indentedWriter.Indent++;
|
_indentedWriter.Indent++;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceBuilder WriteClosingBracket()
|
public SourceBuilder WriteClosingBracket()
|
||||||
{
|
{
|
||||||
_indentedWriter.Indent--;
|
_indentedWriter.Indent--;
|
||||||
_indentedWriter.WriteLine("}");
|
_indentedWriter.WriteLine("}");
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceBuilder WriteUsings(IEnumerable<string> usings)
|
public SourceBuilder WriteUsings(IEnumerable<string> usings)
|
||||||
{
|
{
|
||||||
foreach (var u in usings.OrderBy(s => s))
|
foreach (var u in usings.OrderBy(s => s))
|
||||||
{
|
{
|
||||||
WriteUsing(u);
|
WriteUsing(u);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceBuilder WriteUsing(string u)
|
public SourceBuilder WriteUsing(string u)
|
||||||
{
|
{
|
||||||
WriteLine($"using {u};");
|
WriteLine($"using {u};");
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceBuilder Indent()
|
public SourceBuilder Indent()
|
||||||
{
|
{
|
||||||
_indentedWriter.Indent++;
|
_indentedWriter.Indent++;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceBuilder Unindent()
|
public SourceBuilder Unindent()
|
||||||
{
|
{
|
||||||
_indentedWriter.Indent--;
|
_indentedWriter.Indent--;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString() => _writer.ToString();
|
public override string ToString() => _writer.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using MapTo.Extensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
|
namespace MapTo
|
||||||
|
{
|
||||||
|
internal class StructMappingContext : MappingContext
|
||||||
|
{
|
||||||
|
internal StructMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
||||||
|
: base(compilation, sourceGenerationOptions, typeSyntax) { }
|
||||||
|
|
||||||
|
protected override ImmutableArray<MappedProperty> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass)
|
||||||
|
{
|
||||||
|
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||||
|
|
||||||
|
return typeSymbol
|
||||||
|
.GetAllMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol))
|
||||||
|
.Select(property => MapProperty(sourceTypeSymbol, sourceProperties, property))
|
||||||
|
.Where(mappedProperty => mappedProperty is not null)
|
||||||
|
.ToImmutableArray()!;
|
||||||
|
}
|
||||||
|
protected override ImmutableArray<MappedProperty> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass)
|
||||||
|
{
|
||||||
|
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||||
|
|
||||||
|
return sourceTypeSymbol
|
||||||
|
.GetAllMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol))
|
||||||
|
.Select(property => MapPropertySimple(typeSymbol, property))
|
||||||
|
.Where(mappedProperty => mappedProperty is not null)
|
||||||
|
.ToImmutableArray()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,85 +1,85 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MapTo.Integration.Tests.Data.Models;
|
using MapTo.Integration.Tests.Data.Models;
|
||||||
using MapTo.Integration.Tests.Data.ViewModels;
|
using MapTo.Integration.Tests.Data.ViewModels;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace MapTo.Integration.Tests
|
namespace MapTo.Integration.Tests
|
||||||
{
|
{
|
||||||
public class CyclicReferenceTests
|
public class CyclicReferenceTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void VerifySelfReference()
|
public void VerifySelfReference()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var manager = new Manager { Id = 1, EmployeeCode = "M001", Level = 100 };
|
var manager = new Manager { Id = 1, EmployeeCode = "M001", Level = 100 };
|
||||||
manager.Manager = manager;
|
manager.Manager = manager;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = manager.ToManagerViewModel();
|
var result = manager.ToManagerViewModel();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
result.Id.ShouldBe(manager.Id);
|
result.Id.ShouldBe(manager.Id);
|
||||||
result.EmployeeCode.ShouldBe(manager.EmployeeCode);
|
result.EmployeeCode.ShouldBe(manager.EmployeeCode);
|
||||||
result.Level.ShouldBe(manager.Level);
|
result.Level.ShouldBe(manager.Level);
|
||||||
result.Manager.ShouldBeSameAs(result);
|
result.Manager.ShouldBeSameAs(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void VerifyNestedReference()
|
public void VerifyNestedReference()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var manager1 = new Manager { Id = 100, EmployeeCode = "M001", Level = 100 };
|
var manager1 = new Manager { Id = 100, EmployeeCode = "M001", Level = 100 };
|
||||||
var manager2 = new Manager { Id = 102, EmployeeCode = "M002", Level = 100 };
|
var manager2 = new Manager { Id = 102, EmployeeCode = "M002", Level = 100 };
|
||||||
|
|
||||||
var employee1 = new Employee { Id = 200, EmployeeCode = "E001"};
|
var employee1 = new Employee { Id = 200, EmployeeCode = "E001"};
|
||||||
var employee2 = new Employee { Id = 201, EmployeeCode = "E002"};
|
var employee2 = new Employee { Id = 201, EmployeeCode = "E002"};
|
||||||
|
|
||||||
employee1.Manager = manager1;
|
employee1.Manager = manager1;
|
||||||
employee2.Manager = manager2;
|
employee2.Manager = manager2;
|
||||||
|
|
||||||
manager2.Manager = manager1;
|
manager2.Manager = manager1;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var manager1ViewModel = manager1.ToManagerViewModel();
|
var manager1ViewModel = manager1.ToManagerViewModel();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
manager1ViewModel.Id.ShouldBe(manager1.Id);
|
manager1ViewModel.Id.ShouldBe(manager1.Id);
|
||||||
manager1ViewModel.Manager.ShouldBeNull();
|
manager1ViewModel.Manager.ShouldBeNull();
|
||||||
manager1ViewModel.Employees.Count.ShouldBe(2);
|
manager1ViewModel.Employees.Count.ShouldBe(2);
|
||||||
manager1ViewModel.Employees[0].Id.ShouldBe(employee1.Id);
|
manager1ViewModel.Employees[0].Id.ShouldBe(employee1.Id);
|
||||||
manager1ViewModel.Employees[0].Manager.ShouldBeSameAs(manager1ViewModel);
|
manager1ViewModel.Employees[0].Manager.ShouldBeSameAs(manager1ViewModel);
|
||||||
manager1ViewModel.Employees[1].Id.ShouldBe(manager2.Id);
|
manager1ViewModel.Employees[1].Id.ShouldBe(manager2.Id);
|
||||||
manager1ViewModel.Employees[1].Manager.ShouldBeSameAs(manager1ViewModel);
|
manager1ViewModel.Employees[1].Manager.ShouldBeSameAs(manager1ViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void VerifyNestedSelfReference()
|
public void VerifyNestedSelfReference()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var manager1 = new Manager { Id = 100, EmployeeCode = "M001", Level = 100 };
|
var manager1 = new Manager { Id = 100, EmployeeCode = "M001", Level = 100 };
|
||||||
var manager3 = new Manager { Id = 101, EmployeeCode = "M003", Level = 100 };
|
var manager3 = new Manager { Id = 101, EmployeeCode = "M003", Level = 100 };
|
||||||
var manager2 = new Manager { Id = 102, EmployeeCode = "M002", Level = 100 };
|
var manager2 = new Manager { Id = 102, EmployeeCode = "M002", Level = 100 };
|
||||||
|
|
||||||
var employee1 = new Employee { Id = 200, EmployeeCode = "E001"};
|
var employee1 = new Employee { Id = 200, EmployeeCode = "E001"};
|
||||||
var employee2 = new Employee { Id = 201, EmployeeCode = "E002"};
|
var employee2 = new Employee { Id = 201, EmployeeCode = "E002"};
|
||||||
var employee3 = new Employee { Id = 202, EmployeeCode = "E003"};
|
var employee3 = new Employee { Id = 202, EmployeeCode = "E003"};
|
||||||
|
|
||||||
employee1.Manager = manager1;
|
employee1.Manager = manager1;
|
||||||
employee2.Manager = manager2;
|
employee2.Manager = manager2;
|
||||||
employee3.Manager = manager3;
|
employee3.Manager = manager3;
|
||||||
|
|
||||||
manager2.Manager = manager1;
|
manager2.Manager = manager1;
|
||||||
manager3.Manager = manager2;
|
manager3.Manager = manager2;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var manager3ViewModel = manager3.ToManagerViewModel();
|
var manager3ViewModel = manager3.ToManagerViewModel();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
manager3ViewModel.Manager.ShouldNotBeNull();
|
manager3ViewModel.Manager.ShouldNotBeNull();
|
||||||
manager3ViewModel.Manager.Id.ShouldBe(manager2.Id);
|
manager3ViewModel.Manager.Id.ShouldBe(manager2.Id);
|
||||||
manager3ViewModel.Manager.Manager.Id.ShouldBe(manager1.Id);
|
manager3ViewModel.Manager.Manager.Id.ShouldBe(manager1.Id);
|
||||||
manager3ViewModel.Employees.All(e => ReferenceEquals(e.Manager, manager3ViewModel)).ShouldBeTrue();
|
manager3ViewModel.Employees.All(e => ReferenceEquals(e.Manager, manager3ViewModel)).ShouldBeTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,29 +1,29 @@
|
||||||
namespace MapTo.Integration.Tests.Data.Models
|
namespace MapTo.Integration.Tests.Data.Models
|
||||||
{
|
{
|
||||||
public class Employee
|
public class Employee
|
||||||
{
|
{
|
||||||
private Manager _manager;
|
private Manager _manager;
|
||||||
|
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
public string EmployeeCode { get; set; }
|
public string EmployeeCode { get; set; }
|
||||||
|
|
||||||
public Manager Manager
|
public Manager Manager
|
||||||
{
|
{
|
||||||
get => _manager;
|
get => _manager;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
_manager.Employees.Remove(this);
|
_manager.Employees.Remove(this);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
value.Employees.Add(this);
|
value.Employees.Add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_manager = value;
|
_manager = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace MapTo.Integration.Tests.Data.Models
|
namespace MapTo.Integration.Tests.Data.Models
|
||||||
{
|
{
|
||||||
public class Manager : Employee
|
public class Manager : Employee
|
||||||
{
|
{
|
||||||
public int Level { get; set; }
|
public int Level { get; set; }
|
||||||
|
|
||||||
public List<Employee> Employees { get; set; } = new();
|
public List<Employee> Employees { get; set; } = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,14 +1,14 @@
|
||||||
using MapTo.Integration.Tests.Data.Models;
|
using MapTo.Integration.Tests.Data.Models;
|
||||||
|
|
||||||
namespace MapTo.Integration.Tests.Data.ViewModels
|
namespace MapTo.Integration.Tests.Data.ViewModels
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(Employee))]
|
[MapFrom(typeof(Employee))]
|
||||||
public partial class EmployeeViewModel
|
public partial class EmployeeViewModel
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
public string EmployeeCode { get; set; }
|
public string EmployeeCode { get; set; }
|
||||||
|
|
||||||
public ManagerViewModel Manager { get; set; }
|
public ManagerViewModel Manager { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using MapTo.Integration.Tests.Data.Models;
|
using MapTo.Integration.Tests.Data.Models;
|
||||||
|
|
||||||
namespace MapTo.Integration.Tests.Data.ViewModels
|
namespace MapTo.Integration.Tests.Data.ViewModels
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(Manager))]
|
[MapFrom(typeof(Manager))]
|
||||||
public partial class ManagerViewModel : EmployeeViewModel
|
public partial class ManagerViewModel : EmployeeViewModel
|
||||||
{
|
{
|
||||||
public int Level { get; set; }
|
public int Level { get; set; }
|
||||||
|
|
||||||
public List<EmployeeViewModel> Employees { get; set; } = new();
|
public List<EmployeeViewModel> Employees { get; set; } = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,26 +1,26 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\BlueWest.MapTo\BlueWest.MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
<ProjectReference Include="..\..\src\MapTo\MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||||
<PackageReference Include="Shouldly" Version="4.0.3" />
|
<PackageReference Include="Shouldly" Version="4.0.3" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="coverlet.collector" Version="3.0.3">
|
<PackageReference Include="coverlet.collector" Version="3.0.3">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,252 +1,252 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MapTo.Extensions;
|
using MapTo.Extensions;
|
||||||
using MapTo.Sources;
|
using MapTo.Sources;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
|
|
||||||
namespace MapTo.Tests
|
namespace MapTo.Tests
|
||||||
{
|
{
|
||||||
internal static class Common
|
internal static class Common
|
||||||
{
|
{
|
||||||
internal const int Indent1 = 4;
|
internal const int Indent1 = 4;
|
||||||
internal const int Indent2 = Indent1 * 2;
|
internal const int Indent2 = Indent1 * 2;
|
||||||
internal const int Indent3 = Indent1 * 3;
|
internal const int Indent3 = Indent1 * 3;
|
||||||
internal static readonly Location IgnoreLocation = Location.None;
|
internal static readonly Location IgnoreLocation = Location.None;
|
||||||
|
|
||||||
internal static readonly Dictionary<string, string> DefaultAnalyzerOptions = new()
|
internal static readonly Dictionary<string, string> DefaultAnalyzerOptions = new()
|
||||||
{
|
{
|
||||||
[GeneratorExecutionContextExtensions.GetBuildPropertyName(nameof(SourceGenerationOptions.GenerateXmlDocument))] = "false"
|
[GeneratorExecutionContextExtensions.GetBuildPropertyName(nameof(SourceGenerationOptions.GenerateXmlDocument))] = "false"
|
||||||
};
|
};
|
||||||
|
|
||||||
internal static string GetSourceText(SourceGeneratorOptions? options = null)
|
internal static string GetSourceText(SourceGeneratorOptions? options = null)
|
||||||
{
|
{
|
||||||
const string ns = "Test";
|
const string ns = "Test";
|
||||||
options ??= new SourceGeneratorOptions();
|
options ??= new SourceGeneratorOptions();
|
||||||
var hasDifferentSourceNamespace = options.SourceClassNamespace != ns;
|
var hasDifferentSourceNamespace = options.SourceClassNamespace != ns;
|
||||||
var builder = new SourceBuilder();
|
var builder = new SourceBuilder();
|
||||||
|
|
||||||
builder.WriteLine("//");
|
builder.WriteLine("//");
|
||||||
builder.WriteLine("// Test source code.");
|
builder.WriteLine("// Test source code.");
|
||||||
builder.WriteLine("//");
|
builder.WriteLine("//");
|
||||||
builder.WriteLine();
|
builder.WriteLine();
|
||||||
|
|
||||||
options.Usings?.ForEach(s => builder.WriteLine($"using {s};"));
|
options.Usings?.ForEach(s => builder.WriteLine($"using {s};"));
|
||||||
|
|
||||||
if (options.UseMapToNamespace)
|
if (options.UseMapToNamespace)
|
||||||
{
|
{
|
||||||
builder.WriteLine($"using {Constants.RootNamespace};");
|
builder.WriteLine($"using {Constants.RootNamespace};");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine($"using {options.SourceClassNamespace};")
|
.WriteLine($"using {options.SourceClassNamespace};")
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine();
|
.WriteLine();
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine($"namespace {ns}")
|
.WriteLine($"namespace {ns}")
|
||||||
.WriteOpeningBracket();
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
if (hasDifferentSourceNamespace && options.UseMapToNamespace)
|
if (hasDifferentSourceNamespace && options.UseMapToNamespace)
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine($"using {options.SourceClassNamespace};")
|
.WriteLine($"using {options.SourceClassNamespace};")
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine();
|
.WriteLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine(options.UseMapToNamespace ? "[MapFrom(typeof(Baz))]" : "[MapTo.MapFrom(typeof(Baz))]")
|
.WriteLine(options.UseMapToNamespace ? "[MapFrom(typeof(Baz))]" : "[MapTo.MapFrom(typeof(Baz))]")
|
||||||
.WriteLine("public partial class Foo")
|
.WriteLine("public partial class Foo")
|
||||||
.WriteOpeningBracket();
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
for (var i = 1; i <= options.ClassPropertiesCount; i++)
|
for (var i = 1; i <= options.ClassPropertiesCount; i++)
|
||||||
{
|
{
|
||||||
builder.WriteLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
|
builder.WriteLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
|
||||||
}
|
}
|
||||||
|
|
||||||
options.PropertyBuilder?.Invoke(builder);
|
options.PropertyBuilder?.Invoke(builder);
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteLine();
|
.WriteLine();
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine($"namespace {options.SourceClassNamespace}")
|
.WriteLine($"namespace {options.SourceClassNamespace}")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine("public class Baz")
|
.WriteLine("public class Baz")
|
||||||
.WriteOpeningBracket();
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
for (var i = 1; i <= options.SourceClassPropertiesCount; i++)
|
for (var i = 1; i <= options.SourceClassPropertiesCount; i++)
|
||||||
{
|
{
|
||||||
builder.WriteLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
|
builder.WriteLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
|
||||||
}
|
}
|
||||||
|
|
||||||
options.SourcePropertyBuilder?.Invoke(builder);
|
options.SourcePropertyBuilder?.Invoke(builder);
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteClosingBracket();
|
.WriteClosingBracket();
|
||||||
|
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string[] GetEmployeeManagerSourceText(
|
internal static string[] GetEmployeeManagerSourceText(
|
||||||
Func<string>? employeeClassSource = null,
|
Func<string>? employeeClassSource = null,
|
||||||
Func<string>? managerClassSource = null,
|
Func<string>? managerClassSource = null,
|
||||||
Func<string>? employeeViewModelSource = null,
|
Func<string>? employeeViewModelSource = null,
|
||||||
Func<string>? managerViewModelSource = null,
|
Func<string>? managerViewModelSource = null,
|
||||||
bool useDifferentViewModelNamespace = false)
|
bool useDifferentViewModelNamespace = false)
|
||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
employeeClassSource?.Invoke() ?? DefaultEmployeeClassSource(),
|
employeeClassSource?.Invoke() ?? DefaultEmployeeClassSource(),
|
||||||
managerClassSource?.Invoke() ?? DefaultManagerClassSource(),
|
managerClassSource?.Invoke() ?? DefaultManagerClassSource(),
|
||||||
employeeViewModelSource?.Invoke() ??
|
employeeViewModelSource?.Invoke() ??
|
||||||
DefaultEmployeeViewModelSource(useDifferentViewModelNamespace),
|
DefaultEmployeeViewModelSource(useDifferentViewModelNamespace),
|
||||||
managerViewModelSource?.Invoke() ?? DefaultManagerViewModelSource(useDifferentViewModelNamespace)
|
managerViewModelSource?.Invoke() ?? DefaultManagerViewModelSource(useDifferentViewModelNamespace)
|
||||||
};
|
};
|
||||||
|
|
||||||
static string DefaultEmployeeClassSource() =>
|
static string DefaultEmployeeClassSource() =>
|
||||||
@"
|
@"
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Test.Data.Models
|
namespace Test.Data.Models
|
||||||
{
|
{
|
||||||
public class Employee
|
public class Employee
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
public string EmployeeCode { get; set; }
|
public string EmployeeCode { get; set; }
|
||||||
|
|
||||||
public Manager Manager { get; set; }
|
public Manager Manager { get; set; }
|
||||||
}
|
}
|
||||||
}".Trim();
|
}".Trim();
|
||||||
|
|
||||||
static string DefaultManagerClassSource() =>
|
static string DefaultManagerClassSource() =>
|
||||||
@"using System;
|
@"using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Test.Data.Models
|
namespace Test.Data.Models
|
||||||
{
|
{
|
||||||
public class Manager: Employee
|
public class Manager: Employee
|
||||||
{
|
{
|
||||||
public int Level { get; set; }
|
public int Level { get; set; }
|
||||||
|
|
||||||
public IEnumerable<Employee> Employees { get; set; } = Array.Empty<Employee>();
|
public IEnumerable<Employee> Employees { get; set; } = Array.Empty<Employee>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
static string DefaultEmployeeViewModelSource(bool useDifferentNamespace) => useDifferentNamespace
|
static string DefaultEmployeeViewModelSource(bool useDifferentNamespace) => useDifferentNamespace
|
||||||
? @"
|
? @"
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using Test.Data.Models;
|
using Test.Data.Models;
|
||||||
using Test.ViewModels2;
|
using Test.ViewModels2;
|
||||||
|
|
||||||
namespace Test.ViewModels
|
namespace Test.ViewModels
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(Employee))]
|
[MapFrom(typeof(Employee))]
|
||||||
public partial class EmployeeViewModel
|
public partial class EmployeeViewModel
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
public string EmployeeCode { get; set; }
|
public string EmployeeCode { get; set; }
|
||||||
|
|
||||||
public ManagerViewModel Manager { get; set; }
|
public ManagerViewModel Manager { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
".Trim()
|
".Trim()
|
||||||
: @"
|
: @"
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using Test.Data.Models;
|
using Test.Data.Models;
|
||||||
|
|
||||||
namespace Test.ViewModels
|
namespace Test.ViewModels
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(Employee))]
|
[MapFrom(typeof(Employee))]
|
||||||
public partial class EmployeeViewModel
|
public partial class EmployeeViewModel
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
public string EmployeeCode { get; set; }
|
public string EmployeeCode { get; set; }
|
||||||
|
|
||||||
public ManagerViewModel Manager { get; set; }
|
public ManagerViewModel Manager { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
static string DefaultManagerViewModelSource(bool useDifferentNamespace) => useDifferentNamespace
|
static string DefaultManagerViewModelSource(bool useDifferentNamespace) => useDifferentNamespace
|
||||||
? @"
|
? @"
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using Test.Data.Models;
|
using Test.Data.Models;
|
||||||
using Test.ViewModels;
|
using Test.ViewModels;
|
||||||
|
|
||||||
namespace Test.ViewModels2
|
namespace Test.ViewModels2
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(Manager))]
|
[MapFrom(typeof(Manager))]
|
||||||
public partial class ManagerViewModel : EmployeeViewModel
|
public partial class ManagerViewModel : EmployeeViewModel
|
||||||
{
|
{
|
||||||
public int Level { get; set; }
|
public int Level { get; set; }
|
||||||
|
|
||||||
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
|
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
".Trim()
|
".Trim()
|
||||||
: @"
|
: @"
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using Test.Data.Models;
|
using Test.Data.Models;
|
||||||
|
|
||||||
namespace Test.ViewModels
|
namespace Test.ViewModels
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(Manager))]
|
[MapFrom(typeof(Manager))]
|
||||||
public partial class ManagerViewModel : EmployeeViewModel
|
public partial class ManagerViewModel : EmployeeViewModel
|
||||||
{
|
{
|
||||||
public int Level { get; set; }
|
public int Level { get; set; }
|
||||||
|
|
||||||
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
|
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
|
||||||
}
|
}
|
||||||
}".Trim();
|
}".Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static PropertyDeclarationSyntax GetPropertyDeclarationSyntax(SyntaxTree syntaxTree, string targetPropertyName, string targetClass = "Foo")
|
internal static PropertyDeclarationSyntax GetPropertyDeclarationSyntax(SyntaxTree syntaxTree, string targetPropertyName, string targetClass = "Foo")
|
||||||
{
|
{
|
||||||
return syntaxTree.GetRoot()
|
return syntaxTree.GetRoot()
|
||||||
.DescendantNodes()
|
.DescendantNodes()
|
||||||
.OfType<ClassDeclarationSyntax>()
|
.OfType<ClassDeclarationSyntax>()
|
||||||
.Single(c => c.Identifier.ValueText == targetClass)
|
.Single(c => c.Identifier.ValueText == targetClass)
|
||||||
.DescendantNodes()
|
.DescendantNodes()
|
||||||
.OfType<PropertyDeclarationSyntax>()
|
.OfType<PropertyDeclarationSyntax>()
|
||||||
.Single(p => p.Identifier.ValueText == targetPropertyName);
|
.Single(p => p.Identifier.ValueText == targetPropertyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IPropertySymbol GetSourcePropertySymbol(string propertyName, Compilation compilation, string targetClass = "Foo")
|
internal static IPropertySymbol GetSourcePropertySymbol(string propertyName, Compilation compilation, string targetClass = "Foo")
|
||||||
{
|
{
|
||||||
var syntaxTree = compilation.SyntaxTrees.First();
|
var syntaxTree = compilation.SyntaxTrees.First();
|
||||||
var propSyntax = GetPropertyDeclarationSyntax(syntaxTree, propertyName, targetClass);
|
var propSyntax = GetPropertyDeclarationSyntax(syntaxTree, propertyName, targetClass);
|
||||||
|
|
||||||
var semanticModel = compilation.GetSemanticModel(syntaxTree);
|
var semanticModel = compilation.GetSemanticModel(syntaxTree);
|
||||||
return semanticModel.GetDeclaredSymbol(propSyntax).ShouldNotBeNull();
|
return semanticModel.GetDeclaredSymbol(propSyntax).ShouldNotBeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal record SourceGeneratorOptions(
|
internal record SourceGeneratorOptions(
|
||||||
bool UseMapToNamespace = false,
|
bool UseMapToNamespace = false,
|
||||||
string SourceClassNamespace = "Test.Models",
|
string SourceClassNamespace = "Test.Models",
|
||||||
int ClassPropertiesCount = 3,
|
int ClassPropertiesCount = 3,
|
||||||
int SourceClassPropertiesCount = 3,
|
int SourceClassPropertiesCount = 3,
|
||||||
Action<SourceBuilder>? PropertyBuilder = null,
|
Action<SourceBuilder>? PropertyBuilder = null,
|
||||||
Action<SourceBuilder>? SourcePropertyBuilder = null,
|
Action<SourceBuilder>? SourcePropertyBuilder = null,
|
||||||
IEnumerable<string>? Usings = null);
|
IEnumerable<string>? Usings = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,16 +1,16 @@
|
||||||
// ReSharper disable UnusedType.Global
|
// ReSharper disable UnusedType.Global
|
||||||
// ReSharper disable CheckNamespace
|
// ReSharper disable CheckNamespace
|
||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace System.Runtime.CompilerServices
|
namespace System.Runtime.CompilerServices
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reserved to be used by the compiler for tracking metadata.
|
/// Reserved to be used by the compiler for tracking metadata.
|
||||||
/// This class should not be used by developers in source code.
|
/// This class should not be used by developers in source code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
internal static class IsExternalInit { }
|
internal static class IsExternalInit { }
|
||||||
}
|
}
|
|
@ -1,42 +1,42 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace MapTo.Tests.Extensions
|
namespace MapTo.Tests.Extensions
|
||||||
{
|
{
|
||||||
internal static class RoslynExtensions
|
internal static class RoslynExtensions
|
||||||
{
|
{
|
||||||
internal static SyntaxTree? GetGeneratedSyntaxTree(this Compilation compilation, string className) =>
|
internal static SyntaxTree? GetGeneratedSyntaxTree(this Compilation compilation, string className) =>
|
||||||
compilation.SyntaxTrees.FirstOrDefault(s => s.FilePath.EndsWith($"{className}.g.cs"));
|
compilation.SyntaxTrees.SingleOrDefault(s => s.FilePath.EndsWith($"{className}.g.cs"));
|
||||||
|
|
||||||
internal static string PrintSyntaxTree(this Compilation compilation)
|
internal static string PrintSyntaxTree(this Compilation compilation)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
return string.Join(
|
return string.Join(
|
||||||
Environment.NewLine,
|
Environment.NewLine,
|
||||||
compilation.SyntaxTrees
|
compilation.SyntaxTrees
|
||||||
.Reverse()
|
.Reverse()
|
||||||
.Select((s, i) =>
|
.Select((s, i) =>
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.Clear()
|
.Clear()
|
||||||
.AppendLine("----------------------------------------")
|
.AppendLine("----------------------------------------")
|
||||||
.AppendFormat("File Path: \"{0}\"", s.FilePath).AppendLine()
|
.AppendFormat("File Path: \"{0}\"", s.FilePath).AppendLine()
|
||||||
.AppendFormat("Index: \"{0}\"", i).AppendLine()
|
.AppendFormat("Index: \"{0}\"", i).AppendLine()
|
||||||
.AppendLine();
|
.AppendLine();
|
||||||
|
|
||||||
var lines = s.ToString().Split(Environment.NewLine);
|
var lines = s.ToString().Split(Environment.NewLine);
|
||||||
var lineNumber = 0;
|
var lineNumber = 0;
|
||||||
foreach (var line in lines)
|
foreach (var line in lines)
|
||||||
{
|
{
|
||||||
builder.AppendFormat("{0:00}: {1}", lineNumber, line).AppendLine();
|
builder.AppendFormat("{0:00}: {1}", lineNumber, line).AppendLine();
|
||||||
lineNumber++;
|
lineNumber++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,88 +1,88 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace MapTo.Tests.Extensions
|
namespace MapTo.Tests.Extensions
|
||||||
{
|
{
|
||||||
internal static class ShouldlyExtensions
|
internal static class ShouldlyExtensions
|
||||||
{
|
{
|
||||||
internal static void ShouldContainSource(this IEnumerable<SyntaxTree> syntaxTree, string typeName, string expectedSource, string? customMessage = null)
|
internal static void ShouldContainSource(this IEnumerable<SyntaxTree> syntaxTree, string typeName, string expectedSource, string? customMessage = null)
|
||||||
{
|
{
|
||||||
var syntax = syntaxTree
|
var syntax = syntaxTree
|
||||||
.Select(s => s.ToString().Trim())
|
.Select(s => s.ToString().Trim())
|
||||||
.FirstOrDefault(s => s.Contains(typeName));
|
.SingleOrDefault(s => s.Contains(typeName));
|
||||||
|
|
||||||
syntax.ShouldNotBeNullOrWhiteSpace();
|
syntax.ShouldNotBeNullOrWhiteSpace();
|
||||||
syntax.ShouldBe(expectedSource, customMessage);
|
syntax.ShouldBe(expectedSource, customMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void ShouldContainPartialSource(this IEnumerable<SyntaxTree> syntaxTree, string typeName, string expectedSource, string? customMessage = null)
|
internal static void ShouldContainPartialSource(this IEnumerable<SyntaxTree> syntaxTree, string typeName, string expectedSource, string? customMessage = null)
|
||||||
{
|
{
|
||||||
var syntax = syntaxTree
|
var syntax = syntaxTree
|
||||||
.Select(s => s.ToString().Trim())
|
.Select(s => s.ToString().Trim())
|
||||||
.FirstOrDefault(s => s.Contains(typeName));
|
.SingleOrDefault(s => s.Contains(typeName));
|
||||||
|
|
||||||
syntax.ShouldNotBeNullOrWhiteSpace();
|
syntax.ShouldNotBeNullOrWhiteSpace();
|
||||||
syntax.ShouldContainWithoutWhitespace(expectedSource, customMessage);
|
syntax.ShouldContainWithoutWhitespace(expectedSource, customMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void ShouldContainPartialSource(this SyntaxTree syntaxTree, string expectedSource, string? customMessage = null)
|
internal static void ShouldContainPartialSource(this SyntaxTree syntaxTree, string expectedSource, string? customMessage = null)
|
||||||
{
|
{
|
||||||
var syntax = syntaxTree.ToString();
|
var syntax = syntaxTree.ToString();
|
||||||
syntax.ShouldNotBeNullOrWhiteSpace();
|
syntax.ShouldNotBeNullOrWhiteSpace();
|
||||||
syntax.ShouldContainWithoutWhitespace(expectedSource, customMessage);
|
syntax.ShouldContainWithoutWhitespace(expectedSource, customMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void ShouldBeSuccessful(this IEnumerable<Diagnostic> diagnostics, Compilation? compilation = null, IEnumerable<string>? ignoreDiagnosticsIds = null)
|
internal static void ShouldBeSuccessful(this IEnumerable<Diagnostic> diagnostics, Compilation? compilation = null, IEnumerable<string>? ignoreDiagnosticsIds = null)
|
||||||
{
|
{
|
||||||
var actual = diagnostics
|
var actual = diagnostics
|
||||||
.Where(d => (ignoreDiagnosticsIds is null || ignoreDiagnosticsIds.All(i => !d.Id.StartsWith(i) )) && (d.Severity is DiagnosticSeverity.Warning or DiagnosticSeverity.Error))
|
.Where(d => (ignoreDiagnosticsIds is null || ignoreDiagnosticsIds.All(i => !d.Id.StartsWith(i) )) && (d.Severity is DiagnosticSeverity.Warning or DiagnosticSeverity.Error))
|
||||||
.Select(c => $"{c.Severity}: {c.Location.GetLineSpan()} - {c.GetMessage()}").ToArray();
|
.Select(c => $"{c.Severity}: {c.Location.GetLineSpan()} - {c.GetMessage()}").ToArray();
|
||||||
|
|
||||||
if (!actual.Any())
|
if (!actual.Any())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.AppendLine("Failed");
|
builder.AppendLine("Failed");
|
||||||
|
|
||||||
foreach (var d in actual)
|
foreach (var d in actual)
|
||||||
{
|
{
|
||||||
builder.AppendFormat("- {0}", d).AppendLine();
|
builder.AppendFormat("- {0}", d).AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compilation is not null)
|
if (compilation is not null)
|
||||||
{
|
{
|
||||||
builder.AppendLine("Generated Sources:");
|
builder.AppendLine("Generated Sources:");
|
||||||
builder.AppendLine(compilation.PrintSyntaxTree());
|
builder.AppendLine(compilation.PrintSyntaxTree());
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.False(true, builder.ToString());
|
Assert.False(true, builder.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void ShouldNotBeSuccessful(this ImmutableArray<Diagnostic> diagnostics, Diagnostic expectedError)
|
internal static void ShouldNotBeSuccessful(this ImmutableArray<Diagnostic> diagnostics, Diagnostic expectedError)
|
||||||
{
|
{
|
||||||
var actualDiagnostics = diagnostics.FirstOrDefault(d => d.Id == expectedError.Id);
|
var actualDiagnostics = diagnostics.SingleOrDefault(d => d.Id == expectedError.Id);
|
||||||
var compilationDiagnostics = actualDiagnostics == null ? diagnostics : diagnostics.Except(new[] { actualDiagnostics });
|
var compilationDiagnostics = actualDiagnostics == null ? diagnostics : diagnostics.Except(new[] { actualDiagnostics });
|
||||||
|
|
||||||
compilationDiagnostics.ShouldBeSuccessful();
|
compilationDiagnostics.ShouldBeSuccessful();
|
||||||
|
|
||||||
Assert.NotNull(actualDiagnostics);
|
Assert.NotNull(actualDiagnostics);
|
||||||
Assert.Equal(expectedError.Id, actualDiagnostics?.Id);
|
Assert.Equal(expectedError.Id, actualDiagnostics?.Id);
|
||||||
Assert.Equal(expectedError.Descriptor.Id, actualDiagnostics?.Descriptor.Id);
|
Assert.Equal(expectedError.Descriptor.Id, actualDiagnostics?.Descriptor.Id);
|
||||||
Assert.Equal(expectedError.Descriptor.Description, actualDiagnostics?.Descriptor.Description);
|
Assert.Equal(expectedError.Descriptor.Description, actualDiagnostics?.Descriptor.Description);
|
||||||
Assert.Equal(expectedError.Descriptor.Title, actualDiagnostics?.Descriptor.Title);
|
Assert.Equal(expectedError.Descriptor.Title, actualDiagnostics?.Descriptor.Title);
|
||||||
|
|
||||||
if (expectedError.Location != Location.None)
|
if (expectedError.Location != Location.None)
|
||||||
{
|
{
|
||||||
Assert.Equal(expectedError.Location, actualDiagnostics?.Location);
|
Assert.Equal(expectedError.Location, actualDiagnostics?.Location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,81 +1,79 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MapTo.Extensions;
|
using MapTo.Extensions;
|
||||||
using MapTo.Sources;
|
using MapTo.Sources;
|
||||||
using MapTo.Tests.Extensions;
|
using MapTo.Tests.Extensions;
|
||||||
using MapTo.Tests.Infrastructure;
|
using MapTo.Tests.Infrastructure;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using static MapTo.Tests.Common;
|
using static MapTo.Tests.Common;
|
||||||
|
|
||||||
namespace MapTo.Tests
|
namespace MapTo.Tests
|
||||||
{
|
{
|
||||||
public class IgnorePropertyAttributeTests
|
public class IgnorePropertyAttributeTests
|
||||||
{
|
{
|
||||||
/*
|
[Fact]
|
||||||
[Fact]
|
public void VerifyIgnorePropertyAttribute()
|
||||||
public void VerifyIgnorePropertyAttribute()
|
{
|
||||||
{
|
// Arrange
|
||||||
// Arrange
|
const string source = "";
|
||||||
const string source = "";
|
var expectedAttribute = $@"
|
||||||
var expectedAttribute = $@"
|
{Constants.GeneratedFilesHeader}
|
||||||
{Constants.GeneratedFilesHeader}
|
using System;
|
||||||
using System;
|
|
||||||
|
namespace MapTo
|
||||||
namespace MapTo
|
{{
|
||||||
{{
|
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||||
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
public sealed class IgnorePropertyAttribute : Attribute {{ }}
|
||||||
public sealed class IgnorePropertyAttribute : Attribute {{ }}
|
}}
|
||||||
}}
|
".Trim();
|
||||||
".Trim();
|
|
||||||
|
// Act
|
||||||
// Act
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
// Assert
|
||||||
// Assert
|
diagnostics.ShouldBeSuccessful();
|
||||||
diagnostics.ShouldBeSuccessful();
|
compilation.SyntaxTrees.ShouldContainSource(IgnorePropertyAttributeSource.AttributeName, expectedAttribute);
|
||||||
compilation.SyntaxTrees.ShouldContainSource(IgnorePropertyAttributeSource.AttributeName, expectedAttribute);
|
}
|
||||||
}
|
|
||||||
*/
|
[Fact]
|
||||||
|
public void When_IgnorePropertyAttributeIsSpecified_Should_NotGenerateMappingsForThatProperty()
|
||||||
[Fact]
|
{
|
||||||
public void When_IgnorePropertyAttributeIsSpecified_Should_NotGenerateMappingsForThatProperty()
|
// Arrange
|
||||||
{
|
var source = GetSourceText(new SourceGeneratorOptions(
|
||||||
// Arrange
|
true,
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
PropertyBuilder: builder =>
|
||||||
true,
|
{
|
||||||
PropertyBuilder: builder =>
|
builder
|
||||||
{
|
.WriteLine("[IgnoreProperty]")
|
||||||
builder
|
.WriteLine("public int Prop4 { get; set; }");
|
||||||
.WriteLine("[IgnoreProperty]")
|
},
|
||||||
.WriteLine("public int Prop4 { get; set; }");
|
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
||||||
},
|
|
||||||
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
var expectedResult = @"
|
||||||
|
partial class Foo
|
||||||
var expectedResult = @"
|
{
|
||||||
partial class Foo
|
public Foo(Test.Models.Baz baz)
|
||||||
{
|
: this(new MappingContext(), baz) { }
|
||||||
public Foo(Test.Models.Baz baz)
|
|
||||||
: this(new MappingContext(), baz) { }
|
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
||||||
|
{
|
||||||
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
{
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
context.Register(baz, this);
|
||||||
|
|
||||||
context.Register(baz, this);
|
Prop1 = baz.Prop1;
|
||||||
|
Prop2 = baz.Prop2;
|
||||||
Prop1 = baz.Prop1;
|
Prop3 = baz.Prop3;
|
||||||
Prop2 = baz.Prop2;
|
}
|
||||||
Prop3 = baz.Prop3;
|
".Trim();
|
||||||
}
|
|
||||||
".Trim();
|
// Act
|
||||||
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
// Assert
|
||||||
|
diagnostics.ShouldBeSuccessful();
|
||||||
// Assert
|
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||||
diagnostics.ShouldBeSuccessful();
|
}
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,66 +1,64 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics;
|
using System.Linq;
|
||||||
using System.Linq;
|
using MapTo.Tests.Extensions;
|
||||||
using System.Threading;
|
using Microsoft.CodeAnalysis;
|
||||||
using MapTo.Tests.Extensions;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
namespace MapTo.Tests.Infrastructure
|
||||||
|
{
|
||||||
namespace MapTo.Tests.Infrastructure
|
internal static class CSharpGenerator
|
||||||
{
|
{
|
||||||
internal static class CSharpGenerator
|
internal static (Compilation compilation, ImmutableArray<Diagnostic> diagnostics) GetOutputCompilation(
|
||||||
{
|
string source,
|
||||||
internal static (Compilation compilation, ImmutableArray<Diagnostic> diagnostics) GetOutputCompilation(
|
bool assertCompilation = false,
|
||||||
string source,
|
IDictionary<string, string>? analyzerConfigOptions = null,
|
||||||
bool assertCompilation = false,
|
NullableContextOptions nullableContextOptions = NullableContextOptions.Disable,
|
||||||
IDictionary<string, string>? analyzerConfigOptions = null,
|
LanguageVersion languageVersion = LanguageVersion.CSharp7_3) =>
|
||||||
NullableContextOptions nullableContextOptions = NullableContextOptions.Disable,
|
GetOutputCompilation(
|
||||||
LanguageVersion languageVersion = LanguageVersion.CSharp7_3) =>
|
new[] { source },
|
||||||
GetOutputCompilation(
|
assertCompilation,
|
||||||
new[] { source },
|
analyzerConfigOptions,
|
||||||
assertCompilation,
|
nullableContextOptions,
|
||||||
analyzerConfigOptions,
|
languageVersion);
|
||||||
nullableContextOptions,
|
|
||||||
languageVersion);
|
internal static (Compilation compilation, ImmutableArray<Diagnostic> diagnostics) GetOutputCompilation(
|
||||||
|
IEnumerable<string> sources,
|
||||||
internal static (Compilation compilation, ImmutableArray<Diagnostic> diagnostics) GetOutputCompilation(
|
bool assertCompilation = false,
|
||||||
IEnumerable<string> sources,
|
IDictionary<string, string>? analyzerConfigOptions = null,
|
||||||
bool assertCompilation = false,
|
NullableContextOptions nullableContextOptions = NullableContextOptions.Disable,
|
||||||
IDictionary<string, string>? analyzerConfigOptions = null,
|
LanguageVersion languageVersion = LanguageVersion.CSharp7_3)
|
||||||
NullableContextOptions nullableContextOptions = NullableContextOptions.Disable,
|
{
|
||||||
LanguageVersion languageVersion = LanguageVersion.CSharp7_3)
|
var references = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
{
|
.Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location))
|
||||||
var references = AppDomain.CurrentDomain.GetAssemblies()
|
.Select(a => MetadataReference.CreateFromFile(a.Location))
|
||||||
.Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location))
|
.ToList();
|
||||||
.Select(a => MetadataReference.CreateFromFile(a.Location))
|
|
||||||
.ToList();
|
var compilation = CSharpCompilation.Create(
|
||||||
|
$"{typeof(CSharpGenerator).Assembly.GetName().Name}.Dynamic",
|
||||||
var compilation = CSharpCompilation.Create(
|
sources.Select((source, index) => CSharpSyntaxTree.ParseText(source, path: $"Test{index:00}.g.cs", options: new CSharpParseOptions(languageVersion))),
|
||||||
$"{typeof(CSharpGenerator).Assembly.GetName().Name}.Dynamic",
|
references,
|
||||||
sources.Select((source, index) => CSharpSyntaxTree.ParseText(source, path: $"Test{index:00}.g.cs", options: new CSharpParseOptions(languageVersion))),
|
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, nullableContextOptions: nullableContextOptions));
|
||||||
references,
|
|
||||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, nullableContextOptions: nullableContextOptions));
|
if (assertCompilation)
|
||||||
|
{
|
||||||
if (assertCompilation)
|
// NB: fail tests when the injected program isn't valid _before_ running generators
|
||||||
{
|
compilation.GetDiagnostics().ShouldBeSuccessful();
|
||||||
// NB: fail tests when the injected program isn't valid _before_ running generators
|
}
|
||||||
compilation.GetDiagnostics().ShouldBeSuccessful();
|
|
||||||
}
|
var driver = CSharpGeneratorDriver.Create(
|
||||||
|
new[] { new MapToGenerator() },
|
||||||
var driver = CSharpGeneratorDriver.Create(
|
optionsProvider: new TestAnalyzerConfigOptionsProvider(analyzerConfigOptions),
|
||||||
new List<ISourceGenerator>() { new MapToGenerator()},
|
parseOptions: new CSharpParseOptions(languageVersion)
|
||||||
optionsProvider: new TestAnalyzerConfigOptionsProvider(analyzerConfigOptions),
|
);
|
||||||
parseOptions: new CSharpParseOptions(languageVersion)
|
|
||||||
);
|
driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics);
|
||||||
|
|
||||||
driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics);
|
generateDiagnostics.ShouldBeSuccessful(ignoreDiagnosticsIds: new[] { "MT" });
|
||||||
|
outputCompilation.GetDiagnostics().ShouldBeSuccessful(outputCompilation);
|
||||||
generateDiagnostics.ShouldBeSuccessful(ignoreDiagnosticsIds: new[] { "MT" });
|
|
||||||
outputCompilation.GetDiagnostics().ShouldBeSuccessful(outputCompilation);
|
return (outputCompilation, generateDiagnostics);
|
||||||
|
}
|
||||||
return (outputCompilation, generateDiagnostics);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,19 +1,19 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace MapTo.Tests.Infrastructure
|
namespace MapTo.Tests.Infrastructure
|
||||||
{
|
{
|
||||||
internal sealed class TestAnalyzerConfigOptions : AnalyzerConfigOptions
|
internal sealed class TestAnalyzerConfigOptions : AnalyzerConfigOptions
|
||||||
{
|
{
|
||||||
private readonly ImmutableDictionary<string, string> _backing;
|
private readonly ImmutableDictionary<string, string> _backing;
|
||||||
|
|
||||||
public TestAnalyzerConfigOptions(IDictionary<string, string>? properties)
|
public TestAnalyzerConfigOptions(IDictionary<string, string>? properties)
|
||||||
{
|
{
|
||||||
_backing = properties?.ToImmutableDictionary(KeyComparer) ?? ImmutableDictionary.Create<string, string>(KeyComparer);
|
_backing = properties?.ToImmutableDictionary(KeyComparer) ?? ImmutableDictionary.Create<string, string>(KeyComparer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool TryGetValue(string key, out string? value) => _backing.TryGetValue(key, out value);
|
public override bool TryGetValue(string key, out string? value) => _backing.TryGetValue(key, out value);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,24 +1,24 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace MapTo.Tests.Infrastructure
|
namespace MapTo.Tests.Infrastructure
|
||||||
{
|
{
|
||||||
internal sealed class TestAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider
|
internal sealed class TestAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider
|
||||||
{
|
{
|
||||||
public TestAnalyzerConfigOptionsProvider(IDictionary<string, string>? options)
|
public TestAnalyzerConfigOptionsProvider(IDictionary<string, string>? options)
|
||||||
{
|
{
|
||||||
GlobalOptions = new TestAnalyzerConfigOptions(options);
|
GlobalOptions = new TestAnalyzerConfigOptions(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override AnalyzerConfigOptions GlobalOptions { get; }
|
public override AnalyzerConfigOptions GlobalOptions { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => throw new NotImplementedException();
|
public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => throw new NotImplementedException();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => throw new NotImplementedException();
|
public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,202 +1,202 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MapTo.Sources;
|
using MapTo.Sources;
|
||||||
using MapTo.Tests.Extensions;
|
using MapTo.Tests.Extensions;
|
||||||
using MapTo.Tests.Infrastructure;
|
using MapTo.Tests.Infrastructure;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using static MapTo.Tests.Common;
|
using static MapTo.Tests.Common;
|
||||||
|
|
||||||
namespace MapTo.Tests
|
namespace MapTo.Tests
|
||||||
{
|
{
|
||||||
public class MapPropertyTests
|
public class MapPropertyTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(NullableContextOptions.Disable)]
|
[InlineData(NullableContextOptions.Disable)]
|
||||||
[InlineData(NullableContextOptions.Enable)]
|
[InlineData(NullableContextOptions.Enable)]
|
||||||
public void VerifyMapPropertyAttribute(NullableContextOptions nullableContextOptions)
|
public void VerifyMapPropertyAttribute(NullableContextOptions nullableContextOptions)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
const string source = "";
|
const string source = "";
|
||||||
var nullableSyntax = nullableContextOptions == NullableContextOptions.Enable ? "?" : string.Empty;
|
var nullableSyntax = nullableContextOptions == NullableContextOptions.Enable ? "?" : string.Empty;
|
||||||
var languageVersion = nullableContextOptions == NullableContextOptions.Enable ? LanguageVersion.CSharp8 : LanguageVersion.CSharp7_3;
|
var languageVersion = nullableContextOptions == NullableContextOptions.Enable ? LanguageVersion.CSharp8 : LanguageVersion.CSharp7_3;
|
||||||
var expectedInterface = $@"
|
var expectedInterface = $@"
|
||||||
{Constants.GeneratedFilesHeader}
|
{Constants.GeneratedFilesHeader}
|
||||||
{(nullableContextOptions == NullableContextOptions.Enable ? $"#nullable enable{Environment.NewLine}" : string.Empty)}
|
{(nullableContextOptions == NullableContextOptions.Enable ? $"#nullable enable{Environment.NewLine}" : string.Empty)}
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace MapTo
|
namespace MapTo
|
||||||
{{
|
{{
|
||||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)]
|
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)]
|
||||||
public sealed class MapPropertyAttribute : Attribute
|
public sealed class MapPropertyAttribute : Attribute
|
||||||
{{
|
{{
|
||||||
public string{nullableSyntax} SourcePropertyName {{ get; set; }}
|
public string{nullableSyntax} SourcePropertyName {{ get; set; }}
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: nullableContextOptions, languageVersion: languageVersion);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: nullableContextOptions, languageVersion: languageVersion);
|
||||||
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.ShouldContainSource(MapPropertyAttributeSource.AttributeName, expectedInterface);
|
compilation.SyntaxTrees.ShouldContainSource(MapPropertyAttributeSource.AttributeName, expectedInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void When_MapPropertyFound_Should_UseItToMapToSourceProperty()
|
public void When_MapPropertyFound_Should_UseItToMapToSourceProperty()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
var source = GetSourceText(new SourceGeneratorOptions(
|
||||||
true,
|
true,
|
||||||
PropertyBuilder: builder =>
|
PropertyBuilder: builder =>
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine("[MapProperty(SourcePropertyName = nameof(Baz.Prop3))]")
|
.WriteLine("[MapProperty(SourcePropertyName = nameof(Baz.Prop3))]")
|
||||||
.WriteLine("public int Prop4 { get; set; }");
|
.WriteLine("public int Prop4 { get; set; }");
|
||||||
},
|
},
|
||||||
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
||||||
|
|
||||||
var expectedResult = @"
|
var expectedResult = @"
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Test.Models.Baz baz)
|
public Foo(Test.Models.Baz baz)
|
||||||
: this(new MappingContext(), baz) { }
|
: this(new MappingContext(), baz) { }
|
||||||
|
|
||||||
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
||||||
{
|
{
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
context.Register(baz, this);
|
context.Register(baz, this);
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
Prop1 = baz.Prop1;
|
||||||
Prop2 = baz.Prop2;
|
Prop2 = baz.Prop2;
|
||||||
Prop3 = baz.Prop3;
|
Prop3 = baz.Prop3;
|
||||||
Prop4 = baz.Prop3;
|
Prop4 = baz.Prop3;
|
||||||
}
|
}
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(MapPropertyWithImplicitConversionFoundData))]
|
[MemberData(nameof(MapPropertyWithImplicitConversionFoundData))]
|
||||||
public void When_MapPropertyWithImplicitConversionFound_Should_UseItToMapToSourceProperty(string source, string expectedResult, LanguageVersion languageVersion)
|
public void When_MapPropertyWithImplicitConversionFound_Should_UseItToMapToSourceProperty(string source, string expectedResult, LanguageVersion languageVersion)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
source = source.Trim();
|
source = source.Trim();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: languageVersion);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: languageVersion);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<object[]> MapPropertyWithImplicitConversionFoundData => new List<object[]>
|
public static IEnumerable<object[]> MapPropertyWithImplicitConversionFoundData => new List<object[]>
|
||||||
{
|
{
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
@"
|
@"
|
||||||
namespace Test
|
namespace Test
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
public class InnerClass { public int Prop1 { get; set; } }
|
public class InnerClass { public int Prop1 { get; set; } }
|
||||||
public class OuterClass
|
public class OuterClass
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public List<InnerClass> InnerProp { get; set; }
|
public List<InnerClass> InnerProp { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Test.Models
|
namespace Test.Models
|
||||||
{
|
{
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
[MapFrom(typeof(Test.InnerClass))]
|
[MapFrom(typeof(Test.InnerClass))]
|
||||||
public partial class InnerClass { public int Prop1 { get; set; } }
|
public partial class InnerClass { public int Prop1 { get; set; } }
|
||||||
|
|
||||||
[MapFrom(typeof(Test.OuterClass))]
|
[MapFrom(typeof(Test.OuterClass))]
|
||||||
public partial class OuterClass
|
public partial class OuterClass
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public IReadOnlyList<InnerClass> InnerProp { get; set; }
|
public IReadOnlyList<InnerClass> InnerProp { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
@"
|
@"
|
||||||
private protected OuterClass(MappingContext context, Test.OuterClass outerClass)
|
private protected OuterClass(MappingContext context, Test.OuterClass outerClass)
|
||||||
{
|
{
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (outerClass == null) throw new ArgumentNullException(nameof(outerClass));
|
if (outerClass == null) throw new ArgumentNullException(nameof(outerClass));
|
||||||
|
|
||||||
context.Register(outerClass, this);
|
context.Register(outerClass, this);
|
||||||
|
|
||||||
Id = outerClass.Id;
|
Id = outerClass.Id;
|
||||||
InnerProp = outerClass.InnerProp.Select(context.MapFromWithContext<Test.InnerClass, InnerClass>).ToList();
|
InnerProp = outerClass.InnerProp.Select(context.MapFromWithContext<Test.InnerClass, InnerClass>).ToList();
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
LanguageVersion.CSharp7_3
|
LanguageVersion.CSharp7_3
|
||||||
},
|
},
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
@"
|
@"
|
||||||
namespace Test
|
namespace Test
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
public class InnerClass
|
public class InnerClass
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OuterClass
|
public class OuterClass
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public List<InnerClass> InnerClasses { get; set; }
|
public List<InnerClass> InnerClasses { get; set; }
|
||||||
public DateTime? SomeDate { get; set; }
|
public DateTime? SomeDate { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Test.Models
|
namespace Test.Models
|
||||||
{
|
{
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
[MapFrom(typeof(Test.InnerClass))]
|
[MapFrom(typeof(Test.InnerClass))]
|
||||||
public partial record InnerClass(int Id, string Name);
|
public partial record InnerClass(int Id, string Name);
|
||||||
|
|
||||||
[MapFrom(typeof(Test.OuterClass))]
|
[MapFrom(typeof(Test.OuterClass))]
|
||||||
public partial record OuterClass(int Id, DateTime? SomeDate, IReadOnlyList<InnerClass> InnerClasses);
|
public partial record OuterClass(int Id, DateTime? SomeDate, IReadOnlyList<InnerClass> InnerClasses);
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
@"
|
@"
|
||||||
private protected OuterClass(MappingContext context, Test.OuterClass outerClass)
|
private protected OuterClass(MappingContext context, Test.OuterClass outerClass)
|
||||||
: this(Id: outerClass.Id, SomeDate: outerClass.SomeDate, InnerClasses: outerClass.InnerClasses.Select(context.MapFromWithContext<Test.InnerClass, InnerClass>).ToList())
|
: this(Id: outerClass.Id, SomeDate: outerClass.SomeDate, InnerClasses: outerClass.InnerClasses.Select(context.MapFromWithContext<Test.InnerClass, InnerClass>).ToList())
|
||||||
{
|
{
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (outerClass == null) throw new ArgumentNullException(nameof(outerClass));
|
if (outerClass == null) throw new ArgumentNullException(nameof(outerClass));
|
||||||
|
|
||||||
context.Register(outerClass, this);
|
context.Register(outerClass, this);
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
LanguageVersion.CSharp9
|
LanguageVersion.CSharp9
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,42 +1,37 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
<RootNamespace>MapTo.Tests</RootNamespace>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.2.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
|
||||||
<PackageReference Update="Nerdbank.GitVersioning">
|
|
||||||
<Version>3.5.109</Version>
|
|
||||||
</PackageReference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||||
<PackageReference Include="Shouldly" Version="4.0.3" />
|
<PackageReference Include="Shouldly" Version="4.0.3" />
|
||||||
<PackageReference Include="xunit" Version="2.4.2" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
<PackageReference Include="coverlet.collector" Version="3.0.3">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\BlueWest.MapTo\BlueWest.MapTo.csproj" />
|
<ProjectReference Include="..\..\src\MapTo\MapTo.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
File diff suppressed because it is too large
Load Diff
|
@ -1,283 +1,283 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MapTo.Sources;
|
using MapTo.Sources;
|
||||||
using MapTo.Tests.Extensions;
|
using MapTo.Tests.Extensions;
|
||||||
using MapTo.Tests.Infrastructure;
|
using MapTo.Tests.Infrastructure;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using static MapTo.Tests.Common;
|
using static MapTo.Tests.Common;
|
||||||
|
|
||||||
namespace MapTo.Tests
|
namespace MapTo.Tests
|
||||||
{
|
{
|
||||||
public class MapTypeConverterTests
|
public class MapTypeConverterTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void VerifyMapTypeConverterAttribute()
|
public void VerifyMapTypeConverterAttribute()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
const string source = "";
|
const string source = "";
|
||||||
var expectedInterface = $@"
|
var expectedInterface = $@"
|
||||||
{Constants.GeneratedFilesHeader}
|
{Constants.GeneratedFilesHeader}
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace MapTo
|
namespace MapTo
|
||||||
{{
|
{{
|
||||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]
|
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]
|
||||||
public sealed class MapTypeConverterAttribute : Attribute
|
public sealed class MapTypeConverterAttribute : Attribute
|
||||||
{{
|
{{
|
||||||
public MapTypeConverterAttribute(Type converter, object[] converterParameters = null)
|
public MapTypeConverterAttribute(Type converter, object[] converterParameters = null)
|
||||||
{{
|
{{
|
||||||
Converter = converter;
|
Converter = converter;
|
||||||
ConverterParameters = converterParameters;
|
ConverterParameters = converterParameters;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
public Type Converter {{ get; }}
|
public Type Converter {{ get; }}
|
||||||
|
|
||||||
public object[] ConverterParameters {{ get; }}
|
public object[] ConverterParameters {{ get; }}
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
|
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void VerifyMapTypeConverterAttributeWithNullableOptionOn()
|
public void VerifyMapTypeConverterAttributeWithNullableOptionOn()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
const string source = "";
|
const string source = "";
|
||||||
var expectedInterface = $@"
|
var expectedInterface = $@"
|
||||||
{Constants.GeneratedFilesHeader}
|
{Constants.GeneratedFilesHeader}
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace MapTo
|
namespace MapTo
|
||||||
{{
|
{{
|
||||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]
|
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]
|
||||||
public sealed class MapTypeConverterAttribute : Attribute
|
public sealed class MapTypeConverterAttribute : Attribute
|
||||||
{{
|
{{
|
||||||
public MapTypeConverterAttribute(Type converter, object[]? converterParameters = null)
|
public MapTypeConverterAttribute(Type converter, object[]? converterParameters = null)
|
||||||
{{
|
{{
|
||||||
Converter = converter;
|
Converter = converter;
|
||||||
ConverterParameters = converterParameters;
|
ConverterParameters = converterParameters;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
public Type Converter {{ get; }}
|
public Type Converter {{ get; }}
|
||||||
|
|
||||||
public object[]? ConverterParameters {{ get; }}
|
public object[]? ConverterParameters {{ get; }}
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable, languageVersion: LanguageVersion.CSharp8);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable, languageVersion: LanguageVersion.CSharp8);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
|
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void VerifyTypeConverterInterface()
|
public void VerifyTypeConverterInterface()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
const string source = "";
|
const string source = "";
|
||||||
var expectedInterface = $@"
|
var expectedInterface = $@"
|
||||||
{Constants.GeneratedFilesHeader}
|
{Constants.GeneratedFilesHeader}
|
||||||
|
|
||||||
namespace MapTo
|
namespace MapTo
|
||||||
{{
|
{{
|
||||||
public interface ITypeConverter<in TSource, out TDestination>
|
public interface ITypeConverter<in TSource, out TDestination>
|
||||||
{{
|
{{
|
||||||
TDestination Convert(TSource source, object[] converterParameters);
|
TDestination Convert(TSource source, object[] converterParameters);
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
|
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void VerifyTypeConverterInterfaceWithNullableOptionOn()
|
public void VerifyTypeConverterInterfaceWithNullableOptionOn()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
const string source = "";
|
const string source = "";
|
||||||
var expectedInterface = $@"
|
var expectedInterface = $@"
|
||||||
{Constants.GeneratedFilesHeader}
|
{Constants.GeneratedFilesHeader}
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
namespace MapTo
|
namespace MapTo
|
||||||
{{
|
{{
|
||||||
public interface ITypeConverter<in TSource, out TDestination>
|
public interface ITypeConverter<in TSource, out TDestination>
|
||||||
{{
|
{{
|
||||||
TDestination Convert(TSource source, object[]? converterParameters);
|
TDestination Convert(TSource source, object[]? converterParameters);
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable, languageVersion: LanguageVersion.CSharp8);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable, languageVersion: LanguageVersion.CSharp8);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
|
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterAndItsParametersToAssignProperties()
|
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterAndItsParametersToAssignProperties()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
var source = GetSourceText(new SourceGeneratorOptions(
|
||||||
true,
|
true,
|
||||||
PropertyBuilder: builder =>
|
PropertyBuilder: builder =>
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine("[MapTypeConverter(typeof(Prop4Converter), new object[]{\"G\", 'C', 10})]")
|
.WriteLine("[MapTypeConverter(typeof(Prop4Converter), new object[]{\"G\", 'C', 10})]")
|
||||||
.WriteLine("public string Prop4 { get; set; }");
|
.WriteLine("public string Prop4 { get; set; }");
|
||||||
},
|
},
|
||||||
SourcePropertyBuilder: builder => builder.WriteLine("public long Prop4 { get; set; }")));
|
SourcePropertyBuilder: builder => builder.WriteLine("public long Prop4 { get; set; }")));
|
||||||
|
|
||||||
source += @"
|
source += @"
|
||||||
namespace Test
|
namespace Test
|
||||||
{
|
{
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
|
||||||
public class Prop4Converter: ITypeConverter<long, string>
|
public class Prop4Converter: ITypeConverter<long, string>
|
||||||
{
|
{
|
||||||
public string Convert(long source, object[] converterParameters) => source.ToString(converterParameters[0] as string);
|
public string Convert(long source, object[] converterParameters) => source.ToString(converterParameters[0] as string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
";
|
";
|
||||||
|
|
||||||
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, new object[] { \"G\", 'C', 10 });";
|
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, new object[] { \"G\", 'C', 10 });";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedSyntax);
|
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedSyntax);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterToAssignProperties()
|
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterToAssignProperties()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
var source = GetSourceText(new SourceGeneratorOptions(
|
||||||
true,
|
true,
|
||||||
PropertyBuilder: builder =>
|
PropertyBuilder: builder =>
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine("[MapTypeConverter(typeof(Prop4Converter))]")
|
.WriteLine("[MapTypeConverter(typeof(Prop4Converter))]")
|
||||||
.WriteLine("public long Prop4 { get; set; }");
|
.WriteLine("public long Prop4 { get; set; }");
|
||||||
},
|
},
|
||||||
SourcePropertyBuilder: builder => builder.WriteLine("public string Prop4 { get; set; }")));
|
SourcePropertyBuilder: builder => builder.WriteLine("public string Prop4 { get; set; }")));
|
||||||
|
|
||||||
source += @"
|
source += @"
|
||||||
namespace Test
|
namespace Test
|
||||||
{
|
{
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
|
||||||
public class Prop4Converter: ITypeConverter<string, long>
|
public class Prop4Converter: ITypeConverter<string, long>
|
||||||
{
|
{
|
||||||
public long Convert(string source, object[] converterParameters) => long.Parse(source);
|
public long Convert(string source, object[] converterParameters) => long.Parse(source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
";
|
";
|
||||||
|
|
||||||
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, null);";
|
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, null);";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedSyntax);
|
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedSyntax);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void When_FoundMatchingPropertyNameWithDifferentImplicitlyConvertibleType_Should_GenerateTheProperty()
|
public void When_FoundMatchingPropertyNameWithDifferentImplicitlyConvertibleType_Should_GenerateTheProperty()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
var source = GetSourceText(new SourceGeneratorOptions(
|
||||||
true,
|
true,
|
||||||
PropertyBuilder: builder => { builder.WriteLine("public long Prop4 { get; set; }"); },
|
PropertyBuilder: builder => { builder.WriteLine("public long Prop4 { get; set; }"); },
|
||||||
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
||||||
|
|
||||||
var expectedResult = @"
|
var expectedResult = @"
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Test.Models.Baz baz)
|
public Foo(Test.Models.Baz baz)
|
||||||
: this(new MappingContext(), baz) { }
|
: this(new MappingContext(), baz) { }
|
||||||
|
|
||||||
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
||||||
{
|
{
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
context.Register(baz, this);
|
context.Register(baz, this);
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
Prop1 = baz.Prop1;
|
||||||
Prop2 = baz.Prop2;
|
Prop2 = baz.Prop2;
|
||||||
Prop3 = baz.Prop3;
|
Prop3 = baz.Prop3;
|
||||||
Prop4 = baz.Prop4;
|
Prop4 = baz.Prop4;
|
||||||
}
|
}
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void When_FoundMatchingPropertyNameWithIncorrectConverterType_ShouldReportError()
|
public void When_FoundMatchingPropertyNameWithIncorrectConverterType_ShouldReportError()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
var source = GetSourceText(new SourceGeneratorOptions(
|
||||||
true,
|
true,
|
||||||
PropertyBuilder: builder =>
|
PropertyBuilder: builder =>
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WriteLine("[IgnoreProperty]")
|
.WriteLine("[IgnoreProperty]")
|
||||||
.WriteLine("public long IgnoreMe { get; set; }")
|
.WriteLine("public long IgnoreMe { get; set; }")
|
||||||
.WriteLine("[MapTypeConverter(typeof(Prop4Converter))]")
|
.WriteLine("[MapTypeConverter(typeof(Prop4Converter))]")
|
||||||
.WriteLine("public long Prop4 { get; set; }");
|
.WriteLine("public long Prop4 { get; set; }");
|
||||||
},
|
},
|
||||||
SourcePropertyBuilder: builder => builder.WriteLine("public string Prop4 { get; set; }")));
|
SourcePropertyBuilder: builder => builder.WriteLine("public string Prop4 { get; set; }")));
|
||||||
|
|
||||||
source += @"
|
source += @"
|
||||||
namespace Test
|
namespace Test
|
||||||
{
|
{
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
|
||||||
public class Prop4Converter: ITypeConverter<string, int>
|
public class Prop4Converter: ITypeConverter<string, int>
|
||||||
{
|
{
|
||||||
public int Convert(string source, object[] converterParameters) => int.Parse(source);
|
public int Convert(string source, object[] converterParameters) => int.Parse(source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
";
|
";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var expectedError = DiagnosticsFactory.InvalidTypeConverterGenericTypesError(GetSourcePropertySymbol("Prop4", compilation), GetSourcePropertySymbol("Prop4", compilation, "Baz"));
|
var expectedError = DiagnosticsFactory.InvalidTypeConverterGenericTypesError(GetSourcePropertySymbol("Prop4", compilation), GetSourcePropertySymbol("Prop4", compilation, "Baz"));
|
||||||
diagnostics.ShouldNotBeSuccessful(expectedError);
|
diagnostics.ShouldNotBeSuccessful(expectedError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,102 +1,102 @@
|
||||||
using MapTo.Sources;
|
using MapTo.Sources;
|
||||||
using MapTo.Tests.Extensions;
|
using MapTo.Tests.Extensions;
|
||||||
using MapTo.Tests.Infrastructure;
|
using MapTo.Tests.Infrastructure;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using static MapTo.Tests.Common;
|
using static MapTo.Tests.Common;
|
||||||
|
|
||||||
namespace MapTo.Tests
|
namespace MapTo.Tests
|
||||||
{
|
{
|
||||||
public class MappingContextTests
|
public class MappingContextTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void VerifyMappingContextSource()
|
public void VerifyMappingContextSource()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
const string source = "";
|
const string source = "";
|
||||||
var expected = @"
|
var expected = @"
|
||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace MapTo
|
namespace MapTo
|
||||||
{
|
{
|
||||||
internal sealed class MappingContext
|
internal sealed class MappingContext
|
||||||
{
|
{
|
||||||
private readonly Dictionary<object, object> _cache;
|
private readonly Dictionary<object, object> _cache;
|
||||||
|
|
||||||
internal MappingContext()
|
internal MappingContext()
|
||||||
{
|
{
|
||||||
_cache = new Dictionary<object, object>(1);
|
_cache = new Dictionary<object, object>(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static TMapped Create<TOriginal, TMapped>(TOriginal original)
|
internal static TMapped Create<TOriginal, TMapped>(TOriginal original)
|
||||||
{
|
{
|
||||||
if (original == null) throw new ArgumentNullException(nameof(original));
|
if (original == null) throw new ArgumentNullException(nameof(original));
|
||||||
|
|
||||||
var context = new MappingContext();
|
var context = new MappingContext();
|
||||||
var mapped = context.MapFromWithContext<TOriginal, TMapped>(original);
|
var mapped = context.MapFromWithContext<TOriginal, TMapped>(original);
|
||||||
|
|
||||||
if (mapped == null)
|
if (mapped == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapped;
|
return mapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal TMapped MapFromWithContext<TOriginal, TMapped>(TOriginal original)
|
internal TMapped MapFromWithContext<TOriginal, TMapped>(TOriginal original)
|
||||||
{
|
{
|
||||||
if (original == null)
|
if (original == null)
|
||||||
{
|
{
|
||||||
return default(TMapped);
|
return default(TMapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryGetValue<TOriginal, TMapped>(original, out var mapped))
|
if (!TryGetValue<TOriginal, TMapped>(original, out var mapped))
|
||||||
{
|
{
|
||||||
var instance = Activator.CreateInstance(typeof(TMapped), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { this, original }, null);
|
var instance = Activator.CreateInstance(typeof(TMapped), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { this, original }, null);
|
||||||
if (instance != null)
|
if (instance != null)
|
||||||
{
|
{
|
||||||
mapped = (TMapped)instance;
|
mapped = (TMapped)instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapped;
|
return mapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Register<TOriginal, TMapped>(TOriginal original, TMapped mapped)
|
internal void Register<TOriginal, TMapped>(TOriginal original, TMapped mapped)
|
||||||
{
|
{
|
||||||
if (original == null) throw new ArgumentNullException(nameof(original));
|
if (original == null) throw new ArgumentNullException(nameof(original));
|
||||||
if (mapped == null) throw new ArgumentNullException(nameof(mapped));
|
if (mapped == null) throw new ArgumentNullException(nameof(mapped));
|
||||||
|
|
||||||
if (!_cache.ContainsKey(original))
|
if (!_cache.ContainsKey(original))
|
||||||
{
|
{
|
||||||
_cache.Add(original, mapped);
|
_cache.Add(original, mapped);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryGetValue<TOriginal, TMapped>(TOriginal original, out TMapped mapped)
|
private bool TryGetValue<TOriginal, TMapped>(TOriginal original, out TMapped mapped)
|
||||||
{
|
{
|
||||||
if (original != null && _cache.TryGetValue(original, out var value))
|
if (original != null && _cache.TryGetValue(original, out var value))
|
||||||
{
|
{
|
||||||
mapped = (TMapped)value;
|
mapped = (TMapped)value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapped = default(TMapped);
|
mapped = default(TMapped);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.ShouldContainSource(MappingContextSource.ClassName, expected);
|
compilation.SyntaxTrees.ShouldContainSource(MappingContextSource.ClassName, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,59 +0,0 @@
|
||||||
using System;
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
namespace BlueWest.Data
|
|
||||||
{
|
|
||||||
public enum FinanceSymbol
|
|
||||||
{
|
|
||||||
BTC_EUR,
|
|
||||||
BTC_BUSD,
|
|
||||||
BTC_USD,
|
|
||||||
BTC_USDT,
|
|
||||||
LTC_EUR,
|
|
||||||
LTC_BUSD,
|
|
||||||
LTC_USDT
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum FinanceTransactionType
|
|
||||||
{
|
|
||||||
Buy,
|
|
||||||
Sell
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonExtension]
|
|
||||||
[MapFrom(typeof(FinanceTransactionInsertDto))]
|
|
||||||
public partial struct FinanceTransaction
|
|
||||||
{
|
|
||||||
public readonly int Id;
|
|
||||||
public readonly int UserId;
|
|
||||||
public readonly FinanceTransactionType FinanceTransactionType;
|
|
||||||
public readonly FinanceSymbol FinanceSymbol;
|
|
||||||
public readonly double Amount; // To Buy
|
|
||||||
public readonly double Quantity; // Bought
|
|
||||||
public readonly double Fee;
|
|
||||||
public readonly DateTime DateTime;
|
|
||||||
|
|
||||||
|
|
||||||
public FinanceTransaction(int id, int userId, FinanceTransactionType financeTransactionType,
|
|
||||||
FinanceSymbol financeSymbol, double amount, double quantity, double fee, DateTime dateTime)
|
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
UserId = userId;
|
|
||||||
FinanceTransactionType = financeTransactionType;
|
|
||||||
FinanceSymbol = financeSymbol;
|
|
||||||
Amount = amount;
|
|
||||||
Quantity = quantity;
|
|
||||||
Fee = fee;
|
|
||||||
DateTime = dateTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace BlueWest.Data
|
|
||||||
{
|
|
||||||
|
|
||||||
public partial struct FinanceTransactionInsertDto
|
|
||||||
{
|
|
||||||
public readonly int UserId;
|
|
||||||
public readonly FinanceTransactionType FinanceTransactionType;
|
|
||||||
public readonly FinanceSymbol FinanceSymbol;
|
|
||||||
public readonly double Amount; // To Buy
|
|
||||||
public readonly double Quantity; // Bought
|
|
||||||
public readonly double Fee;
|
|
||||||
public readonly DateTime DateTime;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
namespace BlueWest.Data
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(FinanceTransaction))]
|
|
||||||
|
|
||||||
partial struct FinanceTransactionReadDto
|
|
||||||
{
|
|
||||||
public readonly int UserId;
|
|
||||||
public readonly FinanceTransactionType FinanceTransactionType;
|
|
||||||
public readonly FinanceSymbol FinanceSymbol;
|
|
||||||
public readonly double Amount; // To Buy
|
|
||||||
public readonly double Quantity; // Bought
|
|
||||||
public readonly double Fee;
|
|
||||||
public readonly DateTime DateTime;
|
|
||||||
|
|
||||||
public readonly string ReadData;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MapTo;
|
|
||||||
using TestConsoleApp.ViewModels;
|
|
||||||
|
|
||||||
namespace TestConsoleApp.Data.Models
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(CarReadDto))]
|
|
||||||
[UseUpdate]
|
|
||||||
partial class Car
|
|
||||||
{
|
|
||||||
public int Size { get; }
|
|
||||||
public int Id { get; }
|
|
||||||
|
|
||||||
public string Brand { get; }
|
|
||||||
|
|
||||||
public Car(int size, int id, string brand)
|
|
||||||
{
|
|
||||||
Size = size;
|
|
||||||
Id = id;
|
|
||||||
Brand = brand;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace TestConsoleApp.Data.Models
|
namespace TestConsoleApp.Data.Models
|
||||||
{
|
{
|
||||||
|
public class Employee
|
||||||
public class Employee
|
{
|
||||||
{
|
public int Id { get; set; }
|
||||||
public int Id { get; }
|
|
||||||
|
public string EmployeeCode { get; set; }
|
||||||
public string EmployeeCode { get; }
|
|
||||||
|
}
|
||||||
public Employee(int id, string employeeCode)
|
}
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
EmployeeCode = employeeCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using TestConsoleApp.ViewModels;
|
using TestConsoleApp.ViewModels;
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
|
||||||
namespace TestConsoleApp.Data.Models
|
namespace TestConsoleApp.Data.Models
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(MyStructViewModel))]
|
[MapFrom(typeof(MyStructViewModel))]
|
||||||
[UseUpdate]
|
public partial struct MyStruct
|
||||||
public partial struct MyStruct
|
{
|
||||||
{
|
public int SomeInt { get; set; }
|
||||||
public int SomeInt { get; set; }
|
|
||||||
|
public string ReadOnlyString { get; }
|
||||||
public string ReadOnlyString { get; }
|
|
||||||
|
public MyStruct(int someInt, string readOnlyString)
|
||||||
public MyStruct(int someInt, string readOnlyString)
|
{
|
||||||
{
|
SomeInt = someInt;
|
||||||
SomeInt = someInt;
|
ReadOnlyString = readOnlyString;
|
||||||
ReadOnlyString = readOnlyString;
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
using TestConsoleApp.ViewModels;
|
||||||
|
using MapTo;
|
||||||
|
|
||||||
|
namespace TestConsoleApp.Data.Models
|
||||||
|
{
|
||||||
|
[MapFrom(typeof(UserViewModel))]
|
||||||
|
public partial class User
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset RegisteredAt { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,44 +0,0 @@
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
|
|
||||||
namespace BlueWest.Data
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(UserUpdateDto))]
|
|
||||||
[JsonExtension]
|
|
||||||
public partial class User
|
|
||||||
{
|
|
||||||
public readonly int Id;
|
|
||||||
public string Name;
|
|
||||||
public string Address;
|
|
||||||
|
|
||||||
public string BTCAddress;
|
|
||||||
public string LTCAddress;
|
|
||||||
|
|
||||||
public double BTCAmount;
|
|
||||||
public double LTCAmount;
|
|
||||||
|
|
||||||
public readonly List<FinanceTransaction> FinanceTransactions;
|
|
||||||
|
|
||||||
public User(int id, string name, string address, string btcAddress, string ltcAddress, double btcAmount, double ltcAmount, List<FinanceTransaction> financeTransactions)
|
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
Name = name;
|
|
||||||
Address = address;
|
|
||||||
BTCAddress = btcAddress;
|
|
||||||
LTCAddress = ltcAddress;
|
|
||||||
BTCAmount = btcAmount;
|
|
||||||
LTCAmount = ltcAmount;
|
|
||||||
FinanceTransactions = financeTransactions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddTransaction(FinanceTransaction financeTransaction)
|
|
||||||
{
|
|
||||||
FinanceTransactions.Add(financeTransaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace BlueWest.Data
|
|
||||||
{
|
|
||||||
public class UserList
|
|
||||||
{
|
|
||||||
public List<User> Users;
|
|
||||||
|
|
||||||
public UserList(List<User> users)
|
|
||||||
{
|
|
||||||
Users = users;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Length => Users.Count;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
namespace BlueWest.Data
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(User))]
|
|
||||||
|
|
||||||
public partial class UserUpdateDto
|
|
||||||
{
|
|
||||||
public string Name;
|
|
||||||
public string Address;
|
|
||||||
|
|
||||||
public string BTCAddress;
|
|
||||||
public string LTCAddress;
|
|
||||||
|
|
||||||
public double BTCAmount;
|
|
||||||
public double LTCAmount;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +1,39 @@
|
||||||
using System;
|
using System;
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using TestConsoleApp.Data.Models;
|
using TestConsoleApp.Data.Models;
|
||||||
using TestConsoleApp.ViewModels;
|
using TestConsoleApp.ViewModels;
|
||||||
|
|
||||||
namespace TestConsoleApp
|
namespace TestConsoleApp
|
||||||
{
|
{
|
||||||
internal class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
private static void Main(string[] args)
|
private static void Main(string[] args)
|
||||||
{
|
{
|
||||||
//UserTest();
|
//UserTest();
|
||||||
|
|
||||||
// EmployeeManagerTest();
|
// EmployeeManagerTest();
|
||||||
Console.WriteLine("done");
|
Console.WriteLine("done");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmployeeManagerTest()
|
private static void EmployeeManagerTest()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
var employee1 = new Employee
|
||||||
var employee = new Employee(1, "hello");
|
{
|
||||||
|
Id = 101,
|
||||||
|
EmployeeCode = "E101",
|
||||||
|
};
|
||||||
|
|
||||||
}
|
var employee2 = new Employee
|
||||||
|
{
|
||||||
|
Id = 102,
|
||||||
|
EmployeeCode = "E102",
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,18 +1,18 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net471</TargetFramework>
|
<TargetFramework>net471</TargetFramework>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\BlueWest.MapTo\BlueWest.MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
<ProjectReference Include="..\..\src\MapTo\MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Import Project="..\..\src\BlueWest.MapTo\MapTo.props" />
|
<Import Project="..\..\src\MapTo\MapTo.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<MapTo_ConstructorAccessModifier>Internal</MapTo_ConstructorAccessModifier>
|
<MapTo_ConstructorAccessModifier>Internal</MapTo_ConstructorAccessModifier>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MapTo;
|
|
||||||
using TestConsoleApp.Data.Models;
|
|
||||||
|
|
||||||
namespace TestConsoleApp.ViewModels
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(Car))]
|
|
||||||
partial class CarReadDto
|
|
||||||
{
|
|
||||||
public int Size { get; }
|
|
||||||
public string Brand { get; }
|
|
||||||
|
|
||||||
public CarReadDto(int size, string brand)
|
|
||||||
{
|
|
||||||
Size = size;
|
|
||||||
Brand = brand;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +1,13 @@
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using TestConsoleApp.Data.Models;
|
using TestConsoleApp.Data.Models;
|
||||||
|
|
||||||
namespace TestConsoleApp.ViewModels
|
namespace TestConsoleApp.ViewModels
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(Employee))]
|
[MapFrom(typeof(Employee))]
|
||||||
public partial class EmployeeViewModel
|
public partial class EmployeeViewModel
|
||||||
{
|
{
|
||||||
public int Id { get; }
|
public int Id { get; }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using TestConsoleApp.Data.Models;
|
using TestConsoleApp.Data.Models;
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
|
||||||
namespace TestConsoleApp.ViewModels
|
namespace TestConsoleApp.ViewModels
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(MyStruct))]
|
[MapFrom(typeof(MyStruct))]
|
||||||
|
|
||||||
public partial struct MyStructViewModel
|
public partial struct MyStructViewModel
|
||||||
{
|
{
|
||||||
public int SomeInt { get; set; }
|
public int SomeInt { get; set; }
|
||||||
|
|
||||||
public MyStructViewModel(int someInt)
|
public MyStructViewModel(int someInt)
|
||||||
{
|
{
|
||||||
SomeInt = someInt;
|
SomeInt = someInt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
using System;
|
||||||
|
using MapTo;
|
||||||
|
using TestConsoleApp.Data.Models;
|
||||||
|
|
||||||
|
namespace TestConsoleApp.ViewModels
|
||||||
|
{
|
||||||
|
[MapFrom(typeof(User))]
|
||||||
|
public partial class UserViewModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset RegisteredAt { get; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
|
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
|
||||||
"version": "0.9",
|
"version": "0.8",
|
||||||
"semVer1NumericIdentifierPadding": 1,
|
"semVer1NumericIdentifierPadding": 1,
|
||||||
"publicReleaseRefSpec": [
|
"publicReleaseRefSpec": [
|
||||||
"^refs/heads/master$",
|
"^refs/heads/master$",
|
||||||
|
|
Loading…
Reference in New Issue