Skip to content

Commit

Permalink
added ProjectContextHelper.GetXmlKeyValue and IProjectContext.CheckNu…
Browse files Browse the repository at this point in the history
…llableVariable (#2061)

* added ProjectContextHelper.GetXmlKeyValue and IProjectContext.CheckNullableVariable

* minor fix.

* minor fix 2
  • Loading branch information
deepchoudhery committed Feb 17, 2023
1 parent df97668 commit 0f160ce
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ public async Task AddAuthCodeAsync()
_toolOptions.ProjectFilePath = csProjFiles.First();
}

string csprojText = File.ReadAllText(_toolOptions.ProjectFilePath);
_toolOptions.ShortTfms = ProjectModifierHelper.ProcessCsprojTfms(csprojText);

CodeModifierConfig? codeModifierConfig = GetCodeModifierConfig();
if (codeModifierConfig is null || !codeModifierConfig.Files.Any())
{
Expand Down Expand Up @@ -171,7 +174,7 @@ private async Task HandleCodeFileAsync(CodeFile file, CodeAnalysis.Project proje
}

/// <summary>
/// Determines if specified file exists, and if not then creates the
/// Determines if specified file exists, and if not then creates the
/// file based on template stored in AppProvisioningTool.Properties
/// then adds file to the project
/// </summary>
Expand Down Expand Up @@ -401,4 +404,4 @@ internal async Task ApplyTextReplacements(CodeFile file, CodeAnalysis.Project pr
}
}
}
}
}
1 change: 1 addition & 0 deletions src/Scaffolding/VS.Web.CG.Design/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ private static void Execute(string[] args, ConsoleLogger logger)
string projectAssetsFile = ProjectModelHelper.GetProjectAssetsFile(projectInformation);
//fix package dependencies sent from VS
projectInformation = projectInformation.AddPackageDependencies(projectAssetsFile);
projectInformation = projectInformation.CheckNullableVariable();
var codeGenArgs = ToolCommandLineHelper.FilterExecutorArguments(args);
var isSimulationMode = ToolCommandLineHelper.IsSimulationMode(args);
CodeGenCommandExecutor executor = new CodeGenCommandExecutor(projectInformation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,64 @@ internal static async Task<bool> IsUsingTopLevelStatements(IModelTypesLocator mo
return true;
}

/// <summary>
/// Parses the csproj xml text and gets one or more TargetFrameworks for the project.
/// </summary>
/// <param name="csprojText">.csproj file as string</param>
/// <returns>string[] containing target frameworks of the project</returns>
internal static string[] ProcessCsprojTfms(string csprojText)
{
List<string> processedTfms = new List<string>();
if (!string.IsNullOrEmpty(csprojText))
{
//use XDocument to get all csproj elements.
XDocument document = XDocument.Parse(csprojText);
var docNodes = document.Root?.Elements();
var allElements = docNodes?.SelectMany(x => x.Elements());
//add them to a dictionary for easy comparisons.
var csprojVariables = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if (allElements != null && allElements.Any())
{
foreach (var elem in allElements)
{
//dont' add PackageReference(s) since they are useless for getting tfm properties.
if (!elem.Name.LocalName.Equals("PackageReference", StringComparison.OrdinalIgnoreCase))
{
//change the keys from TargetFramework to $(TargetFramework) and so forth for nested variable analysis.
//eg. analysing <TargetFramework>$(X)</TargetFramework> and getting the value for $(X).
//makes for a easy string comparison without using regex and splitting.
csprojVariables.TryAdd(string.Format("$({0})", elem.Name.LocalName), elem.Value);
}
}
}

//if only one TargetFramework
if (csprojVariables.TryGetValue("$(TargetFramework)", out string tfmValue))
{
string processedTfm = ProcessTfm(tfmValue.Trim(), csprojVariables);
if (!string.IsNullOrEmpty(processedTfm) && ProjectModelHelper.ShortTfmDictionary.Values.ToList().Contains(processedTfm, StringComparer.OrdinalIgnoreCase))
{
processedTfms.Add(processedTfm);
}
}
//if multiple, split by ';' and add them all.
else if (csprojVariables.TryGetValue("$(TargetFrameworks)", out string tfms))
{
string processedTfm = ProcessTfm(tfms.Trim(), csprojVariables);
//tfms should be separated by ;
var splitTfms = processedTfm.Split(";");
foreach (var tfm in splitTfms)
{
if (!string.IsNullOrEmpty(tfm) && ProjectModelHelper.ShortTfmDictionary.Values.ToList().Contains(tfm, StringComparer.OrdinalIgnoreCase))
{
processedTfms.Add(tfm);
}
}
}
}
return processedTfms.ToArray();
}

// Returns true when there is no Startup.cs or equivalent
internal static async Task<bool> IsMinimalApp(List<Document> documents)
{
Expand Down Expand Up @@ -442,7 +500,7 @@ internal static bool FilterOptions(string[] options, CodeChangeOptions codeChang

/// <summary>
/// Replaces text within document or appends text to the end of the document
/// depending on whether change.ReplaceSnippet is set
/// depending on whether change.ReplaceSnippet is set
/// </summary>
/// <param name="fileDoc"></param>
/// <param name="codeChanges"></param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static IEnumerable<DependencyDescription> GetReferencingPackages(this IPr
.Dependencies
.Any(dep => dep.Name.Equals(name, StringComparison.OrdinalIgnoreCase)));
}

public static IProjectContext AddPackageDependencies(this IProjectContext projectInformation, string projectAssetsFile)
{
//get project assets file
Expand Down Expand Up @@ -64,5 +65,30 @@ public static IProjectContext AddPackageDependencies(this IProjectContext projec
return newProjectContext;
}

/// <summary>
/// Given an IProjectContext, check if IProjectContext.Nullable is set, if it is, no changes necessary.
/// If not already there, set using ProjectContextHelper.GetXmlKeyValue (parsing the csproj xml file).
/// </summary>
/// <param name="context">IProjectContext which has csproj path, and the Nullable variable to set.</param>
/// <returns>modified IProjectContext with the Nullable property set or the same IProjectContext as passed.</returns>
public static IProjectContext CheckNullableVariable(this IProjectContext context)
{
//if nullable is not empty, return current IProjectContext as is.
if (context != null && string.IsNullOrEmpty(context.Nullable))
{
string csprojText = System.IO.File.ReadAllText(context.ProjectFullPath);
string nullableVarValue = ProjectContextHelper.GetXmlKeyValue("nullable", csprojText);
if (!string.IsNullOrEmpty(nullableVarValue))
{
if (context is CommonProjectContext newProjectContext)
{
newProjectContext.Nullable = nullableVarValue;
return newProjectContext;
}
}
}
return context;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Xml.Linq;
using Microsoft.DotNet.Scaffolding.Shared.ProjectModel;
using Microsoft.VisualStudio.Web.CodeGeneration.Utils;

Expand Down Expand Up @@ -172,5 +173,32 @@ internal static IList<DependencyDescription> DeserializePackages(JsonElement pac

return packageDependencies;
}

/// <summary>
/// Returns the value given a key (aka a xml tag) from the given xml file
/// </summary>
/// <param name="variableKey">variable key or tags in the csproj file</param>
/// <param name="xmlText">xml file text (mostly used for parsing csproj file)</param>
/// <returns>empty or value from the parsing the elements of the xml file.</returns>
internal static string GetXmlKeyValue(string variableKey, string xmlText)
{
string variableValue = string.Empty;
if (!string.IsNullOrEmpty(xmlText) && !string.IsNullOrEmpty(variableKey))
{
//use XDocument to get all csproj elements.
XDocument document = XDocument.Parse(xmlText);
var docNodes = document.Root?.Elements();
var allElements = docNodes?.SelectMany(x => x.Elements());
if (allElements != null && allElements.Any())
{
var varValueElement = allElements.FirstOrDefault(e => e.Name.LocalName.Equals(variableKey, StringComparison.OrdinalIgnoreCase));
if (varValueElement != null)
{
variableValue = varValueElement.Value;
}
}
}
return variableValue;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,18 @@ public class ProjectContextWriterTests : TestBase
{
static string testAppPath = Path.Combine("..", "TestApps", "ModelTypesLocatorTestClassLibrary");
private ITestOutputHelper _outputHelper;
private const string Net7CsprojVariabledCsproj = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
<PropertyGroup>
<Var1>$(Var2)$(Var3)</Var1>
<Var2>net</Var2>
<Var3>7.0</Var3>
<TargetFramework>$(Var1)</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
";
public ProjectContextWriterTests(ITestOutputHelper outputHelper)
{
_outputHelper = outputHelper;
Expand Down Expand Up @@ -116,5 +127,25 @@ public void GetPathTestOSX(string nugetPath, string packageName, string version,
Assert.Equal(expectedPath, ProjectContextHelper.GetPath(nugetPath, nameAndVersion));
}
}

[Fact]
public void GetXmlKeyValueTests()
{
var tfmValue = ProjectContextHelper.GetXmlKeyValue("TargetFramework", Net7CsprojVariabledCsproj);
var var2Value = ProjectContextHelper.GetXmlKeyValue("Var2", Net7CsprojVariabledCsproj);
var nullableValue = ProjectContextHelper.GetXmlKeyValue("Nullable", Net7CsprojVariabledCsproj);
var implicitValue = ProjectContextHelper.GetXmlKeyValue("ImplicitUsings", Net7CsprojVariabledCsproj);
var invalidValue = ProjectContextHelper.GetXmlKeyValue("bleh", Net7CsprojVariabledCsproj);
var emptyValue = ProjectContextHelper.GetXmlKeyValue("", Net7CsprojVariabledCsproj);
var nullValue = ProjectContextHelper.GetXmlKeyValue(null, Net7CsprojVariabledCsproj);

Assert.Equal("$(Var1)", tfmValue, ignoreCase: true);
Assert.Equal("net", var2Value, ignoreCase: true);
Assert.Equal("enable", nullableValue, ignoreCase: true);
Assert.Equal("enable", implicitValue, ignoreCase: true);
Assert.Equal("", invalidValue, ignoreCase: true);
Assert.Equal("", emptyValue, ignoreCase: true);
Assert.Equal("", nullValue, ignoreCase: true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ public void FormatGlobalStatementTests()
"Builder.Build()",
"nonExistentVariable.DoingStuff(string);",
"nonExistentVariable.DoingOtherStuff()",
"Builder.Boolean.ToString()"
"Builder.Boolean.ToString()"
};

string[] correctlyFormattedStatements = new string[]
Expand Down Expand Up @@ -392,7 +392,7 @@ public void EmptyBlockStatementExistsTests(string contents, bool contains)
{
StatementSyntax emptyBlock = SyntaxFactory.ParseStatement(
@"
{
{
}");

BlockSyntax emptyBlockSyntax = SyntaxFactory.Block(emptyBlock);
Expand Down Expand Up @@ -439,7 +439,7 @@ public void DenseBlockStatementExistsTests(string contents, bool contains)
{
endpoints.MapRazorPages();
endpoints.MapControllers();
});
});
}");

BlockSyntax denseBlockSyntax = SyntaxFactory.Block(denseBlock);
Expand Down Expand Up @@ -497,6 +497,33 @@ public async Task GlobalStatementExistsTests(string[] existingStatements, string
}
}

[Fact]
public void ProcessCsprojTfmsTest()
{
var net7Tfms = ProjectModifierHelper.ProcessCsprojTfms(Net7Csproj);
var net7Tfms2 = ProjectModifierHelper.ProcessCsprojTfms(Net7CsprojVariabledCsproj);
var net7Tfms3 = ProjectModifierHelper.ProcessCsprojTfms(Net7CsprojVariabledCsproj2);
Assert.True(net7Tfms.Length == 1 && net7Tfms2.Length == 1 && net7Tfms3.Length == 1);
Assert.True(net7Tfms.First().Equals("net7.0") && net7Tfms2.First().Equals("net7.0") && net7Tfms3.First().Equals("net7.0"));

var emptyTfms = ProjectModifierHelper.ProcessCsprojTfms(EmptyCsproj);
var emptyTfms2 = ProjectModifierHelper.ProcessCsprojTfms(EmptyCsproj2);
Assert.True(emptyTfms.Length == 0 && emptyTfms2.Length == 0);

var invalidTfm = ProjectModifierHelper.ProcessCsprojTfms(InvalidCsproj);
var invalidTfm2 = ProjectModifierHelper.ProcessCsprojTfms(InvalidCsproj2);
var invalidTfm3 = ProjectModifierHelper.ProcessCsprojTfms(InvalidCsproj3);
Assert.True(invalidTfm.Length == 0 && invalidTfm2.Length == 0 && invalidTfm3.Length == 0);

var multiTfm = ProjectModifierHelper.ProcessCsprojTfms(MultiTfmCsproj);
var multiTfm2 = ProjectModifierHelper.ProcessCsprojTfms(MultiTfmVariabledCsproj);
var multiTfm3 = ProjectModifierHelper.ProcessCsprojTfms(MultiTfmVariabledCsproj2);
Assert.True(multiTfm.Length == 2 && net7Tfms2.Length == 1 && multiTfm3.Length == 3);
Assert.True(multiTfm.Contains("net6.0") && multiTfm.Contains("net7.0"));
Assert.True(multiTfm2.Contains("net6.0") && multiTfm2.Contains("net7.0"));
Assert.True(multiTfm3.Contains("net5.0") && multiTfm3.Contains("net6.0") && multiTfm3.Contains("net7.0"));
}

private static readonly ModelType startupModel = new ModelType
{
Name = "Startup",
Expand Down

0 comments on commit 0f160ce

Please sign in to comment.