Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemet conversion of XML namespace imports #852

Merged
merged 2 commits into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 39 additions & 5 deletions CodeConverter/CSharp/DeclarationNodeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ internal class DeclarationNodeVisitor : VBasic.VisualBasicSyntaxVisitor<Task<CSh
private readonly TypeContext _typeContext = new TypeContext();
private uint _failedMemberConversionMarkerCount;
private readonly HashSet<string> _extraUsingDirectives = new HashSet<string>();
private readonly XmlImportContext _xmlImportContext;
private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison;
private HashSet<string> _accessedThroughMyClass;
public CommentConvertingVisitorWrapper TriviaConvertingDeclarationVisitor { get; }
Expand All @@ -58,12 +59,13 @@ public DeclarationNodeVisitor(Document document, Compilation compilation, Semant
_semanticModel = semanticModel;
_csSyntaxGenerator = csSyntaxGenerator;
_typeToInheritors = typeToInheritors;
_xmlImportContext = new XmlImportContext(document);
_visualBasicEqualityComparison = new VisualBasicEqualityComparison(_semanticModel, _extraUsingDirectives);
TriviaConvertingDeclarationVisitor = new CommentConvertingVisitorWrapper(this, _semanticModel.SyntaxTree);
var expressionEvaluator = new ExpressionEvaluator(semanticModel, _visualBasicEqualityComparison);
var typeConversionAnalyzer = new TypeConversionAnalyzer(semanticModel, csCompilation, _extraUsingDirectives, _csSyntaxGenerator, expressionEvaluator);
CommonConversions = new CommonConversions(document, semanticModel, typeConversionAnalyzer, csSyntaxGenerator, compilation, csCompilation, _typeContext, _visualBasicEqualityComparison);
var expressionNodeVisitor = new ExpressionNodeVisitor(semanticModel, _visualBasicEqualityComparison, _typeContext, CommonConversions, _extraUsingDirectives);
var expressionNodeVisitor = new ExpressionNodeVisitor(semanticModel, _visualBasicEqualityComparison, _typeContext, CommonConversions, _extraUsingDirectives, _xmlImportContext);
_triviaConvertingExpressionVisitor = expressionNodeVisitor.TriviaConvertingExpressionVisitor;
_createMethodBodyVisitorAsync = expressionNodeVisitor.CreateMethodBodyVisitorAsync;
CommonConversions.TriviaConvertingExpressionVisitor = _triviaConvertingExpressionVisitor;
Expand Down Expand Up @@ -91,6 +93,8 @@ public override async Task<CSharpSyntaxNode> VisitCompilationUnit(VBSyntax.Compi

var attributes = SyntaxFactory.List(await node.Attributes.SelectMany(a => a.AttributeLists).SelectManyAsync(CommonConversions.ConvertAttributeAsync));

var xmlImportHelperClassDeclarationOrNull = (await _xmlImportContext.HandleImportsAsync(importsClauses, x => x.AcceptAsync<FieldDeclarationSyntax>(TriviaConvertingDeclarationVisitor))).GenerateHelper();

var sourceAndConverted = await node.Members
.Where(m => !(m is VBSyntax.OptionStatementSyntax))
.SelectAsync(async m => (Source: m, Converted: await ConvertMemberAsync(m)));
Expand All @@ -99,20 +103,21 @@ public override async Task<CSharpSyntaxNode> VisitCompilationUnit(VBSyntax.Compi
var convertedMembers = string.IsNullOrEmpty(options.RootNamespace)
? sourceAndConverted.Select(sd => sd.Converted)
: PrependRootNamespace(sourceAndConverted, SyntaxFactory.IdentifierName(options.RootNamespace));

var usings = await importsClauses
.SelectAsync(async c => await c.AcceptAsync<UsingDirectiveSyntax>(TriviaConvertingDeclarationVisitor));
.SelectAsync(async c => await c.AcceptAsync<UsingDirectiveSyntax>(TriviaConvertingDeclarationVisitor));
var usingDirectiveSyntax = usings
.Concat(_extraUsingDirectives.Select(u => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(u))))
.OrderByDescending(IsSystemUsing).ThenBy(u => u.Name.ToString().Replace("global::", "")).ThenByDescending(HasSourceMapAnnotations)
.GroupBy(u => (Name: u.Name.ToString(), Alias: u.Alias))
.Select(g => g.First());
.Select(g => g.First())
.Concat(xmlImportHelperClassDeclarationOrNull.YieldNotNull().Select(x => SyntaxFactory.UsingDirective(SyntaxFactory.NameEquals(_xmlImportContext.HelperClassShortIdentifierName), _xmlImportContext.HelperClassUniqueIdentifierName)));

return SyntaxFactory.CompilationUnit(
SyntaxFactory.List<ExternAliasDirectiveSyntax>(),
SyntaxFactory.List(usingDirectiveSyntax),
attributes,
SyntaxFactory.List(convertedMembers)
SyntaxFactory.List(xmlImportHelperClassDeclarationOrNull.YieldNotNull().Concat(convertedMembers))
);
}

Expand Down Expand Up @@ -1607,5 +1612,34 @@ public override async Task<CSharpSyntaxNode> VisitTypeConstraint(VBSyntax.TypeCo
return SyntaxFactory.TypeConstraint(await node.Type.AcceptAsync<TypeSyntax>(_triviaConvertingExpressionVisitor));
}

public override async Task<CSharpSyntaxNode> VisitXmlNamespaceImportsClause(VBSyntax.XmlNamespaceImportsClauseSyntax node)
{
var identifierName = await node.XmlNamespace.Name.AcceptAsync<IdentifierNameSyntax>(TriviaConvertingDeclarationVisitor);
var valueLiteral = await node.XmlNamespace.Value.AcceptAsync<ExpressionSyntax>(TriviaConvertingDeclarationVisitor);
var declarator = SyntaxFactory.VariableDeclarator(identifierName.Identifier, null, SyntaxFactory.EqualsValueClause(valueLiteral));
return SyntaxFactory.FieldDeclaration(
SyntaxFactory.List<AttributeListSyntax>(),
SyntaxFactory.TokenList(SyntaxFactory.Token(CSSyntaxKind.InternalKeyword),
SyntaxFactory.Token(CSSyntaxKind.StaticKeyword),
SyntaxFactory.Token(CSSyntaxKind.ReadOnlyKeyword)),
SyntaxFactory.VariableDeclaration(SyntaxFactory.IdentifierName("XNamespace"), SyntaxFactory.SingletonSeparatedList(declarator)));
}

public override async Task<CSharpSyntaxNode> VisitXmlName(VBSyntax.XmlNameSyntax node)
{
if (node.Prefix == null && node.LocalName.ValueText == "xmlns") {
// default namespace
return _xmlImportContext.DefaultIdentifierName;
} else if (node.Prefix.Name.ValueText == "xmlns") {
// namespace alias
return SyntaxFactory.IdentifierName(node.LocalName.ValueText);
} else {
// Having this case in VB would cause error BC31187: Namespace declaration must start with 'xmlns'
throw new NotImplementedException($"Cannot convert non-xmlns attribute in XML namespace import").WithNodeInformation(node);
}
}

public override async Task<CSharpSyntaxNode> VisitXmlString(VBSyntax.XmlStringSyntax node) =>
CommonConversions.Literal(node.TextTokens.Aggregate("", (a, b) => a + LiteralConversions.EscapeVerbatimQuotes(b.Text)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure of the likely inputs here myself, but if there could be quite a few strings and especially if they could be big, consider StringBuilder

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I guess String.Join could also work, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that'd probably be slightly more readable too actually 👍

}
}
46 changes: 39 additions & 7 deletions CodeConverter/CSharp/ExpressionNodeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ internal class ExpressionNodeVisitor : VBasic.VisualBasicSyntaxVisitor<Task<CSha
public CommentConvertingVisitorWrapper TriviaConvertingExpressionVisitor { get; }
private readonly SemanticModel _semanticModel;
private readonly HashSet<string> _extraUsingDirectives;
private readonly XmlImportContext _xmlImportContext;
private readonly IOperatorConverter _operatorConverter;
private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison;
private readonly Stack<ExpressionSyntax> _withBlockLhs = new();
Expand All @@ -47,7 +48,7 @@ internal class ExpressionNodeVisitor : VBasic.VisualBasicSyntaxVisitor<Task<CSha

public ExpressionNodeVisitor(SemanticModel semanticModel,
VisualBasicEqualityComparison visualBasicEqualityComparison, ITypeContext typeContext, CommonConversions commonConversions,
HashSet<string> extraUsingDirectives)
HashSet<string> extraUsingDirectives, XmlImportContext xmlImportContext)
{
CommonConversions = commonConversions;
_semanticModel = semanticModel;
Expand All @@ -57,6 +58,7 @@ public ExpressionNodeVisitor(SemanticModel semanticModel,
_queryConverter = new QueryConverter(commonConversions, _semanticModel, TriviaConvertingExpressionVisitor);
_typeContext = typeContext;
_extraUsingDirectives = extraUsingDirectives;
_xmlImportContext = xmlImportContext;
_operatorConverter = VbOperatorConversion.Create(TriviaConvertingExpressionVisitor, semanticModel, visualBasicEqualityComparison, commonConversions.TypeConversionAnalyzer);
// If this isn't needed, the assembly with Conversions may not be referenced, so this must be done lazily
_convertMethodsLookupByReturnType =
Expand Down Expand Up @@ -102,28 +104,36 @@ public override async Task<CSharpSyntaxNode> VisitXmlDocument(VBasic.Syntax.XmlD
.Concat(SyntaxFactory.Argument(await node.Root.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor)).Yield())
.Concat(await node.FollowingMisc.SelectAsync(async misc => SyntaxFactory.Argument(await misc.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor))))
);
return SyntaxFactory.ObjectCreationExpression(SyntaxFactory.IdentifierName("XDocument")).WithArgumentList(SyntaxFactory.ArgumentList(arguments));
return ApplyXmlImportsIfNecessary(node, SyntaxFactory.ObjectCreationExpression(SyntaxFactory.IdentifierName("XDocument")).WithArgumentList(SyntaxFactory.ArgumentList(arguments)));
}

