From 795252e5df39ddd9cca7994c526da1f8d39ca7e8 Mon Sep 17 00:00:00 2001 From: Dennis Doomen Date: Tue, 22 Oct 2024 21:10:14 +0200 Subject: [PATCH] SAVEPOINT --- .editorconfig | 321 ++++++++++++++++ .gitattributes | 65 ++++ .github/FUNDING.yml | 2 + .github/dependabot.yml | 17 + .github/release.yml | 29 ++ .github/workflows/build.yml | 72 ++++ .gitignore | 354 ++++-------------- .idea/.idea.reflectorator/.idea/.gitignore | 13 + .../.idea.reflectorator/.idea/indexLayout.xml | 8 + .idea/.idea.reflectorator/.idea/vcs.xml | 6 + .nuke/build.schema.json | 125 +++++++ .nuke/parameters.json | 4 + Directory.Build.props | 43 +++ GitVersion.yml | 19 + README.md | 24 ++ Reflectify.sln | 32 ++ Reflectify.sln.DotSettings | 193 ++++++++++ build.cmd | 7 + build.ps1 | 74 ++++ build.sh | 67 ++++ build/.editorconfig | 11 + build/Build.cs | 181 +++++++++ build/Configuration.cs | 16 + build/Directory.Build.props | 8 + build/Directory.Build.targets | 8 + build/_build.csproj | 21 ++ build/_build.csproj.DotSettings | 31 ++ global.json | 6 + src/Reflectify/.nuspec | 24 ++ src/Reflectify/Reflectify.cs | 207 ++++++++++ src/Reflectify/Reflectify.csproj | 13 + src/Reflectify/Reflectify.props | 7 + tests/.editorconfig | 42 +++ .../Reflectify.Specs/Reflectify.Specs.csproj | 51 +++ tests/Reflectify.Specs/TypeExtensionsSpecs.cs | 297 +++++++++++++++ 35 files changed, 2119 insertions(+), 279 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/FUNDING.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/release.yml create mode 100644 .github/workflows/build.yml create mode 100644 .idea/.idea.reflectorator/.idea/.gitignore create mode 100644 .idea/.idea.reflectorator/.idea/indexLayout.xml create mode 100644 .idea/.idea.reflectorator/.idea/vcs.xml create mode 100644 .nuke/build.schema.json create mode 100644 .nuke/parameters.json create mode 100644 Directory.Build.props create mode 100644 GitVersion.yml create mode 100644 README.md create mode 100644 Reflectify.sln create mode 100644 Reflectify.sln.DotSettings create mode 100755 build.cmd create mode 100644 build.ps1 create mode 100755 build.sh create mode 100644 build/.editorconfig create mode 100644 build/Build.cs create mode 100644 build/Configuration.cs create mode 100644 build/Directory.Build.props create mode 100644 build/Directory.Build.targets create mode 100644 build/_build.csproj create mode 100644 build/_build.csproj.DotSettings create mode 100644 global.json create mode 100644 src/Reflectify/.nuspec create mode 100644 src/Reflectify/Reflectify.cs create mode 100644 src/Reflectify/Reflectify.csproj create mode 100644 src/Reflectify/Reflectify.props create mode 100644 tests/.editorconfig create mode 100644 tests/Reflectify.Specs/Reflectify.Specs.csproj create mode 100644 tests/Reflectify.Specs/TypeExtensionsSpecs.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4c52e62 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,321 @@ +root = true +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file + +# Global settings +[*] +end_of_line = crlf +insert_final_newline = true +trim_trailing_whitespace = true + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_style = space +indent_size = 2 + +# Xml config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_style = space +indent_size = 2 + +[*.{md,json}] +indent_style = space +indent_size = 4 + +[*.cs] +indent_style = space +indent_size = 4 +max_line_length = 130 + +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# only use var when it's obvious what the variable type is +csharp_style_var_for_built_in_types = false:none +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = false:suggestion + +# use language keywords instead of BCL types +dotnet_style_predefined_type_for_locals_parameters_members = true:error +dotnet_style_predefined_type_for_member_access = true:error + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style + +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# Code style defaults +csharp_using_directive_placement = outside_namespace:warning +dotnet_sort_system_directives_first = true +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = true:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none +csharp_style_expression_bodied_properties = true:none +csharp_style_expression_bodied_indexers = true:none +csharp_style_expression_bodied_accessors = true:none + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:error +csharp_style_pattern_matching_over_as_with_null_check = true:error +csharp_style_inlined_variable_declaration = true:error + +# Null checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +dotnet_style_require_accessibility_modifiers = for_non_interface_members:error +dotnet_style_readonly_field = true:error + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_attribute_sections = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +resharper_blank_lines_after_multiline_statements = 1 + +# Purpose: Don't put elements of an array on new lines, unless the length exceeds the maximum line length. +resharper_wrap_array_initializer_style = chop_always + +# Purpose: An item within a C# enumeration is missing an Xml documentation header +# Reason: Some enums are self-explanatory +dotnet_diagnostic.ca1062.severity = suggestion + +# Purpose: Specify StringComparison +# Reason: Ignored since we force invariant culture on the csproj level +dotnet_diagnostic.ca1307.severity = none + +# Purpose: Specify StringComparison +# Reason: Specify StringComparison for methods that don't use Ordinal by default +dotnet_diagnostic.ca1310.severity = error + +# Purpose: 'X' is an internal class that is apparently never instantiated. If so, remove the code from the assembly +# Reason: The class is used, but not directly through the constructor, which is fine. +dotnet_diagnostic.ca1812.severity = none + +# Purpose: Properties should not return arrays +# Reason: Although it's good advice, we should decide this case-by-case +dotnet_diagnostic.ca1819.severity = suggestion + +# Purpose: For improved performance, use the LoggerMessage delegates instead of calling 'LoggerExtensions.LogInformation(ILogger, string?, params object?[])' +# Reason: For now, we prefer to use explicit checks using IsEnabled +dotnet_diagnostic.ca1848.severity = none + +# Purpose: Use Task.ConfigureAwait(false) if the current SynchronizationContext is not needed +# Reason: Not relevant for .NET Core web applications. +dotnet_diagnostic.ca2007.severity = none + +# Purpose: # SA0001: XmlCommentAnalysisDisabled +# Reason: We don't want to force this +dotnet_diagnostic.sa0001.severity = none + +# Purpose: Use string.Empty for empty string +# Reason: We don't want to force this +dotnet_diagnostic.sa1122.severity = none + +# Purpose: Prefix local calls with this +# Reason: We don't want to force this +dotnet_diagnostic.sa1101.severity = none + +# Purpose: A enum should not follow a class +# Reason: We don't want to force this for a content-only package +dotnet_diagnostic.sa1201.severity = none + +# Purpose: public' members should come before 'private' members +# Reason: We keep members in order of execution +dotnet_diagnostic.SA1202.severity = none + +# Purpose: Static members should appear before non-static members +# Reason: We keep members in order of execution +dotnet_diagnostic.SA1204.severity = none + +# Purpose: Code should not contain multiple blank lines in a row +# Reason: No need to fail a build on that +dotnet_diagnostic.sa1507.severity = none + +# Purpose: A single-line comment within C# code is not preceded by a blank line. +# Reason: Unnecessarily strict +dotnet_diagnostic.sa1515.severity = none + +# Purpose: File is required to end with a single newline character +# Reason: We don't case +dotnet_diagnostic.sa1518.severity = none + +# Purpose: Element parameters should be documented +# Reason: We don't want to force this +dotnet_diagnostic.sa1611.severity = none + +# Purpose: Element return value should be documented +# Reason: We don't want to force this +dotnet_diagnostic.sa1615.severity = none + +# Purpose: Generic type parameters should be documented +# Reason: We don't want to force this +dotnet_diagnostic.sa1618.severity = none + +# Purpose: Element return value should be documented +# Reason: We don't want to force this +dotnet_diagnostic.sa1622.severity = none + +# Purpose: The property's documentation summary text should begin with: 'Gets or sets' +# Reason: We don't want to force this +dotnet_diagnostic.sa1623.severity = none + +# Purpose: Documentation text should end with a period +# Reason: We're not building a library +dotnet_diagnostic.sa1629.severity = none + +# Purpose: The file header is missing or not located at the top of the file +# Reason: We don't want to force this +dotnet_diagnostic.sa1633.severity = none + +# Purpose: File name should match first type name +# Reason: To keep the content-only package simple, we keep everything together +dotnet_diagnostic.sa1649.severity = none + +# Purpose: Elements should be documented +# Reason: We don't want to force documentation for all elements +dotnet_diagnostic.sa1600.severity = suggestion + +# Purpose: Partial elements should be documented +# Reason: We don't want to force this +dotnet_diagnostic.sa1601.severity = none + +# Purpose: Closing parenthesis should be on line of last parameter +# Reason: We don't want to force this +dotnet_diagnostic.sa1111.severity = none + +# Purpose: The parameters should begin on the line after the declaration, whenever the parameter span across multiple lines +# Reason: We don't want to force this +dotnet_diagnostic.sa1116.severity = none + +# Purpose: Use an overload of 'Contains' that has a StringComparison parameter +# Reason: Ignored since we force invariant culture on the csproj level +dotnet_diagnostic.ma0001.severity = none + +# Purpose: MA0002 : Use an overload that has a IEqualityComparer or IComparer parameter +# Reason: Ignored since we force invariant culture on the csproj level +dotnet_diagnostic.ma0002.severity = none + +# Purpose: Use Task.ConfigureAwait(false) if the current SynchronizationContext is not needed +# Reason: Not relevant for .NET Core web applications. +dotnet_diagnostic.ma0004.severity = none + +# Purpose: Use string.Equals instead of NotEquals operator +# Reason: Ignored since we force invariant culture on the csproj level +dotnet_diagnostic.ma0006.severity = none + +# Purpose: MA0011 : Use an overload of 'ToString' that has a 'System.IFormatProvider' parameter +# Reason: Ignored since we force invariant culture on the csproj level +dotnet_diagnostic.ma0011.severity = none + +# Purpose: Fix TODO comment +# Reason: We don't want to force this +dotnet_diagnostic.ma0026.severity = suggestion + +# Purpose: File name must match type name. +# Reason: Too many purposeful violations. +dotnet_diagnostic.MA0048.severity = none + +# Purpose: Closing parenthesis should not be preceded by a space +# Reason: We don't want to force this +dotnet_diagnostic.sa1009.severity = none + +# Purpose: File may only contain a single type +# Reason: We keep everything together in this project. +dotnet_diagnostic.SA1402.severity = none + +# Purpose: Use trailing comma in multi-line initialize +# Reason: We don't want to force this +dotnet_diagnostic.sa1413.severity = none + +# Purpose: Method contains 8 statements, which exceeds the maximum of 7 statements +# Reason: We don't want to force this +dotnet_diagnostic.av1500.severity = none + +# Purpose: Loop statement contains nested loop +# Reason: We don't want to completely prevent this. +dotnet_diagnostic.av1532.severity = suggestion + +# Purpose: AV1555 : Parameter in the call is invoked with a named argument +# Reason: False positives. See https://github.com/bkoelman/CSharpGuidelinesAnalyzer/issues/145 +dotnet_diagnostic.av1555.severity = none + +# Purpose: Argument for parameter 'param' in method calls nested method +# Reason: Not a real issue in Rider anymore +dotnet_diagnostic.av1580.severity = none + +# Purpose: AV1706 : Parameter 'p' should have a more descriptive name +# Reason: Also complains about lambda parameters. See https://github.com/bkoelman/CSharpGuidelinesAnalyzer/issues/147 +dotnet_diagnostic.av1706.severity = none + +# Purpose: Property 'Username' contains the name of its containing type 'User'. We should make that decision case-by-case +# Reason: We don't want to judge this case-by-case +dotnet_diagnostic.av1710.severity = none + +# Purpose: Error AV1755 : Name of async method should end with Async or TaskAsync +# Reason: We don't want to force this +dotnet_diagnostic.av1755.severity = none + +# Purpose: Missing XML comment for internally visible type or member +# Reason: We don't want to force this +dotnet_diagnostic.av2305.severity = suggestion + +# Purpose: Pass -warnaserror to the compiler or add True to your project +# Reason: It is already enabled, but during a Sonar scan, the scanner switches back causing unnecessary warnings +dotnet_diagnostic.av2210.severity = none diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7c59b39 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,65 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +*.sh text eol=lf + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..99db83d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: dennisdoomen +ko_fi: dennisdoomen diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..d054656 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "16:00" + timezone: "Europe/Copenhagen" + + - package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "16:00" + timezone: "Europe/Copenhagen" diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..859710d --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,29 @@ +changelog: + exclude: + labels: + - changelog-ignore + authors: [ ] + categories: + - title: Breaking Changes + labels: + - "breaking change" + - title: New features + labels: + - "feature" + - title: Improvements + labels: + - "enhancement" + - "performance" + - title: Fixes + labels: + - "bug" + - "regression" + - title: Fixes (extensibility) + labels: + - "extensibility" + - title: Documentation + labels: + - "documentation" + - title: Others + labels: + - "*" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..10a45ad --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,72 @@ +name: Build + +on: [push, pull_request] + +jobs: + build: + name: "Build, Test, Analyze and Publish" + runs-on: windows-latest + + env: + DOTNET_NOLOGO: false + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET SDKs + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + + - name: Run NUKE + run: ./build.ps1 + env: + NuGetApiKey: ${{ secrets.NUGETAPIKEY }} + + - name: Check for 'lcov.info' existence + id: check_files + uses: andstor/file-existence-action@v3 + with: + files: "TestResults/reports/lcov.info" + + - name: coveralls + uses: coverallsapp/github-action@v2 + if: steps.check_files.outputs.files_exists == 'true' + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + file: TestResults/reports/lcov.info + + - name: Upload artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: windows-artifacts + path: | + ./Artifacts/* + ./TestResults/*.trx + + publish-test-results: + name: "Publish Tests Results" + needs: [ build ] + runs-on: ubuntu-latest + permissions: + checks: write + pull-requests: write + if: always() + + steps: + - name: Download Artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + comment_mode: always + files: "artifacts/**/**/*.trx" diff --git a/.gitignore b/.gitignore index 8a30d25..ee534ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,84 +1,41 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore # User-specific files -*.rsuser *.suo *.user -*.userosscache *.sln.docstates +*.sln.ide +.vs/ +.vscode/ -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* +project.lock.json +*.nuget.props +*.nuget.targets # Build results +Artifacts/ [Dd]ebug/ -[Dd]ebugPublic/ [Rr]elease/ -[Rr]eleases/ x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ [Bb]in/ [Oo]bj/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ -# Visual Studio 2017 auto generated files -Generated\ Files/ +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ +packages/** # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio *_i.c *_p.c -*_h.h *.ilk *.meta *.obj -*.iobj *.pch *.pdb -*.ipdb *.pgc *.pgd *.rsp @@ -88,41 +45,27 @@ StyleCopReport.xml *.tlh *.tmp *.tmp_proj -*_wpftmp.csproj *.log -*.tlog +*.binlog *.vspscc *.vssscc .builds *.pidb -*.svclog +*.log *.scc -# Chutzpah Test files -_Chutzpah* - # Visual C++ cache files ipch/ *.aps *.ncb -*.opendb *.opensdf *.sdf *.cachefile -*.VC.db -*.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ # Guidance Automation Toolkit *.gpState @@ -130,7 +73,6 @@ $tf/ # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper -*.DotSettings.user # TeamCity is a build add-in _TeamCity* @@ -138,30 +80,9 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - # NCrunch -_NCrunch_* .*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ +_NCrunch_FluentAssertions/* # Installshield output folder [Ee]xpress/ @@ -180,219 +101,94 @@ DocProject/Help/html publish/ # Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# 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 -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets +*.Publish.xml -# Microsoft Azure Build Output -csx/ -*.build.csdef +# NuGet Packages Directory +packages/ -# Microsoft Azure Emulator -ecf/ -rcf/ +# Windows Azure Build Output +csx +*.build.csdef -# Windows Store app package directories and files +# Windows Store app package directory AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ # Others +sql/ +*.Cache ClientBin/ +[Ss]tyle[Cc]op.* ~$* *~ *.dbmdl -*.dbproj.schemaview -*.jfm +*.[Pp]ublish.xml *.pfx *.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ # RIA/Silverlight projects Generated_Code/ -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak # SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ +App_Data/*.mdf +App_Data/*.ldf -# CodeRush personal settings -.cr/personal -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc +#LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config +# ========================= +# Windows detritus +# ========================= -# Tabs Studio -*.tss +# Windows image file caches +Thumbs.db +ehthumbs.db -# Telerik's JustMock configuration file -*.jmconfig +# Folder config file +Desktop.ini -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs +# Recycle Bin used on file shares +$RECYCLE.BIN/ -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# Visual Studio History (VSHistory) files -.vshistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp - -# JetBrains Rider -*.sln.iml +# Mac desktop service store files +.DS_Store +Package/Lib +*.nupkg +*.zip + +.idea* + +# Jekyll +*.gem +*.sublime-project +*.sublime-workspace +docs/.bundle +docs/.DS_Store +docs/.jekyll-metadata +docs/.sass-cache +docs/_asset_bundler_cache +docs/_site +docs/codekit-config.json +docs/Gemfile.lock +docs/node_modules + +# Approval Tests +*.received.txt + +# Documentation file needed for StyleCop.Analyzers +Tests/FluentAssertions.Specs/FluentAssertions.Specs.xml + +# BenchmarkDotNet +Tests/Benchmarks/BenchmarkDotNet.Artifacts/ + +# Documentation spell check +node_modules/ \ No newline at end of file diff --git a/.idea/.idea.reflectorator/.idea/.gitignore b/.idea/.idea.reflectorator/.idea/.gitignore new file mode 100644 index 0000000..e363d7b --- /dev/null +++ b/.idea/.idea.reflectorator/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/.idea.reflectorator.iml +/modules.xml +/contentModel.xml +/projectSettingsUpdater.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.reflectorator/.idea/indexLayout.xml b/.idea/.idea.reflectorator/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.reflectorator/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.reflectorator/.idea/vcs.xml b/.idea/.idea.reflectorator/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/.idea.reflectorator/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json new file mode 100644 index 0000000..06d267a --- /dev/null +++ b/.nuke/build.schema.json @@ -0,0 +1,125 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "properties": { + "Configuration": { + "type": "string", + "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)", + "enum": [ + "Debug", + "Release" + ] + }, + "NuGetApiKey": { + "type": "string", + "description": "The key to push to Nuget", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "Solution": { + "type": "string", + "description": "Path to a solution file that is automatically loaded" + } + }, + "definitions": { + "Host": { + "type": "string", + "enum": [ + "AppVeyor", + "AzurePipelines", + "Bamboo", + "Bitbucket", + "Bitrise", + "GitHubActions", + "GitLab", + "Jenkins", + "Rider", + "SpaceAutomation", + "TeamCity", + "Terminal", + "TravisCI", + "VisualStudio", + "VSCode" + ] + }, + "ExecutableTarget": { + "type": "string", + "enum": [ + "CalculateNugetVersion", + "CodeCoverage", + "Compile", + "Default", + "Pack", + "Push", + "Restore", + "RunTests" + ] + }, + "Verbosity": { + "type": "string", + "description": "", + "enum": [ + "Verbose", + "Normal", + "Minimal", + "Quiet" + ] + }, + "NukeBuild": { + "properties": { + "Continue": { + "type": "boolean", + "description": "Indicates to continue a previously failed build attempt" + }, + "Help": { + "type": "boolean", + "description": "Shows the help text for this build assembly" + }, + "Host": { + "description": "Host for execution. Default is 'automatic'", + "$ref": "#/definitions/Host" + }, + "NoLogo": { + "type": "boolean", + "description": "Disables displaying the NUKE logo" + }, + "Partition": { + "type": "string", + "description": "Partition to use on CI" + }, + "Plan": { + "type": "boolean", + "description": "Shows the execution plan (HTML)" + }, + "Profile": { + "type": "array", + "description": "Defines the profiles to load", + "items": { + "type": "string" + } + }, + "Root": { + "type": "string", + "description": "Root directory during build execution" + }, + "Skip": { + "type": "array", + "description": "List of targets to be skipped. Empty list skips all dependencies", + "items": { + "$ref": "#/definitions/ExecutableTarget" + } + }, + "Target": { + "type": "array", + "description": "List of targets to be invoked. Default is '{default_target}'", + "items": { + "$ref": "#/definitions/ExecutableTarget" + } + }, + "Verbosity": { + "description": "Logging verbosity during build execution. Default is 'Normal'", + "$ref": "#/definitions/Verbosity" + } + } + } + }, + "$ref": "#/definitions/NukeBuild" +} diff --git a/.nuke/parameters.json b/.nuke/parameters.json new file mode 100644 index 0000000..e444003 --- /dev/null +++ b/.nuke/parameters.json @@ -0,0 +1,4 @@ +{ + "$schema": "build.schema.json", + "Solution": "Reflectify.sln" +} diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..d6b9f91 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,43 @@ + + + 11.0 + false + $(WarningsNotAsErrors);NU1902;NU1903 + true + + + + + false + false + false + + + true + 7.0 + All + true + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000..0440058 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,19 @@ +workflow: GitHubFlow/v1 +branches: + develop: + regex: ^dev(elop)?(ment)?$ + label: alpha + increment: Minor + release: + regex: releases?[/-] + label: rc + increment: Patch + pull-request: + mode: ContinuousDelivery + regex: ((pull|pull\-requests|pr)[/-]|[/-](merge)) + label: pr + label-number-pattern: '[/-]?(?\d+)' + prevent-increment: + of-merged-branch: false +ignore: + sha: [] diff --git a/README.md b/README.md new file mode 100644 index 0000000..bce8dc0 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Reflectify - Reflection extensions without causing dependency pains + +[![](https://img.shields.io/github/actions/workflow/status/dennisdoomen/reflectify/build.yml?branch=main)](https://github.com/dennisdoomen/reflectify/actions?query=branch%3amain) +[![Coveralls branch](https://img.shields.io/coverallsCoverage/github/dennisdoomen/reflectify?branch=main)](https://coveralls.io/github/dennisdoomen/reflectify?branch=main) +[![](https://img.shields.io/github/release/DennisDoomen/Reflectify.svg?label=latest%20release&color=007edf)](https://github.com/dennisdoomen/reflectify/releases/latest) +[![](https://img.shields.io/nuget/dt/Reflectify.svg?label=downloads&color=007edf&logo=nuget)](https://www.nuget.org/packages/Reflectify) +[![](https://img.shields.io/librariesio/dependents/nuget/Reflectify.svg?label=dependent%20libraries)](https://libraries.io/nuget/Reflectify) +[![GitHub Repo stars](https://img.shields.io/github/stars/dennisdoomen/reflectify)](https://github.com/dennisdoomen/reflectify/stargazers) +[![GitHub contributors](https://img.shields.io/github/contributors/dennisdoomen/reflectify)](https://github.com/dennisdoomen/reflectify/graphs/contributors) +[![GitHub last commit](https://img.shields.io/github/last-commit/dennisdoomen/reflectify)](https://github.com/dennisdoomen/reflectify) +[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/dennisdoomen/reflectify)](https://github.com/dennisdoomen/reflectify/graphs/commit-activity) +[![open issues](https://img.shields.io/github/issues/dennisdoomen/reflectify)](https://github.com/dennisdoomen/reflectify/issues) +![](https://img.shields.io/badge/release%20strategy-githubflow-orange.svg) + +> [!TIP] +> If you like this package, consider sponsoring me through [Github Sponsors](https://github.com/sponsors/dennisdoomen) + +## What's this about? + +Reflectify offers a bunch of extension methods to provide information such as the properties or fields a type exposes and metadata about those members. It supports all major .NET versions and even understands explicltly implemented properties or properties coming from default interface implementations, a C# 8 feature. + +## What's so special about that? + +Nothing really, but it offers that functionality through a content-only NuGet package. In other words, you can use this package in your own packages, without the need to tie yourself to the Reflectify package. diff --git a/Reflectify.sln b/Reflectify.sln new file mode 100644 index 0000000..3edf8bc --- /dev/null +++ b/Reflectify.sln @@ -0,0 +1,32 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reflectify", "src\Reflectify\Reflectify.csproj", "{C40328B9-91F9-491A-8831-AAE00D62980F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reflectify.Specs", "tests\Reflectify.Specs\Reflectify.Specs.csproj", "{07AE886E-509D-49C3-BB61-EC62903FDD90}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{506022A4-AD68-43A1-A670-F2798A4091FB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {506022A4-AD68-43A1-A670-F2798A4091FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {506022A4-AD68-43A1-A670-F2798A4091FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C40328B9-91F9-491A-8831-AAE00D62980F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C40328B9-91F9-491A-8831-AAE00D62980F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C40328B9-91F9-491A-8831-AAE00D62980F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C40328B9-91F9-491A-8831-AAE00D62980F}.Release|Any CPU.Build.0 = Release|Any CPU + {07AE886E-509D-49C3-BB61-EC62903FDD90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07AE886E-509D-49C3-BB61-EC62903FDD90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07AE886E-509D-49C3-BB61-EC62903FDD90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07AE886E-509D-49C3-BB61-EC62903FDD90}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Reflectify.sln.DotSettings b/Reflectify.sln.DotSettings new file mode 100644 index 0000000..ab64a3d --- /dev/null +++ b/Reflectify.sln.DotSettings @@ -0,0 +1,193 @@ + + True + True + False + False + True + HINT + DO_NOT_SHOW + <?xml version="1.0" encoding="utf-16"?><Profile name="Safe Cleanup"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" RemoveRedundantParentheses="True" /><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><IDEA_SETTINGS>&lt;profile version="1.0"&gt; + &lt;option name="myName" value="Safe Cleanup" /&gt; + &lt;inspection_tool class="ES6ShorthandObjectProperty" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="JSPrimitiveTypeWrapperUsage" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="JSRemoveUnnecessaryParentheses" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="JSUnnecessarySemicolon" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="TypeScriptExplicitMemberType" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryContinueJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryLabelJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryLabelOnBreakStatementJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryLabelOnContinueStatementJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryReturnJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="WrongPropertyKeyValueDelimiter" enabled="false" level="WEAK WARNING" enabled_by_default="false" /&gt; +&lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt; + &lt;Language id="CSS"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; + &lt;/Language&gt; + &lt;Language id="EditorConfig"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HTML"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; + &lt;/Language&gt; + &lt;Language id="HTTP Request"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Handlebars"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Ini"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JSON"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Jade"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JavaScript"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; + &lt;/Language&gt; + &lt;Language id="Markdown"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="PowerShell"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Properties"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="RELAX-NG"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="SQL"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="VueExpr"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="XML"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; + &lt;/Language&gt; + &lt;Language id="yaml"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; +&lt;/profile&gt;</RIDER_SETTINGS><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags></Profile> + Safe Cleanup + Required + Required + Required + Required + False + 1 + 1 + True + NEVER + False + NEVER + True + False + 130 + False + 1 + 130 + False + UseExplicitType + UseVarWhenEvident + UseExplicitType + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + + OUTLINE + SOLUTION_FOLDER + True + True + True + True + True + True + True + True + True + True + True + D:\Workspaces\FluentAssertions\Default.testsettings + 4 + False + True + False + True + 0 + + False + + aaa + Arrange-Act-Assert + [Fact] +public void $Fact$() +{ + // Arrange + $END$ + + // Act + + + // Assert +} + True + True + True + True + InCSharpFile + 2.0 + True + True + True + True + True diff --git a/build.cmd b/build.cmd new file mode 100755 index 0000000..b08cc59 --- /dev/null +++ b/build.cmd @@ -0,0 +1,7 @@ +:; set -eo pipefail +:; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) +:; ${SCRIPT_DIR}/build.sh "$@" +:; exit $? + +@ECHO OFF +powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %* diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..4634dc0 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,74 @@ +[CmdletBinding()] +Param( + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$BuildArguments +) + +Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)" + +Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 } +$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent + +########################################################################### +# CONFIGURATION +########################################################################### + +$BuildProjectFile = "$PSScriptRoot\build\_build.csproj" +$TempDirectory = "$PSScriptRoot\\.nuke\temp" + +$DotNetGlobalFile = "$PSScriptRoot\\global.json" +$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" +$DotNetChannel = "STS" + +$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 +$env:DOTNET_NOLOGO = 1 + +########################################################################### +# EXECUTION +########################################################################### + +function ExecSafe([scriptblock] $cmd) { + & $cmd + if ($LASTEXITCODE) { exit $LASTEXITCODE } +} + +# If dotnet CLI is installed globally and it matches requested version, use for execution +if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` + $(dotnet --version) -and $LASTEXITCODE -eq 0) { + $env:DOTNET_EXE = (Get-Command "dotnet").Path +} +else { + # Download install script + $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" + New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) + + # If global.json exists, load expected version + if (Test-Path $DotNetGlobalFile) { + $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) + if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { + $DotNetVersion = $DotNetGlobal.sdk.version + } + } + + # Install by channel or version + $DotNetDirectory = "$TempDirectory\dotnet-win" + if (!(Test-Path variable:DotNetVersion)) { + ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } + } else { + ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } + } + $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" + $env:PATH = "$DotNetDirectory;$env:PATH" +} + +Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)" + +if (Test-Path env:NUKE_ENTERPRISE_TOKEN) { + & $env:DOTNET_EXE nuget remove source "nuke-enterprise" > $null + & $env:DOTNET_EXE nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password $env:NUKE_ENTERPRISE_TOKEN > $null +} + +ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } +ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..fdff0c6 --- /dev/null +++ b/build.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +bash --version 2>&1 | head -n 1 + +set -eo pipefail +SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) + +########################################################################### +# CONFIGURATION +########################################################################### + +BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj" +TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp" + +DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json" +DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh" +DOTNET_CHANNEL="STS" + +export DOTNET_CLI_TELEMETRY_OPTOUT=1 +export DOTNET_NOLOGO=1 + +########################################################################### +# EXECUTION +########################################################################### + +function FirstJsonValue { + perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}" +} + +# If dotnet CLI is installed globally and it matches requested version, use for execution +if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then + export DOTNET_EXE="$(command -v dotnet)" +else + # Download install script + DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh" + mkdir -p "$TEMP_DIRECTORY" + curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL" + chmod +x "$DOTNET_INSTALL_FILE" + + # If global.json exists, load expected version + if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then + DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")") + if [[ "$DOTNET_VERSION" == "" ]]; then + unset DOTNET_VERSION + fi + fi + + # Install by channel or version + DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix" + if [[ -z ${DOTNET_VERSION+x} ]]; then + "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path + else + "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path + fi + export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet" + export PATH="$DOTNET_DIRECTORY:$PATH" +fi + +echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)" + +if [[ ! -z ${NUKE_ENTERPRISE_TOKEN+x} && "$NUKE_ENTERPRISE_TOKEN" != "" ]]; then + "$DOTNET_EXE" nuget remove source "nuke-enterprise" &>/dev/null || true + "$DOTNET_EXE" nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password "$NUKE_ENTERPRISE_TOKEN" --store-password-in-clear-text &>/dev/null || true +fi + +"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet +"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@" diff --git a/build/.editorconfig b/build/.editorconfig new file mode 100644 index 0000000..31e43dc --- /dev/null +++ b/build/.editorconfig @@ -0,0 +1,11 @@ +[*.cs] +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning +dotnet_style_require_accessibility_modifiers = never:warning + +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning diff --git a/build/Build.cs b/build/Build.cs new file mode 100644 index 0000000..e107b04 --- /dev/null +++ b/build/Build.cs @@ -0,0 +1,181 @@ +using System; +using System.Linq; +using Nuke.Common; +using Nuke.Common.CI; +using Nuke.Common.CI.GitHubActions; +using Nuke.Common.Execution; +using Nuke.Common.IO; +using Nuke.Common.ProjectModel; +using Nuke.Common.Tooling; +using Nuke.Common.Tools.DotNet; +using Nuke.Common.Tools.GitVersion; +using Nuke.Common.Tools.ReportGenerator; +using Nuke.Common.Utilities.Collections; +using Nuke.Components; +using YamlDotNet.Serialization; +using static Nuke.Common.EnvironmentInfo; +using static Nuke.Common.IO.FileSystemTasks; +using static Nuke.Common.IO.PathConstruction; +using static Nuke.Common.Tools.DotNet.DotNetTasks; +using static Nuke.Common.Tools.ReportGenerator.ReportGeneratorTasks; +using static Serilog.Log; + +class Build : NukeBuild +{ + /// Support plugins are available for: + /// - JetBrains ReSharper https://nuke.build/resharper + /// - JetBrains Rider https://nuke.build/rider + /// - Microsoft VisualStudio https://nuke.build/visualstudio + /// - Microsoft VSCode https://nuke.build/vscode + + public static int Main () => Execute(x => x.Default); + + [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] + readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release; + + GitHubActions GitHubActions => GitHubActions.Instance; + + string BranchSpec => GitHubActions?.Ref; + + string BuildNumber => GitHubActions?.RunNumber.ToString(); + + string PullRequestBase => GitHubActions?.BaseRef; + + [Parameter("The key to push to Nuget")] + [Secret] + readonly string NuGetApiKey; + + [Solution(GenerateProjects = true)] + readonly Solution Solution; + + [GitVersion(Framework = "net6.0", NoFetch = true)] + readonly GitVersion GitVersion; + + AbsolutePath ArtifactsDirectory => RootDirectory / "Artifacts"; + + AbsolutePath TestResultsDirectory => RootDirectory / "TestResults"; + + string SemVer; + + Target CalculateNugetVersion => _ => _ + .Executes(() => + { + SemVer = GitVersion.SemVer; + if (IsPullRequest) + { + Information( + "Branch spec {branchspec} is a pull request. Adding build number {buildnumber}", + BranchSpec, BuildNumber); + + SemVer = string.Join('.', GitVersion.SemVer.Split('.').Take(3).Union(new[] { BuildNumber })); + } + + Information("SemVer = {semver}", SemVer); + }); + + Target Restore => _ => _ + .Executes(() => + { + DotNetRestore(s => s + .SetProjectFile(Solution) + .EnableNoCache()); + }); + + Target Compile => _ => _ + .DependsOn(CalculateNugetVersion) + .DependsOn(Restore) + .Executes(() => + { + ReportSummary(s => s + .WhenNotNull(SemVer, (summary, semVer) => summary + .AddPair("Version", semVer))); + + DotNetBuild(s => s + .SetProjectFile(Solution) + .SetConfiguration(Configuration) + .EnableNoLogo() + .EnableNoRestore() + .SetAssemblyVersion(GitVersion.AssemblySemVer) + .SetFileVersion(GitVersion.AssemblySemFileVer) + .SetInformationalVersion(GitVersion.InformationalVersion)); + }); + + Target RunTests => _ => _ + .DependsOn(Compile) + .Executes(() => + { + TestResultsDirectory.CreateOrCleanDirectory(); + + DotNetTest(s => s + .SetConfiguration(Configuration.Debug) + .SetProcessEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE", "en-US") + .SetDataCollector("XPlat Code Coverage") + .SetResultsDirectory(TestResultsDirectory) + .SetProjectFile(Solution.Reflectify_Specs) + .CombineWith(Solution.Reflectify_Specs.GetTargetFrameworks(), + (ss, framework) => ss + .SetFramework(framework) + .AddLoggers($"trx;LogFileName={framework}.trx") + )); + }); + + Target CodeCoverage => _ => _ + .DependsOn(RunTests) + .Executes(() => + { + ReportGenerator(s => s + .SetTargetDirectory(TestResultsDirectory / "reports") + .AddReports(TestResultsDirectory / "**/coverage.cobertura.xml") + .AddReportTypes(ReportTypes.lcov, ReportTypes.Html) + .AddFileFilters("-*.g.cs")); + + string link = TestResultsDirectory / "reports" / "index.html"; + Information($"Code coverage report: \x1b]8;;file://{link.Replace('\\', '/')}\x1b\\{link}\x1b]8;;\x1b\\"); + }); + + Target Pack => _ => _ + .DependsOn(CalculateNugetVersion) + .DependsOn(CodeCoverage) + .Executes(() => + { + ReportSummary(s => s + .WhenNotNull(SemVer, (c, semVer) => c + .AddPair("Packed version", semVer))); + + DotNetPack(s => s + .SetProject(Solution.Reflectify) + .SetOutputDirectory(ArtifactsDirectory) + .SetConfiguration(Configuration == Configuration.Debug ? "Debug" : "Release") + .EnableNoBuild() + .EnableNoLogo() + .EnableNoRestore() + .EnableContinuousIntegrationBuild() // Necessary for deterministic builds + .SetVersion(SemVer)); + }); + + Target Push => _ => _ + .DependsOn(Pack) + .OnlyWhenDynamic(() => IsTag) + .ProceedAfterFailure() + .Executes(() => + { + var packages = ArtifactsDirectory.GlobFiles("*.nupkg"); + + Assert.NotEmpty(packages); + + DotNetNuGetPush(s => s + .SetApiKey(NuGetApiKey) + .EnableSkipDuplicate() + .SetSource("https://api.nuget.org/v3/index.json") + .EnableNoSymbols() + .CombineWith(packages, + (v, path) => v.SetTargetPath(path))); + }); + + Target Default => _ => _ + .DependsOn(Push); + + bool IsPullRequest => GitHubActions?.IsPullRequest ?? false; + + bool IsTag => BranchSpec != null && BranchSpec.Contains("refs/tags", StringComparison.OrdinalIgnoreCase); +} diff --git a/build/Configuration.cs b/build/Configuration.cs new file mode 100644 index 0000000..9c08b1a --- /dev/null +++ b/build/Configuration.cs @@ -0,0 +1,16 @@ +using System; +using System.ComponentModel; +using System.Linq; +using Nuke.Common.Tooling; + +[TypeConverter(typeof(TypeConverter))] +public class Configuration : Enumeration +{ + public static Configuration Debug = new Configuration { Value = nameof(Debug) }; + public static Configuration Release = new Configuration { Value = nameof(Release) }; + + public static implicit operator string(Configuration configuration) + { + return configuration.Value; + } +} diff --git a/build/Directory.Build.props b/build/Directory.Build.props new file mode 100644 index 0000000..e147d63 --- /dev/null +++ b/build/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/Directory.Build.targets b/build/Directory.Build.targets new file mode 100644 index 0000000..2532609 --- /dev/null +++ b/build/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/_build.csproj b/build/_build.csproj new file mode 100644 index 0000000..a1833b6 --- /dev/null +++ b/build/_build.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + + CS0649;CS0169;CA1050;CA1822;CA2211;IDE1006 + .. + .. + 1 + false + + + + + + + + + + diff --git a/build/_build.csproj.DotSettings b/build/_build.csproj.DotSettings new file mode 100644 index 0000000..c815d36 --- /dev/null +++ b/build/_build.csproj.DotSettings @@ -0,0 +1,31 @@ + + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + Implicit + Implicit + ExpressionBody + 0 + NEXT_LINE + True + False + 120 + IF_OWNER_IS_SINGLE_LINE + WRAP_IF_LONG + False + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + True + True + True + True + True + True + True + True + True + True diff --git a/global.json b/global.json new file mode 100644 index 0000000..368a14e --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.100", + "rollForward": "latestMajor" + } +} diff --git a/src/Reflectify/.nuspec b/src/Reflectify/.nuspec new file mode 100644 index 0000000..9058ddf --- /dev/null +++ b/src/Reflectify/.nuspec @@ -0,0 +1,24 @@ + + + + + Reflectify + $version$ + + true + Dennis Doomen + MIT + false + https://github.com/dennisdoomen/Reflectify + A source-only package to help get metadata from .NET types + source reflection + + + + + + + + + + diff --git a/src/Reflectify/Reflectify.cs b/src/Reflectify/Reflectify.cs new file mode 100644 index 0000000..32de67a --- /dev/null +++ b/src/Reflectify/Reflectify.cs @@ -0,0 +1,207 @@ +#if !REFLECTIFY_COMPILE +// +#endif + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Reflectify; + +public static class TypeExtensions +{ + public static IEnumerable GetProperties(this Type type, MemberKind kind) + { + var reflector = new Reflector(type, kind); + return reflector.Properties; + } + + public static IEnumerable GetFields(this Type type, MemberKind kind) + { + var reflector = new Reflector(type, kind); + return reflector.Fields; + } + + public static IEnumerable GetMembers(this Type type, MemberKind kind) + { + var reflector = new Reflector(type, kind); + return reflector.Members; + } +} + +public static class PropertyInfoExtensions +{ + /// + /// Returns true if the property is an indexer, or false otherwise. + /// + public static bool IsIndexer(this PropertyInfo member) + { + return member.GetIndexParameters().Length != 0; + } + + /// + /// Returns true if the property is explicitly implemented on the + /// + public static bool IsExplicitlyImplemented(this PropertyInfo prop) + { +#if NETCOREAPP3_0_OR_GREATER + return prop.Name.Contains('.', StringComparison.OrdinalIgnoreCase); +#else + return prop.Name.Contains('.'); +#endif + } +} + +/// +/// Defines the kinds of members you want to get from +/// +[Flags] +public enum MemberKind +{ + None = 0, + Public = 1, + Internal = 2, + ExplicitlyImplemented = 4, + DefaultInterfaceProperties = 8 +} + +/// +/// Helper class to get all the public and internal fields and properties from a type. +/// +internal sealed class Reflector +{ + private readonly HashSet collectedPropertyNames = new(); + private readonly HashSet collectedFields = new(); + private readonly List fields = new(); + private List properties = new(); + + public Reflector(Type typeToReflect, MemberKind kind) + { + LoadProperties(typeToReflect, kind); + LoadFields(typeToReflect, kind); + + Members = properties.Concat(fields).ToArray(); + } + + private void LoadProperties(Type typeToReflect, MemberKind kind) + { + while (typeToReflect != null && typeToReflect != typeof(object)) + { + var allProperties = typeToReflect.GetProperties( + BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic); + + AddNormalProperties(kind, allProperties); + + AddExplicitlyImplementedProperties(kind, allProperties); + + AddInterfaceProperties(typeToReflect, kind); + + // Move to the base type + typeToReflect = typeToReflect.BaseType; + } + + properties = properties.Where(x => !x.IsIndexer()).ToList(); + } + + private void AddNormalProperties(MemberKind kind, PropertyInfo[] allProperties) + { + if (kind.HasFlag(MemberKind.Public) || kind.HasFlag(MemberKind.Internal) || + kind.HasFlag(MemberKind.ExplicitlyImplemented)) + { + foreach (var property in allProperties) + { + if (!collectedPropertyNames.Contains(property.Name) && !property.IsExplicitlyImplemented() && + HasVisibility(kind, property)) + { + properties.Add(property); + collectedPropertyNames.Add(property.Name); + } + } + } + } + + private static bool HasVisibility(MemberKind kind, PropertyInfo prop) + { + return (kind.HasFlag(MemberKind.Public) && prop.GetMethod?.IsPublic is true) || + (kind.HasFlag(MemberKind.Internal) && + (prop.GetMethod?.IsAssembly is true || prop.GetMethod?.IsFamilyOrAssembly is true)); + } + + private void AddExplicitlyImplementedProperties(MemberKind kind, PropertyInfo[] allProperties) + { + if (kind.HasFlag(MemberKind.ExplicitlyImplemented)) + { + foreach (var p in allProperties) + { + if (p.IsExplicitlyImplemented()) + { + var name = p.Name.Split('.').Last(); + + if (!collectedPropertyNames.Contains(name)) + { + properties.Add(p); + collectedPropertyNames.Add(name); + } + } + } + } + } + + private void AddInterfaceProperties(Type typeToReflect, MemberKind kind) + { + if (kind.HasFlag(MemberKind.DefaultInterfaceProperties) || typeToReflect.IsInterface) + { + // Add explicitly implemented interface properties (not included above) + var interfaces = typeToReflect.GetInterfaces(); + + foreach (var iface in interfaces) + { + foreach (var prop in iface.GetProperties()) + { + if (!collectedPropertyNames.Contains(prop.Name) && + (!prop.GetMethod.IsAbstract || typeToReflect.IsInterface)) + { + properties.Add(prop); + collectedPropertyNames.Add(prop.Name); + } + } + } + } + } + + private void LoadFields(Type typeToReflect, MemberKind kind) + { + while (typeToReflect != null && typeToReflect != typeof(object)) + { + var files = typeToReflect.GetFields( + BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic); + + foreach (var field in files) + { + if (!collectedFields.Contains(field.Name) && HasVisibility(kind, field)) + { + fields.Add(field); + collectedFields.Add(field.Name); + } + } + + // Move to the base type + typeToReflect = typeToReflect.BaseType; + } + } + + private static bool HasVisibility(MemberKind kind, FieldInfo field) + { + return (kind.HasFlag(MemberKind.Public) && field.IsPublic) || + (kind.HasFlag(MemberKind.Internal) && (field.IsAssembly || field.IsFamilyOrAssembly)); + } + + public MemberInfo[] Members { get; } + + public PropertyInfo[] Properties => properties.ToArray(); + + public FieldInfo[] Fields => fields.ToArray(); +} diff --git a/src/Reflectify/Reflectify.csproj b/src/Reflectify/Reflectify.csproj new file mode 100644 index 0000000..04e40ed --- /dev/null +++ b/src/Reflectify/Reflectify.csproj @@ -0,0 +1,13 @@ + + + + net47;net6.0;netstandard2.0;netstandard2.1 + enable + default + disable + .nuspec + version=$(Version) + REFLECTIFY_COMPILE + + + diff --git a/src/Reflectify/Reflectify.props b/src/Reflectify/Reflectify.props new file mode 100644 index 0000000..574faca --- /dev/null +++ b/src/Reflectify/Reflectify.props @@ -0,0 +1,7 @@ + + + + false + + + diff --git a/tests/.editorconfig b/tests/.editorconfig new file mode 100644 index 0000000..2df66ca --- /dev/null +++ b/tests/.editorconfig @@ -0,0 +1,42 @@ +[*.cs] + +# Purpose: Do not nest type GetProperties. +# Reason: We don't care about this in tests +dotnet_diagnostic.CA1034.severity = none + +# Purpose: Type is a static holder type but is neither static nor NotInheritable +# Reason: We don't care about this in tests +dotnet_diagnostic.CA1052.severity = none + +# Purpose: Remove the underscores from member name +# Reason: We don't care about this in tests +dotnet_diagnostic.CA1707.severity = none + +# Purpose: Type 'SuperClass' can be sealed +# Reason: We don't care about this in tests +dotnet_diagnostic.CA1852.severity = none + +# Purpose: Elements must be documented +# Reason: Not needed for tests +dotnet_diagnostic.SA1600.severity = none + +# Purpose: Field should be private +# Reason: Not needed for tests +dotnet_diagnostic.SA1401.severity = none + +# Purpose: Make class static +# Reason: Not needed for tests +dotnet_diagnostic.RCS1102.severity = none + +# Purpose: Type contains the word 'and' +# Reason: Not needed for tests +dotnet_diagnostic.AV1000.severity = none + +# Purpose: Type hides inherited member +# Reason: Not needed for tests +dotnet_diagnostic.AV1010.severity = none + + + + + diff --git a/tests/Reflectify.Specs/Reflectify.Specs.csproj b/tests/Reflectify.Specs/Reflectify.Specs.csproj new file mode 100644 index 0000000..0a3fd96 --- /dev/null +++ b/tests/Reflectify.Specs/Reflectify.Specs.csproj @@ -0,0 +1,51 @@ + + + + net6.0;netcoreapp3.0;net472 + default + true + true + false + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/tests/Reflectify.Specs/TypeExtensionsSpecs.cs b/tests/Reflectify.Specs/TypeExtensionsSpecs.cs new file mode 100644 index 0000000..495cdb2 --- /dev/null +++ b/tests/Reflectify.Specs/TypeExtensionsSpecs.cs @@ -0,0 +1,297 @@ +using System.Globalization; +using System.Linq; +using System.Reflection; +using FluentAssertions; +using JetBrains.Annotations; +using Xunit; + +namespace Reflectify.Specs; + +public class TypeExtensionsSpecs +{ + public class GetProperties + { + [Fact] + public void Can_get_all_public_explicit_and_default_interface_properties() + { + // Act + var properties = typeof(SuperClass).GetProperties( + MemberKind.Public | MemberKind.ExplicitlyImplemented | MemberKind.DefaultInterfaceProperties); + + // Assert + properties.Should().BeEquivalentTo(new[] + { + new { Name = "NormalProperty", PropertyType = typeof(string) }, + new { Name = "NewProperty", PropertyType = typeof(int) }, + new { Name = "InterfaceProperty", PropertyType = typeof(string) }, + new + { + Name = $"{typeof(IInterfaceWithSingleProperty).FullName!.Replace("+", ".")}.ExplicitlyImplementedProperty", + PropertyType = typeof(string) + }, +#if NETCOREAPP3_0_OR_GREATER + new { Name = "DefaultProperty", PropertyType = typeof(string) } +#endif + }); + } + + [Fact] + public void Can_get_all_properties_from_an_interface() + { + // Act + var properties = typeof(IInterfaceWithDefaultProperty).GetProperties(MemberKind.Public); + + // Assert + properties.Should().BeEquivalentTo(new[] + { + new { Name = "InterfaceProperty", PropertyType = typeof(string) }, + new { Name = "ExplicitlyImplementedProperty", PropertyType = typeof(string) }, +#if NETCOREAPP3_0_OR_GREATER + new { Name = "DefaultProperty", PropertyType = typeof(string) }, +#endif + }); + } + + [Fact] + public void Can_get_normal_public_properties() + { + // Act + var properties = typeof(SuperClass).GetProperties(MemberKind.Public); + + // Assert + properties.Should().BeEquivalentTo(new[] + { + new { Name = "NormalProperty", PropertyType = typeof(string) }, + new { Name = "NewProperty", PropertyType = typeof(int) }, + new { Name = "InterfaceProperty", PropertyType = typeof(string) } + }); + } + + [Fact] + public void Can_get_explicit_properties_only() + { + // Act + var properties = typeof(SuperClass).GetProperties(MemberKind.ExplicitlyImplemented); + + // Assert + properties.Should().BeEquivalentTo(new[] + { + new + { + Name = + $"{typeof(IInterfaceWithSingleProperty).FullName!.Replace("+", ".")}.ExplicitlyImplementedProperty", + PropertyType = typeof(string) + } + }); + } + + [Fact] + public void Prefers_normal_property_over_explicitly_implemented_one() + { + // Act + var properties = typeof(ClassWithExplicitAndNormalProperty).GetProperties( + MemberKind.Public | MemberKind.ExplicitlyImplemented); + + // Assert + properties.Should().BeEquivalentTo(new[] + { + new + { + Name = "ExplicitlyImplementedProperty", + PropertyType = typeof(int) + } + }); + } + +#if NETCOREAPP3_0_OR_GREATER + [Fact] + public void Can_get_default_interface_properties_only() + { + // Act + var properties = typeof(SuperClass).GetProperties(MemberKind.DefaultInterfaceProperties); + + // Assert + properties.Should().BeEquivalentTo(new[] + { + new { Name = "DefaultProperty", PropertyType = typeof(string) } + }); + } +#endif + + [Fact] + public void Can_get_internal_properties() + { + // Act + var properties = typeof(SuperClass).GetProperties(MemberKind.Internal); + + // Assert + properties.Should().BeEquivalentTo(new[] + { + new { Name = "InternalProperty", PropertyType = typeof(bool) }, + new { Name = "InternalProtectedProperty", PropertyType = typeof(bool) } + }); + } + + [Fact] + public void Will_ignore_indexers() + { + // Act + var properties = typeof(ClassWithIndexer).GetProperties(MemberKind.Public); + + // Assert + properties.Should().BeEquivalentTo(new[] + { + new { Name = "Foo", PropertyType = typeof(object) } + }); + } + } + + public class GetFields + { + [Fact] + public void Can_find_public_fields() + { + // Act + var fields = typeof(SuperClass).GetFields(MemberKind.Public); + + // Assert + fields.Should().BeEquivalentTo(new[] + { + new { Name = "NormalField", FieldType = typeof(string) } + }); + } + + [Fact] + public void Can_find_internal_fields() + { + // Act + var fields = typeof(SuperClass).GetFields(MemberKind.Internal); + + // Assert + fields.Should().BeEquivalentTo(new[] + { + new { Name = "InternalField", FieldType = typeof(string) }, + new { Name = "ProtectedInternalField", FieldType = typeof(string) } + }); + } + + [Fact] + public void Can_find_all_fields() + { + // Act + var fields = typeof(SuperClass).GetFields( + MemberKind.Internal | MemberKind.Public); + + // Assert + fields.Should().BeEquivalentTo(new[] + { + new { Name = "NormalField", FieldType = typeof(string) }, + new { Name = "InternalField", FieldType = typeof(string) }, + new { Name = "ProtectedInternalField", FieldType = typeof(string) } + }); + } + } + + public class GetMembers + { + [Fact] + public void Can_find_all_members() + { + // Act + var members = typeof(SuperClass).GetMembers(MemberKind.Public); + + // Assert + members.Should().BeEquivalentTo([ + new { Name = "NormalProperty" }, + new { Name = "NewProperty" }, + new { Name = "InterfaceProperty" }, + new { Name = "NormalField" } + ]); + } + } + + public class PropertyInfoExtensions + { + [Fact] + public void Can_determine_a_property_is_an_indexer() + { + // Act + var indexer = typeof(ClassWithIndexer).GetProperty("Item"); + + // Assert + indexer.IsIndexer().Should().BeTrue(); + } + + [Fact] + public void Can_determine_a_property_is_not_an_indexer() + { + // Act + var indexer = typeof(ClassWithIndexer).GetProperty("Foo"); + + // Assert + indexer.IsIndexer().Should().BeFalse(); + } + } + + private class SuperClass : BaseClass, IInterfaceWithDefaultProperty + { + public string NormalProperty { get; set; } + + public new int NewProperty { get; set; } + + internal bool InternalProperty { get; set; } + + protected internal bool InternalProtectedProperty { get; set; } + + string IInterfaceWithSingleProperty.ExplicitlyImplementedProperty { get; set; } + + public string InterfaceProperty { get; set; } + +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value + public string NormalField; + + internal string InternalField; + + protected internal string ProtectedInternalField; +#pragma warning restore CS0649 // Field is never assigned to, and will always have its default value + } + + private sealed class ClassWithExplicitAndNormalProperty : IInterfaceWithSingleProperty + { + string IInterfaceWithSingleProperty.ExplicitlyImplementedProperty { get; set; } + + [UsedImplicitly] + public int ExplicitlyImplementedProperty { get; set; } + } + + private class BaseClass + { + [UsedImplicitly] + public string NewProperty { get; set; } + } + + private interface IInterfaceWithDefaultProperty : IInterfaceWithSingleProperty + { + [UsedImplicitly] + string InterfaceProperty { get; set; } + +#if NETCOREAPP3_0_OR_GREATER + [UsedImplicitly] + string DefaultProperty => "Default"; +#endif + } + + private interface IInterfaceWithSingleProperty + { + [UsedImplicitly] + string ExplicitlyImplementedProperty { get; set; } + } + + private sealed class ClassWithIndexer + { + [UsedImplicitly] + public object Foo { get; set; } + + public string this[int n] => n.ToString(CultureInfo.InvariantCulture); + } +}