diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs
index 681843c6cbc8..3ccfbc535f8f 100644
--- a/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs
+++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs
@@ -36,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, ErrorReporter reportError)
+ public static ImmutableArray FindDirectives(SourceFile sourceFile, bool reportAllErrors, ErrorReporter errorReporter)
{
var builder = ImmutableArray.CreateBuilder();
var tokenizer = CreateTokenizer(sourceFile.Text);
@@ -44,7 +44,7 @@ public static ImmutableArray FindDirectives(SourceFile sourceFi
var result = tokenizer.ParseLeadingTrivia();
var triviaList = result.Token.LeadingTrivia;
- FindLeadingDirectives(sourceFile, triviaList, reportError, builder);
+ FindLeadingDirectives(sourceFile, triviaList, errorReporter, 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.
@@ -73,7 +73,7 @@ void ReportErrorFor(SyntaxTrivia trivia)
{
if (trivia.ContainsDiagnostics && trivia.IsKind(SyntaxKind.IgnoredDirectiveTrivia))
{
- reportError(sourceFile, trivia.Span, FileBasedProgramsResources.CannotConvertDirective);
+ errorReporter(sourceFile.Text, sourceFile.Path, trivia.Span, FileBasedProgramsResources.CannotConvertDirective);
}
}
@@ -86,7 +86,7 @@ void ReportErrorFor(SyntaxTrivia trivia)
public static void FindLeadingDirectives(
SourceFile sourceFile,
SyntaxTriviaList triviaList,
- ErrorReporter reportError,
+ ErrorReporter errorReporter,
ImmutableArray.Builder? builder)
{
Debug.Assert(triviaList.Span.Start == 0);
@@ -144,7 +144,7 @@ public static void FindLeadingDirectives(
LeadingWhiteSpace = whiteSpace.Leading,
TrailingWhiteSpace = whiteSpace.Trailing,
},
- ReportError = reportError,
+ ErrorReporter = errorReporter,
SourceFile = sourceFile,
DirectiveKind = name,
DirectiveText = value,
@@ -153,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('"'))
{
- reportError(sourceFile, context.Info.Span, FileBasedProgramsResources.QuoteInDirective);
+ context.ReportError(FileBasedProgramsResources.QuoteInDirective);
}
if (CSharpDirective.Parse(context) is { } directive)
@@ -162,7 +162,7 @@ public static void FindLeadingDirectives(
if (deduplicated.TryGetValue(directive, out var existingDirective))
{
var typeAndName = $"#:{existingDirective.GetType().Name.ToLowerInvariant()} {existingDirective.Name}";
- reportError(sourceFile, directive.Info.Span, string.Format(FileBasedProgramsResources.DuplicateDirective, typeAndName));
+ context.ReportError(directive.Info.Span, string.Format(FileBasedProgramsResources.DuplicateDirective, typeAndName));
}
else
{
@@ -231,11 +231,6 @@ public static SourceFile Load(string filePath)
return new SourceFile(filePath, SourceText.From(stream, encoding: null));
}
- public SourceFile WithText(SourceText newText)
- {
- return new SourceFile(Path, newText);
- }
-
public void Save()
{
using var stream = File.Open(Path, FileMode.Create, FileAccess.Write);
@@ -244,17 +239,6 @@ public void Save()
using var writer = new StreamWriter(stream, encoding);
Text.Write(writer);
}
-
- public FileLinePositionSpan GetFileLinePositionSpan(TextSpan span)
- {
- return new FileLinePositionSpan(Path, Text.Lines.GetLinePositionSpan(span));
- }
-
- public string GetLocationString(TextSpan span)
- {
- var positionSpan = GetFileLinePositionSpan(span);
- return $"{positionSpan.Path}({positionSpan.StartLinePosition.Line + 1})";
- }
}
internal static partial class Patterns
@@ -293,10 +277,16 @@ public readonly struct ParseInfo
public readonly struct ParseContext
{
public required ParseInfo Info { get; init; }
- public required ErrorReporter ReportError { get; init; }
+ public required ErrorReporter ErrorReporter { get; init; }
public required SourceFile SourceFile { get; init; }
public required string DirectiveKind { get; init; }
public required string DirectiveText { get; init; }
+
+ public void ReportError(string message)
+ => ErrorReporter(SourceFile.Text, SourceFile.Path, Info.Span, message);
+
+ public void ReportError(TextSpan span, string message)
+ => ErrorReporter(SourceFile.Text, SourceFile.Path, span, message);
}
public static Named? Parse(in ParseContext context)
@@ -308,7 +298,7 @@ public readonly struct ParseContext
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));
+ context.ReportError(string.Format(FileBasedProgramsResources.UnrecognizedDirective, context.DirectiveKind));
return null;
};
}
@@ -321,14 +311,14 @@ private static (string, string?)? ParseOptionalTwoParts(in ParseContext context,
string directiveKind = context.DirectiveKind;
if (firstPart.IsWhiteSpace())
{
- context.ReportError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.MissingDirectiveName, directiveKind));
+ context.ReportError(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)
{
- context.ReportError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.InvalidDirectiveName, directiveKind, separator));
+ context.ReportError(string.Format(FileBasedProgramsResources.InvalidDirectiveName, directiveKind, separator));
return null;
}
@@ -404,7 +394,7 @@ public sealed class Property(in ParseInfo info) : Named(info)
if (propertyValue is null)
{
- context.ReportError(context.SourceFile, context.Info.Span, FileBasedProgramsResources.PropertyDirectiveMissingParts);
+ context.ReportError(FileBasedProgramsResources.PropertyDirectiveMissingParts);
return null;
}
@@ -414,14 +404,14 @@ public sealed class Property(in ParseInfo info) : Named(info)
}
catch (XmlException ex)
{
- context.ReportError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.PropertyDirectiveInvalidName, ex.Message));
+ context.ReportError(string.Format(FileBasedProgramsResources.PropertyDirectiveInvalidName, ex.Message));
return null;
}
if (propertyName.Equals("RestoreUseStaticGraphEvaluation", StringComparison.OrdinalIgnoreCase) &&
MSBuildUtilities.ConvertStringToBool(propertyValue))
{
- context.ReportError(context.SourceFile, context.Info.Span, FileBasedProgramsResources.StaticGraphRestoreNotSupported);
+ context.ReportError(FileBasedProgramsResources.StaticGraphRestoreNotSupported);
}
return new Property(context.Info)
@@ -493,8 +483,7 @@ public Project(in ParseInfo info, string name) : base(info)
var directiveText = context.DirectiveText;
if (directiveText.IsWhiteSpace())
{
- string directiveKind = context.DirectiveKind;
- context.ReportError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.MissingDirectiveName, directiveKind));
+ context.ReportError(string.Format(FileBasedProgramsResources.MissingDirectiveName, context.DirectiveKind));
return null;
}
@@ -532,14 +521,15 @@ 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, ErrorReporter reportError)
+ public Project EnsureProjectFilePath(SourceFile sourceFile, ErrorReporter errorReporter)
{
var resolvedName = Name;
+ var sourcePath = sourceFile.Path;
// 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 sourceDirectory = Path.GetDirectoryName(sourcePath)
+ ?? throw new InvalidOperationException($"Source file path '{sourcePath}' does not have a containing directory.");
var resolvedProjectPath = Path.Combine(sourceDirectory, resolvedName.Replace('\\', '/'));
if (Directory.Exists(resolvedProjectPath))
@@ -553,16 +543,18 @@ public Project EnsureProjectFilePath(SourceFile sourceFile, ErrorReporter report
}
else
{
- reportError(sourceFile, Info.Span, string.Format(FileBasedProgramsResources.InvalidProjectDirective, error));
+ ReportError(string.Format(FileBasedProgramsResources.InvalidProjectDirective, error));
}
}
else if (!File.Exists(resolvedProjectPath))
{
- reportError(sourceFile, Info.Span,
- string.Format(FileBasedProgramsResources.InvalidProjectDirective, string.Format(FileBasedProgramsResources.CouldNotFindProjectOrDirectory, resolvedProjectPath)));
+ ReportError(string.Format(FileBasedProgramsResources.InvalidProjectDirective, string.Format(FileBasedProgramsResources.CouldNotFindProjectOrDirectory, resolvedProjectPath)));
}
return WithName(resolvedName, NameKind.ProjectFilePath);
+
+ void ReportError(string message)
+ => errorReporter(sourceFile.Text, sourcePath, Info.Span, message);
}
public override string ToString() => $"#:project {Name}";
@@ -617,25 +609,25 @@ public readonly struct Position
}
}
-internal delegate void ErrorReporter(SourceFile sourceFile, TextSpan textSpan, string message);
+internal delegate void ErrorReporter(SourceText text, string path, TextSpan textSpan, string message);
internal static partial class ErrorReporters
{
public static readonly ErrorReporter IgnoringReporter =
- static (_, _, _) => { };
+ static (_, _, _, _) => { };
public static ErrorReporter CreateCollectingReporter(out ImmutableArray.Builder builder)
{
var capturedBuilder = builder = ImmutableArray.CreateBuilder();
- return (sourceFile, textSpan, message) =>
+ return (text, path, textSpan, message) =>
capturedBuilder.Add(new SimpleDiagnostic
{
Location = new SimpleDiagnostic.Position()
{
- Path = sourceFile.Path,
+ Path = path,
TextSpan = textSpan,
- Span = sourceFile.GetFileLinePositionSpan(textSpan).Span
+ Span = text.Lines.GetLinePositionSpan(textSpan)
},
Message = message
});
diff --git a/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs b/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs
index 3c0952b37688..73bad41342b3 100644
--- a/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs
+++ b/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs
@@ -43,7 +43,7 @@ public override int Execute()
// Create a project instance for evaluation.
var projectCollection = new ProjectCollection();
- var builder = new VirtualProjectBuilder(file, VirtualProjectBuildingCommand.TargetFrameworkVersion);
+ var builder = new VirtualProjectBuilder(file, VirtualProjectBuildingCommand.TargetFramework);
builder.CreateProjectInstance(
projectCollection,
diff --git a/src/Cli/dotnet/Commands/Run/FileBasedAppSourceEditor.cs b/src/Cli/dotnet/Commands/Run/FileBasedAppSourceEditor.cs
index 27ac23b56687..764aeec88f95 100644
--- a/src/Cli/dotnet/Commands/Run/FileBasedAppSourceEditor.cs
+++ b/src/Cli/dotnet/Commands/Run/FileBasedAppSourceEditor.cs
@@ -80,7 +80,7 @@ static string GetNewLine(SourceText text)
public void Add(CSharpDirective directive)
{
var change = DetermineAddChange(directive);
- SourceFile = SourceFile.WithText(SourceFile.Text.WithChanges([change]));
+ SourceFile = SourceFile with { Text = SourceFile.Text.WithChanges([change]) };
}
private TextChange DetermineAddChange(CSharpDirective directive)
@@ -231,7 +231,7 @@ public void Remove(CSharpDirective directive)
var span = directive.Info.Span;
var start = span.Start;
var length = span.Length + DetermineTrailingLengthToRemove(directive);
- SourceFile = SourceFile.WithText(SourceFile.Text.Replace(start: start, length: length, newText: string.Empty));
+ SourceFile = SourceFile with { Text = SourceFile.Text.Replace(start: start, length: length, newText: string.Empty) };
}
private static int DetermineTrailingLengthToRemove(CSharpDirective directive)
diff --git a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
index 967cdc7fd884..6b97511dc75a 100644
--- a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
+++ b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
@@ -12,6 +12,7 @@
using Microsoft.Build.Logging;
using Microsoft.Build.Logging.SimpleErrorLogger;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
using Microsoft.DotNet.Cli.Commands.Clean.FileBasedAppArtifacts;
using Microsoft.DotNet.Cli.Commands.Restore;
using Microsoft.DotNet.Cli.Utils;
@@ -82,6 +83,7 @@ internal sealed class VirtualProjectBuildingCommand : CommandBase
];
public static string TargetFrameworkVersion => Product.TargetFrameworkVersion;
+ public static string TargetFramework => $"net{Product.TargetFrameworkVersion}";
public bool NoRestore { get; init; }
@@ -139,7 +141,7 @@ public VirtualProjectBuildingCommand(
}
.AsReadOnly());
- Builder = new VirtualProjectBuilder(entryPointFileFullPath, TargetFrameworkVersion, MSBuildArgs.GetResolvedTargets(), artifactsPath);
+ Builder = new VirtualProjectBuilder(entryPointFileFullPath, TargetFramework, MSBuildArgs.GetResolvedTargets(), artifactsPath);
}
public override int Execute()
@@ -1076,7 +1078,8 @@ public static void CreateTempSubdirectory(string path)
}
public static readonly ErrorReporter ThrowingReporter =
- static (sourceFile, textSpan, message) => throw new GracefulException($"{sourceFile.GetLocationString(textSpan)}: {FileBasedProgramsResources.DirectiveError}: {message}");
+ static (text, path, textSpan, message) =>
+ throw new GracefulException($"{path}({text.Lines.GetLinePositionSpan(textSpan).Start.Line + 1}): {FileBasedProgramsResources.DirectiveError}: {message}");
}
internal sealed class RunFileBuildCacheEntry
diff --git a/src/Microsoft.DotNet.ProjectTools/Microsoft.DotNet.ProjectTools.csproj b/src/Microsoft.DotNet.ProjectTools/Microsoft.DotNet.ProjectTools.csproj
index 9de97043c331..c1156c178c0b 100644
--- a/src/Microsoft.DotNet.ProjectTools/Microsoft.DotNet.ProjectTools.csproj
+++ b/src/Microsoft.DotNet.ProjectTools/Microsoft.DotNet.ProjectTools.csproj
@@ -18,8 +18,8 @@
+
-
diff --git a/src/Microsoft.DotNet.ProjectTools/VirtualProjectBuilder.cs b/src/Microsoft.DotNet.ProjectTools/VirtualProjectBuilder.cs
index ace65c8c4726..cc6b5f8010c5 100644
--- a/src/Microsoft.DotNet.ProjectTools/VirtualProjectBuilder.cs
+++ b/src/Microsoft.DotNet.ProjectTools/VirtualProjectBuilder.cs
@@ -21,7 +21,7 @@ internal sealed class VirtualProjectBuilder
public string EntryPointFileFullPath { get; }
- public SourceFile EntryPointSourceFile
+ internal SourceFile EntryPointSourceFile
{
get
{
@@ -41,7 +41,7 @@ public string ArtifactsPath
public VirtualProjectBuilder(
string entryPointFileFullPath,
- string targetFrameworkVersion,
+ string targetFramework,
string[]? requestedTargets = null,
string? artifactsPath = null)
{
@@ -50,16 +50,16 @@ public VirtualProjectBuilder(
EntryPointFileFullPath = entryPointFileFullPath;
RequestedTargets = requestedTargets;
ArtifactsPath = artifactsPath;
- _defaultProperties = GetDefaultProperties(targetFrameworkVersion);
+ _defaultProperties = GetDefaultProperties(targetFramework);
}
///
/// Kept in sync with the default dotnet new console project file (enforced by DotnetProjectConvertTests.SameAsTemplate).
///
- public static IEnumerable<(string name, string value)> GetDefaultProperties(string targetFrameworkVersion) =>
+ public static IEnumerable<(string name, string value)> GetDefaultProperties(string targetFramework) =>
[
("OutputType", "Exe"),
- ("TargetFramework", $"net{targetFrameworkVersion}"),
+ ("TargetFramework", targetFramework),
("ImplicitUsings", "enable"),
("Nullable", "enable"),
("PublishAot", "true"),
@@ -76,6 +76,9 @@ public static string GetArtifactsPath(string entryPointFileFullPath)
return GetTempSubpath(directoryName);
}
+ public static string GetVirtualProjectPath(string entryPointFilePath)
+ => Path.ChangeExtension(entryPointFilePath, ".csproj");
+
///
/// Obtains a temporary subdirectory for file-based app artifacts, e.g., /tmp/dotnet/runfile/.
///
@@ -154,7 +157,18 @@ internal static ImmutableArray EvaluateDirectives(
return directives;
}
- public void CreateProjectInstance(
+ public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection, ErrorReporter errorReporter)
+ {
+ CreateProjectInstance(
+ projectCollection,
+ errorReporter,
+ out var projectInstance,
+ out _);
+
+ return projectInstance;
+ }
+
+ internal void CreateProjectInstance(
ProjectCollection projectCollection,
ErrorReporter errorReporter,
out ProjectInstance project,
@@ -199,7 +213,7 @@ private ProjectInstance CreateProjectInstance(
ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
{
- var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+ var projectFileFullPath = GetVirtualProjectPath(EntryPointFileFullPath);
var projectFileWriter = new StringWriter();
WriteProjectFile(
@@ -221,7 +235,7 @@ ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
}
}
- public static void WriteProjectFile(
+ internal static void WriteProjectFile(
TextWriter writer,
ImmutableArray directives,
IEnumerable<(string name, string value)> defaultProperties,
@@ -531,7 +545,7 @@ static void WriteImport(TextWriter writer, string project, CSharpDirective.Sdk s
}
}
- public static SourceText? RemoveDirectivesFromFile(ImmutableArray directives, SourceText text)
+ internal static SourceText? RemoveDirectivesFromFile(ImmutableArray directives, SourceText text)
{
if (directives.Length == 0)
{
@@ -549,7 +563,7 @@ static void WriteImport(TextWriter writer, string project, CSharpDirective.Sdk s
return text;
}
- public static void RemoveDirectivesFromFile(ImmutableArray directives, SourceText text, string filePath)
+ internal static void RemoveDirectivesFromFile(ImmutableArray directives, SourceText text, string filePath)
{
if (RemoveDirectivesFromFile(directives, text) is { } modifiedText)
{
diff --git a/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs b/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs
index 1b800a103c8b..7b6365d8733e 100644
--- a/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs
+++ b/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs
@@ -1746,7 +1746,7 @@ private static void Convert(string inputCSharp, out string actualProject, out st
var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: !force, diagnosticBag);
directives = VirtualProjectBuilder.EvaluateDirectives(project: null, directives, sourceFile, diagnosticBag);
var projectWriter = new StringWriter();
- VirtualProjectBuilder.WriteProjectFile(projectWriter, directives, VirtualProjectBuilder.GetDefaultProperties(VirtualProjectBuildingCommand.TargetFrameworkVersion), isVirtualProject: false);
+ VirtualProjectBuilder.WriteProjectFile(projectWriter, directives, VirtualProjectBuilder.GetDefaultProperties(VirtualProjectBuildingCommand.TargetFramework), isVirtualProject: false);
actualProject = projectWriter.ToString();
actualCSharp = VirtualProjectBuilder.RemoveDirectivesFromFile(directives, sourceFile.Text)?.ToString();
}