Skip to content

Commit

Permalink
Fix issue icsharpcode#1003.
Browse files Browse the repository at this point in the history
  • Loading branch information
philippejer committed Apr 16, 2023
1 parent 4ea68bd commit ddf9a92
Show file tree
Hide file tree
Showing 13 changed files with 124 additions and 164 deletions.
122 changes: 63 additions & 59 deletions CodeConverter/CSharp/VBToCSConversion.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Text;
using SyntaxKind = Microsoft.CodeAnalysis.VisualBasic.SyntaxKind;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.VisualBasic;
using ISymbolExtensions = ICSharpCode.CodeConverter.Util.ISymbolExtensions;
Expand Down Expand Up @@ -60,85 +61,88 @@ public string PostTransformProjectFile(string xml)
{
xml = ProjectFileTextEditor.WithUpdatedDefaultItemExcludes(xml, "cs", "vb");

if (!Regex.IsMatch(xml, @"<Reference\s+Include=""Microsoft.VisualBasic""\s*/>")) {
xml = new Regex(@"(<ItemGroup>)(\s*)").Replace(xml, "$1$2<Reference Include=\"Microsoft.VisualBasic\" />$2", 1);
}
var xmlDoc = XDocument.Parse(xml);
XNamespace xmlNs = xmlDoc.Root.GetDefaultNamespace();

AddVisualBasicReference(xmlNs, xmlDoc);
AddLangVersion(xmlNs, xmlDoc);

if (!Regex.IsMatch(xml, @"<\s*LangVersion\s*>")) {
xml = new Regex(@"(\s*)(</\s*PropertyGroup\s*>)").Replace(xml, $"$1 <LangVersion>{_vbToCsProjectContentsConverter.LanguageVersion}</LangVersion>$1$2", 1);
var propertyGroups = xmlDoc.Descendants(xmlNs + "PropertyGroup");
foreach (XElement propertyGroup in propertyGroups) {
TweakDefineConstants(xmlNs, propertyGroup);
TweakOutputPath(xmlNs, propertyGroup);
}

xml = TweakDefineConstants(xml);
xml = TweakOutputPaths(xml);
return xml;
return xmlDoc.Declaration != null ? xmlDoc.Declaration + Environment.NewLine + xmlDoc : xmlDoc.ToString();
}

private static string TweakDefineConstants(string xml)
private static void AddVisualBasicReference(XNamespace xmlNs, XDocument xmlDoc)
{
// TODO Find API to, or parse project file sections to remove "<DefineDebug>true</DefineDebug>" + "<DefineTrace>true</DefineTrace>"
// Then add them to the define constants in the same section, or create one if necessary.

var defineConstantsStart = xml.IndexOf("<DefineConstants>", StringComparison.Ordinal);
var defineConstantsEnd = xml.IndexOf("</DefineConstants>", StringComparison.Ordinal);
if (defineConstantsStart == -1 || defineConstantsEnd == -1)
return xml;
var reference = xmlDoc.Descendants(xmlNs + "Reference").FirstOrDefault(e => e.Attribute("Include")?.Value == "Microsoft.VisualBasic");
if (reference != null) {
return;
}

return xml.Substring(0, defineConstantsStart) +
xml.Substring(defineConstantsStart, defineConstantsEnd - defineConstantsStart).Replace(",", ";") +
xml.Substring(defineConstantsEnd);
var firstItemGroup = xmlDoc.Descendants(xmlNs + "ItemGroup").FirstOrDefault();
reference = new XElement(xmlNs + "Reference");
reference.SetAttributeValue("Include", "Microsoft.VisualBasic");
firstItemGroup?.AddFirst(reference);
}

private static string TweakOutputPaths(string s)
private void AddLangVersion(XNamespace xmlNs, XDocument xmlDoc)
{
var startTag = "<PropertyGroup";
var endTag = "</PropertyGroup>";
var prevGroupEnd = 0;
var propertyGroupStart = s.IndexOf(startTag, StringComparison.Ordinal);
var propertyGroupEnd = s.IndexOf(endTag, StringComparison.Ordinal);
var sb = new StringBuilder();
var langVersion = xmlDoc.Descendants(xmlNs + "LangVersion").FirstOrDefault();
if (langVersion != null) {
return;
}

if (propertyGroupStart == -1 || propertyGroupEnd == -1)
return s;
var firstPropertyGroup = xmlDoc.Descendants(xmlNs + "PropertyGroup").FirstOrDefault();
langVersion = new XElement(xmlNs + "LangVersion", _vbToCsProjectContentsConverter.LanguageVersion);
firstPropertyGroup?.Add(langVersion);
}

do {
sb.Append(s.Substring(prevGroupEnd, propertyGroupStart - prevGroupEnd));
private static void TweakDefineConstants(XNamespace xmlNs, XElement propertyGroup)
{
var defineConstants = propertyGroup.Element(xmlNs + "DefineConstants");

var curSegment = s.Substring(propertyGroupStart, propertyGroupEnd - propertyGroupStart);
curSegment = TweakOutputPath(curSegment);
sb.Append(curSegment);
prevGroupEnd = propertyGroupEnd;
propertyGroupStart = s.IndexOf(startTag, propertyGroupEnd, StringComparison.Ordinal);
propertyGroupEnd = s.IndexOf(endTag, prevGroupEnd + 1, StringComparison.Ordinal);
} while (propertyGroupStart != -1 && propertyGroupEnd != -1);
var defineDebug = propertyGroup.Element(xmlNs + "DefineDebug");
bool shouldDefineDebug = defineDebug?.Value == "true";
defineDebug?.Remove();

sb.Append(s.Substring(prevGroupEnd));
var defineTrace = propertyGroup.Element(xmlNs + "DefineTrace");
bool shouldDefineTrace = defineTrace?.Value == "true";
defineTrace?.Remove();

return sb.ToString();
}
if (defineConstants == null && (shouldDefineDebug || shouldDefineTrace)) {
defineConstants = new XElement(xmlNs + "DefineConstants", "");
propertyGroup.Add(defineConstants);
}

private static string TweakOutputPath(string s)
{
var startPathTag = "<OutputPath>";
var endPathTag = "</OutputPath>";
var pathStart = s.IndexOf(startPathTag, StringComparison.Ordinal);
var pathEnd = s.IndexOf(endPathTag, StringComparison.Ordinal);
if (defineConstants != null) {
// CS uses semi-colon as separator, VB uses comma
var values = defineConstants.Value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).ToList();

if (pathStart == -1 || pathEnd == -1)
return s;
var filePath = s.Substring(pathStart + startPathTag.Length,
pathEnd - (pathStart + startPathTag.Length));
// DefineDebug and DefineTrace are ignored in CS
if (shouldDefineDebug && !values.Contains("DEBUG")) {
values.Insert(0, "DEBUG");
}
if (shouldDefineTrace && !values.Contains("TRACE")) {
values.Insert(0, "TRACE");
}

var startFileTag = "<DocumentationFile>";
var endFileTag = "</DocumentationFile>";
var fileTagStart = s.IndexOf(startFileTag, StringComparison.Ordinal);
var fileTagEnd = s.IndexOf(endFileTag, StringComparison.Ordinal);
defineConstants.Value = string.Join(";", values);
}
}

