private ValueSource <AnalyzerConfig> CreateAnalyzerConfigValueSource() { return(new AsyncLazy <AnalyzerConfig>( asynchronousComputeFunction: async cancellationToken => AnalyzerConfig.Parse(await GetTextAsync(cancellationToken).ConfigureAwait(false), FilePath), synchronousComputeFunction: cancellationToken => AnalyzerConfig.Parse(GetTextSynchronously(cancellationToken), FilePath), cacheResult: true)); }
private AnalyzerConfigSet(ImmutableArray <AnalyzerConfig> analyzerConfigs) { _analyzerConfigs = analyzerConfigs; var allMatchers = ArrayBuilder <ImmutableArray <SectionNameMatcher?> > .GetInstance(_analyzerConfigs.Length); foreach (var config in _analyzerConfigs) { // Create an array of regexes with each entry corresponding to the same index // in <see cref="EditorConfig.NamedSections"/>. var builder = ArrayBuilder <SectionNameMatcher?> .GetInstance(config.NamedSections.Length); foreach (var section in config.NamedSections) { SectionNameMatcher?matcher = AnalyzerConfig.TryCreateSectionNameMatcher(section.Name); builder.Add(matcher); } Debug.Assert(builder.Count == config.NamedSections.Length); allMatchers.Add(builder.ToImmutableAndFree()); } Debug.Assert(allMatchers.Count == _analyzerConfigs.Length); _analyzerMatchers = allMatchers.ToImmutableAndFree(); }
public AnalyzerConfigOptionsResult GetOptionsForSourcePath(string sourcePath) { if (sourcePath == null) { throw new ArgumentNullException(nameof(sourcePath)); } var sectionKey = _sectionKeyPool.Allocate(); var normalizedPath = PathUtilities.NormalizeWithForwardSlash(sourcePath); // If we have a global config, add any sections that match the full path foreach (var section in _globalConfig.NamedSections) { if (normalizedPath.Equals(section.Name, Section.NameComparer)) { sectionKey.Add(section); } } int globalConfigOptionsCount = sectionKey.Count; // The editorconfig paths are sorted from shortest to longest, so matches // are resolved from most nested to least nested, where last setting wins for (int analyzerConfigIndex = 0; analyzerConfigIndex < _analyzerConfigs.Length; analyzerConfigIndex++) { var config = _analyzerConfigs[analyzerConfigIndex]; if (normalizedPath.StartsWith(config.NormalizedDirectory, StringComparison.Ordinal)) { // If this config is a root config, then clear earlier options since they don't apply // to this source file. if (config.IsRoot) { sectionKey.RemoveRange(globalConfigOptionsCount, sectionKey.Count - globalConfigOptionsCount); } int dirLength = config.NormalizedDirectory.Length; // Leave '/' if the normalized directory ends with a '/'. This can happen if // we're in a root directory (e.g. '/' or 'Z:/'). The section matching // always expects that the relative path start with a '/'. if (config.NormalizedDirectory[dirLength - 1] == '/') { dirLength--; } string relativePath = normalizedPath.Substring(dirLength); ImmutableArray <SectionNameMatcher?> matchers = _analyzerMatchers[analyzerConfigIndex]; for (int sectionIndex = 0; sectionIndex < matchers.Length; sectionIndex++) { if (matchers[sectionIndex]?.IsMatch(relativePath) == true) { var section = config.NamedSections[sectionIndex]; sectionKey.Add(section); } } } } // Try to avoid creating extra dictionaries if we've already seen an options result with the // exact same options if (!_optionsCache.TryGetValue(sectionKey, out var result)) { var treeOptionsBuilder = _treeOptionsPool.Allocate(); var analyzerOptionsBuilder = _analyzerOptionsPool.Allocate(); var diagnosticBuilder = ArrayBuilder <Diagnostic> .GetInstance(); int sectionKeyIndex = 0; analyzerOptionsBuilder.AddRange(GlobalConfigOptions.AnalyzerOptions); foreach (var configSection in _globalConfig.NamedSections) { if (sectionKey.Count > 0 && configSection == sectionKey[sectionKeyIndex]) { ParseSectionOptions( sectionKey[sectionKeyIndex], treeOptionsBuilder, analyzerOptionsBuilder, diagnosticBuilder, GlobalAnalyzerConfigBuilder.GlobalConfigPath, _diagnosticIdCache); sectionKeyIndex++; if (sectionKeyIndex == sectionKey.Count) { break; } } } for (int analyzerConfigIndex = 0; analyzerConfigIndex < _analyzerConfigs.Length && sectionKeyIndex < sectionKey.Count; analyzerConfigIndex++) { AnalyzerConfig config = _analyzerConfigs[analyzerConfigIndex]; ImmutableArray <SectionNameMatcher?> matchers = _analyzerMatchers[analyzerConfigIndex]; for (int matcherIndex = 0; matcherIndex < matchers.Length; matcherIndex++) { if (sectionKey[sectionKeyIndex] == config.NamedSections[matcherIndex]) { ParseSectionOptions( sectionKey[sectionKeyIndex], treeOptionsBuilder, analyzerOptionsBuilder, diagnosticBuilder, config.PathToFile, _diagnosticIdCache); sectionKeyIndex++; if (sectionKeyIndex == sectionKey.Count) { // Exit the inner 'for' loop now that work is done. The outer loop is handled by a // top-level condition. break; } } } } result = new AnalyzerConfigOptionsResult( treeOptionsBuilder.Count > 0 ? treeOptionsBuilder.ToImmutable() : SyntaxTree.EmptyDiagnosticOptions, analyzerOptionsBuilder.Count > 0 ? analyzerOptionsBuilder.ToImmutable() : AnalyzerConfigOptions.EmptyDictionary, diagnosticBuilder.ToImmutableAndFree()); if (_optionsCache.TryAdd(sectionKey, result)) { // Release the pooled object to be used as a key _sectionKeyPool.ForgetTrackedObject(sectionKey); } else { freeKey(sectionKey, _sectionKeyPool); } treeOptionsBuilder.Clear(); analyzerOptionsBuilder.Clear(); _treeOptionsPool.Free(treeOptionsBuilder); _analyzerOptionsPool.Free(analyzerOptionsBuilder); } else { freeKey(sectionKey, _sectionKeyPool); } return(result);