public CompilerSyntaxTreeOptionsProvider(
            SyntaxTree?[] trees,
            ImmutableArray <AnalyzerConfigOptionsResult> results,
            AnalyzerConfigOptionsResult globalResults)
        {
            var builder = ImmutableDictionary.CreateBuilder <SyntaxTree, Options>();

            for (int i = 0; i < trees.Length; i++)
            {
                if (trees[i] != null)
                {
                    builder.Add(
                        trees[i] !,
                        new Options(results.IsDefault ? null : (AnalyzerConfigOptionsResult?)results[i]));
                }
            }
            _options       = builder.ToImmutableDictionary();
            _globalOptions = globalResults;
        }
Пример #2
0
        /// <summary>
        /// Returns a <see cref="AnalyzerConfigOptionsResult"/> for a source file. This computes which <see cref="AnalyzerConfig"/> rules applies to this file, and correctly applies
        /// precedence rules if there are multiple rules for the same file.
        /// </summary>
        /// <param name="sourcePath">The path to a file such as a source file or additional file. Must be non-null.</param>
        /// <remarks>This method is safe to call from multiple threads.</remarks>
        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() : AnalyzerOptions.Empty,
                    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);