├── .editorconfig ├── .gitattributes ├── .github └── dependabot.yml ├── .gitignore ├── .nuke ├── build.schema.json └── parameters.json ├── Directory.Build.props ├── LICENSE ├── README.md ├── appveyor.yml ├── build.cmd ├── build.gradle ├── build.ps1 ├── build.sh ├── build ├── .editorconfig ├── Build.cs ├── Directory.Build.props ├── Directory.Build.targets ├── ReSharper.Structured.Logging.nuspec ├── _build.csproj └── _build.csproj.DotSettings ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images └── highlighting.png ├── rules ├── AnonymousObjectDestructuringProblem.md ├── ComplexObjectDestructuringProblem.md ├── ComplexObjectInContextDestructuringProblem.md ├── ContextualLoggerProblem.md ├── ExceptionPassedAsTemplateArgumentProblem.md ├── InconsistentContextLogPropertyNaming.md ├── InconsistentLogPropertyNaming.md ├── LogMessageIsSentenceProblem.md ├── PositionalPropertyUsedProblem.md ├── TemplateDuplicatePropertyProblem.md └── TemplateIsNotCompileTimeConstantProblem.md ├── settings.gradle ├── src ├── .idea │ └── .idea.ReSharper.Structured.Logging │ │ └── .idea │ │ ├── .gitignore │ │ ├── .name │ │ ├── encodings.xml │ │ ├── indexLayout.xml │ │ └── vcs.xml ├── .run │ ├── Pack ReSharper.run.xml │ ├── Pack Rider.run.xml │ ├── Test ReSharper.run.xml │ └── Test Rider.run.xml ├── ReSharper.Structured.Logging.sln ├── ReSharper.Structured.Logging │ ├── Analyzer │ │ ├── AnonymousTypeDestructureAnalyzer.cs │ │ ├── CompileTimeConstantTemplateAnalyzer.cs │ │ ├── ComplexObjectDestructureAnalyzer.cs │ │ ├── ContextualLoggerConstructorAnalyzer.cs │ │ ├── ContextualLoggerSerilogFactoryAnalyzer.cs │ │ ├── CorrectExceptionPassingAnalyzer.cs │ │ ├── DuplicatePropertiesTemplateAnalyzer.cs │ │ ├── LogMessageIsSentenceAnalyzer.cs │ │ ├── PositionalPropertiesUsageAnalyzer.cs │ │ └── PropertiesNamingAnalyzer.cs │ ├── Caching │ │ └── TemplateParameterNameAttributeProvider.cs │ ├── Extensions │ │ └── PsiExtensions.cs │ ├── Highlighting │ │ ├── AnonymousObjectWithoutDestructuringWarning.cs │ │ ├── ComplexObjectDestructuringInContextWarning.cs │ │ ├── ComplexObjectDestructuringWarning.cs │ │ ├── ComplexObjectDestructuringWarningBase.cs │ │ ├── ContextualLoggerWarning.cs │ │ ├── DuplicateTemplatePropertyWarning.cs │ │ ├── ExceptionPassedAsTemplateArgumentWarning.cs │ │ ├── InconsistentContextLogPropertyNamingWarning.cs │ │ ├── InconsistentLogPropertyNamingWarning.cs │ │ ├── InconsistentLogPropertyNamingWarningBase.cs │ │ ├── LogMessageIsSentenceWarning.cs │ │ ├── PositionalPropertyUsedWarning.cs │ │ ├── TemplateFormatStringNonExistingArgumentWarning.cs │ │ └── TemplateIsNotCompileTimeConstantWarning.cs │ ├── Models │ │ └── MessageTemplateTokenInformation.cs │ ├── QuickFixes │ │ ├── AddDestructuringToMessageTemplatePropertyFix.cs │ │ ├── RemoveTrailingPeriodFix.cs │ │ ├── RenameContextLogPropertyFix.cs │ │ ├── RenameLogPropertyFix.cs │ │ └── TemplateIsNotCompileTimeConstantFix.cs │ ├── ReSharper.Structured.Logging.Rider.csproj │ ├── ReSharper.Structured.Logging.csproj │ ├── Serilog │ │ ├── Core │ │ │ └── IMessageTemplateParser.cs │ │ ├── Events │ │ │ └── MessageTemplate.cs │ │ └── Parsing │ │ │ ├── Alignment.cs │ │ │ ├── AlignmentDirection.cs │ │ │ ├── Destructuring.cs │ │ │ ├── MessageTemplateParser.cs │ │ │ ├── MessageTemplateToken.cs │ │ │ ├── PropertyToken.cs │ │ │ └── TextToken.cs │ ├── Settings │ │ ├── PropertyNamingType.cs │ │ ├── StructuredLoggingGroup.cs │ │ ├── StructuredLoggingOptionsPage.cs │ │ ├── StructuredLoggingSettings.cs │ │ └── StructuredLoggingSettingsAccessor.cs │ ├── Utils │ │ └── PropertyNameProvider.cs │ ├── Wiki │ │ └── StructuredLoggingWikiDataProvider.cs │ ├── ZoneMarker.cs │ └── app.config └── rider │ └── main │ ├── kotlin │ └── com │ │ └── jetbrains │ │ └── rider │ │ └── settings │ │ ├── StructuredLoggingBundle.kt │ │ └── StructuredLoggingPluginOptionsPage.kt │ └── resources │ ├── META-INF │ └── plugin.xml │ └── messages │ └── StructuredLoggingBundle.properties └── test ├── data ├── Analyzers │ ├── AnonymousTypeDestructure │ │ ├── SerilogWithComplexPropertyWithoutDestructure.cs │ │ ├── SerilogWithComplexPropertyWithoutDestructure.cs.gold │ │ ├── SerilogWithoutDestructure.cs │ │ └── SerilogWithoutDestructure.cs.gold │ ├── ComplexTypeDestructure │ │ ├── SerilogContextExplicitDestructure.cs │ │ ├── SerilogContextExplicitDestructure.cs.gold │ │ ├── SerilogContextNumericWithoutDestructure.cs │ │ ├── SerilogContextNumericWithoutDestructure.cs.gold │ │ ├── SerilogContextWithoutDestructure.cs │ │ ├── SerilogContextWithoutDestructure.cs.gold │ │ ├── SerilogCustomExceptionWithoutDestructure.cs │ │ ├── SerilogCustomExceptionWithoutDestructure.cs.gold │ │ ├── SerilogDictionaryWithoutDestructure.cs │ │ ├── SerilogDictionaryWithoutDestructure.cs.gold │ │ ├── SerilogEnumerableWithoutDestructure.cs │ │ ├── SerilogEnumerableWithoutDestructure.cs.gold │ │ ├── SerilogForceStringWithoutDestructure.cs │ │ ├── SerilogForceStringWithoutDestructure.cs.gold │ │ ├── SerilogNullableWithoutDestructure.cs │ │ ├── SerilogNullableWithoutDestructure.cs.gold │ │ ├── SerilogNumericWithoutDestructure.cs │ │ ├── SerilogNumericWithoutDestructure.cs.gold │ │ ├── SerilogParentWithOverriddenToString.cs │ │ ├── SerilogParentWithOverriddenToString.cs.gold │ │ ├── SerilogWithoutDestructure.cs │ │ └── SerilogWithoutDestructure.cs.gold │ ├── ContextualLoggerConstructor │ │ ├── MicrosoftCorrectContextType.cs │ │ ├── MicrosoftCorrectContextType.cs.gold │ │ ├── MicrosoftWrongContextType.cs │ │ ├── MicrosoftWrongContextType.cs.gold │ │ ├── MicrosoftWrongContextTypeMultipleNamespaces.cs │ │ ├── MicrosoftWrongContextTypeMultipleNamespaces.cs.gold │ │ ├── MicrosoftWrongContextTypeMultipleParameters.cs │ │ └── MicrosoftWrongContextTypeMultipleParameters.cs.gold │ ├── ContextualLoggerSerilogFactory │ │ ├── SerilogCorrectContextType.cs │ │ ├── SerilogCorrectContextType.cs.gold │ │ ├── SerilogWrongContextType.cs │ │ └── SerilogWrongContextType.cs.gold │ ├── CorrectExceptionPassing │ │ ├── SerilogCorrectExceptionPassing.cs │ │ ├── SerilogCorrectExceptionPassing.cs.gold │ │ ├── SerilogIncorrectExceptionPassing.cs │ │ ├── SerilogIncorrectExceptionPassing.cs.gold │ │ ├── SerilogIncorrectExceptionPassingDynamicTemplate.cs │ │ ├── SerilogIncorrectExceptionPassingDynamicTemplate.cs.gold │ │ ├── SerilogMultipleExceptionPassing.cs │ │ └── SerilogMultipleExceptionPassing.cs.gold │ ├── DuplicatePropertiesTemplate │ │ ├── SerilogDuplicateNamedProperty.cs │ │ └── SerilogDuplicateNamedProperty.cs.gold │ ├── LogMessageIsSentence │ │ ├── SerilogNotSentenceMessage.cs │ │ ├── SerilogNotSentenceMessage.cs.gold │ │ ├── SerilogSentenceMessage.cs │ │ └── SerilogSentenceMessage.cs.gold │ ├── PositionalPropertiesUsage │ │ ├── SerilogPositionProperty.cs │ │ └── SerilogPositionProperty.cs.gold │ ├── PropertiesNamingAnalyzer │ │ ├── SerilogContextInterpolatedStringProperty.cs │ │ ├── SerilogContextInterpolatedStringProperty.cs.gold │ │ ├── SerilogContextInvalidNamedProperty.cs │ │ ├── SerilogContextInvalidNamedProperty.cs.gold │ │ ├── SerilogIgnoredInvalidNamedProperty.cs │ │ ├── SerilogIgnoredInvalidNamedProperty.cs.gold │ │ ├── SerilogInvalidElasticNamedProperty.cs │ │ ├── SerilogInvalidElasticNamedProperty.cs.gold │ │ ├── SerilogInvalidNamedProperty.cs │ │ ├── SerilogInvalidNamedProperty.cs.gold │ │ ├── SerilogInvalidNamedPropertyWithDot.cs │ │ ├── SerilogInvalidNamedPropertyWithDot.cs.gold │ │ ├── SerilogInvalidNamedPropertyWithSpace.cs │ │ ├── SerilogInvalidNamedPropertyWithSpace.cs.gold │ │ ├── SerilogInvalidSyntax.cs │ │ ├── SerilogInvalidSyntax.cs.gold │ │ ├── SerilogValidDestructuredNamedProperty.cs │ │ ├── SerilogValidDestructuredNamedProperty.cs.gold │ │ ├── SerilogValidNamedProperty.cs │ │ └── SerilogValidNamedProperty.cs.gold │ └── PropertiesNamingAnalyzerDotNet6 │ │ ├── ZLoggerInvalidNamedProperty.cs │ │ └── ZLoggerInvalidNamedProperty.cs.gold ├── QuickFixes │ ├── AddDestructuringFix │ │ ├── SerilogEscapedString.cs │ │ ├── SerilogEscapedString.cs.gold │ │ ├── SerilogNewAnonymousObject.cs │ │ ├── SerilogNewAnonymousObject.cs.gold │ │ ├── SerilogNewComplexObject.cs │ │ └── SerilogNewComplexObject.cs.gold │ ├── RemoveTrailingPeriodFix │ │ ├── SerilogTrailingPeriod.cs │ │ └── SerilogTrailingPeriod.cs.gold │ ├── RenameContextLogPropertyFix │ │ ├── SerilogContextProperty.cs │ │ └── SerilogContextProperty.cs.gold │ └── RenameLogPropertyFix │ │ ├── SerilogDestructuredProperty.cs │ │ ├── SerilogDestructuredProperty.cs.gold │ │ ├── SerilogProperty.cs │ │ ├── SerilogProperty.cs.gold │ │ ├── SerilogPropertyConcatenated.cs │ │ └── SerilogPropertyConcatenated.cs.gold └── nuget.config └── src ├── Analyzer ├── AnonymousTypeDestructureAnalyzerTests.cs ├── ComplexObjectDestructureAnalyzerTests.cs ├── ContextualLoggerConstructorAnalyzerTests.cs ├── ContextualLoggerSerilogFactoryAnalyzerTests.cs ├── CorrectExceptionPassingAnalyzerTests.cs ├── DuplicatePropertiesTemplateAnalyzerTests.cs ├── LogMessageIsSentenceAnalyzerTests.cs ├── MessageTemplateTests.cs ├── PositionalPropertiesUsageAnalyzerTests.cs ├── PropertiesElasticNamingAnalyzerTests.cs ├── PropertiesIgnoredRegexNamingAnalyzerTests.cs ├── PropertiesNamingAnalyzerDotNet6Tests.cs └── PropertiesNamingAnalyzerTests.cs ├── Constants └── NugetPackages.cs ├── QuickFixes ├── AddDestructuringToMessageTemplatePropertyFixTests.cs ├── QuickFixTestBase.cs ├── RemoveTrailingPeriodFixTests.cs ├── RenameContextLogPropertyFixTests.cs └── RenameLogPropertyFixTests.cs ├── ReSharper.Structured.Logging.Rider.Tests.csproj ├── ReSharper.Structured.Logging.Tests.csproj ├── TestEnvironment.cs └── app.config /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | dotnet_separate_import_directive_groups = true 9 | dotnet_sort_system_directives_first = true 10 | indent_size = 4 11 | 12 | [{*.csproj,*.json,*.yml,*.xml,*.props,*.nuspec,*.config}] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gradle" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | 8 | - package-ecosystem: "nuget" 9 | directory: "/build/" 10 | schedule: 11 | interval: "monthly" 12 | -------------------------------------------------------------------------------- /.nuke/build.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "Host": { 5 | "type": "string", 6 | "enum": [ 7 | "AppVeyor", 8 | "AzurePipelines", 9 | "Bamboo", 10 | "Bitbucket", 11 | "Bitrise", 12 | "GitHubActions", 13 | "GitLab", 14 | "Jenkins", 15 | "Rider", 16 | "SpaceAutomation", 17 | "TeamCity", 18 | "Terminal", 19 | "TravisCI", 20 | "VisualStudio", 21 | "VSCode" 22 | ] 23 | }, 24 | "ExecutableTarget": { 25 | "type": "string", 26 | "enum": [ 27 | "Clean", 28 | "Compile", 29 | "Pack", 30 | "PackRiderPlugin", 31 | "Sonar", 32 | "SonarBegin", 33 | "Test", 34 | "UpdateBuildVersion", 35 | "UploadReSharperArtifact", 36 | "UploadRiderArtifact" 37 | ] 38 | }, 39 | "Verbosity": { 40 | "type": "string", 41 | "description": "", 42 | "enum": [ 43 | "Verbose", 44 | "Normal", 45 | "Minimal", 46 | "Quiet" 47 | ] 48 | }, 49 | "NukeBuild": { 50 | "properties": { 51 | "Continue": { 52 | "type": "boolean", 53 | "description": "Indicates to continue a previously failed build attempt" 54 | }, 55 | "Help": { 56 | "type": "boolean", 57 | "description": "Shows the help text for this build assembly" 58 | }, 59 | "Host": { 60 | "description": "Host for execution. Default is 'automatic'", 61 | "$ref": "#/definitions/Host" 62 | }, 63 | "NoLogo": { 64 | "type": "boolean", 65 | "description": "Disables displaying the NUKE logo" 66 | }, 67 | "Partition": { 68 | "type": "string", 69 | "description": "Partition to use on CI" 70 | }, 71 | "Plan": { 72 | "type": "boolean", 73 | "description": "Shows the execution plan (HTML)" 74 | }, 75 | "Profile": { 76 | "type": "array", 77 | "description": "Defines the profiles to load", 78 | "items": { 79 | "type": "string" 80 | } 81 | }, 82 | "Root": { 83 | "type": "string", 84 | "description": "Root directory during build execution" 85 | }, 86 | "Skip": { 87 | "type": "array", 88 | "description": "List of targets to be skipped. Empty list skips all dependencies", 89 | "items": { 90 | "$ref": "#/definitions/ExecutableTarget" 91 | } 92 | }, 93 | "Target": { 94 | "type": "array", 95 | "description": "List of targets to be invoked. Default is '{default_target}'", 96 | "items": { 97 | "$ref": "#/definitions/ExecutableTarget" 98 | } 99 | }, 100 | "Verbosity": { 101 | "description": "Logging verbosity during build execution. Default is 'Normal'", 102 | "$ref": "#/definitions/Verbosity" 103 | } 104 | } 105 | } 106 | }, 107 | "allOf": [ 108 | { 109 | "properties": { 110 | "Configuration": { 111 | "type": "string" 112 | }, 113 | "IsRiderHost": { 114 | "type": "boolean" 115 | }, 116 | "Solution": { 117 | "type": "string", 118 | "description": "Path to a solution file that is automatically loaded" 119 | } 120 | } 121 | }, 122 | { 123 | "$ref": "#/definitions/NukeBuild" 124 | } 125 | ] 126 | } 127 | -------------------------------------------------------------------------------- /.nuke/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./build.schema.json", 3 | "Solution": "src/ReSharper.Structured.Logging.sln" 4 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 2025.1.0 6 | 7 | 8 | 9 | $(MSBuildWarningsAsMessages);MSB3277;MSB3270 10 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Oleg Shevchenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReSharper Structured Logging 2 | [![Build status](https://ci.appveyor.com/api/projects/status/c4riih64hbd4sebw?svg=true)](https://ci.appveyor.com/project/olsh/resharper-structured-logging) 3 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=resharper-structured-logging&metric=alert_status)](https://sonarcloud.io/dashboard?id=resharper-structured-logging) 4 | 5 | An extension for ReSharper and Rider IDE that highlights structured logging templates and contains some useful analyzers 6 | 7 | > [The highlighting is a built-in feature starting from R#/Rider 2021.2](https://github.com/olsh/resharper-structured-logging/issues/35#issuecomment-900883583), 8 | > but the extension still contains some useful analyzers that are not (yet) implemented by JetBrains team 9 | 10 | At the moment it supports Serilog, NLog, Microsoft.Extensions.Logging and ZLogger 11 | 12 | ## Installation ReSharper 13 | 14 | Look for `Structured Logging` in ReSharper -> Extension manager. 15 | [JetBrains Plugins Repository](https://plugins.jetbrains.com/plugin/12083-structured-logging) 16 | 17 | ## Installation Rider 18 | 19 | Look for `Structured Logging` in Settings -> Plugins -> Browse repositories. 20 | [JetBrains Plugins Repository](https://plugins.jetbrains.com/plugin/12832-structured-logging) 21 | 22 | ## Highlighting 23 | 24 | ![Highlighting](https://github.com/olsh/resharper-structured-logging/raw/master/images/highlighting.png) 25 | 26 | ## Analyzers 27 | 28 | * [Anonymous object is not destructured](rules/AnonymousObjectDestructuringProblem.md) 29 | * [Complex object is not destructured](rules/ComplexObjectDestructuringProblem.md) 30 | * [Complex object is not destructured in context](rules/ComplexObjectInContextDestructuringProblem.md) 31 | * [Contextual logger mismatch](rules/ContextualLoggerProblem.md) 32 | * [Exception passed as a template argument](rules/ExceptionPassedAsTemplateArgumentProblem.md) 33 | * [Duplicate properties in a template](rules/TemplateDuplicatePropertyProblem.md) 34 | * [Template should be a compile-time constant](rules/TemplateIsNotCompileTimeConstantProblem.md) 35 | * [Prefer named properties instead of positional ones](rules/PositionalPropertyUsedProblem.md) 36 | * [Inconsistent log property naming](rules/InconsistentLogPropertyNaming.md) 37 | * [Inconsistent log property naming in context](rules/InconsistentContextLogPropertyNaming.md) 38 | * [Log event messages should be fragments, not sentences](rules/LogMessageIsSentenceProblem.md) 39 | 40 | ## Turning Off Analyzers 41 | 42 | Individual analyzers can be disabled as needed either through code comments or by adding a line to a project's 43 | `.editorconfig` file. 44 | 45 | ### Turning Off Via Comments 46 | 47 | The analyzer name can be used as-is in a ReSharper comment to disable an analyzer on a per-file or per-line basis. 48 | For example: 49 | 50 | ```csharp 51 | // ReSharper disable once TemplateIsNotCompileTimeConstantProblem 52 | ``` 53 | 54 | ### Turning Off Via `.editorconfig` 55 | 56 | To disable an analyzer for an entire directory, you can add a line to a `.editorconfig` file 57 | ([learn more](https://editorconfig.org)). In this case, the analyzer name needs to be converted to `snake_case`, prefixed 58 | with `resharper_` and suffixed with `_highlighting`. For example: 59 | 60 | ```editorconfig 61 | resharper_template_is_not_compile_time_constant_problem_highlighting = none 62 | ``` 63 | 64 | ## Credits 65 | 66 | Inspired by [SerilogAnalyzer](https://github.com/Suchiman/SerilogAnalyzer) 67 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | 3 | skip_branch_with_pr: true 4 | skip_tags: true 5 | 6 | install: 7 | - SET JAVA_HOME=C:\Program Files\Java\jdk17 8 | - SET PATH=%JAVA_HOME%\bin;%PATH% 9 | build_script: 10 | - cmd: >- 11 | build.cmd update-build-version 12 | 13 | build.cmd upload-resharper-artifact --configuration Release 14 | 15 | build.cmd upload-rider-artifact --configuration Release --is-rider-host 16 | 17 | build.cmd sonar --configuration Release 18 | test: false 19 | 20 | cache: 21 | - '%USERPROFILE%\.sonar\cache' 22 | - '.gradle -> build.gradle' 23 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | :; set -eo pipefail 2 | :; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 3 | :; ${SCRIPT_DIR}/build.sh "$@" 4 | :; exit $? 5 | 6 | @ECHO OFF 7 | powershell -ExecutionPolicy ByPass -NoProfile "%~dp0build.ps1" %* 8 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.jetbrains.kotlin.jvm' version '2.1.21' 3 | id 'org.jetbrains.intellij.platform' version '2.6.0' 4 | } 5 | 6 | buildDir = 'gradle-build' 7 | version = ext.PluginVersion 8 | 9 | compileKotlin { 10 | kotlinOptions { 11 | jvmTarget = "17" 12 | } 13 | } 14 | 15 | sourceSets { 16 | main { 17 | java.srcDir 'src/rider/main/kotlin' 18 | resources.srcDir 'src/rider/main/resources' 19 | } 20 | } 21 | 22 | repositories { 23 | mavenCentral() 24 | intellijPlatform { 25 | defaultRepositories() 26 | jetbrainsRuntime() 27 | } 28 | } 29 | 30 | dependencies { 31 | intellijPlatform { 32 | rider(ProductVersion, false) 33 | jetbrainsRuntime() 34 | } 35 | } 36 | 37 | intellijPlatform { 38 | pluginConfiguration { 39 | name = rootProject.name 40 | } 41 | } 42 | 43 | prepareSandbox { 44 | def dotNetFiles = [ 45 | "${DotNetOutputDirectory}/${DotNetProjectName}.dll", 46 | "${DotNetOutputDirectory}/${DotNetProjectName}.pdb", 47 | ] 48 | 49 | dotNetFiles.forEach { f -> 50 | def file = file(f) 51 | from(file) { 52 | into "${rootProject.name}/dotnet" 53 | } 54 | } 55 | 56 | doLast { 57 | dotNetFiles.forEach { f -> 58 | if (!file(f).exists()) { 59 | throw new RuntimeException("File $f does not exist") 60 | } 61 | } 62 | } 63 | } 64 | 65 | wrapper { 66 | gradleVersion = '8.13' 67 | distributionType = Wrapper.DistributionType.ALL 68 | distributionUrl = "https://cache-redirector.jetbrains.com/services.gradle.org/distributions/gradle-${gradleVersion}-all.zip" 69 | } 70 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 4 | [string[]]$BuildArguments 5 | ) 6 | 7 | Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)" 8 | 9 | Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 } 10 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 11 | 12 | ########################################################################### 13 | # CONFIGURATION 14 | ########################################################################### 15 | 16 | $BuildProjectFile = "$PSScriptRoot\build\_build.csproj" 17 | $TempDirectory = "$PSScriptRoot\\.nuke\temp" 18 | 19 | $DotNetGlobalFile = "$PSScriptRoot\\global.json" 20 | $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" 21 | $DotNetChannel = "Current" 22 | 23 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 24 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 25 | $env:DOTNET_MULTILEVEL_LOOKUP = 0 26 | 27 | ########################################################################### 28 | # EXECUTION 29 | ########################################################################### 30 | 31 | function ExecSafe([scriptblock] $cmd) { 32 | & $cmd 33 | if ($LASTEXITCODE) { exit $LASTEXITCODE } 34 | } 35 | 36 | # If dotnet CLI is installed globally and it matches requested version, use for execution 37 | if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` 38 | $(dotnet --version) -and $LASTEXITCODE -eq 0) { 39 | $env:DOTNET_EXE = (Get-Command "dotnet").Path 40 | } 41 | else { 42 | # Download install script 43 | $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" 44 | New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null 45 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 46 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) 47 | 48 | # If global.json exists, load expected version 49 | if (Test-Path $DotNetGlobalFile) { 50 | $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) 51 | if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { 52 | $DotNetVersion = $DotNetGlobal.sdk.version 53 | } 54 | } 55 | 56 | # Install by channel or version 57 | $DotNetDirectory = "$TempDirectory\dotnet-win" 58 | if (!(Test-Path variable:DotNetVersion)) { 59 | ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } 60 | } else { 61 | ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } 62 | } 63 | $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" 64 | } 65 | 66 | Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)" 67 | 68 | ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } 69 | ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } 70 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | bash --version 2>&1 | head -n 1 4 | 5 | set -eo pipefail 6 | SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 7 | 8 | ########################################################################### 9 | # CONFIGURATION 10 | ########################################################################### 11 | 12 | BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj" 13 | TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp" 14 | 15 | DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json" 16 | DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh" 17 | DOTNET_CHANNEL="Current" 18 | 19 | export DOTNET_CLI_TELEMETRY_OPTOUT=1 20 | export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 21 | export DOTNET_MULTILEVEL_LOOKUP=0 22 | 23 | ########################################################################### 24 | # EXECUTION 25 | ########################################################################### 26 | 27 | function FirstJsonValue { 28 | perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}" 29 | } 30 | 31 | # If dotnet CLI is installed globally and it matches requested version, use for execution 32 | if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then 33 | export DOTNET_EXE="$(command -v dotnet)" 34 | else 35 | # Download install script 36 | DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh" 37 | mkdir -p "$TEMP_DIRECTORY" 38 | curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL" 39 | chmod +x "$DOTNET_INSTALL_FILE" 40 | 41 | # If global.json exists, load expected version 42 | if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then 43 | DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")") 44 | if [[ "$DOTNET_VERSION" == "" ]]; then 45 | unset DOTNET_VERSION 46 | fi 47 | fi 48 | 49 | # Install by channel or version 50 | DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix" 51 | if [[ -z ${DOTNET_VERSION+x} ]]; then 52 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path 53 | else 54 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path 55 | fi 56 | export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet" 57 | fi 58 | 59 | echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)" 60 | 61 | "$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet 62 | "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@" 63 | -------------------------------------------------------------------------------- /build/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | dotnet_style_qualification_for_field = false:warning 3 | dotnet_style_qualification_for_property = false:warning 4 | dotnet_style_qualification_for_method = false:warning 5 | dotnet_style_qualification_for_event = false:warning 6 | dotnet_style_require_accessibility_modifiers = never:warning 7 | 8 | csharp_style_expression_bodied_methods = true:silent 9 | csharp_style_expression_bodied_properties = true:warning 10 | csharp_style_expression_bodied_indexers = true:warning 11 | csharp_style_expression_bodied_accessors = true:warning 12 | -------------------------------------------------------------------------------- /build/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /build/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /build/ReSharper.Structured.Logging.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $project$ 6 | 1.0.0 7 | Structured Logging 8 | Oleg Shevchenko 9 | false 10 | MIT 11 | https://github.com/olsh/resharper-structured-logging 12 | Contains some useful analyzers for structured logging. Supports Serilog, NLog, and Microsoft.Extensions.Logging. 13 | https://github.com/olsh/resharper-structured-logging/releases 14 | resharper serilog nlog templates logging structuredlogging 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /build/_build.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | CS0649;CS0169 8 | .. 9 | .. 10 | 1 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /build/_build.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | DO_NOT_SHOW 3 | DO_NOT_SHOW 4 | DO_NOT_SHOW 5 | DO_NOT_SHOW 6 | Implicit 7 | Implicit 8 | ExpressionBody 9 | 0 10 | NEXT_LINE 11 | True 12 | False 13 | 120 14 | IF_OWNER_IS_SINGLE_LINE 15 | WRAP_IF_LONG 16 | False 17 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 18 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 19 | <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> 20 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> 21 | True 22 | True 23 | True 24 | True 25 | True 26 | True 27 | True 28 | True 29 | True 30 | True 31 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Any property can be overwritten from command-line via 2 | # -P= 3 | 4 | PluginVersion=_PLACEHOLDER_ 5 | ProductVersion=_PLACEHOLDER_ 6 | DotNetOutputDirectory=./src/ReSharper.Structured.Logging/bin/ReSharper.Structured.Logging.Rider/Debug 7 | DotNetProjectName=ReSharper.Structured.Logging.Rider 8 | 9 | # We need to disable Gradle daemon after build otherwise the build hangs on build server 10 | # https://github.com/appveyor/ci/issues/1745 11 | org.gradle.daemon=false 12 | 13 | # We need it to avoid bundle Kotlin jars into plugin 14 | kotlin.stdlib.default.dependency=false 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olsh/resharper-structured-logging/db8bd99bbd9089a7b842a1c008afdf319936dfdf/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://cache-redirector.jetbrains.com/services.gradle.org/distributions/gradle-8.13-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /images/highlighting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olsh/resharper-structured-logging/db8bd99bbd9089a7b842a1c008afdf319936dfdf/images/highlighting.png -------------------------------------------------------------------------------- /rules/AnonymousObjectDestructuringProblem.md: -------------------------------------------------------------------------------- 1 | #### Anonymous objects must be destructured 2 | 3 | Noncompliant Code Examples: 4 | ```csharp 5 | Log.Error("Processed {Position}", new { x = 4, y = 2}); 6 | ``` 7 | 8 | Compliant Solution: 9 | ```csharp 10 | Log.Error("Processed {@Position}", new { x = 4, y = 2}); 11 | ``` 12 | -------------------------------------------------------------------------------- /rules/ComplexObjectDestructuringProblem.md: -------------------------------------------------------------------------------- 1 | #### Complex objects with default `ToString()` implementation probably need to be destructured 2 | 3 | Noncompliant Code Example: 4 | ```csharp 5 | class User 6 | { 7 | public int Age { get; set; } 8 | } 9 | 10 | ... 11 | 12 | Log.Information("The user is {MyUser}", new User()); 13 | ``` 14 | 15 | Compliant Solution: 16 | ```csharp 17 | class User 18 | { 19 | public int Age { get; set; } 20 | } 21 | 22 | ... 23 | 24 | Log.Information("The user is {@MyUser}", new User()); 25 | 26 | // or 27 | 28 | Log.Information("The user is {$MyUser}", new User()); 29 | ``` 30 | -------------------------------------------------------------------------------- /rules/ComplexObjectInContextDestructuringProblem.md: -------------------------------------------------------------------------------- 1 | #### Complex objects with default `ToString()` implementation probably need to be destructured 2 | 3 | Noncompliant Code Example: 4 | ```csharp 5 | class User 6 | { 7 | public int Age { get; set; } 8 | } 9 | 10 | ... 11 | 12 | LogContext.PushProperty("User", new User()); 13 | ``` 14 | 15 | Compliant Solution: 16 | ```csharp 17 | class User 18 | { 19 | public int Age { get; set; } 20 | } 21 | 22 | ... 23 | 24 | LogContext.PushProperty("User", new User(), true); 25 | 26 | // or 27 | 28 | LogContext.PushProperty("User", new User(), false); 29 | ``` 30 | -------------------------------------------------------------------------------- /rules/ContextualLoggerProblem.md: -------------------------------------------------------------------------------- 1 | #### Incorrect type is used for contextual logger 2 | 3 | Noncompliant Code Examples: 4 | ```csharp 5 | class A 6 | { 7 | private static readonly ILogger Logger = Logger.ForContext(); 8 | } 9 | 10 | class B {} 11 | ``` 12 | 13 | ```csharp 14 | class A 15 | { 16 | ILogger _log; 17 | 18 | public A(ILogger log) 19 | { 20 | _log = log; 21 | } 22 | } 23 | 24 | class B { } 25 | ``` 26 | 27 | Compliant Solution: 28 | ```csharp 29 | class A 30 | { 31 | private static readonly ILogger Logger = Logger.ForContext(); 32 | } 33 | 34 | class B {} 35 | ``` 36 | 37 | ```csharp 38 | class A 39 | { 40 | ILogger _log; 41 | 42 | public A(ILogger log) 43 | { 44 | _log = log; 45 | } 46 | } 47 | 48 | class B {} 49 | ``` 50 | -------------------------------------------------------------------------------- /rules/ExceptionPassedAsTemplateArgumentProblem.md: -------------------------------------------------------------------------------- 1 | #### Exception passed as a template argument 2 | 3 | Noncompliant Code Example: 4 | ```csharp 5 | catch (Exception exception) 6 | { 7 | Log.Error(ex, "Disk quota {Quota} MB exceeded {Exception}", quota, exception); 8 | } 9 | ``` 10 | 11 | Compliant Solution: 12 | ```csharp 13 | catch (Exception exception) 14 | { 15 | Log.Error(exception, "Disk quota {Quota} MB exceeded", quota); 16 | } 17 | ``` 18 | -------------------------------------------------------------------------------- /rules/InconsistentContextLogPropertyNaming.md: -------------------------------------------------------------------------------- 1 | #### Inconsistent log property naming in context (can be configured in the extension settings) 2 | 3 | Noncompliant Code Examples: 4 | ```csharp 5 | LogContext.PushProperty("property_name", 1); 6 | ``` 7 | 8 | Compliant Solution: 9 | ```csharp 10 | LogContext.PushProperty("PropertyName", 1); 11 | ``` 12 | -------------------------------------------------------------------------------- /rules/InconsistentLogPropertyNaming.md: -------------------------------------------------------------------------------- 1 | #### Inconsistent log property naming (can be configured in the extension settings) 2 | 3 | Noncompliant Code Examples: 4 | ```csharp 5 | Log.Error("Processed {property_name}", 1); 6 | ``` 7 | 8 | Compliant Solution: 9 | ```csharp 10 | Log.Error("Processed {PropertyName}", 1); 11 | ``` 12 | -------------------------------------------------------------------------------- /rules/LogMessageIsSentenceProblem.md: -------------------------------------------------------------------------------- 1 | #### Log event messages should be fragments, not sentences 2 | 3 | [https://benfoster.io/blog/serilog-best-practices/#message-template-recommendations](https://benfoster.io/blog/serilog-best-practices/#message-template-recommendations) 4 | 5 | Noncompliant Code Examples: 6 | ```csharp 7 | Log.Error("Disk quota {Quota} MB exceeded by {User}.", quota, user); 8 | ``` 9 | 10 | 11 | Compliant Solution: 12 | ```csharp 13 | Log.Error("Disk quota {Quota} MB exceeded by {User}", quota, user); 14 | ``` 15 | -------------------------------------------------------------------------------- /rules/PositionalPropertyUsedProblem.md: -------------------------------------------------------------------------------- 1 | #### Prefer named properties instead of positional ones 2 | 3 | Noncompliant Code Examples: 4 | ```csharp 5 | Log.Error("Disk quota {0} MB exceeded by {1}", quota, user); 6 | ``` 7 | 8 | 9 | Compliant Solution: 10 | ```csharp 11 | Log.Error("Disk quota {Quota} MB exceeded by {User}", quota, user); 12 | ``` 13 | -------------------------------------------------------------------------------- /rules/TemplateDuplicatePropertyProblem.md: -------------------------------------------------------------------------------- 1 | #### Duplicate template property 2 | 3 | Noncompliant Code Example: 4 | ```csharp 5 | Log.Error("Disk quota {Quota} MB exceeded by {Quota}", quota, user); 6 | ``` 7 | 8 | Compliant Solution: 9 | ```csharp 10 | Log.Error("Disk quota {Quota} MB exceeded by {User}", quota, user); 11 | ``` 12 | -------------------------------------------------------------------------------- /rules/TemplateIsNotCompileTimeConstantProblem.md: -------------------------------------------------------------------------------- 1 | #### Message template is not a compile time constant 2 | 3 | Noncompliant Code Examples: 4 | ```csharp 5 | Log.Error($"Disk quota {quota} MB exceeded by {user}"); 6 | ``` 7 | 8 | ```csharp 9 | Log.Error(string.Format("Disk quota {0} MB exceeded by {1}", quota, user)); 10 | ``` 11 | 12 | 13 | Compliant Solution: 14 | ```csharp 15 | Log.Error("Disk quota {Quota} MB exceeded by {User}", quota, user); 16 | ``` 17 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'rider-structured-logging' 2 | -------------------------------------------------------------------------------- /src/.idea/.idea.ReSharper.Structured.Logging/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /modules.xml 6 | /contentModel.xml 7 | /projectSettingsUpdater.xml 8 | /.idea.ReSharper.Structured.Logging.iml 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | # GitHub Copilot persisted chat sessions 15 | /copilot/chatSessions 16 | -------------------------------------------------------------------------------- /src/.idea/.idea.ReSharper.Structured.Logging/.idea/.name: -------------------------------------------------------------------------------- 1 | ReSharper.Structured.Logging -------------------------------------------------------------------------------- /src/.idea/.idea.ReSharper.Structured.Logging/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/.idea/.idea.ReSharper.Structured.Logging/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/.idea/.idea.ReSharper.Structured.Logging/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/.run/Pack ReSharper.run.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 20 | -------------------------------------------------------------------------------- /src/.run/Pack Rider.run.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 20 | -------------------------------------------------------------------------------- /src/.run/Test ReSharper.run.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 20 | -------------------------------------------------------------------------------- /src/.run/Test Rider.run.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 20 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32126.317 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReSharper.Structured.Logging", "ReSharper.Structured.Logging\ReSharper.Structured.Logging.csproj", "{0B29307F-4E38-4611-BA0D-408A4B4F4E15}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReSharper.Structured.Logging.Tests", "..\test\src\ReSharper.Structured.Logging.Tests.csproj", "{D07C40A7-39BE-4725-889F-FF2F2B781442}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8F7509A6-86AF-48A9-BD71-464ED879D8FA}" 11 | ProjectSection(SolutionItems) = preProject 12 | ..\.editorconfig = ..\.editorconfig 13 | ..\appveyor.yml = ..\appveyor.yml 14 | ..\build.gradle = ..\build.gradle 15 | ..\Directory.Build.props = ..\Directory.Build.props 16 | rider\main\resources\META-INF\plugin.xml = rider\main\resources\META-INF\plugin.xml 17 | ..\README.md = ..\README.md 18 | ..\.gitignore = ..\.gitignore 19 | ..\.github\dependabot.yml = ..\.github\dependabot.yml 20 | EndProjectSection 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReSharper.Structured.Logging.Rider", "ReSharper.Structured.Logging\ReSharper.Structured.Logging.Rider.csproj", "{81C42342-6FD5-48D0-B45F-FD364B170FC1}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReSharper.Structured.Logging.Rider.Tests", "..\test\src\ReSharper.Structured.Logging.Rider.Tests.csproj", "{6BC99E0B-6362-4A62-84F1-103B93896294}" 25 | EndProject 26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Rules", "Rules", "{D93C6901-5685-495B-A790-6C1467978205}" 27 | ProjectSection(SolutionItems) = preProject 28 | ..\rules\AnonymousObjectDestructuringProblem.md = ..\rules\AnonymousObjectDestructuringProblem.md 29 | ..\rules\ComplexObjectDestructuringProblem.md = ..\rules\ComplexObjectDestructuringProblem.md 30 | ..\rules\ComplexObjectInContextDestructuringProblem.md = ..\rules\ComplexObjectInContextDestructuringProblem.md 31 | ..\rules\ContextualLoggerProblem.md = ..\rules\ContextualLoggerProblem.md 32 | ..\rules\ExceptionPassedAsTemplateArgumentProblem.md = ..\rules\ExceptionPassedAsTemplateArgumentProblem.md 33 | ..\rules\InconsistentContextLogPropertyNaming.md = ..\rules\InconsistentContextLogPropertyNaming.md 34 | ..\rules\InconsistentLogPropertyNaming.md = ..\rules\InconsistentLogPropertyNaming.md 35 | ..\rules\LogMessageIsSentenceProblem.md = ..\rules\LogMessageIsSentenceProblem.md 36 | ..\rules\PositionalPropertyUsedProblem.md = ..\rules\PositionalPropertyUsedProblem.md 37 | ..\rules\TemplateDuplicatePropertyProblem.md = ..\rules\TemplateDuplicatePropertyProblem.md 38 | ..\rules\TemplateIsNotCompileTimeConstantProblem.md = ..\rules\TemplateIsNotCompileTimeConstantProblem.md 39 | EndProjectSection 40 | EndProject 41 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "..\build\_build.csproj", "{A7717BDB-133B-433E-97CF-624B746FDDC8}" 42 | EndProject 43 | Global 44 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 45 | Debug|Any CPU = Debug|Any CPU 46 | Release|Any CPU = Release|Any CPU 47 | EndGlobalSection 48 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 49 | {0B29307F-4E38-4611-BA0D-408A4B4F4E15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {0B29307F-4E38-4611-BA0D-408A4B4F4E15}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {0B29307F-4E38-4611-BA0D-408A4B4F4E15}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {0B29307F-4E38-4611-BA0D-408A4B4F4E15}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {D07C40A7-39BE-4725-889F-FF2F2B781442}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {D07C40A7-39BE-4725-889F-FF2F2B781442}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {D07C40A7-39BE-4725-889F-FF2F2B781442}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {D07C40A7-39BE-4725-889F-FF2F2B781442}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {81C42342-6FD5-48D0-B45F-FD364B170FC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 58 | {81C42342-6FD5-48D0-B45F-FD364B170FC1}.Debug|Any CPU.Build.0 = Debug|Any CPU 59 | {81C42342-6FD5-48D0-B45F-FD364B170FC1}.Release|Any CPU.ActiveCfg = Release|Any CPU 60 | {81C42342-6FD5-48D0-B45F-FD364B170FC1}.Release|Any CPU.Build.0 = Release|Any CPU 61 | {6BC99E0B-6362-4A62-84F1-103B93896294}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 62 | {6BC99E0B-6362-4A62-84F1-103B93896294}.Debug|Any CPU.Build.0 = Debug|Any CPU 63 | {6BC99E0B-6362-4A62-84F1-103B93896294}.Release|Any CPU.ActiveCfg = Release|Any CPU 64 | {6BC99E0B-6362-4A62-84F1-103B93896294}.Release|Any CPU.Build.0 = Release|Any CPU 65 | {A7717BDB-133B-433E-97CF-624B746FDDC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 66 | {A7717BDB-133B-433E-97CF-624B746FDDC8}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | EndGlobalSection 68 | GlobalSection(SolutionProperties) = preSolution 69 | HideSolutionNode = FALSE 70 | EndGlobalSection 71 | GlobalSection(ExtensibilityGlobals) = postSolution 72 | SolutionGuid = {2861CBD1-55F1-4AF9-8F6D-F2C345E8BB03} 73 | EndGlobalSection 74 | EndGlobal 75 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Analyzer/AnonymousTypeDestructureAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | using JetBrains.ReSharper.Feature.Services.Daemon; 5 | using JetBrains.ReSharper.Psi; 6 | using JetBrains.ReSharper.Psi.CodeAnnotations; 7 | using JetBrains.ReSharper.Psi.CSharp.Tree; 8 | 9 | using ReSharper.Structured.Logging.Caching; 10 | using ReSharper.Structured.Logging.Extensions; 11 | using ReSharper.Structured.Logging.Highlighting; 12 | using ReSharper.Structured.Logging.Serilog.Parsing; 13 | 14 | namespace ReSharper.Structured.Logging.Analyzer 15 | { 16 | [ElementProblemAnalyzer(typeof(IInvocationExpression))] 17 | public class AnonymousTypeDestructureAnalyzer : ElementProblemAnalyzer 18 | { 19 | private readonly MessageTemplateParser _messageTemplateParser; 20 | 21 | private readonly Lazy _templateParameterNameAttributeProvider; 22 | 23 | public AnonymousTypeDestructureAnalyzer(MessageTemplateParser messageTemplateParser, CodeAnnotationsCache codeAnnotationsCache) 24 | { 25 | _messageTemplateParser = messageTemplateParser; 26 | _templateParameterNameAttributeProvider = codeAnnotationsCache.GetLazyProvider(); 27 | } 28 | 29 | protected override void Run( 30 | IInvocationExpression element, 31 | ElementProblemAnalyzerData data, 32 | IHighlightingConsumer consumer) 33 | { 34 | var templateArgument = element.GetTemplateArgument(_templateParameterNameAttributeProvider.Value); 35 | if (templateArgument == null) 36 | { 37 | return; 38 | } 39 | 40 | var anonymousObjectsArguments = element.ArgumentList.Arguments 41 | .Where(a => a.Value is IAnonymousObjectCreationExpression) 42 | .ToArray(); 43 | if (anonymousObjectsArguments.Length == 0) 44 | { 45 | return; 46 | } 47 | 48 | var templateText = templateArgument.TryGetTemplateText(); 49 | if (templateText == null) 50 | { 51 | return; 52 | } 53 | 54 | var messageTemplate = _messageTemplateParser.Parse(templateText); 55 | if (messageTemplate.NamedProperties == null) 56 | { 57 | return; 58 | } 59 | 60 | var templateArgumentIndex = templateArgument.IndexOf(); 61 | foreach (var argument in anonymousObjectsArguments) 62 | { 63 | var index = argument.IndexOf() - templateArgumentIndex - 1; 64 | if (index < messageTemplate.NamedProperties.Length) 65 | { 66 | var namedProperty = messageTemplate.NamedProperties[index]; 67 | if (namedProperty.Destructuring != Destructuring.Default) 68 | { 69 | continue; 70 | } 71 | 72 | var tokenInformation = templateArgument.GetTokenInformation(namedProperty); 73 | consumer.AddHighlighting(new AnonymousObjectDestructuringWarning(tokenInformation)); 74 | } 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Analyzer/CompileTimeConstantTemplateAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using JetBrains.ReSharper.Feature.Services.Daemon; 4 | using JetBrains.ReSharper.Psi.CodeAnnotations; 5 | using JetBrains.ReSharper.Psi.CSharp.Tree; 6 | 7 | using ReSharper.Structured.Logging.Caching; 8 | using ReSharper.Structured.Logging.Extensions; 9 | using ReSharper.Structured.Logging.Highlighting; 10 | 11 | namespace ReSharper.Structured.Logging.Analyzer 12 | { 13 | [ElementProblemAnalyzer(typeof(IInvocationExpression))] 14 | public class CompileTimeConstantTemplateAnalyzer : ElementProblemAnalyzer 15 | { 16 | private readonly Lazy _templateParameterNameAttributeProvider; 17 | 18 | public CompileTimeConstantTemplateAnalyzer(CodeAnnotationsCache codeAnnotationsCache) 19 | { 20 | _templateParameterNameAttributeProvider = codeAnnotationsCache.GetLazyProvider(); 21 | } 22 | 23 | protected override void Run( 24 | IInvocationExpression element, 25 | ElementProblemAnalyzerData data, 26 | IHighlightingConsumer consumer) 27 | { 28 | var templateArgument = element.GetTemplateArgument(_templateParameterNameAttributeProvider.Value); 29 | if (templateArgument?.Value == null) 30 | { 31 | return; 32 | } 33 | 34 | if (templateArgument.Value.IsConstantValue()) 35 | { 36 | return; 37 | } 38 | 39 | consumer.AddHighlighting(new TemplateIsNotCompileTimeConstantWarning(element, templateArgument)); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Analyzer/ContextualLoggerConstructorAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.ReSharper.Feature.Services.Daemon; 2 | using JetBrains.ReSharper.Psi; 3 | using JetBrains.ReSharper.Psi.CSharp.Tree; 4 | using JetBrains.ReSharper.Psi.Tree; 5 | using JetBrains.ReSharper.Psi.Util; 6 | 7 | using ReSharper.Structured.Logging.Extensions; 8 | using ReSharper.Structured.Logging.Highlighting; 9 | 10 | namespace ReSharper.Structured.Logging.Analyzer 11 | { 12 | [ElementProblemAnalyzer(typeof(IConstructorDeclaration))] 13 | public class ContextualLoggerConstructorAnalyzer : ElementProblemAnalyzer 14 | { 15 | // ReSharper disable once CognitiveComplexity 16 | protected override void Run(IConstructorDeclaration element, ElementProblemAnalyzerData data, IHighlightingConsumer consumer) 17 | { 18 | if (element.Params?.ParameterDeclarations == null) 19 | { 20 | return; 21 | } 22 | 23 | foreach (var declaration in element.Params.ParameterDeclarations) 24 | { 25 | if (!(declaration.Type is IDeclaredType declaredType)) 26 | { 27 | continue; 28 | } 29 | 30 | if (!declaredType.IsGenericMicrosoftExtensionsLogger()) 31 | { 32 | continue; 33 | } 34 | 35 | var argumentType = declaredType.GetFirstGenericArgumentType(); 36 | if (argumentType == null) 37 | { 38 | continue; 39 | } 40 | 41 | var containingType = element.DeclaredElement?.GetContainingType(); 42 | var className = containingType?.GetClrName().FullName; 43 | if (className == null) 44 | { 45 | continue; 46 | } 47 | 48 | if (className.Equals(argumentType.GetClassType()?.GetClrName().FullName)) 49 | { 50 | continue; 51 | } 52 | 53 | consumer.AddHighlighting(new ContextualLoggerWarning(declaration.TypeUsage.GetDocumentRange())); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Analyzer/ContextualLoggerSerilogFactoryAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.ReSharper.Feature.Services.Daemon; 2 | using JetBrains.ReSharper.Psi.CSharp.Tree; 3 | using JetBrains.ReSharper.Psi.Tree; 4 | 5 | using ReSharper.Structured.Logging.Extensions; 6 | using ReSharper.Structured.Logging.Highlighting; 7 | 8 | namespace ReSharper.Structured.Logging.Analyzer 9 | { 10 | [ElementProblemAnalyzer(typeof(IInvocationExpression))] 11 | public class ContextualLoggerSerilogFactoryAnalyzer : ElementProblemAnalyzer 12 | { 13 | protected override void Run(IInvocationExpression element, ElementProblemAnalyzerData data, IHighlightingConsumer consumer) 14 | { 15 | if (!element.IsSerilogContextFactoryLogger()) 16 | { 17 | return; 18 | } 19 | 20 | var containingNode = element.GetContainingNode(); 21 | if (containingNode == null) 22 | { 23 | return; 24 | } 25 | 26 | var invocationTypeArgument = element.TypeArguments[0]?.GetScalarType()?.GetClrName(); 27 | if (invocationTypeArgument?.FullName == containingNode.CLRName) 28 | { 29 | return; 30 | } 31 | 32 | consumer.AddHighlighting(new ContextualLoggerWarning(element.GetDocumentRange())); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Analyzer/CorrectExceptionPassingAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using JetBrains.Metadata.Reader.API; 4 | using JetBrains.ReSharper.Feature.Services.Daemon; 5 | using JetBrains.ReSharper.Psi; 6 | using JetBrains.ReSharper.Psi.CodeAnnotations; 7 | using JetBrains.ReSharper.Psi.CSharp.Tree; 8 | using JetBrains.ReSharper.Psi.Util; 9 | 10 | using ReSharper.Structured.Logging.Caching; 11 | using ReSharper.Structured.Logging.Extensions; 12 | using ReSharper.Structured.Logging.Highlighting; 13 | 14 | namespace ReSharper.Structured.Logging.Analyzer 15 | { 16 | [ElementProblemAnalyzer(typeof(IInvocationExpression))] 17 | public class CorrectExceptionPassingAnalyzer : ElementProblemAnalyzer 18 | { 19 | private readonly Lazy _templateParameterNameAttributeProvider; 20 | 21 | public CorrectExceptionPassingAnalyzer(CodeAnnotationsCache codeAnnotationsCache) 22 | { 23 | _templateParameterNameAttributeProvider = codeAnnotationsCache.GetLazyProvider(); 24 | } 25 | 26 | // ReSharper disable once CognitiveComplexity 27 | protected override void Run( 28 | IInvocationExpression element, 29 | ElementProblemAnalyzerData data, 30 | IHighlightingConsumer consumer) 31 | { 32 | var templateArgument = element.GetTemplateArgument(_templateParameterNameAttributeProvider.Value); 33 | if (templateArgument == null) 34 | { 35 | return; 36 | } 37 | 38 | var exceptionType = element.PsiModule.GetPredefinedType().TryGetType(PredefinedType.EXCEPTION_FQN, NullableAnnotation.Unknown); 39 | if (exceptionType == null) 40 | { 41 | return; 42 | } 43 | 44 | ICSharpArgument invalidExceptionArgument = FindInvalidExceptionArgument(element, templateArgument, exceptionType); 45 | if (invalidExceptionArgument == null) 46 | { 47 | return; 48 | } 49 | 50 | var overloadAvailable = false; 51 | var candidates = element.InvocationExpressionReference.GetCandidates().ToArray(); 52 | var invalidArgumentIndex = invalidExceptionArgument.IndexOf(); 53 | foreach (var candidate in candidates) 54 | { 55 | if (!(candidate.GetDeclaredElement() is IMethod declaredElement)) 56 | { 57 | continue; 58 | } 59 | 60 | foreach (var parameter in declaredElement.Parameters) 61 | { 62 | if (invalidArgumentIndex <= parameter.IndexOf()) 63 | { 64 | break; 65 | } 66 | 67 | if (parameter.Type.IsSubtypeOf(exceptionType)) 68 | { 69 | overloadAvailable = true; 70 | break; 71 | } 72 | } 73 | 74 | if (overloadAvailable) 75 | { 76 | break; 77 | } 78 | } 79 | 80 | if (!overloadAvailable) 81 | { 82 | return; 83 | } 84 | 85 | consumer.AddHighlighting(new ExceptionPassedAsTemplateArgumentWarning(invalidExceptionArgument.GetDocumentRange())); 86 | } 87 | 88 | private ICSharpArgument FindInvalidExceptionArgument(IInvocationExpression invocationExpression, ICSharpArgument templateArgument, IDeclaredType exceptionType) 89 | { 90 | var templateArgumentIndex = templateArgument.IndexOf(); 91 | foreach (var argument in invocationExpression.ArgumentList.Arguments) 92 | { 93 | var argumentType = argument.Value?.Type(); 94 | if (!(argumentType is IDeclaredType declaredType)) 95 | { 96 | continue; 97 | } 98 | 99 | if (!declaredType.IsSubtypeOf(exceptionType)) 100 | { 101 | continue; 102 | } 103 | 104 | if (templateArgumentIndex > argument.IndexOf()) 105 | { 106 | return null; 107 | } 108 | 109 | return argument; 110 | } 111 | 112 | return null; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Analyzer/DuplicatePropertiesTemplateAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | using JetBrains.ReSharper.Feature.Services.Daemon; 5 | using JetBrains.ReSharper.Psi.CodeAnnotations; 6 | using JetBrains.ReSharper.Psi.CSharp.Tree; 7 | 8 | using ReSharper.Structured.Logging.Caching; 9 | using ReSharper.Structured.Logging.Extensions; 10 | using ReSharper.Structured.Logging.Highlighting; 11 | using ReSharper.Structured.Logging.Serilog.Parsing; 12 | 13 | namespace ReSharper.Structured.Logging.Analyzer 14 | { 15 | [ElementProblemAnalyzer(typeof(IInvocationExpression))] 16 | public class DuplicatePropertiesTemplateAnalyzer : ElementProblemAnalyzer 17 | { 18 | private readonly MessageTemplateParser _messageTemplateParser; 19 | 20 | private readonly Lazy _templateParameterNameAttributeProvider; 21 | 22 | public DuplicatePropertiesTemplateAnalyzer(MessageTemplateParser messageTemplateParser, CodeAnnotationsCache codeAnnotationsCache) 23 | { 24 | _messageTemplateParser = messageTemplateParser; 25 | _templateParameterNameAttributeProvider = codeAnnotationsCache.GetLazyProvider(); 26 | } 27 | 28 | protected override void Run( 29 | IInvocationExpression element, 30 | ElementProblemAnalyzerData data, 31 | IHighlightingConsumer consumer) 32 | { 33 | var templateArgument = element.GetTemplateArgument(_templateParameterNameAttributeProvider.Value); 34 | var templateText = templateArgument?.TryGetTemplateText(); 35 | if (templateText == null) 36 | { 37 | return; 38 | } 39 | 40 | var messageTemplate = _messageTemplateParser.Parse(templateText); 41 | if (messageTemplate.NamedProperties == null) 42 | { 43 | return; 44 | } 45 | 46 | foreach (var duplicates in messageTemplate.NamedProperties 47 | .GroupBy(n => n.PropertyName) 48 | .Where(g => g.Count() > 1)) 49 | { 50 | foreach (var token in duplicates) 51 | { 52 | consumer.AddHighlighting(new DuplicateTemplatePropertyWarning(templateArgument.GetTokenInformation(token))); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Analyzer/LogMessageIsSentenceAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | 4 | using JetBrains.ReSharper.Feature.Services.Daemon; 5 | using JetBrains.ReSharper.Psi.CodeAnnotations; 6 | using JetBrains.ReSharper.Psi.CSharp.Tree; 7 | using JetBrains.ReSharper.Psi.Util; 8 | 9 | using ReSharper.Structured.Logging.Caching; 10 | using ReSharper.Structured.Logging.Extensions; 11 | using ReSharper.Structured.Logging.Highlighting; 12 | 13 | namespace ReSharper.Structured.Logging.Analyzer 14 | { 15 | [ElementProblemAnalyzer(typeof(IInvocationExpression), HighlightingTypes = new[] { typeof(LogMessageIsSentenceWarning) })] 16 | public class LogMessageIsSentenceAnalyzer : ElementProblemAnalyzer 17 | { 18 | private readonly Lazy _templateParameterNameAttributeProvider; 19 | 20 | private static readonly Regex DotAtTheEnd = new Regex(@"(?(); 25 | } 26 | 27 | protected override void Run(IInvocationExpression element, ElementProblemAnalyzerData data, IHighlightingConsumer consumer) 28 | { 29 | var templateArgument = element.GetTemplateArgument(_templateParameterNameAttributeProvider.Value); 30 | var lastFragmentExpression = templateArgument?.TryCreateLastTemplateFragmentExpression(); 31 | if (lastFragmentExpression == null) 32 | { 33 | return; 34 | } 35 | 36 | var unquotedText = lastFragmentExpression.Expression.GetUnquotedText(); 37 | if (!DotAtTheEnd.IsMatch(unquotedText)) 38 | { 39 | return; 40 | } 41 | 42 | consumer.AddHighlighting(new LogMessageIsSentenceWarning(lastFragmentExpression, DotAtTheEnd)); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Analyzer/PositionalPropertiesUsageAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using JetBrains.ReSharper.Feature.Services.Daemon; 4 | using JetBrains.ReSharper.Psi.CodeAnnotations; 5 | using JetBrains.ReSharper.Psi.CSharp.Tree; 6 | 7 | using ReSharper.Structured.Logging.Caching; 8 | using ReSharper.Structured.Logging.Extensions; 9 | using ReSharper.Structured.Logging.Highlighting; 10 | using ReSharper.Structured.Logging.Serilog.Parsing; 11 | 12 | namespace ReSharper.Structured.Logging.Analyzer 13 | { 14 | [ElementProblemAnalyzer(typeof(IInvocationExpression))] 15 | public class PositionalPropertiesUsageAnalyzer : ElementProblemAnalyzer 16 | { 17 | private readonly MessageTemplateParser _messageTemplateParser; 18 | 19 | private readonly Lazy _templateParameterNameAttributeProvider; 20 | 21 | public PositionalPropertiesUsageAnalyzer(MessageTemplateParser messageTemplateParser, CodeAnnotationsCache codeAnnotationsCache) 22 | { 23 | _messageTemplateParser = messageTemplateParser; 24 | _templateParameterNameAttributeProvider = codeAnnotationsCache.GetLazyProvider(); 25 | } 26 | 27 | protected override void Run( 28 | IInvocationExpression element, 29 | ElementProblemAnalyzerData data, 30 | IHighlightingConsumer consumer) 31 | { 32 | var templateArgument = element.GetTemplateArgument(_templateParameterNameAttributeProvider.Value); 33 | var templateText = templateArgument?.TryGetTemplateText(); 34 | if (templateText == null) 35 | { 36 | return; 37 | } 38 | 39 | var messageTemplate = _messageTemplateParser.Parse(templateText); 40 | if (messageTemplate.PositionalProperties == null) 41 | { 42 | return; 43 | } 44 | 45 | foreach (var property in messageTemplate.PositionalProperties) 46 | { 47 | consumer.AddHighlighting(new PositionalPropertyUsedWarning(templateArgument.GetTokenInformation(property))); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Analyzer/PropertiesNamingAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | 4 | using JetBrains.Application.Settings; 5 | using JetBrains.ProjectModel; 6 | using JetBrains.ReSharper.Feature.Services.Daemon; 7 | using JetBrains.ReSharper.Psi.CodeAnnotations; 8 | using JetBrains.ReSharper.Psi.CSharp.Tree; 9 | using JetBrains.ReSharper.Psi.Tree; 10 | 11 | using ReSharper.Structured.Logging.Caching; 12 | using ReSharper.Structured.Logging.Extensions; 13 | using ReSharper.Structured.Logging.Highlighting; 14 | using ReSharper.Structured.Logging.Serilog.Parsing; 15 | using ReSharper.Structured.Logging.Services; 16 | using ReSharper.Structured.Logging.Settings; 17 | 18 | namespace ReSharper.Structured.Logging.Analyzer; 19 | 20 | [ElementProblemAnalyzer(typeof(IInvocationExpression))] 21 | public class PropertiesNamingAnalyzer : ElementProblemAnalyzer 22 | { 23 | private readonly MessageTemplateParser _messageTemplateParser; 24 | 25 | private readonly Lazy _templateParameterNameAttributeProvider; 26 | 27 | public PropertiesNamingAnalyzer(MessageTemplateParser messageTemplateParser, CodeAnnotationsCache codeAnnotationsCache) 28 | { 29 | _messageTemplateParser = messageTemplateParser; 30 | _templateParameterNameAttributeProvider = codeAnnotationsCache.GetLazyProvider(); 31 | } 32 | 33 | protected override void Run( 34 | IInvocationExpression element, 35 | ElementProblemAnalyzerData data, 36 | IHighlightingConsumer consumer) 37 | { 38 | var settingsStore = element.GetProject() 39 | ?.GetSolution() 40 | .GetSettingsStore(); 41 | 42 | var ignoreRegexString = settingsStore?.GetValue(StructuredLoggingSettingsAccessor.IgnoredPropertiesRegex); 43 | var ignoredPropertiesRegex = string.IsNullOrWhiteSpace(ignoreRegexString) ? null : new Regex(ignoreRegexString); 44 | 45 | CheckPropertiesInTemplate(element, consumer, settingsStore, ignoredPropertiesRegex); 46 | CheckPropertiesInContext(element, consumer, settingsStore, ignoredPropertiesRegex); 47 | } 48 | 49 | private void CheckPropertiesInTemplate( 50 | IInvocationExpression element, 51 | IHighlightingConsumer consumer, 52 | IContextBoundSettingsStore settingsStore, 53 | Regex ignoredPropertiesRegex) 54 | { 55 | var templateArgument = element.GetTemplateArgument(_templateParameterNameAttributeProvider.Value); 56 | var templateText = templateArgument?.TryGetTemplateText(); 57 | if (templateText == null) 58 | { 59 | return; 60 | } 61 | 62 | var messageTemplate = _messageTemplateParser.Parse(templateText); 63 | if (messageTemplate.NamedProperties == null) 64 | { 65 | return; 66 | } 67 | 68 | foreach (var property in messageTemplate.NamedProperties) 69 | { 70 | if (string.IsNullOrEmpty(property.PropertyName)) 71 | { 72 | continue; 73 | } 74 | 75 | var suggestedName = GetSuggestedName(property.PropertyName, settingsStore, ignoredPropertiesRegex); 76 | if (string.Equals(suggestedName, property.PropertyName)) 77 | { 78 | continue; 79 | } 80 | 81 | consumer.AddHighlighting( 82 | new InconsistentLogPropertyNamingWarning(templateArgument.GetTokenInformation(property), property, 83 | suggestedName)); 84 | } 85 | } 86 | 87 | private void CheckPropertiesInContext( 88 | IInvocationExpression element, 89 | IHighlightingConsumer consumer, 90 | IContextBoundSettingsStore settingsStore, 91 | Regex ignoredPropertiesRegex) 92 | { 93 | if (!element.IsSerilogContextPushPropertyMethod()) 94 | { 95 | return; 96 | } 97 | 98 | if (element.ArgumentList.Arguments.Count < 1) 99 | { 100 | return; 101 | } 102 | 103 | var propertyArgument = element.ArgumentList.Arguments[0]; 104 | 105 | var propertyName = string.Empty; 106 | propertyArgument.Value?.ConstantValue.IsString(out propertyName); 107 | if (string.IsNullOrEmpty(propertyName)) 108 | { 109 | return; 110 | } 111 | 112 | var suggestedName = GetSuggestedName(propertyName, settingsStore, ignoredPropertiesRegex); 113 | if (string.Equals(propertyName, suggestedName)) 114 | { 115 | return; 116 | } 117 | 118 | consumer.AddHighlighting(new InconsistentContextLogPropertyNamingWarning(propertyArgument, propertyName, suggestedName)); 119 | } 120 | 121 | private string GetSuggestedName(string propertyName, IContextBoundSettingsStore settingsStore, Regex ignoredPropertiesRegex) 122 | { 123 | if (ignoredPropertiesRegex != null && ignoredPropertiesRegex.IsMatch(propertyName)) 124 | { 125 | return propertyName; 126 | } 127 | 128 | return PropertyNameProvider.GetSuggestedName(propertyName, settingsStore); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Caching/TemplateParameterNameAttributeProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using JetBrains.Application.Parts; 6 | using JetBrains.ReSharper.Psi; 7 | using JetBrains.ReSharper.Psi.CodeAnnotations; 8 | 9 | namespace ReSharper.Structured.Logging.Caching; 10 | 11 | [CodeAnnotationProvider(Instantiation.DemandAnyThreadUnsafe)] 12 | public class TemplateParameterNameAttributeProvider( 13 | AttributeInstancesProvider attributeInstancesProvider, 14 | CodeAnnotationsConfiguration codeAnnotationsConfiguration) 15 | : CodeAnnotationInfoProvider(attributeInstancesProvider, codeAnnotationsConfiguration, true) 16 | { 17 | private const string MessageTemplateFormatMethodAttribute = "MessageTemplateFormatMethodAttribute"; 18 | 19 | protected override string CalculateInfo(ITypeMember attributesOwner, IEnumerable attributeInstances) 20 | { 21 | var templateFormatAttribute = attributeInstances 22 | .FirstOrDefault(a => string.Equals(a.GetAttributeShortName(), MessageTemplateFormatMethodAttribute, StringComparison.Ordinal)); 23 | 24 | if (templateFormatAttribute != null) 25 | { 26 | return templateFormatAttribute.PositionParameters() 27 | .FirstOrDefault() 28 | ?.ConstantValue.StringValue; 29 | } 30 | 31 | var className = attributesOwner.ContainingType?.GetClrName().FullName; 32 | if (className == "Microsoft.Extensions.Logging.LoggerExtensions") 33 | { 34 | return attributesOwner.ShortName == "BeginScope" ? "messageFormat" : "message"; 35 | } 36 | 37 | if (className == "ZLogger.ZLoggerExtensions") 38 | { 39 | return "format"; 40 | } 41 | 42 | return null; 43 | } 44 | 45 | protected override string GetDefaultInfo(ITypeMember attributesOwner) 46 | { 47 | return null; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Highlighting/AnonymousObjectWithoutDestructuringWarning.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.DocumentModel; 2 | using JetBrains.ReSharper.Feature.Services.Daemon; 3 | using JetBrains.ReSharper.Psi.CSharp; 4 | 5 | using ReSharper.Structured.Logging.Models; 6 | using ReSharper.Structured.Logging.Settings; 7 | 8 | namespace ReSharper.Structured.Logging.Highlighting 9 | { 10 | [RegisterConfigurableSeverity( 11 | SeverityId, 12 | null, 13 | StructuredLoggingGroup.Id, 14 | Message, 15 | Message, 16 | Severity.WARNING)] 17 | [ConfigurableSeverityHighlighting( 18 | SeverityId, 19 | CSharpLanguage.Name, 20 | OverlapResolve = OverlapResolveKind.WARNING, 21 | ToolTipFormatString = Message)] 22 | public class AnonymousObjectDestructuringWarning : IHighlighting 23 | { 24 | private const string Message = "Anonymous objects must be destructured"; 25 | 26 | public const string SeverityId = "AnonymousObjectDestructuringProblem"; 27 | 28 | public AnonymousObjectDestructuringWarning(MessageTemplateTokenInformation tokenInformation) 29 | { 30 | TokenInformation = tokenInformation; 31 | } 32 | 33 | public string ErrorStripeToolTip => ToolTip; 34 | 35 | public MessageTemplateTokenInformation TokenInformation { get; } 36 | 37 | public string ToolTip => Message; 38 | 39 | public DocumentRange CalculateRange() 40 | { 41 | return TokenInformation.DocumentRange; 42 | } 43 | 44 | public bool IsValid() 45 | { 46 | return TokenInformation.DocumentRange.IsValid(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Highlighting/ComplexObjectDestructuringInContextWarning.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.DocumentModel; 2 | using JetBrains.ReSharper.Feature.Services.Daemon; 3 | using JetBrains.ReSharper.Psi.CSharp; 4 | using JetBrains.ReSharper.Psi.CSharp.Tree; 5 | using JetBrains.ReSharper.Psi.Tree; 6 | 7 | using ReSharper.Structured.Logging.Settings; 8 | 9 | namespace ReSharper.Structured.Logging.Highlighting 10 | { 11 | [RegisterConfigurableSeverity( 12 | SeverityId, 13 | null, 14 | StructuredLoggingGroup.Id, 15 | Message, 16 | Message, 17 | Severity.WARNING)] 18 | [ConfigurableSeverityHighlighting( 19 | SeverityId, 20 | CSharpLanguage.Name, 21 | OverlapResolve = OverlapResolveKind.WARNING, 22 | ToolTipFormatString = Message)] 23 | public class ComplexObjectDestructuringInContextWarning : ComplexObjectDestructuringWarningBase, IHighlighting 24 | { 25 | public const string SeverityId = "ComplexObjectInContextDestructuringProblem"; 26 | 27 | private readonly IInvocationExpression _invocationExpression; 28 | 29 | public ComplexObjectDestructuringInContextWarning(IInvocationExpression invocationExpression) 30 | { 31 | _invocationExpression = invocationExpression; 32 | } 33 | 34 | public string ErrorStripeToolTip => ToolTip; 35 | 36 | public string ToolTip => Message; 37 | 38 | public DocumentRange CalculateRange() 39 | { 40 | return _invocationExpression.GetDocumentRange(); 41 | } 42 | 43 | public bool IsValid() 44 | { 45 | return _invocationExpression.GetDocumentRange().IsValid(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Highlighting/ComplexObjectDestructuringWarning.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.DocumentModel; 2 | using JetBrains.ReSharper.Feature.Services.Daemon; 3 | using JetBrains.ReSharper.Psi.CSharp; 4 | 5 | using ReSharper.Structured.Logging.Models; 6 | using ReSharper.Structured.Logging.Settings; 7 | 8 | namespace ReSharper.Structured.Logging.Highlighting 9 | { 10 | [RegisterConfigurableSeverity( 11 | SeverityId, 12 | null, 13 | StructuredLoggingGroup.Id, 14 | Message, 15 | Message, 16 | Severity.WARNING)] 17 | [ConfigurableSeverityHighlighting( 18 | SeverityId, 19 | CSharpLanguage.Name, 20 | OverlapResolve = OverlapResolveKind.WARNING, 21 | ToolTipFormatString = Message)] 22 | public class ComplexObjectDestructuringWarning : ComplexObjectDestructuringWarningBase, IHighlighting 23 | { 24 | public const string SeverityId = "ComplexObjectDestructuringProblem"; 25 | 26 | public ComplexObjectDestructuringWarning(MessageTemplateTokenInformation tokenInformation) 27 | { 28 | TokenInformation = tokenInformation; 29 | } 30 | 31 | public string ErrorStripeToolTip => ToolTip; 32 | 33 | public MessageTemplateTokenInformation TokenInformation { get; } 34 | 35 | public string ToolTip => Message; 36 | 37 | public DocumentRange CalculateRange() 38 | { 39 | return TokenInformation.DocumentRange; 40 | } 41 | 42 | public bool IsValid() 43 | { 44 | return TokenInformation.DocumentRange.IsValid(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Highlighting/ComplexObjectDestructuringWarningBase.cs: -------------------------------------------------------------------------------- 1 | namespace ReSharper.Structured.Logging.Highlighting 2 | { 3 | public abstract class ComplexObjectDestructuringWarningBase 4 | { 5 | protected const string Message = "Complex objects with default ToString() implementation probably need to be destructured"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Highlighting/ContextualLoggerWarning.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.DocumentModel; 2 | using JetBrains.ReSharper.Feature.Services.Daemon; 3 | using JetBrains.ReSharper.Psi.CSharp; 4 | 5 | using ReSharper.Structured.Logging.Settings; 6 | 7 | namespace ReSharper.Structured.Logging.Highlighting 8 | { 9 | [RegisterConfigurableSeverity( 10 | SeverityId, 11 | null, 12 | StructuredLoggingGroup.Id, 13 | Message, 14 | Message, 15 | Severity.WARNING)] 16 | [ConfigurableSeverityHighlighting( 17 | SeverityId, 18 | CSharpLanguage.Name, 19 | OverlapResolve = OverlapResolveKind.WARNING, 20 | ToolTipFormatString = Message)] 21 | public class ContextualLoggerWarning : IHighlighting 22 | { 23 | private const string Message = "Incorrect type is used for contextual logger"; 24 | 25 | public const string SeverityId = "ContextualLoggerProblem"; 26 | 27 | private readonly DocumentRange _range; 28 | 29 | public ContextualLoggerWarning(DocumentRange documentRange) 30 | { 31 | _range = documentRange; 32 | } 33 | 34 | public string ErrorStripeToolTip => ToolTip; 35 | 36 | public string ToolTip => Message; 37 | 38 | public DocumentRange CalculateRange() 39 | { 40 | return _range; 41 | } 42 | 43 | public bool IsValid() 44 | { 45 | return _range.IsValid(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Highlighting/DuplicateTemplatePropertyWarning.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.DocumentModel; 2 | using JetBrains.ReSharper.Feature.Services.Daemon; 3 | using JetBrains.ReSharper.Psi.CSharp; 4 | 5 | using ReSharper.Structured.Logging.Models; 6 | using ReSharper.Structured.Logging.Settings; 7 | 8 | namespace ReSharper.Structured.Logging.Highlighting 9 | { 10 | [RegisterConfigurableSeverity( 11 | SeverityId, 12 | null, 13 | StructuredLoggingGroup.Id, 14 | Message, 15 | Message, 16 | Severity.WARNING)] 17 | [ConfigurableSeverityHighlighting( 18 | SeverityId, 19 | CSharpLanguage.Name, 20 | OverlapResolve = OverlapResolveKind.WARNING, 21 | ToolTipFormatString = Message)] 22 | public class DuplicateTemplatePropertyWarning : IHighlighting 23 | { 24 | private const string Message = "Duplicate properties in message template"; 25 | 26 | public const string SeverityId = "TemplateDuplicatePropertyProblem"; 27 | 28 | private readonly DocumentRange _documentRange; 29 | 30 | public DuplicateTemplatePropertyWarning(MessageTemplateTokenInformation tokenInformation) 31 | { 32 | _documentRange = tokenInformation.DocumentRange; 33 | } 34 | 35 | public string ErrorStripeToolTip => ToolTip; 36 | 37 | public string ToolTip => Message; 38 | 39 | public DocumentRange CalculateRange() 40 | { 41 | return _documentRange; 42 | } 43 | 44 | public bool IsValid() 45 | { 46 | return _documentRange.IsValid(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Highlighting/ExceptionPassedAsTemplateArgumentWarning.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.DocumentModel; 2 | using JetBrains.ReSharper.Feature.Services.Daemon; 3 | using JetBrains.ReSharper.Psi.CSharp; 4 | 5 | using ReSharper.Structured.Logging.Settings; 6 | 7 | namespace ReSharper.Structured.Logging.Highlighting 8 | { 9 | [RegisterConfigurableSeverity( 10 | SeverityId, 11 | null, 12 | StructuredLoggingGroup.Id, 13 | Message, 14 | Message, 15 | Severity.WARNING)] 16 | [ConfigurableSeverityHighlighting( 17 | SeverityId, 18 | CSharpLanguage.Name, 19 | OverlapResolve = OverlapResolveKind.WARNING, 20 | ToolTipFormatString = Message)] 21 | public class ExceptionPassedAsTemplateArgumentWarning : IHighlighting 22 | { 23 | public const string SeverityId = "ExceptionPassedAsTemplateArgumentProblem"; 24 | 25 | private const string Message = "Exception should be passed to the exception argument"; 26 | 27 | private readonly DocumentRange _documentRange; 28 | 29 | public ExceptionPassedAsTemplateArgumentWarning(DocumentRange documentRange) 30 | { 31 | _documentRange = documentRange; 32 | } 33 | 34 | public string ErrorStripeToolTip => ToolTip; 35 | 36 | public string ToolTip => Message; 37 | 38 | public DocumentRange CalculateRange() 39 | { 40 | return _documentRange; 41 | } 42 | 43 | public bool IsValid() 44 | { 45 | return _documentRange.IsValid(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Highlighting/InconsistentContextLogPropertyNamingWarning.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.DocumentModel; 2 | using JetBrains.ReSharper.Feature.Services.Daemon; 3 | using JetBrains.ReSharper.Psi.CSharp; 4 | using JetBrains.ReSharper.Psi.CSharp.Tree; 5 | 6 | using ReSharper.Structured.Logging.Settings; 7 | 8 | namespace ReSharper.Structured.Logging.Highlighting 9 | { 10 | [RegisterConfigurableSeverity( 11 | SeverityId, 12 | null, 13 | StructuredLoggingGroup.Id, 14 | Message, 15 | Message, 16 | Severity.WARNING)] 17 | [ConfigurableSeverityHighlighting( 18 | SeverityId, 19 | CSharpLanguage.Name, 20 | OverlapResolve = OverlapResolveKind.WARNING, 21 | ToolTipFormatString = Message)] 22 | public class InconsistentContextLogPropertyNamingWarning : InconsistentLogPropertyNamingWarningBase, IHighlighting 23 | { 24 | private readonly string _propertyName; 25 | 26 | public const string SeverityId = "InconsistentContextLogPropertyNaming"; 27 | 28 | public InconsistentContextLogPropertyNamingWarning(ICSharpArgument argument, string propertyName, string suggestedName) 29 | { 30 | _propertyName = propertyName; 31 | Argument = argument; 32 | SuggestedName = suggestedName; 33 | } 34 | 35 | public string ErrorStripeToolTip => ToolTip; 36 | 37 | public ICSharpArgument Argument { get; } 38 | 39 | 40 | public string SuggestedName { get; } 41 | 42 | public string ToolTip => GetToolTipMessage(_propertyName, SuggestedName); 43 | 44 | public DocumentRange CalculateRange() 45 | { 46 | return Argument.GetDocumentRange(); 47 | } 48 | 49 | public bool IsValid() 50 | { 51 | return Argument.GetDocumentRange().IsValid(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Highlighting/InconsistentLogPropertyNamingWarning.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.DocumentModel; 2 | using JetBrains.ReSharper.Feature.Services.Daemon; 3 | using JetBrains.ReSharper.Psi.CSharp; 4 | 5 | using ReSharper.Structured.Logging.Models; 6 | using ReSharper.Structured.Logging.Serilog.Parsing; 7 | using ReSharper.Structured.Logging.Settings; 8 | 9 | namespace ReSharper.Structured.Logging.Highlighting 10 | { 11 | [RegisterConfigurableSeverity( 12 | SeverityId, 13 | null, 14 | StructuredLoggingGroup.Id, 15 | Message, 16 | Message, 17 | Severity.WARNING)] 18 | [ConfigurableSeverityHighlighting( 19 | SeverityId, 20 | CSharpLanguage.Name, 21 | OverlapResolve = OverlapResolveKind.WARNING, 22 | ToolTipFormatString = Message)] 23 | public class InconsistentLogPropertyNamingWarning : InconsistentLogPropertyNamingWarningBase, IHighlighting 24 | { 25 | public const string SeverityId = "InconsistentLogPropertyNaming"; 26 | 27 | public InconsistentLogPropertyNamingWarning( 28 | MessageTemplateTokenInformation tokenInformation, 29 | PropertyToken namedProperty, 30 | string suggestedName) 31 | { 32 | TokenInformation = tokenInformation; 33 | NamedProperty = namedProperty; 34 | SuggestedName = suggestedName; 35 | } 36 | 37 | public string ErrorStripeToolTip => ToolTip; 38 | 39 | public MessageTemplateTokenInformation TokenInformation { get; } 40 | 41 | public PropertyToken NamedProperty { get; } 42 | 43 | public string SuggestedName { get; } 44 | 45 | public string ToolTip => GetToolTipMessage(NamedProperty.PropertyName, SuggestedName); 46 | 47 | public DocumentRange CalculateRange() 48 | { 49 | return TokenInformation.DocumentRange; 50 | } 51 | 52 | public bool IsValid() 53 | { 54 | return TokenInformation.DocumentRange.IsValid(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Highlighting/InconsistentLogPropertyNamingWarningBase.cs: -------------------------------------------------------------------------------- 1 | namespace ReSharper.Structured.Logging.Highlighting 2 | { 3 | public class InconsistentLogPropertyNamingWarningBase 4 | { 5 | protected const string Message = "Property name '{0}' does not match naming rules. Suggested name is '{1}'."; 6 | 7 | protected string GetToolTipMessage(string propertyName, string suggestedName) 8 | { 9 | return $"Property name '{propertyName}' does not match naming rules. Suggested name is '{suggestedName}'."; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Highlighting/LogMessageIsSentenceWarning.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | using JetBrains.DocumentModel; 4 | using JetBrains.ReSharper.Feature.Services.Daemon; 5 | using JetBrains.ReSharper.Psi.CSharp; 6 | using JetBrains.ReSharper.Psi.Tree; 7 | using JetBrains.ReSharper.Psi.Util; 8 | 9 | using ReSharper.Structured.Logging.Settings; 10 | 11 | namespace ReSharper.Structured.Logging.Highlighting 12 | { 13 | [RegisterConfigurableSeverity( 14 | SeverityId, 15 | null, 16 | StructuredLoggingGroup.Id, 17 | Message, 18 | Message, 19 | Severity.WARNING)] 20 | [ConfigurableSeverityHighlighting( 21 | SeverityId, 22 | CSharpLanguage.Name, 23 | OverlapResolve = OverlapResolveKind.WARNING, 24 | ToolTipFormatString = Message)] 25 | public class LogMessageIsSentenceWarning : IHighlighting 26 | { 27 | private const string Message = "Log event messages should be fragments, not sentences. Avoid a trailing period/full stop."; 28 | 29 | public const string SeverityId = "LogMessageIsSentenceProblem"; 30 | 31 | private readonly DocumentRange _documentRange; 32 | 33 | public LogMessageIsSentenceWarning(IStringLiteralAlterer stringLiteral, Regex regex) 34 | { 35 | StringLiteral = stringLiteral; 36 | Regex = regex; 37 | _documentRange = stringLiteral.Expression.GetDocumentRange(); 38 | } 39 | 40 | public string ErrorStripeToolTip => ToolTip; 41 | 42 | public string ToolTip => Message; 43 | 44 | public IStringLiteralAlterer StringLiteral { get; } 45 | 46 | public Regex Regex { get; } 47 | 48 | public DocumentRange CalculateRange() 49 | { 50 | return _documentRange; 51 | } 52 | 53 | public bool IsValid() 54 | { 55 | return _documentRange.IsValid(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Highlighting/PositionalPropertyUsedWarning.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.DocumentModel; 2 | using JetBrains.ReSharper.Feature.Services.Daemon; 3 | using JetBrains.ReSharper.Psi.CSharp; 4 | 5 | using ReSharper.Structured.Logging.Models; 6 | using ReSharper.Structured.Logging.Settings; 7 | 8 | namespace ReSharper.Structured.Logging.Highlighting 9 | { 10 | [RegisterConfigurableSeverity( 11 | SeverityId, 12 | null, 13 | StructuredLoggingGroup.Id, 14 | Message, 15 | Message, 16 | Severity.WARNING)] 17 | [ConfigurableSeverityHighlighting( 18 | SeverityId, 19 | CSharpLanguage.Name, 20 | OverlapResolve = OverlapResolveKind.WARNING, 21 | ToolTipFormatString = Message)] 22 | public class PositionalPropertyUsedWarning : IHighlighting 23 | { 24 | private readonly MessageTemplateTokenInformation _tokenInformation; 25 | 26 | private const string Message = "Prefer named properties instead of positional ones"; 27 | 28 | public const string SeverityId = "PositionalPropertyUsedProblem"; 29 | 30 | public PositionalPropertyUsedWarning(MessageTemplateTokenInformation tokenInformation) 31 | { 32 | _tokenInformation = tokenInformation; 33 | } 34 | 35 | public string ErrorStripeToolTip => ToolTip; 36 | 37 | public string ToolTip => Message; 38 | 39 | public DocumentRange CalculateRange() 40 | { 41 | return _tokenInformation.DocumentRange; 42 | } 43 | 44 | public bool IsValid() 45 | { 46 | return _tokenInformation.DocumentRange.IsValid(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Highlighting/TemplateFormatStringNonExistingArgumentWarning.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.DocumentModel; 2 | using JetBrains.ReSharper.Feature.Services.Daemon; 3 | using JetBrains.ReSharper.Psi.CSharp; 4 | 5 | using ReSharper.Structured.Logging.Settings; 6 | 7 | namespace ReSharper.Structured.Logging.Highlighting 8 | { 9 | [RegisterConfigurableSeverity( 10 | SeverityId, 11 | null, 12 | StructuredLoggingGroup.Id, 13 | Message, 14 | Message, 15 | Severity.WARNING)] 16 | [ConfigurableSeverityHighlighting( 17 | SeverityId, 18 | CSharpLanguage.Name, 19 | OverlapResolve = OverlapResolveKind.WARNING, 20 | ToolTipFormatString = Message)] 21 | public class TemplateFormatStringNonExistingArgumentWarning : IHighlighting 22 | { 23 | private const string SeverityId = "TemplateFormatStringProblem"; 24 | 25 | private const string Message = "Non-existing argument in message template"; 26 | 27 | private readonly DocumentRange _documentRange; 28 | 29 | public TemplateFormatStringNonExistingArgumentWarning(DocumentRange documentRange) 30 | { 31 | _documentRange = documentRange; 32 | } 33 | 34 | public string ErrorStripeToolTip => ToolTip; 35 | 36 | public string ToolTip => Message; 37 | 38 | public DocumentRange CalculateRange() 39 | { 40 | return _documentRange; 41 | } 42 | 43 | public bool IsValid() 44 | { 45 | return _documentRange.IsValid(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Highlighting/TemplateIsNotCompileTimeConstantWarning.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.DocumentModel; 2 | using JetBrains.ReSharper.Feature.Services.Daemon; 3 | using JetBrains.ReSharper.Psi.CSharp; 4 | using JetBrains.ReSharper.Psi.CSharp.Tree; 5 | using JetBrains.ReSharper.Psi.Tree; 6 | 7 | using ReSharper.Structured.Logging.Settings; 8 | 9 | namespace ReSharper.Structured.Logging.Highlighting 10 | { 11 | [RegisterConfigurableSeverity( 12 | SeverityId, 13 | null, 14 | StructuredLoggingGroup.Id, 15 | Message, 16 | Message, 17 | Severity.WARNING)] 18 | [ConfigurableSeverityHighlighting( 19 | SeverityId, 20 | CSharpLanguage.Name, 21 | OverlapResolve = OverlapResolveKind.WARNING, 22 | ToolTipFormatString = Message)] 23 | public class TemplateIsNotCompileTimeConstantWarning : IHighlighting 24 | { 25 | public const string SeverityId = "TemplateIsNotCompileTimeConstantProblem"; 26 | 27 | private const string Message = "Message template should be compile time constant"; 28 | 29 | public TemplateIsNotCompileTimeConstantWarning( 30 | IInvocationExpression invocationExpression, 31 | ICSharpArgument messageTemplateArgument) 32 | { 33 | InvocationExpression = invocationExpression; 34 | MessageTemplateArgument = messageTemplateArgument; 35 | } 36 | 37 | public IInvocationExpression InvocationExpression { get; } 38 | public ICSharpArgument MessageTemplateArgument { get; } 39 | 40 | public string ErrorStripeToolTip => ToolTip; 41 | 42 | public string ToolTip => Message; 43 | 44 | public DocumentRange CalculateRange() 45 | { 46 | return MessageTemplateArgument.Expression.GetDocumentRange(); 47 | } 48 | 49 | public bool IsValid() 50 | { 51 | return MessageTemplateArgument.IsValid(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Models/MessageTemplateTokenInformation.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.DocumentModel; 2 | using JetBrains.ReSharper.Psi.Tree; 3 | using JetBrains.ReSharper.Psi.Util; 4 | 5 | namespace ReSharper.Structured.Logging.Models 6 | { 7 | public class MessageTemplateTokenInformation 8 | { 9 | public MessageTemplateTokenInformation( 10 | DocumentRange documentRange, 11 | IStringLiteralAlterer stringLiteral) 12 | { 13 | DocumentRange = documentRange; 14 | StringLiteral = stringLiteral; 15 | } 16 | 17 | public DocumentRange DocumentRange { get; } 18 | 19 | public IStringLiteralAlterer StringLiteral { get; } 20 | 21 | public int RelativeStartIndex => DocumentRange.StartOffset - StringLiteral.Expression.GetDocumentRange().StartOffset - 1; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/QuickFixes/AddDestructuringToMessageTemplatePropertyFix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using JetBrains.Annotations; 4 | using JetBrains.Application.Progress; 5 | using JetBrains.ProjectModel; 6 | using JetBrains.ReSharper.Feature.Services.QuickFixes; 7 | using JetBrains.ReSharper.Psi.CSharp; 8 | using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree; 9 | using JetBrains.ReSharper.Psi.Util; 10 | using JetBrains.ReSharper.Resources.Shell; 11 | using JetBrains.TextControl; 12 | using JetBrains.Util; 13 | 14 | using ReSharper.Structured.Logging.Highlighting; 15 | using ReSharper.Structured.Logging.Models; 16 | 17 | namespace ReSharper.Structured.Logging.QuickFixes 18 | { 19 | [QuickFix] 20 | public class AddDestructuringToMessageTemplatePropertyFix : QuickFixBase 21 | { 22 | private readonly MessageTemplateTokenInformation _tokenInformation; 23 | 24 | public AddDestructuringToMessageTemplatePropertyFix([NotNull] AnonymousObjectDestructuringWarning error) 25 | { 26 | _tokenInformation = error.TokenInformation; 27 | } 28 | 29 | public AddDestructuringToMessageTemplatePropertyFix([NotNull] ComplexObjectDestructuringWarning error) 30 | { 31 | _tokenInformation = error.TokenInformation; 32 | } 33 | 34 | public override string Text => "Add destructuring to property"; 35 | 36 | public override bool IsAvailable(IUserDataHolder cache) 37 | { 38 | return _tokenInformation.DocumentRange.IsValid(); 39 | } 40 | 41 | protected override Action ExecutePsiTransaction(ISolution solution, IProgressIndicator progress) 42 | { 43 | using (WriteLockCookie.Create()) 44 | { 45 | var factory = CSharpElementFactory.GetInstance(_tokenInformation.StringLiteral.Expression, false); 46 | var startIndex = _tokenInformation.RelativeStartIndex; 47 | var expression = factory.CreateExpression( 48 | $"\"{_tokenInformation.StringLiteral.Expression.GetUnquotedText().Insert(startIndex + 1, "@")}\""); 49 | 50 | // ReSharper disable once AssignNullToNotNullAttribute 51 | ModificationUtil.ReplaceChild(_tokenInformation.StringLiteral.Expression, expression); 52 | } 53 | 54 | return null; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/QuickFixes/RemoveTrailingPeriodFix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | 4 | using JetBrains.Annotations; 5 | using JetBrains.Application.Progress; 6 | using JetBrains.ProjectModel; 7 | using JetBrains.ReSharper.Feature.Services.QuickFixes; 8 | using JetBrains.ReSharper.Psi.CSharp; 9 | using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree; 10 | using JetBrains.ReSharper.Psi.Tree; 11 | using JetBrains.ReSharper.Psi.Util; 12 | using JetBrains.ReSharper.Resources.Shell; 13 | using JetBrains.TextControl; 14 | using JetBrains.Util; 15 | 16 | using ReSharper.Structured.Logging.Highlighting; 17 | 18 | namespace ReSharper.Structured.Logging.QuickFixes 19 | { 20 | [QuickFix] 21 | public class RemoveTrailingPeriodFix : ScopedQuickFixBase 22 | { 23 | private readonly IStringLiteralAlterer _stringLiteral; 24 | 25 | private readonly Regex _regex; 26 | 27 | public RemoveTrailingPeriodFix([NotNull] LogMessageIsSentenceWarning error) 28 | { 29 | _stringLiteral = error.StringLiteral; 30 | _regex = error.Regex; 31 | } 32 | 33 | public override string Text => "Remove period"; 34 | 35 | public override bool IsAvailable(IUserDataHolder cache) 36 | { 37 | return _stringLiteral.Expression.GetDocumentRange().IsValid(); 38 | } 39 | 40 | /// 41 | protected override ITreeNode TryGetContextTreeNode() 42 | { 43 | return _stringLiteral.Expression; 44 | } 45 | 46 | protected override Action ExecutePsiTransaction(ISolution solution, IProgressIndicator progress) 47 | { 48 | using (WriteLockCookie.Create()) 49 | { 50 | var factory = CSharpElementFactory.GetInstance(_stringLiteral.Expression, false); 51 | var expression = factory.CreateExpression( 52 | $"\"{_regex.Replace(_stringLiteral.Expression.GetUnquotedText(), string.Empty)}\""); 53 | 54 | ModificationUtil.ReplaceChild(_stringLiteral.Expression, expression); 55 | } 56 | 57 | return null; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/QuickFixes/RenameContextLogPropertyFix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using JetBrains.Annotations; 4 | using JetBrains.Application.Progress; 5 | using JetBrains.ProjectModel; 6 | using JetBrains.ReSharper.Feature.Services.QuickFixes; 7 | using JetBrains.ReSharper.Psi.CSharp; 8 | using JetBrains.ReSharper.Psi.CSharp.Tree; 9 | using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree; 10 | using JetBrains.ReSharper.Resources.Shell; 11 | using JetBrains.TextControl; 12 | using JetBrains.Util; 13 | 14 | using ReSharper.Structured.Logging.Highlighting; 15 | 16 | namespace ReSharper.Structured.Logging.QuickFixes 17 | { 18 | [QuickFix] 19 | public class RenameContextLogPropertyFix : QuickFixBase 20 | { 21 | private readonly ICSharpArgument _argument; 22 | 23 | private readonly string _suggestedName; 24 | 25 | public RenameContextLogPropertyFix([NotNull] InconsistentContextLogPropertyNamingWarning error) 26 | { 27 | _suggestedName = error.SuggestedName; 28 | _argument = error.Argument; 29 | } 30 | 31 | public override string Text => $"Rename property to '{_suggestedName}'"; 32 | 33 | public override bool IsAvailable(IUserDataHolder cache) 34 | { 35 | return _argument.IsValid(); 36 | } 37 | 38 | protected override Action ExecutePsiTransaction(ISolution solution, IProgressIndicator progress) 39 | { 40 | using (WriteLockCookie.Create()) 41 | { 42 | // ReSharper disable once AssignNullToNotNullAttribute 43 | var factory = CSharpElementFactory.GetInstance(_argument.Expression, false); 44 | 45 | var expression = factory.CreateExpression($"\"{_suggestedName}\""); 46 | ModificationUtil.ReplaceChild(_argument.Expression, expression); 47 | } 48 | 49 | return null; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/QuickFixes/RenameLogPropertyFix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using JetBrains.Annotations; 4 | using JetBrains.Application.Progress; 5 | using JetBrains.ProjectModel; 6 | using JetBrains.ReSharper.Feature.Services.QuickFixes; 7 | using JetBrains.ReSharper.Psi.CSharp; 8 | using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree; 9 | using JetBrains.ReSharper.Psi.Util; 10 | using JetBrains.ReSharper.Resources.Shell; 11 | using JetBrains.TextControl; 12 | using JetBrains.Util; 13 | 14 | using ReSharper.Structured.Logging.Highlighting; 15 | using ReSharper.Structured.Logging.Models; 16 | using ReSharper.Structured.Logging.Serilog.Parsing; 17 | 18 | namespace ReSharper.Structured.Logging.QuickFixes 19 | { 20 | [QuickFix] 21 | public class RenameLogPropertyFix : QuickFixBase 22 | { 23 | private readonly PropertyToken _namedProperty; 24 | 25 | private readonly MessageTemplateTokenInformation _tokenInformation; 26 | 27 | private readonly string _suggestedName; 28 | 29 | public RenameLogPropertyFix([NotNull] InconsistentLogPropertyNamingWarning error) 30 | { 31 | _namedProperty = error.NamedProperty; 32 | _suggestedName = error.SuggestedName; 33 | _tokenInformation = error.TokenInformation; 34 | } 35 | 36 | public override string Text => $"Rename property to '{_suggestedName}'"; 37 | 38 | public override bool IsAvailable(IUserDataHolder cache) 39 | { 40 | return _tokenInformation.DocumentRange.IsValid(); 41 | } 42 | 43 | protected override Action ExecutePsiTransaction(ISolution solution, IProgressIndicator progress) 44 | { 45 | using (WriteLockCookie.Create()) 46 | { 47 | var factory = CSharpElementFactory.GetInstance(_tokenInformation.StringLiteral.Expression, false); 48 | var relativeStartIndex = _tokenInformation.RelativeStartIndex; 49 | var startIndex = _namedProperty.Destructuring == Destructuring.Default 50 | ? relativeStartIndex + 1 51 | : relativeStartIndex + 2; 52 | var length = _namedProperty.Destructuring == Destructuring.Default 53 | ? _namedProperty.Length - 2 54 | : _namedProperty.Length - 3; 55 | 56 | var expression = factory.CreateExpression( 57 | $"\"{_tokenInformation.StringLiteral.Expression.GetUnquotedText().Remove(startIndex, length).Insert(startIndex, _suggestedName)}\""); 58 | ModificationUtil.ReplaceChild(_tokenInformation.StringLiteral.Expression, expression); 59 | } 60 | 61 | return null; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/ReSharper.Structured.Logging.Rider.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net472 4 | false 5 | latest 6 | 7 | 8 | 9 | true 10 | false 11 | ReSharper.Structured.Logging 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/ReSharper.Structured.Logging.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net472 4 | false 5 | latest 6 | 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | bin\$(MSBuildProjectName)\$(Configuration)\ 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Serilog/Core/IMessageTemplateParser.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using ReSharper.Structured.Logging.Serilog.Events; 16 | 17 | namespace ReSharper.Structured.Logging.Serilog.Core 18 | { 19 | public interface IMessageTemplateParser 20 | { 21 | MessageTemplate Parse(string messageTemplate); 22 | } 23 | } -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Serilog/Events/MessageTemplate.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Collections.Generic; 17 | using System.Linq; 18 | 19 | using ReSharper.Structured.Logging.Serilog.Parsing; 20 | 21 | namespace ReSharper.Structured.Logging.Serilog.Events 22 | { 23 | /// 24 | /// Represents a message template passed to a log method. The template 25 | /// can subsequently render the template in textual form given the list 26 | /// of properties. 27 | /// 28 | public class MessageTemplate 29 | { 30 | /// 31 | /// Represents the empty message template. 32 | /// 33 | public static MessageTemplate Empty { get; } = new MessageTemplate(Enumerable.Empty()); 34 | 35 | readonly MessageTemplateToken[] _tokens; 36 | 37 | /// 38 | /// Construct a message template using manually-defined text and property tokens. 39 | /// 40 | /// The text and property tokens defining the template. 41 | public MessageTemplate(IEnumerable tokens) 42 | // ReSharper disable PossibleMultipleEnumeration 43 | : this(string.Join("", tokens), tokens) 44 | // ReSharper enable PossibleMultipleEnumeration 45 | { 46 | } 47 | 48 | /// 49 | /// Construct a message template using manually-defined text and property tokens. 50 | /// 51 | /// The full text of the template; used by Serilog internally to avoid unneeded 52 | /// string concatenation. 53 | /// The text and property tokens defining the template. 54 | public MessageTemplate(string text, IEnumerable tokens) 55 | { 56 | if (text == null) throw new ArgumentNullException(nameof(text)); 57 | if (tokens == null) throw new ArgumentNullException(nameof(tokens)); 58 | 59 | Text = text; 60 | _tokens = tokens.ToArray(); 61 | 62 | var propertyTokens = GetElementsOfTypeToArray(_tokens); 63 | if (propertyTokens.Length != 0) 64 | { 65 | var allPositional = true; 66 | var anyPositional = false; 67 | foreach (var propertyToken in propertyTokens) 68 | { 69 | if (propertyToken.IsPositional) 70 | anyPositional = true; 71 | else 72 | allPositional = false; 73 | } 74 | 75 | if (allPositional) 76 | { 77 | PositionalProperties = propertyTokens; 78 | } 79 | else 80 | { 81 | if (anyPositional) 82 | IsMixedTemplate = true; 83 | 84 | NamedProperties = propertyTokens; 85 | } 86 | } 87 | } 88 | 89 | /// 90 | /// Similar to , but faster. 91 | /// 92 | static TResult[] GetElementsOfTypeToArray(MessageTemplateToken[] tokens) 93 | where TResult: class 94 | { 95 | var result = new List(tokens.Length / 2); 96 | for (var i = 0; i < tokens.Length; i++) 97 | { 98 | var token = tokens[i] as TResult; 99 | if (token != null) 100 | { 101 | result.Add(token); 102 | } 103 | } 104 | return result.ToArray(); 105 | } 106 | 107 | /// 108 | /// The raw text describing the template. 109 | /// 110 | public string Text { get; } 111 | 112 | /// 113 | /// Render the template as a string. 114 | /// 115 | /// The string representation of the template. 116 | public override string ToString() 117 | { 118 | return Text; 119 | } 120 | 121 | /// 122 | /// The tokens parsed from the template. 123 | /// 124 | public IEnumerable Tokens => _tokens; 125 | 126 | internal MessageTemplateToken[] TokenArray => _tokens; 127 | 128 | internal PropertyToken[] NamedProperties { get; } 129 | 130 | internal PropertyToken[] PositionalProperties { get; } 131 | 132 | public bool IsMixedTemplate { get; } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Serilog/Parsing/Alignment.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace ReSharper.Structured.Logging.Serilog.Parsing 16 | { 17 | /// 18 | /// A structure representing the alignment settings to apply when rendering a property. 19 | /// 20 | public struct Alignment 21 | { 22 | /// 23 | /// Initializes a new instance of . 24 | /// 25 | /// The text alignment direction. 26 | /// The width of the text, in characters. 27 | public Alignment(AlignmentDirection direction, int width) 28 | { 29 | Direction = direction; 30 | Width = width; 31 | } 32 | 33 | /// 34 | /// The text alignment direction. 35 | /// 36 | public AlignmentDirection Direction { get; } 37 | 38 | /// 39 | /// The width of the text. 40 | /// 41 | public int Width { get; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Serilog/Parsing/AlignmentDirection.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace ReSharper.Structured.Logging.Serilog.Parsing 16 | { 17 | /// 18 | /// Defines the direction of the alignment. 19 | /// 20 | public enum AlignmentDirection 21 | { 22 | /// 23 | /// Text will be left-aligned. 24 | /// 25 | Left, 26 | /// 27 | /// Text will be right-aligned. 28 | /// 29 | Right 30 | } 31 | } -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Serilog/Parsing/Destructuring.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace ReSharper.Structured.Logging.Serilog.Parsing 16 | { 17 | /// 18 | /// Instructs the logger on how to store information about provided 19 | /// parameters. 20 | /// 21 | public enum Destructuring 22 | { 23 | /// 24 | /// Convert known types and objects to scalars, arrays to sequences. 25 | /// 26 | Default, 27 | 28 | /// 29 | /// Convert all types to scalar strings. Prefix name with '$'. 30 | /// 31 | Stringify, 32 | 33 | /// 34 | /// Convert known types to scalars, destructure objects and collections 35 | /// into sequences and structures. Prefix name with '@'. 36 | /// 37 | Destructure 38 | } 39 | } -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Serilog/Parsing/MessageTemplateToken.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace ReSharper.Structured.Logging.Serilog.Parsing 16 | { 17 | /// 18 | /// An element parsed from a message template string. 19 | /// 20 | public abstract class MessageTemplateToken 21 | { 22 | /// 23 | /// Construct a . 24 | /// 25 | /// The token's start index in the template. 26 | protected MessageTemplateToken(int startIndex) 27 | { 28 | StartIndex = startIndex; 29 | } 30 | 31 | /// 32 | /// The token's start index in the template. 33 | /// 34 | // ReSharper disable once UnusedAutoPropertyAccessor.Global 35 | public int StartIndex { get; } 36 | 37 | /// 38 | /// The token's length. 39 | /// 40 | public abstract int Length { get; } 41 | } 42 | } -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Serilog/Parsing/TextToken.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | 17 | namespace ReSharper.Structured.Logging.Serilog.Parsing 18 | { 19 | /// 20 | /// A message template token representing literal text. 21 | /// 22 | public sealed class TextToken : MessageTemplateToken 23 | { 24 | /// 25 | /// Construct a . 26 | /// 27 | /// The text of the token. 28 | /// The token's start index in the template. 29 | /// 30 | public TextToken(string text, int startIndex = -1) : base(startIndex) 31 | { 32 | Text = text ?? throw new ArgumentNullException(nameof(text)); 33 | } 34 | 35 | /// 36 | /// The token's length. 37 | /// 38 | public override int Length => Text.Length; 39 | 40 | /// 41 | /// Determines whether the specified is equal to the current . 42 | /// 43 | /// 44 | /// true if the specified object is equal to the current object; otherwise, false. 45 | /// 46 | /// The object to compare with the current object. 2 47 | public override bool Equals(object obj) 48 | { 49 | var tt = obj as TextToken; 50 | return tt != null && tt.Text == Text; 51 | } 52 | 53 | /// 54 | /// Serves as a hash function for a particular type. 55 | /// 56 | /// 57 | /// A hash code for the current . 58 | /// 59 | /// 2 60 | public override int GetHashCode() => Text.GetHashCode(); 61 | 62 | /// 63 | /// Returns a string that represents the current object. 64 | /// 65 | /// 66 | /// A string that represents the current object. 67 | /// 68 | /// 2 69 | public override string ToString() => Text; 70 | 71 | /// 72 | /// The text of the token. 73 | /// 74 | public string Text { get; } 75 | } 76 | } -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Settings/PropertyNamingType.cs: -------------------------------------------------------------------------------- 1 | namespace ReSharper.Structured.Logging.Settings 2 | { 3 | public enum PropertyNamingType 4 | { 5 | PascalCase, 6 | 7 | CamelCase, 8 | 9 | SnakeCase, 10 | 11 | /// 12 | /// The elastic naming convention. 13 | /// 14 | /// 15 | /// https://www.elastic.co/guide/en/beats/devguide/current/event-conventions.html 16 | /// 17 | ElasticNaming 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Settings/StructuredLoggingGroup.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.ReSharper.Feature.Services.Daemon; 2 | 3 | namespace ReSharper.Structured.Logging.Settings 4 | { 5 | [RegisterConfigurableHighlightingsGroup(Id, Name)] 6 | public static class StructuredLoggingGroup 7 | { 8 | public const string Id = "StructuredLogging"; 9 | 10 | private const string Name = "Structured Logging Misuse"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Settings/StructuredLoggingOptionsPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using JetBrains.Application.Settings; 4 | using JetBrains.Application.UI.Options; 5 | using JetBrains.Application.UI.Options.OptionPages; 6 | using JetBrains.Application.UI.Options.OptionsDialog; 7 | using JetBrains.IDE.UI.Extensions; 8 | using JetBrains.IDE.UI.Options; 9 | using JetBrains.Lifetimes; 10 | using JetBrains.ReSharper.Feature.Services.Resources; 11 | 12 | namespace ReSharper.Structured.Logging.Settings 13 | { 14 | [OptionsPage(Pid, "Structured Logging", typeof(FeaturesEnvironmentOptionsThemedIcons.StringFormat), ParentId = EnvironmentPage.Pid)] 15 | public class StructuredLoggingOptionsPage : BeSimpleOptionsPage 16 | { 17 | private const string Pid = "StructuredLogging"; 18 | 19 | public StructuredLoggingOptionsPage( 20 | Lifetime lifetime, 21 | OptionsPageContext optionsPageContext, 22 | OptionsSettingsSmartContext optionsSettingsSmartContext, 23 | bool wrapInScrollablePanel = false) 24 | : base(lifetime, optionsPageContext, optionsSettingsSmartContext, wrapInScrollablePanel) 25 | { 26 | AddHeader("Log properties naming style"); 27 | AddComboOptionFromEnum( 28 | (StructuredLoggingSettings settings) => settings.PropertyNamingType, 29 | type => 30 | { 31 | switch (type) 32 | { 33 | case PropertyNamingType.PascalCase: 34 | return "PascalCase"; 35 | case PropertyNamingType.CamelCase: 36 | return "camelCase"; 37 | case PropertyNamingType.SnakeCase: 38 | return "snake_case"; 39 | case PropertyNamingType.ElasticNaming: 40 | return "elastic.naming"; 41 | default: 42 | throw new ArgumentOutOfRangeException(nameof(type), type, null); 43 | } 44 | }); 45 | 46 | AddHeader("Ignored properties naming"); 47 | AddCommentText("You may specify a regular expression here and if a property matches this expression, then it will be skipped during naming analysis."); 48 | var ignoredRegex = OptionsSettingsSmartContext 49 | .GetValueProperty(lifetime, StructuredLoggingSettingsAccessor.IgnoredPropertiesRegex); 50 | AddControl(ignoredRegex.GetBeTextBox(lifetime)); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Settings/StructuredLoggingSettings.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.Settings; 2 | using JetBrains.Application.Settings.WellKnownRootKeys; 3 | 4 | namespace ReSharper.Structured.Logging.Settings 5 | { 6 | [SettingsKey(typeof(EnvironmentSettings), "Settings for Structured Logging")] 7 | public class StructuredLoggingSettings 8 | { 9 | [SettingsEntry(PropertyNamingType.PascalCase, "Properties naming case")] 10 | public PropertyNamingType PropertyNamingType { get; set; } 11 | 12 | [SettingsEntry("", "Ignored properties RegEx")] 13 | public string IgnoredPropertiesRegex { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Settings/StructuredLoggingSettingsAccessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | using JetBrains.Annotations; 5 | 6 | namespace ReSharper.Structured.Logging.Settings 7 | { 8 | public static class StructuredLoggingSettingsAccessor 9 | { 10 | [NotNull] 11 | public static readonly Expression> PropertyNamingType = x => x.PropertyNamingType; 12 | 13 | [NotNull] 14 | public static readonly Expression> IgnoredPropertiesRegex = x => x.IgnoredPropertiesRegex; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Utils/PropertyNameProvider.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using JetBrains.Application.Settings; 3 | using JetBrains.Util; 4 | 5 | using ReSharper.Structured.Logging.Settings; 6 | 7 | namespace ReSharper.Structured.Logging.Services; 8 | 9 | public static class PropertyNameProvider 10 | { 11 | public static string GetSuggestedName([NotNull]string propertyName, [CanBeNull]IContextBoundSettingsStore settingsStore) 12 | { 13 | var namingType = settingsStore 14 | ?.GetValue(StructuredLoggingSettingsAccessor.PropertyNamingType) 15 | ?? PropertyNamingType.PascalCase; 16 | 17 | switch (namingType) 18 | { 19 | case PropertyNamingType.PascalCase: 20 | return StringUtil.MakeUpperCamelCaseName(propertyName); 21 | case PropertyNamingType.CamelCase: 22 | return StringUtil.MakeUpperCamelCaseName(propertyName).Decapitalize(); 23 | case PropertyNamingType.SnakeCase: 24 | return StringUtil.MakeUnderscoreCaseName(propertyName); 25 | case PropertyNamingType.ElasticNaming: 26 | return StringUtil.MakeUnderscoreCaseName(propertyName).Replace('_', '.'); 27 | default: 28 | return propertyName; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/Wiki/StructuredLoggingWikiDataProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | using JetBrains.Application; 4 | using JetBrains.Application.Parts; 5 | using JetBrains.ReSharper.Feature.Services.Explanatory; 6 | 7 | using ReSharper.Structured.Logging.Highlighting; 8 | 9 | namespace ReSharper.Structured.Logging.Wiki 10 | { 11 | [ShellComponent(Instantiation.DemandAnyThreadSafe)] 12 | public class StructuredLoggingWikiDataProvider : ICodeInspectionWikiDataProvider 13 | { 14 | private static readonly IDictionary AttributeUrlMap = new Dictionary 15 | { 16 | { 17 | DuplicateTemplatePropertyWarning.SeverityId, 18 | CreateSeverityUrl(DuplicateTemplatePropertyWarning.SeverityId) 19 | }, 20 | { 21 | ExceptionPassedAsTemplateArgumentWarning.SeverityId, 22 | CreateSeverityUrl(ExceptionPassedAsTemplateArgumentWarning.SeverityId) 23 | }, 24 | { 25 | TemplateIsNotCompileTimeConstantWarning.SeverityId, 26 | CreateSeverityUrl(TemplateIsNotCompileTimeConstantWarning.SeverityId) 27 | }, 28 | { 29 | AnonymousObjectDestructuringWarning.SeverityId, 30 | CreateSeverityUrl(AnonymousObjectDestructuringWarning.SeverityId) 31 | }, 32 | { 33 | ContextualLoggerWarning.SeverityId, 34 | CreateSeverityUrl(ContextualLoggerWarning.SeverityId) 35 | }, 36 | { 37 | ComplexObjectDestructuringWarning.SeverityId, 38 | CreateSeverityUrl(ComplexObjectDestructuringWarning.SeverityId) 39 | }, 40 | { 41 | PositionalPropertyUsedWarning.SeverityId, 42 | CreateSeverityUrl(PositionalPropertyUsedWarning.SeverityId) 43 | }, 44 | { 45 | InconsistentLogPropertyNamingWarning.SeverityId, 46 | CreateSeverityUrl(InconsistentLogPropertyNamingWarning.SeverityId) 47 | }, 48 | { 49 | LogMessageIsSentenceWarning.SeverityId, 50 | CreateSeverityUrl(LogMessageIsSentenceWarning.SeverityId) 51 | }, 52 | { 53 | ComplexObjectDestructuringInContextWarning.SeverityId, 54 | CreateSeverityUrl(ComplexObjectDestructuringInContextWarning.SeverityId) 55 | }, 56 | { 57 | InconsistentContextLogPropertyNamingWarning.SeverityId, 58 | CreateSeverityUrl(InconsistentContextLogPropertyNamingWarning.SeverityId) 59 | } 60 | }; 61 | 62 | public bool TryGetValue(string attributeId, out string url) 63 | { 64 | return AttributeUrlMap.TryGetValue(attributeId, out url); 65 | } 66 | 67 | private static string CreateSeverityUrl(string severityId) 68 | { 69 | return $"https://github.com/olsh/resharper-structured-logging/blob/master/rules/{severityId}.md"; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/ZoneMarker.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.BuildScript.Application.Zones; 2 | using JetBrains.ReSharper.Psi.CSharp; 3 | 4 | namespace ReSharper.Structured.Logging 5 | { 6 | [ZoneMarker] 7 | public class ZoneMarker : IRequire 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ReSharper.Structured.Logging/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/com/jetbrains/rider/settings/StructuredLoggingBundle.kt: -------------------------------------------------------------------------------- 1 | package com.jetbrains.rider.settings 2 | 3 | import com.intellij.DynamicBundle 4 | import org.jetbrains.annotations.Nls 5 | import org.jetbrains.annotations.NonNls 6 | import org.jetbrains.annotations.PropertyKey 7 | 8 | class StructuredLoggingBundle : DynamicBundle(BUNDLE) { 9 | companion object { 10 | @NonNls 11 | private const val BUNDLE = "messages.StructuredLoggingBundle" 12 | private val INSTANCE: StructuredLoggingBundle = StructuredLoggingBundle() 13 | 14 | @Nls 15 | fun message( 16 | @PropertyKey(resourceBundle = BUNDLE) key: String, 17 | vararg params: Any 18 | ): String { 19 | return INSTANCE.getMessage(key, *params) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/com/jetbrains/rider/settings/StructuredLoggingPluginOptionsPage.kt: -------------------------------------------------------------------------------- 1 | package com.jetbrains.rider.settings 2 | 3 | import com.jetbrains.rider.settings.simple.SimpleOptionsPage 4 | import com.jetbrains.rider.settings.StructuredLoggingBundle 5 | 6 | class StructuredLoggingPluginOptionsPage : SimpleOptionsPage( 7 | name = StructuredLoggingBundle.message("configurable.name.structuredlogging.title"), 8 | pageId = "StructuredLogging") 9 | { 10 | override fun getId(): String { 11 | return "StructuredLogging" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/rider/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.intellij.resharper.StructuredLogging 3 | Structured Logging 4 | 7 | For more information visit the project repository. 8 | 9 |
10 |
11 | Release notes 12 | ]]> 13 | https://github.com/olsh/resharper-structured-logging/releases 14 | 15 | Oleg Shevchenko 16 | 17 | messages.StructuredLoggingBundle 18 | 19 | 25 | 26 | com.intellij.modules.rider 27 | 28 | -------------------------------------------------------------------------------- /src/rider/main/resources/messages/StructuredLoggingBundle.properties: -------------------------------------------------------------------------------- 1 | configurable.name.structuredlogging.title=Structured Logging 2 | -------------------------------------------------------------------------------- /test/data/Analyzers/AnonymousTypeDestructure/SerilogWithComplexPropertyWithoutDestructure.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using System; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information("{MyProperty}", new { Test = 1, Complex = new Random() }); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/data/Analyzers/AnonymousTypeDestructure/SerilogWithComplexPropertyWithoutDestructure.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using System; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information("|{MyProperty}|(0)", new { Test = 1, Complex = new Random() }); 11 | } 12 | } 13 | } 14 | 15 | --------------------------------------------------------- 16 | (0): ReSharper Warning: Anonymous objects must be destructured 17 | -------------------------------------------------------------------------------- /test/data/Analyzers/AnonymousTypeDestructure/SerilogWithoutDestructure.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{MyProperty}", new { Test = 1 }); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/Analyzers/AnonymousTypeDestructure/SerilogWithoutDestructure.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("|{MyProperty}|(0)", new { Test = 1 }); 10 | } 11 | } 12 | } 13 | 14 | --------------------------------------------------------- 15 | (0): ReSharper Warning: Anonymous objects must be destructured 16 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogContextExplicitDestructure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | using Serilog.Context; 4 | 5 | namespace ConsoleApp 6 | { 7 | public static class Program 8 | { 9 | public static void Main() 10 | { 11 | LogContext.PushProperty("Test", new Random(), true); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogContextExplicitDestructure.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | using Serilog.Context; 4 | 5 | namespace ConsoleApp 6 | { 7 | public static class Program 8 | { 9 | public static void Main() 10 | { 11 | LogContext.PushProperty("Test", new Random(), true); 12 | } 13 | } 14 | } 15 | 16 | --------------------------------------------------------- 17 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogContextNumericWithoutDestructure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | using Serilog.Context; 4 | 5 | namespace ConsoleApp 6 | { 7 | public static class Program 8 | { 9 | public static void Main() 10 | { 11 | LogContext.PushProperty("Test", 1); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogContextNumericWithoutDestructure.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | using Serilog.Context; 4 | 5 | namespace ConsoleApp 6 | { 7 | public static class Program 8 | { 9 | public static void Main() 10 | { 11 | LogContext.PushProperty("Test", 1); 12 | } 13 | } 14 | } 15 | 16 | --------------------------------------------------------- 17 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogContextWithoutDestructure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | using Serilog.Context; 4 | 5 | namespace ConsoleApp 6 | { 7 | public static class Program 8 | { 9 | public static void Main() 10 | { 11 | LogContext.PushProperty("Test", new Random()); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogContextWithoutDestructure.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | using Serilog.Context; 4 | 5 | namespace ConsoleApp 6 | { 7 | public static class Program 8 | { 9 | public static void Main() 10 | { 11 | |LogContext.PushProperty("Test", new Random())|(0); 12 | } 13 | } 14 | } 15 | 16 | --------------------------------------------------------- 17 | (0): ReSharper Warning: Complex objects with default ToString() implementation probably need to be destructured 18 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogCustomExceptionWithoutDestructure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Error(new MyException(), "{MyProperty}", new Random()); 11 | } 12 | } 13 | 14 | public class MyException : Exception 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogCustomExceptionWithoutDestructure.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Error(new MyException(), "|{MyProperty}|(0)", new Random()); 11 | } 12 | } 13 | 14 | public class MyException : Exception 15 | { 16 | } 17 | } 18 | 19 | --------------------------------------------------------- 20 | (0): ReSharper Warning: Complex objects with default ToString() implementation probably need to be destructured 21 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogDictionaryWithoutDestructure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Serilog; 4 | 5 | namespace ConsoleApp 6 | { 7 | public static class Program 8 | { 9 | public static void Main() 10 | { 11 | Log.Logger.Information("{$MyProperty}", new Dictionary()); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogDictionaryWithoutDestructure.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Serilog; 4 | 5 | namespace ConsoleApp 6 | { 7 | public static class Program 8 | { 9 | public static void Main() 10 | { 11 | Log.Logger.Information("{$MyProperty}", new Dictionary()); 12 | } 13 | } 14 | } 15 | 16 | --------------------------------------------------------- 17 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogEnumerableWithoutDestructure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Serilog; 4 | 5 | namespace ConsoleApp 6 | { 7 | public static class Program 8 | { 9 | public static void Main() 10 | { 11 | IEnumerable list = new List() { "test" }; 12 | Log.Logger.Information("{MyProperty}", list); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogEnumerableWithoutDestructure.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Serilog; 4 | 5 | namespace ConsoleApp 6 | { 7 | public static class Program 8 | { 9 | public static void Main() 10 | { 11 | IEnumerable list = new List() { "test" }; 12 | Log.Logger.Information("{MyProperty}", list); 13 | } 14 | } 15 | } 16 | 17 | --------------------------------------------------------- 18 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogForceStringWithoutDestructure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information("{$MyProperty}", new Random()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogForceStringWithoutDestructure.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information("{$MyProperty}", new Random()); 11 | } 12 | } 13 | } 14 | 15 | --------------------------------------------------------- 16 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogNullableWithoutDestructure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | int? a = 1; 11 | Log.Logger.Information("{$MyProperty}", a); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogNullableWithoutDestructure.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | int? a = 1; 11 | Log.Logger.Information("{$MyProperty}", a); 12 | } 13 | } 14 | } 15 | 16 | --------------------------------------------------------- 17 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogNumericWithoutDestructure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information("{$MyProperty}", 3); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogNumericWithoutDestructure.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information("{$MyProperty}", 3); 11 | } 12 | } 13 | } 14 | 15 | --------------------------------------------------------- 16 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogParentWithOverriddenToString.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information("{MyProperty}", new B()); 11 | } 12 | } 13 | 14 | public class A 15 | { 16 | public override string ToString() => "Custom ToString"; 17 | } 18 | 19 | public class B: A { } 20 | } 21 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogParentWithOverriddenToString.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information("{MyProperty}", new B()); 11 | } 12 | } 13 | 14 | public class A 15 | { 16 | public override string ToString() => "Custom ToString"; 17 | } 18 | 19 | public class B: A { } 20 | } 21 | 22 | --------------------------------------------------------- 23 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogWithoutDestructure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information("{MyProperty}", new Random()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/data/Analyzers/ComplexTypeDestructure/SerilogWithoutDestructure.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information("|{MyProperty}|(0)", new Random()); 11 | } 12 | } 13 | } 14 | 15 | --------------------------------------------------------- 16 | (0): ReSharper Warning: Complex objects with default ToString() implementation probably need to be destructured 17 | -------------------------------------------------------------------------------- /test/data/Analyzers/ContextualLoggerConstructor/MicrosoftCorrectContextType.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | class A 4 | { 5 | ILogger _log; 6 | 7 | public A(ILogger log) 8 | { 9 | _log = log; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/data/Analyzers/ContextualLoggerConstructor/MicrosoftCorrectContextType.cs.gold: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | class A 4 | { 5 | ILogger _log; 6 | 7 | public A(ILogger log) 8 | { 9 | _log = log; 10 | } 11 | } 12 | 13 | --------------------------------------------------------- 14 | -------------------------------------------------------------------------------- /test/data/Analyzers/ContextualLoggerConstructor/MicrosoftWrongContextType.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | class A 4 | { 5 | ILogger _log; 6 | 7 | public A(ILogger log) 8 | { 9 | _log = log; 10 | } 11 | } 12 | 13 | class B { } 14 | -------------------------------------------------------------------------------- /test/data/Analyzers/ContextualLoggerConstructor/MicrosoftWrongContextType.cs.gold: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | class A 4 | { 5 | ILogger _log; 6 | 7 | public A(|ILogger|(0) log) 8 | { 9 | _log = log; 10 | } 11 | } 12 | 13 | class B { } 14 | 15 | --------------------------------------------------------- 16 | (0): ReSharper Warning: Incorrect type is used for contextual logger 17 | -------------------------------------------------------------------------------- /test/data/Analyzers/ContextualLoggerConstructor/MicrosoftWrongContextTypeMultipleNamespaces.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace X 4 | { 5 | class A { } 6 | } 7 | 8 | namespace Y 9 | { 10 | class A 11 | { 12 | ILogger _log; 13 | 14 | public A(ILogger log) 15 | { 16 | _log = log; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/data/Analyzers/ContextualLoggerConstructor/MicrosoftWrongContextTypeMultipleNamespaces.cs.gold: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace X 4 | { 5 | class A { } 6 | } 7 | 8 | namespace Y 9 | { 10 | class A 11 | { 12 | ILogger _log; 13 | 14 | public A(|ILogger|(0) log) 15 | { 16 | _log = log; 17 | } 18 | } 19 | } 20 | 21 | --------------------------------------------------------- 22 | (0): ReSharper Warning: Incorrect type is used for contextual logger 23 | -------------------------------------------------------------------------------- /test/data/Analyzers/ContextualLoggerConstructor/MicrosoftWrongContextTypeMultipleParameters.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | class A 4 | { 5 | ILogger _log; 6 | 7 | public A(int a, ILogger log) 8 | { 9 | _log = log; 10 | } 11 | } 12 | 13 | class B { } 14 | -------------------------------------------------------------------------------- /test/data/Analyzers/ContextualLoggerConstructor/MicrosoftWrongContextTypeMultipleParameters.cs.gold: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | class A 4 | { 5 | ILogger _log; 6 | 7 | public A(int a, |ILogger|(0) log) 8 | { 9 | _log = log; 10 | } 11 | } 12 | 13 | class B { } 14 | 15 | --------------------------------------------------------- 16 | (0): ReSharper Warning: Incorrect type is used for contextual logger 17 | -------------------------------------------------------------------------------- /test/data/Analyzers/ContextualLoggerSerilogFactory/SerilogCorrectContextType.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | class A 4 | { 5 | private static readonly ILogger Logger = Logger.ForContext(); 6 | } 7 | 8 | class B {} 9 | -------------------------------------------------------------------------------- /test/data/Analyzers/ContextualLoggerSerilogFactory/SerilogCorrectContextType.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | class A 4 | { 5 | private static readonly ILogger Logger = Logger.ForContext(); 6 | } 7 | 8 | class B {} 9 | 10 | --------------------------------------------------------- 11 | -------------------------------------------------------------------------------- /test/data/Analyzers/ContextualLoggerSerilogFactory/SerilogWrongContextType.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | class A 4 | { 5 | private static readonly ILogger Logger = Logger.ForContext(); 6 | } 7 | 8 | class B {} 9 | -------------------------------------------------------------------------------- /test/data/Analyzers/ContextualLoggerSerilogFactory/SerilogWrongContextType.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | class A 4 | { 5 | private static readonly ILogger Logger = |Logger.ForContext()|(0); 6 | } 7 | 8 | class B {} 9 | 10 | --------------------------------------------------------- 11 | (0): ReSharper Warning: Incorrect type is used for contextual logger 12 | -------------------------------------------------------------------------------- /test/data/Analyzers/CorrectExceptionPassing/SerilogCorrectExceptionPassing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information(new Exception(), "{One}", 1); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/data/Analyzers/CorrectExceptionPassing/SerilogCorrectExceptionPassing.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information(new Exception(), "{One}", 1); 11 | } 12 | } 13 | } 14 | 15 | --------------------------------------------------------- 16 | -------------------------------------------------------------------------------- /test/data/Analyzers/CorrectExceptionPassing/SerilogIncorrectExceptionPassing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information("{One} {Exc}", 1, new Exception()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/data/Analyzers/CorrectExceptionPassing/SerilogIncorrectExceptionPassing.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information("{One} {Exc}", 1, |new Exception()|(0)); 11 | } 12 | } 13 | } 14 | 15 | --------------------------------------------------------- 16 | (0): ReSharper Warning: Exception should be passed to the exception argument 17 | -------------------------------------------------------------------------------- /test/data/Analyzers/CorrectExceptionPassing/SerilogIncorrectExceptionPassingDynamicTemplate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information($"{DateTime.Now} {{Error}}", new Exception()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/data/Analyzers/CorrectExceptionPassing/SerilogIncorrectExceptionPassingDynamicTemplate.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information($"{DateTime.Now} |{{|(0)Error|}}|(1)", |new Exception()|(2)); 11 | } 12 | } 13 | } 14 | 15 | --------------------------------------------------------- 16 | (0): ReSharper C# Escape Character 1: 17 | (1): ReSharper C# Escape Character 1: 18 | (2): ReSharper Warning: Exception should be passed to the exception argument 19 | -------------------------------------------------------------------------------- /test/data/Analyzers/CorrectExceptionPassing/SerilogMultipleExceptionPassing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information(new Exception(), "{One} {OtherException}", 1, new Exception()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/data/Analyzers/CorrectExceptionPassing/SerilogMultipleExceptionPassing.cs.gold: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information(new Exception(), "{One} {OtherException}", 1, new Exception()); 11 | } 12 | } 13 | } 14 | 15 | --------------------------------------------------------- 16 | -------------------------------------------------------------------------------- /test/data/Analyzers/DuplicatePropertiesTemplate/SerilogDuplicateNamedProperty.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{Test} {Test}", 1, 2); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/Analyzers/DuplicatePropertiesTemplate/SerilogDuplicateNamedProperty.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("|{Test}|(0) |{Test}|(1)", 1, 2); 10 | } 11 | } 12 | } 13 | 14 | --------------------------------------------------------- 15 | (0): ReSharper Warning: Duplicate properties in message template 16 | (1): ReSharper Warning: Duplicate properties in message template 17 | -------------------------------------------------------------------------------- /test/data/Analyzers/LogMessageIsSentence/SerilogNotSentenceMessage.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("Loading {Name}...", "World"); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/Analyzers/LogMessageIsSentence/SerilogNotSentenceMessage.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("Loading {Name}...", "World"); 10 | } 11 | } 12 | } 13 | 14 | --------------------------------------------------------- 15 | -------------------------------------------------------------------------------- /test/data/Analyzers/LogMessageIsSentence/SerilogSentenceMessage.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("Hello {Name}.", "World"); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/Analyzers/LogMessageIsSentence/SerilogSentenceMessage.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information(|"Hello {Name}."|(0), "World"); 10 | } 11 | } 12 | } 13 | 14 | --------------------------------------------------------- 15 | (0): ReSharper Warning: Log event messages should be fragments, not sentences. Avoid a trailing period/full stop. 16 | -------------------------------------------------------------------------------- /test/data/Analyzers/PositionalPropertiesUsage/SerilogPositionProperty.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{0}", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/Analyzers/PositionalPropertiesUsage/SerilogPositionProperty.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("|{0}|(0)", 1); 10 | } 11 | } 12 | } 13 | 14 | --------------------------------------------------------- 15 | (0): ReSharper Warning: Prefer named properties instead of positional ones 16 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogContextInterpolatedStringProperty.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Context; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | var s = "world"; 11 | LogContext.PushProperty($"Hello{s}", 1); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogContextInterpolatedStringProperty.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Context; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | var s = "world"; 11 | LogContext.PushProperty($"Hello{s}", 1); 12 | } 13 | } 14 | } 15 | 16 | --------------------------------------------------------- 17 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogContextInvalidNamedProperty.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Context; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | LogContext.PushProperty("test", 1); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogContextInvalidNamedProperty.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Context; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | LogContext.PushProperty(|"test"|(0), 1); 11 | } 12 | } 13 | } 14 | 15 | --------------------------------------------------------- 16 | (0): ReSharper Warning: Property name 'test' does not match naming rules. Suggested name is 'Test'. 17 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogIgnoredInvalidNamedProperty.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{MY_IGNORED.Property_}", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogIgnoredInvalidNamedProperty.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{MY_IGNORED.Property_}", 1); 10 | } 11 | } 12 | } 13 | 14 | --------------------------------------------------------- 15 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogInvalidElasticNamedProperty.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{myProperty}", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogInvalidElasticNamedProperty.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("|{myProperty}|(0)", 1); 10 | } 11 | } 12 | } 13 | 14 | --------------------------------------------------------- 15 | (0): ReSharper Warning: Property name 'myProperty' does not match naming rules. Suggested name is 'my.property'. 16 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogInvalidNamedProperty.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{myProperty}", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogInvalidNamedProperty.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("|{myProperty}|(0)", 1); 10 | } 11 | } 12 | } 13 | 14 | --------------------------------------------------------- 15 | (0): ReSharper Warning: Property name 'myProperty' does not match naming rules. Suggested name is 'MyProperty'. 16 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogInvalidNamedPropertyWithDot.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{My.Property}", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogInvalidNamedPropertyWithDot.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("|{My.Property}|(0)", 1); 10 | } 11 | } 12 | } 13 | 14 | --------------------------------------------------------- 15 | (0): ReSharper Warning: Property name 'My.Property' does not match naming rules. Suggested name is 'MyProperty'. 16 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogInvalidNamedPropertyWithSpace.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{My Property}", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogInvalidNamedPropertyWithSpace.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("|{My Property}|(0)", 1); 10 | } 11 | } 12 | } 13 | 14 | --------------------------------------------------------- 15 | (0): ReSharper Warning: Property name 'My Property' does not match naming rules. Suggested name is 'MyProperty'. 16 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogInvalidSyntax.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information(%"{MyProperty}", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogInvalidSyntax.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information(%"{MyProperty}", 1); 10 | } 11 | } 12 | } 13 | 14 | --------------------------------------------------------- 15 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogValidDestructuredNamedProperty.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{@MyProperty}", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogValidDestructuredNamedProperty.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{@MyProperty}", 1); 10 | } 11 | } 12 | } 13 | 14 | --------------------------------------------------------- 15 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogValidNamedProperty.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{MyProperty}", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzer/SerilogValidNamedProperty.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{MyProperty}", 1); 10 | } 11 | } 12 | } 13 | 14 | --------------------------------------------------------- 15 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzerDotNet6/ZLoggerInvalidNamedProperty.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using ZLogger; 3 | 4 | namespace ConsoleApp 5 | { 6 | class A 7 | { 8 | public A(ILogger log) 9 | { 10 | log.ZLogInformation("{myProperty}", 1); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/data/Analyzers/PropertiesNamingAnalyzerDotNet6/ZLoggerInvalidNamedProperty.cs.gold: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using ZLogger; 3 | 4 | namespace ConsoleApp 5 | { 6 | class A 7 | { 8 | public A(ILogger log) 9 | { 10 | log.ZLogInformation("|{myProperty}|(0)", 1); 11 | } 12 | } 13 | } 14 | 15 | --------------------------------------------------------- 16 | (0): ReSharper Warning: Property name 'myProperty' does not match naming rules. Suggested name is 'MyProperty'. 17 | -------------------------------------------------------------------------------- /test/data/QuickFixes/AddDestructuringFix/SerilogEscapedString.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("Escaped \r\n {MyPro{caret}perty} \r\n string", new { Test = 1 }); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/QuickFixes/AddDestructuringFix/SerilogEscapedString.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("Escaped \r\n {@MyPro{caret}perty} \r\n string", new { Test = 1 }); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/QuickFixes/AddDestructuringFix/SerilogNewAnonymousObject.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{MyPro{caret}perty}", new { Test = 1 }); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/QuickFixes/AddDestructuringFix/SerilogNewAnonymousObject.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("{@MyPro{caret}perty}", new { Test = 1 }); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/QuickFixes/AddDestructuringFix/SerilogNewComplexObject.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using System; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information("{MyPro{caret}perty}", new Random()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/data/QuickFixes/AddDestructuringFix/SerilogNewComplexObject.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using System; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | Log.Logger.Information("{@MyPro{caret}perty}", new Random()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/data/QuickFixes/RemoveTrailingPeriodFix/SerilogTrailingPeriod.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("Test {caret}{Property} prop.", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/QuickFixes/RemoveTrailingPeriodFix/SerilogTrailingPeriod.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("Test {caret}{Property} prop", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/QuickFixes/RenameContextLogPropertyFix/SerilogContextProperty.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Context; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | LogContext.PushProperty("{caret}test", 1); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/data/QuickFixes/RenameContextLogPropertyFix/SerilogContextProperty.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Context; 3 | 4 | namespace ConsoleApp 5 | { 6 | public static class Program 7 | { 8 | public static void Main() 9 | { 10 | LogContext.PushProperty("T{caret}est", 1); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/data/QuickFixes/RenameLogPropertyFix/SerilogDestructuredProperty.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("Test {@my{caret}Property} prop", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/QuickFixes/RenameLogPropertyFix/SerilogDestructuredProperty.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("Test {@My{caret}Property} prop", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/QuickFixes/RenameLogPropertyFix/SerilogProperty.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("Test {my{caret}Property} prop", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/QuickFixes/RenameLogPropertyFix/SerilogProperty.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("Test {My{caret}Property} prop", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/QuickFixes/RenameLogPropertyFix/SerilogPropertyConcatenated.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("Test" + " {my{caret}Property} prop", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/QuickFixes/RenameLogPropertyFix/SerilogPropertyConcatenated.cs.gold: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace ConsoleApp 4 | { 5 | public static class Program 6 | { 7 | public static void Main() 8 | { 9 | Log.Logger.Information("Test" + " {My{caret}Property} prop", 1); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/src/Analyzer/AnonymousTypeDestructureAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace ReSharper.Structured.Logging.Tests.Analyzer 4 | { 5 | public class AnonymousTypeDestructureAnalyzerTests : MessageTemplateAnalyzerTestBase 6 | { 7 | protected override string SubPath => "AnonymousTypeDestructure"; 8 | 9 | [Test] public void TestSerilogWithoutDestructure() => DoNamedTest2(); 10 | 11 | [Test] public void TestSerilogWithComplexPropertyWithoutDestructure() => DoNamedTest2(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/src/Analyzer/ComplexObjectDestructureAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace ReSharper.Structured.Logging.Tests.Analyzer 4 | { 5 | public class ComplexObjectDestructureAnalyzerTests : MessageTemplateAnalyzerTestBase 6 | { 7 | protected override string SubPath => "ComplexTypeDestructure"; 8 | 9 | [Test] public void TestSerilogWithoutDestructure() => DoNamedTest2(); 10 | 11 | [Test] public void TestSerilogForceStringWithoutDestructure() => DoNamedTest2(); 12 | 13 | [Test] public void TestSerilogNumericWithoutDestructure() => DoNamedTest2(); 14 | 15 | [Test] public void TestSerilogEnumerableWithoutDestructure() => DoNamedTest2(); 16 | 17 | [Test] public void TestSerilogNullableWithoutDestructure() => DoNamedTest2(); 18 | 19 | [Test] public void TestSerilogDictionaryWithoutDestructure() => DoNamedTest2(); 20 | 21 | [Test] public void TestSerilogContextWithoutDestructure() => DoNamedTest2(); 22 | 23 | [Test] public void TestSerilogContextNumericWithoutDestructure() => DoNamedTest2(); 24 | 25 | [Test] public void TestSerilogContextExplicitDestructure() => DoNamedTest2(); 26 | 27 | [Test] public void TestSerilogCustomExceptionWithoutDestructure() => DoNamedTest2(); 28 | 29 | [Test] public void TestSerilogParentWithOverriddenToString() => DoNamedTest2(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/src/Analyzer/ContextualLoggerConstructorAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.ReSharper.TestFramework; 2 | 3 | using NUnit.Framework; 4 | 5 | namespace ReSharper.Structured.Logging.Tests.Analyzer 6 | { 7 | [TestNet60] 8 | public class ContextualLoggerConstructorAnalyzerTests : MessageTemplateAnalyzerTestBase 9 | { 10 | protected override string SubPath => "ContextualLoggerConstructor"; 11 | 12 | [Test] public void TestMicrosoftCorrectContextType() => DoNamedTest2(); 13 | 14 | [Test] public void TestMicrosoftWrongContextType() => DoNamedTest2(); 15 | 16 | [Test] public void TestMicrosoftWrongContextTypeMultipleNamespaces() => DoNamedTest2(); 17 | 18 | [Test] public void TestMicrosoftWrongContextTypeMultipleParameters() => DoNamedTest2(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/src/Analyzer/ContextualLoggerSerilogFactoryAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace ReSharper.Structured.Logging.Tests.Analyzer 4 | { 5 | public class ContextualLoggerSerilogFactoryAnalyzerTests : MessageTemplateAnalyzerTestBase 6 | { 7 | protected override string SubPath => "ContextualLoggerSerilogFactory"; 8 | 9 | [Test] public void TestSerilogCorrectContextType() => DoNamedTest2(); 10 | 11 | [Test] public void TestSerilogWrongContextType() => DoNamedTest2(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/src/Analyzer/CorrectExceptionPassingAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace ReSharper.Structured.Logging.Tests.Analyzer 4 | { 5 | public class CorrectExceptionPassingAnalyzerTests : MessageTemplateAnalyzerTestBase 6 | { 7 | protected override string SubPath => "CorrectExceptionPassing"; 8 | 9 | [Test] public void TestSerilogCorrectExceptionPassing() => DoNamedTest2(); 10 | 11 | [Test] public void TestSerilogIncorrectExceptionPassing() => DoNamedTest2(); 12 | 13 | [Test] public void TestSerilogIncorrectExceptionPassingDynamicTemplate() => DoNamedTest2(); 14 | 15 | [Test] public void TestSerilogMultipleExceptionPassing() => DoNamedTest2(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/src/Analyzer/DuplicatePropertiesTemplateAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace ReSharper.Structured.Logging.Tests.Analyzer 4 | { 5 | public class DuplicatePropertiesTemplateAnalyzerTests : MessageTemplateAnalyzerTestBase 6 | { 7 | protected override string SubPath => "DuplicatePropertiesTemplate"; 8 | 9 | [Test] public void TestSerilogDuplicateNamedProperty() => DoNamedTest2(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/src/Analyzer/LogMessageIsSentenceAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace ReSharper.Structured.Logging.Tests.Analyzer 4 | { 5 | public class LogMessageIsSentenceAnalyzerTests : MessageTemplateAnalyzerTestBase 6 | { 7 | protected override string SubPath => "LogMessageIsSentence"; 8 | 9 | [Test] public void TestSerilogSentenceMessage() => DoNamedTest2(); 10 | 11 | [Test] public void TestSerilogNotSentenceMessage() => DoNamedTest2(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/src/Analyzer/MessageTemplateTests.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using JetBrains.Application.Settings; 3 | using JetBrains.ReSharper.Daemon.StringAnalysis; 4 | using JetBrains.ReSharper.Feature.Services.Daemon; 5 | using JetBrains.ReSharper.FeaturesTestFramework.Daemon; 6 | using JetBrains.ReSharper.Psi; 7 | using JetBrains.ReSharper.TestFramework; 8 | 9 | using ReSharper.Structured.Logging.Highlighting; 10 | using ReSharper.Structured.Logging.Tests.Constants; 11 | 12 | namespace ReSharper.Structured.Logging.Tests.Analyzer 13 | { 14 | [TestPackages( 15 | NugetPackages.SerilogNugetPackage, 16 | NugetPackages.MicrosoftLoggingPackage, 17 | NugetPackages.NlogLoggingPackage, 18 | Inherits = true)] 19 | public abstract class MessageTemplateAnalyzerTestBase : CSharpHighlightingTestBase 20 | { 21 | protected abstract string SubPath { get; } 22 | 23 | protected override string RelativeTestDataPath => @"Analyzers\" + SubPath; 24 | 25 | protected override void DoTestSolution([NotNull] params string[] fileSet) 26 | { 27 | ExecuteWithinSettingsTransaction( 28 | settingsStore => 29 | { 30 | RunGuarded(() => MutateSettings(settingsStore)); 31 | base.DoTestSolution(fileSet); 32 | }); 33 | } 34 | 35 | protected override bool HighlightingPredicate( 36 | IHighlighting highlighting, 37 | IPsiSourceFile sourceFile, 38 | IContextBoundSettingsStore settingsStore) 39 | { 40 | return highlighting is TemplateFormatStringNonExistingArgumentWarning 41 | || highlighting is StringEscapeCharacterHighlighting 42 | || highlighting is DuplicateTemplatePropertyWarning 43 | || highlighting is AnonymousObjectDestructuringWarning 44 | || highlighting is ContextualLoggerWarning 45 | || highlighting is ExceptionPassedAsTemplateArgumentWarning 46 | || highlighting is ComplexObjectDestructuringWarning 47 | || highlighting is ComplexObjectDestructuringInContextWarning 48 | || highlighting is PositionalPropertyUsedWarning 49 | || highlighting is InconsistentLogPropertyNamingWarning 50 | || highlighting is InconsistentContextLogPropertyNamingWarning 51 | || highlighting is LogMessageIsSentenceWarning; 52 | } 53 | 54 | protected virtual void MutateSettings([NotNull] IContextBoundSettingsStore settingsStore) 55 | { 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/src/Analyzer/PositionalPropertiesUsageAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace ReSharper.Structured.Logging.Tests.Analyzer 4 | { 5 | public class PositionalPropertiesUsageAnalyzerTests : MessageTemplateAnalyzerTestBase 6 | { 7 | protected override string SubPath => "PositionalPropertiesUsage"; 8 | 9 | [Test] public void TestSerilogPositionProperty() => DoNamedTest2(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/src/Analyzer/PropertiesElasticNamingAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.Settings; 2 | 3 | using NUnit.Framework; 4 | 5 | using ReSharper.Structured.Logging.Settings; 6 | 7 | namespace ReSharper.Structured.Logging.Tests.Analyzer 8 | { 9 | // ReSharper disable once TestFileNameWarning 10 | public class PropertiesElasticNamingAnalyzerTests : MessageTemplateAnalyzerTestBase 11 | { 12 | protected override string SubPath => "PropertiesNamingAnalyzer"; 13 | 14 | [Test] public void TestSerilogInvalidElasticNamedProperty() => DoNamedTest2(); 15 | 16 | protected override void MutateSettings(IContextBoundSettingsStore settingsStore) 17 | { 18 | settingsStore.SetValue(settings => settings.PropertyNamingType, PropertyNamingType.ElasticNaming); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/src/Analyzer/PropertiesIgnoredRegexNamingAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.Settings; 2 | 3 | using NUnit.Framework; 4 | 5 | using ReSharper.Structured.Logging.Settings; 6 | 7 | namespace ReSharper.Structured.Logging.Tests.Analyzer 8 | { 9 | // ReSharper disable once TestFileNameWarning 10 | public class PropertiesIgnoredRegexNamingAnalyzerTests : MessageTemplateAnalyzerTestBase 11 | { 12 | protected override string SubPath => "PropertiesNamingAnalyzer"; 13 | 14 | [Test] public void TestSerilogIgnoredInvalidNamedProperty() => DoNamedTest2(); 15 | 16 | protected override void MutateSettings(IContextBoundSettingsStore settingsStore) 17 | { 18 | settingsStore.SetValue(settings => settings.IgnoredPropertiesRegex,"MY_.*"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/src/Analyzer/PropertiesNamingAnalyzerDotNet6Tests.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.ReSharper.TestFramework; 2 | 3 | using NUnit.Framework; 4 | 5 | using ReSharper.Structured.Logging.Tests.Constants; 6 | 7 | namespace ReSharper.Structured.Logging.Tests.Analyzer 8 | { 9 | [TestNet60] 10 | [TestPackages( 11 | NugetPackages.ZLoggerLoggingPackage, 12 | Inherits = true)] 13 | public class PropertiesNamingAnalyzerDotNet6Tests : MessageTemplateAnalyzerTestBase 14 | { 15 | protected override string SubPath => "PropertiesNamingAnalyzerDotNet6"; 16 | 17 | [Test] public void TestZLoggerInvalidNamedProperty() => DoNamedTest2(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/src/Analyzer/PropertiesNamingAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace ReSharper.Structured.Logging.Tests.Analyzer 4 | { 5 | public class PropertiesNamingAnalyzerTests : MessageTemplateAnalyzerTestBase 6 | { 7 | protected override string SubPath => "PropertiesNamingAnalyzer"; 8 | 9 | [Test] public void TestSerilogInvalidNamedProperty() => DoNamedTest2(); 10 | 11 | [Test] public void TestSerilogValidNamedProperty() => DoNamedTest2(); 12 | 13 | [Test] public void TestSerilogValidDestructuredNamedProperty() => DoNamedTest2(); 14 | 15 | [Test] public void TestSerilogContextInvalidNamedProperty() => DoNamedTest2(); 16 | 17 | [Test] public void TestSerilogContextInterpolatedStringProperty() => DoNamedTest2(); 18 | 19 | [Test] public void TestSerilogInvalidNamedPropertyWithDot() => DoNamedTest2(); 20 | 21 | [Test] public void TestSerilogInvalidSyntax() => DoNamedTest2(); 22 | 23 | [Test] public void TestSerilogInvalidNamedPropertyWithSpace() => DoNamedTest2(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/src/Constants/NugetPackages.cs: -------------------------------------------------------------------------------- 1 | namespace ReSharper.Structured.Logging.Tests.Constants 2 | { 3 | internal static class NugetPackages 4 | { 5 | public const string SerilogNugetPackage = "Serilog/2.7.1"; 6 | 7 | public const string MicrosoftLoggingPackage = "Microsoft.Extensions.Logging/6.0.0"; 8 | 9 | public const string NlogLoggingPackage = "NLog/4.5.11"; 10 | 11 | public const string ZLoggerLoggingPackage = "ZLogger/1.7.0"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/src/QuickFixes/AddDestructuringToMessageTemplatePropertyFixTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using ReSharper.Structured.Logging.QuickFixes; 3 | 4 | namespace ReSharper.Structured.Logging.Tests.QuickFixes 5 | { 6 | public class AddDestructuringToMessageTemplatePropertyFixTests : QuickFixTestBase 7 | { 8 | protected override string SubPath => "AddDestructuringFix"; 9 | 10 | [Test] public void TestSerilogEscapedString() => DoNamedTest2(); 11 | 12 | [Test] public void TestSerilogNewAnonymousObject() => DoNamedTest2(); 13 | 14 | [Test] public void TestSerilogNewComplexObject() => DoNamedTest2(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/src/QuickFixes/QuickFixTestBase.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.ReSharper.Feature.Services.QuickFixes; 2 | using JetBrains.ReSharper.FeaturesTestFramework.Intentions; 3 | using JetBrains.ReSharper.TestFramework; 4 | 5 | using NUnit.Framework; 6 | 7 | using ReSharper.Structured.Logging.Tests.Constants; 8 | 9 | namespace ReSharper.Structured.Logging.Tests.QuickFixes 10 | { 11 | [TestFixture] 12 | [TestNetFramework46] 13 | [TestPackages( 14 | NugetPackages.SerilogNugetPackage, 15 | NugetPackages.MicrosoftLoggingPackage, 16 | NugetPackages.NlogLoggingPackage, 17 | Inherits = true)] 18 | 19 | // ReSharper disable once TestClassNameSuffixWarning 20 | public abstract class QuickFixTestBase : CSharpQuickFixTestBase 21 | where TQuickFix : IQuickFix 22 | { 23 | protected override string RelativeTestDataPath => @"QuickFixes\" + SubPath; 24 | 25 | protected abstract string SubPath { get; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/src/QuickFixes/RemoveTrailingPeriodFixTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | using ReSharper.Structured.Logging.QuickFixes; 4 | 5 | namespace ReSharper.Structured.Logging.Tests.QuickFixes 6 | { 7 | public class RemoveTrailingPeriodFixTests : QuickFixTestBase 8 | { 9 | protected override string SubPath => "RemoveTrailingPeriodFix"; 10 | 11 | [Test] public void TestSerilogTrailingPeriod() => DoNamedTest2(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/src/QuickFixes/RenameContextLogPropertyFixTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | using ReSharper.Structured.Logging.QuickFixes; 4 | 5 | namespace ReSharper.Structured.Logging.Tests.QuickFixes 6 | { 7 | public class RenameContextLogPropertyFixTests : QuickFixTestBase 8 | { 9 | protected override string SubPath => "RenameContextLogPropertyFix"; 10 | 11 | [Test] public void TestSerilogContextProperty() => DoNamedTest2(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/src/QuickFixes/RenameLogPropertyFixTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | using ReSharper.Structured.Logging.QuickFixes; 4 | 5 | namespace ReSharper.Structured.Logging.Tests.QuickFixes 6 | { 7 | public class RenameLogPropertyFixTests : QuickFixTestBase 8 | { 9 | protected override string SubPath => "RenameLogPropertyFix"; 10 | 11 | [Test] public void TestSerilogProperty() => DoNamedTest2(); 12 | 13 | [Test] public void TestSerilogDestructuredProperty() => DoNamedTest2(); 14 | 15 | [Test] public void TestSerilogPropertyConcatenated() => DoNamedTest2(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/src/ReSharper.Structured.Logging.Rider.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net472 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | true 15 | ReSharper.Structured.Logging.Tests 16 | 17 | 18 | RIDER 19 | bin\$(MSBuildProjectName)\$(Configuration)\ 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/src/ReSharper.Structured.Logging.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net472 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | bin\$(MSBuildProjectName)\$(Configuration)\ 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/src/TestEnvironment.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.BuildScript.Application.Zones; 2 | using JetBrains.ReSharper.TestFramework; 3 | using JetBrains.TestFramework; 4 | using JetBrains.TestFramework.Application.Zones; 5 | 6 | using NUnit.Framework; 7 | 8 | #if RIDER 9 | using JetBrains.Rider.Backend.Env; 10 | #endif 11 | 12 | [assembly: RequiresThread(System.Threading.ApartmentState.STA)] 13 | 14 | namespace ReSharper.Structured.Logging.Tests 15 | { 16 | [ZoneDefinition] 17 | public interface IReSharperSerilog : ITestsEnvZone, IRequire 18 | #if RIDER 19 | , IRequire 20 | #endif 21 | { 22 | } 23 | 24 | [SetUpFixture] 25 | public class TestEnvironment : ExtensionTestEnvironmentAssembly 26 | { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/src/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | --------------------------------------------------------------------------------