I’m trying my hand at packaging a working Roslyn code analyzer, and I’m afraid it’s not going so well. I’ve run out of things to try.
What I have so far I’ve largely gleaned from ChatGPT, who of course is supremely confident in the accuracy of his answers, so he’s likely the first culprit in this head-scratcher.
I’ve verified the apparently-important bits against this blog post, to include the PowerShell scripts. According to my Package Manager output, however, the scripts don’t appear to be running when I install the package into the consuming project (Console1
):
Running restore with 20 concurrent jobs.
Reading project file D:DevProjectsCommonLibrariesIntexx.EmailValidatorConsoleApp1ConsoleApp1.vbproj.
Restoring packages for D:DevProjectsCommonLibrariesIntexx.EmailValidatorConsoleApp1ConsoleApp1.vbproj...
Restoring packages for .NETCoreApp,Version=v8.0...
Resolving conflicts for net8.0...
Acquiring lock for the installation of Intexx.EmailValidator 1.0.69
Acquired lock for the installation of Intexx.EmailValidator 1.0.69
Installed Intexx.EmailValidator 1.0.69 from D:DevProjectsCommonLibrariesIntexx.EmailValidatorIntexx.EmailValidatorbinDebug to D:DevPackagesintexx.emailvalidator1.0.69 with content hash xqaUYidXPgY7yeWTK5NonM8Bch7sYVQgfdBXC/xqJKlOqoBWDS4SDSLzf+gI+4IKSU2C6FN20Xmw1QEnOCHBjg==.
CACHE https://api.nuget.org/v3/vulnerabilities/index.json
CACHE https://api.nuget.org/v3-vulnerabilities/2024.06.05.23.37.01/vulnerability.base.json
CACHE https://api.nuget.org/v3-vulnerabilities/2024.06.05.23.37.01/2024.06.07.11.37.04/vulnerability.update.json
All packages and projects are compatible with net8.0.
Installing NuGet package Intexx.EmailValidator 1.0.69.
Committing restore...
Generating MSBuild file D:DevProjectsCommonLibrariesIntexx.EmailValidatorConsoleApp1objConsoleApp1.vbproj.nuget.g.props.
Generating MSBuild file D:DevProjectsCommonLibrariesIntexx.EmailValidatorConsoleApp1objConsoleApp1.vbproj.nuget.g.targets.
Writing assets file to disk. Path: D:DevProjectsCommonLibrariesIntexx.EmailValidatorConsoleApp1objproject.assets.json
Writing cache file to disk. Path: D:DevProjectsCommonLibrariesIntexx.EmailValidatorConsoleApp1objproject.nuget.cache
Persisting dg to D:DevProjectsCommonLibrariesIntexx.EmailValidatorConsoleApp1objConsoleApp1.vbproj.nuget.dgspec.json
Restored D:DevProjectsCommonLibrariesIntexx.EmailValidatorConsoleApp1ConsoleApp1.vbproj (in 49 ms).
Successfully uninstalled 'Intexx.EmailValidator 1.0.67' from ConsoleApp1
Successfully installed 'Intexx.EmailValidator 1.0.69' to ConsoleApp1
Executing nuget actions took 41 ms
Time Elapsed: 00:00:00.2669689
========== Finished ==========
Restoring NuGet packages...
Running restore with 20 concurrent jobs.
Reading project file D:DevProjectsCommonLibrariesIntexx.EmailValidatorConsoleApp1ConsoleApp1.vbproj.
The restore inputs for 'ConsoleApp1' have not changed. No further actions are required to complete the restore.
Committing restore...
Assets file has not changed. Skipping assets file writing. Path: D:DevProjectsCommonLibrariesIntexx.EmailValidatorConsoleApp1objproject.assets.json
No-Op restore. The cache will not be updated. Path: D:DevProjectsCommonLibrariesIntexx.EmailValidatorConsoleApp1objproject.nuget.cache
Restored D:DevProjectsCommonLibrariesIntexx.EmailValidatorConsoleApp1ConsoleApp1.vbproj (in 0.5 ms).
NuGet Config files used:
C:UsersWorkAppDataRoamingNuGetNuGet.Config
C:Program Files (x86)NuGetConfigMicrosoft.VisualStudio.FallbackLocation.config
C:Program Files (x86)NuGetConfigMicrosoft.VisualStudio.Offline.config
Feeds used:
https://api.nuget.org/v3/index.json
https://server5/InteXX/_packaging/Packages/nuget/v3/index.json
C:Program Files (x86)Microsoft SDKsNuGetPackages
D:DevProjectsCommonLibrariesIntexx.EmailValidatorIntexx.EmailValidatorbinDebug
C:Program Filesdotnetlibrary-packs
Time Elapsed: 00:00:00.0467482
========== Finished ==========
Further, I tried manually copying Analyzers.dll
to $(OutputPath)analyzers/dotnet/vb/
, but that didn’t make any difference. For reasons unknown to me, my analyzer refuses to run.
Also, I’m not sure what that install.ps1
script is doing with this call:
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
Is the consuming project file supposed to be altered somehow at package installation time? Maybe that’s the key; the official documentation doesn’t specify one way or another. The sample package found there is quite complex and doesn’t offer much help to a beginner.
I did try creating a project based on the Analyer template, as discussed here, but again the complexity of that is too much to grasp for a beginner. I can’t escape a nagging feeling that this should be a simple matter of getting a small thing just right. Easy when you know how, right?
Which is what’s frustrating about the other post—it provides an extremely simple example to follow. Yet when I replicate it, my version doesn’t work.
What am I doing wrong? How can I get my analyzer to run, given the setup that I have so far?
Here’re my files:
Analyzer
Imports System
Imports System.Collections.Immutable
Imports System.Linq
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Analyzers
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Public Class ProhibitValidator
Inherits DiagnosticAnalyzer
Shared Sub New()
MessageFormat = "Class 'Intexx.Email.Validation.Server.Validator' is only allowed in projects targeting .NET Framework 4.8 or earlier."
Description = "This class is prohibited in projects targeting frameworks later than .NET Framework 4.8."
Title = "Prohibited class usage"
Rule = New DiagnosticDescriptor(
isEnabledByDefault:=True,
defaultSeverity:=DiagnosticSeverity.Error,
messageFormat:=MessageFormat,
description:=Description,
category:=CATEGORY,
title:=Title,
id:=DIAGNOSTIC_ID
)
End Sub
Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor)
Get
Return ImmutableArray.Create(Rule)
End Get
End Property
Public Overrides Sub Initialize(Context As AnalysisContext)
Context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None)
Context.EnableConcurrentExecution()
Context.RegisterSyntaxNodeAction(Me.AnalyzeNode, SyntaxKind.ObjectCreationExpression)
End Sub
Private Function AnalyzeNode() As Action(Of SyntaxNodeAnalysisContext)
Dim oObjectCreationExpression As ObjectCreationExpressionSyntax
Dim oValidatorSymbol As INamedTypeSymbol
Dim sTargetFramework As String
Dim oUnknownSymbol As INamedTypeSymbol
Dim oDiagnostic As Diagnostic
Return Sub(Context)
oObjectCreationExpression = Context.Node
oUnknownSymbol = TryCast(Context.SemanticModel.GetSymbolInfo(oObjectCreationExpression.Type).Symbol, INamedTypeSymbol)
If oUnknownSymbol IsNot Nothing Then
oValidatorSymbol = Context.Compilation.GetTypeByMetadataName("Intexx.Email.Validation.Server.Validator")
' Check whether the symbol matches the prohibited class
If oValidatorSymbol IsNot Nothing AndAlso SymbolEqualityComparer.Default.Equals(oUnknownSymbol, oValidatorSymbol) Then
sTargetFramework = Context.Options.AdditionalFiles.FirstOrDefault?.Path
If sTargetFramework IsNot Nothing AndAlso Not sTargetFramework = "net48" Then
oDiagnostic = Diagnostic.Create(Rule, oObjectCreationExpression.GetLocation)
Context.ReportDiagnostic(oDiagnostic)
End If
End If
End If
End Sub
End Function
Public Const DIAGNOSTIC_ID As String = "PCU001"
Private Const CATEGORY As String = "Usage"
Private Shared ReadOnly MessageFormat As LocalizableString
Private Shared ReadOnly Description As LocalizableString
Private Shared ReadOnly Title As LocalizableString
Private Shared ReadOnly Rule As DiagnosticDescriptor
End Class
End Namespace
Package Project
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<DisableImplicitNamespaceImports>True</DisableImplicitNamespaceImports>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<AppDesignerFolder>Properties</AppDesignerFolder>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace></RootNamespace>
<OutputType>Library</OutputType>
<IsPackable>True</IsPackable>
<Version>1.0.69</Version>
</PropertyGroup>
<ItemGroup>
<Folder Include="Properties" />
</ItemGroup>
<ItemGroup>
<Reference Include="HexDns.NET">
<HintPath>HexillionHexDns.NET.dll</HintPath>
</Reference>
<Reference Include="HexNetwork.NET">
<HintPath>HexillionHexNetwork.NET.dll</HintPath>
</Reference>
<Reference Include="HexValidEmail.NET">
<HintPath>HexillionHexValidEmail.NET.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="HexillionHexDns.NET.dll">
<Pack>true</Pack>
<PackagePath>libnetstandard2.0HexDns.NET.dll</PackagePath>
</None>
<None Include="HexillionHexNetwork.NET.dll">
<Pack>true</Pack>
<PackagePath>libnetstandard2.0HexNetwork.NET.dll</PackagePath>
</None>
<None Include="HexillionHexValidEmail.NET.dll">
<Pack>true</Pack>
<PackagePath>libnetstandard2.0HexValidEmail.NET.dll</PackagePath>
</None>
<Content Include="HexValidEmail.NET.lic">
<Pack>true</Pack>
<PackagePath>contentFilesanyany;content</PackagePath>
<PackageCopyToOutput>true</PackageCopyToOutput>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Update="buildIntexx.EmailValidator.targets">
<Pack>True</Pack>
<BuildAction>Content</BuildAction>
<PackagePath>buildIntexx.EmailValidator.targets</PackagePath>
</None>
</ItemGroup>
<Target Name="PrintOutputPath" AfterTargets="Build">
<Message Importance="high" Text="OutputPath: $(OutputPath)" />
</Target>
<ItemGroup>
<None Update="tools*.ps1" CopyToOutputDirectory="Always" Pack="true" PackagePath="" />
<None Include="$(OutputPath)Analyzers.dll" Pack="True" PackagePath="analyzersdotnetvb" Visible="False" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Intexx.IO" Version="2024.6.3.1" />
<PackageReference Include="itxcorlib" Version="2024.6.5.1" />
<PackageReference Include="Microsoft.Build.Framework" Version="17.10.4" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.10.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..AnalyzersAnalyzers.vbproj" />
</ItemGroup>
</Project>
.targets
File
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask
AssemblyFile="$(ProjectDir)$(OutputPath)Intexx.EmailValidator.dll"
TaskName="CheckFrameworkForClassTask" />
<Target Name="CheckFrameworkForClass" BeforeTargets="Build">
<CheckFrameworkForClassTask
TargetFramework="$(TargetFramework)"
StartingFolder="$(ProjectDir)"
ErrorMessage="The validation server component only supports applications targeting .NET Framework 4.8." />
</Target>
</Project>
Consuming Project
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>ConsoleApp1</RootNamespace>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Intexx.EmailValidator" Version="1.0.69" />
</ItemGroup>
</Project>
Package Contents