C
C#5mo ago
Octal!

Writing custom codestyle checkers?

How would I go about doing this? Are there any libraries to help?
24 Replies
Jimmacle
Jimmacle5mo ago
you'd want to look into roslyn analyzers and #roslyn
Thinker
Thinker5mo ago
Tutorial: Write your first analyzer and code fix - C#
This tutorial provides step-by-step instructions to build an analyzer and code fix using the .NET Compiler SDK (Roslyn APIs).
Thinker
Thinker5mo ago
Essentially, you write a library which acts as an extension to the compiler, analyzing the user's code for things like whitespace or style, and report diagnostics when you find something offending. (also nice protogen pfp)
Octal!
Octal!5mo ago
How do I run it without using visual studio? I'm on Linux and prefer to use cli anyways
jcotton42
jcotton425mo ago
analyzers are also run during builds
Thinker
Thinker5mo ago
yeah
jcotton42
jcotton425mo ago
plus any editor you use (unless it's just a plain text editor with no C# awareness) can show them eg they'll work just fine in Rider or VSCode
Thinker
Thinker5mo ago
As long as you reference the project containing your analyzers properly, it'll work
Octal!
Octal!5mo ago
How do I know if I'm doing that right I have a rule set to always error so I can make sure it's working but it's not erroring when I build
jcotton42
jcotton425mo ago
show us your csproj
Octal!
Octal!5mo ago
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Voxel.CodeChecker</RootNamespace>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
</ItemGroup>

<Target Name="_AddAnalyzersToOutput">
<ItemGroup>
<TfmSpecificPackageFile Include="$(OutputPath)\CodeChecker.dll" />
</ItemGroup>
</Target>

</Project>
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Voxel.CodeChecker</RootNamespace>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
</ItemGroup>

<Target Name="_AddAnalyzersToOutput">
<ItemGroup>
<TfmSpecificPackageFile Include="$(OutputPath)\CodeChecker.dll" />
</ItemGroup>
</Target>

</Project>
Thinker
Thinker5mo ago
Analyzer projects have to target netstandard2.0
Octal!
Octal!5mo ago
Is there a reason for that?
MODiX
MODiX5mo ago
jaredpar
The biggest blockers to shifting analyzers / generators from netstandard2.0 to net6+ is 1. The VS OOP process. This can run as a .NET core process but believe you can still push it back to .NET Framework too. Honestly lost track of which is the default now but so long as it can run on .NET Framework it's a blocker 2. The VS process still hosts generators at least and it's a .NET Framework process. 3. The VS Build infrastructure, think typing MSBuild at the command line, is still a .NET Framework entity. There is no supported way to have tools within the build infra depend on the VS private .NET Core runtime.
Quoted by
<@334287249816420352> from #roslyn (click here)
React with ❌ to remove this embed.
jcotton42
jcotton425mo ago
so they can run in VS they might work in other contexts without ns2.0, but it's best to just stick to that also, can we see the csproj for the project consuming the analyzer (assuming fixing the TargetFramework doesn't fix your issue)
Octal!
Octal!5mo ago
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RollForward>Major</RollForward>
<PublishReadyToRun>false</PublishReadyToRun>
<RootNamespace>Voxel.Client</RootNamespace>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>CLIENT</DefineConstants>
<LangVersion>11</LangVersion>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
<PackageReference Include="NLog" Version="5.2.7"/>
<PackageReference Include="PeterO.Cbor" Version="4.5.2"/>
<PackageReference Include="Tomlyn" Version="0.16.2"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CodeChecker\CodeChecker.csproj"/>

<ProjectReference Include="..\Common\Common.csproj"/>
<ProjectReference Include="..\Core\Core.csproj"/>
</ItemGroup>

<!-- Builds the assets for the project-->
<Target Name="BuildAssets" BeforeTargets="BeforeBuild">
<Exec Command="dotnet run" WorkingDirectory="../AssetBuilder"/>
</Target>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RollForward>Major</RollForward>
<PublishReadyToRun>false</PublishReadyToRun>
<RootNamespace>Voxel.Client</RootNamespace>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>CLIENT</DefineConstants>
<LangVersion>11</LangVersion>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
<PackageReference Include="NLog" Version="5.2.7"/>
<PackageReference Include="PeterO.Cbor" Version="4.5.2"/>
<PackageReference Include="Tomlyn" Version="0.16.2"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CodeChecker\CodeChecker.csproj"/>

<ProjectReference Include="..\Common\Common.csproj"/>
<ProjectReference Include="..\Core\Core.csproj"/>
</ItemGroup>

<!-- Builds the assets for the project-->
<Target Name="BuildAssets" BeforeTargets="BeforeBuild">
<Exec Command="dotnet run" WorkingDirectory="../AssetBuilder"/>
</Target>
</Project>
jcotton42
jcotton425mo ago
the ProjectReference needs to be a little different
jcotton42
jcotton425mo ago
Meziantou's blog
Referencing an analyzer from a project - Gérald Barré
Once you have created a Roslyn Analyzer, you have multiple ways to consume it in your project: Using a Visual Studio extension Using a NuGet package Using a Project Reference when the Roslyn Analyzer is in the same solution The first 2 solutions are the most common ones, and are described in the first post of the series: Writing a Roslyn Analyze...
jcotton42
jcotton425mo ago
<ProjectReference Include="..\Analyzer1\Analyzer1.csproj"
PrivateAssets="all"
ReferenceOutputAssembly="false"
OutputItemType="Analyzer"
SetTargetFramework="TargetFramework=netstandard2.0" />
<ProjectReference Include="..\Analyzer1\Analyzer1.csproj"
PrivateAssets="all"
ReferenceOutputAssembly="false"
OutputItemType="Analyzer"
SetTargetFramework="TargetFramework=netstandard2.0" />
(I don't think you need SetTargetFramework tho)
Octal!
Octal!5mo ago
I'm getting this error now: CSC : warning AD0001: Analyzer 'Voxel.CodeChecker.Checker' threw an exception of type 'System.NullReferenceException' with message 'Object reference not set to an instance of an object.'.
jcotton42
jcotton425mo ago
you've got a bug in your analyzer then
Octal!
Octal!5mo ago
namespace Voxel.CodeChecker {
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class Checker : DiagnosticAnalyzer {
private static readonly DiagnosticDescriptor ReadonlyPascalCase = new DiagnosticDescriptor(
"ReadonlyPascalCase",
"Code Naming Scheme",
"Readonly / Const should be changed to PascalCase",
"naming",
DiagnosticSeverity.Error,
true
);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => new ImmutableArray<DiagnosticDescriptor> {
ReadonlyPascalCase
};

public override void Initialize(AnalysisContext context) {
}
}
}
namespace Voxel.CodeChecker {
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class Checker : DiagnosticAnalyzer {
private static readonly DiagnosticDescriptor ReadonlyPascalCase = new DiagnosticDescriptor(
"ReadonlyPascalCase",
"Code Naming Scheme",
"Readonly / Const should be changed to PascalCase",
"naming",
DiagnosticSeverity.Error,
true
);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => new ImmutableArray<DiagnosticDescriptor> {
ReadonlyPascalCase
};

public override void Initialize(AnalysisContext context) {
}
}
}
This is all I have, code-wise right now
jcotton42
jcotton425mo ago
huh, weird I'd check the docs for how to debug analyzers #roslyn can also help
Octal!
Octal!5mo ago
I'm gonna take a break and look at it again once my head is clear