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)); }
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; }
public WorkspaceAnalyzerConfigOptions(AnalyzerConfigOptionsResult analyzerConfigOptions) { _analyzerOptions = analyzerConfigOptions.AnalyzerOptions; }
/// <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);
public WorkspaceAnalyzerConfigOptions(AnalyzerConfigOptionsResult analyzerConfigOptions) => _backing = analyzerConfigOptions.AnalyzerOptions;
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);