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

Code before:
        /// to start with '/./' to prevent them from being interpreted as switches.
        /// </summary>
        /// <param name="sources">The source files to transform</param>
        /// <returns>Transformed source files suitable for passing to the compiler command line</returns>
        private ITaskItem[]? GetSourcesForCommandLine(ITaskItem[]? sources)
        {
            if (sources is null || sources.Length == 0)
                return sources;

            // On Windows, no transformation is needed
            if (Path.DirectorySeparatorChar == '\\')
                return sources;

            // Check if any source file starts with '/' (Unix root directory)
            bool needsTransformation = false;
            foreach (var source in sources)
            {
                if (source.ItemSpec.StartsWith("/") && !source.ItemSpec.StartsWith("/./"))
                {
                    needsTransformation = true;
                    break;
                }
            }

            if (!needsTransformation)
                return sources;

            // Create a copy of the array with transformed paths
            var transformedSources = new ITaskItem[sources.Length];
            for (int i = 0; i < sources.Length; i++)
            {
                var source = sources[i];
                if (source.ItemSpec.StartsWith("/") && !source.ItemSpec.StartsWith("/./"))
                {
                    // Create a simple wrapper that returns the transformed path
                    transformedSources[i] = new TransformedTaskItem(source, "/." + source.ItemSpec);
                }
                else
                {
                    transformedSources[i] = source;
                }
            }

            return transformedSources;
        }

        /// <summary>
        /// A wrapper around ITaskItem that returns a transformed ItemSpec but delegates
        /// all other operations to the underlying item.
        /// </summary>
        private class TransformedTaskItem : ITaskItem

Review comment (suggestion):
Consider going over the sources just once and lazily initialize `transformedSources` if we determine it's necessary.

Code after:
                return;

            foreach (var item in taskItems)
            {
                item.ItemSpec = Utilities.GetFullPathNoThrow(item.ItemSpec);
            }
        }

        /// <summary>
        /// Transforms source file paths to avoid misinterpretation as command-line switches.
        /// On Unix, source files that would be misinterpreted as options (e.g., those starting 
        /// with '/' without another '/' after the first character) are transformed to start 
        /// with '/./' to prevent them from being interpreted as switches.
        /// This uses the same heuristic as the compiler's CommandLineParser.TryParseOption.
        /// </summary>
        /// <param name="sources">The source files to transform</param>
        /// <returns>Array of source file paths as strings, or null if no transformation needed</returns>
        private string[]? GetTransformedSourcesForCommandLine(ITaskItem[]? sources)
        {
            if (sources is null || sources.Length == 0)
                return null;

            // On Windows, no transformation is needed
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                return null;

            // Go over sources once and lazily initialize transformedSources if needed
            string[]? transformedSources = null;
            for (int i = 0; i < sources.Length; i++)
            {
                var itemSpec = sources[i].ItemSpec;

                // Check if this path needs transformation using the compiler's heuristic:
                // A path starting with '/' is treated as an option unless it contains 
                // another '/' after the first character (e.g., "/dir/file.cs" is safe)
                if (itemSpec.Length > 1 && itemSpec[0] == '/' && itemSpec.IndexOf('/', 1) < 0)
                {
                    // Lazy initialization: create the array on first transformation
                    if (transformedSources == null)
                    {
                        transformedSources = new string[sources.Length];
                        // Copy all items processed so far
                        for (int j = 0; j < i; j++)
                        {
                            transformedSources[j] = sources[j].ItemSpec;
                        }
                    }

                    // Transform this path to prevent misinterpretation as an option
                    transformedSources[i] = "/." + itemSpec;
                }