diff --git a/.gitignore b/.gitignore index fd5204b..1faa2e9 100644 --- a/.gitignore +++ b/.gitignore @@ -181,3 +181,4 @@ UpgradeLog*.htm # Microsoft Fakes FakesAssemblies/ +/NullParameterCheckRefactoring_(CS+VB) diff --git a/NullParameterCheckRefactoring.sln b/NullParameterCheckRefactoring.sln index a03ccef..5e0d27f 100644 --- a/NullParameterCheckRefactoring.sln +++ b/NullParameterCheckRefactoring.sln @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.22310.1 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NullParameterCheckRefactoring", "src\NullParameterCheckRefactoring\NullParameterCheckRefactoring.csproj", "{6FD2E415-46CD-4370-87BD-A5E2D03A5C0D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NullParameterCheckRefactoring.CSharp", "src\NullParameterCheckRefactoring.CSharp\NullParameterCheckRefactoring.CSharp.csproj", "{6FD2E415-46CD-4370-87BD-A5E2D03A5C0D}" +EndProject +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "NullParameterCheckRefactoring.VB", "src\NullParameterCheckRefactoring.VB\NullParameterCheckRefactoring.VB.vbproj", "{5C5C6CFE-22CB-4E7A-9916-B6F28785B5C0}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NullParameterCheckRefactoring.Vsix", "src\NullParameterCheckRefactoring.Vsix\NullParameterCheckRefactoring.Vsix.csproj", "{E9BE117A-4BF4-41C3-843A-CB659B433963}" EndProject @@ -17,6 +19,10 @@ Global {6FD2E415-46CD-4370-87BD-A5E2D03A5C0D}.Debug|Any CPU.Build.0 = Debug|Any CPU {6FD2E415-46CD-4370-87BD-A5E2D03A5C0D}.Release|Any CPU.ActiveCfg = Release|Any CPU {6FD2E415-46CD-4370-87BD-A5E2D03A5C0D}.Release|Any CPU.Build.0 = Release|Any CPU + {5C5C6CFE-22CB-4E7A-9916-B6F28785B5C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C5C6CFE-22CB-4E7A-9916-B6F28785B5C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C5C6CFE-22CB-4E7A-9916-B6F28785B5C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C5C6CFE-22CB-4E7A-9916-B6F28785B5C0}.Release|Any CPU.Build.0 = Release|Any CPU {E9BE117A-4BF4-41C3-843A-CB659B433963}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E9BE117A-4BF4-41C3-843A-CB659B433963}.Debug|Any CPU.Build.0 = Debug|Any CPU {E9BE117A-4BF4-41C3-843A-CB659B433963}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/NullParameterCheckRefactoring/NullParameterCheckRefactoring.csproj b/src/NullParameterCheckRefactoring.CSharp/NullParameterCheckRefactoring.CSharp.csproj similarity index 93% rename from src/NullParameterCheckRefactoring/NullParameterCheckRefactoring.csproj rename to src/NullParameterCheckRefactoring.CSharp/NullParameterCheckRefactoring.CSharp.csproj index 46e3f77..78c052f 100644 --- a/src/NullParameterCheckRefactoring/NullParameterCheckRefactoring.csproj +++ b/src/NullParameterCheckRefactoring.CSharp/NullParameterCheckRefactoring.CSharp.csproj @@ -11,7 +11,7 @@ Library Properties NullParameterCheckRefactoring - NullParameterCheckRefactoring + NullParameterCheckRefactoring.CSharp Profile7 v4.5 @@ -53,6 +53,12 @@ ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0-beta1-20141031-01\lib\portable-net45+win8\Microsoft.CodeAnalysis.Workspaces.dll False + + ..\..\packages\RoslynExts.1.0.7\lib\portable-net45+win8\RoslynExts.CS.dll + + + ..\..\packages\RoslynExts.1.0.7\lib\portable-net45+win8\RoslynExts.VB.dll + ..\..\packages\System.Collections.Immutable.1.1.32-beta\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll False diff --git a/src/NullParameterCheckRefactoring.CSharp/NullParameterCheckRefactoringProvider.cs b/src/NullParameterCheckRefactoring.CSharp/NullParameterCheckRefactoringProvider.cs new file mode 100644 index 0000000..8e083b3 --- /dev/null +++ b/src/NullParameterCheckRefactoring.CSharp/NullParameterCheckRefactoringProvider.cs @@ -0,0 +1,84 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using System; +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using RoslynExts.CS; + + +namespace NullParameterCheckRefactoring +{ + [ExportCodeRefactoringProvider(RefactoringId, LanguageNames.CSharp), Shared] + public class NullParameterCheckRefactoringProvider : CodeRefactoringProvider + { + internal const string RefactoringId = "TR0001"; + + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + SyntaxNode node = root.FindNode(context.Span); + ParameterSyntax parameterSyntax = node as ParameterSyntax; + + if (parameterSyntax != null) + { + TypeSyntax paramTypeName = parameterSyntax.Type; + SemanticModel semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken); + ITypeSymbol type = semanticModel.GetTypeInfo(paramTypeName).ConvertedType; + + BaseMethodDeclarationSyntax methodDeclaration = parameterSyntax.Parent.Parent as BaseMethodDeclarationSyntax; + IEnumerable availableIfStatements = methodDeclaration.Body.ChildNodes().OfType(); + + if (type.IsReferenceType) + { + // check if the null check already exists. + bool isNullCheckAlreadyPresent = availableIfStatements.Any(ifStatement => + { + return ifStatement.ChildNodes() + .OfType() + .Where(x => x.IsKind(SyntaxKind.EqualsExpression)) + .Any(expression => + { + if (expression.Right.IsKind(SyntaxKind.NullLiteralExpression)) + { + var identifierSyntaxt = expression.ChildNodes().OfType().FirstOrDefault(); + return (identifierSyntaxt != null) && + (identifierSyntaxt.Identifier.Text.Equals(parameterSyntax.Identifier.Text, StringComparison.Ordinal)); + } + return false; + }); + }); + + if (isNullCheckAlreadyPresent == false) + { + CodeAction action = CodeAction.Create( + "Check parameter for null", + ct => AddParameterNullCheckAsync(context.Document, parameterSyntax, methodDeclaration, ct)); + + context.RegisterRefactoring(action); + } + } + } + } + + private async Task AddParameterNullCheckAsync(Document document, ParameterSyntax parameter, BaseMethodDeclarationSyntax methodDeclaration, CancellationToken cancellationToken) + { + + var nullCheckIfStatement = + "if( \{parameter.Identifier} == null ) { throw new ArgumentNullException( nameof( \{parameter.Identifier.Text} )); };".ToSExpr(); + SyntaxList newStatements = methodDeclaration.Body.Statements.Insert(0, nullCheckIfStatement); + BlockSyntax newBlock = SyntaxFactory.Block(newStatements).WithAdditionalAnnotations(Formatter.Annotation); + SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken); + SyntaxNode newRoot = root.ReplaceNode(methodDeclaration.Body, newBlock); + + return document.WithSyntaxRoot(newRoot); + } + } +} \ No newline at end of file diff --git a/src/NullParameterCheckRefactoring/Properties/AssemblyInfo.cs b/src/NullParameterCheckRefactoring.CSharp/Properties/AssemblyInfo.cs similarity index 89% rename from src/NullParameterCheckRefactoring/Properties/AssemblyInfo.cs rename to src/NullParameterCheckRefactoring.CSharp/Properties/AssemblyInfo.cs index 2c1029b..90a67cb 100644 --- a/src/NullParameterCheckRefactoring/Properties/AssemblyInfo.cs +++ b/src/NullParameterCheckRefactoring.CSharp/Properties/AssemblyInfo.cs @@ -4,11 +4,11 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("NullParameterCheckRefactoring")] +[assembly: AssemblyTitle("NullParameterCheckRefactoring.CSharp")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NullParameterCheckRefactoring")] +[assembly: AssemblyProduct("NullParameterCheckRefactoring.CSharp")] [assembly: AssemblyCopyright("Copyright © 2014")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/NullParameterCheckRefactoring/packages.config b/src/NullParameterCheckRefactoring.CSharp/packages.config similarity index 91% rename from src/NullParameterCheckRefactoring/packages.config rename to src/NullParameterCheckRefactoring.CSharp/packages.config index c1e36d2..17c9bcd 100644 --- a/src/NullParameterCheckRefactoring/packages.config +++ b/src/NullParameterCheckRefactoring.CSharp/packages.config @@ -5,6 +5,7 @@ + \ No newline at end of file diff --git a/src/NullParameterCheckRefactoring.VB/CodeRefactoringProvider.vb b/src/NullParameterCheckRefactoring.VB/CodeRefactoringProvider.vb new file mode 100644 index 0000000..27a1965 --- /dev/null +++ b/src/NullParameterCheckRefactoring.VB/CodeRefactoringProvider.vb @@ -0,0 +1,187 @@ +Imports Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory +'Imports Microsoft.CodeAnalysis.VisualBasic.SyntaxKind +Imports RoslynExts.VB + + + +Friend Class NullCheck_CodeRefactoringCodeRefactoringProvider + Inherits CodeRefactoringProvider + + Public Const RefactoringId As String = "TR0001" + + Public NotOverridable Overrides Async Function ComputeRefactoringsAsync(context As CodeRefactoringContext) As Task + Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + ' Find the node at the selection. + Dim node = root.FindNode(context.Span) + ' Only offer a refactoring if the selected node is a type statement node. + Dim _Identifier_ = node.Try(Of ModifiedIdentifierSyntax) : If _Identifier_ Is Nothing Then Return + Dim _parmeter_ = _Identifier_.Parent.Try(of ParameterSyntax) : If _parmeter_ Is Nothing Then Return + Dim _method_ = _parmeter_.Parent.Parent.Parent.Try(of MethodBlockSyntax) : If _method_ Is Nothing Then Return + + Dim _Model_ = Await context.Document.GetSemanticModelAsync(context.CancellationToken) + + Dim ifStatements = _method_.Statements.Where(Function(s) (TypeOf s Is MultiLineIfBlockSyntax) OrElse (TypeOf s Is SingleLineIfStatementSyntax)) + Dim pinfo = _Model_.GetTypeInfo(_parmeter_.AsClause.Type, context.CancellationToken) + If pinfo.ConvertedType.IsReferenceType = False Then Return + Dim GuardStatements = ifStatements.Where(NullChecks(_parmeter_)) + + Dim IsNullCheckAlreadyPresent = GuardStatements.Any + If Not IsNullCheckAlreadyPresent Then context.RegisterRefactoring(CodeAction.Create("Check Parameter for null", Function(ct As CancellationToken) AddParameterNullCheckAsync(context.Document, _parmeter_, _method_, ct))) + End Function + + Private Shared Function NullChecks(_parmeter_ As ParameterSyntax) As Func(Of StatementSyntax, Boolean) + Return Function(s) + If TypeOf s Is SingleLineIfStatementSyntax Then + Dim singleIF = s.As(of SingleLineIfStatementSyntax) + Dim isExpr = singleIF.Condition.Try(of BinaryExpressionSyntax) + If (isExpr Is Nothing) OrElse (Not isExpr.IsKind(SyntaxKind.IsExpression)) Then Return False + Dim res = CheckIfCondition(_parmeter_, isExpr) + If Not res Then Return res + Dim _sif_ = GetGuardStatement(_parmeter_).NormalizeWhitespace + Return Not singleIF.IsEquivalentTo(_sif_) + ElseIf TypeOf s Is MultiLineIfBlockSyntax Then + Dim multiIF = s.As(Of MultiLineIfBlockSyntax) + Dim isExpr = multiIF.IfStatement.Condition.Try(of BinaryExpressionSyntax) + If (isExpr Is Nothing) OrElse (Not isExpr.IsKind(SyntaxKind.IsExpression)) Then Return False + Dim res = CheckIfCondition(_parmeter_, isExpr) + If Not res Then Return res + Dim _mif_ = GetMultiLineGuardStatement(_parmeter_) + Return Not multiIF.WithoutAnnotations().IsEquivalentTo(_mif_) + End If + Return False + End Function + End Function + + Private Shared Function CheckIfCondition(paramSyntax As ParameterSyntax, isExpr As BinaryExpressionSyntax) As Boolean + Dim l = isExpr.Left.Try(of IdentifierNameSyntax) + Dim r = isExpr.Right.Try(Of LiteralExpressionSyntax) + If l Is Nothing OrElse r Is Nothing Then Return False + If r.IsKind(SyntaxKind.NothingLiteralExpression) = False Then Return False + Return String.Compare(l.Identifier.Text, paramSyntax.Identifier.Identifier.Text, StringComparison.Ordinal) = 0 + End Function + + Private Shared Function CheckIfCondition(isExpr As BinaryExpressionSyntax) As Boolean + Dim l = isExpr.Left.Try(Of IdentifierNameSyntax) + Dim r = isExpr.Right.Try(Of LiteralExpressionSyntax) + If l Is Nothing OrElse r Is Nothing Then Return False + Return r.IsKind(SyntaxKind.NothingLiteralExpression) + End Function + + Private Async Function AddParameterNullCheckAsync(document As Document, + _parmeter_ As ParameterSyntax, + method As MethodBlockSyntax, + cancellationToken As CancellationToken) As Task(Of Document) + + Dim NewGuardStatement = GetGuardStatement(_parmeter_) + + Dim ifStatements = method.Statements.Where(Function(s) (TypeOf s Is MultiLineIfBlockSyntax) OrElse (TypeOf s Is SingleLineIfStatementSyntax)) + ' Dim ifStatements = method.Statements.OfType(Of MultiLineIfBlockSyntax, SingleLineIfStatementSyntax) + + Dim ExistingGuards = ifStatements.Where( + Function(s) + If TypeOf s Is SingleLineIfStatementSyntax Then Return CheckIfCondition(s.As(Of SingleLineIfStatementSyntax).Condition.Try(Of BinaryExpressionSyntax)) + If TypeOf s Is MultiLineIfBlockSyntax Then Return CheckIfCondition(s.As(Of MultiLineIfBlockSyntax).IfStatement.Condition.Try(Of BinaryExpressionSyntax)) + Return False + End Function) + + Dim ExistingGuardParameters = ExistingGuards.Select( + Function(s) + If TypeOf s Is SingleLineIfStatementSyntax Then Return s.As(Of SingleLineIfStatementSyntax).Condition.Try(Of BinaryExpressionSyntax)?.Left.As(Of IdentifierNameSyntax) + If TypeOf s Is MultiLineIfBlockSyntax Then Return s.As(Of MultiLineIfBlockSyntax).IfStatement.Condition.Try(Of BinaryExpressionSyntax)?.Left.As(Of IdentifierNameSyntax) + Return Nothing + End Function) + + + Dim parameters = method.Begin.ParameterList.Parameters + Dim NumberOfParameters = parameters.Count + + + Dim Guards = Enumerable.Repeat(Of StatementSyntax)(Nothing, NumberOfParameters).ToArray + Dim parameterNames = parameters.Select(Function(p) p.Identifier).ToArray + + For i = 0 To ExistingGuards.Count - 1 + Dim eg = ExistingGuards(i) + Dim Id = ExistingGuardParameters(i) + Dim index = FindGuardIndex(parameterNames, Id) + If index.HasValue Then Guards(index.Value) = eg '.WithSameTriviaAs(eg) + Next + + Dim NewGuardIndex = FindGuardIndex(parameterNames, _parmeter_.Identifier) + If NewGuardIndex.HasValue Then Guards(NewGuardIndex.Value) = NewGuardStatement.AddEOL + + ' Remove the existing guard statement + Dim newmethod = method.RemoveNodes(ExistingGuards, SyntaxRemoveOptions.KeepNoTrivia) + ' Get the Guards that will exist + Dim NonNullGuards = Guards.WhereNonNull + ' Insert them into the code + Dim newStatements = newmethod.Statements.InsertRange(0, NonNullGuards) + newmethod = newmethod.WithStatements(newStatements) + Dim newBlock = newmethod.WithAdditionalAnnotations(Formatting.Formatter.Annotation) + ' Get the new document with the applied changes + Return document.WithSyntaxRoot((Await document.GetSyntaxRootAsync(cancellationToken)).ReplaceNode(method, newBlock)) + End Function + + Private Shared Function FindGuardIndex(ParametersNames() As ModifiedIdentifierSyntax, Id As IdentifierNameSyntax) As Integer? + For j = 0 To ParametersNames.Count - 1 + If String.Compare(ParametersNames(j).Identifier.Text, + Id.Identifier.Text, StringComparison.Ordinal) = 0 Then Return New Integer?(j) + Next j + Return New Integer?() + End Function + + Private Shared Function FindGuardIndex(ParametersNames() As ModifiedIdentifierSyntax, Id As ModifiedIdentifierSyntax) As Integer? + For j = 0 To ParametersNames.Count - 1 + If String.Compare(ParametersNames(j).Identifier.Text, + Id.Identifier.Text, StringComparison.Ordinal) = 0 Then Return New Integer?(j) + Next j + Return New Integer?() + End Function + + + + Private Shared Function GetGuardStatement(parameterStmt As ParameterSyntax) As SingleLineIfStatementSyntax + Return String.Format("If {0} Then {1}", GetIsNothingExpr(parameterStmt), GetThrowStatementForParameter(parameterStmt)).ToSExpr(Of SingleLineIfStatementSyntax) + End Function + + Private Shared Function GetThrowStatementForParameter(parameterStmt As ParameterSyntax) As ThrowStatementSyntax + Return String.Format(" Throw New System.ArgumentNullException({0})", GetParameterName(parameterStmt)).ToSExpr(Of ThrowStatementSyntax) + End Function + + Private Shared Function GetIsNothingExpr(forParameter As ParameterSyntax) As BinaryExpressionSyntax + Return String.Format(" {0} Is Nothing ", forParameter.Identifier.Identifier.Text).ToExpr(Of BinaryExpressionSyntax) + End Function + + Private Shared Function GetMultiLineGuardStatement(parameterStmt As ParameterSyntax) As MultiLineIfBlockSyntax + Return String.Format("If {0} Then + {1} +End If", + GetIsNothingExpr(parameterStmt), + GetThrowStatementForParameter(parameterStmt)).ToSExpr(Of MultiLineIfBlockSyntax) + + End Function + + Private Shared Function GetParameterName(parameterStmt As ParameterSyntax) As LiteralExpressionSyntax + ' Note: If I can find the nameof feature in VB.net, then I'll change this line to reflect that + Return StringLiteralExpression(Literal(parameterStmt.Identifier.Identifier.Text)) + End Function + +End Class + +Public Module Exts + + + Public Function WhereNonNull(Of T As Class)(xs As IEnumerable(Of T)) As IEnumerable(Of T) + Return xs.Where(Function(x) x IsNot Nothing) + End Function + + + Public Function OfType(Of xT, T1 As xT, T2 As xT)(xs As IEnumerable(Of xT)) As IEnumerable(Of xT) + Return xs.Where(Function(x) (TypeOf xs Is T1) OrElse (TypeOf xs Is T2)) + End Function + + + ' + 'Public Function AddEOL(Of T0 As SyntaxNode)(node As T0) As T0 + ' Return node.WithTrailingTrivia(SyntaxFactory.SyntaxTrivia(SyntaxKind.EndOfLineTrivia, Environment.NewLine)) + 'End Function +End Module \ No newline at end of file diff --git a/src/NullParameterCheckRefactoring.VB/My Project/AssemblyInfo.vb b/src/NullParameterCheckRefactoring.VB/My Project/AssemblyInfo.vb new file mode 100644 index 0000000..343f7f7 --- /dev/null +++ b/src/NullParameterCheckRefactoring.VB/My Project/AssemblyInfo.vb @@ -0,0 +1,29 @@ +Imports System.Reflection +Imports System.Runtime.InteropServices + +' General Information about an assembly is controlled through the following +' set of attributes. Change these attribute values to modify the information +' associated with an assembly. + +' Review the values of the assembly attributes + + + + + + + + + + +' Version information for an assembly consists of the following four values: +' +' Major Version +' Minor Version +' Build Number +' Revision +' +' You can specify all the values or you can default the Build and Revision Numbers +' by using the '*' as shown below: + + diff --git a/src/NullParameterCheckRefactoring.VB/NullParameterCheckRefactoring.VB.vbproj b/src/NullParameterCheckRefactoring.VB/NullParameterCheckRefactoring.VB.vbproj new file mode 100644 index 0000000..676056b --- /dev/null +++ b/src/NullParameterCheckRefactoring.VB/NullParameterCheckRefactoring.VB.vbproj @@ -0,0 +1,142 @@ + + + + + 11.0 + Debug + AnyCPU + 2.0 + {14182A97-F7F0-4C62-8B27-98AA8AE2109A};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} + {5C5C6CFE-22CB-4E7A-9916-B6F28785B5C0} + Library + NullParameterCheckRefactoring + NullParameterCheckRefactoring.VB + Profile7 + v4.5 + 512 + + + true + full + false + bin\Debug\ + true + true + prompt + 41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 + + + pdbonly + true + bin\Release\ + false + true + prompt + 41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 + + + On + + + Binary + + + On + + + On + + + Program + $(DevEnvDir)devenv.exe + /rootsuffix Roslyn + + + + + + + + + + + + + + + + + + + + + + + + + + + + ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0-beta1-20141031-01\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll + False + + + ..\..\packages\Microsoft.CodeAnalysis.VisualBasic.1.0.0-beta1-20141031-01\lib\portable-net45+win8\Microsoft.CodeAnalysis.VisualBasic.dll + False + + + ..\..\packages\Microsoft.CodeAnalysis.VisualBasic.Workspaces.1.0.0-beta1-20141031-01\lib\portable-net45+win8\Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll + False + + + ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0-beta1-20141031-01\lib\portable-net45+win8\Microsoft.CodeAnalysis.Workspaces.dll + False + + + ..\..\packages\RoslynExts.1.0.7\lib\portable-net45+win8\RoslynExts.CS.dll + + + ..\..\packages\RoslynExts.1.0.7\lib\portable-net45+win8\RoslynExts.VB.dll + + + ..\..\packages\System.Collections.Immutable.1.1.32-beta\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + False + + + ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll + False + + + ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll + False + + + ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll + False + + + ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll + False + + + ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll + False + + + ..\..\packages\System.Reflection.Metadata.1.0.17-beta\lib\portable-net45+win8\System.Reflection.Metadata.dll + False + + + + + + + + + \ No newline at end of file diff --git a/src/NullParameterCheckRefactoring.VB/packages.config b/src/NullParameterCheckRefactoring.VB/packages.config new file mode 100644 index 0000000..fd86554 --- /dev/null +++ b/src/NullParameterCheckRefactoring.VB/packages.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/NullParameterCheckRefactoring.Vsix/NullParameterCheckRefactoring.Vsix.csproj b/src/NullParameterCheckRefactoring.Vsix/NullParameterCheckRefactoring.Vsix.csproj index 0a70eb5..043518d 100644 --- a/src/NullParameterCheckRefactoring.Vsix/NullParameterCheckRefactoring.Vsix.csproj +++ b/src/NullParameterCheckRefactoring.Vsix/NullParameterCheckRefactoring.Vsix.csproj @@ -47,15 +47,30 @@ /rootsuffix Roslyn + Designer - + {6FD2E415-46CD-4370-87BD-A5E2D03A5C0D} - NullParameterCheckRefactoring + NullParameterCheckRefactoring.CSharp + + {5c5c6cfe-22cb-4e7a-9916-b6f28785b5c0} + NullParameterCheckRefactoring.VB + + + + + False + ..\..\packages\RoslynExts.1.0.7\lib\portable-net45+win8\RoslynExts.CS.dll + + + False + ..\..\packages\RoslynExts.1.0.7\lib\portable-net45+win8\RoslynExts.VB.dll + diff --git a/src/NullParameterCheckRefactoring.Vsix/packages.config b/src/NullParameterCheckRefactoring.Vsix/packages.config new file mode 100644 index 0000000..263cef5 --- /dev/null +++ b/src/NullParameterCheckRefactoring.Vsix/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/NullParameterCheckRefactoring.Vsix/source.extension.vsixmanifest b/src/NullParameterCheckRefactoring.Vsix/source.extension.vsixmanifest index bdb9f36..df0b51f 100644 --- a/src/NullParameterCheckRefactoring.Vsix/source.extension.vsixmanifest +++ b/src/NullParameterCheckRefactoring.Vsix/source.extension.vsixmanifest @@ -3,7 +3,8 @@ NullParameterCheckRefactoring.Vsix - This is a sample code refactoring extension for the .NET Compiler Platform ("Roslyn"). + Add Guard Statement for Reference Parameter Types. This code refactoring extension for the .NET Compiler Platform ("Roslyn"). + https://github.com/DotNetAnalyzers/NullParameterCheckRefactoring @@ -15,6 +16,7 @@ - + + \ No newline at end of file diff --git a/src/NullParameterCheckRefactoring/NullParameterCheckRefactoringProvider.cs b/src/NullParameterCheckRefactoring/NullParameterCheckRefactoringProvider.cs deleted file mode 100644 index 34e8677..0000000 --- a/src/NullParameterCheckRefactoring/NullParameterCheckRefactoringProvider.cs +++ /dev/null @@ -1,128 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Formatting; -using System; -using System.Collections.Generic; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace NullParameterCheckRefactoring -{ - [ExportCodeRefactoringProvider(RefactoringId, LanguageNames.CSharp), Shared] - public class NullParameterCheckRefactoringProvider : CodeRefactoringProvider - { - internal const string RefactoringId = "TR0001"; - - public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) - { - SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - SyntaxNode node = root.FindNode(context.Span); - ParameterSyntax parameterSyntax = node as ParameterSyntax; - - if(parameterSyntax != null) - { - TypeSyntax paramTypeName = parameterSyntax.Type; - SemanticModel semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken); - ITypeSymbol type = semanticModel.GetTypeInfo(paramTypeName).ConvertedType; - - BaseMethodDeclarationSyntax methodDeclaration = parameterSyntax.Parent.Parent as BaseMethodDeclarationSyntax; - IEnumerable availableIfStatements = methodDeclaration.Body.ChildNodes().OfType(); - - if (type.IsReferenceType) - { - // check if the null check already exists. - bool isNullCheckAlreadyPresent = availableIfStatements.Any(ifStatement => - { - return ifStatement.ChildNodes() - .OfType() - .Where(x => x.IsKind(SyntaxKind.EqualsExpression)) - .Any(expression => - { - bool result; - bool isNullCheck = expression.Right.IsKind(SyntaxKind.NullLiteralExpression); - if (isNullCheck) - { - IdentifierNameSyntax identifierSyntaxt = expression.ChildNodes().OfType().FirstOrDefault(); - if (identifierSyntaxt != null) - { - string identifierText = identifierSyntaxt.Identifier.Text; - string paramText = parameterSyntax.Identifier.Text; - - if (identifierText.Equals(paramText, StringComparison.Ordinal)) - { - // There is already a null check for this parameter. Skip it. - result = true; - } - else - { - result = false; - } - } - else - { - result = false; - } - } - else - { - result = false; - } - - return result; - }); - }); - - if(isNullCheckAlreadyPresent == false) - { - CodeAction action = CodeAction.Create( - "Check parameter for null", - ct => AddParameterNullCheckAsync(context.Document, parameterSyntax, methodDeclaration, ct)); - - context.RegisterRefactoring(action); - } - } - } - } - - private async Task AddParameterNullCheckAsync(Document document, ParameterSyntax parameter, BaseMethodDeclarationSyntax methodDeclaration, CancellationToken cancellationToken) - { - BinaryExpressionSyntax binaryExpression = SyntaxFactory.BinaryExpression( - SyntaxKind.EqualsExpression, - SyntaxFactory.IdentifierName(parameter.Identifier), - SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)); - - NameOfExpressionSyntax nameOfExpression = SyntaxFactory.NameOfExpression( - "nameof", - SyntaxFactory.ParseTypeName(parameter.Identifier.Text)); - - ObjectCreationExpressionSyntax objectCreationExpression = SyntaxFactory.ObjectCreationExpression( - SyntaxFactory.ParseTypeName(nameof(ArgumentNullException)), - SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[] { SyntaxFactory.Argument(nameOfExpression) })), - null); - - BlockSyntax syntaxBlock = SyntaxFactory.Block( - SyntaxFactory.Token(SyntaxKind.OpenBraceToken), - new SyntaxList().Add(SyntaxFactory.ThrowStatement(objectCreationExpression)), - SyntaxFactory.Token(SyntaxKind.CloseBraceToken)).WithAdditionalAnnotations(Formatter.Annotation); - - IfStatementSyntax nullCheckIfStatement = SyntaxFactory.IfStatement( - SyntaxFactory.Token(SyntaxKind.IfKeyword), - SyntaxFactory.Token(SyntaxKind.OpenParenToken), - binaryExpression, - SyntaxFactory.Token(SyntaxKind.CloseParenToken), - syntaxBlock, null).WithAdditionalAnnotations(Formatter.Annotation); - - SyntaxList newStatements = methodDeclaration.Body.Statements.Insert(0, nullCheckIfStatement); - BlockSyntax newBlock = SyntaxFactory.Block(newStatements).WithAdditionalAnnotations(Formatter.Annotation); - SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken); - SyntaxNode newRoot = root.ReplaceNode(methodDeclaration.Body, newBlock); - - return document.WithSyntaxRoot(newRoot); - } - } -} \ No newline at end of file