|
| 1 | +[//]: # (title: Code Inspections) |
| 2 | + |
| 3 | +A central feature of ReSharper and Rider is to show [Code Inspections](https://www.jetbrains.com/help/rider/Code_Analysis__Code_Inspections.html) for potential quality issues, common practices and improvements, code redundancies, and other [inspection categories](https://www.jetbrains.com/help/rider/Code_Analysis__Code_Inspections.html#categories). Such code inspections are further classified by [severity levels](https://www.jetbrains.com/help/rider/Code_Analysis__Code_Inspections.html#severity), which determine how the code is underlined in the editor. |
| 4 | + |
| 5 | + |
| 6 | + |
| 7 | +You can provide new code inspections by implementing: |
| 8 | + |
| 9 | +- `IHighlighting` – an interface containing all the necessary data to show code inspections (and to enable quick-fixes) |
| 10 | +- `ElementProblemAnalyzer` – a component that gets called by the SDK to analyze syntax trees and find highlightings |
| 11 | + |
| 12 | +> You should declare a [zone marker]() that requires `PsiFeaturesImplZone`. For C# code inspections, you can also require `ILanguageCSharpZone`. |
| 13 | +> |
| 14 | +{type="note"} |
| 15 | + |
| 16 | +## IHighlighting |
| 17 | + |
| 18 | +The `IHighlighting` interface requires to implement 4 members, which return data specific to an individual occurrence of an inspection in code: |
| 19 | + |
| 20 | +- `ToolTip` and `ErrorStripeToolTip` are the strings that are shown while hovering the inspection in the editor or the stripes on the right-hand side of the editor |
| 21 | +- `IsValid()` determines whether the data connected with the inspection are still valid (usually PSI elements or text ranges) |
| 22 | +- `CalculateRange()` returns the range in the document where the inspection should be shown |
| 23 | + |
| 24 | +## Static and Configurable Severities |
| 25 | + |
| 26 | +Independent of an individual `IHighlighting` instance, the SDK requires general information about the inspection type, which is provided through the `StaticSeverityHighlightingAttribute` or `ConfigurableSeverityHighlightingAttribute`. As the names suggest, one is for highlightings with static severities and the other to let users change them: |
| 27 | + |
| 28 | +- `ConfigurableSeverityId` is a unique [inspection identifier](https://www.jetbrains.com/help/rider/Code_Analysis__Code_Inspections.html#ids-of-code-inspections) used for severity configuration and suppression through comments |
| 29 | +- `Languages` defines the languages (C#, ...) for which the inspection is intended |
| 30 | +- `OverlapResolve` defines the policy when two inspections overlap |
| 31 | +- `OverloadResolvePriority` defines the priority when two inspections with the same overlap policy and range collide |
| 32 | + |
| 33 | +- [//]: # (- TODO: MK `ToolTipFormatString*`) |
| 34 | + |
| 35 | +If you've applied the `ConfigurableSeverityHighlightingAttribute`, you also need to add the `RegisterConfigurableSeverity` with the following information: |
| 36 | + |
| 37 | +- `Group` should reference a well-known identifier from `HighlightingGroupIds` (but you can also define your own group) |
| 38 | +- `DefaultSeverity` is the pre-configured severity level of the inspection |
| 39 | +- `Title*` represents the searchable title and is shown in the list of all inspections |
| 40 | +- `Description*` is shown in the bottom field when the inspection is selected |
| 41 | + |
| 42 | +## ElementProblemAnalyzer |
| 43 | + |
| 44 | +The `ElementProblemAnalyzer<T>` component is responsible to examine all syntax trees in the solution and to create highlightings accordingly. The generic parameter `T` is used to constrain the elements on which the analyzer should run, e.g., with `ICSharpDeclaration`, it is only called for types and members. The `Run` is invoked by the SDK with the following parameters: |
| 45 | + |
| 46 | +- `T element` is the start element from where the syntax tree is analyzed |
| 47 | +- `ElementProblemAnalyzerData` provides additional context about the analysis |
| 48 | + - `Solution` and `File` for the analyzed element |
| 49 | + - `SettingsStore` for checking settings that influence the highlighting (except severity) |
| 50 | + - `ThrowIfInterrupted()` should be called in more complex analyzers |
| 51 | +- `IHighlightingConsumer` is used to pass created highlightings out of the analyzer, usually `AddHighlighting` |
| 52 | + |
| 53 | +> When choosing a type for `T`, you should be as specific as possible to keep safe-casts and invocations to a minimum. |
| 54 | +> |
| 55 | +{type="tip"} |
| 56 | + |
| 57 | +To allow the SDK working with the analyzer, it must be marked with the `ElementProblemAnalyzerAttribute` and include the following information: |
| 58 | + |
| 59 | +- `ElementTypes` are the `ITreeNode` abstractions on which the analyzer is called; usually this is the same as `T`, but an analyzer can also implement the non-generic `IElementProblemAnalyzer` |
| 60 | +- `HighlightingTypes` enumerates all highlighting types that are generated by the analyzer |
| 61 | + |
| 62 | +## Sample Implementation |
| 63 | + |
| 64 | +### Highlighting |
| 65 | + |
| 66 | +<!-- snippet: sample-highlighting --> |
| 67 | +```csharp |
| 68 | +[RegisterConfigurableSeverity( |
| 69 | + SeverityId, |
| 70 | + CompoundItemName: null, |
| 71 | + Group: HighlightingGroupIds.CodeSmell, |
| 72 | + Title: Message, |
| 73 | + Description: Description, |
| 74 | + DefaultSeverity: Severity.WARNING)] |
| 75 | +[ConfigurableSeverityHighlighting( |
| 76 | + SeverityId, |
| 77 | + CSharpLanguage.Name, |
| 78 | + OverlapResolve = OverlapResolveKind.ERROR, |
| 79 | + OverloadResolvePriority = 0, |
| 80 | + // Appears in solution-wide analysis |
| 81 | + ToolTipFormatString = Message)] |
| 82 | +public class SampleDeclarationHighlighting : IHighlighting |
| 83 | +{ |
| 84 | + // Appears in suppression comments |
| 85 | + public const string SeverityId = "SampleDeclarationInspection"; |
| 86 | + |
| 87 | + // Appears in settings |
| 88 | + public const string Message = $"ReSharper SDK: {nameof(SampleDeclarationHighlighting)}.{nameof(Message)}"; |
| 89 | + public const string Description = $"ReSharper SDK: {nameof(SampleDeclarationHighlighting)}.{nameof(Description)}"; |
| 90 | + |
| 91 | + public SampleDeclarationHighlighting(ICSharpDeclaration declaration) |
| 92 | + { |
| 93 | + Declaration = declaration; |
| 94 | + } |
| 95 | + |
| 96 | + // Used for IsValid and quick-fixes |
| 97 | + public ICSharpDeclaration Declaration { get; } |
| 98 | + |
| 99 | + public bool IsValid() => Declaration.IsValid(); |
| 100 | + public DocumentRange CalculateRange() => Declaration.NameIdentifier.NotNull().GetHighlightingRange(); |
| 101 | + |
| 102 | + // Appears in editor |
| 103 | + public string ToolTip => $"ReSharper SDK: {nameof(SampleDeclarationHighlighting)}.{nameof(Message)}"; |
| 104 | + |
| 105 | + // Appears in scrollbar |
| 106 | + public string ErrorStripeToolTip => $"ReSharper SDK: {nameof(SampleDeclarationHighlighting)}.{nameof(ErrorStripeToolTip)}"; |
| 107 | +} |
| 108 | +``` |
| 109 | +<!-- endSnippet --> |
| 110 | + |
| 111 | +### ProblemAnalyzer |
| 112 | + |
| 113 | +<!-- snippet: sample-problem-analyzer --> |
| 114 | +```csharp |
| 115 | +[ElementProblemAnalyzer( |
| 116 | + typeof(ICSharpDeclaration), |
| 117 | + // Allows to disable the problem analyzer if code inspection is disabled |
| 118 | + HighlightingTypes = new[] { typeof(SampleDeclarationHighlighting) })] |
| 119 | +public class SampleDeclarationProblemAnalyzer : ElementProblemAnalyzer<ICSharpDeclaration> |
| 120 | +{ |
| 121 | + protected override void Run( |
| 122 | + ICSharpDeclaration element, |
| 123 | + ElementProblemAnalyzerData data, |
| 124 | + IHighlightingConsumer consumer) |
| 125 | + { |
| 126 | + if (element.NameIdentifier?.Name.All(char.IsUpper) ?? true) |
| 127 | + return; |
| 128 | + |
| 129 | + consumer.AddHighlighting(new SampleDeclarationHighlighting(element)); |
| 130 | + } |
| 131 | +} |
| 132 | +``` |
| 133 | +<!-- endSnippet --> |
0 commit comments