| File: MetaAnalyzers\DiagnosticDescriptorCreationAnalyzer_ResourceStringsFormat.cs | Web Access |
| Project: src\roslyn\src\RoslynAnalyzers\Microsoft.CodeAnalysis.Analyzers\Core\Microsoft.CodeAnalysis.Analyzers.csproj (Microsoft.CodeAnalysis.Analyzers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Xml.Linq; using Analyzer.Utilities.Extensions; using Analyzer.Utilities.PooledObjects; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers { using PooledResourcesDataValueConcurrentDictionary = PooledConcurrentDictionary<string, ImmutableDictionary<string, (string value, Location location)>>; public sealed partial class DiagnosticDescriptorCreationAnalyzer { private static bool HasResxAdditionalFiles(AnalyzerOptions options) { foreach (var file in options.AdditionalFiles) { if (string.Equals(".resx", Path.GetExtension(file.Path), StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static ImmutableDictionary<string, (string value, Location location)> GetOrCreateResourceMap( AnalyzerOptions options, string resourceFileName, PooledResourcesDataValueConcurrentDictionary resourceMap, CancellationToken cancellationToken) { Debug.Assert(HasResxAdditionalFiles(options)); Debug.Assert(!resourceFileName.EndsWith(".resx", StringComparison.OrdinalIgnoreCase)); resourceFileName += ".resx"; if (resourceMap.TryGetValue(resourceFileName, out var map)) { return map; } map = CreateResourceMap(options, resourceFileName, cancellationToken); return resourceMap.GetOrAdd(resourceFileName, map); } private static ImmutableDictionary<string, (string value, Location location)> CreateResourceMap(AnalyzerOptions options, string resourceFileName, CancellationToken cancellationToken) { foreach (var file in options.AdditionalFiles) { var fileName = Path.GetFileName(file.Path); if (string.Equals(resourceFileName, fileName, StringComparison.OrdinalIgnoreCase)) { return CreateResourceMapForFile(file, cancellationToken); } } return ImmutableDictionary<string, (string value, Location location)>.Empty; static ImmutableDictionary<string, (string value, Location location)> CreateResourceMapForFile(AdditionalText file, CancellationToken cancellationToken) { const string valueTagPrefix = @"<value>"; const string valueTagSuffix = @"</value>"; var builder = ImmutableDictionary.CreateBuilder<string, (string value, Location location)>(); var sourceText = file.GetTextOrEmpty(cancellationToken); var sourceTextStr = sourceText.ToString(); var parsedDocument = XDocument.Parse(sourceTextStr, LoadOptions.PreserveWhitespace); foreach (var dataElement in parsedDocument.Descendants("data")) { if (!(dataElement.Attribute("name")?.Value is { } name) || !(dataElement.Elements("value").FirstOrDefault() is { } valueElement)) { continue; } var dataElementStr = dataElement.ToString(); var valueElementStr = valueElement.ToString(); var indexOfDataElement = sourceTextStr.IndexOf(dataElementStr, StringComparison.Ordinal); if (indexOfDataElement < 0 || !valueElementStr.StartsWith(valueTagPrefix, StringComparison.OrdinalIgnoreCase) || !valueElementStr.EndsWith(valueTagSuffix, StringComparison.OrdinalIgnoreCase)) { continue; } var indexOfValue = indexOfDataElement + dataElementStr.IndexOf(valueElementStr, StringComparison.Ordinal) + valueTagPrefix.Length; var valueLength = valueElementStr.Length - (valueTagPrefix.Length + valueTagSuffix.Length); var span = new TextSpan(indexOfValue, valueLength); var linePositionSpan = sourceText.Lines.GetLinePositionSpan(span); var location = Location.Create(file.Path, span, linePositionSpan); var value = valueElementStr.Substring(valueTagPrefix.Length, valueLength); builder[name] = (value, location); } return builder.ToImmutable(); } } } }