static void addOptions( AnalyzerConfig.Section section, TreeOptions.Builder treeBuilder, AnalyzerOptions.Builder analyzerBuilder, ArrayBuilder <Diagnostic> diagnosticBuilder, string analyzerConfigPath, ConcurrentDictionary <ReadOnlyMemory <char>, string> diagIdCache) { const string DiagnosticOptionPrefix = "dotnet_diagnostic."; const string DiagnosticOptionSuffix = ".severity"; foreach (var(key, value) in section.Properties) { // Keys are lowercased in editorconfig parsing int diagIdLength = -1; if (key.StartsWith(DiagnosticOptionPrefix, StringComparison.Ordinal) && key.EndsWith(DiagnosticOptionSuffix, StringComparison.Ordinal)) { diagIdLength = key.Length - (DiagnosticOptionPrefix.Length + DiagnosticOptionSuffix.Length); } if (diagIdLength >= 0) { ReadOnlyMemory <char> idSlice = key.AsMemory().Slice(DiagnosticOptionPrefix.Length, diagIdLength); // PERF: this is similar to a double-checked locking pattern, and trying to fetch the ID first // lets us avoid an allocation if the id has already been added if (!diagIdCache.TryGetValue(idSlice, out var diagId)) { // We use ReadOnlyMemory<char> to allow allocation-free lookups in the // dictionary, but the actual keys stored in the dictionary are trimmed // to avoid holding GC references to larger strings than necessary. The // GetOrAdd APIs do not allow the key to be manipulated between lookup // and insertion, so we separate the operations here in code. diagId = idSlice.ToString(); diagId = diagIdCache.GetOrAdd(diagId.AsMemory(), diagId); } if (TryParseSeverity(value, out ReportDiagnostic severity)) { treeBuilder[diagId] = severity; } else { diagnosticBuilder.Add(Diagnostic.Create( InvalidAnalyzerConfigSeverityDescriptor, Location.None, diagId, value, analyzerConfigPath)); } } else { analyzerBuilder[key] = value; } } }
static void addOptions( AnalyzerConfig.Section section, TreeOptions.Builder treeBuilder, AnalyzerOptions.Builder analyzerBuilder, ArrayBuilder <Diagnostic> diagnosticBuilder, string analyzerConfigPath) { const string DiagnosticOptionPrefix = "dotnet_diagnostic."; const string DiagnosticOptionSuffix = ".severity"; foreach (var(key, value) in section.Properties) { // Keys are lowercased in editorconfig parsing int diagIdLength = -1; if (key.StartsWith(DiagnosticOptionPrefix, StringComparison.Ordinal) && key.EndsWith(DiagnosticOptionSuffix, StringComparison.Ordinal)) { diagIdLength = key.Length - (DiagnosticOptionPrefix.Length + DiagnosticOptionSuffix.Length); } if (diagIdLength >= 0) { var diagId = key.Substring( DiagnosticOptionPrefix.Length, diagIdLength); ReportDiagnostic?severity; var comparer = StringComparer.OrdinalIgnoreCase; if (comparer.Equals(value, "default")) { severity = ReportDiagnostic.Default; } else if (comparer.Equals(value, "error")) { severity = ReportDiagnostic.Error; } else if (comparer.Equals(value, "warning")) { severity = ReportDiagnostic.Warn; } else if (comparer.Equals(value, "suggestion")) { severity = ReportDiagnostic.Info; } else if (comparer.Equals(value, "silent") || comparer.Equals(value, "refactoring")) { severity = ReportDiagnostic.Hidden; } else if (comparer.Equals(value, "none")) { severity = ReportDiagnostic.Suppress; } else { severity = null; diagnosticBuilder.Add(Diagnostic.Create( InvalidAnalyzerConfigSeverityDescriptor, Location.None, diagId, value, analyzerConfigPath)); } if (severity.HasValue) { treeBuilder[diagId] = severity.GetValueOrDefault(); } } else { analyzerBuilder[key] = value; } } }