internal static string AddDocComments(string code)
        {
            if (!CodeGenHelpers.HasPublicType(code, out var typeInfo))
            {
                return(code);
            }

            var acquireMethods = Regex.Matches(
                code,
                $@"(?<docComment>([ \t]+///.*?\n)*)(?<indent>        |\t\t){(typeInfo.isInterface ? "" : "public ")}(?<returnType>\S+) (?<name>([a-zA-Z])+)\("
                + @"((?<paramType>\S+) (?<paramName>([a-zA-Z])+)( = \S+)(, )?)+\)"
                );

            var updatedCode = code;

            foreach (var acquireMethod in acquireMethods.Cast <Match>())
            {
                var name = acquireMethod.Groups["name"].Value;
                if (!name.Contains("Acquire"))
                {
                    continue;
                }

                var isTry    = name.StartsWith("Try");
                var isAsync  = name.EndsWith("Async");
                var lockType = name.Contains("Upgradeable") ? LockType.Upgrade
                    : name.Contains("Write") ? LockType.Write
                    : name.Contains("Read") ? LockType.Read
                    : typeInfo.typeName.Contains("Semaphore") ? LockType.Semaphore
                    : LockType.Mutex;

                var @object      = lockType == LockType.Semaphore ? "semaphore" : "lock";
                var handleObject = lockType == LockType.Semaphore ? "ticket" : "lock";

                var lineStart  = acquireMethod.Groups["indent"].Value + "/// ";
                var docComment = new StringBuilder();

                docComment.AppendLine(lineStart + "<summary>");

                docComment.Append(lineStart);
                if (isTry)
                {
                    docComment.Append("Attempts to acquire ");
                }
                else
                {
                    docComment.Append("Acquires ");
                }
                docComment.Append(lockType switch
                {
                    LockType.Read => "a READ lock ",
                    LockType.Upgrade => "an UPGRADE lock ",
                    LockType.Write => "a WRITE lock ",
                    LockType.Mutex => "the lock ",
                    LockType.Semaphore => "a semaphore ticket ",
                    _ => throw new NotSupportedException()
                });
Exemple #2
0
        public void GenerateForIDistributedLockAndSemaphore([Values("Lock", "Semaphore")] string name)
        {
            var files = CodeGenHelpers.EnumerateSolutionFiles()
                        .Where(f => !f.Contains($"Distributed{name}.Core", StringComparison.OrdinalIgnoreCase))
                        .Where(f => f.EndsWith($"Distributed{name}.cs", StringComparison.OrdinalIgnoreCase) && Path.GetFileName(f)[0] != 'I');

            var errors = new List <string>();

            foreach (var file in files)
            {
                var lockCode = File.ReadAllText(file);
                if (lockCode.Contains("AUTO-GENERATED") ||
                    !CodeGenHelpers.HasPublicType(lockCode, out _))
                {
                    continue;
                }

                if (!lockCode.Contains($": IInternalDistributed{name}<"))
                {
                    errors.Add($"{file} does not implement the expected interface");
                    continue;
                }

                var lockType         = Path.GetFileNameWithoutExtension(file);
                var handleType       = lockType + "Handle";
                var supportsSyncApis = CodeGenHelpers.SupportsSyncApis(file);

                var explicitImplementations = new StringBuilder();
                var @interface = $"IDistributed{name}";
                foreach (var method in new[] { "TryAcquire", "Acquire", "TryAcquireAsync", "AcquireAsync" })
                {
                    AppendExplicitInterfaceMethod(
                        explicitImplementations,
                        @interface,
                        method,
                        "IDistributedSynchronizationHandle",
                        @as: supportsSyncApis || method.EndsWith("Async") ? null : $"IInternalDistributed{name}<{handleType}>"
                        );
                }

                var @namespace = Regex.Match(lockCode, @"\nnamespace (?<namespace>\S+)").Groups["namespace"].Value;
                var code       =
                    $@"using System;
using System.Threading;
using System.Threading.Tasks;
using Medallion.Threading.Internal;

namespace {@namespace}
{{
    public partial class {lockType}
    {{
        // AUTO-GENERATED

{explicitImplementations}
        {IfSyncApis("public ")}{handleType}? {(supportsSyncApis ? "" : $"IInternalDistributed{name}<{handleType}>.")}TryAcquire(TimeSpan timeout{IfSyncApis(" = default")}, CancellationToken cancellationToken{IfSyncApis(" = default")}) =>
        public void GenerateDocComments()
        {
            var changes = CodeGenHelpers.EnumerateSolutionFiles()
                          .Select(f => (file: f, code: File.ReadAllText(f)))
                          .Select(t => (t.file, t.code, updatedCode: AddDocComments(t.code)))
                          .Where(t => CodeGenHelpers.NormalizeCodeWhitespace(t.updatedCode) != CodeGenHelpers.NormalizeCodeWhitespace(t.code))
                          .ToList();

            changes.ForEach(t => File.WriteAllText(t.file, t.updatedCode));
            Assert.IsEmpty(changes.Select(t => t.file));
        }
        public void GenerateForIDistributedReaderWriterLock()
        {
            var files = CodeGenHelpers.EnumerateSolutionFiles()
                        .Where(f => f.IndexOf("DistributedLock.Core", StringComparison.OrdinalIgnoreCase) < 0)
                        .Where(f => Regex.IsMatch(Path.GetFileName(f), @"Distributed.*?ReaderWriterLock\.cs$", RegexOptions.IgnoreCase));

            var errors = new List <string>();

            foreach (var file in files)
            {
                var lockCode = File.ReadAllText(file);
                if (lockCode.Contains("AUTO-GENERATED") ||
                    !CodeGenHelpers.HasPublicType(lockCode, out _))
                {
                    continue;
                }

                bool isUpgradeable;
                if (lockCode.Contains(": IInternalDistributedUpgradeableReaderWriterLock<"))
                {
                    isUpgradeable = true;
                }
                else if (lockCode.Contains(": IInternalDistributedReaderWriterLock<"))
                {
                    isUpgradeable = false;
                }
                else
                {
                    errors.Add($"{file} does not implement the expected interface");
                    continue;
                }

                var lockType = Path.GetFileNameWithoutExtension(file);

                var explicitImplementations = new StringBuilder();
                var publicMethods           = new StringBuilder();
                foreach (var methodLockType in new[] { LockType.Read, LockType.Upgrade, LockType.Write }.Where(t => isUpgradeable || t != LockType.Upgrade))
                {
                    foreach (var isAsync in new[] { false, true })
                    {
                        foreach (var isTry in new[] { true, false })
                        {
                            var upgradeableText = methodLockType == LockType.Upgrade ? "Upgradeable" : "";
                            var handleType      = lockType + upgradeableText + "Handle";

                            var methodName = $"{(isTry ? "Try" : "")}Acquire{upgradeableText}{(methodLockType == LockType.Write ? "Write" : "Read")}Lock{(isAsync ? "Async" : "")}";
                            AppendExplicitInterfaceMethod(
                                explicitImplementations,
                                $"IDistributed{upgradeableText}ReaderWriterLock",
                                methodName,
                                $"IDistributed{(methodLockType == LockType.Upgrade ? "LockUpgradeable" : "Synchronization")}Handle"
                                );

                            var simplifiedMethodName = methodLockType == LockType.Upgrade ? methodName : methodName.Replace("ReadLock", "").Replace("WriteLock", "");

                            publicMethods.AppendLine()
                            .Append(' ', 8).Append("public ")
                            .Append(isAsync ? "ValueTask<" : "").Append(handleType).Append(isTry ? "?" : "").Append(isAsync ? ">" : "").Append(' ')
                            .Append(methodName)
                            .Append("(").Append("TimeSpan").Append(isTry ? "" : "?").AppendLine($" timeout = {(isTry ? "default" : "null")}, CancellationToken cancellationToken = default) =>")
                            .Append(' ', 12)
                            .Append(
                                isTry && isAsync
                                ? $"this.As<IInternalDistributed{upgradeableText}ReaderWriterLock<{(methodLockType == LockType.Upgrade ? lockType + "Handle, " : "")}{handleType}>>()"
                                + $".Internal{simplifiedMethodName}(timeout, cancellationToken"
                                : $"DistributedLockHelpers.{simplifiedMethodName}(this, timeout, cancellationToken"
                                )
                            .Append(methodLockType == LockType.Read ? ", isWrite: false" : methodLockType == LockType.Write ? ", isWrite: true" : "")
                            .AppendLine(");");
                        }
                    }
                }

                var @namespace = Regex.Match(lockCode, @"\nnamespace (?<namespace>\S+)").Groups["namespace"].Value;
                var code       =
                    $@"using System;
using System.Threading;
using System.Threading.Tasks;
using Medallion.Threading.Internal;

namespace {@namespace}
{{
    public partial class {lockType}
    {{
        // AUTO-GENERATED

{explicitImplementations}{publicMethods}
    }}
}}";
                code = DocCommentGenerator.AddDocComments(code);

                var outputPath = Path.Combine(Path.GetDirectoryName(file) !, $"{Path.GetFileNameWithoutExtension(file)}.IDistributed{(isUpgradeable ? "Upgradeable" : "")}ReaderWriterLock.cs");
                if (!File.Exists(outputPath) || CodeGenHelpers.NormalizeCodeWhitespace(File.ReadAllText(outputPath)) != CodeGenHelpers.NormalizeCodeWhitespace(code))
                {
                    File.WriteAllText(outputPath, code);
                    errors.Add($"updated {file}");
                }
            }

            Assert.IsEmpty(errors);
        }
        public void GenerateForIDistributedLockAndSemaphore([Values("Lock", "Semaphore")] string name)
        {
            var files = CodeGenHelpers.EnumerateSolutionFiles()
                        .Where(f => f.IndexOf($"Distributed{name}.Core", StringComparison.OrdinalIgnoreCase) < 0)
                        .Where(f => f.EndsWith($"Distributed{name}.cs", StringComparison.OrdinalIgnoreCase) && Path.GetFileName(f)[0] != 'I');

            var errors = new List <string>();

            foreach (var file in files)
            {
                var lockCode = File.ReadAllText(file);
                if (lockCode.Contains("AUTO-GENERATED") ||
                    !CodeGenHelpers.HasPublicType(lockCode, out _))
                {
                    continue;
                }

                if (!lockCode.Contains($": IInternalDistributed{name}<"))
                {
                    errors.Add($"{file} does not implement the expected interface");
                    continue;
                }

                var lockType   = Path.GetFileNameWithoutExtension(file);
                var handleType = lockType + "Handle";

                var explicitImplementations = new StringBuilder();
                var @interface = $"IDistributed{name}";
                foreach (var method in new[] { "TryAcquire", "Acquire", "TryAcquireAsync", "AcquireAsync" })
                {
                    AppendExplicitInterfaceMethod(explicitImplementations, @interface, method, "IDistributedSynchronizationHandle");
                }

                var @namespace = Regex.Match(lockCode, @"\nnamespace (?<namespace>\S+)").Groups["namespace"].Value;
                var code       =
                    $@"using System;
using System.Threading;
using System.Threading.Tasks;
using Medallion.Threading.Internal;

namespace {@namespace}
{{
    public partial class {lockType}
    {{
        // AUTO-GENERATED

{explicitImplementations}
        public {handleType}? TryAcquire(TimeSpan timeout = default, CancellationToken cancellationToken = default) =>
            DistributedLockHelpers.TryAcquire(this, timeout, cancellationToken);

        public {handleType} Acquire(TimeSpan? timeout = null, CancellationToken cancellationToken = default) =>
            DistributedLockHelpers.Acquire(this, timeout, cancellationToken);

        public ValueTask<{handleType}?> TryAcquireAsync(TimeSpan timeout = default, CancellationToken cancellationToken = default) =>
            this.As<IInternalDistributed{name}<{handleType}>>().InternalTryAcquireAsync(timeout, cancellationToken);

        public ValueTask<{handleType}> AcquireAsync(TimeSpan? timeout = null, CancellationToken cancellationToken = default) =>
            DistributedLockHelpers.AcquireAsync(this, timeout, cancellationToken);
    }}
}}";
                code = DocCommentGenerator.AddDocComments(code);

                var outputPath = Path.Combine(Path.GetDirectoryName(file) !, Path.GetFileNameWithoutExtension(file) + $".IDistributed{name}.cs");
                if (!File.Exists(outputPath) || CodeGenHelpers.NormalizeCodeWhitespace(File.ReadAllText(outputPath)) != CodeGenHelpers.NormalizeCodeWhitespace(code))
                {
                    File.WriteAllText(outputPath, code);
                    errors.Add($"updated {file}");
                }
            }

            Assert.IsEmpty(errors);
        }