Example #1
0
        private static (ValueSource <TextAndVersion>, TreeAndVersion) CreateRecoverableTextAndTree(
            SyntaxNode newRoot,
            string filePath,
            VersionStamp textVersion,
            VersionStamp treeVersion,
            Encoding?encoding,
            DocumentInfo.DocumentAttributes attributes,
            ParseOptions options,
            AnalyzerConfigOptionsResult analyzerConfigOptionsResult,
            ISyntaxTreeFactoryService factory,
            PreservationMode mode)
        {
            SyntaxTree tree;
            ValueSource <TextAndVersion> lazyTextAndVersion;

            if (mode == PreservationMode.PreserveIdentity || !factory.CanCreateRecoverableTree(newRoot))
            {
                tree = factory.CreateSyntaxTree(filePath, options, encoding, newRoot, analyzerConfigOptionsResult);

                // its okay to use a strong cached AsyncLazy here because the compiler layer SyntaxTree will also keep the text alive once its built.
                lazyTextAndVersion = new TreeTextSource(
                    new AsyncLazy <SourceText>(
                        c => tree.GetTextAsync(c),
                        c => tree.GetText(c),
                        cacheResult: true),
                    textVersion,
                    filePath);
            }
            else
            {
                // There is a strange circularity here: the creation of lazyTextAndVersion reads this local, but will see it as non-null since it
                // only uses it through a lambda that won't have ran. The assignment exists to placate the definite-assignment analysis (which is
                // right to be suspicious of this).
                tree = null !;

                // Uses CachedWeakValueSource so the document and tree will return the same SourceText instance across multiple accesses as long
                // as the text is referenced elsewhere.
                lazyTextAndVersion = new TreeTextSource(
                    new WeaklyCachedValueSource <SourceText>(
                        new AsyncLazy <SourceText>(
                            // Build text from root, so recoverable tree won't cycle.
                            async cancellationToken => (await tree.GetRootAsync(cancellationToken).ConfigureAwait(false)).GetText(encoding),
                            cancellationToken => tree.GetRoot(cancellationToken).GetText(encoding),
                            cacheResult: false)),
                    textVersion,
                    filePath);

                tree = factory.CreateRecoverableTree(attributes.Id.ProjectId, filePath, options, lazyTextAndVersion, encoding, newRoot, analyzerConfigOptionsResult.TreeOptions);
            }

            return(lazyTextAndVersion, TreeAndVersion.Create(tree, treeVersion));
        }
Example #2
0
        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;
        }
Example #3
0
 public WorkspaceAnalyzerConfigOptions(AnalyzerConfigOptionsResult analyzerConfigOptions)
 {
     _analyzerOptions = analyzerConfigOptions.AnalyzerOptions;
 }
Example #4
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 System.ArgumentNullException(nameof(sourcePath));
            }

            var treeOptionsBuilder     = _treeOptionsPool.Allocate();
            var analyzerOptionsBuilder = _analyzerOptionsPool.Allocate();
            var diagnosticBuilder      = ArrayBuilder <Diagnostic> .GetInstance();

            var sectionKey = _sectionKeyPool.Allocate();

            var normalizedPath = PathUtilities.NormalizeWithForwardSlash(sourcePath);

            // 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)
                    {
                        analyzerOptionsBuilder.Clear();
                        treeOptionsBuilder.Clear();
                        diagnosticBuilder.Clear();
                    }

                    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];
                            addOptions(
                                section,
                                treeOptionsBuilder,
                                analyzerOptionsBuilder,
                                diagnosticBuilder,
                                config.PathToFile,
                                _diagnosticIdCache);
                            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))
            {
                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);
                }
            }
            else
            {
                freeKey(sectionKey, _sectionKeyPool);
            }

            treeOptionsBuilder.Clear();
            analyzerOptionsBuilder.Clear();
            _treeOptionsPool.Free(treeOptionsBuilder);
            _analyzerOptionsPool.Free(analyzerOptionsBuilder);

            return(result);
Example #5
0
 public WorkspaceAnalyzerConfigOptions(AnalyzerConfigOptionsResult analyzerConfigOptions)
 => _backing = analyzerConfigOptions.AnalyzerOptions;
Example #6
0
        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);