| File: ValueTypeParser.cs | Web Access |
| Project: src\msbuild\src\Framework\Microsoft.Build.Framework.csproj (Microsoft.Build.Framework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Globalization; using System.IO; using Microsoft.Build.Framework; namespace Microsoft.Build.Shared { /// <summary> /// Provides unified parsing and formatting logic for value types used in MSBuild task parameters. /// This ensures consistent behavior between TaskItem<T> parsing and TaskExecutionHost parameter binding. /// </summary> internal static class ValueTypeParser { /// <summary> /// Returns true if <paramref name="type"/> is an intended target type for typed task-item binding. /// </summary> internal static bool IsSupportedType(Type type) => type == typeof(string) || type == typeof(bool) || type == typeof(char) || type == typeof(byte) || type == typeof(sbyte) || type == typeof(short) || type == typeof(ushort) || type == typeof(int) || type == typeof(uint) || type == typeof(long) || type == typeof(ulong) || type == typeof(float) || type == typeof(double) || type == typeof(decimal) || type == typeof(DateTime) || TaskItemTypeDetector.IsSupportedPathType(type); /// <summary> /// Parses a boolean value using MSBuild's boolean conventions. /// Supports: true/on/yes/!false/!off/!no for true, false/off/no/!true/!on/!yes for false. /// </summary> private static bool ParseBool(string value) { // Reuse MSBuild's canonical boolean parsing (true/false, on/off, yes/no, and '!' negation) return ConversionUtilities.ConvertStringToBool(value); } /// <summary> /// Parses a string value as the specified type. /// </summary> /// <param name="value">The string value to parse.</param> /// <param name="targetType">The type to parse the value as.</param> /// <returns>The parsed value as an object.</returns> /// <exception cref="ArgumentException">Thrown if the value cannot be parsed as the target type.</exception> public static object Parse(string value, Type targetType) { try { // Path-like types (AbsolutePath/FileInfo/DirectoryInfo) are constructed directly from // 'value' without rooting it here, because callers are expected to pass an already-rooted // (absolute) path. The engine's parameter-binding path roots the string via // TaskEnvironment.GetAbsolutePath before calling Parse, and the TaskItem<T> path derives // 'value' from the item's FullPath metadata (also absolute). Constructing FileInfo/ // DirectoryInfo from a relative string would silently resolve against the current working // directory, so we rely on that rooting invariant rather than re-resolving here. // Special handling for AbsolutePath if (targetType == typeof(AbsolutePath)) { return new AbsolutePath(value); } // Special handling for FileInfo if (targetType == typeof(FileInfo)) { return new FileInfo(value); } // Special handling for DirectoryInfo if (targetType == typeof(DirectoryInfo)) { return new DirectoryInfo(value); } // Special handling for bool - MSBuild supports various boolean representations if (targetType == typeof(bool)) { return ParseBool(value); } // Special handling for string (no parsing needed) if (targetType == typeof(string)) { return value; } // Everything else (numeric types, char, etc.) is converted via Convert.ChangeType, pinned // to InvariantCulture. This is deliberately permissive: a future analyzer is expected to // steer authors toward the first-class supported types above and away from relying on this // general-purpose conversion for parameter types we don't really intend to support. return Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture); } catch (Exception ex) when (ex is FormatException || ex is InvalidCastException || ex is OverflowException || ex is ArgumentException || ex is NotSupportedException) { throw new ArgumentException($"Cannot parse '{value}' as type {targetType.Name}.", nameof(value), ex); } } /// <summary> /// Converts a value to its string representation for use in MSBuild. /// </summary> /// <param name="value">The value to convert.</param> /// <returns>The string representation of the value.</returns> public static string ToString(object? value) { if (value == null) { return string.Empty; } // AbsolutePath needs special handling because Convert.ChangeType doesn't work with implicit operators if (value is AbsolutePath absolutePath) { return absolutePath.Value ?? string.Empty; } // FileInfo and DirectoryInfo need special handling to return their path if (value is FileInfo fileInfo) { return fileInfo.FullName; } if (value is DirectoryInfo directoryInfo) { return directoryInfo.FullName; } // Use InvariantCulture for consistent formatting return Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty; } } }