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);
}