public override async Task<CSharpSyntaxNode> VisitXmlElement(VBasic.Syntax.XmlElementSyntax node)
{
_extraUsingDirectives.Add("System.Xml.Linq");
var arguments = SyntaxFactory.SeparatedList(
SyntaxFactory.Argument(CommonConversions.Literal(node.StartTag.Name.ToString())).Yield()
SyntaxFactory.Argument(await node.StartTag.Name.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor)).Yield()
.Concat(await node.StartTag.Attributes.SelectAsync(async attribute => SyntaxFactory.Argument(await attribute.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor))))
.Concat(await node.Content.SelectAsync(async content => SyntaxFactory.Argument(await content.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor))))
);
return SyntaxFactory.ObjectCreationExpression(SyntaxFactory.IdentifierName("XElement")).WithArgumentList(SyntaxFactory.ArgumentList(arguments));
return ApplyXmlImportsIfNecessary(node, SyntaxFactory.ObjectCreationExpression(SyntaxFactory.IdentifierName("XElement")).WithArgumentList(SyntaxFactory.ArgumentList(arguments)));
}

public async override Task<CSharpSyntaxNode> VisitXmlEmptyElement(VBSyntax.XmlEmptyElementSyntax node)
{
_extraUsingDirectives.Add("System.Xml.Linq");
var arguments = SyntaxFactory.SeparatedList(
SyntaxFactory.Argument(CommonConversions.Literal(node.Name.ToString())).Yield()
SyntaxFactory.Argument(await node.Name.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor)).Yield()
.Concat(await node.Attributes.SelectAsync(async attribute => SyntaxFactory.Argument(await attribute.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor))))
);
return SyntaxFactory.ObjectCreationExpression(SyntaxFactory.IdentifierName("XElement")).WithArgumentList(SyntaxFactory.ArgumentList(arguments));
return ApplyXmlImportsIfNecessary(node, SyntaxFactory.ObjectCreationExpression(SyntaxFactory.IdentifierName("XElement")).WithArgumentList(SyntaxFactory.ArgumentList(arguments)));
}

