From 13f31e8b725e46ca8442a943a5675723d22b4fdc Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 10 Apr 2018 18:23:57 -0400 Subject: warn for fields which no longer work (#471) --- .../Mock/StardewValley/Farmer.cs | 11 ++ .../NetFieldAnalyzerTests.cs | 140 +++++++++++++++++++++ .../ObsoleteFieldAnalyzerTests.cs | 88 +++++++++++++ .../UnitTests.cs | 140 --------------------- 4 files changed, 239 insertions(+), 140 deletions(-) create mode 100644 src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Farmer.cs create mode 100644 src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs create mode 100644 src/SMAPI.ModBuildConfig.Analyzer.Tests/ObsoleteFieldAnalyzerTests.cs delete mode 100644 src/SMAPI.ModBuildConfig.Analyzer.Tests/UnitTests.cs (limited to 'src/SMAPI.ModBuildConfig.Analyzer.Tests') diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Farmer.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Farmer.cs new file mode 100644 index 00000000..e0f0e30c --- /dev/null +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Farmer.cs @@ -0,0 +1,11 @@ +// ReSharper disable CheckNamespace, InconsistentNaming -- matches Stardew Valley's code +using System.Collections.Generic; + +namespace StardewValley +{ + /// A simplified version of Stardew Valley's StardewValley.Farmer class for unit testing. + internal class Farmer + { + public IDictionary friendships; + } +} diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs new file mode 100644 index 00000000..101f4c21 --- /dev/null +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs @@ -0,0 +1,140 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using NUnit.Framework; +using SMAPI.ModBuildConfig.Analyzer.Tests.Framework; +using StardewModdingAPI.ModBuildConfig.Analyzer; + +namespace SMAPI.ModBuildConfig.Analyzer.Tests +{ + /// Unit tests for . + [TestFixture] + public class NetFieldAnalyzerTests : DiagnosticVerifier + { + /********* + ** Properties + *********/ + /// Sample C# mod code, with a {{test-code}} placeholder for the code in the Entry method to test. + const string SampleProgram = @" + using System; + using StardewValley; + using Netcode; + using SObject = StardewValley.Object; + + namespace SampleMod + { + class ModEntry + { + public void Entry() + { + {{test-code}} + } + } + } + "; + + /// The line number where the unit tested code is injected into . + private const int SampleCodeLine = 13; + + /// The column number where the unit tested code is injected into . + private const int SampleCodeColumn = 25; + + + /********* + ** Unit tests + *********/ + /// Test that no diagnostics are raised for an empty code block. + [TestCase] + public void EmptyCode_HasNoDiagnostics() + { + // arrange + string test = @""; + + // assert + this.VerifyCSharpDiagnostic(test); + } + + /// Test that the expected diagnostic message is raised for implicit net field comparisons. + /// The code line to test. + /// The column within the code line where the diagnostic message should be reported. + /// The expression which should be reported. + /// The source type name which should be reported. + /// The target type name which should be reported. + [TestCase("Item item = null; if (item.netIntField < 42);", 22, "item.netIntField", "NetInt", "int")] + [TestCase("Item item = null; if (item.netIntField <= 42);", 22, "item.netIntField", "NetInt", "int")] + [TestCase("Item item = null; if (item.netIntField > 42);", 22, "item.netIntField", "NetInt", "int")] + [TestCase("Item item = null; if (item.netIntField >= 42);", 22, "item.netIntField", "NetInt", "int")] + [TestCase("Item item = null; if (item.netIntField == 42);", 22, "item.netIntField", "NetInt", "int")] + [TestCase("Item item = null; if (item.netIntField != 42);", 22, "item.netIntField", "NetInt", "int")] + [TestCase("Item item = null; if (item?.netIntField != 42);", 22, "item?.netIntField", "NetInt", "int")] + [TestCase("Item item = null; if (item?.netIntField != null);", 22, "item?.netIntField", "NetInt", "object")] + [TestCase("Item item = null; if (item.netIntProperty < 42);", 22, "item.netIntProperty", "NetInt", "int")] + [TestCase("Item item = null; if (item.netIntProperty <= 42);", 22, "item.netIntProperty", "NetInt", "int")] + [TestCase("Item item = null; if (item.netIntProperty > 42);", 22, "item.netIntProperty", "NetInt", "int")] + [TestCase("Item item = null; if (item.netIntProperty >= 42);", 22, "item.netIntProperty", "NetInt", "int")] + [TestCase("Item item = null; if (item.netIntProperty == 42);", 22, "item.netIntProperty", "NetInt", "int")] + [TestCase("Item item = null; if (item.netIntProperty != 42);", 22, "item.netIntProperty", "NetInt", "int")] + [TestCase("Item item = null; if (item?.netIntProperty != 42);", 22, "item?.netIntProperty", "NetInt", "int")] + [TestCase("Item item = null; if (item?.netIntProperty != null);", 22, "item?.netIntProperty", "NetInt", "object")] + [TestCase("Item item = null; if (item.netRefField == null);", 22, "item.netRefField", "NetRef", "object")] + [TestCase("Item item = null; if (item.netRefField != null);", 22, "item.netRefField", "NetRef", "object")] + [TestCase("Item item = null; if (item.netRefProperty == null);", 22, "item.netRefProperty", "NetRef", "object")] + [TestCase("Item item = null; if (item.netRefProperty != null);", 22, "item.netRefProperty", "NetRef", "object")] + [TestCase("SObject obj = null; if (obj.netIntField != 42);", 24, "obj.netIntField", "NetInt", "int")] // ↓ same as above, but inherited from base class + [TestCase("SObject obj = null; if (obj.netIntProperty != 42);", 24, "obj.netIntProperty", "NetInt", "int")] + [TestCase("SObject obj = null; if (obj.netRefField == null);", 24, "obj.netRefField", "NetRef", "object")] + [TestCase("SObject obj = null; if (obj.netRefField != null);", 24, "obj.netRefField", "NetRef", "object")] + [TestCase("SObject obj = null; if (obj.netRefProperty == null);", 24, "obj.netRefProperty", "NetRef", "object")] + [TestCase("SObject obj = null; if (obj.netRefProperty != null);", 24, "obj.netRefProperty", "NetRef", "object")] + public void AvoidImplicitNetFieldComparisons_RaisesDiagnostic(string codeText, int column, string expression, string fromType, string toType) + { + // arrange + string code = NetFieldAnalyzerTests.SampleProgram.Replace("{{test-code}}", codeText); + DiagnosticResult expected = new DiagnosticResult + { + Id = "SMAPI001", + Message = $"This implicitly converts '{expression}' from {fromType} to {toType}, but {fromType} has unintuitive implicit conversion rules. Consider comparing against the actual value instead to avoid bugs. See https://smapi.io/buildmsg/smapi001 for details.", + Severity = DiagnosticSeverity.Warning, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", NetFieldAnalyzerTests.SampleCodeLine, NetFieldAnalyzerTests.SampleCodeColumn + column) } + }; + + // assert + this.VerifyCSharpDiagnostic(code, expected); + } + + /// Test that the expected diagnostic message is raised for avoidable net field references. + /// The code line to test. + /// The column within the code line where the diagnostic message should be reported. + /// The expression which should be reported. + /// The net type name which should be reported. + /// The suggested property name which should be reported. + [TestCase("Item item = null; int category = item.category;", 33, "item.category", "NetInt", "Category")] + [TestCase("Item item = null; int category = (item).category;", 33, "(item).category", "NetInt", "Category")] + [TestCase("Item item = null; int category = ((Item)item).category;", 33, "((Item)item).category", "NetInt", "Category")] + [TestCase("SObject obj = null; int category = obj.category;", 35, "obj.category", "NetInt", "Category")] + public void AvoidNetFields_RaisesDiagnostic(string codeText, int column, string expression, string netType, string suggestedProperty) + { + // arrange + string code = NetFieldAnalyzerTests.SampleProgram.Replace("{{test-code}}", codeText); + DiagnosticResult expected = new DiagnosticResult + { + Id = "SMAPI002", + Message = $"'{expression}' is a {netType} field; consider using the {suggestedProperty} property instead. See https://smapi.io/buildmsg/smapi002 for details.", + Severity = DiagnosticSeverity.Warning, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", NetFieldAnalyzerTests.SampleCodeLine, NetFieldAnalyzerTests.SampleCodeColumn + column) } + }; + + // assert + this.VerifyCSharpDiagnostic(code, expected); + } + + + /********* + ** Helpers + *********/ + /// Get the analyzer being tested. + protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() + { + return new NetFieldAnalyzer(); + } + } +} diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/ObsoleteFieldAnalyzerTests.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/ObsoleteFieldAnalyzerTests.cs new file mode 100644 index 00000000..dc7476ef --- /dev/null +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/ObsoleteFieldAnalyzerTests.cs @@ -0,0 +1,88 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using NUnit.Framework; +using SMAPI.ModBuildConfig.Analyzer.Tests.Framework; +using StardewModdingAPI.ModBuildConfig.Analyzer; + +namespace SMAPI.ModBuildConfig.Analyzer.Tests +{ + /// Unit tests for . + [TestFixture] + public class ObsoleteFieldAnalyzerTests : DiagnosticVerifier + { + /********* + ** Properties + *********/ + /// Sample C# mod code, with a {{test-code}} placeholder for the code in the Entry method to test. + const string SampleProgram = @" + using System; + using StardewValley; + using Netcode; + using SObject = StardewValley.Object; + + namespace SampleMod + { + class ModEntry + { + public void Entry() + { + {{test-code}} + } + } + } + "; + + /// The line number where the unit tested code is injected into . + private const int SampleCodeLine = 13; + + /// The column number where the unit tested code is injected into . + private const int SampleCodeColumn = 25; + + + /********* + ** Unit tests + *********/ + /// Test that no diagnostics are raised for an empty code block. + [TestCase] + public void EmptyCode_HasNoDiagnostics() + { + // arrange + string test = @""; + + // assert + this.VerifyCSharpDiagnostic(test); + } + + /// Test that the expected diagnostic message is raised for an obsolete field reference. + /// The code line to test. + /// The column within the code line where the diagnostic message should be reported. + /// The old field name which should be reported. + /// The new field name which should be reported. + [TestCase("var x = new Farmer().friendships;", 8, "StardewValley.Farmer.friendships", "friendshipData")] + public void AvoidObsoleteField_RaisesDiagnostic(string codeText, int column, string oldName, string newName) + { + // arrange + string code = ObsoleteFieldAnalyzerTests.SampleProgram.Replace("{{test-code}}", codeText); + DiagnosticResult expected = new DiagnosticResult + { + Id = "SMAPI003", + Message = $"The '{oldName}' field is obsolete and should be replaced with '{newName}'. See https://smapi.io/buildmsg/smapi003 for details.", + Severity = DiagnosticSeverity.Warning, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", ObsoleteFieldAnalyzerTests.SampleCodeLine, ObsoleteFieldAnalyzerTests.SampleCodeColumn + column) } + }; + + // assert + this.VerifyCSharpDiagnostic(code, expected); + } + + + /********* + ** Helpers + *********/ + /// Get the analyzer being tested. + protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() + { + return new ObsoleteFieldAnalyzer(); + } + } +} diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/UnitTests.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/UnitTests.cs deleted file mode 100644 index 8ca27847..00000000 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/UnitTests.cs +++ /dev/null @@ -1,140 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using NUnit.Framework; -using SMAPI.ModBuildConfig.Analyzer.Tests.Framework; -using StardewModdingAPI.ModBuildConfig.Analyzer; - -namespace SMAPI.ModBuildConfig.Analyzer.Tests -{ - /// Unit tests for the C# analyzers. - [TestFixture] - public class UnitTests : DiagnosticVerifier - { - /********* - ** Properties - *********/ - /// Sample C# code which contains a simplified representation of Stardew Valley's Netcode types, and sample mod code with a {{test-code}} placeholder for the code being tested. - const string SampleProgram = @" - using System; - using StardewValley; - using Netcode; - using SObject = StardewValley.Object; - - namespace SampleMod - { - class ModEntry - { - public void Entry() - { - {{test-code}} - } - } - } - "; - - /// The line number where the unit tested code is injected into . - private const int SampleCodeLine = 13; - - /// The column number where the unit tested code is injected into . - private const int SampleCodeColumn = 25; - - - /********* - ** Unit tests - *********/ - /// Test that no diagnostics are raised for an empty code block. - [TestCase] - public void EmptyCode_HasNoDiagnostics() - { - // arrange - string test = @""; - - // assert - this.VerifyCSharpDiagnostic(test); - } - - /// Test that the expected diagnostic message is raised for implicit net field comparisons. - /// The code line to test. - /// The column within the code line where the diagnostic message should be reported. - /// The expression which should be reported. - /// The source type name which should be reported. - /// The target type name which should be reported. - [TestCase("Item item = null; if (item.netIntField < 42);", 22, "item.netIntField", "NetInt", "int")] - [TestCase("Item item = null; if (item.netIntField <= 42);", 22, "item.netIntField", "NetInt", "int")] - [TestCase("Item item = null; if (item.netIntField > 42);", 22, "item.netIntField", "NetInt", "int")] - [TestCase("Item item = null; if (item.netIntField >= 42);", 22, "item.netIntField", "NetInt", "int")] - [TestCase("Item item = null; if (item.netIntField == 42);", 22, "item.netIntField", "NetInt", "int")] - [TestCase("Item item = null; if (item.netIntField != 42);", 22, "item.netIntField", "NetInt", "int")] - [TestCase("Item item = null; if (item?.netIntField != 42);", 22, "item?.netIntField", "NetInt", "int")] - [TestCase("Item item = null; if (item?.netIntField != null);", 22, "item?.netIntField", "NetInt", "object")] - [TestCase("Item item = null; if (item.netIntProperty < 42);", 22, "item.netIntProperty", "NetInt", "int")] - [TestCase("Item item = null; if (item.netIntProperty <= 42);", 22, "item.netIntProperty", "NetInt", "int")] - [TestCase("Item item = null; if (item.netIntProperty > 42);", 22, "item.netIntProperty", "NetInt", "int")] - [TestCase("Item item = null; if (item.netIntProperty >= 42);", 22, "item.netIntProperty", "NetInt", "int")] - [TestCase("Item item = null; if (item.netIntProperty == 42);", 22, "item.netIntProperty", "NetInt", "int")] - [TestCase("Item item = null; if (item.netIntProperty != 42);", 22, "item.netIntProperty", "NetInt", "int")] - [TestCase("Item item = null; if (item?.netIntProperty != 42);", 22, "item?.netIntProperty", "NetInt", "int")] - [TestCase("Item item = null; if (item?.netIntProperty != null);", 22, "item?.netIntProperty", "NetInt", "object")] - [TestCase("Item item = null; if (item.netRefField == null);", 22, "item.netRefField", "NetRef", "object")] - [TestCase("Item item = null; if (item.netRefField != null);", 22, "item.netRefField", "NetRef", "object")] - [TestCase("Item item = null; if (item.netRefProperty == null);", 22, "item.netRefProperty", "NetRef", "object")] - [TestCase("Item item = null; if (item.netRefProperty != null);", 22, "item.netRefProperty", "NetRef", "object")] - [TestCase("SObject obj = null; if (obj.netIntField != 42);", 24, "obj.netIntField", "NetInt", "int")] // ↓ same as above, but inherited from base class - [TestCase("SObject obj = null; if (obj.netIntProperty != 42);", 24, "obj.netIntProperty", "NetInt", "int")] - [TestCase("SObject obj = null; if (obj.netRefField == null);", 24, "obj.netRefField", "NetRef", "object")] - [TestCase("SObject obj = null; if (obj.netRefField != null);", 24, "obj.netRefField", "NetRef", "object")] - [TestCase("SObject obj = null; if (obj.netRefProperty == null);", 24, "obj.netRefProperty", "NetRef", "object")] - [TestCase("SObject obj = null; if (obj.netRefProperty != null);", 24, "obj.netRefProperty", "NetRef", "object")] - public void AvoidImplicitNetFieldComparisons_RaisesDiagnostic(string codeText, int column, string expression, string fromType, string toType) - { - // arrange - string code = UnitTests.SampleProgram.Replace("{{test-code}}", codeText); - DiagnosticResult expected = new DiagnosticResult - { - Id = "SMAPI001", - Message = $"This implicitly converts '{expression}' from {fromType} to {toType}, but {fromType} has unintuitive implicit conversion rules. Consider comparing against the actual value instead to avoid bugs. See https://smapi.io/buildmsg/smapi001 for details.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", UnitTests.SampleCodeLine, UnitTests.SampleCodeColumn + column) } - }; - - // assert - this.VerifyCSharpDiagnostic(code, expected); - } - - /// Test that the expected diagnostic message is raised for avoidable net field references. - /// The code line to test. - /// The column within the code line where the diagnostic message should be reported. - /// The expression which should be reported. - /// The net type name which should be reported. - /// The suggested property name which should be reported. - [TestCase("Item item = null; int category = item.category;", 33, "item.category", "NetInt", "Category")] - [TestCase("Item item = null; int category = (item).category;", 33, "(item).category", "NetInt", "Category")] - [TestCase("Item item = null; int category = ((Item)item).category;", 33, "((Item)item).category", "NetInt", "Category")] - [TestCase("SObject obj = null; int category = obj.category;", 35, "obj.category", "NetInt", "Category")] - public void AvoidNetFields_RaisesDiagnostic(string codeText, int column, string expression, string netType, string suggestedProperty) - { - // arrange - string code = UnitTests.SampleProgram.Replace("{{test-code}}", codeText); - DiagnosticResult expected = new DiagnosticResult - { - Id = "SMAPI002", - Message = $"'{expression}' is a {netType} field; consider using the {suggestedProperty} property instead. See https://smapi.io/buildmsg/smapi002 for details.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", UnitTests.SampleCodeLine, UnitTests.SampleCodeColumn + column) } - }; - - // assert - this.VerifyCSharpDiagnostic(code, expected); - } - - - /********* - ** Helpers - *********/ - /// Get the analyzer being tested. - protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return new NetFieldAnalyzer(); - } - } -} -- cgit