Skip to content

Commit

Permalink
Add NoUsingDeclaration analyzer (#195)
Browse files Browse the repository at this point in the history
- Fix the test framework so that test cases can import System.Net
- Ignore SA1008
- Move StyleChecker.Refactoring.UnnecessaryUsing.Classes to
  StyleChecker.Refactoring
- Add GetTypeInfoSupplier(SemanticModelAnalysisContext) methods to
  SmaContextExtentions class
- Add GetFullNameWithoutNullability(ITypeSymbol) method to TypeSymbols
  class
- Add ToSymbol(VariableDeclaratorSyntax) to ISymbolizer interface
  • Loading branch information
maroontress-tomohisa authored Jan 17, 2025
1 parent a888570 commit 6202f9b
Show file tree
Hide file tree
Showing 17 changed files with 1,142 additions and 2 deletions.
10 changes: 9 additions & 1 deletion StyleChecker/StyleChecker.Test/Framework/Projects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ namespace StyleChecker.Test.Framework;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using Maroontress.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand Down Expand Up @@ -39,7 +41,13 @@ public static IEnumerable<MetadataReference>
/* Microsoft.CodeAnalysis */
NewReference<Compilation>(),
/* System.Runtime.Extensions */
NewReference<StringReader>()
NewReference<StringReader>(),
/* System.Net.Sockets */
NewReference<Socket>(),
/* System.Private.Url */
NewReference<Uri>(),
/* System.Net.Primitives */
NewReference<IPAddress>(),
];

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace StyleChecker.Test.Refactoring.NoUsingDeclaration;

using Microsoft.VisualStudio.TestTools.UnitTesting;
using StyleChecker.Refactoring.NoUsingDeclaration;
using StyleChecker.Test.Framework;

