forked from Azure/bicep
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLinterRuleBase.cs
152 lines (129 loc) · 6.1 KB
/
LinterRuleBase.cs
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Bicep.Core.Analyzers.Interfaces;
using Bicep.Core.CodeAction;
using Bicep.Core.Configuration;
using Bicep.Core.Diagnostics;
using Bicep.Core.Parsing;
using Bicep.Core.Semantics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Bicep.Core.Analyzers.Linter
{
public abstract class LinterRuleBase : IBicepAnalyzerRule
{
public LinterRuleBase(
string code,
string description,
Uri? docUri = null,
DiagnosticLevel diagnosticLevel = DiagnosticLevel.Warning,
DiagnosticStyling diagnosticStyling = DiagnosticStyling.Default)
{
this.AnalyzerName = LinterAnalyzer.AnalyzerName;
this.Code = code;
this.Description = description;
this.Uri = docUri;
this.DefaultDiagnosticLevel = diagnosticLevel;
this.DiagnosticStyling = diagnosticStyling;
}
public string AnalyzerName { get; }
public string Code { get; }
public readonly string RuleConfigSection = $"{LinterAnalyzer.AnalyzerName}.rules";
public DiagnosticLevel DefaultDiagnosticLevel { get; }
public string Description { get; }
public Uri? Uri { get; }
// If specified, adds the given diagnostic label to every diagnostic created for this rule (such as for unnecessary or obsolete code).
// Should be left as None/null for most rules.
public DiagnosticStyling DiagnosticStyling { get; }
/// <summary>
/// Override to implement detailed message for rule
/// </summary>
/// <param name="values"></param>
/// <returns></returns>
public virtual string FormatMessage(params object[] values) => this.Description;
/// <summary>
/// Gets a message using the supplied parameter values (if any).
/// Otherwise returns the rule description
/// </summary>
/// <param name="values"></param>
/// <returns></returns>
public string GetMessage(params object[] values)
=> (values.Any() ? FormatMessage(values) : this.Description);
public IEnumerable<IDiagnostic> Analyze(SemanticModel model)
{
if (GetDiagnosticLevel(model) == DiagnosticLevel.Off)
{
return Enumerable.Empty<IDiagnostic>();
}
return AnalyzeInternal(model, GetDiagnosticLevel(model));
}
/// <summary>
/// Abstract method each rule must implement to provide analyzer
/// diagnostics through the Analyze API
/// </summary>
public abstract IEnumerable<IDiagnostic> AnalyzeInternal(SemanticModel model, DiagnosticLevel diagnosticLevel);
protected DiagnosticLevel GetDiagnosticLevel(SemanticModel model) => GetDiagnosticLevel(model.Configuration.Analyzers);
protected DiagnosticLevel GetDiagnosticLevel(AnalyzersConfiguration configuration)
{
if (GetConfigurationValue(configuration, "level", DefaultDiagnosticLevel.ToString()) is string configuredLevel && Enum.TryParse<DiagnosticLevel>(configuredLevel, true, out var parsed))
{
return parsed;
}
return DefaultDiagnosticLevel;
}
/// <summary>
/// Get a setting from defaults or local override
/// Expectation: key names for settings are lower case
/// </summary>
/// <typeparam name="T">The type of the value to convert to.</typeparam>
/// <param name="configuration">The configuration of the model being analyzed.</param>
/// <param name="key">The linter configuration key.</param>
/// <param name="defaultValue">The default value to use if no value is found.</param>
/// <returns></returns>
protected T GetConfigurationValue<T>(AnalyzersConfiguration configuration, string key, T defaultValue) =>
configuration.GetValue($"{RuleConfigSection}.{Code}.{key}", defaultValue);
/// <summary>
/// Create a simple diagnostic that displays the defined Description
/// of the derived rule.
/// </summary>
/// <param name="level"></param>
/// <param name="span"></param>
/// <returns></returns>
protected virtual AnalyzerDiagnostic CreateDiagnosticForSpan(DiagnosticLevel level, TextSpan span) =>
new(analyzerName: this.AnalyzerName,
span: span,
level: level,
code: this.Code,
message: this.GetMessage(),
documentationUri: this.Uri,
styling: this.DiagnosticStyling);
/// <summary>
/// Create a diagnostic message for a span that has a customized string
/// formatter defined in the deriving class.
/// </summary>
/// <param name="level"></param>
/// <param name="span"></param>
/// <param name="values"></param>
/// <returns></returns>
protected virtual AnalyzerDiagnostic CreateDiagnosticForSpan(DiagnosticLevel level, TextSpan span, params object[] values) =>
new(analyzerName: this.AnalyzerName,
span: span,
level: level,
code: this.Code,
message: this.GetMessage(values),
documentationUri: this.Uri,
styling: this.DiagnosticStyling);
protected virtual AnalyzerFixableDiagnostic CreateFixableDiagnosticForSpan(DiagnosticLevel level, TextSpan span, CodeFix fix, params object[] values) =>
CreateFixableDiagnosticForSpan(level, span, new[] { fix }, values);
protected virtual AnalyzerFixableDiagnostic CreateFixableDiagnosticForSpan(DiagnosticLevel level, TextSpan span, CodeFix[] fixes, params object[] values) =>
new(analyzerName: this.AnalyzerName,
span: span,
level: level,
code: this.Code,
message: this.GetMessage(values),
documentationUri: this.Uri,
codeFixes: fixes,
styling: this.DiagnosticStyling);
}
}