Skip to content
Merged
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
1 change: 1 addition & 0 deletions Aspire.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
<Project Path="src/Aspire.Cli/Aspire.Cli.csproj" />
<Project Path="src/Aspire.Dashboard/Aspire.Dashboard.csproj" />
<Project Path="src/Aspire.Hosting.Analyzers/Aspire.Hosting.Analyzers.csproj" />
<Project Path="src/Aspire.Hosting.Integration.Analyzers/Aspire.Hosting.Integration.Analyzers.csproj" />
<Project Path="src/Aspire.Hosting.AppHost/Aspire.Hosting.AppHost.csproj" />
<Project Path="src/Aspire.Hosting.DevTunnels/Aspire.Hosting.DevTunnels.csproj" />
<Project Path="src/Aspire.Hosting.Docker/Aspire.Hosting.Docker.csproj" />
Expand Down
15 changes: 0 additions & 15 deletions src/Aspire.Hosting.Analyzers/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,2 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
ASPIREEXPORT001 | Design | Error | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT001)
ASPIREEXPORT002 | Design | Error | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT002)
ASPIREEXPORT003 | Design | Error | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT003)
ASPIREEXPORT004 | Design | Error | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT004)
ASPIREEXPORT005 | Design | Warning | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT005)
ASPIREEXPORT006 | Design | Warning | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT006)
ASPIREEXPORT007 | Design | Warning | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT007)
ASPIREEXPORT008 | Design | Warning | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT008)
ASPIREEXPORT009 | Design | Warning | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT009)
ASPIREEXPORT010 | Design | Warning | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT010)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
; Shipped analyzer releases
; https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
ASPIREEXPORT001 | Design | Error | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT001)
ASPIREEXPORT002 | Design | Error | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT002)
ASPIREEXPORT003 | Design | Error | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT003)
ASPIREEXPORT004 | Design | Error | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT004)
ASPIREEXPORT005 | Design | Warning | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT005)
ASPIREEXPORT006 | Design | Warning | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT006)
ASPIREEXPORT007 | Design | Warning | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT007)
ASPIREEXPORT008 | Design | Warning | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT008)
ASPIREEXPORT009 | Design | Warning | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT009)
ASPIREEXPORT010 | Design | Warning | AspireExportAnalyzer, [Documentation](https://aka.ms/aspire/diagnostics/ASPIREEXPORT010)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsRoslynComponent>true</IsRoslynComponent>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<UsePublicApiAnalyzers>false</UsePublicApiAnalyzers>
<EnablePackageValidation>false</EnablePackageValidation>
<IsPackable>false</IsPackable>
<LangVersion>12.0</LangVersion>
<!--
Analyzer projects don't need XML docs
CS1573: Parameter 'parameter' has no matching param tag in the XML comment for 'parameter' (but other parameters do)
CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member'
CS1712: Type parameter 'type_parameter' has no matching typeparam tag in the XML comment on 'type_or_member' (but other type parameters do)
-->
<NoWarn>$(NoWarn),1573,1591,1712</NoWarn>
<!--
We manually control the dependency versions that the analyzer uses as it needs to be loaded into the compiler host, e.g. Visual Studio,
which may have a different set of dependencies.
-->
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>

<ItemGroup>
<!-- Shared Infrastructure from Aspire.Hosting.Analyzers -->
<Compile Include="..\Aspire.Hosting.Analyzers\Infrastructure\BoundedCacheWithFactory.cs" Link="Infrastructure\BoundedCacheWithFactory.cs" />
<Compile Include="..\Aspire.Hosting.Analyzers\Infrastructure\WellKnownTypeData.cs" Link="Infrastructure\WellKnownTypeData.cs" />
<Compile Include="..\Aspire.Hosting.Analyzers\Infrastructure\WellKnownTypes.cs" Link="Infrastructure\WellKnownTypes.cs" />
</ItemGroup>

<ItemGroup>
<!-- The version of Roslyn used needs to consider the min-supported version of VS and .NET SDK for Aspire.
Roslyn 4.8 aligns with .NET SDK 8.0.100 & VS 17.8 and the min-supported Aspire VS version is 17.10, so
we take the minimum of those. -->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="All" />
<PackageReference Update="Microsoft.DotNet.GenAPI.Task" Version="9.0.103-servicing.25065.25" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Aspire.Hosting.Analyzers.Tests" />
</ItemGroup>

</Project>
26 changes: 26 additions & 0 deletions src/Aspire.Hosting/Aspire.Hosting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
<Description>Core abstractions for the Aspire application model.</Description>
</PropertyGroup>

<ItemGroup>
<None Include="buildTransitive\Aspire.Hosting.targets" Pack="true" PackagePath="buildTransitive\$(DefaultTargetFramework)" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(SharedDir)BundleDiscovery.cs" Link="Dcp\BundleDiscovery.cs" />
<Compile Include="$(SharedDir)ChannelExtensions.cs" Link="ChannelExtensions.cs" />
Expand Down Expand Up @@ -138,4 +142,26 @@
<InternalsVisibleTo Include="Aspire.Hosting.CodeGeneration.Rust.Tests" />
</ItemGroup>

<!-- Include the integration analyzer in the package -->
<PropertyGroup>
<BeforePack>$(BeforePack);IncludeIntegrationAnalyzerInPackage</BeforePack>
</PropertyGroup>
<Target Name="IncludeIntegrationAnalyzerInPackage">
<MSBuild Projects="..\Aspire.Hosting.Integration.Analyzers\Aspire.Hosting.Integration.Analyzers.csproj"
Targets="Build"
Properties="Configuration=$(Configuration);Platform=$(Platform)" />

<MSBuild Projects="..\Aspire.Hosting.Integration.Analyzers\Aspire.Hosting.Integration.Analyzers.csproj"
Targets="GetTargetPath"
Properties="Configuration=$(Configuration);Platform=$(Platform)">
<Output TaskParameter="TargetOutputs" PropertyName="AspireIntegrationAnalyzerFile" />
</MSBuild>

<Error Condition="'$(AspireIntegrationAnalyzerFile)' == ''" Text="Could not find Aspire.Hosting.Integration.Analyzers.dll." />

<ItemGroup>
<None Include="$(AspireIntegrationAnalyzerFile)" Pack="True" PackagePath="buildTransitive\$(DefaultTargetFramework)" />
</ItemGroup>
</Target>

</Project>
12 changes: 12 additions & 0 deletions src/Aspire.Hosting/buildTransitive/Aspire.Hosting.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project>

<PropertyGroup>
<EnableAspireIntegrationAnalyzers Condition="'$(EnableAspireIntegrationAnalyzers)' == ''">false</EnableAspireIntegrationAnalyzers>
<_AspireIntegrationAnalyzerAssembly Condition="'$(_AspireIntegrationAnalyzerAssembly)' == ''">$(MSBuildThisFileDirectory)Aspire.Hosting.Integration.Analyzers.dll</_AspireIntegrationAnalyzerAssembly>
</PropertyGroup>

<ItemGroup Condition="'$(EnableAspireIntegrationAnalyzers)' == 'true'">
<Analyzer Include="$(_AspireIntegrationAnalyzerAssembly)" Condition="Exists('$(_AspireIntegrationAnalyzerAssembly)')" />
</ItemGroup>

</Project>
8 changes: 6 additions & 2 deletions src/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@
<ItemGroup Condition="
$([System.String]::Copy('$(MSBuildProjectName)').StartsWith('Aspire.Hosting.'))
and
'$(MSBuildProjectName)' != 'Aspire.Hosting.Analyzers'">
<ProjectReference Include="$(MSBuildThisFileDirectory)\Aspire.Hosting.Analyzers\Aspire.Hosting.Analyzers.csproj"
'$(MSBuildProjectName)' != 'Aspire.Hosting.Analyzers'
and
'$(MSBuildProjectName)' != 'Aspire.Hosting.AppHost'
and
'$(MSBuildProjectName)' != 'Aspire.Hosting.Integration.Analyzers'">
<ProjectReference Include="$(MSBuildThisFileDirectory)\Aspire.Hosting.Integration.Analyzers\Aspire.Hosting.Integration.Analyzers.csproj"
PrivateAssets="all"
ReferenceOutputAssembly="false"
OutputItemType="Analyzer"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Aspire.Hosting.AppHost\Aspire.Hosting.AppHost.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\src\Aspire.Hosting.Analyzers\Aspire.Hosting.Analyzers.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\src\Aspire.Hosting.Integration.Analyzers\Aspire.Hosting.Integration.Analyzers.csproj" IsAspireProjectResource="false" />
</ItemGroup>

<ItemGroup>
Expand Down
127 changes: 127 additions & 0 deletions tests/Aspire.Hosting.Tests/MSBuildTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,133 @@ the Aspire.AppHost.SDK targets that will automatically add these references to p
Assert.Contains("warning ASPIRE004", output);
}

