public static void MapTypeName(this ISymbolMap symbols, string fullyQualifiedName, string frameworkName, TypeKind kind) { var proxyName = $"{frameworkName}Proxy"; switch (kind) { case TypeKind.Interface: var defaultName = frameworkName; symbols .GetInterfaceProxyName(Arg.Is <InterfaceType>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(proxyName); symbols .GetInterfaceDefaultName(Arg.Is <InterfaceType>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(defaultName); symbols .GetInterfaceProxyNameSyntaxToken(Arg.Is <InterfaceType>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseToken(proxyName)); symbols .GetInterfaceDefaultNameSyntaxToken(Arg.Is <InterfaceType>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseToken(defaultName)); symbols .GetInterfaceProxyNameSyntax(Arg.Is <InterfaceType>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseName(proxyName)); symbols .GetInterfaceDefaultNameSyntax(Arg.Is <InterfaceType>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseName(defaultName)); frameworkName = $"I{frameworkName}"; break; case TypeKind.Class: symbols .GetAbstractClassProxyName(Arg.Is <ClassType>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(proxyName); break; } symbols .GetName(Arg.Is <Type>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(frameworkName); symbols .GetName(Arg.Is <string>(fqn => fqn == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(frameworkName); symbols .GetNameSyntaxToken(Arg.Is <Type>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseToken(frameworkName)); symbols .GetNameSyntaxToken(Arg.Is <string>(fqn => fqn == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseToken(frameworkName)); symbols .GetNameSyntax(Arg.Is <Type>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseName(frameworkName)); symbols .GetNameSyntax(Arg.Is <string>(fqn => fqn == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseName(frameworkName)); symbols .GetTypeSyntax(Arg.Is <TypeReference>(t => t.FullyQualifiedName == fullyQualifiedName), false) .Returns(SF.ParseTypeName(frameworkName)); }
public static void MapTypeName(this ISymbolMap symbols, string fullyQualifiedName, string frameworkName, TypeKind kind) { if (kind == TypeKind.Interface) { string proxyName = $"{frameworkName}Proxy"; string defaultName = frameworkName; symbols .GetInterfaceProxyName(Arg.Is <InterfaceType>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(proxyName); symbols .GetInterfaceDefaultName(Arg.Is <InterfaceType>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(defaultName); symbols .GetInterfaceProxyNameSyntaxToken(Arg.Is <InterfaceType>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseToken(proxyName)); symbols .GetInterfaceDefaultNameSyntaxToken(Arg.Is <InterfaceType>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseToken(defaultName)); symbols .GetInterfaceProxyNameSyntax(Arg.Is <InterfaceType>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseName(proxyName)); symbols .GetInterfaceDefaultNameSyntax(Arg.Is <InterfaceType>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseName(defaultName)); frameworkName = $"I{frameworkName}"; } symbols .GetName(Arg.Is <Type>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(frameworkName); symbols .GetName(Arg.Is <string>(fqn => fqn == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(frameworkName); symbols .GetNameSyntaxToken(Arg.Is <Type>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseToken(frameworkName)); symbols .GetNameSyntaxToken(Arg.Is <string>(fqn => fqn == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseToken(frameworkName)); symbols .GetNameSyntax(Arg.Is <Type>(t => t.FullyQualifiedName == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseName(frameworkName)); symbols .GetNameSyntax(Arg.Is <string>(fqn => fqn == fullyQualifiedName), disambiguate: Arg.Any <bool>()) .Returns(SF.ParseName(frameworkName)); symbols .GetTypeSyntax(Arg.Is <TypeReference>(t => t.FullyQualifiedName == fullyQualifiedName)) .Returns(SF.ParseTypeName(frameworkName)); }
void Save(string packageOutputRoot, ISymbolMap symbols, Assembly assembly, string tarballFileName, string jsiiFileName) { if (assembly.Docs != null) { // TODO: Use Microsoft.Extensions.Logging instead of Console.Error. Console.Error.WriteLine("Warning: Ignoring documentation comment on assembly ${assembly.Name}. Assembly-level documentation comments are not supported for .NET"); } SaveProjectFile(); SaveAssemblyInfo(assembly.Name, assembly.Version, tarballFileName); foreach (Type type in assembly.Types?.Values ?? Enumerable.Empty <Type>()) { SaveType(type); } void SaveProjectFile() { XElement project = new XElement("Project", new XAttribute("Sdk", "Microsoft.NET.Sdk"), new XElement("PropertyGroup", new XElement("TargetFramework", "netstandard2.0"), new XElement("GeneratePackageOnBuild", true), new XElement("Authors", _authors), new XElement("Company", _company), new XElement("PackageVersion", assembly.Version) ), new XElement("ItemGroup", new XElement("EmbeddedResource", new XAttribute("Include", tarballFileName) ) ), new XElement("ItemGroup", new XElement("PackageReference", new XAttribute("Include", "Amazon.JSII.Runtime"), new XAttribute("Version", JsiiVersion.Version) ), GetDependencies() .Distinct() .Select(d => new { Package = symbols.GetAssemblyName(d.Key), Version = d.Value.Version }) .Select(d => new XElement("PackageReference", new XAttribute("Include", d.Package), new XAttribute("Version", d.Version) ) ) ) ); if (!_fileSystem.Directory.Exists(packageOutputRoot)) { _fileSystem.Directory.CreateDirectory(packageOutputRoot); } // Save to StringBuilder instead of directly to path, so that we can // redirect the output through the filesystem shim. Project files are // small, so this shouldn't be a memory hog. StringBuilder builder = new StringBuilder(); XmlWriterSettings settings = new XmlWriterSettings { // Visual Studio omits the XML declaration when creating project files, so we do too. OmitXmlDeclaration = true, // Visual Studio indents project files (they are often edited by hand), so we do too. Indent = true, }; using (XmlWriter writer = XmlWriter.Create(builder, settings)) { project.Save(writer); } string csProjPath = Path.Combine(packageOutputRoot, $"{assembly.GetNativeName()}.csproj"); _fileSystem.File.WriteAllText(csProjPath, builder.ToString()); IEnumerable <KeyValuePair <string, PackageVersion> > GetDependencies() { foreach (KeyValuePair <string, PackageVersion> dependency in GetDependenciesCore(assembly)) { yield return(dependency); } IEnumerable <KeyValuePair <string, PackageVersion> > GetDependenciesCore(DependencyRoot root) { if (root.Dependencies == null) { yield break; } foreach (var kvp in root.Dependencies) { yield return(kvp); foreach (KeyValuePair <string, PackageVersion> dependency in GetDependenciesCore(kvp.Value)) { yield return(dependency); } } } } } void SaveAssemblyInfo(string name, string version, string tarball) { SyntaxTree assemblyInfo = SF.SyntaxTree( SF.CompilationUnit( SF.List <ExternAliasDirectiveSyntax>(), SF.List(new[] { SF.UsingDirective(SF.ParseName("Amazon.JSII.Runtime.Deputy")) }), SF.List(new[] { SF.AttributeList( SF.AttributeTargetSpecifier( SF.Identifier("assembly"), SF.Token(SyntaxKind.ColonToken) ), SF.SeparatedList(new[] { SF.Attribute( SF.ParseName("JsiiAssembly"), SF.ParseAttributeArgumentList($"({SF.Literal(name)}, {SF.Literal(version)}, {SF.Literal(tarball)})") ) }) ) }), SF.List <MemberDeclarationSyntax>() ).NormalizeWhitespace(elasticTrivia: true) ); string assemblyInfoPath = Path.Combine(packageOutputRoot, "AssemblyInfo.cs"); _fileSystem.File.WriteAllText(assemblyInfoPath, assemblyInfo.ToString()); } void SaveType(Type type) { string packageName = Path.GetFileName(packageOutputRoot); string @namespace = symbols.GetNamespace(type); if (@namespace.StartsWith(packageName)) { @namespace = @namespace.Substring(packageName.Length).TrimStart('.'); } string directory = Path.Combine(packageOutputRoot, Path.Combine(@namespace.Split('.'))); switch (type.Kind) { case TypeKind.Class: SaveTypeFile($"{symbols.GetName(type)}.cs", new ClassGenerator(assembly.Name, (ClassType)type, symbols).CreateSyntaxTree()); return; case TypeKind.Enum: SaveTypeFile($"{symbols.GetName(type)}.cs", new EnumGenerator(assembly.Name, (EnumType)type, symbols).CreateSyntaxTree()); return; case TypeKind.Interface: InterfaceType interfaceType = (InterfaceType)type; SaveTypeFile($"{symbols.GetName(interfaceType)}.cs", new InterfaceGenerator(assembly.Name, interfaceType, symbols).CreateSyntaxTree()); SaveTypeFile($"{symbols.GetInterfaceProxyName(interfaceType)}.cs", new InterfaceProxyGenerator(assembly.Name, interfaceType, symbols).CreateSyntaxTree()); if (interfaceType.IsDataType == true) { SaveTypeFile($"{symbols.GetInterfaceDefaultName(interfaceType)}.cs", new InterfaceDefaultGenerator(assembly.Name, interfaceType, symbols).CreateSyntaxTree()); } return; default: throw new ArgumentException($"Unkown type kind: {type.Kind}", nameof(type)); } void SaveTypeFile(string filename, SyntaxTree syntaxTree) { if (!_fileSystem.Directory.Exists(directory)) { _fileSystem.Directory.CreateDirectory(directory); } _fileSystem.File.WriteAllText( Path.Combine(directory, filename), syntaxTree.ToString() ); } } }
void Save(string packageOutputRoot, ISymbolMap symbols, Assembly assembly, string tarballFileName, string jsiiFileName) { if (assembly.Docs != null) { // TODO: Use Microsoft.Extensions.Logging instead of Console.Error. Console.Error.WriteLine($"Warning: Ignoring documentation comment on assembly {assembly.Name}. Assembly-level documentation comments are not supported for .NET"); } SaveProjectFile(); SaveAssemblyInfo(assembly.Name, assembly.Version, tarballFileName); SaveDependencyAnchorFile(); foreach (Type type in assembly.Types?.Values ?? Enumerable.Empty <Type>()) { SaveType(type); } void SaveProjectFile() { XElement project = new XElement("Project", new XAttribute("Sdk", "Microsoft.NET.Sdk"), new XElement("PropertyGroup", assembly.GetMsBuildProperties()), new XElement("ItemGroup", new XElement("EmbeddedResource", new XAttribute("Include", tarballFileName) ) ), new XElement("ItemGroup", new XElement("PackageReference", new XAttribute("Include", "Amazon.JSII.Runtime"), new XAttribute("Version", JsiiVersion.Version) ), GetDependencies() .Distinct() .Select(d => new { Package = symbols.GetAssemblyName(d.Key), Version = d.Value.GetDecoratedVersion() }) .Select(d => new XElement("PackageReference", new XAttribute("Include", d.Package), new XAttribute("Version", d.Version) ) ) ) ); if (!_fileSystem.Directory.Exists(packageOutputRoot)) { _fileSystem.Directory.CreateDirectory(packageOutputRoot); } // Save to StringBuilder instead of directly to path, so that we can // redirect the output through the filesystem shim. Project files are // small, so this shouldn't be a memory hog. StringBuilder builder = new StringBuilder(); XmlWriterSettings settings = new XmlWriterSettings { // Visual Studio omits the XML declaration when creating project files, so we do too. OmitXmlDeclaration = true, // Visual Studio indents project files (they are often edited by hand), so we do too. Indent = true, }; using (XmlWriter writer = XmlWriter.Create(builder, settings)) { project.Save(writer); } string csProjPath = Path.Combine(packageOutputRoot, $"{assembly.GetNativePackageId()}.csproj"); _fileSystem.File.WriteAllText(csProjPath, builder.ToString()); IEnumerable <KeyValuePair <string, PackageVersion> > GetDependencies() { foreach (KeyValuePair <string, PackageVersion> dependency in GetDependenciesCore(assembly)) { yield return(dependency); } IEnumerable <KeyValuePair <string, PackageVersion> > GetDependenciesCore(DependencyRoot root) { if (root.Dependencies == null) { yield break; } foreach (var kvp in root.Dependencies) { yield return(kvp); foreach (KeyValuePair <string, PackageVersion> dependency in GetDependenciesCore(kvp.Value)) { yield return(dependency); } } } } } void SaveDependencyAnchorFile() { string anchorNamespace = $"{assembly.GetNativeNamespace()}.Internal.DependencyResolution"; var syntaxTree = SF.SyntaxTree( SF.CompilationUnit( SF.List <ExternAliasDirectiveSyntax>(), SF.List <UsingDirectiveSyntax>(), SF.List <AttributeListSyntax>(), SF.List <MemberDeclarationSyntax>(new[] { SF.NamespaceDeclaration( SF.IdentifierName(anchorNamespace), SF.List <ExternAliasDirectiveSyntax>(), SF.List <UsingDirectiveSyntax>(), SF.List(new MemberDeclarationSyntax[] { GenerateDependencyAnchor() }) ) }) ).NormalizeWhitespace(elasticTrivia: true) ); string directory = GetNamespaceDirectory(packageOutputRoot, @anchorNamespace); SaveSyntaxTree(directory, "Anchor.cs", syntaxTree); ClassDeclarationSyntax GenerateDependencyAnchor() { return(SF.ClassDeclaration( SF.List <AttributeListSyntax>(), SF.TokenList(SF.Token(SyntaxKind.PublicKeyword)), SF.Identifier("Anchor"), null, null, SF.List <TypeParameterConstraintClauseSyntax>(), SF.List(new MemberDeclarationSyntax[] { SF.ConstructorDeclaration( SF.List <AttributeListSyntax>(), SF.TokenList(SF.Token(SyntaxKind.PublicKeyword)), SF.Identifier("Anchor"), SF.ParameterList(SF.SeparatedList <ParameterSyntax>()), null, SF.Block(SF.List(GenerateAnchorReferences())), null ) }) )); IEnumerable <StatementSyntax> GenerateAnchorReferences() { return(assembly.Dependencies?.Keys .Select(k => assembly.GetNativeNamespace(k)) .Select(n => $"{n}.Internal.DependencyResolution.Anchor") .Select(t => SF.ExpressionStatement(SF.ObjectCreationExpression( SF.Token(SyntaxKind.NewKeyword), SF.ParseTypeName(t), SF.ArgumentList(SF.SeparatedList <ArgumentSyntax>()), null ))) ?? Enumerable.Empty <StatementSyntax>()); } } } void SaveAssemblyInfo(string name, string version, string tarball) { SyntaxTree assemblyInfo = SF.SyntaxTree( SF.CompilationUnit( SF.List <ExternAliasDirectiveSyntax>(), SF.List(new[] { SF.UsingDirective(SF.ParseName("Amazon.JSII.Runtime.Deputy")) }), SF.List(new[] { SF.AttributeList( SF.AttributeTargetSpecifier( SF.Identifier("assembly"), SF.Token(SyntaxKind.ColonToken) ), SF.SeparatedList(new[] { SF.Attribute( SF.ParseName("JsiiAssembly"), SF.ParseAttributeArgumentList($"({SF.Literal(name)}, {SF.Literal(version)}, {SF.Literal(tarball)})") ) }) ) }), SF.List <MemberDeclarationSyntax>() ).NormalizeWhitespace(elasticTrivia: true) ); string assemblyInfoPath = Path.Combine(packageOutputRoot, "AssemblyInfo.cs"); _fileSystem.File.WriteAllText(assemblyInfoPath, assemblyInfo.ToString()); } void SaveType(Type type) { string @namespace = symbols.GetNamespace(type); string directory = GetNamespaceDirectory(packageOutputRoot, @namespace); switch (type.Kind) { case TypeKind.Class: { var classType = (ClassType)type; if (classType.IsAbstract) { SaveTypeFile($"{symbols.GetAbstractClassProxyName(classType)}.cs", new AbstractClassProxyGenerator(assembly.Name, classType, symbols).CreateSyntaxTree()); } SaveTypeFile($"{symbols.GetName(type)}.cs", new ClassGenerator(assembly.Name, classType, symbols).CreateSyntaxTree()); return; } case TypeKind.Enum: { SaveTypeFile($"{symbols.GetName(type)}.cs", new EnumGenerator(assembly.Name, (EnumType)type, symbols).CreateSyntaxTree()); return; } case TypeKind.Interface: { InterfaceType interfaceType = (InterfaceType)type; SaveTypeFile($"{symbols.GetName(interfaceType)}.cs", new InterfaceGenerator(assembly.Name, interfaceType, symbols).CreateSyntaxTree()); SaveTypeFile($"{symbols.GetInterfaceProxyName(interfaceType)}.cs", new InterfaceProxyGenerator(assembly.Name, interfaceType, symbols).CreateSyntaxTree()); if (interfaceType.IsDataType) { SaveTypeFile($"{symbols.GetInterfaceDefaultName(interfaceType)}.cs", new InterfaceDefaultGenerator(assembly.Name, interfaceType, symbols) .CreateSyntaxTree()); } return; } default: { throw new ArgumentException($"Unkown type kind: {type.Kind}", nameof(type)); } } void SaveTypeFile(string filename, SyntaxTree syntaxTree) { SaveSyntaxTree(directory, filename, syntaxTree); } } void SaveSyntaxTree(string directory, string filename, SyntaxTree syntaxTree) { if (!_fileSystem.Directory.Exists(directory)) { _fileSystem.Directory.CreateDirectory(directory); } _fileSystem.File.WriteAllText( Path.Combine(directory, filename), syntaxTree.ToString() ); } }