• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

HicServices / RDMP / 13318089130

13 Feb 2025 10:13PM UTC coverage: 57.398% (+0.004%) from 57.394%
13318089130

Pull #2134

github

jas88
Update ChildProviderTests.cs

Fix up TestUpTo method
Pull Request #2134: CodeQL fixups

11346 of 21308 branches covered (53.25%)

Branch coverage included in aggregate %.

104 of 175 new or added lines in 45 files covered. (59.43%)

362 existing lines in 23 files now uncovered.

32218 of 54590 relevant lines covered (59.02%)

17091.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

39.34
/Rdmp.Core/ReusableLibraryCode/UsefulStuff.cs
1
// Copyright (c) The University of Dundee 2018-2019
2
// This file is part of the Research Data Management Platform (RDMP).
3
// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4
// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5
// You should have received a copy of the GNU General Public License along with RDMP. If not, see <https://www.gnu.org/licenses/>.
6

7
using System;
8
using System.Collections.Generic;
9
using System.Data;
10
using System.Data.Common;
11
using System.Diagnostics;
12
using System.Globalization;
13
using System.IO;
14
using System.Linq;
15
using System.Reflection;
16
using System.Runtime.InteropServices;
17
using System.Security.Cryptography;
18
using System.Text;
19
using System.Text.RegularExpressions;
20
using System.Threading;
21
using System.Threading.Tasks;
22
using CommandLine;
23
using CsvHelper;
24

25
namespace Rdmp.Core.ReusableLibraryCode;
26