[Fact]
public void AspireExportAnalyzersAreDisabledByDefault()
{
var repoRoot = MSBuildUtils.GetRepoRoot();
using var tempDirectory = new TestTempDirectory();

var projectDirectory = Path.Combine(tempDirectory.Path, "MyHostingExtension");
Directory.CreateDirectory(projectDirectory);

File.WriteAllText(Path.Combine(projectDirectory, "MyHostingExtension.csproj"),
$"""
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="{repoRoot}\src\Aspire.Hosting\Aspire.Hosting.csproj" />
</ItemGroup>

</Project>
""");

File.WriteAllText(Path.Combine(projectDirectory, "Extensions.cs"),
"""
using Aspire.Hosting;
using Aspire.Hosting.ApplicationModel;

namespace MyHostingExtension;

public static class CustomResourceExtensions
{
public static IResourceBuilder<ContainerResource> AddCustomContainer(this IDistributedApplicationBuilder builder)
{
return builder.AddContainer("custom", "custom-image");
}
}
""");

CreateExportAnalyzerDirectoryBuildFiles(projectDirectory, repoRoot);

var output = BuildProject(projectDirectory);

Assert.DoesNotContain("warning ASPIREEXPORT008", output);
}

[Fact]
public void AspireExportAnalyzersCanBeEnabledWithMsBuildProperty()
{
var repoRoot = MSBuildUtils.GetRepoRoot();
using var tempDirectory = new TestTempDirectory();

var projectDirectory = Path.Combine(tempDirectory.Path, "MyHostingExtension");
Directory.CreateDirectory(projectDirectory);

File.WriteAllText(Path.Combine(projectDirectory, "MyHostingExtension.csproj"),
$"""
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="{repoRoot}\src\Aspire.Hosting\Aspire.Hosting.csproj" />
</ItemGroup>

</Project>
""");

File.WriteAllText(Path.Combine(projectDirectory, "Extensions.cs"),
"""
using Aspire.Hosting;
using Aspire.Hosting.ApplicationModel;

namespace MyHostingExtension;

public static class CustomResourceExtensions
{
public static IResourceBuilder<ContainerResource> AddCustomContainer(this IDistributedApplicationBuilder builder)
{
return builder.AddContainer("custom", "custom-image");
}
}
""");

CreateExportAnalyzerDirectoryBuildFiles(projectDirectory, repoRoot, enableAspireIntegrationAnalyzers: true);

var output = BuildProject(projectDirectory);

Assert.Contains("warning ASPIREEXPORT008", output);
}

