Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions eng/targets/Settings.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
<!-- RestoreUseStaticGraphEvaluation will cause prebuilts when building source-only. -->
<RestoreUseStaticGraphEvaluation Condition="'$(DotNetBuildSourceOnly)' != 'true'">true</RestoreUseStaticGraphEvaluation>

<!-- TODO2: use an officially published package and delete this. -->
<RestoreAdditionalProjectSources>F:\src\dotnet-sdk\artifacts\packages\Debug\NonShipping</RestoreAdditionalProjectSources>

<!-- Disable the implicit nuget fallback folder as it makes it hard to locate and copy ref assemblies to the test output folder -->
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
<ToolsetPackagesDir>$(RepoRoot)build\ToolsetPackages\</ToolsetPackagesDir>
Expand Down
9 changes: 9 additions & 0 deletions src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
<Compile Include="$(MSBuildThisFileDirectory)ConvertSwitchStatementToExpression\ConvertSwitchStatementToExpressionDiagnosticAnalyzer.Analyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ConvertSwitchStatementToExpression\ConvertSwitchStatementToExpressionDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ConvertSwitchStatementToExpression\ConvertSwitchStatementToExpressionHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FileBasedPrograms\AppDirectiveDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FileBasedPrograms\ExternalHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FileHeaders\CSharpFileHeaderDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FileHeaders\CSharpFileHeaderHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)InlineDeclaration\CSharpInlineDeclarationDiagnosticAnalyzer.cs" />
Expand Down Expand Up @@ -166,4 +168,11 @@
<ItemGroup Condition="'$(DefaultLanguageSourceExtension)' != '' AND '$(BuildingInsideVisualStudio)' != 'true'">
<ExpectedCompile Include="$(MSBuildThisFileDirectory)**\*$(DefaultLanguageSourceExtension)" />
</ItemGroup>
<ItemGroup>
<!-- TODO2: central package version. Use published version. -->
<PackageReference Include="Microsoft.DotNet.FileBasedPrograms" VersionOverride="10.0.200-dev" />
</ItemGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants);FILE_BASED_PROGRAMS_SOURCE_PACKAGE_GRACEFUL_EXCEPTION</DefineConstants>
</PropertyGroup>
</Project>
55 changes: 55 additions & 0 deletions src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -440,4 +440,59 @@
<data name="Property_accessor_can_be_simplified" xml:space="preserve">
<value>Property accessor can be simplified</value>
</data>

<!-- TODO2: should this live here? Or sdk source package gives us some other resource file? -->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess source packages can contain any sources, including resx and xlf, and if these strings need to be used inside SDK too, I don't see other option than having these in the package.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try creating a new resource file next to AppDirectiveHelpers for short term, and hopefully it is something we can just move to the source package when ready.

There is no real overhead to creating an additional resource file vs reusing existing one, I think?


<!-- CliStrings -->
<data name="CouldNotFindAnyProjectInDirectory" xml:space="preserve">
<value>Could not find any project in `{0}`.</value>
</data>
<data name="CouldNotFindProjectOrDirectory" xml:space="preserve">
<value>Could not find project or directory `{0}`.</value>
</data>
<data name="MoreThanOneProjectInDirectory" xml:space="preserve">
<value>Found more than one project in `{0}`. Specify which one to use.</value>
</data>