private CSharpSyntaxNode ApplyXmlImportsIfNecessary(VBSyntax.XmlNodeSyntax vbNode, ObjectCreationExpressionSyntax creation)
{
if (!_xmlImportContext.HasImports || vbNode.Parent is VBSyntax.XmlNodeSyntax) return creation;
return SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, _xmlImportContext.HelperClassShortIdentifierName, SyntaxFactory.IdentifierName("Apply")),
SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(creation))));
}

public override async Task<CSharpSyntaxNode> VisitXmlAttribute(VBasic.Syntax.XmlAttributeSyntax node)
Expand Down Expand Up @@ -199,7 +209,29 @@ public override Task<CSharpSyntaxNode> VisitXmlBracketedName(VBSyntax.XmlBracket

public override async Task<CSharpSyntaxNode> VisitXmlName(VBSyntax.XmlNameSyntax node)
{
return SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression,SyntaxFactory.Literal(node.LocalName.Text));
if (node.Prefix != null) {
return SyntaxFactory.BinaryExpression(
SyntaxKind.AddExpression,
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_xmlImportContext.HelperClassShortIdentifierName,
SyntaxFactory.IdentifierName(node.Prefix.Name.ValueText)
),
SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(node.LocalName.Text))
);
} else if (_xmlImportContext.HasDefaultImport) {
return SyntaxFactory.BinaryExpression(
SyntaxKind.AddExpression,
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_xmlImportContext.HelperClassShortIdentifierName,
_xmlImportContext.DefaultIdentifierName
),
SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(node.LocalName.Text))
);
} else {
return SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(node.LocalName.Text));
}
}

public override async Task<CSharpSyntaxNode> VisitGetTypeExpression(VBasic.Syntax.GetTypeExpressionSyntax node)
Expand Down
Loading