From 06d049df045acbb7e575467a9795ec8783b17e06 Mon Sep 17 00:00:00 2001 From: mykolav Date: Sat, 18 Feb 2023 18:44:51 +0200 Subject: [PATCH] Add support for implicit object creation expressions (i. e. `... = new(...)`) Closes #6 --- .../Analyzer/AnalyzerTests.fs | 39 ++++++++++++ .../RequireNamedArgs.Tests.fsproj | 2 +- .../Support/DocumentFactory.fs | 2 +- .../Analysis/InvocationAnalyzer.fs | 59 ++++++++++--------- RequireNamedArgs/RequireNamedArgs.fsproj | 2 +- RequireNamedArgs/RequireNamedArgsAnalyzer.fs | 5 +- 6 files changed, 76 insertions(+), 33 deletions(-) diff --git a/RequireNamedArgs.Tests/Analyzer/AnalyzerTests.fs b/RequireNamedArgs.Tests/Analyzer/AnalyzerTests.fs index da398d6..e9313d1 100644 --- a/RequireNamedArgs.Tests/Analyzer/AnalyzerTests.fs +++ b/RequireNamedArgs.Tests/Analyzer/AnalyzerTests.fs @@ -202,5 +202,44 @@ module AnalyzerTests = paramNamesByType=[[ "line"; "column" ]], fileName="Test0.cs", line=15u, column=36u) + [| expectedDiag |] |> ExpectDiags.toBeEmittedFrom testCodeSnippet + } + test "Constructor w/ [RequireNamedArgs] invoked implicitly w/ named args does not trigger diagnostic" { + ExpectDiags.emptyDiagnostics @" + class Wombat + { + public string Name { get; } + public int PowerLevel { get; } + + [RequireNamedArgs] + public Wombat(string name, int powerLevel) => (Name, PowerLevel) = (name, powerLevel); + + public static Wombat Create() + { + return new(name: ""Goku"", powerLevel: 5000); + } + } + " + } + test "Constructor w/ [RequireNamedArgs] invoked implicitly w/ positional args triggers diagnostic" { + let testCodeSnippet = @" + class Wombat + { + public string Name { get; } + public int PowerLevel { get; } + + [RequireNamedArgs] + public Wombat(string name, int powerLevel) => (Name, PowerLevel) = (name, powerLevel); + + public static Wombat Create() + { + return new(""Goku"", 5000); + } + } + " + let expectedDiag = RequireNamedArgsDiagResult.Create(invokedMethod=".ctor", + paramNamesByType=[[ "line"; "column" ]], + fileName="Test0.cs", line=15u, column=36u) + [| expectedDiag |] |> ExpectDiags.toBeEmittedFrom testCodeSnippet }] diff --git a/RequireNamedArgs.Tests/RequireNamedArgs.Tests.fsproj b/RequireNamedArgs.Tests/RequireNamedArgs.Tests.fsproj index 628d61c..1355027 100644 --- a/RequireNamedArgs.Tests/RequireNamedArgs.Tests.fsproj +++ b/RequireNamedArgs.Tests/RequireNamedArgs.Tests.fsproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net6.0 diff --git a/RequireNamedArgs.Tests/Support/DocumentFactory.fs b/RequireNamedArgs.Tests/Support/DocumentFactory.fs index 9208e93..82178e8 100644 --- a/RequireNamedArgs.Tests/Support/DocumentFactory.fs +++ b/RequireNamedArgs.Tests/Support/DocumentFactory.fs @@ -44,7 +44,7 @@ module DocumentFactory = let parseOptions = if lang = Langs.CSharp - then CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_2) :> ParseOptions + then CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp9) :> ParseOptions else VisualBasicParseOptions.Default :> ParseOptions let solution = (new AdhocWorkspace()) diff --git a/RequireNamedArgs/Analysis/InvocationAnalyzer.fs b/RequireNamedArgs/Analysis/InvocationAnalyzer.fs index fdd11ef..4c3bc67 100644 --- a/RequireNamedArgs/Analysis/InvocationAnalyzer.fs +++ b/RequireNamedArgs/Analysis/InvocationAnalyzer.fs @@ -105,33 +105,36 @@ type InvocationAnalyzer private(_sema: SemanticModel, /// we should stop analysis of the current expression. /// member this.GetArgsMissingNames(): Res = - let argSyntaxes = _exprSyntax.GetArguments() - - if not (argSyntaxes.Any()) - then - Ok NoArgsMissingNames - else + let argSyntaxes = + match _exprSyntax with + | :? ImplicitObjectCreationExpressionSyntax as it -> it.ArgumentList.Arguments + | _ -> _exprSyntax.GetArguments() + + if not (argSyntaxes.Any()) + then + Ok NoArgsMissingNames + else - let lastArgIndex = argSyntaxes.Count - 1 - let lastParamInfoRes = _sema.GetParameterInfo(_methodSymbol, lastArgIndex, argSyntaxes.[lastArgIndex]) - if lastParamInfoRes.ShouldStopAnalysis - then - StopAnalysis - else - - if lastParamInfoRes.Value.ParamSymbol.IsParams - then - Ok NoArgsMissingNames - else + let lastArgIndex = argSyntaxes.Count - 1 + let lastParamInfoRes = _sema.GetParameterInfo(_methodSymbol, lastArgIndex, argSyntaxes.[lastArgIndex]) + if lastParamInfoRes.ShouldStopAnalysis + then + StopAnalysis + else + + if lastParamInfoRes.Value.ParamSymbol.IsParams + then + Ok NoArgsMissingNames + else - let argWithParamSymbolsRes = zipArgSyntaxesWithParamSymbols argSyntaxes - if argWithParamSymbolsRes.ShouldStopAnalysis - then - StopAnalysis - else - - let argsMissingNames = - argWithParamSymbolsRes.Value - |> Array.filter (fun it -> isNull it.ArgSyntax.NameColon) - - Ok argsMissingNames + let argWithParamSymbolsRes = zipArgSyntaxesWithParamSymbols argSyntaxes + if argWithParamSymbolsRes.ShouldStopAnalysis + then + StopAnalysis + else + + let argsMissingNames = + argWithParamSymbolsRes.Value + |> Array.filter (fun it -> isNull it.ArgSyntax.NameColon) + + Ok argsMissingNames diff --git a/RequireNamedArgs/RequireNamedArgs.fsproj b/RequireNamedArgs/RequireNamedArgs.fsproj index dc3f4ec..5975e03 100644 --- a/RequireNamedArgs/RequireNamedArgs.fsproj +++ b/RequireNamedArgs/RequireNamedArgs.fsproj @@ -18,7 +18,7 @@ - + diff --git a/RequireNamedArgs/RequireNamedArgsAnalyzer.fs b/RequireNamedArgs/RequireNamedArgsAnalyzer.fs index 9fe5b48..0f8ad64 100644 --- a/RequireNamedArgs/RequireNamedArgsAnalyzer.fs +++ b/RequireNamedArgs/RequireNamedArgsAnalyzer.fs @@ -50,9 +50,10 @@ type public RequireNamedArgsAnalyzer() = context.RegisterSyntaxNodeAction( (fun c -> this.Analyze c), SyntaxKind.InvocationExpression, - SyntaxKind.ObjectCreationExpression) - + SyntaxKind.ObjectCreationExpression, + SyntaxKind.ImplicitObjectCreationExpression) + member private this.Analyze(context: SyntaxNodeAnalysisContext) = let invocationAnalyzerRes = InvocationAnalyzer.Create(context.SemanticModel, context.Node) if invocationAnalyzerRes.ShouldStopAnalysis