private static void CreateExportAnalyzerDirectoryBuildFiles(
string basePath,
string repoRoot,
bool enableAspireIntegrationAnalyzers = false)
{
File.WriteAllText(Path.Combine(basePath, "Directory.Build.props"),
$"""
<Project>
<PropertyGroup>
<EnableAspireIntegrationAnalyzers>{enableAspireIntegrationAnalyzers.ToString().ToLowerInvariant()}</EnableAspireIntegrationAnalyzers>
</PropertyGroup>
</Project>
""");
File.WriteAllText(Path.Combine(basePath, "Directory.Build.targets"),
$"""
<Project>
<ItemGroup Condition="'$(EnableAspireIntegrationAnalyzers)' == 'true'">
<ProjectReference Include="{repoRoot}\src\Aspire.Hosting.Integration.Analyzers\Aspire.Hosting.Integration.Analyzers.csproj"
PrivateAssets="all"
ReferenceOutputAssembly="false"
OutputItemType="Analyzer"
SetTargetFramework="TargetFramework=netstandard2.0" />
</ItemGroup>

<Import Project="{repoRoot}\src\Aspire.Hosting\buildTransitive\Aspire.Hosting.targets" />
</Project>
""");
}

/// <summary>
/// Tests that when GenerateAssemblyInfo is set to false, a build error is emitted.
/// </summary>
Expand Down
Loading