27
/// <summary>
28
/// Contains lots of generically useful static methods
29
/// </summary>
30
public static partial class UsefulStuff
31
{
32
    public static readonly Regex RegexThingsThatAreNotNumbersOrLetters = NonAlphaNumeric();
2✔
33

34
    public static readonly Regex RegexThingsThatAreNotNumbersOrLettersOrUnderscores =
2✔
35
        NonAlphaNumericUnderscore();
2✔
36

37
    private static readonly Regex NullWithSpaces = NullInSpace();
2✔
38

39
    public static bool IsBasicallyNull(this string result) =>
40
        string.IsNullOrWhiteSpace(result) ||
20,136,834✔
41
        // if user types the literal string null then return null (typically interpreted as - 'I don't want to pick one')
20,136,834✔
42
        // but not the same as task cancellation
20,136,834✔
43
        NullWithSpaces.IsMatch(result);
20,136,834✔
44

45
    public static bool IsBadName(string name) => name?.Any(Path.GetInvalidFileNameChars().Contains) == true;
90!
46

47
    public static void OpenUrl(string url)
48
    {
49
        try
50
        {
51
            Process.Start(url);
×
52
        }
×
53
        catch
×
54
        {
55
            // hack because of this: https://github.com/dotnet/corefx/issues/10361
56
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
×
57
            {
58
                url = url.Replace("&", "^&");
×
59
                Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
×
60
            }
61
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
×
62
            {
63
                Process.Start("xdg-open", url);
×
64
            }
65
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
×
66
            {
67
                Process.Start("open", url);
×
68
            }
69
            else
70
            {
71
                throw;
×
72
            }
73
        }
×
74
    }
×
75

76

77
    internal static DateTime LooseConvertToDate(object iO)
78
    {
79
        DateTime sDate;
80

81
        try
82
        {
83
            sDate = Convert.ToDateTime(iO);
×
84
        }
×
85
        catch (FormatException)
×
86
        {
87
            try
88
            {
89
                var sDateAsString = iO.ToString();
×
90

91
                switch (sDateAsString?.Length)
×
92
                {
93
                    case 6:
94
                    case 8:
95
                        sDate = Convert.ToDateTime($"{sDateAsString[..2]}/{sDateAsString[2..4]}/{sDateAsString[4..]}");
×
96
                        break;
×
97
                    default:
98
                        throw;
×
99
                }
100
            }
×
101
            catch (Exception)
×
102
            {
103
                throw new Exception($"Cannot recognise date format :{iO}");
×
104
            }
105
        }
×
106

107
        return sDate;
×
108
    }
109

110

111
    // find quoted field names at end of line
112
    private static readonly Regex RDoubleQuotes = new("\"([^\"]+)\"$");
2✔
113
    private static readonly Regex RSingleQuotes = new("'([^']+)'$");
2✔
114
    private static readonly Regex RBacktickQuotes = new("`([^']+)`$");
2✔
115
    private static readonly Regex RSquareBrackets = new(@"\[([^[]+)]$");
2✔
116
    private static readonly Regex RNoPunctuation = new(@"^([\w\s]+)$");
2✔
117

118
    public static IEnumerable<string> GetArrayOfColumnNamesFromStringPastedInByUser(string text)
119
    {
120
        if (string.IsNullOrWhiteSpace(text))
90!
121
            yield break;
×
122

123
        var split = text.Split(new char[] { '\r', '\n', ',' }, StringSplitOptions.RemoveEmptyEntries);
90✔
124

125

126
        //trim off [db]..[tbl] 1
127
        for (var i = 0; i < split.Length; i++)
360✔
128
            split[i] = Regex.Replace(split[i], @"\s+[\d\s]*$", "");
90✔
129

130
        //identifies the last word in a collection of multiple words (requires you .Trim() so we don't get ending whitespace match)
131
        var regexLastWord = new Regex("\\s[^\\s]*$");
90✔
132
        foreach (var s in split)
360✔
133
        {
134
            //clean the string
135

136
            var toAdd = s.Trim();
90✔
137
            if (toAdd.Contains('.'))
90✔
138
                toAdd = toAdd[(toAdd.LastIndexOf(".", StringComparison.Ordinal) + 1)..];
40✔
139

140
            var gotDelimitedMatch = false;
90✔
141

142
            // if user has really horrible names like with spaces and stuff
143
            // then try expect them to have quoted them and pull out the capture
144
            // groups.  Remember different DBMS have different quoting symbols
145
            foreach (var r in new[]
670✔
146
                     {
90✔
147
                         RDoubleQuotes, RSingleQuotes,
90✔
148
                         RSquareBrackets, RBacktickQuotes, RNoPunctuation
90✔
149
                     })
90✔
150
            {
151
                var m = r.Matches(toAdd);
290✔
152
                if (!m.Any()) continue;
290✔
153
                yield return m.Last().Groups[1].Value;
90✔
154
                gotDelimitedMatch = true;
90✔
155
                break;
90✔
156
            }
157

158
            if (gotDelimitedMatch)
90!
159
                continue;
160

161
            toAdd = toAdd.Replace("]", "");
×
162
            toAdd = toAdd.Replace("[", "");
×
163

164
            toAdd = toAdd.Replace("`", "");
×
165

166
            if (string.IsNullOrWhiteSpace(toAdd))
×
167
                continue;
168

169
            if (regexLastWord.IsMatch(toAdd))
×
170
                toAdd = regexLastWord.Match(toAdd).Value.Trim();
×
171

172
            yield return toAdd;
×
173
        }
×
174
    }
90✔
175

176
    public static bool CHIisOK(string sCHI) =>
177
        long.TryParse(sCHI, NumberStyles.None, CultureInfo.InvariantCulture, out _) && sCHI.Length == 10 &&
8✔
178
        DateTime.TryParse($"{sCHI[..2]}/{sCHI[2..4]}/{sCHI[4..6]}", out _) && GetCHICheckDigit(sCHI) == sCHI[^1];
8✔
179

180
    private static char GetCHICheckDigit(string sCHI)
181
    {
182
        //sCHI = "120356785";
183
        var lsCHI = sCHI.Length; // Must be 10!!
4✔
184

185
        var sum = 0;
4✔
186
        var c = (int)'0';
4✔
187
        for (var i = 0; i < lsCHI - 1; i++)
80✔
188
            sum += (sCHI[i] - c) * (lsCHI - i);
36✔
189
        sum %= 11;
4✔
190

191
        c = 11 - sum;
4✔
192
        if (c == 11) c = 0;
4!
193

194
        return (char)(c + '0');
4✔
195
    }
196

197
    public static DirectoryInfo GetExecutableDirectory() =>
198
        new(AppDomain.CurrentDomain.BaseDirectory ?? throw new Exception("BaseDirectory was null?!"));
12!
199

200
    public static string HashFile(string filename, int retryCount = 6)
201
    {
202
        try
203
        {
204
            using var hashProvider = SHA512.Create();
88✔
205
            using var stream = File.OpenRead(filename);
88✔
206
            return BitConverter.ToString(hashProvider.ComputeHash(stream));
88✔
207
        }
208
        catch (IOException)
×
209
        {
210
            //couldn't access the file so wait 1 second then try again
211
            Thread.Sleep(1000);
×
212

213
            if (retryCount-- > 0)
×
214
                return HashFile(filename, retryCount); //try it again (recursively)
×
215

UNCOV
216
            throw;
×
217
        }
218
    }
88✔
219

220
    public static bool RequiresLength(string columnType)
221
    {
222
        return columnType.ToLowerInvariant() switch
×
223
        {
×
224
            "binary" => true,
×
225
            "bit" => false,
×
226
            "char" => true,
×
227
            "image" => true,
×
228
            "nchar" => true,
×
229
            "nvarchar" => true,
×
230
            "varbinary" => true,
×
231
            "varchar" => true,
×
232
            "numeric" => true,
×
233
            _ => false
×
234
        };
×
235
    }
236

237
    public static bool HasPrecisionAndScale(string columnType)
238
    {
239
        return columnType.ToLowerInvariant() switch
×
240
        {
×
241
            "decimal" => true,
×
242
            "numeric" => true,
×
243
            _ => false
×
244
        };
×
245
    }
246

247
    public static string RemoveIllegalFilenameCharacters(string value)
248
    {
249
        return string.IsNullOrWhiteSpace(value)
22!
250
            ? value
22✔
251
            : Path.GetInvalidFileNameChars().Aggregate(value, static (current, invalidFileNameChar) => current.Replace(invalidFileNameChar.ToString(), ""));
924✔
252
    }
253

254

255
    public static void ExecuteBatchNonQuery(string sql, DbConnection conn, DbTransaction transaction = null,
256
        int timeout = 30)
257
    {
258
        ExecuteBatchNonQuery(sql, conn, transaction, out _, timeout);
836✔
259
    }
836✔
260

261
    /// <summary>
262
    /// Executes the given SQL against the database + sends GO delimited statements as separate batches
263
    /// </summary>
264
    /// <param name="sql"></param>
265
    /// <param name="conn"></param>
266
    /// <param name="transaction"></param>
267
    /// <param name="performanceFigures">Line number the batch started at and the time it took to complete it</param>
268
    /// <param name="timeout"></param>
269
    public static void ExecuteBatchNonQuery(string sql, DbConnection conn, DbTransaction transaction,
270
        out Dictionary<int, Stopwatch> performanceFigures, int timeout = 30)
271
    {
272
        performanceFigures = new Dictionary<int, Stopwatch>();
844✔
273

274
        var sqlBatch = string.Empty;
844✔
275
        var cmd = DatabaseCommandHelper.GetCommand(string.Empty, conn, transaction);
844✔
276
        var hadToOpen = false;
844✔
277

278
        if (conn.State != ConnectionState.Open)
844!
279
        {
280
            conn.Open();
×
281
            hadToOpen = true;
×
282
        }
283

284
        var lineNumber = 1;
844✔
285

286
        sql += "\nGO"; // make sure last batch is executed.
844✔
287
        try
288
        {
289
            foreach (var line in sql.Split(new[] { "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries))
118,008✔
290
            {
291
                lineNumber++;
58,160✔
292

293
                if (line.ToUpperInvariant().Trim() == "GO")
58,160✔
294
                {
295
                    if (string.IsNullOrWhiteSpace(sqlBatch))
2,088✔
296
                        continue;
297

298
                    if (!performanceFigures.ContainsKey(lineNumber))
1,976✔
299
                        performanceFigures.Add(lineNumber, new Stopwatch());
1,976✔
300
                    performanceFigures[lineNumber].Start();
1,976✔
301

302
                    cmd.CommandText = sqlBatch;
1,976✔
303
                    cmd.CommandTimeout = timeout;
1,976✔
304
                    cmd.ExecuteNonQuery();
1,976✔
305

306
                    performanceFigures[lineNumber].Stop();
1,976✔
307
                    sqlBatch = string.Empty;
1,976✔
308
                }
309
                else
310
                {
311
                    sqlBatch += $"{line}\n";
56,072✔
312
                }
313
            }
314
        }
844✔
315
        finally
316
        {
317
            if (hadToOpen)
844!
318
                conn.Close();
×
319
        }
844✔
320
    }
844✔
321

322

323
    /// <summary>
324
    /// Locates a manifest resource in the assembly under the manifest name subspace.  If you want to spray the resource MySoftwareSuite.MyApplication.MyResources.Bob.txt then pass:
325
    /// 1. the assembly containing the resource (e.g. typeof(MyClass1).Assembly)
326
    /// 2. the full path to the resource file: "MySoftwareSuite.MyApplication.MyResources.Bob.txt"
327
    /// 3. the filename "Bob.txt"
328
    /// </summary>
329
    /// <param name="assembly">The dll e.g. MySoftwareSuite.MyApplication.dll</param>
330
    /// <param name="manifestName">The full path to the manifest resource e.g. MySoftwareSuite.MyApplication.MyResources.Bob.txt</param>
331
    /// <param name="file">The filename ONLY of the resource e.g. Bob.txt</param>
332
    /// <param name="outputDirectory">The directory to put the generated file in.  Defaults to %appdata%/RDMP </param>
333
    public static FileInfo SprayFile(Assembly assembly, string manifestName, string file, string outputDirectory = null)
334
    {
335
        outputDirectory ??= Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RDMP");
6!
336

337
        using var fileToSpray = assembly.GetManifestResourceStream(manifestName) ?? throw new Exception(
6!
338
            $"assembly.GetManifestResourceStream returned null for manifest name {manifestName} in assembly {assembly}");
6✔
339

340
        if (!Directory.Exists(outputDirectory))
6!
341
            Directory.CreateDirectory(outputDirectory);
×
342

343
        var target = new FileInfo(Path.Combine(outputDirectory, file));
6✔
344
        using var destination = target.OpenWrite();
6✔
345
        fileToSpray.CopyTo(destination, 1 << 20);
6✔
346
        return target;
6✔
347
    }
6✔
348

349
    public static string GetHumanReadableByteSize(long len)
350
    {
351
        string[] sizes = { "bytes", "KB", "MB", "GB", "TB", "PB" };
×
352

353
        var order = 0;
×
354
        while (len >= 1024 && order + 1 < sizes.Length)
×
355
        {
356
            order++;
×
357
            len /= 1024;
×
358
        }
359

360
        // Adjust the format string to your preferences. For example "{0:0.#}{1}" would
361
        // show a single decimal place, and no space.
362
        return $"{len:0.##} {sizes[order]}";
×
363
    }
364

365
    public static bool VerifyFileExists(string path, int timeout)
366
    {
367
        var task = new Task<bool>(() =>
×
368
        {
×
369
            try
×
370
            {
×
371
                var fi = new FileInfo(path);
×
372
                return fi.Exists;
×
373
            }
×
374
            catch (Exception)
×
375
            {
×
376
                return false;
×
377
            }
×
378
        });
×
379
        task.Start();
×
380
        return task.Wait(timeout) && task.Result;
×
381
    }
382

383
    public static bool VerifyFileExists(Uri uri, int timeout) => VerifyFileExists(uri.LocalPath, timeout);
×
384

385
    public static string DataTableToHtmlDataTable(DataTable dt)
386
    {
387
        var sb = new StringBuilder();
×
388

389
        sb.AppendLine("<Table>");
×
390
        sb.Append("<TR>");
×
391

392
        foreach (DataColumn column in dt.Columns)
×
393
            sb.Append($"<TD>{column.ColumnName}</TD>");
×
394

395
        sb.AppendLine("</TR>");
×
396

397
        foreach (DataRow row in dt.Rows)
×
398
        {
399
            sb.Append("<TR>");
×
400

401
            foreach (var cellObject in row.ItemArray)
×
402
                sb.Append($"<TD>{cellObject}</TD>");
×
403

404
            sb.AppendLine("</TR>");
×
405
        }
406

407
        sb.Append("</Table>");
×
408

409
        return sb.ToString();
×
410
    }
411

412
    public static string DataTableToCsv(DataTable dt)
413
    {
414
        var sb = new StringBuilder();
×
415

416
        using var w = new CsvWriter(new StringWriter(sb), CultureInfo.CurrentCulture);
×
417
        foreach (DataColumn column in dt.Columns)
×
418
            w.WriteField(column.ColumnName);
×
419

420
        w.NextRecord();
×
421

422
        foreach (DataRow row in dt.Rows)
×
423
        {
424
            foreach (var cellObject in row.ItemArray)
×
425
                w.WriteField(cellObject);
×
426

427
            w.NextRecord();
×
428
        }
429

430
        w.Flush();
×
431

432
        return sb.ToString();
×
433
    }
×
434

435
    public static void ShowPathInWindowsExplorer(FileSystemInfo fileInfo)
436
    {
437
        var argument = $"{(fileInfo is FileInfo ? "/select," : "")} \"{fileInfo.FullName}\"";
×
438
        Process.Start("explorer.exe", argument);
×
439
    }
×
440

441
    public static string GetClipboardFormattedHtmlStringFromHtmlString(string s)
442
    {
443
        const int maxLength = 1_000_000_000 - 52; // Header is 51 characters, total length must fit in 10 digits
444
        if (s.Length > maxLength)
2!
445
            throw new ArgumentException(
×
446
                $"String s is too long, the maximum length is {maxLength} but argument s was length {s.Length}",
×
447
                nameof(s));
×
448

449
        // Circular dependency here: we need a string containing both its own length and the length of the string after it
450
        // {0:D6} is the same length as its own description
451
        // {1:D10} is 3 characters longer than its own description, so +3
452
        // {2} on the end adds 3, so the overall prefix length is constant
453
        const string template = "Version:1.0\r\nStartHTML:{0:D6}\r\nEndHTML:{1:D10}\r\n{2}";
454
        return string.Format(
2✔
455
            template,
2✔
456
            template.Length,
2✔
457
            s.Length + template.Length, s);
2✔
458
    }
459

460
    public static bool IsAssignableToGenericType(Type givenType, Type genericType)
461
    {
462
        var interfaceTypes = givenType.GetInterfaces();
1,680✔
463

464
        if (interfaceTypes.Any(it => it.IsGenericType && it.GetGenericTypeDefinition() == genericType))
6,604!
465
            return true;
×
466

467
        if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
1,680!
468
            return true;
×
469

470
        var baseType = givenType.BaseType;
1,680✔
471
        return baseType != null && IsAssignableToGenericType(baseType, genericType);
1,680✔
472
    }
473

474
    public static string PascalCaseStringToHumanReadable(string pascalCaseString)
475
    {
476
        //Deal with legacy property names by replacing underscore with a space
477
        pascalCaseString = pascalCaseString.Replace("_", " ");
998✔
478

479
        //There are two clauses in this Regex
480
        //Part1: [A-Z][A-Z]*(?=[A-Z][a-z]|\b) - looks for any series of uppercase letters that have a ending uppercase then lowercase OR end of line character: https://regex101.com/r/mCqVk6/2
481
        //Part2: [A-Z](?=[a-z])               - looks for any single  of uppercase letters followed by a lower case letter: https://regex101.com/r/hdSCqH/1
482
        //This is then made into a single group that is matched and we add a space on front during the replacement.
483
        pascalCaseString = Regex.Replace(pascalCaseString, @"([A-Z][A-Z]*(?=[A-Z][a-z]|\b)|[A-Z](?=[a-z]))", " $1");
998✔
484

485
        //Remove any double multiple white space
486
        //Because this matched the first capital letter in a string with Part2 of our regex above we should TRIM to remove the white space.
487
        pascalCaseString = Regex.Replace(pascalCaseString, @"\s\s+", " ").Trim();
998✔
488

489
        return pascalCaseString;
998✔
490
    }
491

492
    /// <summary>
493
    /// Returns the <paramref name="input"/> string split across multiple lines with the
494
    /// <paramref name="newline"/> (or <see cref="Environment.NewLine"/> if null) separator
495
    /// such that no lines are longer than <paramref name="maxLen"/>
496
    /// </summary>
497
    /// <param name="input"></param>
498
    /// <param name="maxLen"></param>
499
    /// <param name="newline"></param>
500
    /// <returns></returns>
501
    public static string SplitByLength(string input, int maxLen, string newline = null)
502
    {
503
        return
×
504
            string.Join(newline ?? Environment.NewLine,
×
505
                Regex.Split(input, $@"(.{{1,{maxLen}}})(?:\s|$)")
×
NEW
506
                    .Where(static x => x.Length > 0)
×
NEW
507
                    .Select(static x => x.Trim()));
×
508
    }
509

510

511
    public static void ConfirmContentsOfDirectoryAreTheSame(DirectoryInfo first, DirectoryInfo other)
512
    {
513
        var filesInFirst = first.EnumerateFiles().ToArray();
×
514
        var filesInOther = other.EnumerateFiles().ToArray();
×
NEW
515
        if (filesInFirst.Length != filesInOther.Length)
×
NEW
516
            throw new Exception(
×
NEW
517
                $"found different number of files in Globals directory {first.FullName} and {other.FullName}");
×
518

519
        for (var i = 0; i < filesInFirst.Length; i++)
×
520
        {
521
            var file1 = filesInFirst[i];
×
522
            var file2 = filesInOther[i];
×
523
            if (!file1.Name.Equals(file2.Name))
×
524
                throw new Exception(
×
525
                    $"Although there were the same number of files in Globals directories {first.FullName} and {other.FullName}, there were differing file names ({file1.Name} and {file2.Name})");
×
526

527
            if (!HashFile(file1.FullName).Equals(HashFile(file2.FullName)))
×
528
                throw new Exception(
×
529
                    $"File found in Globals directory which has a different MD5 from another Globals file.  Files were \"{file1.FullName}\" and \"{file2.FullName}\"");
×
530
        }
531
    }
×
532

533
    public static Parser GetParser()
534
    {
535
        var defaults = Parser.Default.Settings;
×
536

537
        var parser = new Parser(settings =>
×
538
        {
×
539
            settings.CaseSensitive = false;
×
540
            settings.CaseInsensitiveEnumValues = true;
×
541
            settings.EnableDashDash = defaults.EnableDashDash;
×
542
            settings.HelpWriter = defaults.HelpWriter;
×
543
            settings.IgnoreUnknownArguments = defaults.IgnoreUnknownArguments;
×
544
            settings.MaximumDisplayWidth = defaults.MaximumDisplayWidth;
×
545
            settings.ParsingCulture = defaults.ParsingCulture;
×
546
        });
×
547

548
        return parser;
×
549
    }
550

551
    /// <summary>
552
    /// Implementation of <see cref="Convert.ChangeType(object,Type)"/> that works with nullable types,
553
    /// dates etc
554
    /// </summary>
555
    /// <param name="value"></param>
556
    /// <param name="conversionType"></param>
557
    /// <returns></returns>
558
    public static object ChangeType(object value, Type conversionType)
559
    {
560
        var t = Nullable.GetUnderlyingType(conversionType) ?? conversionType;
52✔
561

562
        return t == typeof(DateTime) && value is string s
52!
563
            ? string.Equals(s, "now", StringComparison.InvariantCultureIgnoreCase)
52✔
564
                ? DateTime.Now
52✔
565
                :
52✔
566
                //Convert.ChangeType doesn't handle dates, so let's deal with that
52✔
567
                DateTime.Parse(s)
52✔
568
            : value == null || (value is string sval && string.IsNullOrWhiteSpace(sval))
52✔
569
                ? null
52✔
570
                : Convert.ChangeType(value, t);
52✔
571
    }
572

573
    [GeneratedRegex("[^0-9A-Za-z]+", RegexOptions.Compiled | RegexOptions.CultureInvariant)]
574
    private static partial Regex NonAlphaNumeric();
575

576
    [GeneratedRegex("[^0-9A-Za-z_]+", RegexOptions.Compiled | RegexOptions.CultureInvariant)]
577
    private static partial Regex NonAlphaNumericUnderscore();
578

579
    [GeneratedRegex("^\\s*null\\s*$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant)]
580
    private static partial Regex NullInSpace();
581
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc