File: SourceGeneration\AdditionalSourcesCollection.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
    internal sealed class AdditionalSourcesCollection
    {
        private readonly ArrayBuilder<GeneratedSourceText> _sourcesAdded;
        private readonly string _fileExtension;
 
        private const StringComparison _hintNameComparison = StringComparison.OrdinalIgnoreCase;
 
        private static readonly StringComparer s_hintNameComparer = StringComparer.OrdinalIgnoreCase;
 
        // Matches "/" at the beginning, relative path segments ("../", "./", "//"),
        // and " /" (directories ending with space cause problems).
        private static readonly Regex s_invalidSegmentPattern = new Regex(@"(\.{1,2}|/|^| )/", RegexOptions.Compiled);
 
        internal AdditionalSourcesCollection(string fileExtension)
        {
            Debug.Assert(fileExtension.Length > 0 && fileExtension[0] == '.');
            _sourcesAdded = ArrayBuilder<GeneratedSourceText>.GetInstance();
            _fileExtension = fileExtension;
        }
 
        public void Add(string hintName, SourceText source)
        {
            if (string.IsNullOrWhiteSpace(hintName))
            {
                throw new ArgumentNullException(nameof(hintName));
            }
 
            // allow any identifier character or [.,-+`_ ()[]{}/\\]
            for (int i = 0; i < hintName.Length; i++)
            {
                char c = hintName[i];
                if (!UnicodeCharacterUtilities.IsIdentifierPartCharacter(c)
                    && c != '.'
                    && c != ','
                    && c != '-'
                    && c != '+'
                    && c != '`'
                    && c != '_'
                    && c != ' '
                    && c != '('
                    && c != ')'
                    && c != '['
                    && c != ']'
                    && c != '{'
                    && c != '}'
                    && c != '/'
                    && c != '\\')
                {
                    throw new ArgumentException(string.Format(CodeAnalysisResources.HintNameInvalidChar, hintName, c, i), nameof(hintName));
                }
            }
 
            hintName = hintName.Replace('\\', '/');
 
            if (s_invalidSegmentPattern.Match(hintName) is { Success: true } match)
            {
                throw new ArgumentException(string.Format(CodeAnalysisResources.HintNameInvalidSegment, hintName, match.Value, match.Index), nameof(hintName));
            }
 
            hintName = AppendExtensionIfRequired(hintName);
            if (this.Contains(hintName))
            {
                throw new ArgumentException(string.Format(CodeAnalysisResources.HintNameUniquePerGenerator, hintName), nameof(hintName));
            }
 
            if (source.Encoding is null)
            {
                throw new ArgumentException(string.Format(CodeAnalysisResources.SourceTextRequiresEncoding, hintName), nameof(source));
            }
 
            _sourcesAdded.Add(new GeneratedSourceText(hintName, source));
        }
 
        public void RemoveSource(string hintName)
        {
            hintName = AppendExtensionIfRequired(hintName);
            for (int i = 0; i < _sourcesAdded.Count; i++)
            {
                if (s_hintNameComparer.Equals(_sourcesAdded[i].HintName, hintName))
                {
                    _sourcesAdded.RemoveAt(i);
                    return;
                }
            }
        }
 
        public bool Contains(string hintName)
        {
            hintName = AppendExtensionIfRequired(hintName);
            for (int i = 0; i < _sourcesAdded.Count; i++)
            {
                if (s_hintNameComparer.Equals(_sourcesAdded[i].HintName, hintName))
                {
                    return true;
                }
            }
            return false;
        }
 
        public void CopyTo(AdditionalSourcesCollection asc)
        {
            // we know the individual hint names are valid, but we do need to check that they
            // don't collide with any we already have
            if (asc._sourcesAdded.Count == 0)
            {
                asc._sourcesAdded.AddRange(this._sourcesAdded);
            }
            else
            {
                foreach (var source in this._sourcesAdded)
                {
                    if (asc.Contains(source.HintName))
                    {
                        throw new ArgumentException(string.Format(CodeAnalysisResources.HintNameUniquePerGenerator, source.HintName), "hintName");
                    }
                    asc._sourcesAdded.Add(source);
                }
            }
        }
 
        internal ImmutableArray<GeneratedSourceText> ToImmutableAndFree() => _sourcesAdded.ToImmutableAndFree();
 
        internal ImmutableArray<GeneratedSourceText> ToImmutable() => _sourcesAdded.ToImmutable();
 
        internal void Free() => _sourcesAdded.Free();
 
        private string AppendExtensionIfRequired(string hintName)
        {
            if (!hintName.EndsWith(_fileExtension, _hintNameComparison))
            {
                hintName = string.Concat(hintName, _fileExtension);
            }
 
            return hintName;
        }
    }
}