diff --git a/.editorconfig b/.editorconfig index 34450257dc72..9c25e6d6724f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -265,6 +265,8 @@ dotnet_diagnostic.CA5381.severity = warning dotnet_diagnostic.CA5384.severity = warning # Use Rivest–Shamir–Adleman (RSA) Algorithm With Sufficient Key Size dotnet_diagnostic.CA5385.severity = warning +# Parameter has no matching param tag in the XML comment +dotnet_diagnostic.CS1573.severity = suggestion dotnet_diagnostic.CS1591.severity = suggestion # UseIsNullCheck dotnet_diagnostic.IDE0041.severity = warning diff --git a/src/BuiltInTools/Watch/Context/EnvironmentOptions.cs b/src/BuiltInTools/Watch/Context/EnvironmentOptions.cs index 20b850688437..70c121ccf7c1 100644 --- a/src/BuiltInTools/Watch/Context/EnvironmentOptions.cs +++ b/src/BuiltInTools/Watch/Context/EnvironmentOptions.cs @@ -61,9 +61,8 @@ internal sealed record EnvironmentOptions( ); public TimeSpan GetProcessCleanupTimeout(bool isHotReloadEnabled) - // If Hot Reload mode is disabled the process is restarted on every file change. - // Waiting for graceful termination would slow down the turn around. - => ProcessCleanupTimeout ?? (isHotReloadEnabled ? TimeSpan.FromSeconds(5) : TimeSpan.FromSeconds(0)); + // Allow sufficient time for the process to exit gracefully and release resources (e.g., network ports). + => ProcessCleanupTimeout ?? TimeSpan.FromSeconds(5); private int _uniqueLogId; diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/ExternalHelpers.cs b/src/Cli/Microsoft.DotNet.FileBasedPrograms/ExternalHelpers.cs index 6b554d381eed..01df80caf688 100644 --- a/src/Cli/Microsoft.DotNet.FileBasedPrograms/ExternalHelpers.cs +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/ExternalHelpers.cs @@ -40,25 +40,3 @@ public static partial bool IsPathFullyQualified(string path) #endif } - -// https://github.com/dotnet/sdk/issues/51487: Remove usage of GracefulException from the source package -#if FILE_BASED_PROGRAMS_SOURCE_PACKAGE_GRACEFUL_EXCEPTION -internal class GracefulException : Exception -{ - public GracefulException() - { - } - - public GracefulException(string? message) : base(message) - { - } - - public GracefulException(string format, string arg) : this(string.Format(format, arg)) - { - } - - public GracefulException(string? message, Exception? innerException) : base(message, innerException) - { - } -} -#endif diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs index 1157bc586313..7e4ae824e79e 100644 --- a/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs @@ -13,20 +13,14 @@ using System.Text.Json.Serialization; using System.Text.RegularExpressions; using System.Xml; -using Microsoft.Build.Execution; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; - -// https://github.com/dotnet/sdk/issues/51487: Remove usage of GracefulException from the source package -#if !FILE_BASED_PROGRAMS_SOURCE_PACKAGE_GRACEFUL_EXCEPTION -using Microsoft.DotNet.Cli.Utils; -#endif +using Microsoft.DotNet.ProjectTools; namespace Microsoft.DotNet.FileBasedPrograms; -// https://github.com/dotnet/sdk/issues/51487: Use 'file class' where appropriate to reduce exposed internal API surface internal static class FileLevelDirectiveHelpers { public static SyntaxTokenParser CreateTokenizer(SourceText text) @@ -42,7 +36,7 @@ public static SyntaxTokenParser CreateTokenizer(SourceText text) /// The latter is useful for dotnet run file.cs where if there are app directives after the first token, /// compiler reports anyway, so we speed up success scenarios by not parsing the whole file up front in the SDK CLI. /// - public static ImmutableArray FindDirectives(SourceFile sourceFile, bool reportAllErrors, DiagnosticBag diagnostics) + public static ImmutableArray FindDirectives(SourceFile sourceFile, bool reportAllErrors, ErrorReporter reportError) { var builder = ImmutableArray.CreateBuilder(); var tokenizer = CreateTokenizer(sourceFile.Text); @@ -50,7 +44,7 @@ public static ImmutableArray FindDirectives(SourceFile sourceFi var result = tokenizer.ParseLeadingTrivia(); var triviaList = result.Token.LeadingTrivia; - FindLeadingDirectives(sourceFile, triviaList, diagnostics, builder); + FindLeadingDirectives(sourceFile, triviaList, reportError, builder); // In conversion mode, we want to report errors for any invalid directives in the rest of the file // so users don't end up with invalid directives in the converted project. @@ -79,7 +73,7 @@ void ReportErrorFor(SyntaxTrivia trivia) { if (trivia.ContainsDiagnostics && trivia.IsKind(SyntaxKind.IgnoredDirectiveTrivia)) { - diagnostics.AddError(sourceFile, trivia.Span, FileBasedProgramsResources.CannotConvertDirective); + reportError(sourceFile, trivia.Span, FileBasedProgramsResources.CannotConvertDirective); } } @@ -92,7 +86,7 @@ void ReportErrorFor(SyntaxTrivia trivia) public static void FindLeadingDirectives( SourceFile sourceFile, SyntaxTriviaList triviaList, - DiagnosticBag diagnostics, + ErrorReporter reportError, ImmutableArray.Builder? builder) { Debug.Assert(triviaList.Span.Start == 0); @@ -150,7 +144,7 @@ public static void FindLeadingDirectives( LeadingWhiteSpace = whiteSpace.Leading, TrailingWhiteSpace = whiteSpace.Trailing, }, - Diagnostics = diagnostics, + ReportError = reportError, SourceFile = sourceFile, DirectiveKind = name, DirectiveText = value, @@ -159,7 +153,7 @@ public static void FindLeadingDirectives( // Block quotes now so we can later support quoted values without a breaking change. https://github.com/dotnet/sdk/issues/49367 if (value.Contains('"')) { - diagnostics.AddError(sourceFile, context.Info.Span, FileBasedProgramsResources.QuoteInDirective); + reportError(sourceFile, context.Info.Span, FileBasedProgramsResources.QuoteInDirective); } if (CSharpDirective.Parse(context) is { } directive) @@ -169,7 +163,7 @@ public static void FindLeadingDirectives( { var existingDirective = deduplicated[directive]; var typeAndName = $"#:{existingDirective.GetType().Name.ToLowerInvariant()} {existingDirective.Name}"; - diagnostics.AddError(sourceFile, directive.Info.Span, string.Format(FileBasedProgramsResources.DuplicateDirective, typeAndName)); + reportError(sourceFile, directive.Info.Span, string.Format(FileBasedProgramsResources.DuplicateDirective, typeAndName)); } else { @@ -227,30 +221,6 @@ static bool Fill(ref WhiteSpaceInfo info, in SyntaxTriviaList triviaList, int in } } } - - /// - /// If there are any #:project , expands $() in them and ensures they point to project files (not directories). - /// - public static ImmutableArray EvaluateDirectives( - ProjectInstance? project, - ImmutableArray directives, - SourceFile sourceFile, - DiagnosticBag diagnostics) - { - if (directives.OfType().Any()) - { - return directives - .Select(d => d is CSharpDirective.Project p - ? (project is null - ? p - : p.WithName(project.ExpandString(p.Name), CSharpDirective.Project.NameKind.Expanded)) - .EnsureProjectFilePath(sourceFile, diagnostics) - : d) - .ToImmutableArray(); - } - - return directives; - } } internal readonly record struct SourceFile(string Path, SourceText Text) @@ -321,7 +291,7 @@ public readonly struct ParseInfo public readonly struct ParseContext { public required ParseInfo Info { get; init; } - public required DiagnosticBag Diagnostics { get; init; } + public required ErrorReporter ReportError { get; init; } public required SourceFile SourceFile { get; init; } public required string DirectiveKind { get; init; } public required string DirectiveText { get; init; } @@ -329,13 +299,15 @@ public readonly struct ParseContext public static Named? Parse(in ParseContext context) { - return context.DirectiveKind switch + switch (context.DirectiveKind) { - "sdk" => Sdk.Parse(context), - "property" => Property.Parse(context), - "package" => Package.Parse(context), - "project" => Project.Parse(context), - var other => context.Diagnostics.AddError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.UnrecognizedDirective, other)), + case "sdk": return Sdk.Parse(context); + case "property": return Property.Parse(context); + case "package": return Package.Parse(context); + case "project": return Project.Parse(context); + default: + context.ReportError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.UnrecognizedDirective, context.DirectiveKind)); + return null; }; } @@ -347,13 +319,15 @@ private static (string, string?)? ParseOptionalTwoParts(in ParseContext context, string directiveKind = context.DirectiveKind; if (firstPart.IsWhiteSpace()) { - return context.Diagnostics.AddError<(string, string?)?>(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.MissingDirectiveName, directiveKind)); + context.ReportError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.MissingDirectiveName, directiveKind)); + return null; } // If the name contains characters that resemble separators, report an error to avoid any confusion. if (Patterns.DisallowedNameCharacters.Match(context.DirectiveText, beginning: 0, length: firstPart.Length).Success) { - return context.Diagnostics.AddError<(string, string?)?>(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.InvalidDirectiveName, directiveKind, separator)); + context.ReportError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.InvalidDirectiveName, directiveKind, separator)); + return null; } if (separatorIndex < 0) @@ -428,7 +402,8 @@ public sealed class Property(in ParseInfo info) : Named(info) if (propertyValue is null) { - return context.Diagnostics.AddError(context.SourceFile, context.Info.Span, FileBasedProgramsResources.PropertyDirectiveMissingParts); + context.ReportError(context.SourceFile, context.Info.Span, FileBasedProgramsResources.PropertyDirectiveMissingParts); + return null; } try @@ -437,13 +412,14 @@ public sealed class Property(in ParseInfo info) : Named(info) } catch (XmlException ex) { - return context.Diagnostics.AddError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.PropertyDirectiveInvalidName, ex.Message), ex); + context.ReportError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.PropertyDirectiveInvalidName, ex.Message)); + return null; } if (propertyName.Equals("RestoreUseStaticGraphEvaluation", StringComparison.OrdinalIgnoreCase) && MSBuildUtilities.ConvertStringToBool(propertyValue)) { - context.Diagnostics.AddError(context.SourceFile, context.Info.Span, FileBasedProgramsResources.StaticGraphRestoreNotSupported); + context.ReportError(context.SourceFile, context.Info.Span, FileBasedProgramsResources.StaticGraphRestoreNotSupported); } return new Property(context.Info) @@ -499,7 +475,8 @@ public Project(in ParseInfo info, string name) : base(info) public string OriginalName { get; init; } /// - /// This is the with MSBuild $(..) vars expanded (via ). + /// This is the with MSBuild $(..) vars expanded. + /// E.g. The expansion might be implemented via ProjectInstance.ExpandString. /// public string? ExpandedName { get; init; } @@ -515,7 +492,8 @@ public Project(in ParseInfo info, string name) : base(info) if (directiveText.IsWhiteSpace()) { string directiveKind = context.DirectiveKind; - return context.Diagnostics.AddError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.MissingDirectiveName, directiveKind)); + context.ReportError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.MissingDirectiveName, directiveKind)); + return null; } return new Project(context.Info, directiveText); @@ -552,73 +530,39 @@ public Project WithName(string name, NameKind kind) /// /// If the directive points to a directory, returns a new directive pointing to the corresponding project file. /// - public Project EnsureProjectFilePath(SourceFile sourceFile, DiagnosticBag diagnostics) + public Project EnsureProjectFilePath(SourceFile sourceFile, ErrorReporter reportError) { var resolvedName = Name; - try + // If the path is a directory like '../lib', transform it to a project file path like '../lib/lib.csproj'. + // Also normalize backslashes to forward slashes to ensure the directive works on all platforms. + var sourceDirectory = Path.GetDirectoryName(sourceFile.Path) + ?? throw new InvalidOperationException($"Source file path '{sourceFile.Path}' does not have a containing directory."); + + var resolvedProjectPath = Path.Combine(sourceDirectory, resolvedName.Replace('\\', '/')); + if (Directory.Exists(resolvedProjectPath)) { - // If the path is a directory like '../lib', transform it to a project file path like '../lib/lib.csproj'. - // Also normalize backslashes to forward slashes to ensure the directive works on all platforms. - var sourceDirectory = Path.GetDirectoryName(sourceFile.Path) - ?? throw new InvalidOperationException($"Source file path '{sourceFile.Path}' does not have a containing directory."); - var resolvedProjectPath = Path.Combine(sourceDirectory, resolvedName.Replace('\\', '/')); - if (Directory.Exists(resolvedProjectPath)) + if (ProjectLocator.TryGetProjectFileFromDirectory(resolvedProjectPath, out var projectFilePath, out var error)) { - var fullFilePath = GetProjectFileFromDirectory(resolvedProjectPath).FullName; - // Keep a relative path only if the original directive was a relative path. resolvedName = ExternalHelpers.IsPathFullyQualified(resolvedName) - ? fullFilePath - : ExternalHelpers.GetRelativePath(relativeTo: sourceDirectory, fullFilePath); + ? projectFilePath + : ExternalHelpers.GetRelativePath(relativeTo: sourceDirectory, projectFilePath); } - else if (!File.Exists(resolvedProjectPath)) + else { - throw new GracefulException(FileBasedProgramsResources.CouldNotFindProjectOrDirectory, resolvedProjectPath); + reportError(sourceFile, Info.Span, string.Format(FileBasedProgramsResources.InvalidProjectDirective, error)); } } - catch (GracefulException e) + else if (!File.Exists(resolvedProjectPath)) { - diagnostics.AddError(sourceFile, Info.Span, string.Format(FileBasedProgramsResources.InvalidProjectDirective, e.Message), e); + reportError(sourceFile, Info.Span, + string.Format(FileBasedProgramsResources.InvalidProjectDirective, string.Format(FileBasedProgramsResources.CouldNotFindProjectOrDirectory, resolvedProjectPath))); } return WithName(resolvedName, NameKind.ProjectFilePath); } - // https://github.com/dotnet/sdk/issues/51487: Delete copies of methods from MsbuildProject and MSBuildUtilities from the source package, sharing the original method(s) under src/Cli instead. - private static FileInfo GetProjectFileFromDirectory(string projectDirectory) - { - DirectoryInfo dir; - try - { - dir = new DirectoryInfo(projectDirectory); - } - catch (ArgumentException) - { - throw new GracefulException(FileBasedProgramsResources.CouldNotFindProjectOrDirectory, projectDirectory); - } - - if (!dir.Exists) - { - throw new GracefulException(FileBasedProgramsResources.CouldNotFindProjectOrDirectory, projectDirectory); - } - - FileInfo[] files = dir.GetFiles("*proj"); - if (files.Length == 0) - { - throw new GracefulException( - FileBasedProgramsResources.CouldNotFindAnyProjectInDirectory, - projectDirectory); - } - - if (files.Length > 1) - { - throw new GracefulException(FileBasedProgramsResources.MoreThanOneProjectInDirectory, projectDirectory); - } - - return files.First(); - } - public override string ToString() => $"#:project {Name}"; } } @@ -671,35 +615,27 @@ public readonly struct Position } } -internal readonly struct DiagnosticBag -{ - public bool IgnoreDiagnostics { get; private init; } - - /// - /// If and is , the first diagnostic is thrown as . - /// - public ImmutableArray.Builder? Builder { get; private init; } +internal delegate void ErrorReporter(SourceFile sourceFile, TextSpan textSpan, string message); - public static DiagnosticBag ThrowOnFirst() => default; - public static DiagnosticBag Collect(out ImmutableArray.Builder builder) => new() { Builder = builder = ImmutableArray.CreateBuilder() }; - public static DiagnosticBag Ignore() => new() { IgnoreDiagnostics = true, Builder = null }; +internal static partial class ErrorReporters +{ + public static readonly ErrorReporter IgnoringReporter = + static (_, _, _) => { }; - public void AddError(SourceFile sourceFile, TextSpan textSpan, string message, Exception? inner = null) + public static ErrorReporter CreateCollectingReporter(out ImmutableArray.Builder builder) { - if (Builder != null) - { - Debug.Assert(!IgnoreDiagnostics); - Builder.Add(new SimpleDiagnostic { Location = new SimpleDiagnostic.Position() { Path = sourceFile.Path, TextSpan = textSpan, Span = sourceFile.GetFileLinePositionSpan(textSpan).Span }, Message = message }); - } - else if (!IgnoreDiagnostics) - { - throw new GracefulException($"{sourceFile.GetLocationString(textSpan)}: {FileBasedProgramsResources.DirectiveError}: {message}", inner); - } - } + var capturedBuilder = builder = ImmutableArray.CreateBuilder(); - public T? AddError(SourceFile sourceFile, TextSpan span, string message, Exception? inner = null) - { - AddError(sourceFile, span, message, inner); - return default; + return (sourceFile, textSpan, message) => + capturedBuilder.Add(new SimpleDiagnostic + { + Location = new SimpleDiagnostic.Position() + { + Path = sourceFile.Path, + TextSpan = textSpan, + Span = sourceFile.GetFileLinePositionSpan(textSpan).Span + }, + Message = message + }); } } diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt b/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt index fad31237acf9..9376a191aa0c 100644 --- a/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt @@ -10,8 +10,6 @@ Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Package.Package(in Microsoft. Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Package.Version.get -> string? Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Package.Version.init -> void Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext -Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.Diagnostics.get -> Microsoft.DotNet.FileBasedPrograms.DiagnosticBag -Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.Diagnostics.init -> void Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.DirectiveKind.get -> string! Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.DirectiveKind.init -> void Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.DirectiveText.get -> string! @@ -19,6 +17,8 @@ Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.DirectiveText.in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.Info.get -> Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.Info.init -> void Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.ParseContext() -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.ReportError.get -> Microsoft.DotNet.FileBasedPrograms.ErrorReporter! +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.ReportError.init -> void Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.SourceFile.get -> Microsoft.DotNet.FileBasedPrograms.SourceFile Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.SourceFile.init -> void Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo @@ -30,7 +30,7 @@ Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo.Span.init -> void Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo.TrailingWhiteSpace.get -> Microsoft.DotNet.FileBasedPrograms.WhiteSpaceInfo Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo.TrailingWhiteSpace.init -> void Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project -Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project.EnsureProjectFilePath(Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, Microsoft.DotNet.FileBasedPrograms.DiagnosticBag diagnostics) -> Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project! +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project.EnsureProjectFilePath(Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, Microsoft.DotNet.FileBasedPrograms.ErrorReporter! reportError) -> Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project! Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project.ExpandedName.get -> string? Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project.ExpandedName.init -> void Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project.NameKind @@ -53,20 +53,11 @@ Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Sdk.Version.get -> string? Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Sdk.Version.init -> void Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Shebang Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Shebang.Shebang(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo info) -> void -Microsoft.DotNet.FileBasedPrograms.DiagnosticBag -Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.AddError(Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, Microsoft.CodeAnalysis.Text.TextSpan textSpan, string! message, System.Exception? inner = null) -> void -Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.AddError(Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, Microsoft.CodeAnalysis.Text.TextSpan span, string! message, System.Exception? inner = null) -> T? -Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.Builder.get -> System.Collections.Immutable.ImmutableArray.Builder? -Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.DiagnosticBag() -> void -Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.IgnoreDiagnostics.get -> bool +Microsoft.DotNet.FileBasedPrograms.ErrorReporter +Microsoft.DotNet.FileBasedPrograms.ErrorReporters Microsoft.DotNet.FileBasedPrograms.ExternalHelpers Microsoft.DotNet.FileBasedPrograms.ExternalHelpers.ExternalHelpers() -> void Microsoft.DotNet.FileBasedPrograms.FileLevelDirectiveHelpers -Microsoft.DotNet.FileBasedPrograms.GracefulException -Microsoft.DotNet.FileBasedPrograms.GracefulException.GracefulException() -> void -Microsoft.DotNet.FileBasedPrograms.GracefulException.GracefulException(string! format, string! arg) -> void -Microsoft.DotNet.FileBasedPrograms.GracefulException.GracefulException(string? message) -> void -Microsoft.DotNet.FileBasedPrograms.GracefulException.GracefulException(string? message, System.Exception? innerException) -> void Microsoft.DotNet.FileBasedPrograms.MSBuildUtilities Microsoft.DotNet.FileBasedPrograms.MSBuildUtilities.MSBuildUtilities() -> void Microsoft.DotNet.FileBasedPrograms.NamedDirectiveComparer @@ -104,6 +95,7 @@ Microsoft.DotNet.FileBasedPrograms.WhiteSpaceInfo Microsoft.DotNet.FileBasedPrograms.WhiteSpaceInfo.LineBreaks -> int Microsoft.DotNet.FileBasedPrograms.WhiteSpaceInfo.TotalLength -> int Microsoft.DotNet.FileBasedPrograms.WhiteSpaceInfo.WhiteSpaceInfo() -> void +Microsoft.DotNet.ProjectTools.ProjectLocator override abstract Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ToString() -> string! override Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Package.ToString() -> string! override Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project.ToString() -> string! @@ -116,16 +108,14 @@ static Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Parse(in Microsoft.Dot static Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project.Parse(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext context) -> Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project? static Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Property.Parse(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext context) -> Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Property? static Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Sdk.Parse(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext context) -> Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Sdk? -static Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.Collect(out System.Collections.Immutable.ImmutableArray.Builder! builder) -> Microsoft.DotNet.FileBasedPrograms.DiagnosticBag -static Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.Ignore() -> Microsoft.DotNet.FileBasedPrograms.DiagnosticBag -static Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.ThrowOnFirst() -> Microsoft.DotNet.FileBasedPrograms.DiagnosticBag +static Microsoft.DotNet.FileBasedPrograms.ErrorReporters.CreateCollectingReporter(out System.Collections.Immutable.ImmutableArray.Builder! builder) -> Microsoft.DotNet.FileBasedPrograms.ErrorReporter! static Microsoft.DotNet.FileBasedPrograms.ExternalHelpers.CombineHashCodes(int value1, int value2) -> int static Microsoft.DotNet.FileBasedPrograms.ExternalHelpers.GetRelativePath(string! relativeTo, string! path) -> string! static Microsoft.DotNet.FileBasedPrograms.ExternalHelpers.IsPathFullyQualified(string! path) -> bool static Microsoft.DotNet.FileBasedPrograms.FileLevelDirectiveHelpers.CreateTokenizer(Microsoft.CodeAnalysis.Text.SourceText! text) -> Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser! -static Microsoft.DotNet.FileBasedPrograms.FileLevelDirectiveHelpers.EvaluateDirectives(Microsoft.Build.Execution.ProjectInstance? project, System.Collections.Immutable.ImmutableArray directives, Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, Microsoft.DotNet.FileBasedPrograms.DiagnosticBag diagnostics) -> System.Collections.Immutable.ImmutableArray -static Microsoft.DotNet.FileBasedPrograms.FileLevelDirectiveHelpers.FindDirectives(Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, bool reportAllErrors, Microsoft.DotNet.FileBasedPrograms.DiagnosticBag diagnostics) -> System.Collections.Immutable.ImmutableArray -static Microsoft.DotNet.FileBasedPrograms.FileLevelDirectiveHelpers.FindLeadingDirectives(Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, Microsoft.CodeAnalysis.SyntaxTriviaList triviaList, Microsoft.DotNet.FileBasedPrograms.DiagnosticBag diagnostics, System.Collections.Immutable.ImmutableArray.Builder? builder) -> void +static Microsoft.DotNet.FileBasedPrograms.FileLevelDirectiveHelpers.EvaluateDirectives(Microsoft.Build.Execution.ProjectInstance? project, System.Collections.Immutable.ImmutableArray directives, Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, Microsoft.DotNet.FileBasedPrograms.ErrorReporter! errorReporter) -> System.Collections.Immutable.ImmutableArray +static Microsoft.DotNet.FileBasedPrograms.FileLevelDirectiveHelpers.FindDirectives(Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, bool reportAllErrors, Microsoft.DotNet.FileBasedPrograms.ErrorReporter! reportError) -> System.Collections.Immutable.ImmutableArray +static Microsoft.DotNet.FileBasedPrograms.FileLevelDirectiveHelpers.FindLeadingDirectives(Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, Microsoft.CodeAnalysis.SyntaxTriviaList triviaList, Microsoft.DotNet.FileBasedPrograms.ErrorReporter! reportError, System.Collections.Immutable.ImmutableArray.Builder? builder) -> void static Microsoft.DotNet.FileBasedPrograms.MSBuildUtilities.ConvertStringToBool(string? parameterValue, bool defaultValue = false) -> bool static Microsoft.DotNet.FileBasedPrograms.Patterns.DisallowedNameCharacters.get -> System.Text.RegularExpressions.Regex! static Microsoft.DotNet.FileBasedPrograms.Patterns.EscapedCompilerOption.get -> System.Text.RegularExpressions.Regex! @@ -133,6 +123,9 @@ static Microsoft.DotNet.FileBasedPrograms.Patterns.Whitespace.get -> System.Text static Microsoft.DotNet.FileBasedPrograms.SourceFile.Load(string! filePath) -> Microsoft.DotNet.FileBasedPrograms.SourceFile static Microsoft.DotNet.FileBasedPrograms.SourceFile.operator !=(Microsoft.DotNet.FileBasedPrograms.SourceFile left, Microsoft.DotNet.FileBasedPrograms.SourceFile right) -> bool static Microsoft.DotNet.FileBasedPrograms.SourceFile.operator ==(Microsoft.DotNet.FileBasedPrograms.SourceFile left, Microsoft.DotNet.FileBasedPrograms.SourceFile right) -> bool +static Microsoft.DotNet.ProjectTools.ProjectLocator.TryGetProjectFileFromDirectory(string! projectDirectory, out string? projectFilePath, out string? error) -> bool +static readonly Microsoft.DotNet.FileBasedPrograms.ErrorReporters.IgnoringReporter -> Microsoft.DotNet.FileBasedPrograms.ErrorReporter! static readonly Microsoft.DotNet.FileBasedPrograms.NamedDirectiveComparer.Instance -> Microsoft.DotNet.FileBasedPrograms.NamedDirectiveComparer! +virtual Microsoft.DotNet.FileBasedPrograms.ErrorReporter.Invoke(Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, Microsoft.CodeAnalysis.Text.TextSpan textSpan, string! message) -> void ~override Microsoft.DotNet.FileBasedPrograms.SourceFile.Equals(object obj) -> bool ~override Microsoft.DotNet.FileBasedPrograms.SourceFile.ToString() -> string \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/Microsoft.DotNet.FileBasedPrograms.Package.csproj b/src/Cli/Microsoft.DotNet.FileBasedPrograms/Microsoft.DotNet.FileBasedPrograms.Package.csproj index c0578c734abe..d93aa8468d3c 100644 --- a/src/Cli/Microsoft.DotNet.FileBasedPrograms/Microsoft.DotNet.FileBasedPrograms.Package.csproj +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/Microsoft.DotNet.FileBasedPrograms.Package.csproj @@ -20,13 +20,12 @@ $(NoWarn);NU5128 false - $(DefineConstants);FILE_BASED_PROGRAMS_SOURCE_PACKAGE_BUILD;FILE_BASED_PROGRAMS_SOURCE_PACKAGE_GRACEFUL_EXCEPTION + $(DefineConstants);FILE_BASED_PROGRAMS_SOURCE_PACKAGE_BUILD disable - diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/ProjectLocator.cs b/src/Cli/Microsoft.DotNet.FileBasedPrograms/ProjectLocator.cs new file mode 100644 index 000000000000..22817f3f6790 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/ProjectLocator.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using Microsoft.DotNet.FileBasedPrograms; + +namespace Microsoft.DotNet.ProjectTools; + +internal static class ProjectLocator +{ + public static bool TryGetProjectFileFromDirectory(string projectDirectory, [NotNullWhen(true)] out string? projectFilePath, [NotNullWhen(false)] out string? error) + { + projectFilePath = null; + error = null; + + DirectoryInfo? dir; + try + { + dir = new DirectoryInfo(projectDirectory); + } + catch (ArgumentException) + { + dir = null; + } + + if (dir == null || !dir.Exists) + { + error = string.Format(FileBasedProgramsResources.CouldNotFindProjectOrDirectory, projectDirectory); + return false; + } + + FileInfo[] files = dir.GetFiles("*proj"); + if (files.Length == 0) + { + error = string.Format(FileBasedProgramsResources.CouldNotFindAnyProjectInDirectory, projectDirectory); + return false; + } + + if (files.Length > 1) + { + error = string.Format(FileBasedProgramsResources.MoreThanOneProjectInDirectory, projectDirectory); + return false; + } + + projectFilePath = files.First().FullName; + return true; + } +} diff --git a/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs b/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs index a463165828cb..544cd3f0fda2 100644 --- a/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs +++ b/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs @@ -35,7 +35,7 @@ public override int Execute() string projectFilePath; if (!File.Exists(fileOrDirectory)) { - projectFilePath = MsbuildProject.GetProjectFileFromDirectory(fileOrDirectory).FullName; + projectFilePath = MsbuildProject.GetProjectFileFromDirectory(fileOrDirectory); } else { diff --git a/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs b/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs index e90099382273..7900ee01f2da 100644 --- a/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs +++ b/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs @@ -34,7 +34,7 @@ public override int Execute() string projectFilePath; if (!File.Exists(fileOrDirectory)) { - projectFilePath = MsbuildProject.GetProjectFileFromDirectory(fileOrDirectory).FullName; + projectFilePath = MsbuildProject.GetProjectFileFromDirectory(fileOrDirectory); } else { diff --git a/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs b/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs index 6acf2b7cc63b..9b7ff8d4b20a 100644 --- a/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs +++ b/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs @@ -32,8 +32,7 @@ public override int Execute() // Find directives (this can fail, so do this before creating the target directory). var sourceFile = SourceFile.Load(file); - var diagnostics = DiagnosticBag.ThrowOnFirst(); - var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: !_force, diagnostics); + var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: !_force, VirtualProjectBuildingCommand.ThrowingReporter); // Create a project instance for evaluation. var projectCollection = new ProjectCollection(); @@ -46,7 +45,7 @@ public override int Execute() var projectInstance = command.CreateProjectInstance(projectCollection); // Evaluate directives. - directives = FileLevelDirectiveHelpers.EvaluateDirectives(projectInstance, directives, sourceFile, diagnostics); + directives = VirtualProjectBuildingCommand.EvaluateDirectives(projectInstance, directives, sourceFile, VirtualProjectBuildingCommand.ThrowingReporter); command.Directives = directives; projectInstance = command.CreateProjectInstance(projectCollection); diff --git a/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs b/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs index 3a71f6b761ac..a6a3c6389c56 100644 --- a/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs +++ b/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs @@ -43,7 +43,7 @@ public override int Execute() return Path.GetRelativePath( msbuildProj.ProjectRootElement.FullPath, - MsbuildProject.GetProjectFileFromDirectory(fullPath).FullName + MsbuildProject.GetProjectFileFromDirectory(fullPath) ); }); diff --git a/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs b/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs index db9e327a1206..f1b564132bab 100644 --- a/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs +++ b/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs @@ -65,7 +65,7 @@ public sealed class GetProject : RunApiInput public override RunApiOutput Execute() { var sourceFile = SourceFile.Load(EntryPointFileFullPath); - var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: true, DiagnosticBag.Collect(out var diagnostics)); + var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: true, ErrorReporters.CreateCollectingReporter(out var diagnostics)); string artifactsPath = ArtifactsPath ?? VirtualProjectBuildingCommand.GetArtifactsPath(EntryPointFileFullPath); var csprojWriter = new StringWriter(); diff --git a/src/Cli/dotnet/Commands/Run/FileBasedAppSourceEditor.cs b/src/Cli/dotnet/Commands/Run/FileBasedAppSourceEditor.cs index 0422f389a824..27ac23b56687 100644 --- a/src/Cli/dotnet/Commands/Run/FileBasedAppSourceEditor.cs +++ b/src/Cli/dotnet/Commands/Run/FileBasedAppSourceEditor.cs @@ -34,7 +34,7 @@ public ImmutableArray Directives { if (field.IsDefault) { - field = FileLevelDirectiveHelpers.FindDirectives(SourceFile, reportAllErrors: false, DiagnosticBag.Ignore()); + field = FileLevelDirectiveHelpers.FindDirectives(SourceFile, reportAllErrors: false, ErrorReporters.IgnoringReporter); Debug.Assert(!field.IsDefault); } diff --git a/src/Cli/dotnet/Commands/Run/RunCommand.cs b/src/Cli/dotnet/Commands/Run/RunCommand.cs index ba5801ff9b8c..8207f0abefa5 100644 --- a/src/Cli/dotnet/Commands/Run/RunCommand.cs +++ b/src/Cli/dotnet/Commands/Run/RunCommand.cs @@ -350,7 +350,7 @@ private bool TrySelectTargetFrameworkForFileBasedProject() private static string[]? GetTargetFrameworksFromSourceFile(string sourceFilePath) { var sourceFile = SourceFile.Load(sourceFilePath); - var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: false, DiagnosticBag.Ignore()); + var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: false, ErrorReporters.IgnoringReporter); var targetFrameworksDirective = directives.OfType() .FirstOrDefault(p => string.Equals(p.Name, "TargetFrameworks", StringComparison.OrdinalIgnoreCase)); diff --git a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs index 2d6ae3835550..7721153da981 100644 --- a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs +++ b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs @@ -180,7 +180,7 @@ public ImmutableArray Directives { if (field.IsDefault) { - field = FileLevelDirectiveHelpers.FindDirectives(EntryPointSourceFile, reportAllErrors: false, DiagnosticBag.ThrowOnFirst()); + field = FileLevelDirectiveHelpers.FindDirectives(EntryPointSourceFile, reportAllErrors: false, VirtualProjectBuildingCommand.ThrowingReporter); Debug.Assert(!field.IsDefault); } @@ -1050,6 +1050,30 @@ private void MarkBuildSuccess(CacheInfo cache) JsonSerializer.Serialize(stream, cache.CurrentEntry, RunFileJsonSerializerContext.Default.RunFileBuildCacheEntry); } + /// + /// If there are any #:project , expands $() in them and ensures they point to project files (not directories). + /// + public static ImmutableArray EvaluateDirectives( + ProjectInstance? project, + ImmutableArray directives, + SourceFile sourceFile, + ErrorReporter errorReporter) + { + if (directives.OfType().Any()) + { + return directives + .Select(d => d is CSharpDirective.Project p + ? (project is null + ? p + : p.WithName(project.ExpandString(p.Name), CSharpDirective.Project.NameKind.Expanded)) + .EnsureProjectFilePath(sourceFile, errorReporter) + : d) + .ToImmutableArray(); + } + + return directives; + } + public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection) { return CreateProjectInstance(projectCollection, addGlobalProperties: null); @@ -1061,7 +1085,7 @@ private ProjectInstance CreateProjectInstance( { var project = CreateProjectInstance(projectCollection, Directives, addGlobalProperties); - var directives = FileLevelDirectiveHelpers.EvaluateDirectives(project, Directives, EntryPointSourceFile, DiagnosticBag.ThrowOnFirst()); + var directives = EvaluateDirectives(project, Directives, EntryPointSourceFile, VirtualProjectBuildingCommand.ThrowingReporter); if (directives != Directives) { Directives = directives; @@ -1529,6 +1553,9 @@ public static bool IsValidEntryPointPath(string entryPointFilePath) return false; } } + + public static readonly ErrorReporter ThrowingReporter = + static (sourceFile, textSpan, message) => throw new GracefulException($"{sourceFile.GetLocationString(textSpan)}: {FileBasedProgramsResources.DirectiveError}: {message}"); } internal sealed class RunFileBuildCacheEntry diff --git a/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs b/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs index 6e86dea43214..4d88d85262e9 100644 --- a/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs +++ b/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs @@ -61,7 +61,7 @@ public override int Execute() IEnumerable fullProjectPaths = _projects.Select(project => { var fullPath = Path.GetFullPath(project); - return Directory.Exists(fullPath) ? MsbuildProject.GetProjectFileFromDirectory(fullPath).FullName : fullPath; + return Directory.Exists(fullPath) ? MsbuildProject.GetProjectFileFromDirectory(fullPath) : fullPath; }); // Add projects to the solution diff --git a/src/Cli/dotnet/Commands/Solution/Remove/SolutionRemoveCommand.cs b/src/Cli/dotnet/Commands/Solution/Remove/SolutionRemoveCommand.cs index 36030bd22621..b80bee5609ef 100644 --- a/src/Cli/dotnet/Commands/Solution/Remove/SolutionRemoveCommand.cs +++ b/src/Cli/dotnet/Commands/Solution/Remove/SolutionRemoveCommand.cs @@ -40,7 +40,7 @@ public override int Execute() .Select(p => Path.GetRelativePath( Path.GetDirectoryName(solutionFileFullPath), Directory.Exists(p) - ? MsbuildProject.GetProjectFileFromDirectory(p).FullName + ? MsbuildProject.GetProjectFileFromDirectory(p) : p)); RemoveProjectsAsync(solutionFileFullPath, relativeProjectPaths, CancellationToken.None).GetAwaiter().GetResult(); diff --git a/src/Cli/dotnet/MsbuildProject.cs b/src/Cli/dotnet/MsbuildProject.cs index d06880e00e76..bbb39ebaf2b8 100644 --- a/src/Cli/dotnet/MsbuildProject.cs +++ b/src/Cli/dotnet/MsbuildProject.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Diagnostics.CodeAnalysis; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Exceptions; @@ -11,6 +12,7 @@ using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils.Extensions; +using Microsoft.DotNet.ProjectTools; using NuGet.Frameworks; namespace Microsoft.DotNet.Cli; @@ -66,49 +68,24 @@ public static MsbuildProject FromFile(ProjectCollection projects, string project public static MsbuildProject FromDirectory(ProjectCollection projects, string projectDirectory, bool interactive) { - FileInfo projectFile = GetProjectFileFromDirectory(projectDirectory); + var projectFilePath = GetProjectFileFromDirectory(projectDirectory); - var project = TryOpenProject(projects, projectFile.FullName); + var project = TryOpenProject(projects, projectFilePath); if (project == null) { - throw new GracefulException(CliStrings.FoundInvalidProject, projectFile.FullName); + throw new GracefulException(CliStrings.FoundInvalidProject, projectFilePath); } return new MsbuildProject(projects, project, interactive); } - public static FileInfo GetProjectFileFromDirectory(string projectDirectory) - { - DirectoryInfo dir; - try - { - dir = new DirectoryInfo(projectDirectory); - } - catch (ArgumentException) - { - throw new GracefulException(CliStrings.CouldNotFindProjectOrDirectory, projectDirectory); - } - - if (!dir.Exists) - { - throw new GracefulException(CliStrings.CouldNotFindProjectOrDirectory, projectDirectory); - } - - FileInfo[] files = dir.GetFiles("*proj"); - if (files.Length == 0) - { - throw new GracefulException( - CliStrings.CouldNotFindAnyProjectInDirectory, - projectDirectory); - } + public static string GetProjectFileFromDirectory(string projectDirectory) + => ProjectLocator.TryGetProjectFileFromDirectory(projectDirectory, out var projectFilePath, out var error) + ? projectFilePath + : throw new GracefulException(error); - if (files.Length > 1) - { - throw new GracefulException(CliStrings.MoreThanOneProjectInDirectory, projectDirectory); - } - - return files.First(); - } + public static bool TryGetProjectFileFromDirectory(string projectDirectory, [NotNullWhen(true)] out string projectFilePath) + => ProjectLocator.TryGetProjectFileFromDirectory(projectDirectory, out projectFilePath, out _); public int AddProjectToProjectReferences(string framework, IEnumerable refs) { diff --git a/src/Cli/dotnet/ReleasePropertyProjectLocator.cs b/src/Cli/dotnet/ReleasePropertyProjectLocator.cs index e85fb9878d4c..2f39135d0cf2 100644 --- a/src/Cli/dotnet/ReleasePropertyProjectLocator.cs +++ b/src/Cli/dotnet/ReleasePropertyProjectLocator.cs @@ -125,18 +125,18 @@ DependentCommandOptions commandOptions } else if (Directory.Exists(arg)) // Get here if the user did not provide a .proj or a .sln. (See CWD appended to args above) { - try // First, look for a project in the directory. + // First, look for a project in the directory. + if (MsbuildProject.TryGetProjectFileFromDirectory(arg, out var projectFilePath)) { - return TryGetProjectInstance(MsbuildProject.GetProjectFileFromDirectory(arg).FullName, globalProps); + return TryGetProjectInstance(projectFilePath, globalProps); } - catch (GracefulException) // Fall back to looking for a solution if multiple project files are found, or there's no project in the directory. - { - string? potentialSln = SlnFileFactory.ListSolutionFilesInDirectory(arg, false).FirstOrDefault(); - if (!string.IsNullOrEmpty(potentialSln)) - { - return GetArbitraryProjectFromSolution(potentialSln, globalProps); - } + // Fall back to looking for a solution if multiple project files are found, or there's no project in the directory. + string? potentialSln = SlnFileFactory.ListSolutionFilesInDirectory(arg, false).FirstOrDefault(); + + if (!string.IsNullOrEmpty(potentialSln)) + { + return GetArbitraryProjectFromSolution(potentialSln, globalProps); } } } diff --git a/src/Cli/dotnet/dotnet.csproj b/src/Cli/dotnet/dotnet.csproj index 8a9a238dfab0..390d2c7a1e7b 100644 --- a/src/Cli/dotnet/dotnet.csproj +++ b/src/Cli/dotnet/dotnet.csproj @@ -69,7 +69,6 @@ - @@ -105,6 +104,4 @@ - - diff --git a/src/Microsoft.DotNet.ProjectTools/Microsoft.DotNet.ProjectTools.csproj b/src/Microsoft.DotNet.ProjectTools/Microsoft.DotNet.ProjectTools.csproj index 5dd909306acd..398ea7eecdab 100644 --- a/src/Microsoft.DotNet.ProjectTools/Microsoft.DotNet.ProjectTools.csproj +++ b/src/Microsoft.DotNet.ProjectTools/Microsoft.DotNet.ProjectTools.csproj @@ -4,9 +4,22 @@ enable true enable + MicrosoftAspNetCore + + + + + + + + + + + + diff --git a/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs b/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs index 14beaed209a7..3dd157806387 100644 --- a/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs +++ b/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs @@ -1696,9 +1696,9 @@ private static void Convert(string inputCSharp, out string actualProject, out st { var sourceFile = new SourceFile(filePath ?? programPath, SourceText.From(inputCSharp, Encoding.UTF8)); actualDiagnostics = null; - var diagnosticBag = collectDiagnostics ? DiagnosticBag.Collect(out actualDiagnostics) : DiagnosticBag.ThrowOnFirst(); + var diagnosticBag = collectDiagnostics ? ErrorReporters.CreateCollectingReporter(out actualDiagnostics) : VirtualProjectBuildingCommand.ThrowingReporter; var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: !force, diagnosticBag); - directives = FileLevelDirectiveHelpers.EvaluateDirectives(project: null, directives, sourceFile, diagnosticBag); + directives = VirtualProjectBuildingCommand.EvaluateDirectives(project: null, directives, sourceFile, diagnosticBag); var projectWriter = new StringWriter(); VirtualProjectBuildingCommand.WriteProjectFile(projectWriter, directives, isVirtualProject: false); actualProject = projectWriter.ToString(); @@ -1727,7 +1727,7 @@ private static void VerifyConversionThrows(string inputCSharp, string expectedWi private static void VerifyDirectiveConversionErrors(string inputCSharp, IEnumerable<(int LineNumber, string Message)> expectedErrors) { var sourceFile = new SourceFile(programPath, SourceText.From(inputCSharp, Encoding.UTF8)); - FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: true, DiagnosticBag.Collect(out var diagnostics)); + FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: true, ErrorReporters.CreateCollectingReporter(out var diagnostics)); VerifyErrors(diagnostics, expectedErrors); }