Skip to content

Commit

Permalink
Update v7tov8 script for new IDataProcessor interface
Browse files Browse the repository at this point in the history
Also ignore CS1998 in apps (warning when not awaiting anything in async hooks)
  • Loading branch information
ivarne committed Oct 26, 2023
1 parent 36ed3a1 commit 7fce5ab
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 7 deletions.
10 changes: 9 additions & 1 deletion cli-tools/altinn-app-cli/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Reflection;
using altinn_app_cli.v7Tov8.AppSettingsRewriter;
Expand Down Expand Up @@ -180,12 +180,20 @@ static async Task<int> UpgradeCode(string projectFile)
{
await File.WriteAllTextAsync(sourceTree.FilePath, newSource.ToFullString());
}

UsingRewriter usingRewriter = new();
var newUsingSource = usingRewriter.Visit(newSource);
if (newUsingSource != newSource)
{
await File.WriteAllTextAsync(sourceTree.FilePath, newUsingSource.ToFullString());
}

DataProcessorRewriter dataProcessorRewriter = new(sm);
var dataProcessorSource = dataProcessorRewriter.Visit(newUsingSource);
if (dataProcessorSource != newUsingSource)
{
await File.WriteAllTextAsync(sourceTree.FilePath, dataProcessorSource.ToFullString());
}
}

Console.WriteLine("References and using upgraded");
Expand Down
4 changes: 2 additions & 2 deletions cli-tools/altinn-app-cli/altinn-app-cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>altinn_app_cli</RootNamespace>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down Expand Up @@ -31,7 +31,7 @@
<MinVerDefaultPreReleaseIdentifiers>preview.0</MinVerDefaultPreReleaseIdentifiers>
<MinVerTagPrefix>altinn-app-cli</MinVerTagPrefix>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>10.0</LangVersion>
<LangVersion>12</LangVersion>
</PropertyGroup>

