From 7041cc7c1e70505c647b184412362a16f0a8d286 Mon Sep 17 00:00:00 2001 From: tombogle Date: Sat, 24 May 2025 17:25:00 -0400 Subject: [PATCH 1/4] Added support for date insertion in localized files --- CHANGELOG.md | 7 + SIL.BuildTasks.Tests/FileUpdateTests.cs | 199 ++++++++++++++++++++++++ SIL.BuildTasks/FileUpdate.cs | 105 +++++++++++-- 3 files changed, 302 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc313936..d564379f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- [SIL.BuildTasks] Added FileUpdate.FileLocalePattern (optional param) to infer a locale (e.g., for a localized release notes file) to use when doing date insertion involving month names or abbreviations. + +### Changed + +- [SIL.BuildTasks] Changed FileUpdate.DatePlaceholder to allow the caller to specify a special placeholder `_DATE(*)_` that will look not only for `_DATE_` but also variants that include a date format specifier, such as `_DATE(MMM d, yyyy)_` or `_DATE(MM/yyyy)_` and will use the date format specified instead of the DateFormat. + ## [3.1.1] - 2025-03-18 ### Changed diff --git a/SIL.BuildTasks.Tests/FileUpdateTests.cs b/SIL.BuildTasks.Tests/FileUpdateTests.cs index db9dfdb6..052ddad1 100644 --- a/SIL.BuildTasks.Tests/FileUpdateTests.cs +++ b/SIL.BuildTasks.Tests/FileUpdateTests.cs @@ -2,6 +2,7 @@ // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; +using System.Globalization; using NUnit.Framework; // Sadly, Resharper wants to change Is.EqualTo to NUnit.Framework.Is.EqualTo // ReSharper disable AccessToStaticMemberViaDerivedType @@ -62,6 +63,120 @@ public void GetModifiedContents_RegexTextNotMatched_Throws(string origContents, Assert.That(ex.Message, Is.EqualTo($"No replacements made. Regex: '{regex}'; ReplacementText: '{replacement}'")); } + [TestCase("_DATE_ _VERSION_\r\nStuff", "_DATE_", "M/yyyy", "{0} 3.2.1\r\nStuff")] + [TestCase("_DATE_ _VERSION_\r\nStuff done before _DATE_", "_DATE_", "M/yyyy", "{0} 3.2.1\r\nStuff done before {0}")] + [TestCase("&DATE; _VERSION_\r\n- point #1", "&DATE;", "dd-MM-yy", "{0} 3.2.1\r\n- point #1")] + [TestCase("DATE _VERSION_", "DATE", "dd MMMM, yyyy", "{0} 3.2.1")] + [TestCase("DATE _VERSION_", "DATE", null, "{0} 3.2.1")] + public void GetModifiedContents_DateLiteral_InsertsDateWithSpecifiedDateFormat( + string origContents, string datePlaceholder, string dateFormat, + string expectedResultFormat) + { + var updater = new FileUpdate + { + Regex = "_VERSION_", + ReplacementText = "3.2.1", + DatePlaceholder = datePlaceholder, + DateFormat = dateFormat + }; + + var currentDate = DateTime.UtcNow.Date.ToString(dateFormat ?? updater.DateFormat); + + var result = updater.GetModifiedContents(origContents); + var expectedResult = string.Format(expectedResultFormat, currentDate); + Assert.That(result, Is.EqualTo(expectedResult)); + } + + [TestCase("_DATE_ _VERSION_\r\nStuff", "M/yyyy", "{0} 3.2.1\r\nStuff")] + [TestCase("_DATE_ _VERSION_\r\nStuff done before _DATE_", "dd-MM-yy", "{0} 3.2.1\r\nStuff done before {0}")] + public void GetModifiedContents_SpecialDatePlaceholderButFileDoesNotSpecifyFormat_InsertsDateWithSpecifiedDateFormat( + string origContents, string dateFormat, string expectedResultFormat) + { + var updater = new FileUpdate + { + Regex = "_VERSION_", + ReplacementText = "3.2.1", + DatePlaceholder = "_DATE(*)_", + DateFormat = dateFormat + }; + + var currentDate = DateTime.UtcNow.Date.ToString(dateFormat ?? updater.DateFormat); + + var result = updater.GetModifiedContents(origContents); + var expectedResult = string.Format(expectedResultFormat, currentDate); + Assert.That(result, Is.EqualTo(expectedResult)); + } + + [TestCase("MM-yy")] + [TestCase("dd MMMM")] + public void GetModifiedContents_SpecialDatePlaceholderWithFileSpecifyingFormat_InsertsDateWithFormatFromFile( + string format) + { + var origContents = $"_DATE({format})_\r\nStuff"; + + var updater = new FileUpdate + { + Regex = "(.*)", + ReplacementText = "$1", + DatePlaceholder = "_DATE(*)_", + }; + + var currentDate = DateTime.UtcNow.Date.ToString(format); + + var result = updater.GetModifiedContents(origContents); + Assert.That(result, Is.EqualTo($"{currentDate}\r\nStuff")); + } + + [TestCase("MM-yyyy", "d MMMM yy")] + [TestCase("dd MMMM", "MM/dd/yyyy")] + public void GetModifiedContents_SpecialDatePlaceholderWithFileSpecifyingMultipleFormats_InsertsDateWithFormatsFromFile( + string format1, string format2) + { + var origContents = $"First _DATE({format1})_\r\nSecond _DATE_\r\nLast _DATE({format2})_"; + + var updater = new FileUpdate + { + Regex = "(.*)", + ReplacementText = "$1", + DatePlaceholder = "_DATE(*)_", + }; + + var currentDate1 = DateTime.UtcNow.Date.ToString(format1); + var currentDateInDefaultFmt = DateTime.UtcNow.Date.ToString(updater.DateFormat); + var currentDate2 = DateTime.UtcNow.Date.ToString(format2); + + var result = updater.GetModifiedContents(origContents); + Assert.That(result, Is.EqualTo($"First {currentDate1}\r\nSecond {currentDateInDefaultFmt}\r\nLast {currentDate2}")); + } + + [TestCase("es")] + [TestCase("fr")] + public void GetModifiedContents_SpecialDatePlaceholderWithLocalizedFileSpecifyingFormat_InsertsLocaleSpecificDateWithFormatFromFile(string locale) + { + var origContents = "_DATE(d MMMM yyyy)_\r\nStuff"; + + var updater = new FileUpdate + { + File = $"ReleaseNotes.{locale}.md", + FileLocalePattern = @"\.(?[a-z]{2}(-\w+)?)\.md$", + Regex = "(.*)", + ReplacementText = "$1", + DatePlaceholder = "_DATE(*)_", + }; + + var currentDate = string.Format(DateTime.UtcNow.Date.ToString("d {0} yyyy"), + GetMonthName(locale, DateTime.UtcNow.Month)); + + var result = updater.GetModifiedContents(origContents); + Assert.That(result, Is.EqualTo($"{currentDate}\r\nStuff")); + } + + private string GetMonthName(string locale, int month) + { + var culture = new CultureInfo(locale); + return culture.DateTimeFormat.GetMonthName(month); + } + [Test] public void GetModifiedContents_InvalidRegex_Throws() { @@ -74,5 +189,89 @@ public void GetModifiedContents_InvalidRegex_Throws() var ex = Assert.Throws(() => updater.GetModifiedContents("Whatever")); Assert.That(ex.Message, Is.EqualTo($"Invalid regular expression: parsing \"{updater.Regex}\" - Not enough )'s.")); } + + [Test] + public void FileLocalePattern_InvalidRegex_ThrowsArgumentException() + { + const string expr = @"ReleaseNotes\.(.*\.md"; + Assert.That(() => + { + _ = new FileUpdate + { + FileLocalePattern = expr, + ReplacementText = "oops" + }; + }, Throws.ArgumentException.With.Message.EqualTo($"FileLocalePattern: Invalid regular expression: parsing \"{expr}\" - Not enough )'s.")); + } + + [TestCase("es")] + [TestCase("fr")] + [TestCase("zh-CN")] + public void GetCultureFromFileName_MatchLocaleGroupToKnownCulture_GetsSpecifiedCulture(string localeSpecifier) + { + var fileUpdater = new FileUpdate + { + File = $"ReleaseNotes.{localeSpecifier}.md", + FileLocalePattern = @"\.(?[a-z]{2}(-\w+)?)\.md$", + }; + + Assert.That(fileUpdater.GetCultureFromFileName().IetfLanguageTag, + Is.EqualTo(localeSpecifier)); + } + + [TestCase("zz-Unknown")] + [TestCase("qq-Weird")] + public void GetCultureFromFileName_MatchLocaleGroupToUnknownCulture_ReturnsNull(string localeSpecifier) + { + var fileUpdater = new FileUpdate + { + File = $"ReleaseNotes.{localeSpecifier}.md", + FileLocalePattern = @"\.(?[a-z]{2}(-\w+)?)\.md$", + }; + + Assert.That(fileUpdater.GetCultureFromFileName(), Is.Null); + } + + [TestCase("es")] + [TestCase("fr-FR")] + [TestCase("de")] + public void GetCultureFromFileName_EntireMatchIsKnownCulture_GetsSpecifiedCulture(string localeSpecifier) + { + var fileUpdater = new FileUpdate + { + File = $"ReleaseNotes.{localeSpecifier}.md", + FileLocalePattern = @"(?<=\.)es|fr-FR|de(?=\.)", + }; + + Assert.That(fileUpdater.GetCultureFromFileName().IetfLanguageTag, + Is.EqualTo(localeSpecifier)); + } + + [TestCase("My.bat.ate.your.homework.md", @"(?<=\.)[a-z]{4}(?=\.)")] + [TestCase("ReleaseNotes.htm", ".+")] + public void GetCultureFromFileName_EntireMatchIsUnknownCulture_ReturnsNull(string fileName, string pattern) + { + var fileUpdater = new FileUpdate + { + File = fileName, + FileLocalePattern = pattern, + }; + + Assert.That(fileUpdater.GetCultureFromFileName(), Is.Null); + } + + [TestCase("My.bat.ate.your.homework.md", @"(?<=\.)[a-z]{22}(?=\.)")] + [TestCase("ReleaseNotes.htm", @"(?<=\.)es|fr-FR|de(?=\.)")] + [TestCase("ReleaseNotes.htm", @"\.(?[a-z]{2}(-\w+)?)\.md$")] + public void GetCultureFromFileName_NoMatch_ReturnsNull(string fileName, string pattern) + { + var fileUpdater = new FileUpdate + { + File = fileName, + FileLocalePattern = pattern, + }; + + Assert.That(fileUpdater.GetCultureFromFileName(), Is.Null); + } } } diff --git a/SIL.BuildTasks/FileUpdate.cs b/SIL.BuildTasks/FileUpdate.cs index e2fd4b32..d13f9dd6 100644 --- a/SIL.BuildTasks/FileUpdate.cs +++ b/SIL.BuildTasks/FileUpdate.cs @@ -1,16 +1,21 @@ -// Copyright (c) 2023 SIL Global +// Copyright (c) 2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; using System.Diagnostics; +using System.Globalization; +using System.IO; using System.Text.RegularExpressions; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; +using static System.IO.File; +using static System.Text.RegularExpressions.RegexOptions; namespace SIL.BuildTasks { public class FileUpdate : Task { private string _dateFormat; + private Regex _localeRegex; [Required] public string File { get; set; } @@ -22,12 +27,17 @@ public class FileUpdate : Task public string ReplacementText { get; set; } /// - /// The string pattern to replace with the current date (UTC, dd/MMM/yyyy) + /// The string pattern to replace with the current date. If this is specified as + /// `_DATE(*)_`, then this will be treated as a regex that will match `_DATE_` as well as + /// any string that matches _DATE?<?dateFormat?>([dMy/:.,\-\s']+)_, in which case + /// the `dateFormat` match group will be used to format the date instead of + /// . /// public string DatePlaceholder { get; set; } /// - /// The date format to output (default is dd/MMM/yyyy) + /// Default date format, used unless the finds a match that + /// specifies an alternate format. /// public string DateFormat { @@ -35,19 +45,46 @@ public string DateFormat set => _dateFormat = value; } + /// + /// Optional regex pattern with a named group 'locale' to extract the locale from the + /// filename. + /// Example: @"\.(?<locale>[a-z]{2}(-\w+)?)\.md$" to match "es", "fr", "zh-CN", etc., + /// between dots and preceding the final md (markdown) extension. + /// If there is no named group 'locale', then the entire match will be treated + /// as the locale. + /// Example: @"(?<=\.).es|fr|de(?=\.)" + /// + /// The given pattern is not a well-formed regular expression + /// If the pattern matches more than once, the first match will be used. + public string FileLocalePattern + { + get => _localeRegex?.ToString(); + set + { + try + { + _localeRegex = string.IsNullOrEmpty(value) ? null : new Regex(value); + } + catch (ArgumentException e) + { + throw new ArgumentException("FileLocalePattern: Invalid regular expression: " + e.Message, e); + } + } + } + public override bool Execute() { try { - var content = System.IO.File.ReadAllText(File); + var content = ReadAllText(File); var newContents = GetModifiedContents(content); - System.IO.File.WriteAllText(File, newContents); + WriteAllText(File, newContents); return true; } catch (Exception e) { Console.WriteLine(e); - Debug.WriteLine(e.Message); + Debug.WriteLine(e); SafeLogError(e.Message); return false; } @@ -78,7 +115,27 @@ internal string GetModifiedContents(string content) var newContents = regex.Replace(content, ReplacementText); if (!string.IsNullOrEmpty(DatePlaceholder)) - newContents = newContents.Replace(DatePlaceholder, DateTime.UtcNow.Date.ToString(DateFormat)); + { + var culture = GetCultureFromFileName() ?? CultureInfo.CurrentCulture; + + if (DatePlaceholder.Equals("_DATE(*)_", StringComparison.Ordinal)) + { + var dateRegex = new Regex( + @"_DATE(\((?[dMy\/:.,\-\s'M]+)\))?_", Compiled); + newContents = dateRegex.Replace(newContents, m => + { + var format = m.Groups["dateFormat"].Success + ? m.Groups["dateFormat"].Value + : DateFormat; + return DateTime.UtcNow.Date.ToString(format, culture); + }); + } + else + { + var formattedDate = DateTime.UtcNow.Date.ToString(DateFormat, culture); + newContents = newContents.Replace(DatePlaceholder, formattedDate); + } + } return newContents; } @@ -88,6 +145,36 @@ internal string GetModifiedContents(string content) } } + internal CultureInfo GetCultureFromFileName() + { + if (_localeRegex == null) + return null; + + var fileName = Path.GetFileName(File); + + try + { + var match = _localeRegex.Match(fileName); + if (match.Success) + { + var locale = match.Groups["locale"].Success + ? match.Groups["locale"].Value + : match.Value; + return new CultureInfo(locale); + } + } + catch (CultureNotFoundException) + { + } + catch (Exception ex) + { + SafeLogError( + $"Failed to extract locale from filename using pattern '{FileLocalePattern}': {ex.Message}"); + } + + return null; + } + private void SafeLogError(string msg) { try @@ -96,8 +183,8 @@ private void SafeLogError(string msg) } catch (Exception) { - //swallow... logging fails in the unit test environment, where the log isn't really set up + //swallow... logging fails in the unit test environment, where the log isn't set up } } } -} \ No newline at end of file +} From 3608e3d516b23ace166f5622e406dc4ad3c0d39f Mon Sep 17 00:00:00 2001 From: tombogle Date: Tue, 27 May 2025 12:13:46 -0400 Subject: [PATCH 2/4] Used DateTimeProvider and (in tests) ReproducibleDateTimeProvider to simplify unit tests Note that I intentionally used a very old version of the new dependencies because it was all I needed and I figured it might help avoid conflicts with consumers of this package that wanted to stay back. --- SIL.BuildTasks.Tests/FileUpdateTests.cs | 71 ++++++++++--------- .../SIL.BuildTasks.Tests.csproj | 1 + SIL.BuildTasks/FileUpdate.cs | 6 +- SIL.BuildTasks/SIL.BuildTasks.csproj | 1 + 4 files changed, 42 insertions(+), 37 deletions(-) diff --git a/SIL.BuildTasks.Tests/FileUpdateTests.cs b/SIL.BuildTasks.Tests/FileUpdateTests.cs index 052ddad1..fabdd175 100644 --- a/SIL.BuildTasks.Tests/FileUpdateTests.cs +++ b/SIL.BuildTasks.Tests/FileUpdateTests.cs @@ -1,9 +1,11 @@ -// Copyright (c) 2024 SIL Global +// Copyright (c) 2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; -using System.Globalization; using NUnit.Framework; +using SIL.Providers; +using SIL.TestUtilities.Providers; + // Sadly, Resharper wants to change Is.EqualTo to NUnit.Framework.Is.EqualTo // ReSharper disable AccessToStaticMemberViaDerivedType @@ -12,6 +14,19 @@ namespace SIL.BuildTasks.Tests [TestFixture] public class FileUpdateTests { + [SetUp] + public void Setup() + { + DateTimeProvider.SetProvider( + new ReproducibleDateTimeProvider(new DateTime(2026, 4, 16))); + } + + [TearDown] + public void TearDown() + { + DateTimeProvider.ResetToDefault(); + } + [TestCase("This is the story of the frog prince.", "frog", "monkey", ExpectedResult = "This is the story of the monkey prince.")] [TestCase("This is the story of the frog prince.", "f[^ ]+g", "toad", @@ -63,14 +78,14 @@ public void GetModifiedContents_RegexTextNotMatched_Throws(string origContents, Assert.That(ex.Message, Is.EqualTo($"No replacements made. Regex: '{regex}'; ReplacementText: '{replacement}'")); } - [TestCase("_DATE_ _VERSION_\r\nStuff", "_DATE_", "M/yyyy", "{0} 3.2.1\r\nStuff")] - [TestCase("_DATE_ _VERSION_\r\nStuff done before _DATE_", "_DATE_", "M/yyyy", "{0} 3.2.1\r\nStuff done before {0}")] - [TestCase("&DATE; _VERSION_\r\n- point #1", "&DATE;", "dd-MM-yy", "{0} 3.2.1\r\n- point #1")] - [TestCase("DATE _VERSION_", "DATE", "dd MMMM, yyyy", "{0} 3.2.1")] - [TestCase("DATE _VERSION_", "DATE", null, "{0} 3.2.1")] + [TestCase("_DATE_ _VERSION_\r\nStuff", "_DATE_", "M/yyyy", "4/2026 3.2.1\r\nStuff")] + [TestCase("_DATE_ _VERSION_\r\nStuff done before _DATE_", "_DATE_", "M/yyyy", "4/2026 3.2.1\r\nStuff done before 4/2026")] + [TestCase("&DATE; _VERSION_\r\n- point #1", "&DATE;", "dd-MM-yy", "16-04-26 3.2.1\r\n- point #1")] + [TestCase("DATE _VERSION_", "DATE", "dd MMMM, yyyy", "16 April, 2026 3.2.1")] + [TestCase("DATE _VERSION_", "DATE", null, "16/Apr/2026 3.2.1")] // Uses default date format public void GetModifiedContents_DateLiteral_InsertsDateWithSpecifiedDateFormat( string origContents, string datePlaceholder, string dateFormat, - string expectedResultFormat) + string expectedResult) { var updater = new FileUpdate { @@ -80,17 +95,14 @@ public void GetModifiedContents_DateLiteral_InsertsDateWithSpecifiedDateFormat( DateFormat = dateFormat }; - var currentDate = DateTime.UtcNow.Date.ToString(dateFormat ?? updater.DateFormat); - var result = updater.GetModifiedContents(origContents); - var expectedResult = string.Format(expectedResultFormat, currentDate); Assert.That(result, Is.EqualTo(expectedResult)); } - [TestCase("_DATE_ _VERSION_\r\nStuff", "M/yyyy", "{0} 3.2.1\r\nStuff")] - [TestCase("_DATE_ _VERSION_\r\nStuff done before _DATE_", "dd-MM-yy", "{0} 3.2.1\r\nStuff done before {0}")] + [TestCase("_DATE_ _VERSION_\r\nStuff", "M/yyyy", "4/2026 3.2.1\r\nStuff")] + [TestCase("_DATE_ _VERSION_\r\nStuff done before _DATE_", "dd-MM-yy", "16-04-26 3.2.1\r\nStuff done before 16-04-26")] public void GetModifiedContents_SpecialDatePlaceholderButFileDoesNotSpecifyFormat_InsertsDateWithSpecifiedDateFormat( - string origContents, string dateFormat, string expectedResultFormat) + string origContents, string dateFormat, string expectedResult) { var updater = new FileUpdate { @@ -100,10 +112,7 @@ public void GetModifiedContents_SpecialDatePlaceholderButFileDoesNotSpecifyForma DateFormat = dateFormat }; - var currentDate = DateTime.UtcNow.Date.ToString(dateFormat ?? updater.DateFormat); - var result = updater.GetModifiedContents(origContents); - var expectedResult = string.Format(expectedResultFormat, currentDate); Assert.That(result, Is.EqualTo(expectedResult)); } @@ -121,7 +130,7 @@ public void GetModifiedContents_SpecialDatePlaceholderWithFileSpecifyingFormat_I DatePlaceholder = "_DATE(*)_", }; - var currentDate = DateTime.UtcNow.Date.ToString(format); + var currentDate = DateTimeProvider.Current.UtcNow.ToString(format); var result = updater.GetModifiedContents(origContents); Assert.That(result, Is.EqualTo($"{currentDate}\r\nStuff")); @@ -141,17 +150,18 @@ public void GetModifiedContents_SpecialDatePlaceholderWithFileSpecifyingMultiple DatePlaceholder = "_DATE(*)_", }; - var currentDate1 = DateTime.UtcNow.Date.ToString(format1); - var currentDateInDefaultFmt = DateTime.UtcNow.Date.ToString(updater.DateFormat); - var currentDate2 = DateTime.UtcNow.Date.ToString(format2); + var currentDate = DateTimeProvider.Current.UtcNow; + var currentDate1 = currentDate.ToString(format1); + var currentDate2 = currentDate.ToString(format2); var result = updater.GetModifiedContents(origContents); - Assert.That(result, Is.EqualTo($"First {currentDate1}\r\nSecond {currentDateInDefaultFmt}\r\nLast {currentDate2}")); + Assert.That(result, Is.EqualTo($"First {currentDate1}\r\nSecond 16/Apr/2026\r\nLast {currentDate2}")); } - [TestCase("es")] - [TestCase("fr")] - public void GetModifiedContents_SpecialDatePlaceholderWithLocalizedFileSpecifyingFormat_InsertsLocaleSpecificDateWithFormatFromFile(string locale) + [TestCase("es", "abril")] + [TestCase("fr", "avril")] + public void GetModifiedContents_SpecialDatePlaceholderWithLocalizedFileSpecifyingFormat_InsertsLocaleSpecificDateWithFormatFromFile( + string locale, string localizedMonthName) { var origContents = "_DATE(d MMMM yyyy)_\r\nStuff"; @@ -164,17 +174,8 @@ public void GetModifiedContents_SpecialDatePlaceholderWithLocalizedFileSpecifyin DatePlaceholder = "_DATE(*)_", }; - var currentDate = string.Format(DateTime.UtcNow.Date.ToString("d {0} yyyy"), - GetMonthName(locale, DateTime.UtcNow.Month)); - var result = updater.GetModifiedContents(origContents); - Assert.That(result, Is.EqualTo($"{currentDate}\r\nStuff")); - } - - private string GetMonthName(string locale, int month) - { - var culture = new CultureInfo(locale); - return culture.DateTimeFormat.GetMonthName(month); + Assert.That(result, Is.EqualTo($"16 {localizedMonthName} 2026\r\nStuff")); } [Test] diff --git a/SIL.BuildTasks.Tests/SIL.BuildTasks.Tests.csproj b/SIL.BuildTasks.Tests/SIL.BuildTasks.Tests.csproj index 4417d671..76203f07 100644 --- a/SIL.BuildTasks.Tests/SIL.BuildTasks.Tests.csproj +++ b/SIL.BuildTasks.Tests/SIL.BuildTasks.Tests.csproj @@ -20,6 +20,7 @@ + diff --git a/SIL.BuildTasks/FileUpdate.cs b/SIL.BuildTasks/FileUpdate.cs index d13f9dd6..effccf55 100644 --- a/SIL.BuildTasks/FileUpdate.cs +++ b/SIL.BuildTasks/FileUpdate.cs @@ -7,6 +7,7 @@ using System.Text.RegularExpressions; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; +using SIL.Providers; using static System.IO.File; using static System.Text.RegularExpressions.RegexOptions; @@ -117,6 +118,7 @@ internal string GetModifiedContents(string content) if (!string.IsNullOrEmpty(DatePlaceholder)) { var culture = GetCultureFromFileName() ?? CultureInfo.CurrentCulture; + var currentDate = DateTimeProvider.Current.UtcNow.Date; if (DatePlaceholder.Equals("_DATE(*)_", StringComparison.Ordinal)) { @@ -127,12 +129,12 @@ internal string GetModifiedContents(string content) var format = m.Groups["dateFormat"].Success ? m.Groups["dateFormat"].Value : DateFormat; - return DateTime.UtcNow.Date.ToString(format, culture); + return currentDate.ToString(format, culture); }); } else { - var formattedDate = DateTime.UtcNow.Date.ToString(DateFormat, culture); + var formattedDate = currentDate.ToString(DateFormat, culture); newContents = newContents.Replace(DatePlaceholder, formattedDate); } } diff --git a/SIL.BuildTasks/SIL.BuildTasks.csproj b/SIL.BuildTasks/SIL.BuildTasks.csproj index b258aeb4..a9defc0f 100644 --- a/SIL.BuildTasks/SIL.BuildTasks.csproj +++ b/SIL.BuildTasks/SIL.BuildTasks.csproj @@ -9,6 +9,7 @@ + From 1bd7f2e4fc673077aa974ce2fe1ad1d6e6c5a575 Mon Sep 17 00:00:00 2001 From: tombogle Date: Tue, 27 May 2025 13:58:33 -0400 Subject: [PATCH 3/4] Replaced SuppressMessage attributes with PublicAPI from JetBrains.Annotations Deprecated AbandondedSuites in favor of correctly spelled AbandonedSuites Misc minor code refactoring and cleanup. Fixed typos in comments. --- CHANGELOG.md | 6 +++ SIL.BuildTasks.AWS/AwsTaskBase.cs | 10 ++--- SIL.BuildTasks.AWS/S3/S3BuildPublisher.cs | 12 +++--- SIL.BuildTasks.AWS/SIL.BuildTasks.AWS.csproj | 5 ++- .../SIL.BuildTasks.Tests.csproj | 3 ++ SIL.BuildTasks/Archive/Archive.cs | 7 ++-- SIL.BuildTasks/CpuArchitecture.cs | 6 +-- SIL.BuildTasks/DownloadFile.cs | 17 ++++---- SIL.BuildTasks/FileUpdate.cs | 2 + SIL.BuildTasks/MakePot/MakePot.cs | 17 ++++---- .../MakeWixForDirTree/MakeWixForDirTree.cs | 38 +++++++++--------- SIL.BuildTasks/NormalizeLocales.cs | 4 +- SIL.BuildTasks/SIL.BuildTasks.csproj | 3 ++ .../StampAssemblies/StampAssemblies.cs | 7 ++-- SIL.BuildTasks/SubString/Split.cs | 10 ++--- SIL.BuildTasks/UnitTestTasks/NUnit.cs | 9 ++--- SIL.BuildTasks/UnitTestTasks/NUnit3.cs | 8 ++-- SIL.BuildTasks/UnitTestTasks/TestTask.cs | 40 +++++++++++-------- SIL.BuildTasks/UnixName.cs | 8 ++-- .../UpdateBuildTypeFile.cs | 7 ++-- .../SIL.ReleaseTasks.Dogfood.csproj | 3 ++ SIL.ReleaseTasks/CreateChangelogEntry.cs | 8 ++-- SIL.ReleaseTasks/CreateReleaseNotesHtml.cs | 4 +- SIL.ReleaseTasks/SIL.ReleaseTasks.csproj | 3 ++ SIL.ReleaseTasks/SetReleaseNotesProperty.cs | 12 +++--- .../StampChangelogFileWithVersion.cs | 6 +-- 26 files changed, 130 insertions(+), 125 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d564379f..296503e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,13 +17,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added + - [SIL.BuildTasks] Added FileUpdate.FileLocalePattern (optional param) to infer a locale (e.g., for a localized release notes file) to use when doing date insertion involving month names or abbreviations. ### Changed - [SIL.BuildTasks] Changed FileUpdate.DatePlaceholder to allow the caller to specify a special placeholder `_DATE(*)_` that will look not only for `_DATE_` but also variants that include a date format specifier, such as `_DATE(MMM d, yyyy)_` or `_DATE(MM/yyyy)_` and will use the date format specified instead of the DateFormat. +### Deprecated + +- [SIL.BuildTasks] Deprecated `AbandondedSuites` in favor of correctly spelled `AbandonedSuites`. + ## [3.1.1] - 2025-03-18 + ### Changed - [SIL.BuildTasks] Upgraded dependency on Markdig.Signed to version 0.37.0 (to be consistent with SIL.ReleaseTasks) diff --git a/SIL.BuildTasks.AWS/AwsTaskBase.cs b/SIL.BuildTasks.AWS/AwsTaskBase.cs index 48a5e7c5..6a8dc8de 100644 --- a/SIL.BuildTasks.AWS/AwsTaskBase.cs +++ b/SIL.BuildTasks.AWS/AwsTaskBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2018 SIL Global +// Copyright (c) 2018-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) /* * Original code from https://code.google.com/archive/p/snowcode/ @@ -9,16 +9,15 @@ */ using System; -using System.Diagnostics.CodeAnalysis; using Amazon.Runtime; using Amazon.Runtime.CredentialManagement; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace SIL.BuildTasks.AWS { - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + [PublicAPI] public abstract class AwsTaskBase : Task { /// @@ -33,8 +32,7 @@ public abstract class AwsTaskBase : Task /// protected AWSCredentials GetAwsCredentials() { - AWSCredentials awsCredentials; - if (!new CredentialProfileStoreChain().TryGetAWSCredentials(CredentialStoreProfileName, out awsCredentials)) + if (!new CredentialProfileStoreChain().TryGetAWSCredentials(CredentialStoreProfileName, out var awsCredentials)) throw new ApplicationException("Unable to get AWS credentials from the credential profile store"); Log.LogMessage(MessageImportance.Normal, "Connecting to AWS using AwsAccessKeyId: {0}", diff --git a/SIL.BuildTasks.AWS/S3/S3BuildPublisher.cs b/SIL.BuildTasks.AWS/S3/S3BuildPublisher.cs index 5c52bc3f..f5f2b1b4 100644 --- a/SIL.BuildTasks.AWS/S3/S3BuildPublisher.cs +++ b/SIL.BuildTasks.AWS/S3/S3BuildPublisher.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2018 SIL Global +// Copyright (c) 2018-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) /* * Original code from https://code.google.com/archive/p/snowcode/ @@ -9,8 +9,8 @@ */ using System; -using System.Diagnostics.CodeAnalysis; using Amazon.Runtime; +using JetBrains.Annotations; using Microsoft.Build.Framework; namespace SIL.BuildTasks.AWS.S3 @@ -19,9 +19,7 @@ namespace SIL.BuildTasks.AWS.S3 /// MSBuild task to publish a set of files to a S3 bucket. /// /// If made public the files will be available at https://s3.amazonaws.com/bucket_name/folder/file_name - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - [SuppressMessage("ReSharper", "UnusedMember.Global")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + [PublicAPI] public class S3BuildPublisher : AwsTaskBase { #region Properties @@ -51,7 +49,7 @@ public class S3BuildPublisher : AwsTaskBase public string DestinationBucket { get; set; } /// - /// Gets or sets if the files should be publically readable + /// Gets or sets if the files should be publicly readable /// public bool IsPublicRead { get; set; } @@ -83,7 +81,7 @@ public override bool Execute() private bool ProcessFiles() { - Log.LogMessage(MessageImportance.Normal, "Publishing Sourcefiles={0} to {1}", Join(SourceFiles), DestinationBucket); + Log.LogMessage(MessageImportance.Normal, "Publishing SourceFiles={0} to {1}", Join(SourceFiles), DestinationBucket); ShowAclWarnings(); diff --git a/SIL.BuildTasks.AWS/SIL.BuildTasks.AWS.csproj b/SIL.BuildTasks.AWS/SIL.BuildTasks.AWS.csproj index 67d5a96b..f6b56685 100644 --- a/SIL.BuildTasks.AWS/SIL.BuildTasks.AWS.csproj +++ b/SIL.BuildTasks.AWS/SIL.BuildTasks.AWS.csproj @@ -1,13 +1,16 @@  net472 - SIL.BuildTasks + SIL.BuildTasks.AWS SIL.BuildTasks.AWS defines a S3BuildPublisher msbuild task to publish a set of files to a S3 bucket. SIL.BuildTasks.AWS true + + All + diff --git a/SIL.BuildTasks.Tests/SIL.BuildTasks.Tests.csproj b/SIL.BuildTasks.Tests/SIL.BuildTasks.Tests.csproj index 76203f07..1f34b6e2 100644 --- a/SIL.BuildTasks.Tests/SIL.BuildTasks.Tests.csproj +++ b/SIL.BuildTasks.Tests/SIL.BuildTasks.Tests.csproj @@ -11,6 +11,9 @@ + + All + diff --git a/SIL.BuildTasks/Archive/Archive.cs b/SIL.BuildTasks/Archive/Archive.cs index 79bb4c0d..1b4cc353 100644 --- a/SIL.BuildTasks/Archive/Archive.cs +++ b/SIL.BuildTasks/Archive/Archive.cs @@ -1,17 +1,16 @@ -// Copyright (c) 2018 SIL Global +// Copyright (c) 2018-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Text; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace SIL.BuildTasks.Archive { - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + [PublicAPI] public class Archive : Task { [Required] diff --git a/SIL.BuildTasks/CpuArchitecture.cs b/SIL.BuildTasks/CpuArchitecture.cs index a82ba875..b9a4b2b1 100644 --- a/SIL.BuildTasks/CpuArchitecture.cs +++ b/SIL.BuildTasks/CpuArchitecture.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2018 SIL Global +// Copyright (c) 2018-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -11,7 +11,7 @@ namespace SIL.BuildTasks /// /// Return the CPU architecture of the current system. /// - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] + [PublicAPI] public class CpuArchitecture : Task { public override bool Execute() diff --git a/SIL.BuildTasks/DownloadFile.cs b/SIL.BuildTasks/DownloadFile.cs index b899206f..9802650b 100644 --- a/SIL.BuildTasks/DownloadFile.cs +++ b/SIL.BuildTasks/DownloadFile.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2018 SIL Global +// Copyright (c) 2018-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -16,9 +16,7 @@ namespace SIL.BuildTasks /// may be sent in clear. /// Adapted from http://stackoverflow.com/questions/1089452/how-can-i-use-msbuild-to-download-a-file /// - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - [SuppressMessage("ReSharper", "UnusedMember.Global")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + [PublicAPI] public class DownloadFile : Task { /// @@ -45,9 +43,9 @@ public class DownloadFile : Task public override bool Execute() { - // This doesn't seem to work reliably..can return true even when only network cable is unplugged. - // Left in in case it works in some cases. But the main way of dealing with disconnect is the - // same algorithm in the WebException handler. + // This doesn't seem to work reliably. It can return true even when only network cable + // is unplugged. Left it in just in case it works in some cases. But the main way of + // dealing with disconnect is the same algorithm in the WebException handler. if (!System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable()) { if (File.Exists(LocalFilename)) @@ -60,8 +58,7 @@ public override bool Execute() return false; // Presumably can't continue } - bool success; - var read = DoDownloadFile(Address, LocalFilename, Username, Password, out success); + var read = DoDownloadFile(Address, LocalFilename, Username, Password, out var success); if (success) Log.LogMessage(MessageImportance.Low, "{0} bytes written", read); diff --git a/SIL.BuildTasks/FileUpdate.cs b/SIL.BuildTasks/FileUpdate.cs index effccf55..3beb820d 100644 --- a/SIL.BuildTasks/FileUpdate.cs +++ b/SIL.BuildTasks/FileUpdate.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.IO; using System.Text.RegularExpressions; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using SIL.Providers; @@ -13,6 +14,7 @@ namespace SIL.BuildTasks { + [PublicAPI] public class FileUpdate : Task { private string _dateFormat; diff --git a/SIL.BuildTasks/MakePot/MakePot.cs b/SIL.BuildTasks/MakePot/MakePot.cs index a91a0241..b6238f5e 100644 --- a/SIL.BuildTasks/MakePot/MakePot.cs +++ b/SIL.BuildTasks/MakePot/MakePot.cs @@ -1,18 +1,17 @@ -// Copyright (c) 2018 SIL Global +// Copyright (c) 2018-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text.RegularExpressions; using System.Xml; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace SIL.BuildTasks.MakePot { - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + [PublicAPI] public class MakePot: Task { private readonly Dictionary> _entries = new Dictionary>(); @@ -182,7 +181,7 @@ internal void ProcessSrcFile(string filePath) } var comments = "#: " + filePath; - //catch the second parameter from calls like this: + // Catch the second parameter from calls like this: // StringCataGet("~Note", "The label for the field showing a note."); if (!string.IsNullOrEmpty(match.Groups["note"].Value)) @@ -214,16 +213,16 @@ private static void WriteEntry(string key, IEnumerable comments, TextWri public static string EscapeString(string s) { - var result = s.Replace("\\", "\\\\"); // This must be first + var result = s.Replace(@"\", @"\\"); // This must be first result = result.Replace("\"", "\\\""); return result; } public static string UnescapeString(string s) { - var result = s.Replace("\\'", "'"); - result = result.Replace("\\\"", "\""); - result = result.Replace("\\\\", "\\"); + var result = s.Replace(@"\'", "'"); + result = result.Replace(@"\""", "\""); + result = result.Replace(@"\\", @"\"); return result; } } diff --git a/SIL.BuildTasks/MakeWixForDirTree/MakeWixForDirTree.cs b/SIL.BuildTasks/MakeWixForDirTree/MakeWixForDirTree.cs index 65cc6ae2..e2cbfc53 100644 --- a/SIL.BuildTasks/MakeWixForDirTree/MakeWixForDirTree.cs +++ b/SIL.BuildTasks/MakeWixForDirTree/MakeWixForDirTree.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2018 SIL Global +// Copyright (c) 2018-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) /* * A custom task that walks a directory tree and creates a WiX fragment containing @@ -13,20 +13,18 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Xml; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace SIL.BuildTasks.MakeWixForDirTree { - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - [SuppressMessage("ReSharper", "UnusedMember.Global")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + [PublicAPI] public class MakeWixForDirTree : Task, ILogger { private const string FileNameOfGuidDatabase = ".guidsForInstaller.xml"; @@ -84,7 +82,7 @@ public string IgnoreRegExPattern } /// - /// Whether to just check that all the metadata is uptodate or not. If this is true then no file is output. + /// Whether to just check that all the metadata is up-to-date or not. If this is true then no file is output. /// public bool CheckOnly { get; set; } @@ -112,7 +110,7 @@ public override bool Execute() if (!string.IsNullOrEmpty(InstallerSourceDirectory)) InstallerSourceDirectory = Path.GetFullPath(InstallerSourceDirectory); - /* hatton removed this... it would leave deleted files referenced in the wxs file + /* Hatton removed this... it would leave deleted files referenced in the wxs file if (File.Exists(_outputFilePath)) { DateTime curFileDate = File.GetLastWriteTime(_outputFilePath); @@ -190,7 +188,7 @@ private static void InsertFileDeletionInstruction(XmlNode elemFrag) private void WriteDomToFile(XmlNode doc) { - // write the XML out onlystringles have been modified + // write the XML out only if modified if (CheckOnly || !_filesChanged) return; @@ -342,11 +340,11 @@ private void SetupDirectoryPermissions(XmlNode parent, string parentDirectoryId, private string GetSafeDirectoryId(string directoryPath, string parentDirectoryId) { var id = parentDirectoryId; - //bit of a hack... we don't want our id to have this prefix.dir form fo the top level, - //where it is going to be referenced by other wix files, that will just be expecting the id - //the msbuild target gave for the id of this directory + // This is a bit of a hack... we don't want our id to have this prefix.dir form for the + // top level, where it is going to be referenced by other WIX files; that will just be + // expecting the id the MSBuild target gave for the id of this directory. - //I don't have it quite right, though. See the test file, where you get + // I don't have it quite right, though. See the test file, where you get // kMaxLength) { - id = id.Substring(id.Length - kMaxLength, kMaxLength); //get the last chunk of it + id = id.Substring(id.Length - kMaxLength, kMaxLength); // Get the last chunk of it. } - if (!char.IsLetter(id[0]) && id[0] != '_')//probably not needed now that we're prepending the parent directory id, accept maybe at the root? + if (!char.IsLetter(id[0]) && id[0] != '_') // Probably not needed now that we're prepending the parent directory id, except maybe at the root? id = '_' + id; id = Regex.Replace(id, @"[^\p{Lu}\p{Ll}\p{Nd}._]", "_"); @@ -423,10 +421,10 @@ private void ProcessFile(XmlNode parent, string path, XmlDocument doc, IdToGuidD private static void AddPermissionElement(XmlDocument doc, XmlNode elementToAddPermissionTo) { - var persmission = doc.CreateElement("Permission", Xmlns); - persmission.SetAttribute("GenericAll", "yes"); - persmission.SetAttribute("User", "Everyone"); - elementToAddPermissionTo.AppendChild(persmission); + var permission = doc.CreateElement("Permission", Xmlns); + permission.SetAttribute("GenericAll", "yes"); + permission.SetAttribute("User", "Everyone"); + elementToAddPermissionTo.AppendChild(permission); } public void LogMessage(MessageImportance importance, string message) diff --git a/SIL.BuildTasks/NormalizeLocales.cs b/SIL.BuildTasks/NormalizeLocales.cs index 3c8210ee..1828d859 100644 --- a/SIL.BuildTasks/NormalizeLocales.cs +++ b/SIL.BuildTasks/NormalizeLocales.cs @@ -1,8 +1,9 @@ -// Copyright (c) 2020 SIL Global +// Copyright (c) 2020-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System.IO; using System.Linq; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -12,6 +13,7 @@ namespace SIL.BuildTasks /// Crowdin includes country codes either always or never. Country codes are required for Chinese, but mostly get in the way for other languages. /// This task removes country codes from all locales except Chinese. It expects to work with paths in the form of /%locale%/filename.%locale.extension /// + [PublicAPI] public class NormalizeLocales : Task { /// The directory whose subdirectories are locale names and contain localizations diff --git a/SIL.BuildTasks/SIL.BuildTasks.csproj b/SIL.BuildTasks/SIL.BuildTasks.csproj index a9defc0f..39db4590 100644 --- a/SIL.BuildTasks/SIL.BuildTasks.csproj +++ b/SIL.BuildTasks/SIL.BuildTasks.csproj @@ -7,6 +7,9 @@ true + + All + diff --git a/SIL.BuildTasks/StampAssemblies/StampAssemblies.cs b/SIL.BuildTasks/StampAssemblies/StampAssemblies.cs index e09975e9..95dde747 100644 --- a/SIL.BuildTasks/StampAssemblies/StampAssemblies.cs +++ b/SIL.BuildTasks/StampAssemblies/StampAssemblies.cs @@ -1,17 +1,16 @@ -// Copyright (c) 2018 SIL Global +// Copyright (c) 2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text.RegularExpressions; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace SIL.BuildTasks.StampAssemblies { - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + [PublicAPI] public class StampAssemblies : Task { private enum VersionFormat diff --git a/SIL.BuildTasks/SubString/Split.cs b/SIL.BuildTasks/SubString/Split.cs index ef7327a0..b9edb530 100644 --- a/SIL.BuildTasks/SubString/Split.cs +++ b/SIL.BuildTasks/SubString/Split.cs @@ -1,16 +1,13 @@ -// Copyright (c) 2018 SIL Global +// Copyright (c) 2018-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) -using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace SIL.BuildTasks.SubString { - [SuppressMessage("ReSharper", "UnusedMember.Global")] - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + [PublicAPI] public class Split : Task { public Split() @@ -21,7 +18,6 @@ public Split() MaxSplit = 999; ReturnValue = ""; - } [Required] diff --git a/SIL.BuildTasks/UnitTestTasks/NUnit.cs b/SIL.BuildTasks/UnitTestTasks/NUnit.cs index 44b029dc..cec4e0d3 100644 --- a/SIL.BuildTasks/UnitTestTasks/NUnit.cs +++ b/SIL.BuildTasks/UnitTestTasks/NUnit.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2012-2020 SIL Global +// Copyright (c) 2012-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Microsoft.Win32; @@ -18,10 +18,7 @@ namespace SIL.BuildTasks.UnitTestTasks /// /// Run NUnit on a test assembly. /// - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - [SuppressMessage("ReSharper", "MemberCanBeProtected.Global")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] - [SuppressMessage("ReSharper", "UnusedMember.Global")] + [PublicAPI] public class NUnit : TestTask { public NUnit() diff --git a/SIL.BuildTasks/UnitTestTasks/NUnit3.cs b/SIL.BuildTasks/UnitTestTasks/NUnit3.cs index 02f4eee5..5b205676 100644 --- a/SIL.BuildTasks/UnitTestTasks/NUnit3.cs +++ b/SIL.BuildTasks/UnitTestTasks/NUnit3.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2016-2020 SIL Global +// Copyright (c) 2016-2025 SIL Global // This software is licensed under the MIT license (http://opensource.org/licenses/MIT) using System; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Text; +using JetBrains.Annotations; namespace SIL.BuildTasks.UnitTestTasks { @@ -12,9 +12,7 @@ namespace SIL.BuildTasks.UnitTestTasks /// /// Run NUnit3 on a test assembly. /// - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - [SuppressMessage("ReSharper", "UnusedMember.Global")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + [PublicAPI] public class NUnit3 : NUnit { private bool? _useNUnit3Xml; diff --git a/SIL.BuildTasks/UnitTestTasks/TestTask.cs b/SIL.BuildTasks/UnitTestTasks/TestTask.cs index 5e97040f..ebaaea28 100644 --- a/SIL.BuildTasks/UnitTestTasks/TestTask.cs +++ b/SIL.BuildTasks/UnitTestTasks/TestTask.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2018 SIL Global +// Copyright (c) 20218-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -20,11 +20,7 @@ namespace SIL.BuildTasks.UnitTestTasks /// timeouts. In fact, anything based on ToolTask (at least in Mono 10.4) didn't handle /// timeouts properly in my testing. This code does handle timeouts properly. /// - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - [SuppressMessage("ReSharper", "MemberCanBeProtected.Global")] - [SuppressMessage("ReSharper", "UnusedMember.Global")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] - [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] + [PublicAPI] public abstract class TestTask : Task { private StreamReader _stdError; @@ -83,7 +79,18 @@ protected TestTask() /// Contains the names of test suites that got a timeout or that crashed /// [Output] - public ITaskItem[] AbandondedSuites { get; protected set; } + [Obsolete("Use correctly spelled AbandonedSuites")] + public ITaskItem[] AbandondedSuites + { + get => AbandonedSuites; + protected set => AbandonedSuites = value; + } + + /// + /// Contains the names of test suites that got a timeout or that crashed + /// + [Output] + public ITaskItem[] AbandonedSuites { get; protected set; } public override bool Execute() { @@ -269,9 +276,7 @@ private void StreamReaderThread_Output() } if (Verbose) - { - Log.LogMessage(Importance, logContents); - } + Log.LogMessage(Importance, logContents); // ensure only one thread writes to the log at any time lock (LockObject) @@ -304,16 +309,17 @@ private void StreamReaderThread_Error() } // "The standard error stream is the default destination for error messages and other diagnostic warnings." - // By default log the message as it is most likely a warning. + // By default, log the message as it is most likely a warning. // If the stderr message includes error, crash or fail then log it as an error // and investigate. - // If looks like an error but includes induce or simulator then log as warning instead of error + // If it looks like an error but includes induce or simulator, then log as + // warning instead of error. // Change this if it is still too broad. - string[] toerror = { "error", "crash", "fail" }; - string[] noterror = { "induce", "simulator", "Gdk-CRITICAL **:", "Gtk-CRITICAL **:" }; + string[] toError = { "error", "crash", "fail" }; + string[] notError = { "induce", "simulator", "Gdk-CRITICAL **:", "Gtk-CRITICAL **:" }; - if (toerror.Any(err => logContents.IndexOf(err, StringComparison.OrdinalIgnoreCase) >= 0) && - !noterror.Any(err => logContents.IndexOf(err, StringComparison.OrdinalIgnoreCase) >= 0)) + if (toError.Any(err => logContents.IndexOf(err, StringComparison.OrdinalIgnoreCase) >= 0) && + !notError.Any(err => logContents.IndexOf(err, StringComparison.OrdinalIgnoreCase) >= 0)) { Log.LogError(logContents); } diff --git a/SIL.BuildTasks/UnixName.cs b/SIL.BuildTasks/UnixName.cs index de849845..917b45bb 100644 --- a/SIL.BuildTasks/UnixName.cs +++ b/SIL.BuildTasks/UnixName.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2014-2018 SIL Global +// Copyright (c) 2014-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) // Parts based on code by MJ Hutchinson http://mjhutchinson.com/journal/2010/01/25/integrating_gtk_application_mac using System; -using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -35,9 +35,7 @@ namespace SIL.BuildTasks // // // - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - [SuppressMessage("ReSharper", "UnusedMember.Global")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + [PublicAPI] public class UnixName : Task { public override bool Execute() diff --git a/SIL.BuildTasks/UpdateBuildTypeFile/UpdateBuildTypeFile.cs b/SIL.BuildTasks/UpdateBuildTypeFile/UpdateBuildTypeFile.cs index c0db70eb..c3aed0a5 100644 --- a/SIL.BuildTasks/UpdateBuildTypeFile/UpdateBuildTypeFile.cs +++ b/SIL.BuildTasks/UpdateBuildTypeFile/UpdateBuildTypeFile.cs @@ -1,20 +1,19 @@ -// Copyright (c) 2018 SIL Global +// Copyright (c) 2018-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace SIL.BuildTasks.UpdateBuildTypeFile { - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + [PublicAPI] public class UpdateBuildTypeFile : Task { [Required] diff --git a/SIL.ReleaseTasks.Dogfood/SIL.ReleaseTasks.Dogfood.csproj b/SIL.ReleaseTasks.Dogfood/SIL.ReleaseTasks.Dogfood.csproj index bfc76c23..4b69cb23 100644 --- a/SIL.ReleaseTasks.Dogfood/SIL.ReleaseTasks.Dogfood.csproj +++ b/SIL.ReleaseTasks.Dogfood/SIL.ReleaseTasks.Dogfood.csproj @@ -14,6 +14,9 @@ + + All + diff --git a/SIL.ReleaseTasks/CreateChangelogEntry.cs b/SIL.ReleaseTasks/CreateChangelogEntry.cs index 5f884a3c..5b8cbecd 100644 --- a/SIL.ReleaseTasks/CreateChangelogEntry.cs +++ b/SIL.ReleaseTasks/CreateChangelogEntry.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2018 SIL Global +// Copyright (c) 2018-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.IO; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -15,7 +15,7 @@ namespace SIL.ReleaseTasks /// /// Given a Changelog file, this task will add an entry to the debian changelog. /// - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] + [PublicAPI] public class CreateChangelogEntry: Task { [Required] @@ -39,7 +39,7 @@ public class CreateChangelogEntry: Task /// public string MaintainerInfo { get; set; } - [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")] + [PublicAPI] public override bool Execute() { var oldChangeLog = Path.ChangeExtension(DebianChangelog, ".old"); diff --git a/SIL.ReleaseTasks/CreateReleaseNotesHtml.cs b/SIL.ReleaseTasks/CreateReleaseNotesHtml.cs index 910bbf41..c6c84de2 100644 --- a/SIL.ReleaseTasks/CreateReleaseNotesHtml.cs +++ b/SIL.ReleaseTasks/CreateReleaseNotesHtml.cs @@ -2,10 +2,10 @@ // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Xml.Linq; using System.Xml.XPath; +using JetBrains.Annotations; using Markdig; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -21,7 +21,7 @@ namespace SIL.ReleaseTasks /// The developer-oriented and [Unreleased] beginning will be removed from a Keep a Changelog /// style changelog. /// - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] + [PublicAPI] public class CreateReleaseNotesHtml : Task { public const string kReleaseNotesClassName = "releasenotes"; diff --git a/SIL.ReleaseTasks/SIL.ReleaseTasks.csproj b/SIL.ReleaseTasks/SIL.ReleaseTasks.csproj index 1d6bcf77..bd4d3112 100644 --- a/SIL.ReleaseTasks/SIL.ReleaseTasks.csproj +++ b/SIL.ReleaseTasks/SIL.ReleaseTasks.csproj @@ -7,6 +7,9 @@ SIL.ReleaseTasks.md + + All + diff --git a/SIL.ReleaseTasks/SetReleaseNotesProperty.cs b/SIL.ReleaseTasks/SetReleaseNotesProperty.cs index 5c89bc0a..ec38d20c 100644 --- a/SIL.ReleaseTasks/SetReleaseNotesProperty.cs +++ b/SIL.ReleaseTasks/SetReleaseNotesProperty.cs @@ -1,18 +1,16 @@ -// Copyright (c) 2018 SIL Global +// Copyright (c) 2018-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) -using System; -using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; using System.Text; using System.Text.RegularExpressions; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace SIL.ReleaseTasks { - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] + [PublicAPI] public class SetReleaseNotesProperty : Task { [Required] @@ -220,7 +218,7 @@ private string ExtractPreviousVersionFromChangelog(string currentLevel) // if we have something like // ## [Unreleased] // ## [1.5] - // we're currently at version 1.5 but we want to return the previous version. + // We're currently at version 1.5, but we want to return the previous version. // Skip this heading _currentIndex = i; continue; @@ -235,7 +233,7 @@ private string ExtractPreviousVersionFromChangelog(string currentLevel) private bool SkipHeader(int index) { - //exclude unnecessary headers from changelog file + // Exclude unnecessary headers from changelog file. for (int i = index + 1; i < _markdownLines.Length; i++) { var line = _markdownLines[i]; diff --git a/SIL.ReleaseTasks/StampChangelogFileWithVersion.cs b/SIL.ReleaseTasks/StampChangelogFileWithVersion.cs index 74dcd6a2..29cb7238 100644 --- a/SIL.ReleaseTasks/StampChangelogFileWithVersion.cs +++ b/SIL.ReleaseTasks/StampChangelogFileWithVersion.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2018 SIL Global +// Copyright (c) 2018-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.CompilerServices; +using JetBrains.Annotations; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -18,7 +18,7 @@ namespace SIL.ReleaseTasks /// (Assumes that a temporary line is currently at the top: e.g. ## DEV_VERSION_NUMBER: DEV_RELEASE_DATE /// or that the file contains a `## [Unreleased]` line.) /// - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] + [PublicAPI] public class StampChangelogFileWithVersion : Task { [Required] From faac1698eae8e596090ecaf1a076d1804fbc6aae Mon Sep 17 00:00:00 2001 From: tombogle Date: Tue, 27 May 2025 17:41:00 -0400 Subject: [PATCH 4/4] Fixed copyright date ranges in source files and added note about case-change in CHANGELOG --- CHANGELOG.md | 1 + SIL.BuildTasks/FileUpdate.cs | 2 +- SIL.BuildTasks/StampAssemblies/StampAssemblies.cs | 2 +- SIL.BuildTasks/UnitTestTasks/TestTask.cs | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 296503e8..717ee0fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- [SIL.BuildTasks.AWS] Changed case of text in log message from "Publishing Sourcefiles" "Publishing SourceFiles". If anything is doing a case-sensitive parse of the log file, looking for this text, this could be a breaking change. - [SIL.BuildTasks] Changed FileUpdate.DatePlaceholder to allow the caller to specify a special placeholder `_DATE(*)_` that will look not only for `_DATE_` but also variants that include a date format specifier, such as `_DATE(MMM d, yyyy)_` or `_DATE(MM/yyyy)_` and will use the date format specified instead of the DateFormat. ### Deprecated diff --git a/SIL.BuildTasks/FileUpdate.cs b/SIL.BuildTasks/FileUpdate.cs index 3beb820d..4af317d6 100644 --- a/SIL.BuildTasks/FileUpdate.cs +++ b/SIL.BuildTasks/FileUpdate.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2025 SIL Global +// Copyright (c) 2023-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; using System.Diagnostics; diff --git a/SIL.BuildTasks/StampAssemblies/StampAssemblies.cs b/SIL.BuildTasks/StampAssemblies/StampAssemblies.cs index 95dde747..71f15d9a 100644 --- a/SIL.BuildTasks/StampAssemblies/StampAssemblies.cs +++ b/SIL.BuildTasks/StampAssemblies/StampAssemblies.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2025 SIL Global +// Copyright (c) 2018-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; using System.Diagnostics; diff --git a/SIL.BuildTasks/UnitTestTasks/TestTask.cs b/SIL.BuildTasks/UnitTestTasks/TestTask.cs index ebaaea28..734c1525 100644 --- a/SIL.BuildTasks/UnitTestTasks/TestTask.cs +++ b/SIL.BuildTasks/UnitTestTasks/TestTask.cs @@ -1,4 +1,4 @@ -// Copyright (c) 20218-2025 SIL Global +// Copyright (c) 2018-2025 SIL Global // This software is licensed under the MIT License (http://opensource.org/licenses/MIT) using System; using System.Collections.Generic;