Repository: dotnet/roslyn
File: src/Compilers/Core/MSBuildTask/MapSourceRoots.cs (C#)

Code before:
        public bool Deterministic { get; set; }

        /// <summary>
        /// SourceRoot items with <term>MappedPath</term> metadata set.
        /// Items listed in <see cref="SourceRoots"/> that have the same ItemSpec will be merged into a single item in this list.
        /// </summary>
        [Output]
        public ITaskItem[]? MappedSourceRoots { get; private set; }

        private static class Names
        {
            public const string SourceRoot = nameof(SourceRoot);
            public const string DeterministicSourcePaths = nameof(DeterministicSourcePaths);

            // Names of well-known SourceRoot metadata items:
            public const string SourceControl = nameof(SourceControl);
            public const string RevisionId = nameof(RevisionId);
            public const string NestedRoot = nameof(NestedRoot);
            public const string ContainingRoot = nameof(ContainingRoot);
            public const string MappedPath = nameof(MappedPath);
            public const string SourceLinkUrl = nameof(SourceLinkUrl);

            public static readonly string[] SourceRootMetadataNames = new[] { SourceControl, RevisionId, NestedRoot, ContainingRoot, MappedPath, SourceLinkUrl };
        }

        private static string NormalizePath(string path)
        {
            return string.IsNullOrEmpty(path) ? path : EnsureEndsWithSlash(Utilities.GetFullPathNoThrow(path));
        }

        private static string EnsureEndsWithSlash(string path)
            => EndsWithDirectorySeparator(path) ? path : path + Path.DirectorySeparatorChar;

        private static bool EndsWithDirectorySeparator(string path)
        {
            if (path.Length == 0)
            {
                return false;
            }

            char c = path[path.Length - 1];
            return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
        }

        public override bool Execute()
        {
            // Merge metadata of SourceRoot items with the same identity.
            var mappedSourceRoots = new List<ITaskItem>();
            var rootByItemSpec = new Dictionary<string, ITaskItem>();
            foreach (var sourceRoot in SourceRoots)
            {

Review comment (suggestion):
```suggestion
        [return: NotNullIfNotNull("path")]
        private static string? NormalizePath(string? path)
```

The `IsNullOrEmpty` indicates this can legally be `null`

Code after:
        /// </summary>
        public bool Deterministic { get; set; }

        /// <summary>
        /// SourceRoot items with <term>MappedPath</term> metadata set.
        /// Items listed in <see cref="SourceRoots"/> that have the same ItemSpec will be merged into a single item in this list.
        /// </summary>
        [Output]
        public ITaskItem[]? MappedSourceRoots { get; private set; }

        private static class Names
        {
            public const string SourceRoot = nameof(SourceRoot);
            public const string DeterministicSourcePaths = nameof(DeterministicSourcePaths);

            // Names of well-known SourceRoot metadata items:
            public const string SourceControl = nameof(SourceControl);
            public const string RevisionId = nameof(RevisionId);
            public const string NestedRoot = nameof(NestedRoot);
            public const string ContainingRoot = nameof(ContainingRoot);
            public const string MappedPath = nameof(MappedPath);
            public const string SourceLinkUrl = nameof(SourceLinkUrl);

            public static readonly string[] SourceRootMetadataNames = new[] { SourceControl, RevisionId, NestedRoot, ContainingRoot, MappedPath, SourceLinkUrl };
        }

        [return: NotNullIfNotNull(nameof(path))]
        private static string? NormalizePath(string? path)
        {
            return string.IsNullOrEmpty(path) ? path : EnsureEndsWithSlash(Utilities.GetFullPathNoThrow(path));
        }

        private static string EnsureEndsWithSlash(string path)
            => EndsWithDirectorySeparator(path) ? path : path + Path.DirectorySeparatorChar;

        private static bool EndsWithDirectorySeparator(string path)
        {
            if (path.Length == 0)
            {
                return false;
            }

            char c = path[path.Length - 1];
            return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
        }

        public override bool Execute()
        {
            // Merge metadata of SourceRoot items with the same identity.
            var mappedSourceRoots = new List<ITaskItem>();
            var rootByItemSpec = new Dictionary<string, ITaskItem>();