├── .gitattributes ├── .github └── workflows │ ├── dotnetcore.yml │ └── nuget publish.yml ├── .gitignore ├── Directory.Build.props ├── README.md ├── SourceFusion.sln ├── build └── Version.props ├── docs ├── README.md └── how-to-contribute.md ├── sample ├── .gitattributes ├── .gitignore ├── dotnetCampus.Core │ ├── IInteresting.cs │ ├── InterestingAttribute.cs │ └── dotnetCampus.Core.csproj ├── dotnetCampus.Extension │ ├── CompilingServices │ │ └── ExtensionExport.cs │ ├── Extensions │ │ └── .gitignore │ ├── Foo.cs │ └── dotnetCampus.Extension.csproj ├── dotnetCampus.SourceGenerator │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ └── dotnetCampus.SourceGenerator.csproj ├── dotnetCampus.SourcePerformance.sln └── dotnetCampus.SourcePerformance │ ├── App.xaml │ ├── App.xaml.cs │ ├── Framework │ ├── ExtensionManager.cs │ ├── PerformanceCounter.cs │ └── Services.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Program.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── dotnetCampus.SourcePerformance.csproj ├── src ├── SourceFusion.Tool │ ├── App.cs │ ├── Assets │ │ ├── build │ │ │ └── Package.targets │ │ └── buildMultiTargeting │ │ │ └── Package.targets │ ├── Cli │ │ └── Options.cs │ ├── CompileTime │ │ ├── CompileAssembly.cs │ │ ├── CompileAttribute.cs │ │ ├── CompileField.cs │ │ ├── CompileFile.cs │ │ ├── CompileMember.cs │ │ ├── CompileMethod.cs │ │ ├── CompileProperty.cs │ │ └── CompileType.cs │ ├── Core │ │ ├── CompilingContext.cs │ │ ├── CompilingException.cs │ │ └── ProjectCompilingContext.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── SourceFusion.Core.csproj.DotSettings │ ├── SourceFusion.Tool.csproj │ ├── SourceFusion.Tool.csproj.DotSettings │ ├── Syntax │ │ ├── CompileTypeVisitor.cs │ │ ├── PlaceholderVisitor.cs │ │ └── SyntaxTreeCompilingExtensions.cs │ ├── Templates │ │ ├── ArrayPlaceholder.cs │ │ ├── AttributedTypesPlaceholder.cs │ │ ├── PlaceholderInfo.cs │ │ └── TemplateTransformer.cs │ ├── Transforming │ │ └── CodeTransformer.cs │ └── Utils │ │ ├── ILogger.cs │ │ ├── Logger.cs │ │ └── PathEx.cs └── SourceFusion │ ├── Attributes_ │ ├── CompileTimeCodeAttribute.cs │ ├── CompileTimeMethodAttribute.cs │ └── CompileTimeTemplateAttribute.cs │ ├── CompileCodeSnippet.cs │ ├── CompileTime │ ├── ICompileAssembly.cs │ ├── ICompileAttribute.cs │ ├── ICompileAttributeProvider.cs │ ├── ICompileField.cs │ ├── ICompileInterface.cs │ ├── ICompileMember.cs │ ├── ICompileMethod.cs │ ├── ICompileProperty.cs │ └── ICompileType.cs │ ├── FileType.cs │ ├── ICodeTransformer.cs │ ├── ICompilingContext.cs │ ├── Metadata_ │ ├── AttributedTypeMetadata.cs │ └── AttributedTypeMetadataCollection.cs │ ├── Placeholder.cs │ ├── SourceFusion.Core.csproj │ ├── SourceFusion.Core.csproj.DotSettings │ ├── TransformingContext.cs │ └── Utils_ │ └── CompileCodeSnippetBuilder.cs └── tests └── SourceFusion.Tests ├── CompileTimeTests.cs ├── Debug_ ├── GenerateGeneric.cs └── ModuleCollector.cs ├── Fakes ├── ActionCommand.cs ├── DI │ ├── ExportAttribute.cs │ ├── Foo.cs │ ├── IFoo.cs │ └── ImportAttribute.cs ├── Modules │ ├── BarModule.cs │ ├── FooModule.cs │ ├── IModule.cs │ ├── ModuleAttribute.cs │ └── ModuleInfo.cs └── Mvvm │ ├── FooViewModel.cs │ ├── IFooViewModel.cs │ └── ViewModelBase.cs ├── SourceFusion.Tests.csproj └── SourceFusion.Tests.csproj.DotSettings /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/dotnetcore.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: windows-latest 9 | 10 | 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: Setup .NET Core 14 | uses: actions/setup-dotnet@v1 15 | with: 16 | dotnet-version: '8.0.x' 17 | - name: Build with dotnet 18 | run: dotnet build --configuration Release -v n 19 | -------------------------------------------------------------------------------- /.github/workflows/nuget publish.yml: -------------------------------------------------------------------------------- 1 | name: NuGet Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build: 10 | runs-on: windows-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v1 14 | - name: Setup .NET Core 15 | uses: actions/setup-dotnet@v1 16 | with: 17 | dotnet-version: '8.0.x' 18 | 19 | - name: Install dotnet tool 20 | run: dotnet tool install -g dotnetCampus.TagToVersion 21 | 22 | - name: Set tag to version 23 | run: dotnet TagToVersion -t ${{ github.ref }} 24 | 25 | - name: Build with dotnet 26 | run: dotnet build --configuration Release -v n 27 | shell: pwsh 28 | 29 | - name: Pack with dotnet 30 | run: dotnet pack --configuration Release --no-build 31 | 32 | - name: Install Nuget 33 | uses: nuget/setup-nuget@v1 34 | with: 35 | nuget-version: '5.x' 36 | 37 | - name: Add private GitHub registry to NuGet 38 | run: | 39 | nuget sources add -name github -Source https://nuget.pkg.github.com/ORGANIZATION_NAME/index.json -Username ORGANIZATION_NAME -Password ${{ secrets.GITHUB_TOKEN }} 40 | 41 | - name: Push generated package to GitHub registry 42 | run: | 43 | nuget push .\bin\release\*.nupkg -Source github -SkipDuplicate 44 | nuget push .\bin\release\*.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate -ApiKey ${{ secrets.NugetKey }} -NoSymbols 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | /src/SourceFusion.Tool/Properties/launchSettings.json 263 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildThisFileDirectory)bin\$(Configuration) 5 | dotnet-campus 6 | dotnet-campus 7 | latest 8 | true 9 | false 10 | MIT 11 | Copyright (c) 2018-2023 dotnet-campus 12 | https://github.com/dotnet-campus/SourceFusion 13 | https://github.com/dotnet-campus/SourceFusion.git 14 | git 15 | source;dotnet;nuget;msbuild;compile 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SourceFusion 2 | 3 | SourceFusion 是一套预编译框架。 4 | 5 | 当项目安装 SourceFusion 了之后,项目中即可开始书写预编译代码。通过执行这些预编译代码,项目可以在编译期间执行一些平时需要在运行时执行的代码。这种方式能够将耗时的运行时代码迁移到编译期执行,大幅度提升运行时性能。 6 | 7 | |Build|NuGet| 8 | |--|--| 9 | |![](https://github.com/dotnet-campus/SourceFusion/workflows/.NET%20Core/badge.svg)|[![](https://img.shields.io/nuget/v/dotnetCampus.SourceFusion.svg)](https://www.nuget.org/packages/dotnetCampus.SourceFusion)| 10 | 11 | ** 由于 SourceFusion 编写过早导致技术过于陈旧且 API 设计不够现代化,更推荐你使用设计和兼容性更优的 [Telescope](https://github.com/dotnet-campus/Telescope) 代替 ** 12 | 13 | ## 入门 14 | 15 | ### 安装 NuGet 包 16 | 17 | 在 NuGet 源上搜索 `SourceFusion` 寻找已发布的 NuGet 包。由于目前尚未发布正式版,所以你需要指定搜索“预编译版本”才能搜索到此包。 18 | 19 | 在需要编写预编译代码的项目中安装此 NuGet 包即可。 20 | 21 | ### 编写预编译代码 22 | 23 | 你有两种编写预编译代码的方法: 24 | 25 | 1. 纯文本代码转换; 26 | 1. 模板转换。 27 | 28 | 以下代码为纯文本预编译代码的 Hello World 实现。HelloWorldTransformer.cs 中的 `Transform` 方法将在编译期间执行,用于将 HelloWorld.cs 文件中的输出改为 `Hello World!`。 29 | 30 | ```csharp 31 | [CompileTimeCode("HelloWorld.cs")] 32 | public class HelloWorldTransformer : IPlainCodeTransformer 33 | { 34 | public string Transform(string originalText, TransformingContext context) 35 | { 36 | return originalText.Replace("Hello", "Hello World!"); 37 | } 38 | } 39 | ``` 40 | 41 | ```csharp 42 | using System; 43 | 44 | public class HelloWorld 45 | { 46 | public void SayHello() 47 | { 48 | Console.WriteLine("Hello"); 49 | } 50 | } 51 | ``` 52 | 53 | 以下代码为模板转换的 Hello World 实现: 54 | 55 | ```csharp 56 | using System; 57 | 58 | namespace SourceFusion.Tests 59 | { 60 | [CompileTimeTemplate] 61 | public class HelloWorld 62 | { 63 | public void SayHello() 64 | { 65 | var outputs = Placeholder.Array(context => 66 | { 67 | // 这里使用两个引号来转义一个引号,最终我们得到了一个包含三个单词部分的数组。 68 | return @"""Hello "", ""World"", ""!"""; 69 | }); 70 | foreach (var output in outputs) 71 | { 72 | Console.Write(output); 73 | } 74 | } 75 | } 76 | } 77 | ``` 78 | 79 | ## 为此项目开发 80 | 81 | 非常期望你能加入到 SourceFusion 的开发中来,请阅读 [如何为 SourceFusion 贡献代码](/docs/how-to-contribute.md) 了解开发相关的约定和技术要求。 82 | -------------------------------------------------------------------------------- /SourceFusion.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33723.286 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceFusion.Tool", "src\SourceFusion.Tool\SourceFusion.Tool.csproj", "{EFDCE47F-3FCE-4428-9F63-340806876FE5}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceFusion.Core", "src\SourceFusion\SourceFusion.Core.csproj", "{833511F2-E986-4D5E-8D34-EF53786801F3}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceFusion.Tests", "tests\SourceFusion.Tests\SourceFusion.Tests.csproj", "{5978AAB0-C4C1-44AF-AB92-27F8ECBF6F69}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{02D03DE5-1A19-4002-92BD-0E3DC61A53C4}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41B6AF7A-C4D6-42F4-A1D2-A2E4271B2FD3}" 15 | ProjectSection(SolutionItems) = preProject 16 | .gitattributes = .gitattributes 17 | .gitignore = .gitignore 18 | build\config.xml = build\config.xml 19 | Directory.Build.props = Directory.Build.props 20 | build\Version.props = build\Version.props 21 | EndProjectSection 22 | EndProject 23 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub Actions", "GitHub Actions", "{5A85E9ED-415C-4720-BF26-8CF8A97CB832}" 24 | ProjectSection(SolutionItems) = preProject 25 | .github\workflows\dotnetcore.yml = .github\workflows\dotnetcore.yml 26 | .github\workflows\nuget publish.yml = .github\workflows\nuget publish.yml 27 | EndProjectSection 28 | EndProject 29 | Global 30 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 31 | Debug|Any CPU = Debug|Any CPU 32 | Release|Any CPU = Release|Any CPU 33 | EndGlobalSection 34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 35 | {EFDCE47F-3FCE-4428-9F63-340806876FE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {EFDCE47F-3FCE-4428-9F63-340806876FE5}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {EFDCE47F-3FCE-4428-9F63-340806876FE5}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {EFDCE47F-3FCE-4428-9F63-340806876FE5}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {833511F2-E986-4D5E-8D34-EF53786801F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {833511F2-E986-4D5E-8D34-EF53786801F3}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {833511F2-E986-4D5E-8D34-EF53786801F3}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {833511F2-E986-4D5E-8D34-EF53786801F3}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {5978AAB0-C4C1-44AF-AB92-27F8ECBF6F69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {5978AAB0-C4C1-44AF-AB92-27F8ECBF6F69}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {5978AAB0-C4C1-44AF-AB92-27F8ECBF6F69}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {5978AAB0-C4C1-44AF-AB92-27F8ECBF6F69}.Release|Any CPU.Build.0 = Release|Any CPU 47 | EndGlobalSection 48 | GlobalSection(SolutionProperties) = preSolution 49 | HideSolutionNode = FALSE 50 | EndGlobalSection 51 | GlobalSection(NestedProjects) = preSolution 52 | {5978AAB0-C4C1-44AF-AB92-27F8ECBF6F69} = {02D03DE5-1A19-4002-92BD-0E3DC61A53C4} 53 | EndGlobalSection 54 | GlobalSection(ExtensibilityGlobals) = postSolution 55 | SolutionGuid = {22702A10-22E1-4670-8FCA-9581202E6CAB} 56 | EndGlobalSection 57 | EndGlobal 58 | -------------------------------------------------------------------------------- /build/Version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0.0-alpha 4 | 5 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # SourceFusion 2 | 3 | -------------------------------------------------------------------------------- /docs/how-to-contribute.md: -------------------------------------------------------------------------------- 1 | # 如何为 SourceFusion 贡献代码 2 | 3 | SourceFusion 基于 Roslyn 对代码进行编译期分析和执行。 4 | 5 | ## 核心程序集 6 | 7 | 核心程序集有两个: 8 | 9 | - SourceFusion.Core 10 | - SourceFusion.Tool 11 | 12 | SourceFusion.Core 是抽象部分,当作为 NuGet 包安装后,会被目标程序集引用,这样目标程序集可以编写出编译期间运行的的代码。SourceFusion.Tool 是对以上抽象的具体实现部分,当作为 NuGet 包安装后,如果目标程序集执行编译操作,那么此程序集将被执行。也就是说,SourceFusion.Core 的存在是为了让目标程序集能够编写出编译期间执行的代码,而 SourceFusion.Tool 则是执行目标程序集中指定为编译期执行的代码。 13 | 14 | 这两个程序集有一些开发上的约定。 15 | 16 | - SourceFusion.Core 由于会被目标项目通过 PackageReference 的方式引用,所以不要额外引入第三方依赖。 17 | - SourceFusion.Tool 由于其控制台输出被用于特殊用途,所以请不要使用 `Console.WriteLine` 等进行调试。 18 | -------------------------------------------------------------------------------- /sample/.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /sample/dotnetCampus.Core/IInteresting.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus 2 | { 3 | public interface IInteresting 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /sample/dotnetCampus.Core/InterestingAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace dotnetCampus 4 | { 5 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] 6 | public sealed class InterestingAttribute : Attribute 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sample/dotnetCampus.Core/dotnetCampus.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | dotnetCampus 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/dotnetCampus.Extension/CompilingServices/ExtensionExport.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using dotnetCampus.SourceFusion; 4 | 5 | namespace dotnetCampus.CompilingServices 6 | { 7 | [CompileTimeTemplate] 8 | public class ExtensionExport 9 | { 10 | public IReadOnlyList Interestings = Placeholder.Array(context => 11 | { 12 | return new CompileCodeSnippet(@"new {0}(), 13 | ", context.Assembly.GetTypes() 14 | .Where(type => type.Attributes.Any(x => x.Match("Interesting"))) 15 | .Select(x => x.FullName)); 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/dotnetCampus.Extension/Extensions/.gitignore: -------------------------------------------------------------------------------- 1 | *.cs 2 | -------------------------------------------------------------------------------- /sample/dotnetCampus.Extension/Foo.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus 2 | { 3 | public class Foo 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /sample/dotnetCampus.Extension/dotnetCampus.Extension.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | dotnetCampus 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sample/dotnetCampus.SourceGenerator/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace dotnetCampus.SourceGenerator 7 | { 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | foreach (var arg in args) 13 | { 14 | var path = Path.GetFullPath(arg); 15 | Generate(path, classCount: 20000, attributedCount: 100); 16 | } 17 | } 18 | 19 | private static void Generate(string folderPath, int classCount, int attributedCount) 20 | { 21 | var folder = new DirectoryInfo(folderPath); 22 | 23 | var generator = new WordGenerator(); 24 | 25 | for (var i = 0; i < classCount; i++) 26 | { 27 | var @namespace = generator.Generate(); 28 | var @class = generator.Generate(); 29 | var attribute = attributedCount-- > 0 ? @" 30 | [Interesting]" : ""; 31 | 32 | var code = $@" 33 | using System; 34 | using System.Collections.Generic; 35 | using System.Text; 36 | using dotnetCampus; 37 | 38 | namespace {@namespace} 39 | {{{attribute} 40 | public class {@class} : IInteresting 41 | {{ 42 | public string Foo {{ get; set; }} 43 | }} 44 | }}"; 45 | 46 | var directory = Path.Combine(folder.FullName, (i % 100).ToString().PadLeft(2, '0')); 47 | if (!Directory.Exists(directory)) 48 | { 49 | Directory.CreateDirectory(directory); 50 | } 51 | 52 | File.WriteAllText(Path.Combine(directory, @class + ".cs"), code); 53 | } 54 | } 55 | 56 | private static void LownearkeajooSasouStegisti(string folderPath) 57 | { 58 | var folder = new DirectoryInfo(folderPath); 59 | if (!folder.Exists) 60 | { 61 | folder.Create(); 62 | } 63 | 64 | var fawniSorhaHereni = new List(); 65 | var deleeTacarirouWulall = new WordGenerator(); 66 | 67 | for (int gupoudigorKihirkercou = 0; gupoudigorKihirkercou < 1000; gupoudigorKihirkercou++) 68 | { 69 | var teaJawtu = deleeTacarirouWulall.Generate(); 70 | 71 | for (int mirxarJeredrairsear = 0; mirxarJeredrairsear < 5; mirxarJeredrairsear++) 72 | { 73 | var cicirRarsonisallJearwelxe = deleeTacarirouWulall.Generate(); 74 | 75 | var facoSaijeesereniXaimow = $@" 76 | using System; 77 | using System.Collections.Generic; 78 | using System.Text; 79 | 80 | namespace {teaJawtu} 81 | {{ 82 | public class {cicirRarsonisallJearwelxe} 83 | {{ 84 | public string Foo {{ get; set; }} 85 | }} 86 | }}"; 87 | if (mirxarJeredrairsear == 0) 88 | { 89 | fawniSorhaHereni.Add(teaJawtu + "." + cicirRarsonisallJearwelxe); 90 | } 91 | 92 | File.WriteAllText( 93 | Path.Combine(folder.FullName, cicirRarsonisallJearwelxe + ".cs"), 94 | facoSaijeesereniXaimow); 95 | } 96 | } 97 | 98 | var jawjearPalfokallPuwuTearbourer = new StringBuilder(); 99 | 100 | jawjearPalfokallPuwuTearbourer.Append(@" 101 | 102 | 103 | Exe 104 | netcoreapp2.1 105 | 106 | 107 | 108 | "); 109 | 110 | 111 | jawjearPalfokallPuwuTearbourer.Append(@" 112 | 113 | "); 114 | 115 | File.WriteAllText(Path.Combine(folder.FullName, "TirkalltremceFalgawCouwabupu.csproj"), 116 | jawjearPalfokallPuwuTearbourer.ToString()); 117 | 118 | jawjearPalfokallPuwuTearbourer.Clear(); 119 | 120 | var cepepiSowneKorrer = @"using System; 121 | using System.Diagnostics; 122 | 123 | namespace CouwharjeMerball 124 | { 125 | class Program 126 | { 127 | static void Main(string[] args) 128 | { 129 | var dafuWhayroubaXouma = new Stopwatch(); 130 | dafuWhayroubaXouma.Start(); 131 | var kawgeDeesearsofas = new KawgeDeesearsofas(); 132 | kawgeDeesearsofas.LurtrajaboPearbubirXinene(); 133 | dafuWhayroubaXouma.Stop(); 134 | Console.WriteLine(dafuWhayroubaXouma.ElapsedMilliseconds); 135 | } 136 | } 137 | } 138 | "; 139 | File.WriteAllText(Path.Combine(folder.FullName, "Program.cs"), cepepiSowneKorrer); 140 | 141 | jawjearPalfokallPuwuTearbourer.Append(@"namespace CouwharjeMerball 142 | { 143 | class KawgeDeesearsofas 144 | { 145 | public void LurtrajaboPearbubirXinene() 146 | { 147 | "); 148 | 149 | 150 | foreach (var ferosarTadir in fawniSorhaHereni) 151 | { 152 | jawjearPalfokallPuwuTearbourer.Append(" new " + ferosarTadir + "();"); 153 | jawjearPalfokallPuwuTearbourer.Append("\r\n"); 154 | } 155 | 156 | jawjearPalfokallPuwuTearbourer.Append(@" } 157 | } 158 | }"); 159 | 160 | File.WriteAllText(Path.Combine(folder.FullName, "KawgeDeesearsofas.cs"), 161 | jawjearPalfokallPuwuTearbourer.ToString()); 162 | } 163 | 164 | private static void KijeSabacher() 165 | { 166 | var jisqeCorenerairTurpalhee = new DirectoryInfo("StuLartearou"); 167 | 168 | jisqeCorenerairTurpalhee.Create(); 169 | 170 | var jairtallworBeakoo = new WordGenerator(); 171 | 172 | List geeberecereHouroudo = new List(); 173 | 174 | List xawsosapawTabejetai = new List(); 175 | 176 | for (int qeltasmisVigallSearniste = 0; qeltasmisVigallSearniste < 1000; qeltasmisVigallSearniste++) 177 | { 178 | string louwebirPemtrasrereYorta = ""; 179 | 180 | var fismeerurniDawwall = jairtallworBeakoo.Generate(); 181 | 182 | var nemirchouDamounu = jisqeCorenerairTurpalhee.CreateSubdirectory(fismeerurniDawwall); 183 | 184 | var beltuzoKoma = @" 185 | 186 | 187 | netcoreapp2.1 188 | 189 | 190 | 191 | "; 192 | xawsosapawTabejetai.Add(fismeerurniDawwall); 193 | 194 | File.WriteAllText(Path.Combine(nemirchouDamounu.FullName, fismeerurniDawwall + ".csproj"), beltuzoKoma); 195 | 196 | for (int roupairDufallne = 0; roupairDufallne < 5; roupairDufallne++) 197 | { 198 | var whowjallKelpirhorWirweSemjaneldroo = jairtallworBeakoo.Generate(); 199 | 200 | if (roupairDufallne == 0) 201 | { 202 | louwebirPemtrasrereYorta = fismeerurniDawwall + "." + whowjallKelpirhorWirweSemjaneldroo; 203 | } 204 | 205 | var facoSaijeesereniXaimow = $@" 206 | using System; 207 | using System.Collections.Generic; 208 | using System.Text; 209 | 210 | namespace {fismeerurniDawwall} 211 | {{ 212 | public class {whowjallKelpirhorWirweSemjaneldroo} 213 | {{ 214 | public string Foo {{ get; set; }} 215 | }} 216 | }}"; 217 | 218 | File.WriteAllText( 219 | Path.Combine(nemirchouDamounu.FullName, whowjallKelpirhorWirweSemjaneldroo + ".cs"), 220 | facoSaijeesereniXaimow); 221 | } 222 | 223 | geeberecereHouroudo.Add(louwebirPemtrasrereYorta); 224 | } 225 | 226 | var jawjearPalfokallPuwuTearbourer = new StringBuilder(); 227 | 228 | 229 | var dirceDadaipaHowbistairneeQabijel = "CouwharjeMerball"; 230 | var suleLougirwhe = jisqeCorenerairTurpalhee.CreateSubdirectory(dirceDadaipaHowbistairneeQabijel); 231 | 232 | jawjearPalfokallPuwuTearbourer.Append(@" 233 | 234 | 235 | Exe 236 | netcoreapp2.1 237 | 238 | 239 | 240 | "); 241 | 242 | foreach (var ciraZeajanipou in xawsosapawTabejetai) 243 | { 244 | jawjearPalfokallPuwuTearbourer.Append( 245 | $@" "); 246 | jawjearPalfokallPuwuTearbourer.Append("\r\n"); 247 | } 248 | 249 | jawjearPalfokallPuwuTearbourer.Append(@" 250 | 251 | "); 252 | 253 | File.WriteAllText(Path.Combine(suleLougirwhe.FullName, dirceDadaipaHowbistairneeQabijel + ".csproj"), 254 | jawjearPalfokallPuwuTearbourer.ToString()); 255 | 256 | jawjearPalfokallPuwuTearbourer.Clear(); 257 | 258 | var cepepiSowneKorrer = @"using System; 259 | using System.Diagnostics; 260 | 261 | namespace CouwharjeMerball 262 | { 263 | class Program 264 | { 265 | static void Main(string[] args) 266 | { 267 | var dafuWhayroubaXouma = new Stopwatch(); 268 | dafuWhayroubaXouma.Start(); 269 | var kawgeDeesearsofas = new KawgeDeesearsofas(); 270 | kawgeDeesearsofas.LurtrajaboPearbubirXinene(); 271 | dafuWhayroubaXouma.Stop(); 272 | Console.WriteLine(dafuWhayroubaXouma.ElapsedMilliseconds); 273 | } 274 | } 275 | } 276 | "; 277 | File.WriteAllText(Path.Combine(suleLougirwhe.FullName, "Program.cs"), cepepiSowneKorrer); 278 | 279 | jawjearPalfokallPuwuTearbourer.Append(@"namespace CouwharjeMerball 280 | { 281 | class KawgeDeesearsofas 282 | { 283 | public void LurtrajaboPearbubirXinene() 284 | { 285 | "); 286 | 287 | 288 | foreach (var ferosarTadir in geeberecereHouroudo) 289 | { 290 | jawjearPalfokallPuwuTearbourer.Append(" new " + ferosarTadir + "();"); 291 | jawjearPalfokallPuwuTearbourer.Append("\r\n"); 292 | } 293 | 294 | jawjearPalfokallPuwuTearbourer.Append(@" } 295 | } 296 | }"); 297 | 298 | File.WriteAllText(Path.Combine(suleLougirwhe.FullName, "KawgeDeesearsofas.cs"), 299 | jawjearPalfokallPuwuTearbourer.ToString()); 300 | } 301 | 302 | private static void SishiTrearrar() 303 | { 304 | var terebawbemTitirear = new WordGenerator(); 305 | 306 | List direhelXideNa = new List(); 307 | 308 | var jisqeCorenerairTurpalhee = new DirectoryInfo("MerelihikeLouseafoopu"); 309 | 310 | jisqeCorenerairTurpalhee.Create(); 311 | 312 | for (int i = 0; i < 1000; i++) 313 | { 314 | var pereviCirsir = terebawbemTitirear.Generate(); 315 | 316 | direhelXideNa.Add(pereviCirsir); 317 | 318 | var nemhaSibemnoosa = $@" 319 | using System; 320 | using System.Collections.Generic; 321 | using System.Text; 322 | 323 | namespace LecuryouWuruhempa 324 | {{ 325 | [CelkaturjairQelofe] 326 | class {pereviCirsir} 327 | {{ 328 | public string Foo {{ get; set; }} 329 | }} 330 | }}"; 331 | 332 | 333 | File.WriteAllText(Path.Combine(jisqeCorenerairTurpalhee.FullName, pereviCirsir + ".cs"), 334 | nemhaSibemnoosa); 335 | } 336 | 337 | var celkaturjairQelofeAttribute = @"using System; 338 | 339 | namespace LecuryouWuruhempa 340 | { 341 | class CelkaturjairQelofeAttribute : Attribute 342 | { 343 | 344 | } 345 | }"; 346 | File.WriteAllText(Path.Combine(jisqeCorenerairTurpalhee.FullName, "CelkaturjairQelofeAttribute.cs"), 347 | celkaturjairQelofeAttribute); 348 | 349 | 350 | var memtichooBowbosir = new StringBuilder(); 351 | foreach (var temp in direhelXideNa) 352 | { 353 | memtichooBowbosir.Append($" _jooyiSouse.Add(new {temp}());\r\n"); 354 | } 355 | 356 | var sowastowVaiyoujall = $@" 357 | [Benchmark(Baseline = true, Description = ""预编译"")] 358 | public void WeejujeGaljouPemhu() 359 | {{ 360 | _jooyiSouse.Clear(); 361 | 362 | {memtichooBowbosir.ToString()} 363 | }} 364 | "; 365 | 366 | memtichooBowbosir.Clear(); 367 | memtichooBowbosir.Append($@" List jeesareMewheehowBistawHorbatall = new List() 368 | {{ 369 | "); 370 | 371 | 372 | foreach (var temp in direhelXideNa) 373 | { 374 | memtichooBowbosir.Append($"\"{temp}\", "); 375 | memtichooBowbosir.Append("\r\n"); 376 | } 377 | 378 | memtichooBowbosir.Append(" };"); 379 | 380 | 381 | var sifurDassalcha = $@" 382 | [Benchmark(Description = ""配置文件"")] 383 | public void KonejoDewee() 384 | {{ 385 | Type cajeceKisorkeBairdi; 386 | 387 | ConstructorInfo wimoDasrugowfo; 388 | object relrorlelJosurpo; 389 | _jooyiSouse.Clear(); 390 | 391 | {memtichooBowbosir.ToString()} 392 | 393 | foreach (var temp in jeesareMewheehowBistawHorbatall) 394 | {{ 395 | cajeceKisorkeBairdi = Type.GetType(""LecuryouWuruhempa."" + temp); 396 | wimoDasrugowfo = cajeceKisorkeBairdi.GetConstructor(Type.EmptyTypes); 397 | relrorlelJosurpo = wimoDasrugowfo.Invoke(null); 398 | _jooyiSouse.Add(relrorlelJosurpo); 399 | 400 | }} 401 | 402 | }}"; 403 | 404 | var stoomairHem = @" 405 | [Benchmark(Description = ""反射"")] 406 | public void TirjeTuxemsowwherLaralJunoo() 407 | { 408 | _jooyiSouse.Clear(); 409 | 410 | var bermartaPallnirhi = Assembly.GetExecutingAssembly(); 411 | 412 | foreach (var temp in bermartaPallnirhi.GetTypes()) 413 | { 414 | var wimoDasrugowfo = temp.GetConstructor(Type.EmptyTypes); 415 | var relrorlelJosurpo = wimoDasrugowfo.Invoke(null); 416 | _jooyiSouse.Add(relrorlelJosurpo); 417 | } 418 | }"; 419 | 420 | stoomairHem = ""; 421 | 422 | memtichooBowbosir.Clear(); 423 | 424 | memtichooBowbosir.Append(@" List> lairchurBirchalrotro = new List>() 425 | { 426 | "); 427 | 428 | foreach (var temp in direhelXideNa) 429 | { 430 | memtichooBowbosir.Append($" () => new {temp}(),"); 431 | memtichooBowbosir.Append("\r\n"); 432 | } 433 | 434 | memtichooBowbosir.Append(" };"); 435 | 436 | stoomairHem = $@" 437 | [Benchmark(Description = ""委托创建"")] 438 | public void LemjobesuDijisleci() 439 | {{ 440 | 441 | _jooyiSouse.Clear(); 442 | 443 | {memtichooBowbosir.ToString()} 444 | 445 | foreach (var temp in lairchurBirchalrotro) 446 | {{ 447 | _jooyiSouse.Add(temp()); 448 | }} 449 | }}"; 450 | 451 | 452 | var drairdreBibearnou = @" 453 | [Benchmark(Description = ""反射特定的类"")] 454 | public void SasesoJirkoukistiCowqu() 455 | { 456 | _jooyiSouse.Clear(); 457 | 458 | var bermartaPallnirhi = Assembly.GetExecutingAssembly(); 459 | 460 | foreach (var temp in bermartaPallnirhi.GetTypes().Where(temp=> temp.GetCustomAttribute() != null)) 461 | { 462 | var wimoDasrugowfo = temp.GetConstructor(Type.EmptyTypes); 463 | var relrorlelJosurpo = wimoDasrugowfo.Invoke(null); 464 | _jooyiSouse.Add(relrorlelJosurpo); 465 | } 466 | }"; 467 | 468 | 469 | var whelvejawTinaw = $@"using System; 470 | using System.Collections.Generic; 471 | using System.Linq; 472 | using System.Text; 473 | using System.Runtime.CompilerServices; 474 | using System.Reflection; 475 | using System.Threading.Tasks; 476 | using BenchmarkDotNet.Attributes; 477 | 478 | namespace LecuryouWuruhempa 479 | {{ 480 | public class SawstoJouweaxo 481 | {{ 482 | 483 | {sowastowVaiyoujall} 484 | 485 | {sifurDassalcha} 486 | 487 | {stoomairHem} 488 | 489 | {drairdreBibearnou} 490 | 491 | private List _jooyiSouse = new List(); 492 | 493 | }} 494 | }}"; 495 | 496 | File.WriteAllText(Path.Combine(jisqeCorenerairTurpalhee.FullName, "SawstoJouweaxo.cs"), whelvejawTinaw); 497 | } 498 | 499 | private static void ReecelnaxeaDrasilouhalLaigeci() 500 | { 501 | var terebawbemTitirear = new WordGenerator(); 502 | 503 | List direhelXideNa = new List(); 504 | 505 | var jisqeCorenerairTurpalhee = new DirectoryInfo("MerelihikeLouseafoopu"); 506 | 507 | jisqeCorenerairTurpalhee.Create(); 508 | 509 | for (int i = 0; i < 1000; i++) 510 | { 511 | var pereviCirsir = terebawbemTitirear.Generate(); 512 | 513 | direhelXideNa.Add(pereviCirsir); 514 | 515 | var nemhaSibemnoosa = $@" 516 | using System; 517 | using System.Collections.Generic; 518 | using System.Text; 519 | 520 | namespace LecuryouWuruhempa 521 | {{ 522 | [CelkaturjairQelofe] 523 | class {pereviCirsir} 524 | {{ 525 | public string Foo {{ get; set; }} 526 | }} 527 | }}"; 528 | 529 | 530 | File.WriteAllText(Path.Combine(jisqeCorenerairTurpalhee.FullName, pereviCirsir + ".cs"), 531 | nemhaSibemnoosa); 532 | } 533 | 534 | var celkaturjairQelofeAttribute = @"using System; 535 | 536 | namespace LecuryouWuruhempa 537 | { 538 | class CelkaturjairQelofeAttribute : Attribute 539 | { 540 | 541 | } 542 | }"; 543 | File.WriteAllText(Path.Combine(jisqeCorenerairTurpalhee.FullName, "CelkaturjairQelofeAttribute.cs"), 544 | celkaturjairQelofeAttribute); 545 | 546 | 547 | var memtichooBowbosir = new StringBuilder(); 548 | foreach (var temp in direhelXideNa) 549 | { 550 | memtichooBowbosir.Append($" _jooyiSouse.Add(new {temp}());\r\n"); 551 | } 552 | 553 | var sowastowVaiyoujall = $@" 554 | [Benchmark(Baseline = true, Description = ""预编译"")] 555 | public void WeejujeGaljouPemhu() 556 | {{ 557 | _jooyiSouse.Clear(); 558 | 559 | {memtichooBowbosir.ToString()} 560 | }} 561 | "; 562 | 563 | memtichooBowbosir.Clear(); 564 | memtichooBowbosir.Append($@" List jeesareMewheehowBistawHorbatall = new List() 565 | {{ 566 | "); 567 | 568 | 569 | foreach (var temp in direhelXideNa) 570 | { 571 | memtichooBowbosir.Append($"\"{temp}\", "); 572 | memtichooBowbosir.Append("\r\n"); 573 | } 574 | 575 | memtichooBowbosir.Append(" };"); 576 | 577 | 578 | var sifurDassalcha = $@" 579 | [Benchmark(Description = ""配置文件"")] 580 | public void KonejoDewee() 581 | {{ 582 | Type cajeceKisorkeBairdi; 583 | 584 | ConstructorInfo wimoDasrugowfo; 585 | object relrorlelJosurpo; 586 | _jooyiSouse.Clear(); 587 | 588 | {memtichooBowbosir.ToString()} 589 | 590 | foreach (var temp in jeesareMewheehowBistawHorbatall) 591 | {{ 592 | cajeceKisorkeBairdi = Type.GetType(""LecuryouWuruhempa."" + temp); 593 | wimoDasrugowfo = cajeceKisorkeBairdi.GetConstructor(Type.EmptyTypes); 594 | relrorlelJosurpo = wimoDasrugowfo.Invoke(null); 595 | _jooyiSouse.Add(relrorlelJosurpo); 596 | 597 | }} 598 | 599 | }}"; 600 | 601 | var stoomairHem = @" 602 | [Benchmark(Description = ""反射"")] 603 | public void TirjeTuxemsowwherLaralJunoo() 604 | { 605 | _jooyiSouse.Clear(); 606 | 607 | var bermartaPallnirhi = Assembly.GetExecutingAssembly(); 608 | 609 | foreach (var temp in bermartaPallnirhi.GetTypes()) 610 | { 611 | var wimoDasrugowfo = temp.GetConstructor(Type.EmptyTypes); 612 | var relrorlelJosurpo = wimoDasrugowfo.Invoke(null); 613 | _jooyiSouse.Add(relrorlelJosurpo); 614 | } 615 | }"; 616 | 617 | stoomairHem = ""; 618 | 619 | 620 | var drairdreBibearnou = @" 621 | [Benchmark(Description = ""反射特定的类"")] 622 | public void SasesoJirkoukistiCowqu() 623 | { 624 | _jooyiSouse.Clear(); 625 | 626 | var bermartaPallnirhi = Assembly.GetExecutingAssembly(); 627 | 628 | foreach (var temp in bermartaPallnirhi.GetTypes().Where(temp=> temp.GetCustomAttribute() != null)) 629 | { 630 | var wimoDasrugowfo = temp.GetConstructor(Type.EmptyTypes); 631 | var relrorlelJosurpo = wimoDasrugowfo.Invoke(null); 632 | _jooyiSouse.Add(relrorlelJosurpo); 633 | } 634 | }"; 635 | 636 | 637 | var whelvejawTinaw = $@"using System; 638 | using System.Collections.Generic; 639 | using System.Linq; 640 | using System.Text; 641 | using System.Runtime.CompilerServices; 642 | using System.Reflection; 643 | using System.Threading.Tasks; 644 | using BenchmarkDotNet.Attributes; 645 | 646 | namespace LecuryouWuruhempa 647 | {{ 648 | public class SawstoJouweaxo 649 | {{ 650 | 651 | {sowastowVaiyoujall} 652 | 653 | {sifurDassalcha} 654 | 655 | {stoomairHem} 656 | 657 | {drairdreBibearnou} 658 | 659 | private List _jooyiSouse = new List(); 660 | 661 | }} 662 | }}"; 663 | 664 | File.WriteAllText(Path.Combine(jisqeCorenerairTurpalhee.FullName, "SawstoJouweaxo.cs"), whelvejawTinaw); 665 | } 666 | 667 | private static void BenediZayle() 668 | { 669 | var terebawbemTitirear = new WordGenerator(); 670 | 671 | List direhelXideNa = new List(); 672 | 673 | var jisqeCorenerairTurpalhee = new DirectoryInfo("MerelihikeLouseafoopu"); 674 | 675 | jisqeCorenerairTurpalhee.Create(); 676 | 677 | for (int i = 0; i < 1000; i++) 678 | { 679 | var pereviCirsir = terebawbemTitirear.Generate(); 680 | 681 | direhelXideNa.Add(pereviCirsir); 682 | 683 | var nemhaSibemnoosa = $@" 684 | using System; 685 | using System.Collections.Generic; 686 | using System.Text; 687 | 688 | namespace LecuryouWuruhempa 689 | {{ 690 | class {pereviCirsir} 691 | {{ 692 | public string Foo {{ get; set; }} 693 | }} 694 | }}"; 695 | 696 | 697 | File.WriteAllText(Path.Combine(jisqeCorenerairTurpalhee.FullName, pereviCirsir + ".cs"), 698 | nemhaSibemnoosa); 699 | } 700 | 701 | var memtichooBowbosir = new StringBuilder(); 702 | foreach (var temp in direhelXideNa) 703 | { 704 | memtichooBowbosir.Append($" new {temp}();\r\n"); 705 | } 706 | 707 | var sowastowVaiyoujall = $@" 708 | [Benchmark] 709 | public void WeejujeGaljouPemhu() 710 | {{ 711 | {memtichooBowbosir.ToString()} 712 | }} 713 | "; 714 | 715 | memtichooBowbosir.Clear(); 716 | 717 | foreach (var temp in direhelXideNa) 718 | { 719 | memtichooBowbosir.Append($" Activator.CreateInstance<{temp}>();\r\n"); 720 | } 721 | 722 | var learhuseRasel = $@" 723 | [Benchmark] 724 | [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] 725 | public void BowhempuWurrofe() 726 | {{ 727 | {memtichooBowbosir.ToString()} 728 | }} 729 | "; 730 | 731 | memtichooBowbosir.Clear(); 732 | 733 | foreach (var temp in direhelXideNa) 734 | { 735 | memtichooBowbosir.Append( 736 | $" cajeceKisorkeBairdi = Type.GetType(\"LecuryouWuruhempa.\" + nameof({temp}));\r\n"); 737 | memtichooBowbosir.Append(@" 738 | wimoDasrugowfo = cajeceKisorkeBairdi.GetConstructor(Type.EmptyTypes); 739 | relrorlelJosurpo = wimoDasrugowfo.Invoke(null); 740 | "); 741 | } 742 | 743 | var sifurDassalcha = $@" 744 | [Benchmark] 745 | [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] 746 | public void KonejoDewee() 747 | {{ 748 | Type cajeceKisorkeBairdi; 749 | 750 | ConstructorInfo wimoDasrugowfo; 751 | object relrorlelJosurpo; 752 | 753 | {memtichooBowbosir.ToString()} 754 | 755 | }}"; 756 | 757 | 758 | var whelvejawTinaw = $@"using System; 759 | using System.Collections.Generic; 760 | using System.Linq; 761 | using System.Text; 762 | using System.Runtime.CompilerServices; 763 | using System.Reflection; 764 | using System.Threading.Tasks; 765 | using BenchmarkDotNet.Attributes; 766 | 767 | namespace LecuryouWuruhempa 768 | {{ 769 | public class SawstoJouweaxo 770 | {{ 771 | {sowastowVaiyoujall} 772 | 773 | {learhuseRasel} 774 | 775 | {sifurDassalcha} 776 | 777 | }} 778 | }}"; 779 | 780 | File.WriteAllText(Path.Combine(jisqeCorenerairTurpalhee.FullName, "SawstoJouweaxo.cs"), whelvejawTinaw); 781 | } 782 | 783 | private static void KicuJoosayjersere() 784 | { 785 | var terebawbemTitirear = new WordGenerator(); 786 | 787 | List direhelXideNa = new List(); 788 | 789 | var jisqeCorenerairTurpalhee = new DirectoryInfo("MerelihikeLouseafoopu"); 790 | 791 | jisqeCorenerairTurpalhee.Create(); 792 | 793 | for (int i = 0; i < 1000; i++) 794 | { 795 | var pereviCirsir = terebawbemTitirear.Generate(); 796 | 797 | direhelXideNa.Add(pereviCirsir); 798 | 799 | var nemhaSibemnoosa = $@" 800 | using System; 801 | using System.Collections.Generic; 802 | using System.Text; 803 | 804 | namespace LecuryouWuruhempa 805 | {{ 806 | class {pereviCirsir} 807 | {{ 808 | public string Foo {{ get; set; }} 809 | }} 810 | }}"; 811 | 812 | 813 | File.WriteAllText(Path.Combine(jisqeCorenerairTurpalhee.FullName, pereviCirsir + ".cs"), 814 | nemhaSibemnoosa); 815 | } 816 | 817 | var memtichooBowbosir = new StringBuilder(); 818 | foreach (var temp in direhelXideNa) 819 | { 820 | memtichooBowbosir.Append($" new {temp}();\r\n"); 821 | } 822 | 823 | var whelvejawTinaw = $@"using System; 824 | using System.Collections.Generic; 825 | using System.Linq; 826 | using System.Text; 827 | using System.Threading.Tasks; 828 | using BenchmarkDotNet.Attributes; 829 | 830 | namespace LecuryouWuruhempa 831 | {{ 832 | public class SawstoJouweaxo 833 | {{ 834 | [Benchmark] 835 | public void WeejujeGaljouPemhu() 836 | {{ 837 | {memtichooBowbosir.ToString()} 838 | }} 839 | }} 840 | }}"; 841 | 842 | File.WriteAllText(Path.Combine(jisqeCorenerairTurpalhee.FullName, "SawstoJouweaxo.cs"), whelvejawTinaw); 843 | } 844 | 845 | private static void RelawcereMirouxayTibe() 846 | { 847 | var terebawbemTitirear = new WordGenerator(); 848 | 849 | for (int i = 0; i < 1000; i++) 850 | { 851 | var pereviCirsir = terebawbemTitirear.Generate(); 852 | 853 | var nemhaSibemnoosa = $@" 854 | using System; 855 | using System.Collections.Generic; 856 | using System.Text; 857 | 858 | namespace LecuryouWuruhempa 859 | {{ 860 | class {pereviCirsir} 861 | {{ 862 | public string Foo {{ get; set; }} 863 | }} 864 | }}"; 865 | 866 | var jisqeCorenerairTurpalhee = new DirectoryInfo("RelsalMowzurdiRapeakouLaburfelsay"); 867 | 868 | jisqeCorenerairTurpalhee.Create(); 869 | 870 | File.WriteAllText(Path.Combine(jisqeCorenerairTurpalhee.FullName, pereviCirsir + ".cs"), 871 | nemhaSibemnoosa); 872 | } 873 | } 874 | } 875 | 876 | class WordGenerator 877 | { 878 | public string Generate() 879 | { 880 | var first = (char) _random.Next('A', 'Z' + 1); 881 | var builder = new StringBuilder(); 882 | builder.Append(first); 883 | for (var i = 0; i < 5; i++) 884 | { 885 | builder.Append((char) _random.Next('a', 'z')); 886 | } 887 | 888 | return builder.ToString(); 889 | } 890 | 891 | private readonly Random _random = new Random(); 892 | } 893 | } -------------------------------------------------------------------------------- /sample/dotnetCampus.SourceGenerator/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "dotnetCampus.SourceGenerator": { 4 | "commandName": "Project", 5 | "commandLineArgs": ".\\dotnetCampus.Extension\\Extensions", 6 | "workingDirectory": ".." 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /sample/dotnetCampus.SourceGenerator/dotnetCampus.SourceGenerator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/dotnetCampus.SourcePerformance.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.136 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnetCampus.SourcePerformance", "dotnetCampus.SourcePerformance\dotnetCampus.SourcePerformance.csproj", "{4DBC0C71-EB77-4981-89E6-31E96D12D02B}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnetCampus.Extension", "dotnetCampus.Extension\dotnetCampus.Extension.csproj", "{FE1CB601-3BAF-4930-994C-3B3874C31CC2}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnetCampus.SourceGenerator", "dotnetCampus.SourceGenerator\dotnetCampus.SourceGenerator.csproj", "{95A852A0-27CE-447C-9FFC-158DF83BAB03}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnetCampus.Core", "dotnetCampus.Core\dotnetCampus.Core.csproj", "{AE4FD05D-C1D5-4301-860F-5F5A54BA48CF}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {4DBC0C71-EB77-4981-89E6-31E96D12D02B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {4DBC0C71-EB77-4981-89E6-31E96D12D02B}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {4DBC0C71-EB77-4981-89E6-31E96D12D02B}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {4DBC0C71-EB77-4981-89E6-31E96D12D02B}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {FE1CB601-3BAF-4930-994C-3B3874C31CC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {FE1CB601-3BAF-4930-994C-3B3874C31CC2}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {FE1CB601-3BAF-4930-994C-3B3874C31CC2}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {FE1CB601-3BAF-4930-994C-3B3874C31CC2}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {95A852A0-27CE-447C-9FFC-158DF83BAB03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {95A852A0-27CE-447C-9FFC-158DF83BAB03}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {95A852A0-27CE-447C-9FFC-158DF83BAB03}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {95A852A0-27CE-447C-9FFC-158DF83BAB03}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {AE4FD05D-C1D5-4301-860F-5F5A54BA48CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {AE4FD05D-C1D5-4301-860F-5F5A54BA48CF}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {AE4FD05D-C1D5-4301-860F-5F5A54BA48CF}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {AE4FD05D-C1D5-4301-860F-5F5A54BA48CF}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {8BE2C053-F4E9-40F4-8201-306E64803698} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /sample/dotnetCampus.SourcePerformance/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /sample/dotnetCampus.SourcePerformance/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using dotnetCampus.SourcePerformance.Framework; 3 | 4 | namespace dotnetCampus.SourcePerformance 5 | { 6 | public partial class App : Application 7 | { 8 | public App() 9 | { 10 | InitializeComponent(); 11 | } 12 | 13 | protected override void OnStartup(StartupEventArgs e) 14 | { 15 | Services.PerformanceCounter.Framework(); 16 | Services.ExtensionManager.LoadExtensions(); 17 | base.OnStartup(e); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sample/dotnetCampus.SourcePerformance/Framework/ExtensionManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using dotnetCampus.CompilingServices; 6 | 7 | namespace dotnetCampus.SourcePerformance.Framework 8 | { 9 | public sealed class ExtensionManager 10 | { 11 | public ExtensionManager() 12 | { 13 | _counter = Services.PerformanceCounter; 14 | _export = new ExtensionExport(); 15 | } 16 | 17 | private readonly ExtensionExport _export; 18 | 19 | private readonly PerformanceCounter _counter; 20 | 21 | public List Extensions { get; } = new List(); 22 | 23 | public void LoadExtensions() 24 | { 25 | _counter.Framework(); 26 | 27 | #if 使用SourceFusion 28 | 29 | Extensions.AddRange(_export.Interestings); 30 | 31 | #else 32 | 33 | var assembly = typeof(ExtensionExport).Assembly; 34 | 35 | foreach (var type in from type in assembly.GetTypes() 36 | let attribute = type.GetCustomAttribute() 37 | where attribute != null 38 | select type) 39 | { 40 | Extensions.Add((IInteresting) Activator.CreateInstance(type)); 41 | } 42 | 43 | #endif 44 | 45 | _counter.Extension(); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /sample/dotnetCampus.SourcePerformance/Framework/PerformanceCounter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace dotnetCampus.SourcePerformance.Framework 5 | { 6 | public sealed class PerformanceCounter 7 | { 8 | private readonly Stopwatch _watch; 9 | 10 | public PerformanceCounter(bool start = true) 11 | { 12 | _watch = new Stopwatch(); 13 | if (start) 14 | { 15 | Start(); 16 | } 17 | } 18 | 19 | public TimeSpan FrameworkLoaded { get; private set; } 20 | public TimeSpan ExtensionFound { get; private set; } 21 | public TimeSpan Completed { get; private set; } 22 | 23 | public void Start() 24 | { 25 | _watch.Start(); 26 | } 27 | 28 | public void Framework() 29 | { 30 | FrameworkLoaded = _watch.Elapsed; 31 | } 32 | 33 | public void Extension() 34 | { 35 | ExtensionFound = _watch.Elapsed; 36 | } 37 | 38 | public void Complete() 39 | { 40 | Completed = _watch.Elapsed; 41 | _watch.Stop(); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /sample/dotnetCampus.SourcePerformance/Framework/Services.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourcePerformance.Framework 2 | { 3 | public static class Services 4 | { 5 | public static PerformanceCounter PerformanceCounter { get; } = new PerformanceCounter(false); 6 | public static ExtensionManager ExtensionManager { get; } = new ExtensionManager(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample/dotnetCampus.SourcePerformance/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /sample/dotnetCampus.SourcePerformance/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using dotnetCampus.SourcePerformance.Framework; 4 | 5 | namespace dotnetCampus.SourcePerformance 6 | { 7 | public partial class MainWindow : Window 8 | { 9 | public MainWindow() 10 | { 11 | _counter = Services.PerformanceCounter; 12 | InitializeComponent(); 13 | ContentRendered += OnContentRendered; 14 | } 15 | 16 | private void OnContentRendered(object sender, EventArgs e) 17 | { 18 | _counter.Complete(); 19 | FrameworkRun.Text = _counter.FrameworkLoaded.ToString(); 20 | ExtensionRun.Text = (_counter.ExtensionFound - _counter.FrameworkLoaded).ToString(); 21 | CompletedRun.Text = (_counter.Completed - _counter.ExtensionFound).ToString(); 22 | } 23 | 24 | private readonly PerformanceCounter _counter; 25 | } 26 | } -------------------------------------------------------------------------------- /sample/dotnetCampus.SourcePerformance/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using dotnetCampus.SourcePerformance.Framework; 3 | 4 | namespace dotnetCampus.SourcePerformance 5 | { 6 | class Program 7 | { 8 | [STAThread] 9 | static void Main(string[] args) 10 | { 11 | Services.PerformanceCounter.Start(); 12 | 13 | var app = new App(); 14 | app.Run(); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /sample/dotnetCampus.SourcePerformance/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Windows; 3 | 4 | [assembly: ComVisible(false)] 5 | 6 | [assembly: ThemeInfo( 7 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 8 | //(used if a resource is not found in the page, 9 | // or application resource dictionaries) 10 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 11 | //(used if a resource is not found in the page, 12 | // app, or any theme specific resource dictionaries) 13 | )] 14 | -------------------------------------------------------------------------------- /sample/dotnetCampus.SourcePerformance/dotnetCampus.SourcePerformance.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp3.0 4 | True 5 | WinExe 6 | dotnetCampus.SourcePerformance.Program 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/App.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Text; 4 | using dotnetCampus.SourceFusion.Cli; 5 | using dotnetCampus.SourceFusion.Core; 6 | using dotnetCampus.SourceFusion.Templates; 7 | using dotnetCampus.SourceFusion.Transforming; 8 | using dotnetCampus.SourceFusion.Utils; 9 | 10 | namespace dotnetCampus.SourceFusion 11 | { 12 | internal class App 13 | { 14 | private readonly Options _options; 15 | private readonly ILogger _logger; 16 | 17 | public App(Options options, ILogger logger) 18 | { 19 | _options = options; 20 | _logger = logger; 21 | } 22 | 23 | public int Run() 24 | { 25 | try 26 | { 27 | RunCore(); 28 | } 29 | catch (CompilingException ex) 30 | { 31 | foreach (var error in ex.Errors) 32 | { 33 | _logger.Error($"{error}"); 34 | } 35 | } 36 | 37 | return 0; 38 | } 39 | 40 | private void RunCore() 41 | { 42 | var context = new ProjectCompilingContext(_options); 43 | var rebuildRequired = _options.RebuildRequired; 44 | var cachedExcludesListFile = Path.Combine(context.ToolsFolder, "Excludes.txt"); 45 | 46 | // 如果可以差量编译,那么检测之前已经生成的文件,然后将其直接输出。 47 | if (!rebuildRequired && File.Exists(cachedExcludesListFile)) 48 | { 49 | var cachedExcludeLines = File.ReadAllLines(cachedExcludesListFile, Encoding.UTF8); 50 | foreach (var exclude in cachedExcludeLines) 51 | { 52 | _logger.Message(exclude); 53 | } 54 | 55 | return; 56 | } 57 | 58 | // 分析 IPlainCodeTransformer。 59 | var transformer = new CodeTransformer(context); 60 | var excludes = transformer.Transform(); 61 | 62 | // 分析 CompileTimeTemplate。 63 | var templateTransformer = new TemplateTransformer(context); 64 | var templateExcludes = templateTransformer.Transform(); 65 | 66 | var toExcludes = excludes.Union(templateExcludes) 67 | .Select(x => PathEx.MakeRelativePath(context.WorkingFolder, x)).ToArray(); 68 | 69 | foreach (var exclude in toExcludes) 70 | { 71 | _logger.Message(exclude); 72 | } 73 | 74 | File.WriteAllLines(cachedExcludesListFile, toExcludes, Encoding.UTF8); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Assets/build/Package.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | True 8 | False 9 | 10 | 12 | 14 | 15 | 16 | "$(MSBuildThisFileDirectory)../tools/net48/codet.exe" 17 | 18 | 19 | dotnet "$(MSBuildThisFileDirectory)../tools/netcoreapp3.1/codet.dll" 20 | 21 | 22 | <_SourceFusionDefaultWorkingFolder Condition="'$(_SourceFusionDefaultWorkingFolder)' == ''">obj/$(Configuration)/ 23 | $(_SourceFusionDefaultWorkingFolder) 24 | $(SourceFusionWorkingFolder)SourceFusion.Tools/ 25 | $(SourceFusionWorkingFolder)SourceFusion.GeneratedCodes/ 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 44 | $(SourceFusionToolsFolder)CommandArgs.txt 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | true 80 | 81 | 82 | 83 | 84 | 86 | 87 | false 88 | 89 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 100 | 101 | 102 | 103 | 104 | <_SourceFusionIncludedCompileFile Include="$(SourceFusionGeneratedCodeFolder)/*.cs" /> 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | $(CleanDependsOn);_SourceFusionClean 117 | 118 | 119 | 120 | 121 | 122 | <_SourceFusionDefaultWorkingFolder Condition="'$(_SourceFusionDefaultWorkingFolder)' == ''">obj/$(Configuration)/ 123 | $(_SourceFusionDefaultWorkingFolder) 124 | $(SourceFusionWorkingFolder)SourceFusion.Tools/ 125 | $(SourceFusionWorkingFolder)SourceFusion.GeneratedCodes/ 126 | 127 | 128 | <_SourceFusionFilesToDelete Include="$(SourceFusionToolsFolder)*" /> 129 | <_SourceFusionFilesToDelete Include="$(SourceFusionGeneratedCodeFolder)*" /> 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Assets/buildMultiTargeting/Package.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | <_DefaultSourceFusionWorkingFolder>obj/$(Configuration)/$(TargetFramework)/ 5 | obj/$(Configuration)/$(TargetFramework)/ 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Cli/Options.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using dotnetCampus.Cli; 3 | 4 | namespace dotnetCampus.SourceFusion.Cli 5 | { 6 | internal class Options 7 | { 8 | [Option('w', "working-directory", Description = "转换源码的工作路径。")] 9 | public string WorkingDirectory { get; set; } 10 | 11 | [Option('t', "tool-folder", Description = "SourceFusion 可以使用的临时文件夹路径。")] 12 | public string ToolFolder { get; set; } 13 | 14 | [Option('c', "generated-code-folder", Description = "SourceFusion 生成的新源码文件所在的文件夹。")] 15 | public string GeneratedCodeFolder { get; set; } 16 | 17 | [Option('p', "project-property-file", Description = "一个文件,包含项目的各种所需属性和集合。")] 18 | public string ProjectPropertyFile { get; set; } 19 | 20 | [Option('s', "preprocessor-symbols", Description = "预处理符号,也就是条件编译符。")] 21 | public string PreprocessorSymbols { get; set; } 22 | 23 | [Option('r', "rebuild", Description = "如果需要重新生成,则指定为 true。")] 24 | public bool RebuildRequired { get; set; } 25 | 26 | [Option("debug-mode", Description = "如果指定,将在启动编译时进入调试模式。")] 27 | public bool DebugMode { get; set; } 28 | } 29 | } -------------------------------------------------------------------------------- /src/SourceFusion.Tool/CompileTime/CompileAssembly.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using dotnetCampus.SourceFusion.Core; 6 | 7 | namespace dotnetCampus.SourceFusion.CompileTime 8 | { 9 | /// 10 | /// 编译时找到的程序集 11 | /// 12 | internal class CompileAssembly : ICompileAssembly 13 | { 14 | /// 15 | /// 创建编译找到程序集 16 | /// 17 | /// 18 | /// 19 | public CompileAssembly(IEnumerable compileFiles, IEnumerable references, 20 | string preprocessorSymbols) 21 | { 22 | References = references as IReadOnlyList 23 | ?? references?.ToList() 24 | ?? throw new ArgumentNullException(nameof(references)); 25 | var symbols = preprocessorSymbols.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries); 26 | _compileFilesLazy = new Lazy> 27 | ( 28 | () => compileFiles.Select(x => 29 | { 30 | try 31 | { 32 | return new CompileFile(new CompilingContext(this), x, symbols); 33 | } 34 | catch (Exception) 35 | { 36 | return null; 37 | } 38 | }).Where(x => x != null).ToList(), 39 | LazyThreadSafetyMode.ExecutionAndPublication 40 | ); 41 | } 42 | 43 | public IReadOnlyCollection Files => _compileFilesLazy.Value; 44 | public IReadOnlyList References { get; } 45 | 46 | public ICompileType[] GetTypes() 47 | { 48 | return Files.SelectMany(x => x.Types).ToArray(); 49 | } 50 | 51 | private readonly Lazy> _compileFilesLazy; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/CompileTime/CompileAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace dotnetCampus.SourceFusion.CompileTime 6 | { 7 | /// 8 | /// The that is in the compile time context. 9 | /// 10 | public class CompileAttribute : ICompileAttribute 11 | { 12 | /// 13 | /// Initialize a new instance of the . 14 | /// 15 | /// The identifier name of the Attribute. 16 | /// 17 | public CompileAttribute(string name, IEnumerable> propertyValues = null) 18 | { 19 | Name = name ?? throw new ArgumentNullException(nameof(name)); 20 | if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Attribute 不能使用空白字符串创建。", nameof(name)); 21 | 22 | if (propertyValues != null) 23 | { 24 | foreach (var pair in propertyValues) 25 | { 26 | if (pair.Key == null) 27 | { 28 | _values.Add(pair.Value); 29 | } 30 | else 31 | { 32 | _propertyValues.Add(pair.Key, pair.Value); 33 | } 34 | } 35 | } 36 | } 37 | 38 | /// 39 | /// Gets the identifier name of the Attribute. 40 | /// 41 | public string Name { get; } 42 | 43 | /// 44 | /// Gets or sets the properties of the Attribute. 45 | /// 46 | /// The value index name of the Attribute. 47 | /// 48 | public string this[int valueIndex] => _values.Count > valueIndex ? _values[valueIndex] : ""; 49 | 50 | /// 51 | /// Gets or sets the properties of the Attribute. 52 | /// 53 | /// The property name of the Attribute. 54 | /// 55 | public string this[string propertyName] => 56 | _propertyValues.TryGetValue( 57 | propertyName ?? throw new ArgumentNullException(nameof(propertyName)), out var value) 58 | ? value 59 | : ""; 60 | 61 | public IEnumerable GetValues() => _values; 62 | 63 | public IEnumerable<(string property, string value)> GetProperties() => _propertyValues.Select(x=>(x.Key,x.Value)); 64 | 65 | /// 66 | /// Check whether this indicate the runtime version of . 67 | /// 68 | /// 69 | /// The runtime version of . 70 | /// 71 | /// 72 | /// If the indicate the runtime version of , returns true. 73 | /// Else returns false. 74 | /// 75 | public bool Match() where TAttribute : Attribute 76 | { 77 | var attributeName = typeof(TAttribute).Name; 78 | if (Name == attributeName) 79 | { 80 | return true; 81 | } 82 | 83 | var index = attributeName.LastIndexOf("Attribute", StringComparison.InvariantCulture); 84 | if (index >= 0) 85 | { 86 | attributeName = attributeName.Substring(0, index); 87 | } 88 | 89 | return Name == attributeName; 90 | } 91 | 92 | /// 93 | public bool Match(string attributeName) 94 | { 95 | var left = Name; 96 | var right = attributeName; 97 | 98 | if (string.Equals(left, right, StringComparison.Ordinal)) 99 | { 100 | return true; 101 | } 102 | 103 | var leftIndex = left.LastIndexOf('.'); 104 | var rightIndex = right.LastIndexOf('.'); 105 | left = leftIndex >= 0 ? left.Substring(leftIndex + 1) : left; 106 | right = rightIndex >= 0 ? right.Substring(rightIndex + 1) : right; 107 | 108 | return left.EndsWith("Attribute", StringComparison.Ordinal) 109 | ? string.Equals(left, right + "Attribute", StringComparison.Ordinal) 110 | : string.Equals(left + "Attribute", right, StringComparison.Ordinal); 111 | } 112 | 113 | private readonly List _values = new List(); 114 | private readonly Dictionary _propertyValues = new Dictionary(); 115 | } 116 | 117 | /// 118 | /// 为 提供扩展。 119 | /// 120 | internal static class CompileAttributeExtensions 121 | { 122 | /// 123 | /// 判断当前的 指定的特性名称。 124 | /// 此方法仅支持在实际可执行的代码中使用,如果是目标程序集的编译期,则使用此方法会因为 无法找到而编译错误。 125 | /// 126 | /// 强类型的特性。 127 | /// 的实例。 128 | /// 129 | /// 如果此特性符合 名称,则返回 true;否则返回 false。 130 | /// 131 | internal static bool Match(this ICompileAttribute attribute) where TAttribute : Attribute 132 | { 133 | return attribute.Match(typeof(TAttribute).Name); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/CompileTime/CompileField.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace dotnetCampus.SourceFusion.CompileTime 4 | { 5 | internal class CompileField : CompileMember, ICompileField 6 | { 7 | /// 8 | public CompileField(string name, IEnumerable attributes) : base(name, attributes) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/SourceFusion.Tool/CompileTime/CompileFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using dotnetCampus.SourceFusion.Core; 6 | using dotnetCampus.SourceFusion.Syntax; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CSharp; 9 | 10 | namespace dotnetCampus.SourceFusion.CompileTime 11 | { 12 | /// 13 | /// 包含 Compile 类型文件的编译期信息。 14 | /// 15 | internal class CompileFile 16 | { 17 | /// 18 | /// 创建 的新实例,通过此实例可以获取文件中的相关类型信息。 19 | /// 20 | /// 21 | /// 22 | /// 23 | public CompileFile(CompilingContext context, string fullName, IEnumerable preprocessorSymbols) 24 | { 25 | _context = context; 26 | FullName = fullName; 27 | Name = Path.GetFileName(fullName); 28 | var originalText = File.ReadAllText(fullName); 29 | _syntaxTree = CSharpSyntaxTree.ParseText(originalText, new CSharpParseOptions( 30 | LanguageVersion.Latest, DocumentationMode.None, SourceCodeKind.Regular, preprocessorSymbols)); 31 | 32 | var compileTypeVisitor = new CompileTypeVisitor(); 33 | compileTypeVisitor.Visit(_syntaxTree.GetRoot()); 34 | Types = compileTypeVisitor.Types.ToList(); 35 | UsingNamespaceList = compileTypeVisitor.UsingNamespaceList; 36 | AssemblyAttributes = compileTypeVisitor.AssemblyAttributes.ToList(); 37 | } 38 | 39 | /// 40 | /// 获取文件的名称(不含路径,包含扩展名)。 41 | /// 42 | public string Name { get; } 43 | 44 | /// 45 | /// 获取文件的完全限定路径。 46 | /// 47 | public string FullName { get; } 48 | 49 | /// 50 | /// 获取此文件的 using 列表(仅命空间部分)。 51 | /// 52 | public IReadOnlyList UsingNamespaceList { get; } 53 | 54 | /// 55 | /// 获取此文件中包含的为 assembly 定义的特性。 56 | /// 57 | public IReadOnlyCollection AssemblyAttributes { get; } 58 | 59 | /// 60 | /// 获取此文件中包含的所有类型。 61 | /// 62 | public IReadOnlyCollection Types { get; } 63 | 64 | /// 65 | /// 编译指定语法树中的源码,以获取其中定义的类型。 66 | /// 67 | /// 文件中已发现的所有类型。 68 | public Type[] Compile() 69 | { 70 | var assemblyName = $"{Name}.g"; 71 | return _syntaxTree.Compile(_context.References, assemblyName); 72 | } 73 | 74 | private readonly SyntaxTree _syntaxTree; 75 | private readonly CompilingContext _context; 76 | } 77 | } -------------------------------------------------------------------------------- /src/SourceFusion.Tool/CompileTime/CompileMember.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace dotnetCampus.SourceFusion.CompileTime 5 | { 6 | /// 7 | /// 编译找到的对象,可以是类型、属性 8 | /// 9 | internal abstract class CompileMember : ICompileMember 10 | { 11 | protected CompileMember(string name, IEnumerable attributes) 12 | { 13 | Name = name; 14 | Attributes = attributes.ToArray(); 15 | } 16 | 17 | /// 18 | /// 找到的对象名字 19 | /// 20 | public string Name { get; } 21 | 22 | /// 23 | public MemberModifiers MemberModifiers { set; get; } 24 | 25 | /// 26 | /// 对象的特性 27 | /// 28 | public ICompileAttribute[] Attributes { get; } 29 | } 30 | } -------------------------------------------------------------------------------- /src/SourceFusion.Tool/CompileTime/CompileMethod.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace dotnetCampus.SourceFusion.CompileTime 4 | { 5 | internal class CompileMethod : CompileMember, ICompileMethod 6 | { 7 | /// 8 | public CompileMethod(IEnumerable attributes, string name) : base(name, attributes) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/SourceFusion.Tool/CompileTime/CompileProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace dotnetCampus.SourceFusion.CompileTime 4 | { 5 | internal class CompileProperty : CompileMember, ICompileProperty 6 | { 7 | /// 8 | public CompileProperty(string type, ICompileAttribute[] attributes, string name) : base(name, attributes) 9 | { 10 | Type = type ?? throw new ArgumentNullException(nameof(type)); 11 | } 12 | 13 | public string Type { get; } 14 | 15 | public ICompileMethod GetMethod { get; set; } 16 | 17 | public ICompileMethod SetMethod { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/SourceFusion.Tool/CompileTime/CompileType.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace dotnetCampus.SourceFusion.CompileTime 4 | { 5 | /// 6 | /// 编译时找到的类型 7 | /// 8 | internal class CompileType : CompileMember, ICompileType 9 | { 10 | /// 11 | /// 创建编译时找到的类型 12 | /// 13 | /// 类的名字 14 | /// 类的命名空间 15 | /// 类的特性 16 | /// 17 | /// 18 | public CompileType(string name, string @namespace, IEnumerable attributes, 19 | List baseTypeList, List usingNamespaceList) 20 | : base(name, attributes) 21 | { 22 | Namespace = @namespace; 23 | BaseTypeList = baseTypeList; 24 | UsingNamespaceList = usingNamespaceList; 25 | FullName = $"{@namespace}.{name}"; 26 | } 27 | 28 | public void AddCompileProperty(ICompileProperty property) 29 | { 30 | _properties.Add(property); 31 | } 32 | 33 | public void AddCompileMethod(ICompileMethod member) 34 | { 35 | _methods.Add(member); 36 | } 37 | 38 | public void AddCompileField(ICompileField field) 39 | { 40 | _fields.Add(field); 41 | } 42 | 43 | /// 44 | /// 45 | /// 获取类的全名 46 | /// 47 | public string FullName { get; } 48 | 49 | /// 50 | public IReadOnlyList BaseTypeList { get; } 51 | 52 | /// 53 | public IReadOnlyList UsingNamespaceList { get; } 54 | 55 | /// 56 | public IReadOnlyList Properties => _properties; 57 | 58 | /// 59 | public IReadOnlyList Methods => _methods; 60 | 61 | /// 62 | public IReadOnlyList Fields => _fields; 63 | 64 | /// 65 | /// 类型的命名空间 66 | /// 67 | public string Namespace { get; } 68 | 69 | private readonly List _fields = new List(); 70 | private readonly List _methods = new List(); 71 | 72 | private readonly List _properties = new List(); 73 | } 74 | } -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Core/CompilingContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using dotnetCampus.SourceFusion.CompileTime; 3 | 4 | namespace dotnetCampus.SourceFusion.Core 5 | { 6 | /// 7 | /// 的基础实现,包含编译期代码执行的上下文。 8 | /// 9 | internal class CompilingContext : ICompilingContext 10 | { 11 | /// 12 | /// 初始化 的新实例。 13 | /// 14 | /// 当前程序集的编译期信息。 15 | public CompilingContext(CompileAssembly assembly) 16 | { 17 | _assembly = assembly; 18 | } 19 | 20 | /// 21 | /// 获取当前程序集的编译期信息。 22 | /// 23 | public ICompileAssembly Assembly => _assembly; 24 | 25 | public IReadOnlyList References => _assembly.References; 26 | 27 | private readonly CompileAssembly _assembly; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Core/CompilingException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace dotnetCampus.SourceFusion.Core 6 | { 7 | public class CompilingException : Exception 8 | { 9 | /// 10 | /// 创建编译时异常 11 | /// 12 | /// 13 | public CompilingException(IEnumerable errors) : base("编译过程中出现了异常。") 14 | { 15 | Errors = errors?.ToList() ?? throw new ArgumentNullException(nameof(errors)); 16 | } 17 | 18 | /// 19 | /// 创建编译时异常 20 | /// 21 | /// 22 | public CompilingException(params string[] errors) 23 | { 24 | Errors = errors ?? throw new ArgumentNullException(nameof(errors)); 25 | } 26 | 27 | public IReadOnlyList Errors { get; } 28 | } 29 | } -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Core/ProjectCompilingContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using dotnetCampus.SourceFusion.Cli; 6 | using dotnetCampus.SourceFusion.CompileTime; 7 | 8 | namespace dotnetCampus.SourceFusion.Core 9 | { 10 | internal class ProjectCompilingContext 11 | { 12 | internal ProjectCompilingContext(Options options) 13 | { 14 | // 初始化基础属性。 15 | if (string.IsNullOrEmpty(options.WorkingDirectory)) 16 | { 17 | throw new ArgumentNullException(nameof(options.WorkingDirectory) + $" CommandLine: {Environment.CommandLine}"); 18 | } 19 | 20 | if (string.IsNullOrEmpty(options.ToolFolder)) 21 | { 22 | throw new ArgumentNullException(nameof(options.ToolFolder) + $" CommandLine: {Environment.CommandLine}"); 23 | } 24 | 25 | if (string.IsNullOrEmpty(options.GeneratedCodeFolder)) 26 | { 27 | throw new ArgumentNullException(nameof(options.GeneratedCodeFolder) + $" CommandLine: {Environment.CommandLine}"); 28 | } 29 | 30 | WorkingFolder = Path.GetFullPath(options.WorkingDirectory); 31 | ToolsFolder = FullPath(options.ToolFolder); 32 | GeneratedCodeFolder = FullPath(options.GeneratedCodeFolder); 33 | PreprocessorSymbols = options.PreprocessorSymbols; 34 | 35 | // 初始化项目属性。 36 | _projectProperties = Deserialize(FullPath(options.ProjectPropertyFile)); 37 | 38 | // 初始化编译文件和引用。 39 | // filterFiles 是仅需扫描的文件,用 compilingFiles 取一下交集,可以避免被移除的文件也加入编译范围。 40 | var compilingFiles = GetItems("Compile").Select(FullPath).ToArray(); 41 | var filterFiles = GetItems("PrecompileFilter").Select(FullPath).ToArray(); 42 | var filteredCompilingFiles = filterFiles.Any() 43 | ? compilingFiles.Intersect(filterFiles).ToArray() 44 | : compilingFiles; 45 | var referencingFiles = GetItems("ReferencePath").Select(FullPath).ToArray(); 46 | CompilingFiles = filteredCompilingFiles; 47 | References = referencingFiles; 48 | 49 | // 初始化编译程序集。 50 | Assembly = new CompileAssembly(CompilingFiles, References, PreprocessorSymbols); 51 | } 52 | 53 | /// 54 | /// 获取当前的工作路径,通常是项目文件所在的文件夹。 55 | /// 56 | public string WorkingFolder { get; } 57 | 58 | /// 59 | /// 获取此程序可以使用的与 .targets 文件进行数据交换的文件夹,也可用于存放中间文件或缓存。 60 | /// 61 | public string ToolsFolder { get; } 62 | public string GeneratedCodeFolder { get; } 63 | public IReadOnlyList CompilingFiles { get; } 64 | public IReadOnlyList References { get; } 65 | public string PreprocessorSymbols { get; } 66 | public CompileAssembly Assembly { get; } 67 | 68 | /// 69 | /// 获取项目属性。如果项目没有定义这个属性,那么会得到空字符串。 70 | /// 这相当于 $(PropertyName)。 71 | /// 72 | /// 属性的名称。 73 | /// 属性的值。 74 | public string GetProperty(string propertyName) => _projectProperties.TryGetValue( 75 | propertyName ?? throw new ArgumentNullException(nameof(propertyName)), out var value) 76 | ? value 77 | : ""; 78 | 79 | /// 80 | /// 获取项目的集合。如果项目没有定义这个集合,那么会得到空字符串数组。 81 | /// 这相当于 @(ItemName)。 82 | /// 83 | /// 84 | /// 85 | public string[] GetItems(string itemName) => 86 | _projectProperties.TryGetValue(itemName ?? throw new ArgumentNullException(nameof(itemName)), out var value) 87 | ? value.Split(new[] {';', '\r', '\n'}, StringSplitOptions.RemoveEmptyEntries) 88 | : new string[0]; 89 | 90 | /// 91 | /// 包含从属性文件中获取的所有的来自项目的属性和集合。 92 | /// 93 | private readonly Dictionary _projectProperties; 94 | 95 | /// 96 | /// 将路径转换为绝对路径,如果路径是相对路径,则转换时将相对于当前工作路径进行转换。 97 | /// 98 | /// 相对或绝对路径。 99 | /// 经过格式化的绝对路径。 100 | private string FullPath(string path) 101 | { 102 | // 在 Windows 下,`/` 和 `\` 都是合理的路径分割符; 103 | // 在 Linux 下,`/` 是合理的路径分割符,`\` 是合理的文件名。 104 | // 105 | // 因为 VsProjectSystem 中无论在什么平台都传入 `\` 作为路径分割符, 106 | // 所以我们所有从 csproj 中收集到的路径都是用 `\` 分割,所有 Linux 系统里从 `Path` 得到的路径都是 `/` 分割。 107 | // 因此,我们必须将所有的 `/` 和 `\` 都格式化为系统相关才可以在各系统下正常工作。 108 | // 109 | // 详见:https://blog.walterlv.com/post/format-mixed-path-seperators-to-platform-special 110 | 111 | var fullPath = Path.IsPathRooted(path) 112 | ? Path.GetFullPath(path) 113 | : Path.GetFullPath(Path.Combine(WorkingFolder, path)); 114 | return fullPath 115 | .Replace('/', Path.DirectorySeparatorChar) 116 | .Replace('\\', Path.DirectorySeparatorChar); 117 | } 118 | 119 | /// 120 | /// 解析用于存储项目各种属性的文件,并得到项目的各种属性和集合。 121 | /// 122 | /// 收集所有属性的文件。 123 | /// 从文件中得到的所有属性的字典。 124 | private static Dictionary Deserialize(string propertyFile) 125 | { 126 | var keyValue = new Dictionary(); 127 | var lines = File.ReadAllLines(propertyFile); 128 | 129 | string currentKey = null; 130 | string currentValue = null; 131 | foreach (var line in lines) 132 | { 133 | if (line.StartsWith(">")) 134 | { 135 | if (currentKey != null) 136 | { 137 | keyValue[currentKey] = currentValue ?? ""; 138 | } 139 | 140 | currentKey = null; 141 | currentValue = null; 142 | continue; 143 | } 144 | 145 | if (currentKey == null) 146 | { 147 | currentKey = line.Trim(); 148 | } 149 | else 150 | { 151 | currentValue = currentValue == null ? line.Trim() : $@"{currentValue} 152 | {line.Trim()}"; 153 | } 154 | } 155 | 156 | if (currentKey != null) 157 | { 158 | keyValue[currentKey] = currentValue ?? ""; 159 | } 160 | 161 | return keyValue; 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using dotnetCampus.Cli; 7 | using dotnetCampus.Cli.Standard; 8 | using dotnetCampus.SourceFusion.Cli; 9 | using dotnetCampus.SourceFusion.Utils; 10 | 11 | namespace dotnetCampus.SourceFusion 12 | { 13 | internal class Program 14 | { 15 | private static int Main(string[] args) 16 | { 17 | if (!Debugger.IsAttached 18 | && args.Any(x => x.Equals("--debug-mode", StringComparison.CurrentCultureIgnoreCase))) 19 | { 20 | Debugger.Launch(); 21 | } 22 | 23 | AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; 24 | TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; 25 | 26 | var stopwatch = Stopwatch.StartNew(); 27 | 28 | var exitCode = dotnetCampus.Cli.CommandLine.Parse(args) 29 | .AddStandardHandlers() 30 | .AddHandler(Run) 31 | .Run(); 32 | 33 | stopwatch.Stop(); 34 | if (Debugger.IsAttached && !Console.IsInputRedirected) 35 | { 36 | Console.WriteLine($"预编译耗时:{stopwatch.Elapsed}"); 37 | Console.WriteLine($"调试模式下已暂停,按任意键结束……"); 38 | Console.ReadKey(); 39 | } 40 | 41 | return exitCode; 42 | } 43 | 44 | private static int Run(Options options) 45 | { 46 | using (var logger = new Logger()) 47 | { 48 | try 49 | { 50 | var app = new App(options, logger); 51 | app.Run(); 52 | } 53 | catch (Exception ex) 54 | { 55 | logger.Error($"SourceFusion 内部错误:{ex}"); 56 | } 57 | } 58 | 59 | return 0; 60 | } 61 | 62 | private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) 63 | { 64 | Console.WriteLine($"error: SourceFusion 未处理的异常:{e.ExceptionObject}"); 65 | } 66 | 67 | private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) 68 | { 69 | Console.WriteLine($"error: SourceFusion 未观察的任务异常:{e.Exception}"); 70 | } 71 | 72 | //private static int HandleError(IEnumerable errors) 73 | //{ 74 | // foreach (var error in errors) 75 | // { 76 | // if (error is UnknownOptionError uoe) 77 | // { 78 | // Console.WriteLine($"error: SourceFusion 参数错误:不能识别的参数 {uoe.Token}。"); 79 | // } 80 | // else if (error is MissingRequiredOptionError mroe) 81 | // { 82 | // Console.WriteLine($"error: SourceFusion 参数错误:缺少必需的参数 {mroe.NameInfo.LongName}。"); 83 | // } 84 | // else 85 | // { 86 | // Console.WriteLine($"error: SourceFusion 参数错误:{error}"); 87 | // } 88 | // } 89 | 90 | // return 0; 91 | //} 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace dotnetCampus.SourceFusion.Properties 5 | { 6 | internal static class AssemblyInfo 7 | { 8 | public static string ToolName = 9 | Assembly.GetEntryAssembly().GetCustomAttribute().Product; 10 | 11 | public static string ToolVersion = 12 | Assembly.GetEntryAssembly().GetCustomAttribute() 13 | .InformationalVersion; 14 | 15 | /// 16 | /// 如果需要为类加上 ,则使用此字符串。 17 | /// 18 | public static string GeneratedCodeAttribute = 19 | $@"[System.CodeDom.Compiler.GeneratedCode(""{ToolName}"", ""{ToolVersion}"")]"; 20 | 21 | /// 22 | /// 获取运行时版本号。 23 | /// 24 | private static string RuntimeVersion = 25 | // 以下版本号判断需要区别对待 .NET Core 和 .NET Framework。 26 | // 因为在以下提交中,版本号不再是 4 位了。 27 | // https://github.com/dotnet/runtime/commit/c8809dc3415dfd7a1499d38c23e2a85334d74188 28 | #if NETCOREAPP 29 | Environment.Version.ToString(3); 30 | #else 31 | Environment.Version.ToString(4); 32 | #endif 33 | 34 | /// 35 | /// 获取可以为每一个生成的文件都增加的文件头。 36 | /// 37 | public static string GeneratedCodeComment = 38 | $@"//------------------------------------------------------------------------------ 39 | // 40 | // 此代码由工具生成。 41 | // 运行时版本:{RuntimeVersion} 42 | // 43 | // 对此文件的更改可能会导致不正确的行为,并且如果 44 | // 重新生成代码,这些更改将会丢失。 45 | // 46 | //------------------------------------------------------------------------------ 47 | 48 | #define GENERATED_CODE 49 | 50 | "; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/SourceFusion.Core.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | CSharp72 -------------------------------------------------------------------------------- /src/SourceFusion.Tool/SourceFusion.Tool.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | net6.0;net5.0;netcoreapp3.1;net48 8 | true 9 | codet 10 | dotnetCampus.SourceFusion 11 | dotnetCampus.SourceFusion 12 | dotnetCampus.SourceFusion 13 | 14 | 0.10.7-alpha10 15 | tools 16 | 17 | 使代码在编译期执行,以提升运行时效率。 18 | SourceFusion 提供了一套编译期代码执行框架,以便将运行时可能耗时的操作提前到编译期执行。 19 | 20 | latest 21 | enable 22 | True 23 | 24 | 25 | Major 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/SourceFusion.Tool.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Syntax/CompileTypeVisitor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using dotnetCampus.SourceFusion.CompileTime; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | 8 | namespace dotnetCampus.SourceFusion.Syntax 9 | { 10 | /// 11 | /// 访问编译的所有类和属性 12 | /// 13 | internal class CompileTypeVisitor : CSharpSyntaxRewriter 14 | { 15 | /// 16 | /// 创建文件编译访问 17 | /// 18 | /// 19 | public CompileTypeVisitor(bool visitIntoStructuredTrivia = false) : base(visitIntoStructuredTrivia) 20 | { 21 | } 22 | 23 | /// 24 | /// 所有找到的类型 25 | /// 26 | internal IReadOnlyList Types => _types; 27 | 28 | /// 29 | /// 此文件中的所有命名空间(不含语法性的 using 和分号)。 30 | /// 31 | internal IReadOnlyList UsingNamespaceList => _usingNamespaceList; 32 | 33 | /// 34 | /// 所有程序集级别的特性(Attribute)。 35 | /// 36 | internal IReadOnlyList AssemblyAttributes => _assemblyAttributes; 37 | 38 | /// 39 | public override SyntaxNode VisitUsingDirective(UsingDirectiveSyntax node) 40 | { 41 | if (node.StaticKeyword.Value != null) 42 | { 43 | // 形如 using static System.Math; 44 | var name = node.Name.ToString(); 45 | _usingNamespaceList.Add($"static {name}"); 46 | } 47 | else if (node.Alias != null) 48 | { 49 | // 形如 using Math = System.Math; 50 | var alias = node.Alias.ToFullString(); 51 | var name = node.Name.ToString(); 52 | _usingNamespaceList.Add($"{alias}{name}"); 53 | } 54 | else 55 | { 56 | // 形如 using System; 57 | var name = node.Name.ToString(); 58 | _usingNamespaceList.Add(name); 59 | } 60 | 61 | return base.VisitUsingDirective(node); 62 | } 63 | 64 | public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node) 65 | { 66 | var attributes = GetCompileAttributeList(SyntaxFactory.List(node.ChildNodes().OfType())); 67 | foreach (var attribute in attributes) 68 | { 69 | _assemblyAttributes.Add(attribute); 70 | } 71 | 72 | return base.VisitCompilationUnit(node); 73 | } 74 | 75 | /// 76 | /// 获取命名空间 77 | /// 78 | /// 79 | /// 80 | public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) 81 | { 82 | var nameSyntax = Visit(node.Name); 83 | // 命名空间 84 | _namespace = nameSyntax.ToFullString().Trim(); 85 | 86 | //可能有多个命名空间 87 | return base.VisitNamespaceDeclaration(node); 88 | } 89 | 90 | /// 91 | /// 获取文件命名空间 92 | /// 93 | /// 94 | /// 95 | public override SyntaxNode VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDeclarationSyntax node) 96 | { 97 | var nameSyntax = Visit(node.Name); 98 | // 命名空间 99 | _namespace = nameSyntax.ToFullString().Trim(); 100 | 101 | return base.VisitFileScopedNamespaceDeclaration(node); 102 | } 103 | 104 | /// 105 | /// 获取类 106 | /// 107 | /// 108 | /// 109 | public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) 110 | { 111 | _lastType = GetCompileType(node); 112 | 113 | _types.Add(_lastType); 114 | 115 | return base.VisitClassDeclaration(node); 116 | } 117 | 118 | public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node) 119 | { 120 | _lastType = GetCompileType(node); 121 | 122 | _types.Add(_lastType); 123 | 124 | return base.VisitStructDeclaration(node); 125 | } 126 | 127 | /// 128 | public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) 129 | { 130 | _lastType = GetCompileType(node); 131 | 132 | _types.Add(_lastType); 133 | 134 | return base.VisitInterfaceDeclaration(node); 135 | } 136 | 137 | /// 138 | /// 获取属性 139 | /// 140 | /// 141 | /// 142 | public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node) 143 | { 144 | var attributeLists = GetCompileAttributeList(node.AttributeLists); 145 | 146 | // accessor 就是获取 Set get 147 | 148 | ICompileMethod get = null; 149 | ICompileMethod set = null; 150 | 151 | if (node.AccessorList != null) 152 | { 153 | foreach (var temp in node.AccessorList.Accessors) 154 | { 155 | if (temp.Keyword.Text == "get") 156 | { 157 | get = new CompileMethod(GetCompileAttributeList(temp.AttributeLists), "get"); 158 | } 159 | else if (temp.Keyword.Text == "set") 160 | { 161 | set = new CompileMethod(GetCompileAttributeList(temp.AttributeLists), "set"); 162 | } 163 | } 164 | } 165 | 166 | var compileProperty = new CompileProperty(node.Type.ToString(), attributeLists, node.Identifier.ToString()) 167 | { 168 | SetMethod = set, 169 | GetMethod = get 170 | }; 171 | 172 | foreach (var temp in node.Modifiers) 173 | { 174 | compileProperty.MemberModifiers |= SyntaxKindToMemberModifiers(temp.Kind()); 175 | } 176 | 177 | var type = _lastType; 178 | type.AddCompileProperty(compileProperty); 179 | 180 | return base.VisitPropertyDeclaration(node); 181 | } 182 | 183 | 184 | /// 185 | public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) 186 | { 187 | var type = _lastType; 188 | 189 | type.AddCompileMethod 190 | ( 191 | new CompileMethod(GetCompileAttributeList(node.AttributeLists), node.ToString()) 192 | { 193 | MemberModifiers = SyntaxKindListToMemberModifiers(node.Modifiers.Select(temp => temp.Kind())) 194 | } 195 | ); 196 | 197 | return base.VisitMethodDeclaration(node); 198 | } 199 | 200 | /// 201 | public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node) 202 | { 203 | var type = _lastType; 204 | 205 | type.AddCompileField 206 | ( 207 | new CompileField(node.ToString(), GetCompileAttributeList(node.AttributeLists)) 208 | { 209 | MemberModifiers = SyntaxKindListToMemberModifiers(node.Modifiers.Select(temp => temp.Kind())) 210 | } 211 | ); 212 | 213 | return base.VisitFieldDeclaration(node); 214 | } 215 | 216 | private readonly List _types = new List(); 217 | 218 | private readonly List _usingNamespaceList = new List(); 219 | 220 | private readonly List _assemblyAttributes = new List(); 221 | 222 | private CompileType _lastType; 223 | private string _namespace; 224 | 225 | private CompileType GetCompileType(TypeDeclarationSyntax type) 226 | { 227 | // 获取类型的名称。 228 | var identifier = type.Identifier.ValueText; 229 | 230 | // 对于内部类,将父类的名称叠加到前面。 231 | var parent = type.Parent; 232 | while (parent is ClassDeclarationSyntax cds) 233 | { 234 | identifier = $"{cds.Identifier.ValueText}.{identifier}"; 235 | parent = cds.Parent; 236 | } 237 | 238 | // 添加基类列表。 239 | var baseTypeList = new List(); 240 | if (type.BaseList != null) 241 | { 242 | foreach (var temp in type.BaseList.Types) 243 | { 244 | var name = temp.Type.ToString(); 245 | baseTypeList.Add(name); 246 | } 247 | } 248 | 249 | //获取类的特性 250 | var attributeLists = GetCompileAttributeList(type.AttributeLists); 251 | 252 | return new CompileType 253 | ( 254 | identifier, 255 | _namespace, 256 | attributeLists, 257 | baseTypeList, 258 | _usingNamespaceList 259 | ); 260 | } 261 | 262 | private ICompileAttribute[] GetCompileAttributeList(SyntaxList attributeList) 263 | { 264 | if (!attributeList.Any()) 265 | { 266 | return new ICompileAttribute[0]; 267 | } 268 | 269 | return VisitList(attributeList) 270 | .SelectMany(x => x.Attributes) 271 | .Select(x => new CompileAttribute( 272 | x.Name.ToFullString(), 273 | x.ArgumentList?.Arguments.Select(a => 274 | { 275 | // Attribute 中形如 Property = "Value" 的语法。 276 | var property = a.NameEquals?.Name.Identifier.ToString(); 277 | var value = a.Expression.ToString(); 278 | return new KeyValuePair(property, value); 279 | }))) 280 | .Cast().ToArray(); 281 | } 282 | 283 | private MemberModifiers SyntaxKindListToMemberModifiers(IEnumerable kind) 284 | { 285 | var modifiers = MemberModifiers.Unset; 286 | foreach (var temp in kind) 287 | { 288 | modifiers |= SyntaxKindToMemberModifiers(temp); 289 | } 290 | 291 | return modifiers; 292 | } 293 | 294 | private MemberModifiers SyntaxKindToMemberModifiers(SyntaxKind kind) 295 | { 296 | var kindList = new Dictionary 297 | { 298 | {SyntaxKind.PublicKeyword, MemberModifiers.Public}, 299 | {SyntaxKind.PrivateKeyword, MemberModifiers.Private}, 300 | {SyntaxKind.ProtectedKeyword, MemberModifiers.Protected}, 301 | {SyntaxKind.InternalKeyword, MemberModifiers.Internal} 302 | }; 303 | 304 | if (kindList.ContainsKey(kind)) 305 | { 306 | return kindList[kind]; 307 | } 308 | 309 | return MemberModifiers.Unset; 310 | } 311 | } 312 | } -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Syntax/PlaceholderVisitor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using dotnetCampus.SourceFusion.Core; 4 | using dotnetCampus.SourceFusion.Templates; 5 | using Microsoft.CodeAnalysis; 6 | using Microsoft.CodeAnalysis.CSharp; 7 | using Microsoft.CodeAnalysis.CSharp.Syntax; 8 | using Microsoft.CodeAnalysis.Text; 9 | 10 | namespace dotnetCampus.SourceFusion.Syntax 11 | { 12 | /// 13 | /// 包含从语法树中解析 类型调用的语法树访问者。 14 | /// 15 | internal class PlaceholderVisitor : CSharpSyntaxRewriter 16 | { 17 | /// 18 | /// 在调用 方法后,可通过此属性获取从语法树中解析得到的占位符()信息。 19 | /// 20 | internal IReadOnlyCollection Placeholders => _placeholders; 21 | 22 | /// 23 | /// 在调用 方法后,可通过此属性获取此文件的所有命名空间 using 信息。 24 | /// 25 | internal IReadOnlyCollection<(string @namespace, TextSpan span)> Namespaces => _namespaces; 26 | 27 | public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node) 28 | { 29 | if (node.Expression is MemberAccessExpressionSyntax memberAccessExpression) 30 | { 31 | // 取出 Placeholder.MethodName 片段。 32 | var expression = memberAccessExpression.Expression.ToString(); 33 | if (expression == nameof(Placeholder) && memberAccessExpression.Name is GenericNameSyntax genericName) 34 | { 35 | var textSpan = node.FullSpan; 36 | 37 | // Placeholder 已确认,调用泛型方法已确认。 38 | var methodName = genericName.Identifier.ToString(); 39 | 40 | switch (methodName) 41 | { 42 | case nameof(Placeholder.Array): 43 | { 44 | var returnType = ((IdentifierNameSyntax) genericName.TypeArgumentList.Arguments.First()) 45 | .Identifier.ToString(); 46 | if (node.ArgumentList.Arguments.FirstOrDefault()?.Expression 47 | is SimpleLambdaExpressionSyntax lambdaExpression) 48 | { 49 | // 参数列表为 Lambda 表达式已确认。 50 | var parameter = lambdaExpression.Parameter.Identifier.ToString(); 51 | var body = lambdaExpression.Body.ToString(); 52 | _placeholders.Add(new ArrayPlaceholder(textSpan, methodName, parameter, body, returnType)); 53 | } 54 | 55 | break; 56 | } 57 | case nameof(Placeholder.AttributedTypes): 58 | { 59 | var genericTypes = genericName.TypeArgumentList.Arguments 60 | .Select(x => x.ToString()).ToList(); 61 | // 这里的 2 是 AttributedTypes 方法的两个泛型参数,只要不是两个泛型参数就是错误。 62 | if (genericTypes.Count != 2) 63 | { 64 | throw new CompilingException("Placeholder.AttributedTypes 必须有两个类型参数。"); 65 | } 66 | 67 | var baseType = genericTypes[0]; 68 | var attributeType = genericTypes[1]; 69 | _placeholders.Add(new AttributedTypesPlaceholder(textSpan, baseType, attributeType, 70 | _isNextPlaceholderAssigned)); 71 | 72 | break; 73 | } 74 | default: 75 | throw new CompilingException($"Placeholder 中不包含名为 {methodName} 的方法。"); 76 | } 77 | 78 | _isNextPlaceholderAssigned = false; 79 | } 80 | } 81 | 82 | return base.VisitInvocationExpression(node); 83 | } 84 | 85 | public override SyntaxNode VisitEqualsValueClause(EqualsValueClauseSyntax node) 86 | { 87 | if (node.Value is InvocationExpressionSyntax invocation 88 | && invocation.Expression is MemberAccessExpressionSyntax memberAccessExpression 89 | && memberAccessExpression.Expression.ToString() == nameof(Placeholder)) 90 | { 91 | // 当这个赋值操作的等号右边是 Placeholder 的时候,标记此时正在使用 Placeholder 赋值。 92 | _isNextPlaceholderAssigned = true; 93 | } 94 | 95 | return base.VisitEqualsValueClause(node); 96 | } 97 | 98 | public override SyntaxNode VisitUsingDirective(UsingDirectiveSyntax node) 99 | { 100 | _namespaces.Add((node.ToString().Replace("using ", "").Replace(";", "").Trim(), node.Span)); 101 | return base.VisitUsingDirective(node); 102 | } 103 | 104 | /// 105 | /// 在 内部使用,用于在语法树访问期间添加占位符信息。 106 | /// 107 | private readonly List _placeholders = new List(); 108 | 109 | /// 110 | /// 在 内部使用,用于修改命名空间信息。 111 | /// 112 | private readonly List<(string @namespace, TextSpan span)> _namespaces = new List<(string, TextSpan)>(); 113 | 114 | /// 115 | /// 在查找的过程中,如果发现了一个 被赋值给了某个对象或者被用作其他用途,那么就进行标记。 116 | /// 这样,在实际 的解析过程中就可以使用到这个值进行某些特殊的判断。 117 | /// 118 | private bool _isNextPlaceholderAssigned; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Syntax/SyntaxTreeCompilingExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using dotnetCampus.SourceFusion.Core; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CSharp; 9 | 10 | namespace dotnetCampus.SourceFusion.Syntax 11 | { 12 | /// 13 | /// 包含语法树编译相关的扩展方法。 14 | /// 15 | public static class SyntaxTreeCompilingExtensions 16 | { 17 | /// 18 | /// 编译指定语法树中的源码,以获取其中定义的类型。 19 | /// 20 | /// 文件中已发现的所有类型。 21 | public static Type[] Compile(this SyntaxTree syntaxTree, IEnumerable references, string assemblyName) 22 | { 23 | var compilation = CSharpCompilation.Create(assemblyName, new[] {syntaxTree}, 24 | options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) 25 | .AddReferences(AppDomain.CurrentDomain.GetAssemblies() 26 | .Where(x => !string.IsNullOrEmpty(x.Location)) 27 | .Select(x => MetadataReference.CreateFromFile(x.Location))); 28 | 29 | using (var ms = new MemoryStream()) 30 | { 31 | var result = compilation.Emit(ms); 32 | 33 | if (result.Success) 34 | { 35 | ms.Seek(0, SeekOrigin.Begin); 36 | var assembly = Assembly.Load(ms.ToArray()); 37 | return assembly.GetTypes(); 38 | } 39 | 40 | var failures = result.Diagnostics.Where(diagnostic => 41 | diagnostic.IsWarningAsError || 42 | diagnostic.Severity == DiagnosticSeverity.Error); 43 | throw new CompilingException(failures.Select(x => $"{x.Id}: {x.GetMessage()}")); 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Templates/ArrayPlaceholder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Text; 5 | using dotnetCampus.SourceFusion.Core; 6 | using dotnetCampus.SourceFusion.Syntax; 7 | using Microsoft.CodeAnalysis.CSharp; 8 | using Microsoft.CodeAnalysis.Text; 9 | 10 | namespace dotnetCampus.SourceFusion.Templates 11 | { 12 | /// 13 | /// 包含 占位符在语法树中的信息。 14 | /// 15 | internal class ArrayPlaceholder : PlaceholderInfo 16 | { 17 | /// 18 | /// 初始化 的新实例。 19 | /// 20 | /// 占位符在源代码文件中的文本区间。 21 | /// 此占位符调用的 类型中的方法名称。 22 | /// 占位符中需要在编译期间执行的方法参数名称。 23 | /// 占位符中需要在编译期间执行的方法体。 24 | /// 占位符中需要返回集合的返回值类型。 25 | internal ArrayPlaceholder(TextSpan span, string methodName, 26 | string invocationParameterName, string invocationBody, string returnType) 27 | : base(span) 28 | { 29 | MethodName = methodName; 30 | InvocationParameterName = invocationParameterName; 31 | InvocationBody = invocationBody; 32 | ReturnType = returnType; 33 | } 34 | 35 | /// 36 | /// 获取源代码文件中占位符调用的是 类型中的哪个方法。 37 | /// 38 | public string MethodName { get; } 39 | 40 | /// 41 | /// 获取占位符中需要在编译期间执行的方法参数名称。 42 | /// 43 | public string InvocationParameterName { get; } 44 | 45 | /// 46 | /// 获取占位符中需要在编译期间执行的方法体。 47 | /// 48 | public string InvocationBody { get; } 49 | 50 | /// 51 | /// 获取占位符的返回值单项类型。 52 | /// 53 | public string ReturnType { get; } 54 | 55 | public override string Fill(CompilingContext context) 56 | { 57 | var lambda = Compile(context); 58 | var codeSnippet = lambda(context); 59 | codeSnippet = $@"new {ReturnType}[] 60 | {{ 61 | {codeSnippet}}}"; 62 | return codeSnippet; 63 | } 64 | 65 | private const string PlaceholderClassName = "PlaceholderLambdaRunner"; 66 | 67 | private static readonly string ClassTemplate = $@"using System; 68 | using System.Linq; 69 | using dotnetCampus.SourceFusion; 70 | using dotnetCampus.SourceFusion.CompileTime; 71 | 72 | public static class {PlaceholderClassName} 73 | {{ 74 | public static string InvokePlaceholder(ICompilingContext {{parameterName}}) 75 | {{body}} 76 | }} 77 | "; 78 | 79 | /// 80 | /// 将占位符中的在编译期执行的 Lambda 表达式编译成可执行函数。 81 | /// 82 | /// 用于调用占位符中编译期可执行代码的委托。 83 | private Func Compile(CompilingContext context) 84 | { 85 | var builder = new StringBuilder(ClassTemplate) 86 | .Replace("{parameterName}", InvocationParameterName) 87 | .Replace("{body}", InvocationBody); 88 | var syntaxTree = CSharpSyntaxTree.ParseText(builder.ToString()); 89 | var types = syntaxTree.Compile(context.References, "PlaceholderInvoking.g"); 90 | var placeholderImpl = types.First(x => x.Name == PlaceholderClassName); 91 | var method = placeholderImpl.GetMethod("InvokePlaceholder"); 92 | Debug.Assert(method != null, nameof(method) + " != null"); 93 | var func = (Func) method.CreateDelegate(typeof(Func)); 94 | return func; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Templates/AttributedTypesPlaceholder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using dotnetCampus.SourceFusion.CompileTime; 4 | using dotnetCampus.SourceFusion.Core; 5 | using Microsoft.CodeAnalysis.Text; 6 | 7 | namespace dotnetCampus.SourceFusion.Templates 8 | { 9 | /// 10 | /// 包含 占位符在语法树中的信息。 11 | /// 12 | internal class AttributedTypesPlaceholder : PlaceholderInfo 13 | { 14 | private readonly string _baseType; 15 | private readonly string _attributeType; 16 | 17 | /// 18 | /// 获取或设置用户是否使用了 的返回值中提供的额外元数据。 19 | /// 如果使用了额外的元数据,那么就必须生成这些元数据,否则就不要生成这些元数据。 20 | /// 21 | private readonly bool _useMetadata; 22 | 23 | public AttributedTypesPlaceholder(TextSpan span, string baseType, string attributeType, bool useMetadata) 24 | : base(span) 25 | { 26 | _baseType = baseType; 27 | _attributeType = attributeType; 28 | _useMetadata = useMetadata; 29 | } 30 | 31 | public override string Fill(CompilingContext context) 32 | { 33 | var collectedItems = CollectAttributedTypes(context); 34 | return _useMetadata 35 | // 如果使户使用了元数据,那么就生成更多的信息供用户调用。 36 | ? $@"new System.Collections.Generic.List<(Type Type, {_attributeType} Attribute, Func<{_baseType}> Creator)> 37 | {{ 38 | {string.Join(@", 39 | ", collectedItems.Select(item => $@"(typeof({item.typeName}), {item.attributeCreator}, () => new {item.typeName}())"))} 40 | }}" 41 | // 如果用户没有使用元数据,那么就直接返回类型声明的返回值。 42 | : $@"new (Type, {_attributeType})[] 43 | {{ 44 | {string.Join(@", 45 | ", collectedItems.Select(item => $@"(typeof({item.typeName}), {item.attributeCreator})"))} 46 | }}"; 47 | } 48 | 49 | private IEnumerable<(string typeName, string attributeCreator)> CollectAttributedTypes( 50 | ICompilingContext context) 51 | { 52 | var collected = 53 | from type in context.Assembly.GetTypes() 54 | let attribute = (CompileAttribute) type.Attributes.FirstOrDefault(a => a.Match(_attributeType)) 55 | where attribute != null 56 | select (type, attribute); 57 | 58 | foreach (var (type, attribute) in collected) 59 | { 60 | if (attribute.GetValues().Union(attribute.GetProperties().Select(x => x.value)) 61 | .Any(x => x.Contains("typeof"))) 62 | { 63 | RequireNamespaces(type.UsingNamespaceList.Union(new[] {type.Namespace})); 64 | } 65 | 66 | yield return (type.FullName, ToCreator(attribute)); 67 | } 68 | } 69 | 70 | private string ToCreator(CompileAttribute attribute) 71 | { 72 | return $@"new {_attributeType}({string.Join(", ", attribute.GetValues())}) 73 | {{ 74 | {string.Join(@", 75 | ", attribute.GetProperties().Select(x => $"{x.property} = {x.value}"))} 76 | }}"; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Templates/PlaceholderInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using dotnetCampus.SourceFusion.Core; 3 | using Microsoft.CodeAnalysis.Text; 4 | 5 | namespace dotnetCampus.SourceFusion.Templates 6 | { 7 | internal abstract class PlaceholderInfo 8 | { 9 | private readonly List _requiredNamespaces = new List(); 10 | 11 | protected PlaceholderInfo(TextSpan span) 12 | { 13 | Span = span; 14 | } 15 | 16 | /// 17 | /// 获取占位符在源代码文件中的文本区间。 18 | /// 19 | public TextSpan Span { get; } 20 | 21 | public IReadOnlyCollection RequiredNamespaces => _requiredNamespaces; 22 | 23 | public abstract string Fill(CompilingContext context); 24 | 25 | protected void RequireNamespaces(IEnumerable namespaces) 26 | { 27 | foreach (var ns in namespaces) 28 | { 29 | if (!_requiredNamespaces.Contains(ns)) 30 | { 31 | _requiredNamespaces.Add(ns); 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Templates/TemplateTransformer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using dotnetCampus.SourceFusion.CompileTime; 9 | using dotnetCampus.SourceFusion.Core; 10 | using dotnetCampus.SourceFusion.Properties; 11 | using dotnetCampus.SourceFusion.Syntax; 12 | using Microsoft.CodeAnalysis; 13 | using Microsoft.CodeAnalysis.CSharp; 14 | 15 | namespace dotnetCampus.SourceFusion.Templates 16 | { 17 | internal class TemplateTransformer 18 | { 19 | private readonly ProjectCompilingContext _context; 20 | 21 | private readonly Regex _usingRegex = new Regex(@"using\sdotnetCampus\.SourceFusion[\.\w]*;\r?\n", 22 | RegexOptions.Compiled | RegexOptions.CultureInvariant); 23 | 24 | private readonly Regex _projectPropertyRegex = new Regex(@"\$\((?[_\w]+)\)", 25 | RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); 26 | 27 | internal TemplateTransformer(ProjectCompilingContext context) 28 | { 29 | _context = context; 30 | _workingFolder = context.WorkingFolder; 31 | _generatedCodeFolder = context.GeneratedCodeFolder; 32 | _assembly = context.Assembly; 33 | _compilingContext = new CompilingContext(context.Assembly); 34 | } 35 | 36 | public IEnumerable Transform() 37 | { 38 | return from assemblyFile in _assembly.Files 39 | let compileType = assemblyFile.Types.FirstOrDefault() 40 | where compileType?.Attributes.Any(x => x.Match()) is true 41 | select TransformTemplate(assemblyFile); 42 | } 43 | 44 | /// 45 | /// 获取编译期的程序集。 46 | /// 47 | private readonly CompileAssembly _assembly; 48 | 49 | /// 50 | /// 获取编译期执行上下文。 51 | /// 52 | private readonly CompilingContext _compilingContext; 53 | 54 | /// 55 | /// 获取中间文件的生成路径(文件夹,相对路径)。 56 | /// 57 | private readonly string _generatedCodeFolder; 58 | 59 | /// 60 | /// 获取转换源码的工作路径。 61 | /// 62 | private readonly string _workingFolder; 63 | 64 | private string TransformTemplate(CompileFile assemblyFile) 65 | { 66 | // 读取文件,去掉非期望字符。 67 | var originalText = File.ReadAllText(assemblyFile.FullName); 68 | originalText = originalText.Replace("[CompileTimeTemplate]", AssemblyInfo.GeneratedCodeAttribute); 69 | originalText = _usingRegex.Replace(originalText, ""); 70 | foreach (Match match in _projectPropertyRegex.Matches(originalText)) 71 | { 72 | var propertyName = match.Groups["propertyName"].Value; 73 | var propertyValue = _context.GetProperty(propertyName); 74 | originalText = originalText.Replace(match.Value, propertyValue); 75 | } 76 | 77 | // 解析其语法树。 78 | var symbols = _context.PreprocessorSymbols.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries); 79 | var syntaxTree = CSharpSyntaxTree.ParseText(originalText, new CSharpParseOptions( 80 | LanguageVersion.Latest, DocumentationMode.None, SourceCodeKind.Regular, symbols)); 81 | 82 | // 访问语法节点。 83 | var visitor = new PlaceholderVisitor(); 84 | visitor.Visit(syntaxTree.GetRoot()); 85 | 86 | // 初始化字符串。 87 | var builder = new StringBuilder(AssemblyInfo.GeneratedCodeComment); 88 | var namespaceIndex = builder.Length; 89 | var currentTextPosition = 0; 90 | 91 | // 替换占位符。 92 | var placeholders = visitor.Placeholders; 93 | foreach (var placeholder in placeholders) 94 | { 95 | var actualText = placeholder.Fill(_compilingContext); 96 | 97 | builder.Append( 98 | originalText.Substring(currentTextPosition, placeholder.Span.Start - currentTextPosition)); 99 | builder.Append(actualText); 100 | currentTextPosition = placeholder.Span.End; 101 | } 102 | 103 | // 把文件剩余的部分拼接起来。 104 | builder.Append(originalText.Substring(currentTextPosition)); 105 | 106 | // 去掉原来的命名空间,添加上新补充的命名空间。 107 | var requiredNamespaces = string.Join(Environment.NewLine, placeholders 108 | .SelectMany(x => x.RequiredNamespaces) 109 | .Union(visitor.Namespaces.Select(x => x.@namespace)) 110 | .Distinct() 111 | .OrderBy(s => s, new SystemFirstNamespaceComparer()) 112 | .Select(x => $"using {x};")); 113 | var namespaceStart = visitor.Namespaces.Min(x => x.span.Start); 114 | var namespaceEnd = visitor.Namespaces.Max(x => x.span.End); 115 | builder.Remove(namespaceIndex, namespaceEnd - namespaceStart); 116 | builder.Insert(namespaceIndex, requiredNamespaces); 117 | 118 | // 将新的代码写入到文件。 119 | var targetText = builder.ToString(); 120 | var fileName = Path.GetFileNameWithoutExtension(assemblyFile.FullName); 121 | var targetFile = Path.Combine(_generatedCodeFolder, $"{fileName}.g.cs"); 122 | File.WriteAllText(targetFile, targetText); 123 | 124 | return assemblyFile.FullName; 125 | } 126 | 127 | internal class SystemFirstNamespaceComparer : IComparer 128 | { 129 | public int Compare(string x, string y) 130 | { 131 | if (x is null && y is null) 132 | { 133 | return 0; 134 | } 135 | 136 | if (x is null) 137 | { 138 | return -1; 139 | } 140 | 141 | if (y is null) 142 | { 143 | return 1; 144 | } 145 | 146 | if (x.StartsWith("System") && y.StartsWith("System")) 147 | { 148 | return string.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreCase); 149 | } 150 | 151 | if (x.StartsWith("System")) 152 | { 153 | return -1; 154 | } 155 | 156 | if (y.StartsWith("System")) 157 | { 158 | return 1; 159 | } 160 | 161 | return string.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreCase); 162 | } 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Transforming/CodeTransformer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using dotnetCampus.SourceFusion.CompileTime; 8 | using dotnetCampus.SourceFusion.Core; 9 | using dotnetCampus.SourceFusion.Properties; 10 | 11 | namespace dotnetCampus.SourceFusion.Transforming 12 | { 13 | /// 14 | /// 为编译时提供源码转换。 15 | /// 16 | internal class CodeTransformer 17 | { 18 | /// 19 | /// 创建用于转换源码的 。 20 | /// 21 | /// 项目工作区上下文信息。 22 | internal CodeTransformer(ProjectCompilingContext context) 23 | { 24 | _workingFolder = context.WorkingFolder; 25 | _generatedCodeFolder = context.GeneratedCodeFolder; 26 | _assembly = context.Assembly; 27 | } 28 | 29 | /// 30 | /// 获取编译期的程序集。 31 | /// 32 | private readonly CompileAssembly _assembly; 33 | 34 | /// 35 | /// 获取中间文件的生成路径(文件夹,相对路径)。 36 | /// 37 | private readonly string _generatedCodeFolder; 38 | 39 | /// 40 | /// 获取转换源码的工作路径。 41 | /// 42 | private readonly string _workingFolder; 43 | 44 | /// 45 | /// 执行 的转换代码的方法。 46 | /// 47 | /// 此代码文件的文件路径。 48 | /// 编译好的代码转换类实例。 49 | private IEnumerable InvokeCodeTransformer(string codeFile, IPlainCodeTransformer transformer) 50 | { 51 | var attribute = transformer.GetType().GetCustomAttribute(); 52 | 53 | var sourceFiles = attribute.SourceFiles 54 | .Select(x => Path.GetFullPath(Path.Combine(x.StartsWith("/") || x.StartsWith("\\") 55 | ? _workingFolder 56 | : Path.GetDirectoryName(codeFile), x))); 57 | foreach (var sourceFile in sourceFiles) 58 | { 59 | var fileName = Path.GetFileNameWithoutExtension(sourceFile); 60 | var extension = attribute.TargetType == FileType.Compile ? ".cs" : Path.GetExtension(sourceFile); 61 | 62 | var text = File.ReadAllText(sourceFile); 63 | for (var i = 0; i < attribute.RepeatCount; i++) 64 | { 65 | var transformedText = transformer.Transform(text, new TransformingContext(i)); 66 | var targetFile = Path.Combine(_generatedCodeFolder, $"{fileName}.g.{i}{extension}"); 67 | File.WriteAllText(targetFile, AssemblyInfo.GeneratedCodeComment + transformedText, Encoding.UTF8); 68 | } 69 | 70 | if (!attribute.KeepSourceFiles) 71 | { 72 | yield return sourceFile; 73 | } 74 | } 75 | } 76 | 77 | /// 78 | /// 执行代码转换。这将开始从所有的编译文件中搜索 ,并执行其转换方法。 79 | /// 80 | internal IEnumerable Transform() 81 | { 82 | foreach (var assemblyFile in _assembly.Files) 83 | { 84 | var compileType = assemblyFile.Types.FirstOrDefault(); 85 | if 86 | ( 87 | compileType != null 88 | && compileType.Attributes 89 | .Any(x => x.Match()) 90 | ) 91 | { 92 | var type = assemblyFile.Compile().First(); 93 | var transformer = (IPlainCodeTransformer) Activator.CreateInstance(type); 94 | var excludedFiles = InvokeCodeTransformer(assemblyFile.FullName, transformer); 95 | yield return assemblyFile.FullName; 96 | foreach (var excludedFile in excludedFiles) 97 | { 98 | yield return excludedFile; 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Utils/ILogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace dotnetCampus.SourceFusion.Utils 4 | { 5 | public interface ILogger 6 | { 7 | void Message(string text, ConsoleColor? color = null); 8 | void Warning(string text); 9 | void Error(string text); 10 | void Error(Exception exception); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Utils/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using dotnetCampus.SourceFusion.Core; 4 | 5 | namespace dotnetCampus.SourceFusion.Utils 6 | { 7 | internal sealed class Logger : ILogger, IDisposable 8 | { 9 | private readonly Stopwatch _watch; 10 | private bool _isDisposed; 11 | 12 | public Logger() 13 | { 14 | _watch = new Stopwatch(); 15 | _watch.Start(); 16 | } 17 | 18 | public void Message(string text, ConsoleColor? color = null) 19 | { 20 | if (color == null) 21 | { 22 | Console.WriteLine(text); 23 | } 24 | else 25 | { 26 | var oldColor = Console.ForegroundColor; 27 | Console.ForegroundColor = color.Value; 28 | Console.WriteLine(text); 29 | Console.ForegroundColor = oldColor; 30 | } 31 | } 32 | 33 | public void Time(string progressAbove) 34 | { 35 | 36 | } 37 | 38 | public void Warning(string text) 39 | { 40 | Message($"warning: {text}", ConsoleColor.Yellow); 41 | } 42 | 43 | public void Error(string text) 44 | { 45 | Message($"error: {text}", ConsoleColor.Red); 46 | } 47 | 48 | public void Error(Exception exception) 49 | { 50 | if (exception is CompilingException ce) 51 | { 52 | foreach (var error in ce.Errors) 53 | { 54 | Error(error); 55 | } 56 | } 57 | else if (exception is AggregateException ae) 58 | { 59 | ae.Flatten(); 60 | foreach (var ie in ae.InnerExceptions) 61 | { 62 | Error(ie); 63 | } 64 | } 65 | else 66 | { 67 | Error(exception); 68 | } 69 | } 70 | 71 | internal TimeSpan Elapsed => _watch.Elapsed; 72 | 73 | ~Logger() 74 | { 75 | Dispose(false); 76 | } 77 | 78 | public void Dispose() 79 | { 80 | Dispose(true); 81 | GC.SuppressFinalize(this); 82 | } 83 | 84 | private void Dispose(bool isDisposing) 85 | { 86 | if (_isDisposed) 87 | { 88 | return; 89 | } 90 | 91 | if (isDisposing) 92 | { 93 | _watch.Stop(); 94 | } 95 | 96 | _isDisposed = true; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/SourceFusion.Tool/Utils/PathEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace dotnetCampus.SourceFusion.Utils 5 | { 6 | internal static class PathEx 7 | { 8 | /// 9 | /// Creates a relative path from one file or folder to another. 10 | /// 11 | /// Contains the directory that defines the start of the relative path. 12 | /// Contains the path that defines the endpoint of the relative path. 13 | /// The relative path from the start directory to the end path or toPath if the paths are not related. 14 | /// 15 | /// 16 | /// 17 | internal static string MakeRelativePath(string fromPath, string toPath) 18 | { 19 | if (string.IsNullOrEmpty(fromPath)) throw new ArgumentNullException(nameof(fromPath)); 20 | if (string.IsNullOrEmpty(toPath)) throw new ArgumentNullException(nameof(toPath)); 21 | 22 | var fromUri = new Uri(fromPath); 23 | var toUri = new Uri(toPath); 24 | 25 | if (fromUri.Scheme != toUri.Scheme) 26 | { 27 | // 不是同一种路径,无法转换成相对路径。 28 | return toPath; 29 | } 30 | 31 | if (fromUri.Scheme.Equals("file", StringComparison.InvariantCultureIgnoreCase) 32 | && !fromPath.EndsWith("/") && !fromPath.EndsWith("\\")) 33 | { 34 | // 如果是文件系统,则视来源路径为文件夹。 35 | fromUri = new Uri(fromPath + Path.DirectorySeparatorChar); 36 | } 37 | 38 | var relativeUri = fromUri.MakeRelativeUri(toUri); 39 | var relativePath = Uri.UnescapeDataString(relativeUri.ToString()); 40 | 41 | if (toUri.Scheme.Equals("file", StringComparison.InvariantCultureIgnoreCase)) 42 | { 43 | relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); 44 | } 45 | 46 | return relativePath; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/SourceFusion/Attributes_/CompileTimeCodeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.Contracts; 3 | 4 | namespace dotnetCampus.SourceFusion 5 | { 6 | /// 7 | /// 标记此类型仅在编译期间使用。注意:编译完毕后此类型将从目标程序集中消失。 8 | /// 9 | [AttributeUsage(AttributeTargets.Class, Inherited = false)] 10 | public sealed class CompileTimeCodeAttribute : Attribute 11 | { 12 | /// 13 | /// 获取要转换的所有文件。 14 | /// 15 | public string[] SourceFiles { get; } 16 | 17 | /// 18 | /// 获取要转换的类型(这是用于辅助寻找 的临时属性)。 19 | /// 20 | internal Type SourceType { get; } 21 | 22 | /// 23 | /// 获取或设置转换的目标文件的编译类型。 默认值是自动,即与原类型一致。 24 | /// 25 | public FileType TargetType { get; set; } = FileType.Auto; 26 | 27 | /// 28 | /// 获取或设置代码转换的重复次数。指定多次可以让同一份代码进行多次转换,生成多份不同的目标文件。 29 | /// 30 | public int RepeatCount 31 | { 32 | get => _repeatCount; 33 | set => _repeatCount = value > 0 34 | ? value 35 | : throw new ArgumentOutOfRangeException(nameof(value), 36 | $"{nameof(RepeatCount)} should be more than zero."); 37 | } 38 | 39 | /// 40 | /// 获取或设置一个值,该值指示在转换代码之后,被转换的文件是否还保留。默认不保留。 41 | /// 42 | public bool KeepSourceFiles { get; set; } = false; 43 | 44 | /// 45 | /// 编译时将转换 类型。 46 | /// 47 | public CompileTimeCodeAttribute(Type sourceType) => SourceType = sourceType; 48 | 49 | /// 50 | /// 编译时将转换 文件到新的文件。 51 | /// 52 | public CompileTimeCodeAttribute(params string[] sourceFileNames) => SourceFiles = sourceFileNames; 53 | 54 | [ContractPublicPropertyName(nameof(RepeatCount))] 55 | private int _repeatCount = 1; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/SourceFusion/Attributes_/CompileTimeMethodAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace dotnetCampus.SourceFusion 4 | { 5 | /// 6 | /// 7 | /// 标记方法为编译期间才执行的方法,此方法在编译后将移除。 8 | /// 9 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 10 | internal sealed class CompileTimeMethodAttribute : Attribute 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SourceFusion/Attributes_/CompileTimeTemplateAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace dotnetCampus.SourceFusion 4 | { 5 | /// 6 | /// 表示一个类是一个类型模板,类中的占位符将在编译后被替换为占位符内实际计算出的代码。 7 | /// 8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] 9 | public class CompileTimeTemplateAttribute : Attribute 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/SourceFusion/CompileCodeSnippet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using System.Text; 5 | 6 | namespace dotnetCampus.SourceFusion 7 | { 8 | /// 9 | /// 为编译期生成代码提供代码片段生成功能。 10 | /// 11 | public class CompileCodeSnippet 12 | { 13 | /// 14 | /// 获取生成的代码片段。 15 | /// 16 | private readonly string _snippet; 17 | 18 | /// 19 | /// 创建一个编译期代码片段。 20 | /// 其中参数 将按 序列个数重复多次,并将 “{0}” 替换为集合中的一项。 21 | /// 例如 为 “new {0}(), ”, 为 { "Foo", "Bar" }; 22 | /// 那么最终生成的代码片段为 “new Foo(), new Bar(), ”。 23 | /// 24 | /// 代码片段模板,例如 “new {0}(), ”。 25 | /// 用于填充代码模板的集合,例如 { "Foo", "Bar" }。 26 | public CompileCodeSnippet(string template, IEnumerable placeholders) 27 | => _snippet = BuildSnippet(placeholders, v => string.Format(template, v)); 28 | 29 | public CompileCodeSnippet(string template, IEnumerable<(string, string)> placeholders) 30 | => _snippet = BuildSnippet(placeholders, v => string.Format(template, v.Item1, v.Item2)); 31 | 32 | public CompileCodeSnippet(string template, IEnumerable<(string, string, string)> placeholders) 33 | => _snippet = BuildSnippet(placeholders, v => string.Format(template, v.Item1, v.Item2, v.Item3)); 34 | 35 | public CompileCodeSnippet(string template, IEnumerable<(string, string, string, string)> placeholders) 36 | => _snippet = BuildSnippet(placeholders, v => string.Format(template, v.Item1, v.Item2, v.Item3, v.Item4)); 37 | 38 | public CompileCodeSnippet(string template, IEnumerable<(string, string, string, string, string)> placeholders) 39 | => _snippet = BuildSnippet(placeholders, v => string.Format(template, v.Item1, v.Item2, v.Item3, v.Item4, v.Item5)); 40 | 41 | public CompileCodeSnippet(string template, IEnumerable<(string, string, string, string, string, string)> placeholders) 42 | => _snippet = BuildSnippet(placeholders, v => string.Format(template, v.Item1, v.Item2, v.Item3, v.Item4, v.Item5, v.Item6)); 43 | 44 | public CompileCodeSnippet(string template, IEnumerable<(string, string, string, string, string, string, string)> placeholders) 45 | => _snippet = BuildSnippet(placeholders, v => string.Format(template, v.Item1, v.Item2, v.Item3, v.Item4, v.Item5, v.Item6, v.Item7)); 46 | 47 | /// 48 | /// 输出代码片段。 49 | /// 50 | [Pure] 51 | public override string ToString() => _snippet; 52 | 53 | /// 54 | /// 隐式转换为代码片段字符串。 55 | /// 56 | /// 代码片段的实例。 57 | public static implicit operator string(CompileCodeSnippet snippet) 58 | { 59 | return snippet._snippet; 60 | } 61 | 62 | private static string BuildSnippet(IEnumerable placeholders, Func buildLine) 63 | { 64 | var builder = new StringBuilder(); 65 | 66 | foreach (var placeholder in placeholders) 67 | { 68 | builder.Append(buildLine(placeholder)); 69 | } 70 | 71 | return builder.ToString(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/SourceFusion/CompileTime/ICompileAssembly.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.CompileTime 2 | { 3 | public interface ICompileAssembly 4 | { 5 | ICompileType[] GetTypes(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/SourceFusion/CompileTime/ICompileAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.CompileTime 2 | { 3 | public interface ICompileAttribute 4 | { 5 | /// 6 | /// 获取此特性在代码中书写的名称。 7 | /// 8 | string Name { get; } 9 | 10 | /// 11 | /// 判断当前的 是否匹配指定的特性名称。 12 | /// 由于特性的名称可能包含 Attribute 后缀或命名空间前缀,所以使用此方法判断此特性是否是指定名称的特性会更加安全。 13 | /// 14 | /// 15 | /// 进行比较的特性名称。 16 | /// 17 | /// 18 | /// 如果此特性符合 名称,则返回 true;否则返回 false。 19 | /// 20 | bool Match(string attributeName); 21 | 22 | /// 23 | /// Gets or sets the indexed properties of the Attribute. 24 | /// 25 | /// The property index of the Attribute. 26 | /// 27 | string this[int valueIndex] { get; } 28 | 29 | /// 30 | /// Gets or sets the properties of the Attribute. 31 | /// 32 | /// The property name of the Attribute. 33 | /// 34 | string this[string propertyName] { get; } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/SourceFusion/CompileTime/ICompileAttributeProvider.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.CompileTime 2 | { 3 | public interface ICompileAttributeProvider 4 | { 5 | ICompileAttribute[] Attributes { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/SourceFusion/CompileTime/ICompileField.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.CompileTime 2 | { 3 | public interface ICompileField : ICompileMember 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/SourceFusion/CompileTime/ICompileInterface.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.CompileTime 2 | { 3 | public interface ICompileInterface : ICompileMember 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/SourceFusion/CompileTime/ICompileMember.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace dotnetCampus.SourceFusion.CompileTime 4 | { 5 | public interface ICompileMember : ICompileAttributeProvider 6 | { 7 | string Name { get; } 8 | 9 | MemberModifiers MemberModifiers { get; } 10 | } 11 | 12 | [Flags] 13 | public enum MemberModifiers 14 | { 15 | Unset = 0, 16 | Private = 1, 17 | Protected = 1 << 1, 18 | Public = 1 << 2, 19 | Internal = 1 << 3, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SourceFusion/CompileTime/ICompileMethod.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.CompileTime 2 | { 3 | public interface ICompileMethod : ICompileMember 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/SourceFusion/CompileTime/ICompileProperty.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.CompileTime 2 | { 3 | public interface ICompileProperty : ICompileMember 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/SourceFusion/CompileTime/ICompileType.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace dotnetCampus.SourceFusion.CompileTime 4 | { 5 | /// 6 | /// 编译时的类型 7 | /// 8 | public interface ICompileType : ICompileMember 9 | { 10 | /// 11 | /// 类型的命名空间 12 | /// 13 | string Namespace { get; } 14 | /// 15 | /// 类型的全名 16 | /// 17 | string FullName { get; } 18 | /// 19 | /// 类型的基类 20 | /// 21 | IReadOnlyList BaseTypeList { get; } 22 | /// 23 | /// 引用的命名空间 24 | /// 25 | IReadOnlyList UsingNamespaceList { get; } 26 | 27 | /// 28 | /// 类型包含的属性 29 | /// 30 | IReadOnlyList Properties { get; } 31 | 32 | /// 33 | /// 类型的方法 34 | /// 35 | IReadOnlyList Methods { get; } 36 | 37 | /// 38 | /// 类型的字段 39 | /// 40 | IReadOnlyList Fields { get; } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/SourceFusion/FileType.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion 2 | { 3 | public enum FileType 4 | { 5 | None, 6 | Auto, 7 | Compile, 8 | Content, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/SourceFusion/ICodeTransformer.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion 2 | { 3 | public interface IPlainCodeTransformer 4 | { 5 | string Transform(string originalText, TransformingContext context); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/SourceFusion/ICompilingContext.cs: -------------------------------------------------------------------------------- 1 | using dotnetCampus.SourceFusion.CompileTime; 2 | 3 | namespace dotnetCampus.SourceFusion 4 | { 5 | /// 6 | /// 包含编译期代码执行的上下文。 7 | /// 8 | public interface ICompilingContext 9 | { 10 | /// 11 | /// 获取编译期程序集信息。 12 | /// 13 | ICompileAssembly Assembly { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SourceFusion/Metadata_/AttributedTypeMetadata.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace dotnetCampus.SourceFusion 4 | { 5 | public class AttributedTypeMetadata where TAttribute : Attribute 6 | { 7 | internal AttributedTypeMetadata(Type type, Func creator, TAttribute attribute) 8 | { 9 | Type = type; 10 | Creator = creator; 11 | Attribute = attribute; 12 | } 13 | 14 | public Type Type { get; } 15 | public Func Creator { get; } 16 | public TAttribute Attribute { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/SourceFusion/Metadata_/AttributedTypeMetadataCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | 6 | namespace dotnetCampus.SourceFusion 7 | { 8 | public class AttributedTypeMetadataCollection : Collection> 9 | where TAttribute : Attribute 10 | { 11 | public static implicit operator List<(Type, TAttribute)>( 12 | AttributedTypeMetadataCollection collection) 13 | { 14 | return collection.Select(x => (x.Type, x.Attribute)).ToList(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SourceFusion/Placeholder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace dotnetCampus.SourceFusion 4 | { 5 | /// 6 | /// 在编写期提供编译后才能确认的代码的占位符。 7 | /// 8 | public static class Placeholder 9 | { 10 | /// 11 | /// 返回类型为 [] 的占位符。 12 | /// 13 | /// 任意的类型。 14 | /// 15 | /// 此代码将在编译期执行。 16 | /// 如果需要调用其它方法,则只允许调用 .NET 基础库中的方法和此类型中标记为 的方法。 17 | /// 18 | /// 19 | /// 类型为 [] 的占位符。 20 | /// 在编译完成后,将替换成参数 生成的代码。 21 | /// 22 | /// 23 | /// 此方法由编译期间 NuGet 包的 targets 提供实现。 24 | /// 25 | [CompileTimeMethod] 26 | public static extern T[] Array(Func compileTimeCodeGenerator); 27 | 28 | [CompileTimeMethod] 29 | public static extern AttributedTypeMetadataCollection AttributedTypes() 30 | where TAttribute : Attribute; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/SourceFusion/SourceFusion.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net45 5 | Library 6 | dotnetCampus.SourceFusion 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/SourceFusion/SourceFusion.Core.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True -------------------------------------------------------------------------------- /src/SourceFusion/TransformingContext.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion 2 | { 3 | /// 4 | /// 包含静态转换源码时所需的上下文信息。 5 | /// 6 | public class TransformingContext 7 | { 8 | /// 9 | /// 获取当前执行源码转换时,对于同一源码文件的转换次数。 10 | /// 11 | public int RepeatIndex { get; internal set; } 12 | 13 | public TransformingContext(int repeatIndex) 14 | { 15 | RepeatIndex = repeatIndex; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/SourceFusion/Utils_/CompileCodeSnippetBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace dotnetCampus.SourceFusion 4 | { 5 | public class CompileCodeSnippetBuilder 6 | { 7 | private ICompilingContext _context; 8 | 9 | public CompileCodeSnippetBuilder(ICompilingContext context) 10 | { 11 | _context = context ?? throw new ArgumentNullException(nameof(context)); 12 | } 13 | 14 | public CompileCodeSnippetBuilder CollectAttributedTypes(string attributeName) 15 | { 16 | return this; 17 | } 18 | 19 | public string ToCodeSnippet() 20 | { 21 | return ""; 22 | } 23 | 24 | public override string ToString() => ToCodeSnippet(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/CompileTimeTests.cs: -------------------------------------------------------------------------------- 1 | using dotnetCampus.SourceFusion.CompileTime; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace dotnetCampus.SourceFusion.Tests 5 | { 6 | [TestClass] 7 | public class CompileTimeTests 8 | { 9 | public void Test(ICompileAssembly assembly) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Debug_/GenerateGeneric.cs: -------------------------------------------------------------------------------- 1 | #if DEBUG 2 | using System; 3 | using System.Linq; 4 | 5 | namespace dotnetCampus.SourceFusion.Tests 6 | { 7 | [CompileTimeCode("../Fakes/ActionCommand.cs", RepeatCount = 3, KeepSourceFiles = true)] 8 | public class GenerateGeneric : IPlainCodeTransformer 9 | { 10 | private const string ToolName = "SourceFusion.Tests"; 11 | private const string ToolVersion = "1.0"; 12 | 13 | private static readonly string GeneratedFooter = 14 | $@""; 15 | 16 | private static readonly string Generatedattribute = 17 | $"[System.CodeDom.Compiler.GeneratedCode(\"{ToolName}\", \"{ToolVersion}\")]"; 18 | 19 | public string Transform(string originalText, TransformingContext context) 20 | { 21 | var genericCount = context.RepeatIndex + 2; 22 | 23 | var content = originalText 24 | // 替换泛型。 25 | .Replace("", FromTemplate("<{0}>", "out T{n}", ", ", genericCount)) 26 | .Replace("Task", FromTemplate("Task<({0})>", "T{n}", ", ", genericCount)) 27 | .Replace("Func", FromTemplate("Func<{0}, Task>", "T{n}", ", ", genericCount)) 28 | .Replace(" T, Task>", FromTemplate(" {0}, Task>", "T{n}", ", ", genericCount)) 29 | .Replace("(T, bool", FromTemplate("({0}, bool", "T{n}", ", ", genericCount)) 30 | .Replace("var (t, ", FromTemplate("var ({0}, ", "t{n}", ", ", genericCount)) 31 | .Replace(", t)", FromTemplate(", {0})", "t{n}", ", ", genericCount)) 32 | .Replace("return (t, ", FromTemplate("return ({0}, ", "t{n}", ", ", genericCount)) 33 | .Replace("", FromTemplate("<{0}>", "T{n}", ", ", genericCount)) 34 | .Replace("{T}", FromTemplate("{{{0}}}", "T{n}", ", ", genericCount)) 35 | .Replace("(T value)", FromTemplate("(({0}) value)", "T{n}", ", ", genericCount)) 36 | .Replace("(T t)", FromTemplate("({0})", "T{n} t{n}", ", ", genericCount)) 37 | .Replace("(t)", FromTemplate("({0})", "t{n}", ", ", genericCount)) 38 | .Replace("var t =", FromTemplate("var ({0}) =", "t{n}", ", ", genericCount)) 39 | .Replace(" T ", FromTemplate(" ({0}) ", "T{n}", ", ", genericCount)) 40 | .Replace(" t;", FromTemplate(" ({0});", "t{n}", ", ", genericCount)) 41 | // 生成 [GeneratedCode]。 42 | .Replace(" public interface ", $" {Generatedattribute}{Environment.NewLine} public interface ") 43 | .Replace(" public class ", $" {Generatedattribute}{Environment.NewLine} public class ") 44 | .Replace(" public sealed class ", $" {Generatedattribute}{Environment.NewLine} public sealed class "); 45 | return content.Trim() + Environment.NewLine + GeneratedFooter; 46 | } 47 | 48 | private static string FromTemplate(string template, string part, string seperator, int count) 49 | { 50 | return string.Format(template, 51 | string.Join(seperator, Enumerable.Range(1, count).Select(x => part.Replace("{n}", x.ToString())))); 52 | } 53 | } 54 | } 55 | #endif -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Debug_/ModuleCollector.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using dotnetCampus.SourceFusion.Tests.Fakes.Modules; 3 | 4 | namespace dotnetCampus.SourceFusion.Tests 5 | { 6 | [CompileTimeTemplate] 7 | internal static class ModuleCollector 8 | { 9 | public static ModuleInfo[] Modules { get; } = Placeholder.Array(context => 10 | { 11 | var moduleTypes = context.Assembly.GetTypes().Where(type => type.Attributes.Any(x => x.Match("Module"))); 12 | return new CompileCodeSnippet(@"new ModuleInfo<{0}>(), 13 | ", moduleTypes.Select(x => x.FullName)); 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Fakes/ActionCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Threading.Tasks; 4 | using System.Windows.Input; 5 | 6 | namespace dotnetCampus.SourceFusion.Tests.Fakes 7 | { 8 | /// 9 | /// 这是一个纯测试用类,没有任何用途。 10 | /// 11 | public class ActionCommand : ICommand 12 | { 13 | /// 14 | /// 创建 的新实例,当 被执行时,将调用参数传入的动作。 15 | /// 16 | public ActionCommand(Action action, Func canExecute = null) 17 | { 18 | _action = action ?? throw new ArgumentNullException(nameof(action)); 19 | _canExecute = canExecute; 20 | } 21 | 22 | /// 23 | /// 用于接受所提供的参数并执行的委托。 24 | /// 只可能是 Action{T} 或 Func{T, Task}。 25 | /// 26 | private readonly Action _action; 27 | 28 | /// 29 | /// 此 中用于判定任务是否可以执行。 30 | /// 31 | private readonly Func _canExecute; 32 | 33 | /// 34 | /// 使用指定的参数执行此命令。 35 | /// 框架中没有约定参数值是否允许为 null,这由参数定义时的泛型类型约定(C#8.0)或由命令的实现者约定。 36 | /// 37 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "t")] 38 | public void Execute(T t) 39 | { 40 | _action(t); 41 | } 42 | 43 | #if GENERATED_CODE 44 | /// 45 | /// 尝试以异步的方式执行此同步命令。因为参数的获取可能是异步的(例如使用 ),所以此方法也必须是异步的。 46 | /// 对于传入的参数会被解析成多个参数,传入的参数一定不允许为 null。 47 | /// 48 | /// 接口中传入的原始参数。 49 | #else 50 | /// 51 | /// 尝试以异步的方式执行此同步命令。因为参数的获取可能是异步的(例如使用 ),所以此方法也必须是异步的。 52 | /// 对于单个泛型参数的 而言,传入的参数由业务定义含义,所以不能保证 null 值的合理性。 53 | /// 54 | /// 接口中传入的原始参数。 55 | #endif 56 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 57 | public async Task ExecuteAsync(object parameter) 58 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously 59 | { 60 | Execute((T) parameter); 61 | } 62 | 63 | void ICommand.Execute(object parameter) 64 | { 65 | ExecuteAsync(parameter).ConfigureAwait(false); 66 | } 67 | 68 | bool ICommand.CanExecute( 69 | #if GENERATED_CODE 70 | [NotNull] 71 | #endif 72 | object parameter) => _canExecute?.Invoke() ?? true; 73 | 74 | /// 75 | /// 76 | /// 当命令的可执行性改变时发生。 77 | /// 78 | public event EventHandler CanExecuteChanged; 79 | 80 | protected virtual void OnCanExecuteChanged() 81 | { 82 | CanExecuteChanged?.Invoke(this, EventArgs.Empty); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Fakes/DI/ExportAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace dotnetCampus.SourceFusion.Tests.Fakes.DI 4 | { 5 | [AttributeUsage(AttributeTargets.Class, Inherited = false)] 6 | internal class ExportAttribute : Attribute 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Fakes/DI/Foo.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.Tests.Fakes.DI 2 | { 3 | [Export] 4 | internal class Foo : IFoo 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Fakes/DI/IFoo.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.Tests.Fakes.DI 2 | { 3 | internal interface IFoo 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Fakes/DI/ImportAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace dotnetCampus.SourceFusion.Tests.Fakes.DI 4 | { 5 | [AttributeUsage(AttributeTargets.Class, Inherited = false)] 6 | internal class ImportAttribute : Attribute 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Fakes/Modules/BarModule.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.Tests.Fakes.Modules 2 | { 3 | [Module] 4 | public class BarModule : IModule 5 | { 6 | public void Initialize() 7 | { 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Fakes/Modules/FooModule.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.Tests.Fakes.Modules 2 | { 3 | [Module] 4 | public class FooModule : IModule 5 | { 6 | public void Initialize() 7 | { 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Fakes/Modules/IModule.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.Tests.Fakes.Modules 2 | { 3 | public interface IModule 4 | { 5 | void Initialize(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Fakes/Modules/ModuleAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace dotnetCampus.SourceFusion.Tests.Fakes.Modules 4 | { 5 | [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] 6 | public sealed class ModuleAttribute : Attribute 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Fakes/Modules/ModuleInfo.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.Tests.Fakes.Modules 2 | { 3 | public class ModuleInfo 4 | { 5 | } 6 | 7 | public class ModuleInfo : ModuleInfo where TModule : IModule, new() 8 | { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Fakes/Mvvm/FooViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.Tests.Fakes.Mvvm 2 | { 3 | public class FooViewModel : ViewModelBase, IFooViewModel 4 | { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Fakes/Mvvm/IFooViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.Tests.Fakes.Mvvm 2 | { 3 | public interface IFooViewModel 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/Fakes/Mvvm/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | namespace dotnetCampus.SourceFusion.Tests.Fakes.Mvvm 2 | { 3 | public abstract class ViewModelBase 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/SourceFusion.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | false 6 | dotnetCampus.SourceFusion.Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/SourceFusion.Tests/SourceFusion.Tests.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True --------------------------------------------------------------------------------