<!-- CliCommandStrings -->
<data name="PropertyDirectiveInvalidName" xml:space="preserve">
<value>Invalid property name: {0}</value>
<comment>{0} is an inner exception message.</comment>
</data>
<data name="PropertyDirectiveMissingParts" xml:space="preserve">
<value>The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'.</value>
<comment>{Locked="#:property"}</comment>
</data>
<data name="StaticGraphRestoreNotSupported" xml:space="preserve">
<value>Static graph restore is not supported for file-based apps. Remove the '#:property'.</value>
<comment>{Locked="#:property"}</comment>
</data>
<data name="DirectiveError" xml:space="preserve">
<value>error</value>
<comment>Used when reporting directive errors like "file(location): error: message".</comment>
</data>
<data name="InvalidDirectiveName" xml:space="preserve">
<value>The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'.</value>
<comment>{0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='.</comment>
</data>
<data name="CannotConvertDirective" xml:space="preserve">
<value>Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway.</value>
<comment>{Locked="--force"}</comment>
</data>
<data name="DuplicateDirective" xml:space="preserve">
<value>Duplicate directives are not supported: {0}</value>
<comment>{0} is the directive type and name.</comment>
</data>
<data name="InvalidProjectDirective" xml:space="preserve">
<value>The '#:project' directive is invalid: {0}</value>
<comment>{0} is the inner error message.</comment>
</data>
<data name="MissingDirectiveName" xml:space="preserve">
<value>Missing name of '{0}'.</value>
<comment>{0} is the directive name like 'package' or 'sdk'.</comment>
</data>
<data name="UnrecognizedDirective" xml:space="preserve">
<value>Unrecognized directive '{0}'.</value>
<comment>{0} is the directive name like 'package' or 'sdk'.</comment>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.DotNet.FileBasedPrograms;

namespace Microsoft.CodeAnalysis.FileBasedPrograms;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class AppDirectiveDiagnosticAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "FileBasedPrograms";

private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
Copy link
Member

@jjonescz jjonescz Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting this warning when I open this in VSCode (and didn't see it reported by CI, so just FYI):

The symbol 'DiagnosticDescriptor.DiagnosticDescriptor(string, string, string, string, DiagnosticSeverity, bool, string?, string?, params string[])' is banned in this project: Analyzers should extend 'AbstractBuiltInCodeStyleDiagnosticAnalyzer' or 'AbstractCodeQualityDiagnosticAnalyzer' instead

DiagnosticId,
title: DiagnosticId,
// TODO: we probably want to have a different diagnostic for each kind that the SDK package can produce
messageFormat: "{0}",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);

context.RegisterCompilationStartAction(context =>
{
context.RegisterSyntaxTreeAction(visitSyntaxTree);
});

void visitSyntaxTree(SyntaxTreeAnalysisContext context)
{
var tree = context.Tree;
if (!tree.Options.Features.ContainsKey("FileBasedProgram"))
return;

var root = tree.GetRoot(context.CancellationToken);
if (!root.ContainsDirectives)
return;

// App directives are only valid when they appear before the first C# token
var rootLeadingTrivia = root.GetLeadingTrivia();
var diagnosticBag = DiagnosticBag.Collect(out var diagnosticsBuilder);
AppDirectiveHelpers.FindLeadingDirectives(
new SourceFile(tree.FilePath, tree.GetText(context.CancellationToken)),
root.GetLeadingTrivia(),
diagnosticBag,
builder: null);

foreach (var diag in diagnosticsBuilder)
{
context.ReportDiagnostic(createDiagnostic(tree, diag));
}

// The compiler already reports an error on all the directives past the first token in the file.
// Console.WriteLine("Hello World!");
// #:property foo=bar // error CS9297: '#:' directives cannot be after first token in file
}

// TODO: should SimpleDiagnostics have IDs? message args? TextSpan?
// It feels unreasonable for users to suppress these.
// When these are present, the user cannot build/run, period.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, currently we only have errors. We might want to add warnings in the future, but don't know about any candidates right now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future candidate is #r support. We've talked about enabling it for ease of conversion/pasting from CSX but marking it as a warning.

Diagnostic createDiagnostic(SyntaxTree syntaxTree, SimpleDiagnostic simpleDiagnostic)
{
return Diagnostic.Create(
Rule,
location: Location.Create(syntaxTree, simpleDiagnostic.Location.TextSpan),
simpleDiagnostic.Message);
}
}
}
Loading
Loading