Skip to content

Instantly share code, notes, and snippets.

@adammyhre
Created March 9, 2025 13:11
Show Gist options
  • Save adammyhre/8bbaec4ed012579f7e31a758681b9ae8 to your computer and use it in GitHub Desktop.
Save adammyhre/8bbaec4ed012579f7e31a758681b9ae8 to your computer and use it in GitHub Desktop.
Roslyn Fundamentals
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference ExcludeAssets="runtime" Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
<PackageReference ExcludeAssets="runtime" Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.0"/>
</ItemGroup>
<Target Name="CustomAfterBuild" AfterTargets="Build">
<ItemGroup>
<_FilesToCopy Include="$(OutputPath)**\$(AssemblyName).dll"/>
<_FilesToCopy Include="$(OutputPath)**\$(AssemblyName).pdb"/>
</ItemGroup>
<Copy SourceFiles="@(_FilesToCopy)" DestinationFolder="$(OutputPath)..\..\..\..\..\.."/>
</Target>
</Project>
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;
using System.CodeDom.Compiler;
using System.IO;
namespace ExampleGenerators;
[Generator]
public class ExampleSourceGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var assemblyName = context.CompilationProvider.Select(static (compilation, _) => compilation.AssemblyName);
context.RegisterSourceOutput(assemblyName, (spc, currentAssemblyName) =>
{
if (currentAssemblyName == "Assembly-CSharp")
{
spc.AddSource("ExampleSourceGenerator.g.cs", GenerateSource());
}
});
}
SourceText GenerateSource()
{
using var sourceStream = new StringWriter();
using var codeWriter = new IndentedTextWriter(sourceStream);
codeWriter.WriteLine("using System;");
codeWriter.WriteLine("namespace ExampleSourceGenerated {");
codeWriter.Indent++;
codeWriter.WriteLine("public static class ExampleSourceGenerated {");
codeWriter.Indent++;
codeWriter.WriteLine("public static string GetTestText()");
codeWriter.WriteLine("{");
codeWriter.Indent++;
codeWriter.WriteLine("return \"This is from incremental generator - Generated at build time\";");
codeWriter.Indent--;
codeWriter.WriteLine("}");
codeWriter.Indent--;
codeWriter.WriteLine("}");
codeWriter.Indent--;
codeWriter.WriteLine("}");
return SourceText.From(sourceStream.ToString(), Encoding.UTF8);
}
}
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Linq;
namespace ExampleGenerators;
[Generator]
public class UnityPerformanceAnalyzer : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var syntaxProvider = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (node, _) => IsPotentialMonoBehaviour(node),
transform: static (context, _) => (ClassDeclarationSyntax)context.Node
)
.Where(static node => node != null);
context.RegisterSourceOutput(syntaxProvider, (ctx, classNode) =>
{
if (HasEmptyUpdateMethod(classNode))
{
var diagnostic = Diagnostic.Create(
EmptyUpdateRule,
classNode.Identifier.GetLocation(),
classNode.Identifier.Text
);
ctx.ReportDiagnostic(diagnostic);
}
if (HasExcessiveDebugLogs(classNode)) {
var diagnostic = Diagnostic.Create(
ExcessiveLogsRule,
classNode.Identifier.GetLocation(),
classNode.Identifier.Text
);
ctx.ReportDiagnostic(diagnostic);
}
});
}
static bool IsPotentialMonoBehaviour(SyntaxNode node) =>
node is ClassDeclarationSyntax classDecl &&
classDecl.BaseList?.Types.Any(t => t.Type.ToString() == "MonoBehaviour") == true;
static bool HasEmptyUpdateMethod(ClassDeclarationSyntax classDecl) =>
classDecl.Members.OfType<MethodDeclarationSyntax>()
.Any(m => m.Identifier.Text == "Update" && m.Body?.Statements.Count == 0);
static bool HasExcessiveDebugLogs(ClassDeclarationSyntax classDecl) =>
classDecl.Members.OfType<MethodDeclarationSyntax>()
.SelectMany(m => m.Body?.Statements.OfType<ExpressionStatementSyntax>() ?? [])
.Count(expr =>
expr.Expression is InvocationExpressionSyntax invocation &&
invocation.Expression.ToString().Contains("Debug.Log")) >= 3;
static readonly DiagnosticDescriptor EmptyUpdateRule = new(
id: "UPA001",
title: "Empty Update() Detected",
messageFormat: "Class '{0}' contains an empty Update() method, which may harm performance",
category: "Performance",
DiagnosticSeverity.Warning,
isEnabledByDefault: true
);
static readonly DiagnosticDescriptor ExcessiveLogsRule = new(
id: "UPA002",
title: "Excessive Debug.Log() Calls",
messageFormat: "Class '{0}' has excessive Debug.Log() calls, which may impact performance",
category: "Performance",
DiagnosticSeverity.Warning,
isEnabledByDefault: true
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment