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 bc55584
Show file tree
Hide file tree
Showing 15 changed files with 161 additions and 187 deletions.
123 changes: 63 additions & 60 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 @@ -58,87 +59,89 @@ public SyntaxNode GetSurroundedNode(IEnumerable<SyntaxNode> descendantNodes,

public string PostTransformProjectFile(string xml)
{
xml = ProjectFileTextEditor.WithUpdatedDefaultItemExcludes(xml, "cs", "vb");
var xmlDoc = XDocument.Parse(xml);
XNamespace xmlNs = xmlDoc.Root.GetDefaultNamespace();

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);
}
ProjectFileTextEditor.WithUpdatedDefaultItemExcludes(xmlNs, xmlDoc, "cs", "vb");
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
19 changes: 9 additions & 10 deletions CodeConverter/Common/ProjectFileTextEditor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace ICSharpCode.CodeConverter.Common;

Expand All @@ -7,19 +8,17 @@ internal static class ProjectFileTextEditor
/// <summary>
/// Hide pre-conversion files, and ensure files we've just created aren't hidden
/// </summary>
public static string WithUpdatedDefaultItemExcludes(string s, string extensionNotToExclude, string extensionToExclude)
public static void WithUpdatedDefaultItemExcludes(XNamespace xmlNs, XDocument xmlDoc, string extensionNotToExclude, string extensionToExclude)
{
string verbatimExcludeToRemove = Regex.Escape($@"$(ProjectDir)**\*.{extensionNotToExclude}");
var matchDefaultItemExcludes =
new Regex($@"(<DefaultItemExcludes>.*){verbatimExcludeToRemove}(.*<\/DefaultItemExcludes>)");
if (matchDefaultItemExcludes.IsMatch(s)) {
s = matchDefaultItemExcludes.Replace(s, $@"$1$(ProjectDir)**\*.{extensionToExclude}$2", 1);
var matchDefaultItemExcludes = new Regex($@"(.*){verbatimExcludeToRemove}(.*)");
var defaultItemExcludes = xmlDoc.Descendants("DefaultItemExcludes").FirstOrDefault(e => matchDefaultItemExcludes.IsMatch(e.Value));
if (defaultItemExcludes != null) {
defaultItemExcludes.Value = matchDefaultItemExcludes.Replace(defaultItemExcludes.Value, $@"$1$(ProjectDir)**\*.{extensionToExclude}$2");
} else {
var firstPropertyGroupEnd = new Regex(@"(\s*</PropertyGroup>)");
s = firstPropertyGroupEnd.Replace(s,
"\r\n" + $@" <DefaultItemExcludes>$(DefaultItemExcludes);$(ProjectDir)**\*.{extensionToExclude}</DefaultItemExcludes>$1", 1);
var firstPropertyGroup = xmlDoc.Descendants(xmlNs + "PropertyGroup").FirstOrDefault();
defaultItemExcludes = new XElement(xmlNs + "DefaultItemExcludes", $"$(DefaultItemExcludes);$(ProjectDir)**\\*.{extensionToExclude}");
firstPropertyGroup?.Add(defaultItemExcludes);
}

return s;
}
}
102 changes: 32 additions & 70 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 @@ -55,92 +56,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;
}
var xmlDoc = XDocument.Parse(xml);
XNamespace xmlNs = xmlDoc.Root.GetDefaultNamespace();

private static string AddInfer(string xml)
{
if (xml.IndexOf("<OptionInfer>", StringComparison.Ordinal) > -1) return xml;
ProjectFileTextEditor.WithUpdatedDefaultItemExcludes(xmlNs, xmlDoc, "vb", "cs");
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
Loading

0 comments on commit bc55584

Please sign in to comment.