Skip to content

Commit 1b47f44

Browse files
authored
Add a top-level node for analyzers (dotnet#9906)
This is the first step, and just estabilishes a skeleton. The end goal is organize our analyzers under this node because: 1. We're writing some 'top level' analyzers that depend on everything else 2. We want to be able to service analyzers on the SDK schedule (independent of runtime). Next, we'll merge the MVC analyzers into this assembly since there's no reason for them to be separate. The MVC API analyzers will remain a separate package, but under this node. The component analyzers will remain separate as they need to ship as a package, but will move under this node as well.
1 parent 04600e8 commit 1b47f44

26 files changed

+1452
-7
lines changed

build/repo.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@
166166
$(RepositoryRoot)src\MusicStore\**\*.*proj;
167167
$(RepositoryRoot)src\SignalR\**\*.csproj;
168168
$(RepositoryRoot)src\Components\**\*.csproj;
169+
$(RepositoryRoot)src\Analyzers\**\*.csproj;
169170
$(RepositoryRoot)src\ProjectTemplates\*\*.csproj;
170171
$(RepositoryRoot)src\ProjectTemplates\testassets\*\*.csproj;
171172
"

eng/Versions.props

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,9 @@
150150
<MicrosoftAzureStorageBlobPackageVersion>10.0.1</MicrosoftAzureStorageBlobPackageVersion>
151151
<MicrosoftBuildFrameworkPackageVersion>15.8.166</MicrosoftBuildFrameworkPackageVersion>
152152
<MicrosoftBuildUtilitiesCorePackageVersion>15.8.166</MicrosoftBuildUtilitiesCorePackageVersion>
153-
<MicrosoftCodeAnalysisCommonPackageVersion>2.8.0</MicrosoftCodeAnalysisCommonPackageVersion>
154-
<MicrosoftCodeAnalysisCSharpPackageVersion>2.8.0</MicrosoftCodeAnalysisCSharpPackageVersion>
155-
<MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>2.8.0</MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>
153+
<MicrosoftCodeAnalysisCommonPackageVersion>3.0.0</MicrosoftCodeAnalysisCommonPackageVersion>
154+
<MicrosoftCodeAnalysisCSharpPackageVersion>3.0.0</MicrosoftCodeAnalysisCSharpPackageVersion>
155+
<MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>3.0.0</MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>
156156
<MicrosoftIdentityModelClientsActiveDirectoryPackageVersion>3.19.8</MicrosoftIdentityModelClientsActiveDirectoryPackageVersion>
157157
<MicrosoftIdentityModelLoggingPackageVersion>5.3.0</MicrosoftIdentityModelLoggingPackageVersion>
158158
<MicrosoftIdentityModelProtocolsOpenIdConnectPackageVersion>5.3.0</MicrosoftIdentityModelProtocolsOpenIdConnectPackageVersion>

src/Analyzers/Analyzers.sln

Lines changed: 794 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<Description>CSharp Analyzers for ASP.NET Core.</Description>
4+
<PackageTags>aspnetcore</PackageTags>
5+
6+
<!--
7+
This package is for internal use only. It contains analyzers that are bundled in the .NET Core Web SDK.
8+
9+
We do need to pack it so it can be picked up by the Web SDK.
10+
-->
11+
<IsShippingPackage>false</IsShippingPackage>
12+
<IsPackable>true</IsPackable>
13+
14+
<TargetFramework>netstandard2.0</TargetFramework>
15+
<IncludeBuildOutput>false</IncludeBuildOutput>
16+
<GenerateDocumentationFile>false</GenerateDocumentationFile>
17+
<NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile>
18+
</PropertyGroup>
19+
20+
<ItemGroup>
21+
<Reference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" PrivateAssets="All" />
22+
</ItemGroup>
23+
24+
<Target Name="PopulateNuspec" BeforeTargets="GenerateNuspec">
25+
26+
<PropertyGroup>
27+
<NuspecProperties>
28+
id=$(PackageId);
29+
version=$(PackageVersion);
30+
authors=$(Authors);
31+
description=$(Description);
32+
tags=$(PackageTags.Replace(';', ' '));
33+
licenseUrl=$(PackageLicenseUrl);
34+
projectUrl=$(PackageProjectUrl);
35+
iconUrl=$(PackageIconUrl);
36+
repositoryUrl=$(RepositoryUrl);
37+
repositoryCommit=$(SourceRevisionId);
38+
copyright=$(Copyright);
39+
40+
OutputBinary=$(OutputPath)$(AssemblyName).dll;
41+
OutputSymbol=$(OutputPath)$(AssemblyName).pdb;
42+
</NuspecProperties>
43+
</PropertyGroup>
44+
</Target>
45+
46+
</Project>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
3+
<metadata>
4+
<id>$id$</id>
5+
<version>$version$</version>
6+
<authors>$authors$</authors>
7+
<requireLicenseAcceptance>true</requireLicenseAcceptance>
8+
<licenseUrl>$licenseUrl$</licenseUrl>
9+
<projectUrl>$projectUrl$</projectUrl>
10+
<iconUrl>$iconUrl$</iconUrl>
11+
<description>$description$</description>
12+
<copyright>$copyright$</copyright>
13+
<tags>$tags$</tags>
14+
<repository type="git" url="$repositoryUrl$" commit="$repositoryCommit$" />
15+
<dependencies>
16+
<group targetFramework=".NETStandard1.3" />
17+
</dependencies>
18+
</metadata>
19+
20+
<files>
21+
<file src="$OutputBinary$" target="analyzers\dotnet\cs\" />
22+
<file src="$OutputSymbol$" target="analyzers\dotnet\cs\" />
23+
</files>
24+
</package>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Runtime.CompilerServices;
5+
6+
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Analyzers.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.CodeAnalysis;
6+
7+
namespace Microsoft.AspNetCore.Analyzers
8+
{
9+
internal static class StartupFacts
10+
{
11+
public static bool IsStartupClass(StartupSymbols symbols, INamedTypeSymbol type)
12+
{
13+
if (symbols == null)
14+
{
15+
throw new ArgumentNullException(nameof(symbols));
16+
}
17+
18+
if (type == null)
19+
{
20+
throw new ArgumentNullException(nameof(type));
21+
}
22+
23+
// It's not good enough to just look for a method called ConfigureServices or Configure as a hueristic.
24+
// ConfigureServices might not appear in trivial cases, and Configure might be named ConfigureDevelopment
25+
// or something similar.
26+
//
27+
// Additionally, a startup class could be called anything and wired up explicitly.
28+
//
29+
// Since we already are analyzing the symbol it should be cheap to do a pass over the members.
30+
var members = type.GetMembers();
31+
for (var i = 0; i < members.Length; i++)
32+
{
33+
if (members[i] is IMethodSymbol method && (IsConfigureServices(symbols, method) || IsConfigure(symbols, method)))
34+
{
35+
return true;
36+
}
37+
}
38+
39+
return false;
40+
}
41+
42+
// Based on StartupLoader. The philosophy is that we want to do analysis only on things
43+
// that would be recognized as a ConfigureServices method to avoid false positives.
44+
//
45+
// The ConfigureServices method follows the naming pattern `Configure{Environment?}Services` (ignoring case).
46+
// The ConfigureServices method must be public.
47+
// The ConfigureServices method can be instance or static.
48+
// The ConfigureServices method cannot have other parameters besides IServiceCollection.
49+
//
50+
// The ConfigureServices method does not actually need to accept IServiceCollection
51+
// but we exclude that case because a ConfigureServices method that doesn't accept an
52+
// IServiceCollection can't do anything interesting to analysis.
53+
public static bool IsConfigureServices(StartupSymbols symbols, IMethodSymbol symbol)
54+
{
55+
if (symbol == null)
56+
{
57+
throw new ArgumentNullException(nameof(symbol));
58+
}
59+
60+
if (symbol.DeclaredAccessibility != Accessibility.Public)
61+
{
62+
return false;
63+
}
64+
65+
if (symbol.Name == null ||
66+
!symbol.Name.StartsWith(SymbolNames.ConfigureServicesMethodPrefix, StringComparison.OrdinalIgnoreCase) ||
67+
!symbol.Name.EndsWith(SymbolNames.ConfigureServicesMethodSuffix, StringComparison.OrdinalIgnoreCase))
68+
{
69+
return false;
70+
}
71+
72+
if (symbol.Parameters.Length != 1)
73+
{
74+
return false;
75+
}
76+
77+
if (symbol.Parameters[0].Type != symbols.IServiceCollection)
78+
{
79+
return false;
80+
}
81+
82+
return true;
83+
}
84+
85+
// Based on StartupLoader. The philosophy is that we want to do analysis only on things
86+
// that would be recognized as a Configure method to avoid false positives.
87+
//
88+
// The Configure method follows the naming pattern `Configure{Environment?}` (ignoring case).
89+
// The Configure method must be public.
90+
// The Configure method can be instance or static.
91+
// The Configure method *can* have other parameters besides IApplicationBuilder.
92+
//
93+
// The Configure method does not actually need to accept IApplicationBuilder
94+
// but we exclude that case because a Configure method that doesn't accept an
95+
// IApplicationBuilder can't do anything interesting to analysis.
96+
public static bool IsConfigure(StartupSymbols symbols, IMethodSymbol symbol)
97+
{
98+
if (symbol == null)
99+
{
100+
throw new ArgumentNullException(nameof(symbol));
101+
}
102+
103+
if (symbol.DeclaredAccessibility != Accessibility.Public)
104+
{
105+
return false;
106+
}
107+
108+
if (symbol.Name == null ||
109+
!symbol.Name.StartsWith(SymbolNames.ConfigureMethodPrefix, StringComparison.OrdinalIgnoreCase))
110+
{
111+
return false;
112+
}
113+
114+
// IApplicationBuilder can appear in any parameter, but must appear.
115+
for (var i = 0; i < symbol.Parameters.Length; i++)
116+
{
117+
if (symbol.Parameters[i].Type == symbols.IApplicationBuilder)
118+
{
119+
return true;
120+
}
121+
}
122+
123+
return false;
124+
}
125+
}
126+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.CodeAnalysis;
5+
6+
namespace Microsoft.AspNetCore.Analyzers
7+
{
8+
internal class StartupSymbols
9+
{
10+
public StartupSymbols(Compilation compilation)
11+
{
12+
IApplicationBuilder = compilation.GetTypeByMetadataName(SymbolNames.IApplicationBuilder.MetadataName);
13+
IServiceCollection = compilation.GetTypeByMetadataName(SymbolNames.IServiceCollection.MetadataName);
14+
}
15+
16+
public bool HasRequiredSymbols => IApplicationBuilder != null && IServiceCollection != null;
17+
18+
public INamedTypeSymbol IApplicationBuilder { get; }
19+
20+
public INamedTypeSymbol IServiceCollection { get; }
21+
}
22+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.CodeAnalysis;
5+
6+
namespace Microsoft.AspNetCore.Analyzers
7+
{
8+
internal static class SymbolNames
9+
{
10+
public const string ConfigureServicesMethodPrefix = "Configure";
11+
12+
public const string ConfigureServicesMethodSuffix = "Services";
13+
14+
public const string ConfigureMethodPrefix = "Configure";
15+
16+
public static class IApplicationBuilder
17+
{
18+
public const string MetadataName = "Microsoft.AspNetCore.Builder.IApplicationBuilder";
19+
}
20+
21+
public static class IServiceCollection
22+
{
23+
public const string MetadataName = "Microsoft.Extensions.DependencyInjection.IServiceCollection";
24+
}
25+
}
26+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.IO;
6+
using System.Threading.Tasks;
7+
using Microsoft.AspNetCore.Analyzer.Testing;
8+
using Microsoft.AspNetCore.Testing;
9+
using Microsoft.AspNetCore.Testing.xunit;
10+
using Microsoft.CodeAnalysis;
11+
12+
namespace Microsoft.AspNetCore.Analyzers
13+
{
14+
public abstract class AnalyzerTestBase
15+
{
16+
private static readonly string ProjectDirectory = GetProjectDirectory();
17+
18+
public TestSource Read(string source)
19+
{
20+
var filePath = Path.Combine(ProjectDirectory, "TestFiles", GetType().Name, source);
21+
if (!File.Exists(filePath))
22+
{
23+
throw new FileNotFoundException($"TestFile {source} could not be found at {filePath}.", filePath);
24+
}
25+
26+
var fileContent = File.ReadAllText(filePath);
27+
return TestSource.Read(fileContent);
28+
}
29+
30+
public Project CreateProject(string source)
31+
{
32+
if (!source.EndsWith(".cs"))
33+
{
34+
source = source + ".cs";
35+
}
36+
37+
var read = Read(source);
38+
return DiagnosticProject.Create(GetType().Assembly, new[] { read.Source, });
39+
}
40+
41+
public Task<Compilation> CreateCompilationAsync(string source)
42+
{
43+
return CreateProject(source).GetCompilationAsync();
44+
}
45+
46+
private static string GetProjectDirectory()
47+
{
48+
// On helix we use the published test files
49+
if (SkipOnHelixAttribute.OnHelix())
50+
{
51+
return AppContext.BaseDirectory;
52+
}
53+
54+
var solutionDirectory = TestPathUtilities.GetSolutionRootDirectory("Analyzers");
55+
var projectDirectory = Path.Combine(solutionDirectory, "Analyzers", "test");
56+
return projectDirectory;
57+
}
58+
}
59+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.0</TargetFramework>
5+
<PreserveCompilationContext>true</PreserveCompilationContext>
6+
<RootNamespace>Microsoft.AspNetCore.Analyzers</RootNamespace>
7+
<!-- https://github.com/aspnet/AspNetCore/issues/6549 -->
8+
<BuildHelixPayload>false</BuildHelixPayload>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<Compile Include="$(SharedSourceRoot)test\SkipOnHelixAttribute.cs" />
13+
<Content Include="TestFiles\**\*.*" CopyToPublishDirectory="PreserveNewest" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\src\Microsoft.AspNetCore.Analyzers.csproj" />
18+
19+
<Reference Include="Microsoft.AspNetCore.Analyzer.Testing" />
20+
<Reference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
21+
22+
<!--
23+
These tests include startup code for most parts of our stack. This list will grow as we add more.
24+
-->
25+
<Reference Include="Microsoft.AspNetCore.Components.Server" />
26+
<Reference Include="Microsoft.AspNetCore.Mvc" />
27+
<Reference Include="Microsoft.AspNetCore.SignalR" />
28+
</ItemGroup>
29+
30+
</Project>

0 commit comments

Comments
 (0)