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() });
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); }