<Target Name="AssemblyVersionTarget" AfterTargets="MinVer" Condition="'$(MinVerVersion)'!=''">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace altinn_app_cli.v7Tov8.CodeRewriters
{
public class DataProcessorRewriter : CSharpSyntaxRewriter
{
private readonly SemanticModel semanticModel;

public DataProcessorRewriter(SemanticModel semanticModel)
{
this.semanticModel = semanticModel;
}

public override SyntaxNode? VisitClassDeclaration(ClassDeclarationSyntax node)
{
// Ignore any classes that don't implement `IDataProcessor` (consider using semantic model to ensure correct reference)
if (node.BaseList?.Types.Any(t => t.Type.ToString() == "IDataProcessor") == true)
{
var processDataWrite = node.Members.OfType<MethodDeclarationSyntax>()
.FirstOrDefault(m => m.Identifier.ValueText == "ProcessDataWrite");
if (processDataWrite is not null)
{
node = node.ReplaceNode(processDataWrite, Update_DataProcessWrite(processDataWrite));
}

var processDataRead = node.Members.OfType<MethodDeclarationSyntax>()
.FirstOrDefault(m => m.Identifier.ValueText == "ProcessDataRead");
if (processDataRead is not null)
{
node = node.ReplaceNode(processDataRead, Update_DataProcessRead(processDataRead));
}
}

return base.VisitClassDeclaration(node);
}

private MethodDeclarationSyntax Update_DataProcessRead(MethodDeclarationSyntax processDataRead)
{
if (processDataRead.ParameterList.Parameters.Count == 3 &&
processDataRead.ReturnType.ToString() == "Task<bool>")
{
processDataRead = ChangeReturnType_FromTaskBool_ToTask(processDataRead);
}

return processDataRead;
}

private MethodDeclarationSyntax Update_DataProcessWrite(MethodDeclarationSyntax processDataWrite)
{
if (processDataWrite.ParameterList.Parameters.Count == 3 &&
processDataWrite.ReturnType.ToString() == "Task<bool>")
{
processDataWrite = AddParameter_ChangedFields(processDataWrite);
processDataWrite = ChangeReturnType_FromTaskBool_ToTask(processDataWrite);
}

return processDataWrite;
}

private MethodDeclarationSyntax AddParameter_ChangedFields(MethodDeclarationSyntax method)
{
return method.ReplaceNode(method.ParameterList,
method.ParameterList.AddParameters(SyntaxFactory.Parameter(SyntaxFactory.Identifier("changedFields"))
.WithLeadingTrivia(SyntaxFactory.Space)
.WithType(SyntaxFactory.ParseTypeName("System.Collections.Generic.Dictionary<string, string?>?"))
.WithLeadingTrivia(SyntaxFactory.Space)));
}

private MethodDeclarationSyntax ChangeReturnType_FromTaskBool_ToTask(MethodDeclarationSyntax method)
{
if (method.ReturnType.ToString() == "Task<bool>")
{
var returnTypeRewriter = new ReturnTypeTaskBooleanRewriter();
method = (MethodDeclarationSyntax)returnTypeRewriter.Visit(method)!;
}

return method;

}
}

public class ReturnTypeTaskBooleanRewriter : CSharpSyntaxRewriter
{
public override SyntaxNode? VisitMethodDeclaration(MethodDeclarationSyntax node)
{
if (node.ReturnType.ToString() == "Task<bool>")
{
// Change return type
node = node.WithReturnType(
SyntaxFactory.ParseTypeName("Task").WithTrailingTrivia(SyntaxFactory.Space));
}
return base.VisitMethodDeclaration(node);
}

public override SyntaxNode? VisitBlock(BlockSyntax node)
{
foreach (var returnStatementSyntax in node.Statements.OfType<ReturnStatementSyntax>())
{
var leadingTrivia = returnStatementSyntax.GetLeadingTrivia();
var trailingTrivia = returnStatementSyntax.GetTrailingTrivia();
// When we add multiple lines of code, we need the indentation and a newline
var leadingTriviaMiddle = leadingTrivia.LastOrDefault(t => t.IsKind(SyntaxKind.WhitespaceTrivia));
var trailingTriviaMiddle = trailingTrivia.FirstOrDefault(t => t.IsKind(SyntaxKind.EndOfLineTrivia));
// If we don't find a newline, just guess that LF is used. Will likely work anyway.
if (trailingTriviaMiddle == default) trailingTriviaMiddle = SyntaxFactory.LineFeed;


switch (returnStatementSyntax.Expression)
{
// return true/false/variableName
case IdentifierNameSyntax:
case LiteralExpressionSyntax:
case null:
node = node.ReplaceNode(returnStatementSyntax,
SyntaxFactory.ReturnStatement()
.WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTrivia));
break;
// case "Task.FromResult(...)":
case InvocationExpressionSyntax
{
Expression: MemberAccessExpressionSyntax
{
Expression: IdentifierNameSyntax { Identifier: {Text: "Task" } },
Name: { Identifier: {Text: "FromResult"}}
},
ArgumentList: { Arguments: { Count: 1 } }
}:
node = node.ReplaceNode(returnStatementSyntax,
SyntaxFactory.ReturnStatement(SyntaxFactory.ParseExpression(" Task.CompletedTask"))
.WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTrivia));
break;
// case "await Task.FromResult(...)":
// Assume we need an await to silence CS1998 and rewrite to
// await Task.CompletedTask; return;
// Could be dropped if we ignore CS1998
case AwaitExpressionSyntax
{
Expression: InvocationExpressionSyntax
{
Expression: MemberAccessExpressionSyntax
{
Expression: IdentifierNameSyntax { Identifier: {Text: "Task" } },
Name: { Identifier: {Text: "FromResult"}}
},
ArgumentList: { Arguments: [{Expression: IdentifierNameSyntax or LiteralExpressionSyntax}]}
}
}:
node = node.WithStatements(node.Statements.ReplaceRange(returnStatementSyntax, new StatementSyntax[]
{
// Uncomment if cs1998 isn't disabled
// SyntaxFactory.ParseStatement("await Task.CompletedTask;")
// .WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTriviaMiddle),

SyntaxFactory.ReturnStatement()
.WithLeadingTrivia(leadingTriviaMiddle).WithTrailingTrivia(trailingTrivia),

}));
break;
// Just add move the return; statement after the existing return value
default:
node = node.WithStatements(node.Statements.ReplaceRange(returnStatementSyntax,
new StatementSyntax[]
{
SyntaxFactory.ExpressionStatement(returnStatementSyntax.Expression)
.WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTriviaMiddle),

SyntaxFactory.ReturnStatement()
.WithLeadingTrivia(leadingTriviaMiddle).WithTrailingTrivia(trailingTrivia),
}));
break;
}
}

return base.VisitBlock(node);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,36 @@ public ProjectFileRewriter(string projectFilePath, string targetVersion = "8.0.0
public async Task Upgrade()
{
var altinnAppCoreElements = GetAltinnAppCoreElement();
altinnAppCoreElements?.ForEach(c => c.Attribute("Version")?.SetValue(targetVersion));

var altinnAppApiElements = GetAltinnAppApiElement();
if (altinnAppCoreElements != null && altinnAppApiElements != null)
altinnAppApiElements?.ForEach(a => a.Attribute("Version")?.SetValue(targetVersion));

IgnoreWarnings("1591", "1998"); // Require xml doc and await in async methods

await Save();
}

private void IgnoreWarnings(params string[] warnings)
{
var noWarn = doc.Root?.Elements("PropertyGroup").Elements("NoWarn").ToList();
switch (noWarn?.Count)
{
altinnAppCoreElements.ForEach(c => c.Attribute("Version")?.SetValue(targetVersion));
altinnAppApiElements.ForEach(a => a.Attribute("Version")?.SetValue(targetVersion));
await Save();
case 0:
doc.Root?.Elements("PropertyGroup").First().Add(new XElement("NoWarn", "$(NoWarn);" + string.Join(';', warnings)));
break;

case 1:
var valueElement = noWarn.First();
foreach (var warning in warnings)
{
if (!valueElement.Value.Contains(warning))
{
valueElement.SetValue($"{valueElement.Value};{warning}");
}
}

break;
}
}

Expand Down

0 comments on commit 7fce5ab

Please sign in to comment.