[TestClass]
public sealed class AnalyzerTest : CodeFixVerifier
{
public AnalyzerTest()
: base(new Analyzer(), new CodeFixer())
{
}

[TestMethod]
public void Okay()
=> VerifyDiagnostic(ReadText("Okay"), Atmosphere.Default);

[TestMethod]
public void Code()
{
var code = ReadText("Code");
var fix = ReadText("CodeFix");
static Result Expected(Belief b)
{
return b.ToResult(
Analyzer.DiagnosticId,
$"Insert 'using' before '{b.Message}'.");
}

VerifyDiagnosticAndFix(code, Atmosphere.Default, Expected, fix);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#nullable enable
namespace StyleChecker.Test.Refactoring.NoUsingDeclaration;

using System;
using System.IO;

public sealed class Code
{
public static void Main()
{
var i = new StreamReader("file.txt");
//@ ^var
StreamReader j = new("file.txt");
//@ ^StreamReader
}

public static void ExplicitTypeDeclarationsWithMultipleDeclarators()
{
StreamReader i1 = new StreamReader("1.txt"), i2 = new StreamReader("2.txt");
//@ ^StreamReader
StreamReader j1 = new StreamReader("1.txt"), j2 = new("2.txt");
//@ ^StreamReader
StreamReader k1 = new("1.txt"), k2 = new StreamReader("2.txt");
//@ ^StreamReader
}

public static void Using()
{
// StringReader 'i' does not need to be disposed
StringReader i = new("hello");
// IDisposable 'j' should be disposed
IDisposable j = new StringReader("world");
//@ ^IDisposable
}

public static void Nullable()
{
StreamReader? k = new StreamReader("file.txt");
//@ ^StreamReader?
}

public static void KeepTrivia()
{
/*foo*/ var i = new StreamReader("file.txt"); // bar
//@ ^var
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#nullable enable
namespace StyleChecker.Test.Refactoring.NoUsingDeclaration;

using System;
using System.IO;

public sealed class Code
{
public static void Main()
{
using var i = new StreamReader("file.txt");
using StreamReader j = new("file.txt");
}

public static void ExplicitTypeDeclarationsWithMultipleDeclarators()
{
using StreamReader i1 = new StreamReader("1.txt"), i2 = new StreamReader("2.txt");
using StreamReader j1 = new StreamReader("1.txt"), j2 = new("2.txt");
using StreamReader k1 = new("1.txt"), k2 = new StreamReader("2.txt");
}

public static void Using()
{
// StringReader 'i' does not need to be disposed
StringReader i = new("hello");
// IDisposable 'j' should be disposed
using IDisposable j = new StringReader("world");
}

public static void Nullable()
{
using StreamReader? k = new StreamReader("file.txt");
}

public static void KeepTrivia()
{
/*foo*/
using var i = new StreamReader("file.txt"); // bar
}
}
113 changes: 113 additions & 0 deletions StyleChecker/StyleChecker.Test/Refactoring/NoUsingDeclaration/Okay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#nullable enable
namespace StyleChecker.Test.Refactoring.NoUsingDeclaration;

using System;
using System.IO;
using System.Net.Sockets;

public sealed class Okay
{
private StreamReader? streamReader;

private TextWriter? SharedWriter { get; set; }

public void AssignedToFieldOrProperty()
{
// Replacing 'var' with 'using var' makes no sense.
var reader = new StreamReader("input.txt");
streamReader = reader;
var writer = new StreamWriter("output.txt");
SharedWriter = writer;
}

public static BufferedStream UsedAsAParameter()
{
// Replacing 'var' with 'using var' makes no sense.
var clientSocket = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var b = clientSocket.Connected;
var netStream = new NetworkStream(clientSocket, true);
var bufStream = new BufferedStream(netStream, 1024);
return bufStream;
}

// A factory method that returns a new IDisposable object.
public static Socket? Returned(Uri uri, int port)
{
// Replacing 'var' with 'using var' makes no sense.
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
try
{
socket.Connect(uri.Host, port);
return socket;
}
catch (Exception)
{
socket.Dispose();
}
return null;
}

public static void Reassigned()
{
// Replacing 'var' with 'using var' causes an error CS1656.
var i = new StreamReader("file.txt");
Console.WriteLine(i.ReadLine());
i = new StreamReader("another.txt");
Console.WriteLine(i.ReadLine());
}

public static Action Captured()
{
var i = new StreamWriter("file.txt");
return () =>
{
i.WriteLine("hello");
};
}

public static void Main()
{
using var i = new StreamReader("file.txt");
// StringReader does not need to be disposed.
var j = new StringReader("hello");
}

public static void ExplicitTypeDeclarations()
{
using StreamReader i = new("file.txt");
using IDisposable j = new StringReader("world");
}

public static void Nullable()
{
using StreamReader? k = new StreamReader("file.txt");
}

public static void NotNewOperator()
{
var out1 = Console.Out;
TextWriter out2 = Console.Out;
var file1 = NewStreamReader("file.txt");
StreamReader? file2 = NewStreamReader("file.txt");
}

private static StreamReader? NewStreamReader(string path)
{
try
{
return new StreamReader(path);
}
catch (IOException)
{
return null;
}
}

private static void Loophole()
{
var (r1, r2) = (new StreamReader("1"), new StreamReader("2"));
TextReader[] array = [new StreamReader("1")];
var anotherArray = new[] { new StreamReader("1") };
}
}
12 changes: 12 additions & 0 deletions StyleChecker/StyleChecker.Test/StyleChecker.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
<Compile Remove="Refactoring\EqualsNull\Okay.cs" />
<Compile Remove="Refactoring\NotOneShotInitialization\Code.cs" />
<Compile Remove="Refactoring\NotOneShotInitialization\Okay.cs" />
<Compile Remove="Refactoring\NoUsingDeclaration\Code.cs" />
<Compile Remove="Refactoring\NoUsingDeclaration\CodeFix.cs" />
<Compile Remove="Refactoring\NoUsingDeclaration\Okay.cs" />
<Compile Remove="Refactoring\StinkyBooleanExpression\Code.cs" />
<Compile Remove="Refactoring\StinkyBooleanExpression\CodeFix.cs" />
<Compile Remove="Refactoring\StinkyBooleanExpression\Okay.cs" />
Expand Down Expand Up @@ -255,6 +258,15 @@
<Content Include="Refactoring\NotOneShotInitialization\Okay.cs">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Refactoring\NoUsingDeclaration\CodeFix.cs">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Refactoring\NoUsingDeclaration\Code.cs">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Refactoring\NoUsingDeclaration\Okay.cs">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Refactoring\StinkyBooleanExpression\CodeFix.cs">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down
3 changes: 3 additions & 0 deletions StyleChecker/StyleChecker/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ dotnet_diagnostic.SA1013.severity = none

# SA1012: Opening braces should be spaced correctly
dotnet_diagnostic.SA1012.severity = none

# SA1008: Opening parenthesis should be spaced correctly
dotnet_diagnostic.SA1008.severity = none
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace StyleChecker.Refactoring.UnnecessaryUsing;
namespace StyleChecker.Refactoring;

using System.Collections.Immutable;
using System.IO;
Expand Down
11 changes: 11 additions & 0 deletions StyleChecker/StyleChecker/Refactoring/ISymbolizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,15 @@ public interface ISymbolizer
/// The symbol representing the query range variable.
/// </returns>
IRangeVariableSymbol? ToSymbol(QueryContinuationSyntax node);

/// <summary>
/// Gets the symbol corresponding to the specified variable declarator.
/// </summary>
/// <param name="node">
/// The variable declarator.
/// </param>
/// <returns>
/// The symbol representing the variable.
/// </returns>
ISymbol? ToSymbol(VariableDeclaratorSyntax node);
}
Loading

0 comments on commit 6202f9b

Please sign in to comment.