if (fileTagStart == -1 || fileTagEnd == -1)
return s;
private static void TweakOutputPath(XNamespace xmlNs, XElement propertyGroup)
{
var outputPath = propertyGroup.Element(xmlNs + "OutputPath");
var documentationFile = propertyGroup.Element(xmlNs + "DocumentationFile");

return s.Substring(0, fileTagStart + startFileTag.Length) +
filePath +
s.Substring(fileTagStart + startFileTag.Length);
// In CS, DocumentationFile is prepended by OutputPath, not in VB
if (outputPath != null && documentationFile != null) {
documentationFile.Value = outputPath.Value + documentationFile.Value;
}
}

public string TargetLanguage { get; } = LanguageNames.CSharp;
Expand Down
101 changes: 32 additions & 69 deletions CodeConverter/VB/CSToVBConversion.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Globalization;
using System.Text;
using System.Xml.Linq;
using ICSharpCode.CodeConverter.CSharp;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -56,91 +57,53 @@ public SyntaxNode GetSurroundedNode(IEnumerable<SyntaxNode> descendantNodes,
public string PostTransformProjectFile(string xml)
{
xml = ProjectFileTextEditor.WithUpdatedDefaultItemExcludes(xml, "vb", "cs");
xml = TweakDefineConstantsSeparator(xml);
xml = AddInfer(xml);
xml = TweakOutputPaths(xml);
return xml;
}

private static string AddInfer(string xml)
{
if (xml.IndexOf("<OptionInfer>", StringComparison.Ordinal) > -1) return xml;
var xmlDoc = XDocument.Parse(xml);
XNamespace xmlNs = xmlDoc.Root.GetDefaultNamespace();

AddInfer(xmlNs, xmlDoc);

string propertygroup = "<PropertyGroup>";
var startOfFirstPropertyGroup = xml.IndexOf(propertygroup, StringComparison.Ordinal);
if (startOfFirstPropertyGroup == -1) return xml;
var propertyGroups = xmlDoc.Descendants(xmlNs + "PropertyGroup");
foreach (XElement propertyGroup in propertyGroups) {
TweakDefineConstants(xmlNs, propertyGroup);
TweakOutputPath(xmlNs, propertyGroup);
}

int endOfFirstPropertyGroupStartTag = startOfFirstPropertyGroup + propertygroup.Length;
return xml.Substring(0, endOfFirstPropertyGroupStartTag) + Environment.NewLine + " <OptionInfer>On</OptionInfer>" +
xml.Substring(endOfFirstPropertyGroupStartTag);
return xmlDoc.Declaration != null ? xmlDoc.Declaration + Environment.NewLine + xmlDoc : xmlDoc.ToString();
}

private static string TweakDefineConstantsSeparator(string s)
private static void AddInfer(XNamespace xmlNs, XDocument xmlDoc)
{
var startTag = "<DefineConstants>";
var endTag = "</DefineConstants>";
var defineConstantsStart = s.IndexOf(startTag, StringComparison.Ordinal);
var defineConstantsEnd = s.IndexOf(endTag, StringComparison.Ordinal);
if (defineConstantsStart == -1 || defineConstantsEnd == -1)
return s;
var infer = xmlDoc.Descendants(xmlNs + "OptionInfer").FirstOrDefault();
if (infer != null) {
return;
}

return s.Substring(0, defineConstantsStart) +
s.Substring(defineConstantsStart, defineConstantsEnd - defineConstantsStart).Replace(";", ",") +
s.Substring(defineConstantsEnd);
var firstPropertyGroup = xmlDoc.Descendants(xmlNs + "PropertyGroup").FirstOrDefault();
infer = new XElement(xmlNs + "OptionInfer", "On");
firstPropertyGroup?.AddFirst(infer);
}

private static string TweakOutputPaths(string s)
private static void TweakDefineConstants(XNamespace xmlNs, XElement propertyGroup)
{
var startTag = "<PropertyGroup";
var endTag = "</PropertyGroup>";
var prevGroupEnd = 0;
var propertyGroupStart = s.IndexOf(startTag, StringComparison.Ordinal);
var propertyGroupEnd = s.IndexOf(endTag, StringComparison.Ordinal);
var defineConstants = propertyGroup.Element(xmlNs + "DefineConstants");

if (propertyGroupStart == -1 || propertyGroupEnd == -1) {
return s;
}

var sb = new StringBuilder();
while (propertyGroupStart != -1 && propertyGroupEnd != -1) {
sb.Append(s, prevGroupEnd, propertyGroupStart - prevGroupEnd);

var curSegment = s.Substring(propertyGroupStart, propertyGroupEnd - propertyGroupStart);
curSegment = TweakOutputPath(curSegment);
sb.Append(curSegment);
prevGroupEnd = propertyGroupEnd;
propertyGroupStart = s.IndexOf(startTag, propertyGroupEnd, StringComparison.Ordinal);
propertyGroupEnd = s.IndexOf(endTag, prevGroupEnd + 1, StringComparison.Ordinal);
if (defineConstants != null) {
// CS uses semi-colon as separator, VB uses comma
var values = defineConstants.Value.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries).ToList();
defineConstants.Value = string.Join(",", values);
}

sb.Append(s, prevGroupEnd, s.Length - prevGroupEnd);

return sb.ToString();
}

private static string TweakOutputPath(string s)
private static void TweakOutputPath(XNamespace xmlNs, XElement propertyGroup)
{
var startPathTag = "<OutputPath>";
var endPathTag = "</OutputPath>";
var pathStart = s.IndexOf(startPathTag, StringComparison.Ordinal);
var pathEnd = s.IndexOf(endPathTag, StringComparison.Ordinal);

if (pathStart == -1 || pathEnd == -1)
return s;
var filePath = s.Substring(pathStart + startPathTag.Length,
pathEnd - (pathStart + startPathTag.Length));

var startFileTag = "<DocumentationFile";
var endFileTag = "</DocumentationFile>";
var fileTagStart = s.IndexOf(startFileTag, StringComparison.Ordinal);
var fileTagEnd = s.IndexOf(endFileTag, StringComparison.Ordinal);
var outputPath = propertyGroup.Element(xmlNs + "OutputPath");
var documentationFile = propertyGroup.Element(xmlNs + "DocumentationFile");

if (fileTagStart == -1 || fileTagEnd == -1)
return s;

return s.Substring(0, fileTagStart) +
s.Substring(fileTagStart, fileTagEnd - fileTagStart).Replace(filePath, "") +
s.Substring(fileTagEnd);
// In CS, DocumentationFile is prepended by OutputPath, not in VB
if (outputPath != null && documentationFile != null) {
documentationFile.Value = documentationFile.Value.Replace(outputPath.Value, "");
}
}

public string TargetLanguage { get; } = LanguageNames.VisualBasic;
Expand Down
8 changes: 8 additions & 0 deletions Tests/LanguageAgnostic/SolutionFileTextEditorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,8 @@ private static string GetSolutionProjectReference(IReadOnlyCollection<(string Pr
? (Action<string>) (stringToAppend => referenceBuilder.AppendLine(stringToAppend))
: stringToAppend => referenceBuilder.Append(stringToAppend);

builderAppendMethod("<Project>");

foreach ((string projTypeGuid, string projName, string relativeProjPath, string projRefGuid) in projRefTuples)
{
var referenceStringToAppend = $@"Project(""{{{projTypeGuid}}}"") = ""{projName}"","
Expand All @@ -521,6 +523,8 @@ private static string GetSolutionProjectReference(IReadOnlyCollection<(string Pr
builderAppendMethod(referenceStringToAppend);
}

builderAppendMethod("</Project>");

var referenceString = referenceBuilder.ToString();

return Utils.HomogenizeEol(referenceString);
Expand All @@ -533,12 +537,16 @@ private static string GetProjectProjectReference(IReadOnlyCollection<string> rel
? (Action<string>) (stringToAppend => referenceBuilder.AppendLine(stringToAppend))
: stringToAppend => referenceBuilder.Append(stringToAppend);

builderAppendMethod("<Project>");

foreach (var relativeProjPath in relProjPaths)
{
var referenceStringToAppend = $@"<ProjectReference Include=""{relativeProjPath}"" />";
builderAppendMethod(referenceStringToAppend);
}

builderAppendMethod("</Project>");

var referenceString = referenceBuilder.ToString();

return Utils.HomogenizeEol(referenceString);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OptionInfer>On</OptionInfer>
<TargetFramework>netstandard2.0</TargetFramework>
<DefaultItemExcludes>$(DefaultItemExcludes);$(ProjectDir)**\*.cs</DefaultItemExcludes>
</PropertyGroup>

</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OptionInfer>On</OptionInfer>
<OutputType>Exe</OutputType>
<TargetFramework>net472</TargetFramework>
<DefaultItemExcludes>$(DefaultItemExcludes);$(ProjectDir)**\*.cs</DefaultItemExcludes>
</PropertyGroup>

</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,19 @@
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DefineDebug>true</DefineDebug>
<DefineTrace>true</DefineTrace>
<OutputPath>bin\Debug\</OutputPath>
<DocumentationFile>bin\Debug\VbLibrary.xml</DocumentationFile>
<NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
<DefineConstants>TRACE;DEBUG</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<DefineDebug>false</DefineDebug>
<DefineTrace>true</DefineTrace>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DocumentationFile>bin\Release\VbLibrary.xml</DocumentationFile>
<NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<OptionExplicit>On</OptionExplicit>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,19 @@
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DefineDebug>true</DefineDebug>
<DefineTrace>true</DefineTrace>
<OutputPath>bin\Debug\</OutputPath>
<DocumentationFile>bin\Debug\ConsoleApp1.xml</DocumentationFile>
<NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
<DefineConstants>TRACE;DEBUG</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<DefineDebug>false</DefineDebug>
<DefineTrace>true</DefineTrace>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DocumentationFile>bin\Release\ConsoleApp1.xml</DocumentationFile>
<NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<OptionExplicit>On</OptionExplicit>
Expand Down
Loading

0 comments on commit ddf9a92

Please sign in to comment.