Add Analyzer for banning `init;` props

turns out you can write to `readonly` fields from these, which is a
convention I'm not willing to break
This commit is contained in:
YoshiRulz 2025-07-30 02:36:35 +10:00
parent f89949520e
commit 0743a1f8ea
No known key found for this signature in database
GPG Key ID: C4DE31C245353FB7
11 changed files with 180 additions and 99 deletions

View File

@ -19,6 +19,8 @@ dotnet_diagnostic.BHI1005.severity = silent
dotnet_diagnostic.BHI1006.severity = error dotnet_diagnostic.BHI1006.severity = error
# Don't use target-typed new for throw expressions # Don't use target-typed new for throw expressions
dotnet_diagnostic.BHI1007.severity = error dotnet_diagnostic.BHI1007.severity = error
# Do not use init setter
dotnet_diagnostic.BHI1008.severity = error
# Don't call this.GetType() in sealed type, use typeof operator # Don't call this.GetType() in sealed type, use typeof operator
dotnet_diagnostic.BHI1100.severity = error dotnet_diagnostic.BHI1100.severity = error
# Don't call this.GetType(), use typeof operator (or replace subtype check with better encapsulation) # Don't call this.GetType(), use typeof operator (or replace subtype check with better encapsulation)

View File

@ -55,6 +55,14 @@ public class HawkSourceAnalyzer : DiagnosticAnalyzer
defaultSeverity: DiagnosticSeverity.Error, defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true); isEnabledByDefault: true);
private static readonly DiagnosticDescriptor DiagNoInitAccessor = new(
id: "BHI1008",
title: "Do not use init setter",
messageFormat: "Use a regular `set`ter (or add a constructor parameter)",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: false);
private static readonly DiagnosticDescriptor DiagNoQueryExpression = new( private static readonly DiagnosticDescriptor DiagNoQueryExpression = new(
id: "BHI1003", id: "BHI1003",
title: "Do not use query expression syntax", title: "Do not use query expression syntax",
@ -119,6 +127,7 @@ public class HawkSourceAnalyzer : DiagnosticAnalyzer
DiagNoAnonClasses, DiagNoAnonClasses,
DiagNoAnonDelegates, DiagNoAnonDelegates,
DiagNoDiscardingLocals, DiagNoDiscardingLocals,
DiagNoInitAccessor,
DiagNoQueryExpression, DiagNoQueryExpression,
DiagRecordImplicitlyRefType, DiagRecordImplicitlyRefType,
DiagSwitchShouldThrowIOE, DiagSwitchShouldThrowIOE,
@ -170,6 +179,9 @@ public class HawkSourceAnalyzer : DiagnosticAnalyzer
} }
switch (snac.Node) switch (snac.Node)
{ {
case AccessorDeclarationSyntax ads:
if (ads.Keyword.ToString() is "init") DiagNoInitAccessor.ReportAt(ads, snac);
break;
case AnonymousMethodExpressionSyntax: case AnonymousMethodExpressionSyntax:
DiagNoAnonDelegates.ReportAt(snac.Node, snac); DiagNoAnonDelegates.ReportAt(snac.Node, snac);
break; break;
@ -218,6 +230,7 @@ public class HawkSourceAnalyzer : DiagnosticAnalyzer
SyntaxKind.AnonymousObjectCreationExpression, SyntaxKind.AnonymousObjectCreationExpression,
SyntaxKind.AnonymousMethodExpression, SyntaxKind.AnonymousMethodExpression,
SyntaxKind.CollectionExpression, SyntaxKind.CollectionExpression,
SyntaxKind.InitAccessorDeclaration,
SyntaxKind.InterpolatedStringExpression, SyntaxKind.InterpolatedStringExpression,
SyntaxKind.ListPattern, SyntaxKind.ListPattern,
SyntaxKind.QueryExpression, SyntaxKind.QueryExpression,

View File

@ -101,6 +101,17 @@ public sealed class HawkSourceAnalyzerTests
} }
"""); """);
[TestMethod]
public Task CheckMisuseOfInitAccessor()
=> Verify.VerifyAnalyzerAsync("""
public sealed class Cases {
public int A { get; {|BHI1008:init;|} }
}
namespace System.Runtime.CompilerServices {
public static class IsExternalInit {} // this sample is compiled for lowest-common-denominator of `netstandard2.0`, so `init` accessor gives an error without this
}
""");
[TestMethod] [TestMethod]
public Task CheckMisuseOfInterpolatedString() public Task CheckMisuseOfInterpolatedString()
=> Verify.VerifyAnalyzerAsync(""" => Verify.VerifyAnalyzerAsync("""

Binary file not shown.

View File

@ -9,9 +9,9 @@ namespace BizHawk.Client.Common
{ {
private string? _ser = null; private string? _ser = null;
public bool AppendAllFilesEntry { get; init; } = true; public bool AppendAllFilesEntry { get; set; } = true;
public string? CombinedEntryDesc { get; init; } = null; public string? CombinedEntryDesc { get; set; } = null;
private IReadOnlyCollection<string> CombinedExts private IReadOnlyCollection<string> CombinedExts
=> Filters.SelectMany(static filter => filter.Extensions).Distinct().Order().ToList(); => Filters.SelectMany(static filter => filter.Extensions).Distinct().Order().ToList();

View File

@ -107,9 +107,11 @@ namespace BizHawk.Client.EmuHawk
private readonly uint _domainAddrStart; // addr of _domain where bank begins private readonly uint _domainAddrStart; // addr of _domain where bank begins
private readonly uint _addressMangler; // of course, let's *not* correct internal core byteswapping! private readonly uint _addressMangler; // of course, let's *not* correct internal core byteswapping!
public ReadMemoryFunc ReadFunc { get; protected init; } public virtual ReadMemoryFunc ReadFunc { get; }
public WriteMemoryFunc WriteFunc { get; protected init; }
public ReadMemoryBlockFunc ReadBlockFunc { get; protected init; } public virtual WriteMemoryFunc WriteFunc { get; }
public virtual ReadMemoryBlockFunc ReadBlockFunc { get; }
public uint StartAddress; // this is set for our rcheevos impl public uint StartAddress; // this is set for our rcheevos impl
public readonly uint BankSize; public readonly uint BankSize;
@ -192,13 +194,17 @@ namespace BizHawk.Client.EmuHawk
private class NullMemFunctions : MemFunctions private class NullMemFunctions : MemFunctions
{ {
public override ReadMemoryFunc ReadFunc
=> null;
public override WriteMemoryFunc WriteFunc
=> null;
public override ReadMemoryBlockFunc ReadBlockFunc
=> null;
public NullMemFunctions(long bankSize) public NullMemFunctions(long bankSize)
: base(null, 0, bankSize) : base(null, 0, bankSize) {}
{
ReadFunc = null;
WriteFunc = null;
ReadBlockFunc = null;
}
} }
// this is a complete hack because the libretro Intelli core sucks and so achievements are made expecting this format // this is a complete hack because the libretro Intelli core sucks and so achievements are made expecting this format

View File

@ -97,31 +97,31 @@ namespace BizHawk.Client.EmuHawk
private bool AlwaysDoubleSize private bool AlwaysDoubleSize
{ {
get => cbDoubleSize.Checked; get => cbDoubleSize.Checked;
init => cbDoubleSize.Checked = value; set => cbDoubleSize.Checked = value;
} }
private bool CropSGBFrame private bool CropSGBFrame
{ {
get => cbCropSGBFrame.Checked; get => cbCropSGBFrame.Checked;
init => cbCropSGBFrame.Checked = value; set => cbCropSGBFrame.Checked = value;
} }
private bool NoPPUSpriteLimit private bool NoPPUSpriteLimit
{ {
get => cbNoPPUSpriteLimit.Checked; get => cbNoPPUSpriteLimit.Checked;
init => cbNoPPUSpriteLimit.Checked = value; set => cbNoPPUSpriteLimit.Checked = value;
} }
private bool ShowOverscan private bool ShowOverscan
{ {
get => cbShowOverscan.Checked; get => cbShowOverscan.Checked;
init => cbShowOverscan.Checked = value; set => cbShowOverscan.Checked = value;
} }
private bool ShowCursor private bool ShowCursor
{ {
get => cbShowCursor.Checked; get => cbShowCursor.Checked;
init => cbShowCursor.Checked = value; set => cbShowCursor.Checked = value;
} }
private BsnesApi.ASPECT_RATIO_CORRECTION AspectRatioCorrection => (BsnesApi.ASPECT_RATIO_CORRECTION)AspectRatioCorrectionBox.SelectedIndex; private BsnesApi.ASPECT_RATIO_CORRECTION AspectRatioCorrection => (BsnesApi.ASPECT_RATIO_CORRECTION)AspectRatioCorrectionBox.SelectedIndex;
@ -129,43 +129,43 @@ namespace BizHawk.Client.EmuHawk
private bool Hotfixes private bool Hotfixes
{ {
get => cbGameHotfixes.Checked; get => cbGameHotfixes.Checked;
init => cbGameHotfixes.Checked = value; set => cbGameHotfixes.Checked = value;
} }
private bool FastPPU private bool FastPPU
{ {
get => cbFastPPU.Checked; get => cbFastPPU.Checked;
init => cbDoubleSize.Enabled = cbNoPPUSpriteLimit.Enabled = cbFastPPU.Checked = value; set => cbDoubleSize.Enabled = cbNoPPUSpriteLimit.Enabled = cbFastPPU.Checked = value;
} }
private bool FastDSP private bool FastDSP
{ {
get => cbFastDSP.Checked; get => cbFastDSP.Checked;
init => cbFastDSP.Checked = value; set => cbFastDSP.Checked = value;
} }
private bool FastCoprocessors private bool FastCoprocessors
{ {
get => cbFastCoprocessor.Checked; get => cbFastCoprocessor.Checked;
init => cbFastCoprocessor.Checked = value; set => cbFastCoprocessor.Checked = value;
} }
private bool UseSGB2 private bool UseSGB2
{ {
get => cbUseSGB2.Checked; get => cbUseSGB2.Checked;
init => cbUseSGB2.Checked = value; set => cbUseSGB2.Checked = value;
} }
private bool UseRealTime private bool UseRealTime
{ {
get => cbUseRealTime.Checked; get => cbUseRealTime.Checked;
init => cbUseRealTime.Checked = value; set => cbUseRealTime.Checked = value;
} }
private DateTime InitialTime private DateTime InitialTime
{ {
get => dtpInitialTime.Value; get => dtpInitialTime.Value;
init => dtpInitialTime.Value = value; set => dtpInitialTime.Value = value;
} }
private BsnesApi.ENTROPY Entropy => (BsnesApi.ENTROPY) EntropyBox.SelectedIndex; private BsnesApi.ENTROPY Entropy => (BsnesApi.ENTROPY) EntropyBox.SelectedIndex;
@ -174,19 +174,77 @@ namespace BizHawk.Client.EmuHawk
private BsnesCore.SATELLAVIEW_CARTRIDGE SatellaviewCartridge => (BsnesCore.SATELLAVIEW_CARTRIDGE)SatellaviewCartridgeBox.SelectedIndex; private BsnesCore.SATELLAVIEW_CARTRIDGE SatellaviewCartridge => (BsnesCore.SATELLAVIEW_CARTRIDGE)SatellaviewCartridgeBox.SelectedIndex;
private bool ShowObj1 { get => Obj1Checkbox.Checked; init => Obj1Checkbox.Checked = value; } private bool ShowObj1
private bool ShowObj2 { get => Obj2Checkbox.Checked; init => Obj2Checkbox.Checked = value; } {
private bool ShowObj3 { get => Obj3Checkbox.Checked; init => Obj3Checkbox.Checked = value; } get => Obj1Checkbox.Checked;
private bool ShowObj4 { get => Obj4Checkbox.Checked; init => Obj4Checkbox.Checked = value; } set => Obj1Checkbox.Checked = value;
}
private bool ShowBg1_0 { get => Bg1_0Checkbox.Checked; init => Bg1_0Checkbox.Checked = value; } private bool ShowObj2
private bool ShowBg1_1 { get => Bg1_1Checkbox.Checked; init => Bg1_1Checkbox.Checked = value; } {
private bool ShowBg2_0 { get => Bg2_0Checkbox.Checked; init => Bg2_0Checkbox.Checked = value; } get => Obj2Checkbox.Checked;
private bool ShowBg2_1 { get => Bg2_1Checkbox.Checked; init => Bg2_1Checkbox.Checked = value; } set => Obj2Checkbox.Checked = value;
private bool ShowBg3_0 { get => Bg3_0Checkbox.Checked; init => Bg3_0Checkbox.Checked = value; } }
private bool ShowBg3_1 { get => Bg3_1Checkbox.Checked; init => Bg3_1Checkbox.Checked = value; }
private bool ShowBg4_0 { get => Bg4_0Checkbox.Checked; init => Bg4_0Checkbox.Checked = value; } private bool ShowObj3
private bool ShowBg4_1 { get => Bg4_1Checkbox.Checked; init => Bg4_1Checkbox.Checked = value; } {
get => Obj3Checkbox.Checked;
set => Obj3Checkbox.Checked = value;
}
private bool ShowObj4
{
get => Obj4Checkbox.Checked;
set => Obj4Checkbox.Checked = value;
}
private bool ShowBg1_0
{
get => Bg1_0Checkbox.Checked;
set => Bg1_0Checkbox.Checked = value;
}
private bool ShowBg1_1
{
get => Bg1_1Checkbox.Checked;
set => Bg1_1Checkbox.Checked = value;
}
private bool ShowBg2_0
{
get => Bg2_0Checkbox.Checked;
set => Bg2_0Checkbox.Checked = value;
}
private bool ShowBg2_1
{
get => Bg2_1Checkbox.Checked;
set => Bg2_1Checkbox.Checked = value;
}
private bool ShowBg3_0
{
get => Bg3_0Checkbox.Checked;
set => Bg3_0Checkbox.Checked = value;
}
private bool ShowBg3_1
{
get => Bg3_1Checkbox.Checked;
set => Bg3_1Checkbox.Checked = value;
}
private bool ShowBg4_0
{
get => Bg4_0Checkbox.Checked;
set => Bg4_0Checkbox.Checked = value;
}
private bool ShowBg4_1
{
get => Bg4_1Checkbox.Checked;
set => Bg4_1Checkbox.Checked = value;
}
private void BtnOk_Click(object sender, EventArgs e) private void BtnOk_Click(object sender, EventArgs e)
{ {

View File

@ -17,10 +17,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
BSNES_INPUT_DEVICE.None => new BsnesUnpluggedController(), BSNES_INPUT_DEVICE.None => new BsnesUnpluggedController(),
BSNES_INPUT_DEVICE.Gamepad => new BsnesController(), BSNES_INPUT_DEVICE.Gamepad => new BsnesController(),
BSNES_INPUT_DEVICE.ExtendedGamepad => new BsnesExtendedController(), BSNES_INPUT_DEVICE.ExtendedGamepad => new BsnesExtendedController(),
BSNES_INPUT_DEVICE.Mouse => new BsnesMouseController BSNES_INPUT_DEVICE.Mouse => new BsnesMouseController(LimitAnalogChangeSensitivity: ss.LimitAnalogChangeSensitivity),
{
LimitAnalogChangeSensitivity = ss.LimitAnalogChangeSensitivity
},
BSNES_INPUT_DEVICE.SuperMultitap => new BsnesMultitapController(), BSNES_INPUT_DEVICE.SuperMultitap => new BsnesMultitapController(),
BSNES_INPUT_DEVICE.Payload => new BsnesPayloadController(), BSNES_INPUT_DEVICE.Payload => new BsnesPayloadController(),
BSNES_INPUT_DEVICE.SuperScope => new BsnesSuperScopeController(), BSNES_INPUT_DEVICE.SuperScope => new BsnesSuperScopeController(),
@ -171,14 +168,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
} }
} }
internal class BsnesMouseController : IBsnesController internal class BsnesMouseController(bool LimitAnalogChangeSensitivity = true) : IBsnesController
{ {
private static readonly ControllerDefinition _definition = new ControllerDefinition("(SNES Controller fragment)") private static readonly ControllerDefinition _definition = new ControllerDefinition("(SNES Controller fragment)")
{ BoolButtons = { "0Mouse Left", "0Mouse Right" } } { BoolButtons = { "0Mouse Left", "0Mouse Right" } }
.AddXYPair("0Mouse {0}", AxisPairOrientation.RightAndDown, (-127).RangeTo(127), 0); .AddXYPair("0Mouse {0}", AxisPairOrientation.RightAndDown, (-127).RangeTo(127), 0);
public ControllerDefinition Definition => _definition; public ControllerDefinition Definition => _definition;
public bool LimitAnalogChangeSensitivity { get; init; } = true;
public short GetState(IController controller, int index, int id) public short GetState(IController controller, int index, int id)
{ {

View File

@ -642,24 +642,22 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
{ {
throw new InvalidOperationException("Unexpected error in gambatte_getmemoryarea"); throw new InvalidOperationException("Unexpected error in gambatte_getmemoryarea");
} }
return new GPUMemoryAreas return new GPUMemoryAreas(vram: _vram, oam: _oam, sppal: _sppal, bgpal: _bgpal);
{
Vram = _vram,
Oam = _oam,
Sppal = _sppal,
Bgpal = _bgpal,
};
} }
private class GPUMemoryAreas : IGPUMemoryAreas private sealed class GPUMemoryAreas(IntPtr vram, IntPtr oam, IntPtr sppal, IntPtr bgpal) : IGPUMemoryAreas
{ {
public IntPtr Vram { get; init; } public IntPtr Vram
=> vram;
public IntPtr Oam { get; init; } public IntPtr Oam
=> oam;
public IntPtr Sppal { get; init; } public IntPtr Sppal
=> sppal;
public IntPtr Bgpal { get; init; } public IntPtr Bgpal
=> bgpal;
public void Dispose() {} public void Dispose() {}
} }

View File

@ -350,45 +350,45 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
public BGInfos BG = new BGInfos(); public BGInfos BG = new BGInfos();
public int Mode { get; init; } public int Mode { get; internal set; }
public bool Mode1_BG3_Priority { get; init; } public bool Mode1_BG3_Priority { get; internal set; }
public bool SETINI_Mode7ExtBG { get; init; } public bool SETINI_Mode7ExtBG { get; internal set; }
public bool SETINI_HiRes { get; init; } public bool SETINI_HiRes { get; internal set; }
public bool SETINI_Overscan { get; init; } public bool SETINI_Overscan { get; internal set; }
public bool SETINI_ObjInterlace { get; init; } public bool SETINI_ObjInterlace { get; internal set; }
public bool SETINI_ScreenInterlace { get; init; } public bool SETINI_ScreenInterlace { get; internal set; }
public int CGWSEL_ColorMask { get; init; } public int CGWSEL_ColorMask { get; internal set; }
public int CGWSEL_ColorSubMask { get; init; } public int CGWSEL_ColorSubMask { get; internal set; }
public int CGWSEL_AddSubMode { get; init; } public int CGWSEL_AddSubMode { get; internal set; }
public bool CGWSEL_DirectColor { get; init; } public bool CGWSEL_DirectColor { get; internal set; }
public int CGADSUB_AddSub { get; init; } public int CGADSUB_AddSub { get; internal set; }
public bool CGADSUB_Half { get; init; } public bool CGADSUB_Half { get; internal set; }
public int OBSEL_Size { get; init; } public int OBSEL_Size { get; internal set; }
public int OBSEL_NameSel { get; init; } public int OBSEL_NameSel { get; internal set; }
public int OBSEL_NameBase { get; init; } public int OBSEL_NameBase { get; internal set; }
public int OBJTable0Addr { get; init; } public int OBJTable0Addr { get; internal set; }
public int OBJTable1Addr { get; init; } public int OBJTable1Addr { get; internal set; }
public bool OBJ_MainEnabled { get; init; } public bool OBJ_MainEnabled { get; internal set; }
public bool OBJ_SubEnabled { get; init; } public bool OBJ_SubEnabled { get; internal set; }
public bool OBJ_MathEnabled { get; init; } public bool OBJ_MathEnabled { get; internal set; }
public bool BK_MathEnabled { get; init; } public bool BK_MathEnabled { get; internal set; }
public int M7HOFS { get; init; } public int M7HOFS { get; internal set; }
public int M7VOFS { get; init; } public int M7VOFS { get; internal set; }
public int M7A { get; init; } public int M7A { get; internal set; }
public int M7B { get; init; } public int M7B { get; internal set; }
public int M7C { get; init; } public int M7C { get; internal set; }
public int M7D { get; init; } public int M7D { get; internal set; }
public int M7X { get; init; } public int M7X { get; internal set; }
public int M7Y { get; init; } public int M7Y { get; internal set; }
public int M7SEL_REPEAT { get; init; } public int M7SEL_REPEAT { get; internal set; }
public bool M7SEL_HFLIP { get; init; } public bool M7SEL_HFLIP { get; internal set; }
public bool M7SEL_VFLIP { get; init; } public bool M7SEL_VFLIP { get; internal set; }
} }
public static readonly int[,] ModeBpps = { public static readonly int[,] ModeBpps = {

View File

@ -270,25 +270,22 @@ namespace BizHawk.Emulation.Cores.Nintendo.Sameboy
{ {
throw new InvalidOperationException("Unexpected error in sameboy_getmemoryarea"); throw new InvalidOperationException("Unexpected error in sameboy_getmemoryarea");
} }
return new GPUMemoryAreas(vram: _vram, oam: _oam, sppal: _sppal, bgpal: _bgpal);
return new GPUMemoryAreas()
{
Vram = _vram,
Oam = _oam,
Sppal = _sppal,
Bgpal = _bgpal
};
} }
private class GPUMemoryAreas : IGPUMemoryAreas private sealed class GPUMemoryAreas(IntPtr vram, IntPtr oam, IntPtr sppal, IntPtr bgpal) : IGPUMemoryAreas
{ {
public IntPtr Vram { get; init; } public IntPtr Vram
=> vram;
public IntPtr Oam { get; init; } public IntPtr Oam
=> oam;
public IntPtr Sppal { get; init; } public IntPtr Sppal
=> sppal;
public IntPtr Bgpal { get; init; } public IntPtr Bgpal
=> bgpal;
public void Dispose() {} public void Dispose() {}
} }