Merge pull request #1 from nventive/dev/cdb/initial-publish
Initial commit
This commit is contained in:
Коммит
206bea22e1
|
@ -0,0 +1,31 @@
|
|||
image: Visual Studio 2017
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
# Don't create a build when a PR is opened
|
||||
skip_branch_with_pr: true
|
||||
|
||||
nuget:
|
||||
disable_publish_on_pr: true
|
||||
|
||||
skip_commits:
|
||||
files:
|
||||
- 'doc/**/*.*'
|
||||
|
||||
init:
|
||||
- ps: git config --global core.autocrlf true
|
||||
|
||||
build_script:
|
||||
- ps: .\build\build.ps1 -script build\build.cake
|
||||
|
||||
artifacts:
|
||||
- path: .\build\*.nupkg
|
||||
|
||||
deploy:
|
||||
provider: NuGet
|
||||
api_key:
|
||||
secure: IkXtar7GUlIHtb4exXwdX6TjQA52w9rP7Gw+VwawieVu/frS0nP/RHf+umjWbINk
|
||||
skip_symbols: true
|
||||
artifact: /.*\.nupkg/
|
|
@ -0,0 +1,63 @@
|
|||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
|
@ -0,0 +1,232 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# Remove everything beginning with a dot
|
||||
.*/
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug[-\w]*/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease[-\w]*/
|
||||
[Rr]eleases/
|
||||
[Xx]64/
|
||||
[Xx]86/
|
||||
# [Bb]uild/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# DNX
|
||||
project.lock.json
|
||||
artifacts/
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.mdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.binlog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
|
||||
# TODO: Un-comment the next line if you do not want to checkin
|
||||
# your web deploy settings because they may include unencrypted
|
||||
# passwords
|
||||
#*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# NuGet Packages
|
||||
#*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
#**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
#!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Microsoft Azure ApplicationInsights config file
|
||||
ApplicationInsights.config
|
||||
|
||||
# Windows Store app package directory
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
[Ss]tyle[Cc]op.*
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# Xamarin Android
|
||||
Resource.Designer.cs
|
||||
*.bak
|
||||
*/nuget_version_override.txt
|
||||
/src/crosstargeting_override.props
|
||||
/build/tools
|
||||
/build/*.nupkg
|
|
@ -1,8 +1,8 @@
|
|||
# Uno.SourceGeneration
|
||||
# Uno.CodeGen
|
||||
|
||||
Copyright (c) nventive
|
||||
|
||||
All rights reserved.
|
||||
All rights reserved.
|
||||
|
||||
# Apache 2.0 License
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[assembly: System.Reflection.AssemblyVersion("1.17.0.0")]
|
||||
[assembly: System.Reflection.AssemblyFileVersion("1.17.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersion("1.17.0-dev.0+Branch.master.Sha.0c2c9de2cd573d2042ce42110540c0ef84bc5968")]
|
||||
[assembly: System.Reflection.AssemblyProduct("Uno.Core")]
|
||||
[assembly: System.Reflection.AssemblyCompany("nventive")]
|
||||
[assembly: System.Reflection.AssemblyCopyright("Copyright (C) nVentive 2011-2018")]
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
@ECHO OFF
|
||||
PowerShell.exe -file build.ps1 -target=UpdateHeaders -Verbosity Verbose
|
||||
PAUSE
|
|
@ -0,0 +1,145 @@
|
|||
#addin "Cake.FileHelpers"
|
||||
#addin "Cake.Powershell"
|
||||
#tool "nuget:?package=GitVersion.CommandLine"
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// ARGUMENTS
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
var target = Argument("target", "Default");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// VERSIONS
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
var gitVersioningVersion = "2.0.41";
|
||||
var signClientVersion = "0.9.0";
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// VARIABLES
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
var baseDir = MakeAbsolute(Directory("../")).ToString();
|
||||
var buildDir = baseDir + "/build";
|
||||
var Solution = baseDir + "/src/Uno.CodeGen.sln";
|
||||
GitVersion versionInfo = null;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// METHODS
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
void VerifyHeaders(bool Replace)
|
||||
{
|
||||
var header = FileReadText("header.txt") + "\r\n";
|
||||
bool hasMissing = false;
|
||||
|
||||
Func<IFileSystemInfo, bool> exclude_objDir =
|
||||
fileSystemInfo => !fileSystemInfo.Path.Segments.Contains("obj");
|
||||
|
||||
var files = GetFiles(baseDir + "/**/*.cs", exclude_objDir).Where(file =>
|
||||
{
|
||||
var path = file.ToString();
|
||||
return !(path.EndsWith(".g.cs") || path.EndsWith(".i.cs") || System.IO.Path.GetFileName(path).Contains("TemporaryGeneratedFile"));
|
||||
});
|
||||
|
||||
Information("\nChecking " + files.Count() + " file header(s)");
|
||||
foreach(var file in files)
|
||||
{
|
||||
var oldContent = FileReadText(file);
|
||||
if(oldContent.Contains("// <auto-generated>"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var rgx = new Regex("^(//.*\r?\n|\r?\n)*");
|
||||
var newContent = header + rgx.Replace(oldContent, "");
|
||||
|
||||
if(!newContent.Equals(oldContent, StringComparison.Ordinal))
|
||||
{
|
||||
if(Replace)
|
||||
{
|
||||
Information("\nUpdating " + file + " header...");
|
||||
FileWriteText(file, newContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
Error("\nWrong/missing header on " + file);
|
||||
hasMissing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!Replace && hasMissing)
|
||||
{
|
||||
throw new Exception("Please run UpdateHeaders.bat or '.\\build.ps1 -target=UpdateHeaders' and commit the changes.");
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// DEFAULT TASK
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
Task("Build")
|
||||
.IsDependentOn("Version")
|
||||
.IsDependentOn("ValidateHeaders")
|
||||
.Description("Build all projects and get the assemblies")
|
||||
.Does(() =>
|
||||
{
|
||||
Information("\nBuilding Solution");
|
||||
|
||||
var buildSettings = new MSBuildSettings
|
||||
{
|
||||
}
|
||||
.SetConfiguration("Release")
|
||||
.WithProperty("PackageVersion", versionInfo.FullSemVer)
|
||||
.WithProperty("InformationalVersion", versionInfo.InformationalVersion)
|
||||
.WithProperty("PackageOutputPath", buildDir)
|
||||
.WithTarget("Restore")
|
||||
.WithTarget("Build")
|
||||
.WithTarget("Pack");
|
||||
|
||||
MSBuild(Solution, buildSettings);
|
||||
});
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// TASK TARGETS
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
Task("Default")
|
||||
.IsDependentOn("Build");
|
||||
|
||||
Task("UpdateHeaders")
|
||||
.Description("Updates the headers in *.cs files")
|
||||
.Does(() =>
|
||||
{
|
||||
VerifyHeaders(true);
|
||||
});
|
||||
|
||||
Task("ValidateHeaders")
|
||||
.Description("Validates the headers in *.cs files")
|
||||
.Does(() =>
|
||||
{
|
||||
VerifyHeaders(false);
|
||||
});
|
||||
|
||||
Task("Version")
|
||||
.Description("Updates target versions")
|
||||
.Does(() =>
|
||||
{
|
||||
versionInfo = GitVersion(new GitVersionSettings {
|
||||
UpdateAssemblyInfo = true,
|
||||
UpdateAssemblyInfoFilePath = baseDir + "/build/AssemblyVersion.cs"
|
||||
});
|
||||
|
||||
Information($"FullSemVer: {versionInfo.FullSemVer} Sha: {versionInfo.Sha}");
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// EXECUTION
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
RunTarget(target);
|
|
@ -0,0 +1,235 @@
|
|||
##########################################################################
|
||||
# This is the Cake bootstrapper script for PowerShell.
|
||||
# This file was downloaded from https://github.com/cake-build/resources
|
||||
# Feel free to change this file to fit your needs.
|
||||
##########################################################################
|
||||
|
||||
<#
|
||||
|
||||
.SYNOPSIS
|
||||
This is a Powershell script to bootstrap a Cake build.
|
||||
|
||||
.DESCRIPTION
|
||||
This Powershell script will download NuGet if missing, restore NuGet tools (including Cake)
|
||||
and execute your Cake build script with the parameters you provide.
|
||||
|
||||
.PARAMETER Script
|
||||
The build script to execute.
|
||||
.PARAMETER Target
|
||||
The build script target to run.
|
||||
.PARAMETER Configuration
|
||||
The build configuration to use.
|
||||
.PARAMETER Verbosity
|
||||
Specifies the amount of information to be displayed.
|
||||
.PARAMETER ShowDescription
|
||||
Shows description about tasks.
|
||||
.PARAMETER DryRun
|
||||
Performs a dry run.
|
||||
.PARAMETER Experimental
|
||||
Uses the nightly builds of the Roslyn script engine.
|
||||
.PARAMETER Mono
|
||||
Uses the Mono Compiler rather than the Roslyn script engine.
|
||||
.PARAMETER SkipToolPackageRestore
|
||||
Skips restoring of packages.
|
||||
.PARAMETER ScriptArgs
|
||||
Remaining arguments are added here.
|
||||
|
||||
.LINK
|
||||
https://cakebuild.net
|
||||
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[string]$Script = "build.cake",
|
||||
[string]$Target,
|
||||
[string]$Configuration,
|
||||
[ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
|
||||
[string]$Verbosity,
|
||||
[switch]$ShowDescription,
|
||||
[Alias("WhatIf", "Noop")]
|
||||
[switch]$DryRun,
|
||||
[switch]$Experimental,
|
||||
[switch]$Mono,
|
||||
[switch]$SkipToolPackageRestore,
|
||||
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
|
||||
[string[]]$ScriptArgs
|
||||
)
|
||||
|
||||
[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
|
||||
function MD5HashFile([string] $filePath)
|
||||
{
|
||||
if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf))
|
||||
{
|
||||
return $null
|
||||
}
|
||||
|
||||
[System.IO.Stream] $file = $null;
|
||||
[System.Security.Cryptography.MD5] $md5 = $null;
|
||||
try
|
||||
{
|
||||
$md5 = [System.Security.Cryptography.MD5]::Create()
|
||||
$file = [System.IO.File]::OpenRead($filePath)
|
||||
return [System.BitConverter]::ToString($md5.ComputeHash($file))
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ($file -ne $null)
|
||||
{
|
||||
$file.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function GetProxyEnabledWebClient
|
||||
{
|
||||
$wc = New-Object System.Net.WebClient
|
||||
$proxy = [System.Net.WebRequest]::GetSystemWebProxy()
|
||||
$proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials
|
||||
$wc.Proxy = $proxy
|
||||
return $wc
|
||||
}
|
||||
|
||||
Write-Host "Preparing to run build script..."
|
||||
|
||||
if(!$PSScriptRoot){
|
||||
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
|
||||
}
|
||||
|
||||
$TOOLS_DIR = Join-Path $PSScriptRoot "tools"
|
||||
$ADDINS_DIR = Join-Path $TOOLS_DIR "Addins"
|
||||
$MODULES_DIR = Join-Path $TOOLS_DIR "Modules"
|
||||
$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe"
|
||||
$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe"
|
||||
$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
|
||||
$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config"
|
||||
$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum"
|
||||
$ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config"
|
||||
$MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config"
|
||||
|
||||
# Make sure tools folder exists
|
||||
if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) {
|
||||
Write-Verbose -Message "Creating tools directory..."
|
||||
New-Item -Path $TOOLS_DIR -Type directory | out-null
|
||||
}
|
||||
|
||||
# Make sure that packages.config exist.
|
||||
if (!(Test-Path $PACKAGES_CONFIG)) {
|
||||
Write-Verbose -Message "Downloading packages.config..."
|
||||
try {
|
||||
$wc = GetProxyEnabledWebClient
|
||||
$wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch {
|
||||
Throw "Could not download packages.config."
|
||||
}
|
||||
}
|
||||
|
||||
# Try find NuGet.exe in path if not exists
|
||||
if (!(Test-Path $NUGET_EXE)) {
|
||||
Write-Verbose -Message "Trying to find nuget.exe in PATH..."
|
||||
$existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) }
|
||||
$NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1
|
||||
if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) {
|
||||
Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)."
|
||||
$NUGET_EXE = $NUGET_EXE_IN_PATH.FullName
|
||||
}
|
||||
}
|
||||
|
||||
# Try download NuGet.exe if not exists
|
||||
if (!(Test-Path $NUGET_EXE)) {
|
||||
Write-Verbose -Message "Downloading NuGet.exe..."
|
||||
try {
|
||||
$wc = GetProxyEnabledWebClient
|
||||
$wc.DownloadFile($NUGET_URL, $NUGET_EXE)
|
||||
} catch {
|
||||
Throw "Could not download NuGet.exe."
|
||||
}
|
||||
}
|
||||
|
||||
# Save nuget.exe path to environment to be available to child processed
|
||||
$ENV:NUGET_EXE = $NUGET_EXE
|
||||
|
||||
# Restore tools from NuGet?
|
||||
if(-Not $SkipToolPackageRestore.IsPresent) {
|
||||
Push-Location
|
||||
Set-Location $TOOLS_DIR
|
||||
|
||||
# Check for changes in packages.config and remove installed tools if true.
|
||||
[string] $md5Hash = MD5HashFile($PACKAGES_CONFIG)
|
||||
if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or
|
||||
($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) {
|
||||
Write-Verbose -Message "Missing or changed package.config hash..."
|
||||
Get-ChildItem -Exclude packages.config,nuget.exe,Cake.Bakery |
|
||||
Remove-Item -Recurse
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Restoring tools from NuGet..."
|
||||
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`""
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Throw "An error occurred while restoring NuGet tools."
|
||||
}
|
||||
else
|
||||
{
|
||||
$md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII"
|
||||
}
|
||||
Write-Verbose -Message ($NuGetOutput | out-string)
|
||||
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
# Restore addins from NuGet
|
||||
if (Test-Path $ADDINS_PACKAGES_CONFIG) {
|
||||
Push-Location
|
||||
Set-Location $ADDINS_DIR
|
||||
|
||||
Write-Verbose -Message "Restoring addins from NuGet..."
|
||||
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`""
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Throw "An error occurred while restoring NuGet addins."
|
||||
}
|
||||
|
||||
Write-Verbose -Message ($NuGetOutput | out-string)
|
||||
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
# Restore modules from NuGet
|
||||
if (Test-Path $MODULES_PACKAGES_CONFIG) {
|
||||
Push-Location
|
||||
Set-Location $MODULES_DIR
|
||||
|
||||
Write-Verbose -Message "Restoring modules from NuGet..."
|
||||
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`""
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Throw "An error occurred while restoring NuGet modules."
|
||||
}
|
||||
|
||||
Write-Verbose -Message ($NuGetOutput | out-string)
|
||||
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
# Make sure that Cake has been installed.
|
||||
if (!(Test-Path $CAKE_EXE)) {
|
||||
Throw "Could not find Cake.exe at $CAKE_EXE"
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Build Cake arguments
|
||||
$cakeArguments = @("$Script");
|
||||
if ($Target) { $cakeArguments += "-target=$Target" }
|
||||
if ($Configuration) { $cakeArguments += "-configuration=$Configuration" }
|
||||
if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" }
|
||||
if ($ShowDescription) { $cakeArguments += "-showdescription" }
|
||||
if ($DryRun) { $cakeArguments += "-dryrun" }
|
||||
if ($Experimental) { $cakeArguments += "-experimental" }
|
||||
if ($Mono) { $cakeArguments += "-mono" }
|
||||
$cakeArguments += $ScriptArgs
|
||||
|
||||
# Start Cake
|
||||
Write-Host "Running build script..."
|
||||
&$CAKE_EXE $cakeArguments
|
||||
exit $LASTEXITCODE
|
|
@ -0,0 +1,16 @@
|
|||
// ******************************************************************
|
||||
// Copyright © 2015-2018 nventive inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// ******************************************************************
|
|
@ -0,0 +1,231 @@
|
|||
# Equality Members Generation
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Add a reference _Uno_ package `Uno.ImmutableGenerator` in your project.
|
||||
1. Create a new _POCO_ class with the `GeneratedEquality` attribute
|
||||
``` csharp
|
||||
[GeneratedEquality] // Uno.Equality.GeneratedEquality
|
||||
public partial class MyEntity
|
||||
{
|
||||
[EqualityKey]
|
||||
public string Id { get; }
|
||||
|
||||
[EqualityHash]
|
||||
public string A { get; }
|
||||
|
||||
public string B { get; }
|
||||
|
||||
[EqualityIgnore]
|
||||
public string C { get; }
|
||||
}
|
||||
```
|
||||
|
||||
* Note: The `[GeneratedEquality]` attribute is implicit when you are
|
||||
using the `[Immutable]` attribute to generate an immutable class.
|
||||
1. Compile (the generation process occurres at compile time).
|
||||
1. It will generate the following public methods for you:
|
||||
``` csharp
|
||||
partial class MyEntity : IEquatable<MyEntity>, IKeyEquatable<MyEntity>
|
||||
{
|
||||
// Global static helper for equality
|
||||
public static bool Equals(MyEntity a, MyEntity b)
|
||||
{ ... }
|
||||
|
||||
// IEquatable.Equals() implementation
|
||||
public bool Equals(MyEntity other)
|
||||
{ ... }
|
||||
|
||||
// override for object.Equals()
|
||||
public override bool Equals(MyEntity other)
|
||||
=> Equals(other as MyEntity);
|
||||
|
||||
// override for object.GetHashCode()
|
||||
public override int GetHashCode()
|
||||
{ ... }
|
||||
|
||||
// IKeyEquatable.KeyEquals() implementation
|
||||
public bool KeyEquals(MyEntity other)
|
||||
{ ... }
|
||||
|
||||
// IKeyEquatable.GetKeyHashCode() implementation
|
||||
public int GetKeyHashCode()
|
||||
{ ... }
|
||||
|
||||
// `==` Operator overload
|
||||
public static bool operator ==(MyEntity a, MyEntity b)
|
||||
{ ... }
|
||||
|
||||
// `!=` Operator overload
|
||||
public static bool operator !=(MyEntity a, MyEntity b)
|
||||
{ ... }
|
||||
}
|
||||
```
|
||||
1. Use it in your code
|
||||
``` csharp
|
||||
var e1_1 = new MyEntity {Id ="1", A="a", B="b", C="c"};
|
||||
var e1_2 = new MyEntity {Id ="1", A="a", B="b", C="c2"};
|
||||
var e1_3 = new MyEntity {Id ="1", A="a", B="b2", C="c2"};
|
||||
var e1_4 = new MyEntity {Id ="1", A="a2", B="b2", C="c2"};
|
||||
var e2 = new MyEntity {Id ="2", A="a2", B="b2", C="c2"};
|
||||
|
||||
// All following asserts will pass:
|
||||
|
||||
Assert.IsTrue(e1_1.Equals(e1_2));
|
||||
Assert.IsFalse(e1_1.Equals(e1_3));
|
||||
Assert.IsFalse(e1_1.Equals(e1_4));
|
||||
Assert.IsFalse(e1_1.Equals(e2));
|
||||
|
||||
Assert.IsTrue(e1_1.KeyEquals(e1_2));
|
||||
Assert.IsTrue(e1_1.KeyEquals(e1_3));
|
||||
Assert.IsTrue(e1_1.KeyEquals(e1_4));
|
||||
Assert.IsFalse(e1_1.KeyEquals(e2));
|
||||
|
||||
Assert.AreEquals(e1_1.GetHashCode(), e1_2.GetHashCode());
|
||||
Assert.AreEquals(e1_1.GetHashCode(), e1_3.GetHashCode());
|
||||
Assert.AreNotEquals(e1_1.GetHashCode(), e1_4.GetHashCode());
|
||||
Assert.AreNotEquals(e1_1.GetHashCode(), e2.GetHashCode());
|
||||
|
||||
Assert.AreEquals(e1_1.GetKeyHashCode(), e1_2.GetKeyHashCode());
|
||||
Assert.AreEquals(e1_1.GetKeyHashCode(), e1_3.GetKeyHashCode());
|
||||
Assert.AreNotEquals(e1_1.GetHashCode(), e2.GetHashCode());
|
||||
|
||||
```
|
||||
|
||||
## KeyEquality? What is that?
|
||||
|
||||
It's simply a concept created by nventive to determine if the _key_ of
|
||||
two entities are the same.
|
||||
|
||||
Suppose you have 2 versions of the same entity, an original version and
|
||||
an updated version after a change. The two won't be equals because they
|
||||
are representing different values, but they should be _key equals_ because
|
||||
they have the same key (reprensenting the same thing, just different versions
|
||||
of it).
|
||||
|
||||
|
||||
## How it works
|
||||
|
||||
1. The class needs to be partial - because generated code will augment it.
|
||||
If you want, you can sneak to generated code by checking in the following
|
||||
folder of your project: `obj/<target>/<platform>/g/EqualityGenerator`. Each
|
||||
class will have its own file there.
|
||||
1. Restrictions:
|
||||
* **Only read-only fields/properties should be used**
|
||||
A warning will be generated when using mutable fields.
|
||||
* **Nested classes not supported**, the class must be directly in its
|
||||
namespace for the generation to happend.
|
||||
1. **KeyEquality**:
|
||||
The _Key Equality_ is a feature to compare entities for the equality
|
||||
of their key(s). The main usage of this equality is to compare if
|
||||
different instances represent the same entity, even if they are a
|
||||
different version of it (let's say an updated version coming from the
|
||||
server, having the same unique key... instances will be _key equals_
|
||||
but not _equals_).
|
||||
* **To activate KeyEquality**, you need to have at least one field
|
||||
or property maked as _Equality Key_.
|
||||
* When activated, this feature will generate:
|
||||
* The class will implements the `IKeyEquatable<T>` interface
|
||||
* The `.GetKeyHashCode()` & `.KeyEquals()` methods
|
||||
|
||||
Those fields/properties WILL BE TREATED AS IF THEY WERE TAGGED EXPLICTELY.
|
||||
* You can put the `[EqualityKey]` attribute on more than one
|
||||
field/property.
|
||||
1. **HashCodes**:
|
||||
_Hash Codes_ are used to quickly differentiate/categorize the object.
|
||||
* Computing _hash codes_ should be very quick.
|
||||
* For equivalents instances, the computed _hash code_
|
||||
**MUST PRODUCE THE SAME RESULT**.
|
||||
* **Hash Codes can't change over time** for the same instance (object).
|
||||
For this reason the computed result will be cached.
|
||||
* The _hash code_ will be computed from all fields/properties tagged with
|
||||
`[EqualityHash]` and those tagged with `[EqualityKey]`. The same logic
|
||||
will apply for _key equality_, but will only use those tagged with the
|
||||
`[EqualityKey]` attribute.
|
||||
* IMPORTANT: Tagging a _collection_ (ex: a `List` or an `Array` or any type
|
||||
implementing `ICollection`) with the `[EqualityHash]` attribute
|
||||
**will only use the `.Count` value for calculating the _hash code_.**
|
||||
* If nothing could be used to calculate the hash, the result will be
|
||||
hard-coded to zero (`0`).
|
||||
1. **Operator overloading**:
|
||||
* Overloading of operations will be automatic, you have nothing to do
|
||||
to enable it.
|
||||
* There is no operator overloading (`==`/`!=`) for _Key Equality_.
|
||||
1. Can be used on eiter a _value type_ (`stuct`) or a _reference type_
|
||||
(`class`). When used with a `class`, a reference equality will be done.
|
||||
1. If your code defines any of the generated methods, it will prevent the
|
||||
generator from generating this method. (actually, it will be be generated,
|
||||
but the code will be commented-out, so you'll be able to sneak at it).
|
||||
1. The comparer for a field can be overriden by specifying a
|
||||
`private static readonly` named <Field/Property name> + "`_Comparer`"
|
||||
and of type `IEqualityComparer`
|
||||
1. For `string`, a `StringComparer.Ordinal` will be used by default. If you
|
||||
need a different one you can specify a _custom equality comparer_.
|
||||
|
||||
## Equality Rules
|
||||
|
||||
For processing equality, the generation will generate many comparisons, in
|
||||
this order:
|
||||
|
||||
1. If the type of the field/property is a collection (and not a `string`):
|
||||
1. A reference equality will be done. If `equals`, will return `true`, if one
|
||||
of them is `null`, will return `false` (both `null` will return `true` since
|
||||
they are _reference equals_).
|
||||
1. The `.Count` will be compared (return `false if different`)
|
||||
1. Each items of the collection will be compared by reapplying those rules.
|
||||
1. **Custom Equality Comparer**: If there's a **Private**, **Static** &
|
||||
**Read-Only** field with the name <Field/Propertyname>`_Comparer`
|
||||
of type `IEqualityComparer`, it will be used to compare the value. Example:
|
||||
``` csharp
|
||||
public partial class Person
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
// Default comparer for `Name` property
|
||||
// Tip: You can create another .cs file to keep your entity "clean"
|
||||
private static readonly IEqualityComparer Name_Comparer = StringComparer.Ordinal;
|
||||
}
|
||||
```
|
||||
1. If the type of the field/property is a string,
|
||||
`StringComparer.InvariantCulture` will be used.
|
||||
1. The values will be compared using `EqualityComparer<T>.Default`.
|
||||
|
||||
The generation logic for fields/properties in the class, using a first-match rule...
|
||||
* For `.Equals()`:
|
||||
1. If the tagged with `[EqualityIgnore]` attribute, won't be used in .Equals()
|
||||
1. All non-collection fields/properties will be evaluated **first**.
|
||||
1. The `.Count` of all collections will be checked **before** starting to
|
||||
enumerate them.
|
||||
* For `.KeyEquals`:
|
||||
1. If not tagged with `[EqualityKey]` attribute, won't be used in .KeyEquals()
|
||||
1. Rules for `.Equals()` will apply.
|
||||
* For `.GetHashCode`:
|
||||
1. Take all `[EqualityHash]` & `[EqualityKey]` fields/properties.
|
||||
1. If any of the type are a collection, only the `.Count` will be used to
|
||||
calculate a hash.
|
||||
* For `.GetKeyHashCode`:
|
||||
1. Same as `.GetHashCode`, except only on `[EqualityKey]` attributes.
|
||||
|
||||
## Attributes
|
||||
|
||||
| Attribute | Where | Usage |
|
||||
| -- | -- | -- |
|
||||
| `[GeneratedEquality]` | `class` or `struct` | Use to trigger the code generation |
|
||||
| `[EqualityIgnore]` | field or property | Remove a value from equality comparison |
|
||||
| `[EqualityKey]` | field or property | Indicate values to use for key comparison** |
|
||||
| `[EqualityHash]` | field or property | Indicate values to use for hash computation |
|
||||
|
||||
> About `[EqualityKey]` attribute:
|
||||
> * Some field/property names could implicitly become _key equality member_
|
||||
> * Members with `[EqualityKey]` (explicit or implicit) will also be considered
|
||||
> as `[EqualityHash]`.
|
||||
|
||||
## Important Considerations
|
||||
|
||||
* **Never rely on _Hash Code_ for persistence**: the computed result could
|
||||
be different when the application will be restarted. Hash codes should
|
||||
never _survive_ to the killing of the process.
|
||||
* Remember: **Computing _Hash Codes_ should be quick**. It is better to have a
|
||||
quick computed value than a completely unique value. Colision should be avoided,
|
||||
but never as a result of a big computation. That's why only the length is used
|
||||
by default for collections.
|
|
@ -0,0 +1,193 @@
|
|||
# Immutable Generation
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Add a reference _Uno_ package `Uno.ImmutableGenerator` in your project.
|
||||
1. Create a new _POCO_ class with the `Immutable` attribute
|
||||
``` csharp
|
||||
[GeneratedImmutable] // Uno.GeneratedImmutableAttribute
|
||||
public partial class MyEntity
|
||||
{
|
||||
// Please take note all properties are "get-only"
|
||||
|
||||
public string A { get; } = "a";
|
||||
|
||||
public string B { get; } = "b";
|
||||
}
|
||||
```
|
||||
1. Compile (this is important to generate the partial portion of the class).
|
||||
1. Use it in your code
|
||||
``` csharp
|
||||
MyEntity entity1 = MyEntity.Default; // A="a", B="b"
|
||||
MyEntity entity2 = entity1.WithA("c"); // A="c", B="b"
|
||||
MyEntity entity3 = new MyEntity.Builder(entity2) { B="123" }; // A="c", B="123"
|
||||
MyEntity entity4 = MyEntity.Default
|
||||
.WithA("value for A") // Intermediate fluent value is a builder,
|
||||
.WithB("value for B"); // so there's no memory impact doing this.
|
||||
```
|
||||
|
||||
## How to use it
|
||||
|
||||
1. The class needs to be partial - because generated code will augment it.
|
||||
1. Restrictions:
|
||||
* **No default constructor allowed**
|
||||
(this won't work with _Newtownsoft's JSON.NET_ - that's intentional!)
|
||||
* **No property setters allowed** (even `private` ones):
|
||||
properties should be _read only_, even for the class itself.
|
||||
* **No fields allowed** (except static fields, but that would be weird).
|
||||
* **Static members are ok**, as they can only manipulate immutable stuff.
|
||||
Same apply for extensions.
|
||||
* **Nested classes not supported**, the class must be directly in its
|
||||
namespace for the generator to happend.
|
||||
1. Property Initializers will become default values in the builder.
|
||||
ex:
|
||||
``` csharp
|
||||
public partial class UserPreferences
|
||||
{
|
||||
// Each time a new version of the builder is created
|
||||
// the initializer ("DateTimeOffset.Now" here) will be
|
||||
// applied first.
|
||||
public DateTimeOffSet LastChange { get; } = DateTimeOffset.Now;
|
||||
}
|
||||
```
|
||||
**Warning: don't use this for generating an id like a _guid_, because time you'll
|
||||
update the entity you'll get a new id.**
|
||||
|
||||
* Properties with implementation will be ignored (also called _computed property_).
|
||||
|
||||
Example:
|
||||
``` csharp
|
||||
/// <summary>
|
||||
/// Gets the fullname of the contact.
|
||||
/// </summary>
|
||||
public string FullName => LastName + ", " + FirstName;
|
||||
```
|
||||
* Generated builder are implicitely convertible to/from the entity
|
||||
* Collections must be IReadOnlyCollection, IReadonlyDictionary or an
|
||||
immutable type from System.Collection.Immutable namespace:
|
||||
ImmutableList, ImmutableDictionary, ImmutableArray, ImmutableSet...
|
||||
**The type of the collection must be immutable too**.
|
||||
1. A static `.Default` readonly property will containt a default instance with
|
||||
properties to their default initial values.
|
||||
|
||||
It can be used as starting point to create a new instance, example:
|
||||
``` csharp
|
||||
Invoice invoice = Invoice.Default
|
||||
.WithId(invoiceId)
|
||||
.WithCustomer(customer)
|
||||
.WithItems(items);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
``` csharp
|
||||
// No default constructor allowed on [Immutable] classes
|
||||
var x = new MyEntity() ; // Won't compile
|
||||
|
||||
// Method 1: Creating a builder from its constructor
|
||||
MyEntity.Builder b = new MyEntity.Builder();
|
||||
|
||||
// Method 2: Creating a builder using implicit cast
|
||||
MyEntity myEntity = [...];
|
||||
MyEntity.Builder b = myEntity;
|
||||
|
||||
// Method 3: Creating a builder using .WithXXX() method
|
||||
MyEntity myEntity = [...];
|
||||
MyEntity.Builder b = myEntity.WithName("new name");
|
||||
|
||||
// Method 4: You can also create a builder from a previous version
|
||||
MyEntity.Builder b = new MyEntity.Builder(previousEntity);
|
||||
|
||||
// To get the Immutable entity...
|
||||
|
||||
// Method 1 : Use implicit conversion (MyEntity.Builder => MyEntity)
|
||||
MyEntity e = b;
|
||||
|
||||
// Method 2 : Use the .ToImmutable() method
|
||||
MyEntity e = b.ToImmutable();
|
||||
|
||||
// Method 3 : Use the generated constructor with builder as parameter
|
||||
MyEntity e = new MyEntity(b);
|
||||
```
|
||||
|
||||
## `.WithXXX()` helpers
|
||||
|
||||
All set+set properties will also generate a `With<propertyName>()` method.
|
||||
The method will take a parameter of the type of the corresponding property.
|
||||
This method is present on both the class and the builder and always
|
||||
returns a builder.
|
||||
|
||||
Usage:
|
||||
|
||||
``` csharp
|
||||
public partial class MyEntity
|
||||
{
|
||||
public string A { get; } = string.Empty;
|
||||
public string B { get; } = null;
|
||||
}
|
||||
|
||||
[...]
|
||||
|
||||
// Create a first immutable instance
|
||||
var v1 = new MyEntity.Builder { A="a", B="b" };
|
||||
|
||||
// Create a new immutable instance
|
||||
var v2 = v1
|
||||
.WithB("b2")
|
||||
.ToImmutable();
|
||||
|
||||
// Same as previous but with the usage of implicit conversion
|
||||
MyEntity v2bis = v1.WithB("b2");
|
||||
```
|
||||
|
||||
## Aggregates (graph of objects/classes)
|
||||
|
||||
Let's say we write this...
|
||||
|
||||
``` csharp
|
||||
[Immutable]
|
||||
public partial class MyRootEntity
|
||||
{
|
||||
public string A { get; }
|
||||
public MySubEntity B { get; }
|
||||
}
|
||||
|
||||
[Immutable]
|
||||
public partial class MySubEntity
|
||||
{
|
||||
public string C { get; }
|
||||
public string D { get; }
|
||||
public ImmutableList<string> E { get; }
|
||||
}
|
||||
```
|
||||
|
||||
This will generate something like this:
|
||||
|
||||
``` csharp
|
||||
[Immutable]
|
||||
public partial class MyRootEntity
|
||||
{
|
||||
public partial class Builder
|
||||
{
|
||||
public string A { get; set; }
|
||||
public MySubEntity B { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
[Immutable]
|
||||
public partial class MySubEntity
|
||||
{
|
||||
public partial class Builder
|
||||
{
|
||||
public string C { get; set; }
|
||||
public string D { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Important:
|
||||
|
||||
* Complex properties \*\***MUST**\*\* be immutable entities.
|
||||
A complex property is when it's a defined type, not a CLR primitive.
|
||||
* Indexers are not supported.
|
||||
* _Events Properties_ are not supported.
|
|
@ -0,0 +1,15 @@
|
|||
assembly-versioning-scheme: MajorMinorPatch
|
||||
mode: ContinuousDeployment
|
||||
next-version: 1.20.0
|
||||
continuous-delivery-fallback-tag: ""
|
||||
branches:
|
||||
master:
|
||||
tag: dev
|
||||
(stable):
|
||||
tag: ""
|
||||
dev/.*?/(.*?):
|
||||
tag: dev.{BranchName}
|
||||
projects/(.*?):
|
||||
tag: proj-{BranchName}
|
||||
ignore:
|
||||
sha: []
|
16
readme.md
16
readme.md
|
@ -1,3 +1,17 @@
|
|||
# Uno CodeGen
|
||||
|
||||
TBD
|
||||
`Uno.CodeGen` is a set of tools to generate C# code in Visual Studio projects.
|
||||
|
||||
## Build status
|
||||
|
||||
| Target | Branch | Status | Recommended Nuget packages version |
|
||||
| ------ | ------ | ------ | ------ |
|
||||
| `development` | [master](https://github.com/nventive/Uno.CodeGen/tree/master) | [![Build status](https://ci.appveyor.com/api/projects/status/bh83u4i2lp0hrg8r/branch/master?svg=true)](https://ci.appveyor.com/project/nventivedevops/uno-codegen/branch/master) | [![NuGet](https://img.shields.io/nuget/v/Uno.CodeGen.svg)](https://www.nuget.org/packages/Uno.CodeGen/) |
|
||||
| `stable` | [stable](https://github.com/nventive/Uno.CodeGen/tree/stable) | (no build yet) | (not published yet) |
|
||||
|
||||
## Available Generators
|
||||
|
||||
| Generator | Triggering Attribute | Usage | |
|
||||
| --------- | -------------------- | ----- | -- |
|
||||
| `EqualityGenerator` | `[GenerateEquality]` | Generate code for efficient `.Equals()` members generation. | [Documentation](doc/Equality%20Generation.md) |
|
||||
| `ImmutableGenerator` | `[GenerateImmutable] | Generate truly immutable entities. | [Documentation](doc/Immutable%20Generation.md) |
|
|
@ -0,0 +1,71 @@
|
|||
// ******************************************************************
|
||||
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// ******************************************************************
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Uno.CodeGen.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class Given_GeneratedEquality
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestX()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedEquality]
|
||||
internal partial class MyEqualityClass<TSomething>
|
||||
{
|
||||
[EqualityKey]
|
||||
internal string A { get; set; }
|
||||
|
||||
private static int GetHash_A(string value) => -1;
|
||||
private static IEqualityComparer<string> A_CustomComparer => StringComparer.OrdinalIgnoreCase;
|
||||
|
||||
[EqualityKey]
|
||||
internal int B { get; set; }
|
||||
|
||||
[EqualityHash]
|
||||
internal bool C { get; set; }
|
||||
|
||||
[EqualityHash]
|
||||
internal string D { get; set; }
|
||||
private static IEqualityComparer<string> D_CustomComparer => StringComparer.OrdinalIgnoreCase;
|
||||
|
||||
[EqualityHash]
|
||||
internal TSomething E { get; set; }
|
||||
private static IEqualityComparer<TSomething> E_CustomComparer => EqualityComparer<TSomething>.Default;
|
||||
|
||||
[EqualityHash]
|
||||
internal bool[] F { get; set; }
|
||||
|
||||
[EqualityHash]
|
||||
internal IEnumerable<int> G { get; set; }
|
||||
|
||||
[EqualityHash]
|
||||
internal ICollection<TSomething> H { get; set; }
|
||||
}
|
||||
|
||||
[GeneratedEquality]
|
||||
internal partial struct MyEqualityStruct
|
||||
{
|
||||
[EqualityKey]
|
||||
internal string A { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
// ******************************************************************
|
||||
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// ******************************************************************
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Uno.CodeGen.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class Given_ImmutableEntity
|
||||
{
|
||||
[TestMethod]
|
||||
public void Immutable_When_CreatingFromBuilder_WithDefaultValue()
|
||||
{
|
||||
var entity = new MyImmutableEntity.Builder().ToImmutable();
|
||||
|
||||
entity.Should().NotBeNull();
|
||||
entity.MyField1.ShouldBeEquivalentTo(4);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Immutable_When_CreatingFromBuilder_And_ImplicitlyCasted()
|
||||
{
|
||||
MyImmutableEntity entity = new MyImmutableEntity.Builder
|
||||
{
|
||||
MyField1 = 42
|
||||
};
|
||||
|
||||
entity.Should().NotBeNull();
|
||||
entity.MyField1.ShouldBeEquivalentTo(42);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Immutable_When_CreatingFromBuilder_TheResultIsCached()
|
||||
{
|
||||
var builder = new MyImmutableEntity.Builder
|
||||
{
|
||||
MyField1 = 42
|
||||
};
|
||||
|
||||
var r1 = builder.ToImmutable();
|
||||
var r2 = builder.ToImmutable();
|
||||
|
||||
r1.Should().NotBeNull();
|
||||
r1.MyField1.ShouldBeEquivalentTo(42);
|
||||
r1.Should().BeSameAs(r2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Immutable_When_CreatingABuilderFromExisting_And_NoChanges_Then_ReferenceIsSame()
|
||||
{
|
||||
MyImmutableEntity original = new MyImmutableEntity.Builder
|
||||
{
|
||||
MyField1 = 42
|
||||
};
|
||||
|
||||
|
||||
var builder = new MyImmutableEntity.Builder(original);
|
||||
var newInstance = builder.ToImmutable();
|
||||
|
||||
newInstance.Should().BeSameAs(original);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Immutable_When_CreatingABuilderFromExisting()
|
||||
{
|
||||
MyImmutableEntity original = new MyImmutableEntity.Builder
|
||||
{
|
||||
MyField1 = 42
|
||||
};
|
||||
|
||||
MyImmutableEntity newInstance = original
|
||||
.WithMyField1(43);
|
||||
|
||||
original.MyField1.ShouldBeEquivalentTo(42);
|
||||
newInstance.MyField1.ShouldBeEquivalentTo(43);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Immutable_When_CreatingHierarchyOfBuilders()
|
||||
{
|
||||
A original = A.Default.WithEntity(x => x.WithMyField2(223));
|
||||
original.Entity.MyField2.ShouldBeEquivalentTo(223);
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedImmutable]
|
||||
public partial class A
|
||||
{
|
||||
public MyImmutableEntity Entity { get; } = MyImmutableEntity.Default;
|
||||
|
||||
public bool IsSomething { get; } = true;
|
||||
}
|
||||
|
||||
public partial class B : A
|
||||
{
|
||||
public string SecondField { get; }
|
||||
public long ThirdField { get; }
|
||||
public TimeSpan TimeSpan { get; }
|
||||
public bool Boolean { get; }
|
||||
public DateTimeKind Enum { get; }
|
||||
public new bool IsSomething { get; }
|
||||
}
|
||||
|
||||
[GeneratedImmutable]
|
||||
public partial class MyImmutableEntity
|
||||
{
|
||||
public int MyField1 { get; } = 4;
|
||||
|
||||
public int MyField2 { get; } = 75;
|
||||
|
||||
public int Sum => MyField1 + MyField2; // won't generate any builder code
|
||||
|
||||
public DateTimeOffset Date { get; } = DateTimeOffset.Now;
|
||||
|
||||
public ImmutableList<string> List { get; } = ImmutableList.Create("a", "b");
|
||||
}
|
||||
|
||||
[GeneratedImmutable]
|
||||
public partial class MyGenericImmutable<T>
|
||||
{
|
||||
public T Entity { get; }
|
||||
}
|
||||
|
||||
[GeneratedImmutable(GenerateEquality = false)]
|
||||
public partial class MyOtherImmutable : MyGenericImmutable<MyImmutableEntity>
|
||||
{
|
||||
}
|
||||
|
||||
[GeneratedImmutable]
|
||||
public partial class MyVeryOtherImmutable : MyGenericImmutable<MyOtherImmutable>
|
||||
{
|
||||
}
|
||||
|
||||
[GeneratedImmutable(GenerateEquality = true)]
|
||||
public partial class MySuperGenericImmutable<T1, T2, T3, T4, T5, T6>
|
||||
{
|
||||
public T1 Entity1 { get; }
|
||||
public T2 Entity2 { get; }
|
||||
public T3 Entity3 { get; }
|
||||
public T4 Entity4 { get; }
|
||||
public T5 Entity5 { get; }
|
||||
[EqualityHash]
|
||||
public T6 Entity6 { get; }
|
||||
public (T1, T2, T3, T4, T5, T6) Values { get; }
|
||||
|
||||
private static int GetHash_Entity6(T6 value) => 50;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net46</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<NoWarn>1701;1702;1705;NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<SourceGenerator Include="..\Uno.CodeGen\bin\$(Configuration)\net46\Uno.CodeGen.dll" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\Uno.CodeGen\build\Uno.CodeGen.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="4.19.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.2.0" />
|
||||
<PackageReference Include="Uno.Core" Version="1.20.0-dev.4" />
|
||||
<PackageReference Include="Uno.SourceGenerationTasks" Version="1.20.0-dev.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Uno.Equality\Uno.Equality.csproj" />
|
||||
<ProjectReference Include="..\Uno.CodeGen\Uno.CodeGen.csproj" />
|
||||
<ProjectReference Include="..\Uno.Immutables\Uno.Immutables.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,53 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27009.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.CodeGen", "Uno.CodeGen\Uno.CodeGen.csproj", "{7A05DD54-F3F1-4A48-97A5-1A5F45D9F315}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.CodeGen.Tests", "Uno.CodeGen.Tests\Uno.CodeGen.Tests.csproj", "{AB977C38-BF77-42E2-A10C-335C261E0E03}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{7A05DD54-F3F1-4A48-97A5-1A5F45D9F315} = {7A05DD54-F3F1-4A48-97A5-1A5F45D9F315}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.Immutables", "Uno.Immutables\Uno.Immutables.csproj", "{6D6DD983-D919-4CE0-88FC-26FBF34FC763}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.Equality", "Uno.Equality\Uno.Equality.csproj", "{0F785D54-1A9B-4B3A-AEB2-B40FE8C81577}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Doc", "Doc", "{0C1D81C9-BE02-4D97-999D-D2079A5F8CDA}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
..\doc\Equality Generation.md = ..\doc\Equality Generation.md
|
||||
..\doc\Immutable Generation.md = ..\doc\Immutable Generation.md
|
||||
..\readme.md = ..\readme.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7A05DD54-F3F1-4A48-97A5-1A5F45D9F315}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7A05DD54-F3F1-4A48-97A5-1A5F45D9F315}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7A05DD54-F3F1-4A48-97A5-1A5F45D9F315}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7A05DD54-F3F1-4A48-97A5-1A5F45D9F315}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AB977C38-BF77-42E2-A10C-335C261E0E03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AB977C38-BF77-42E2-A10C-335C261E0E03}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AB977C38-BF77-42E2-A10C-335C261E0E03}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AB977C38-BF77-42E2-A10C-335C261E0E03}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6D6DD983-D919-4CE0-88FC-26FBF34FC763}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6D6DD983-D919-4CE0-88FC-26FBF34FC763}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6D6DD983-D919-4CE0-88FC-26FBF34FC763}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6D6DD983-D919-4CE0-88FC-26FBF34FC763}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0F785D54-1A9B-4B3A-AEB2-B40FE8C81577}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0F785D54-1A9B-4B3A-AEB2-B40FE8C81577}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0F785D54-1A9B-4B3A-AEB2-B40FE8C81577}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0F785D54-1A9B-4B3A-AEB2-B40FE8C81577}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {9F663DEC-A145-475E-A087-347FF527750D}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,561 @@
|
|||
// ******************************************************************
|
||||
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// ******************************************************************
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Uno.Helpers;
|
||||
using Uno.RoslynHelpers;
|
||||
using Uno.SourceGeneration;
|
||||
|
||||
namespace Uno
|
||||
{
|
||||
[SourceGeneratorDependency("Uno.ImmutableGenerator")]
|
||||
public class EqualityGenerator : SourceGenerator
|
||||
{
|
||||
private INamedTypeSymbol _boolSymbol;
|
||||
private INamedTypeSymbol _intSymbol;
|
||||
private INamedTypeSymbol _arraySymbol;
|
||||
private INamedTypeSymbol _collectionSymbol;
|
||||
private INamedTypeSymbol _collectionGenericSymbol;
|
||||
private INamedTypeSymbol _generatedEqualityAttributeSymbol;
|
||||
private INamedTypeSymbol _ignoreForEqualityAttributeSymbol;
|
||||
private INamedTypeSymbol _equalityHashCodeAttributeSymbol;
|
||||
private INamedTypeSymbol _equalityKeyCodeAttributeSymbol;
|
||||
private SourceGeneratorContext _context;
|
||||
|
||||
private static readonly int[] PrimeNumbers =
|
||||
{
|
||||
223469, // 19901st prime number
|
||||
224743, // 20001st prime number
|
||||
225961, // 20101st prime number
|
||||
227251, // 20201st prime number
|
||||
228479, // 20301st prime number
|
||||
229613, // 20401st prime number
|
||||
230767, // 20501st prime number
|
||||
232007, // 20601st prime number
|
||||
233347, // 20701st prime number
|
||||
234653, // 20801st prime number
|
||||
235919, // 20901st prime number
|
||||
237217, // 21001st prime number
|
||||
238477, // 21101st prime number
|
||||
239737, // 21201st prime number
|
||||
240997, // 21301st prime number
|
||||
242129, // 21401st prime number
|
||||
243437, // 21501st prime number
|
||||
244603, // 21601st prime number
|
||||
245851, // 21701st prime number
|
||||
247067, // 21801st prime number
|
||||
248267, // 21901st prime number
|
||||
249449, // 22001st prime number
|
||||
};
|
||||
|
||||
public override void Execute(SourceGeneratorContext context)
|
||||
{
|
||||
_context = context;
|
||||
_boolSymbol = context.Compilation.GetTypeByMetadataName("System.Bool");
|
||||
_intSymbol = context.Compilation.GetTypeByMetadataName("System.Int32");
|
||||
_arraySymbol = context.Compilation.GetTypeByMetadataName("System.Array");
|
||||
_collectionSymbol = context.Compilation.GetTypeByMetadataName("System.Collections.ICollection");
|
||||
_collectionGenericSymbol = context.Compilation.GetTypeByMetadataName("System.Collections.Generic.ICollection`1");
|
||||
_generatedEqualityAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.GeneratedEqualityAttribute");
|
||||
_ignoreForEqualityAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.EqualityIgnoreAttribute");
|
||||
_equalityHashCodeAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.EqualityHashAttribute");
|
||||
_equalityKeyCodeAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.EqualityKeyAttribute");
|
||||
|
||||
foreach (var type in EnumerateEqualityTypesToGenerate())
|
||||
{
|
||||
GenerateEquality(type);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateEquality(INamedTypeSymbol typeSymbol)
|
||||
{
|
||||
var builder = new IndentedStringBuilder();
|
||||
|
||||
var (symbolName, symbolNameWithGenerics, symbolNameForXml, symbolNameDefinition, resultFileName) = typeSymbol.GetSymbolNames();
|
||||
var (equalityMembers, hashMembers, keyEqualityMembers) = GetEqualityMembers(typeSymbol);
|
||||
var generateKeyEquals = keyEqualityMembers.Any();
|
||||
|
||||
builder.AppendLine("using System;");
|
||||
builder.AppendLine();
|
||||
builder.AppendLineInvariant("// <autogenerated>");
|
||||
builder.AppendLineInvariant("// ****************************************************************************************************************");
|
||||
builder.AppendLineInvariant("// This has been generated by Uno.CodeGen (EqualityGenerator), available at https://github.com/nventive/Uno.CodeGen");
|
||||
builder.AppendLineInvariant("// ****************************************************************************************************************");
|
||||
builder.AppendLineInvariant("// </autogenerated>");
|
||||
builder.AppendLine();
|
||||
|
||||
using (builder.BlockInvariant($"namespace {typeSymbol.ContainingNamespace}"))
|
||||
{
|
||||
if (!IsFromPartialDeclaration(typeSymbol))
|
||||
{
|
||||
builder.AppendLineInvariant($"#warning {nameof(EqualityGenerator)}: you should add the partial modifier to the class {symbolNameWithGenerics}.");
|
||||
}
|
||||
|
||||
var classOrStruct = typeSymbol.IsReferenceType ? "class" : "struct";
|
||||
|
||||
var keyEqualsInterfaces = generateKeyEquals
|
||||
? $", global::Uno.Equality.IKeyEquatable<{symbolNameWithGenerics}>, global::Uno.Equality.IKeyEquatable"
|
||||
: "";
|
||||
|
||||
using (builder.BlockInvariant($"{typeSymbol.GetAccessibilityAsCSharpCodeString()} partial {classOrStruct} {symbolNameWithGenerics} : IEquatable<{symbolNameWithGenerics}>{keyEqualsInterfaces}"))
|
||||
{
|
||||
builder.AppendLineInvariant("/// <summary>");
|
||||
builder.AppendLineInvariant($"/// Checks two instances of {symbolNameForXml} for equality.");
|
||||
builder.AppendLineInvariant("/// </summary>");
|
||||
builder.AppendLineInvariant("/// <remarks>");
|
||||
builder.AppendLineInvariant("/// You can also simply use the overriden '==' and '!=' operators.");
|
||||
builder.AppendLineInvariant("/// </remarks>");
|
||||
builder.AppendLineInvariant("[global::System.Diagnostics.Contracts.Pure]");
|
||||
|
||||
using (builder.BlockInvariant($"public static bool Equals({symbolNameWithGenerics} a, {symbolNameWithGenerics} b)"))
|
||||
{
|
||||
if (typeSymbol.IsReferenceType)
|
||||
{
|
||||
builder.AppendLineInvariant("if (ReferenceEquals(a, b)) return true; // Same instance");
|
||||
using (builder.BlockInvariant("if (ReferenceEquals(null, a))"))
|
||||
{
|
||||
builder.AppendLineInvariant("return ReferenceEquals(null, b);");
|
||||
}
|
||||
builder.AppendLineInvariant("return !ReferenceEquals(null, b) && a.InnerEquals(b);");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLineInvariant("return a.InnerEquals(b);");
|
||||
}
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLineInvariant("/// <inheritdoc />");
|
||||
builder.AppendLineInvariant("[global::System.Diagnostics.Contracts.Pure]");
|
||||
using (builder.BlockInvariant($"public bool Equals({symbolNameWithGenerics} other) // Implementation of `IEquatable<{symbolNameWithGenerics}>.Equals()`"))
|
||||
{
|
||||
if (typeSymbol.IsReferenceType)
|
||||
{
|
||||
builder.AppendLineInvariant("if (ReferenceEquals(this, other)) return true;");
|
||||
builder.AppendLineInvariant("if (ReferenceEquals(null, other)) return false;");
|
||||
}
|
||||
builder.AppendLineInvariant("return InnerEquals(other);");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLineInvariant("/// <inheritdoc />");
|
||||
builder.AppendLineInvariant("[global::System.Diagnostics.Contracts.Pure]");
|
||||
using (builder.BlockInvariant("public override bool Equals(object other) // This one from `System.Object.Equals()`"))
|
||||
{
|
||||
if (typeSymbol.IsReferenceType)
|
||||
{
|
||||
builder.AppendLineInvariant($"return Equals(other as {symbolNameWithGenerics});");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLineInvariant($"return other is {symbolNameWithGenerics} ? Equals(({symbolNameWithGenerics})other) : false;");
|
||||
}
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLineInvariant("#region \"InnerEquals\" Method -- THIS IS WHERE EQUALITY IS CHECKED");
|
||||
|
||||
builder.AppendLineInvariant("// private method doing the real .Equals() job");
|
||||
using (builder.BlockInvariant($"private bool InnerEquals({symbolNameWithGenerics} other)"))
|
||||
{
|
||||
builder.AppendLineInvariant("if (other.GetHashCode() != GetHashCode()) return false;");
|
||||
|
||||
GenerateEqualCalculation(typeSymbol, builder, equalityMembers);
|
||||
}
|
||||
|
||||
builder.AppendLineInvariant("#endregion");
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLineInvariant("/// <inheritdoc />");
|
||||
using (builder.BlockInvariant($"public static bool operator ==({symbolNameWithGenerics} a, {symbolNameWithGenerics} b)"))
|
||||
{
|
||||
builder.AppendLineInvariant("return Equals(a, b);");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLineInvariant("/// <inheritdoc />");
|
||||
using (builder.BlockInvariant($"public static bool operator !=({symbolNameWithGenerics} a, {symbolNameWithGenerics} b)"))
|
||||
{
|
||||
builder.AppendLineInvariant("return !Equals(a, b);");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
builder.AppendLineInvariant("#region \".GetHashCode()\" Section -- THIS IS WHERE HASH CODE IS COMPUTED");
|
||||
|
||||
builder.AppendLineInvariant("/// <inheritdoc />");
|
||||
builder.AppendLineInvariant("[global::System.Diagnostics.Contracts.Pure]");
|
||||
using (builder.BlockInvariant("public override int GetHashCode()"))
|
||||
{
|
||||
builder.AppendLineInvariant("return _computedHashCode ?? (int)(_computedHashCode = ComputeHashCode());");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLineInvariant("private int? _computedHashCode;");
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
using (builder.BlockInvariant("private int ComputeHashCode()"))
|
||||
{
|
||||
GenerateHashCalculation(typeSymbol, builder, hashMembers);
|
||||
}
|
||||
|
||||
builder.AppendLineInvariant("#endregion");
|
||||
builder.AppendLine();
|
||||
|
||||
if (generateKeyEquals)
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.AppendLineInvariant("#region \"Key Equality\" Section -- THIS IS WHERE KEY EQUALS IS DONE + KEY HASH CODE IS COMPUTED");
|
||||
|
||||
builder.AppendLineInvariant("/// <inheritdoc />");
|
||||
builder.AppendLineInvariant("[global::System.Diagnostics.Contracts.Pure]");
|
||||
using (builder.BlockInvariant("public bool KeyEquals(object other)"))
|
||||
{
|
||||
if (typeSymbol.IsReferenceType)
|
||||
{
|
||||
builder.AppendLineInvariant($"return KeyEquals(other as {symbolNameWithGenerics});");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLineInvariant($"return other is {symbolNameWithGenerics} ? KeyEquals(({symbolNameWithGenerics})other) : false;");
|
||||
}
|
||||
}
|
||||
builder.AppendLine();
|
||||
using (builder.BlockInvariant($"public bool KeyEquals({symbolNameWithGenerics} other)"))
|
||||
{
|
||||
if (typeSymbol.IsReferenceType)
|
||||
{
|
||||
builder.AppendLineInvariant("if (ReferenceEquals(this, other)) return true;");
|
||||
builder.AppendLineInvariant("if (ReferenceEquals(null, other)) return false;");
|
||||
}
|
||||
|
||||
builder.AppendLineInvariant("return InnerKeyEquals(other);");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLineInvariant("// private method doing the real .KeyEquals() job");
|
||||
using (builder.BlockInvariant($"private bool InnerKeyEquals({symbolNameWithGenerics} other)"))
|
||||
{
|
||||
builder.AppendLineInvariant("if (other.GetKeyHashCode() != GetKeyHashCode()) return false;");
|
||||
|
||||
GenerateEqualCalculation(typeSymbol, builder, keyEqualityMembers);
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLineInvariant("/// <inheritdoc />");
|
||||
builder.AppendLineInvariant("[global::System.Diagnostics.Contracts.Pure]");
|
||||
using (builder.BlockInvariant("public int GetKeyHashCode()"))
|
||||
{
|
||||
builder.AppendLineInvariant("return _computedKeyHashCode ?? (int)(_computedKeyHashCode = ComputeKeyHashCode());");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
using (builder.BlockInvariant("private int ComputeKeyHashCode()"))
|
||||
{
|
||||
GenerateHashCalculation(typeSymbol, builder, keyEqualityMembers);
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLineInvariant("private int? _computedKeyHashCode;");
|
||||
|
||||
builder.AppendLineInvariant("#endregion");
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_context.AddCompilationUnit(resultFileName, builder.ToString());
|
||||
}
|
||||
|
||||
private void GenerateEqualCalculation(INamedTypeSymbol typeSymbol, IndentedStringBuilder builder, ISymbol[] equalityMembers)
|
||||
{
|
||||
if (equalityMembers.Length == 0)
|
||||
{
|
||||
builder.AppendLineInvariant("#error No fields or properties used for equality check.");
|
||||
}
|
||||
|
||||
foreach (var member in equalityMembers)
|
||||
{
|
||||
var type = (member as IFieldSymbol)?.Type ?? (member as IPropertySymbol)?.Type;
|
||||
if (type == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var (_, customComparerProperty) = GetCustomsForMembers(typeSymbol, member, type);
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
if (customComparerProperty == null)
|
||||
{
|
||||
builder.AppendLineInvariant($"// **{member.Name}** You can define a custom comparer for {member.Name} and it will be used.");
|
||||
builder.AppendLineInvariant($"// CUSTOM COMPARER>> private static IEqualityComparer<{type}> {GetCustomComparerPropertyName(member)} => <custom comparer>;");
|
||||
|
||||
using (builder.BlockInvariant(
|
||||
$"if(!System.Collections.Generic.EqualityComparer<{type}>.Default.Equals({member.Name}, other.{member.Name}))"))
|
||||
{
|
||||
builder.AppendLineInvariant($"return false; // {member.Name} not equal");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (builder.BlockInvariant($"if(!{customComparerProperty.Name}.Equals({member.Name}, other.{member.Name})) // Using custom comparer provided by `{customComparerProperty.Name}()`."))
|
||||
{
|
||||
builder.AppendLineInvariant($"return false; // {member.Name} not equal");
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.AppendLineInvariant("return true; // no differences found");
|
||||
}
|
||||
|
||||
private void GenerateHashCalculation(INamedTypeSymbol typeSymbol, IndentedStringBuilder builder, ISymbol[] hashMembers)
|
||||
{
|
||||
if (hashMembers.Length == 0)
|
||||
{
|
||||
builder.AppendLineInvariant("#warning There is no members marked with [Uno.EqualityHash] or [Uno.EqualityKey]. You should add at least one. Documentation: https://github.com/nventive/Uno.CodeGen/blob/master/doc/Equality%20Generation.md");
|
||||
builder.AppendLineInvariant("return 0; // no members to compute hash");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLineInvariant("int hash = 104729; // 10 000th prime number");
|
||||
using (builder.BlockInvariant("unchecked"))
|
||||
{
|
||||
for (var i = 0; i < hashMembers.Length; i++)
|
||||
{
|
||||
var member = hashMembers[i];
|
||||
var primeNumber = PrimeNumbers[i % PrimeNumbers.Length];
|
||||
|
||||
var type = (member as IFieldSymbol)?.Type ?? (member as IPropertySymbol)?.Type;
|
||||
if (type == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
builder.AppendLineInvariant($"// ***** Computation for {member.Name} ({type}) *****");
|
||||
|
||||
var (customHashMethod, customComparerProperty) = GetCustomsForMembers(typeSymbol, member, type);
|
||||
|
||||
if (customHashMethod == null)
|
||||
{
|
||||
builder.AppendLineInvariant($"// **{member.Name}** You can define a custom hash computation by creating a method with the following signature:");
|
||||
builder.AppendLineInvariant($"// CUSTOM HASH METHOD>> private static int {GetCustomHashMethodName(member)}({type} value) => <custom code>;");
|
||||
if (customComparerProperty == null)
|
||||
{
|
||||
builder.AppendLineInvariant($"// ** You can also define a custom comparer for {member.Name} and it will be used to compute the hash:");
|
||||
builder.AppendLineInvariant($"// CUSTOM COMPARER>> private static IEqualityComparer<{type}> {GetCustomComparerPropertyName(member)} => <custom comparer>;");
|
||||
}
|
||||
}
|
||||
else if(customComparerProperty == null)
|
||||
{
|
||||
builder.AppendLineInvariant($"// **{member.Name}** You can define a custom comparer for {member.Name} and it will be used to compute the hash.");
|
||||
builder.AppendLineInvariant($"// CUSTOM COMPARER>> private static IEqualityComparer<{type}> {GetCustomComparerPropertyName(member)} => <custom comparer>;");
|
||||
}
|
||||
|
||||
var definition = type;
|
||||
|
||||
while (definition is INamedTypeSymbol nts && !nts.ConstructedFrom.Equals(definition))
|
||||
{
|
||||
definition = nts.ConstructedFrom;
|
||||
}
|
||||
|
||||
string getHashCode;
|
||||
if (customHashMethod != null)
|
||||
{
|
||||
getHashCode = $"{customHashMethod.Name}({member.Name})";
|
||||
}
|
||||
else if (customComparerProperty != null)
|
||||
{
|
||||
getHashCode = $"{customComparerProperty.Name}.GetHashCode({member.Name})";
|
||||
}
|
||||
else if (definition.Equals(_boolSymbol))
|
||||
{
|
||||
getHashCode = $"({member.Name} ? 1 : 0)";
|
||||
}
|
||||
else if (definition.DerivesFromType(_arraySymbol))
|
||||
{
|
||||
getHashCode = $"{member.Name}.Length";
|
||||
}
|
||||
else if (definition.Equals(_collectionSymbol)
|
||||
|| definition.DerivesFromType(_collectionSymbol))
|
||||
{
|
||||
getHashCode = $"((global::System.Collections.ICollection){member.Name}).Count";
|
||||
}
|
||||
else if (definition.Equals(_collectionGenericSymbol)
|
||||
|| definition.DerivesFromType(_collectionGenericSymbol))
|
||||
{
|
||||
getHashCode = $"((global::System.Collections.Generic.ICollection<{type.GetTypeArgumentNames().FirstOrDefault()}>){member.Name}).Count";
|
||||
}
|
||||
else
|
||||
{
|
||||
var getHashCodeMember = definition
|
||||
.GetMembers("GetHashCode")
|
||||
.OfType<IMethodSymbol>()
|
||||
.Where(m => !m.IsStatic)
|
||||
.Where(m => m.IsOverride)
|
||||
.Where(m => !m.IsAbstract)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (getHashCodeMember == null)
|
||||
{
|
||||
builder.AppendLineInvariant(
|
||||
$"#warning Type `{type.GetDisplayFriendlyName()}` of member `{member.Name}` " +
|
||||
"doesn't implements .GetHashCode(): it won't be used for hash computation. " +
|
||||
$"If you can change the type {type}, you should use a custom hash method or " +
|
||||
"a custom comparer.");
|
||||
continue;
|
||||
}
|
||||
|
||||
getHashCode = $"{member.Name}.GetHashCode()";
|
||||
}
|
||||
|
||||
if (type.IsReferenceType)
|
||||
{
|
||||
using (builder.BlockInvariant($"if ({member.Name} != null)"))
|
||||
{
|
||||
builder.AppendLineInvariant($"hash = ({getHashCode} * {primeNumber}) ^ hash;");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLineInvariant($"hash = ({getHashCode} * {primeNumber}) ^ hash;");
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.AppendLineInvariant("return hash;");
|
||||
}
|
||||
}
|
||||
|
||||
private (IMethodSymbol customHashMethod, IPropertySymbol customComparer) GetCustomsForMembers(INamedTypeSymbol typeSymbol, ISymbol forSymbol, ITypeSymbol symbolType)
|
||||
{
|
||||
var customHashMethodName = GetCustomHashMethodName(forSymbol);
|
||||
var customHashMethod = typeSymbol
|
||||
.GetMethods()
|
||||
.FirstOrDefault(m => m.Name.Equals(customHashMethodName)
|
||||
&& m.IsStatic
|
||||
&& m.DeclaredAccessibility == Accessibility.Private
|
||||
&& m.ReturnType.Equals(_intSymbol)
|
||||
&& m.Parameters.Length == 1
|
||||
&& m.Parameters[0].Type.Equals(symbolType));
|
||||
|
||||
var customComparerPropertyName = GetCustomComparerPropertyName(forSymbol);
|
||||
var customComparerProperty = typeSymbol
|
||||
.GetProperties()
|
||||
.FirstOrDefault(p => p.Name.Equals(customComparerPropertyName)
|
||||
&& p.IsStatic
|
||||
&& p.DeclaredAccessibility == Accessibility.Private
|
||||
&& p.IsReadOnly
|
||||
&& p.Type.Name.Equals("IEqualityComparer"));
|
||||
|
||||
return (customHashMethod, customComparerProperty);
|
||||
}
|
||||
|
||||
private static string GetCustomHashMethodName(ISymbol forSymbol) => $"GetHash_{forSymbol.Name}";
|
||||
private static string GetCustomComparerPropertyName(ISymbol forSymbol) => $"{forSymbol.Name}_CustomComparer";
|
||||
|
||||
private IEnumerable<INamedTypeSymbol> EnumerateEqualityTypesToGenerate()
|
||||
=> from type in _context.Compilation.SourceModule.GlobalNamespace.GetNamespaceTypes()
|
||||
let moduleAttribute = type.FindAttributeFlattened(_generatedEqualityAttributeSymbol)
|
||||
where moduleAttribute != null
|
||||
select type;
|
||||
|
||||
private (ISymbol[] equalitymembers, ISymbol[] hashMembers, ISymbol[] keyEqualityMembers) GetEqualityMembers(INamedTypeSymbol typeSymbol)
|
||||
{
|
||||
var properties =
|
||||
from property in typeSymbol.GetProperties()
|
||||
where !property.IsWriteOnly
|
||||
where !property.IsStatic
|
||||
where !property.IsImplicitlyDeclared
|
||||
where property.GetMethod.DeclaredAccessibility > Accessibility.Private
|
||||
select (symbol: (ISymbol)property, type: property.Type);
|
||||
|
||||
var fields =
|
||||
from field in typeSymbol.GetFields()
|
||||
where !field.IsStatic
|
||||
where !field.IsImplicitlyDeclared
|
||||
where field.DeclaredAccessibility > Accessibility.Private
|
||||
select (symbol: (ISymbol)field, type: field.Type);
|
||||
|
||||
var equalityMembers = new List<ISymbol>();
|
||||
var hashMembers = new List<ISymbol>();
|
||||
var keyEqualityMembers = new List<ISymbol>();
|
||||
|
||||
foreach (var (symbol, type) in properties.Concat(fields))
|
||||
{
|
||||
var symbolAttributes = symbol.GetAttributes();
|
||||
var typeAttributes = type.GetAttributes();
|
||||
|
||||
if (symbolAttributes.Any(a => a.AttributeClass.Equals(_ignoreForEqualityAttributeSymbol))
|
||||
|| typeAttributes.Any(a => a.AttributeClass.Equals(_ignoreForEqualityAttributeSymbol)))
|
||||
{
|
||||
continue; // [EqualityIgnore] on the member or the type itself: this member is ignored
|
||||
}
|
||||
|
||||
equalityMembers.Add(symbol);
|
||||
|
||||
if (symbolAttributes.Any(a => a.AttributeClass.Equals(_equalityKeyCodeAttributeSymbol)))
|
||||
{
|
||||
// [EqualityKey] on the member: this member is used for both key & hash
|
||||
hashMembers.Add(symbol);
|
||||
keyEqualityMembers.Add(symbol);
|
||||
}
|
||||
else if (symbolAttributes.Any(a => a.AttributeClass.Equals(_equalityHashCodeAttributeSymbol)))
|
||||
{
|
||||
// [EqualityHash] on the member: this member is used for hash computation
|
||||
hashMembers.Add(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
return (equalityMembers.ToArray(), hashMembers.ToArray(), keyEqualityMembers.ToArray());
|
||||
}
|
||||
|
||||
private static bool IsFromPartialDeclaration(INamedTypeSymbol symbol)
|
||||
{
|
||||
if(symbol.IsReferenceType)
|
||||
{
|
||||
return symbol
|
||||
.DeclaringSyntaxReferences
|
||||
.Select(reference => reference.GetSyntax(CancellationToken.None))
|
||||
.OfType<ClassDeclarationSyntax>()
|
||||
.Any(node => node.Modifiers.Any(SyntaxKind.PartialKeyword));
|
||||
}
|
||||
else
|
||||
{
|
||||
return symbol
|
||||
.DeclaringSyntaxReferences
|
||||
.Select(reference => reference.GetSyntax(CancellationToken.None))
|
||||
.OfType<StructDeclarationSyntax>()
|
||||
.Any(node => node.Modifiers.Any(SyntaxKind.PartialKeyword));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
// ******************************************************************
|
||||
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// ******************************************************************
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Uno.Helpers
|
||||
{
|
||||
public static class NamedTypeSymbolExtensions
|
||||
{
|
||||
public static (string symbolName, string symbolNameWithGenerics, string symbolForXml, string symbolNameDefinition, string symbolFilename) GetSymbolNames(this INamedTypeSymbol typeSymbol)
|
||||
{
|
||||
var symbolName = typeSymbol.Name;
|
||||
if (typeSymbol.TypeArguments.Length == 0) // not a generic type
|
||||
{
|
||||
return (symbolName, symbolName, symbolName, symbolName, symbolName);
|
||||
}
|
||||
|
||||
var argumentNames = typeSymbol.GetTypeArgumentNames();
|
||||
var arguments = string.Join(", ", argumentNames);
|
||||
|
||||
// symbolNameWithGenerics: MyType<T1, T2>
|
||||
var symbolNameWithGenerics = $"{symbolName}<{arguments}>";
|
||||
|
||||
// symbolNameWithGenerics: MyType<T1, T2>
|
||||
var symbolForXml = $"{symbolName}<{arguments}>";
|
||||
|
||||
// symbolNameDefinition: MyType<,>
|
||||
var symbolNameDefinition = $"{symbolName}<{string.Join(",", typeSymbol.TypeArguments.Select(ta => ""))}>";
|
||||
|
||||
// symbolNameWithGenerics: MyType_T1_T2
|
||||
var symbolFilename = $"{symbolName}_{string.Join("_", argumentNames)}";
|
||||
|
||||
return (symbolName, symbolNameWithGenerics, symbolForXml, symbolNameDefinition, symbolFilename);
|
||||
}
|
||||
|
||||
public static string[] GetTypeArgumentNames(this ITypeSymbol typeSymbol)
|
||||
{
|
||||
return (typeSymbol as INamedTypeSymbol)?.TypeArguments.Select(ta => ta.MetadataName).ToArray() ?? new string[0];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,565 @@
|
|||
// ******************************************************************
|
||||
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// ******************************************************************
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Uno.Helpers;
|
||||
using Uno.RoslynHelpers;
|
||||
using Uno.SourceGeneration;
|
||||
|
||||
namespace Uno
|
||||
{
|
||||
public class ImmutableGenerator : SourceGenerator
|
||||
{
|
||||
private SourceGeneratorContext _context;
|
||||
private INamedTypeSymbol _systemObject;
|
||||
private INamedTypeSymbol _immutableAttributeSymbol;
|
||||
private INamedTypeSymbol _generatedImmutableAttributeSymbol;
|
||||
private INamedTypeSymbol _immutableBuilderAttributeSymbol;
|
||||
|
||||
public override void Execute(SourceGeneratorContext context)
|
||||
{
|
||||
_context = context;
|
||||
_systemObject = context.Compilation.GetTypeByMetadataName("System.Object");
|
||||
_immutableAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.ImmutableAttribute");
|
||||
_generatedImmutableAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.GeneratedImmutableAttribute");
|
||||
_immutableBuilderAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.ImmutableBuilderAttribute");
|
||||
|
||||
var generationData = EnumerateImmutableGeneratedEntities()
|
||||
.OrderBy(x=>x.symbol.Name)
|
||||
.ToArray();
|
||||
|
||||
var immutableEntitiesToGenerate = generationData.Select(x => x.Item1).ToArray();
|
||||
|
||||
foreach ((var type, var moduleAttribute) in generationData)
|
||||
{
|
||||
var baseTypeInfo = GetTypeInfo(context, type, immutableEntitiesToGenerate);
|
||||
|
||||
var generateEquality = GetShouldGenerateEquality(moduleAttribute);
|
||||
|
||||
GenerateImmutable(type, baseTypeInfo, generateEquality);
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetShouldGenerateEquality(AttributeData attribute)
|
||||
{
|
||||
return attribute.NamedArguments
|
||||
.Where(na => na.Key.Equals("GenerateEquality"))
|
||||
.Select(na => (bool) na.Value.Value)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
private (bool isBaseType, string baseType, string builderBaseType, bool isImmutablePresent) GetTypeInfo(
|
||||
SourceGeneratorContext context,
|
||||
INamedTypeSymbol type, INamedTypeSymbol[] immutableEntitiesToGenerate)
|
||||
{
|
||||
var baseType = type.BaseType;
|
||||
if (baseType == null || baseType.Equals(_systemObject))
|
||||
{
|
||||
return (false, null, null, false); // no base type
|
||||
}
|
||||
|
||||
// Check if [Immutable] is present on the non-generated partial
|
||||
var isImmutablePresent = baseType.FindAttributeFlattened(_immutableAttributeSymbol) != null;
|
||||
|
||||
// Is Builder already compiled ? (in another project/assembly)
|
||||
var builderAttribute = baseType.FindAttributeFlattened(_immutableBuilderAttributeSymbol);
|
||||
if (builderAttribute != null)
|
||||
{
|
||||
return (true, null, null, true); // no relevant basetype
|
||||
}
|
||||
|
||||
var baseTypeDefinition = baseType.ConstructedFrom ?? baseType;
|
||||
|
||||
// Builder is to be generated (no attribute yet for reaching the builder)
|
||||
if (immutableEntitiesToGenerate.Contains(baseTypeDefinition))
|
||||
{
|
||||
var names = baseType.GetSymbolNames();
|
||||
|
||||
var isSameNamespace = baseType.ContainingNamespace.Equals(type.ContainingNamespace);
|
||||
var baseTypeName = isSameNamespace ? names.symbolNameWithGenerics : baseType.ToDisplayString();
|
||||
var builderBaseType = baseTypeName + ".Builder";
|
||||
|
||||
return (true, baseTypeName, builderBaseType, isImmutablePresent);
|
||||
}
|
||||
|
||||
return (true, null, null, false); // no relevant basetype
|
||||
}
|
||||
|
||||
private void GenerateImmutable(INamedTypeSymbol typeSymbol,
|
||||
(bool isBaseType, string baseType, string builderBaseType, bool isImmutablePresent) baseTypeInfo,
|
||||
bool generateEquality)
|
||||
{
|
||||
var defaultMemberName = "Default";
|
||||
|
||||
var builder = new IndentedStringBuilder();
|
||||
|
||||
var (symbolName, symbolNameWithGenerics, symbolNameForXml, symbolNameDefinition, resultFileName) = typeSymbol.GetSymbolNames();
|
||||
|
||||
if (!IsFromPartialDeclaration(typeSymbol))
|
||||
{
|
||||
builder.AppendLineInvariant($"#warning {nameof(ImmutableGenerator)}: you should add the partial modifier to the class {symbolNameWithGenerics}.");
|
||||
}
|
||||
|
||||
if (typeSymbol.IsValueType)
|
||||
{
|
||||
builder.AppendLineInvariant($"#error {nameof(ImmutableGenerator)}: Type {symbolNameWithGenerics} **MUST** be a class, not a struct.");
|
||||
}
|
||||
|
||||
if (baseTypeInfo.isBaseType && baseTypeInfo.baseType == null)
|
||||
{
|
||||
builder.AppendLineInvariant($"#error {nameof(ImmutableGenerator)}: Type {symbolNameWithGenerics} **MUST** derive from an immutable class.");
|
||||
}
|
||||
|
||||
builder.AppendLineInvariant("using System;");
|
||||
builder.AppendLine();
|
||||
builder.AppendLineInvariant("// <autogenerated>");
|
||||
builder.AppendLineInvariant("// *****************************************************************************************************************");
|
||||
builder.AppendLineInvariant("// This has been generated by Uno.CodeGen (ImmutableGenerator), available at https://github.com/nventive/Uno.CodeGen");
|
||||
builder.AppendLineInvariant("// *****************************************************************************************************************");
|
||||
builder.AppendLineInvariant("// </autogenerated>");
|
||||
builder.AppendLine();
|
||||
|
||||
using (builder.BlockInvariant($"namespace {typeSymbol.ContainingNamespace}"))
|
||||
{
|
||||
var builderTypeNameAndBaseClass = baseTypeInfo.isBaseType
|
||||
? $"Builder : {baseTypeInfo.builderBaseType}, Uno.IImmutableBuilder<{symbolNameWithGenerics}>"
|
||||
: $"Builder : global::Uno.IImmutableBuilder<{symbolNameWithGenerics}>";
|
||||
|
||||
var newModifier = baseTypeInfo.isBaseType ? "new " : "";
|
||||
|
||||
if (baseTypeInfo.isImmutablePresent)
|
||||
{
|
||||
builder.AppendLineInvariant("// Note: The attribute [Uno.Immutable] is already present on the class");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLineInvariant("[global::Uno.Immutable] // Mark this class as Immutable for some analyzers requiring it.");
|
||||
}
|
||||
|
||||
if (generateEquality)
|
||||
{
|
||||
builder.AppendLineInvariant("[global::Uno.GeneratedEquality] // Set [GeneratedImmutable(GeneratedEquality = false)] if you don't want this attribute.");
|
||||
}
|
||||
|
||||
builder.AppendLineInvariant($"[global::Uno.ImmutableBuilder(typeof({symbolNameDefinition}.Builder))] // Other generators can use this to find the builder.");
|
||||
|
||||
using (builder.BlockInvariant($"{typeSymbol.GetAccessibilityAsCSharpCodeString()} partial class {symbolNameWithGenerics}"))
|
||||
{
|
||||
builder.AppendLineInvariant($"/// <summary>");
|
||||
builder.AppendLineInvariant($"/// {defaultMemberName} instance with only property initializers set.");
|
||||
builder.AppendLineInvariant($"/// </summary>");
|
||||
builder.AppendLineInvariant($"public static readonly {newModifier}{symbolNameWithGenerics} {defaultMemberName} = new {symbolNameWithGenerics}();");
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
(IPropertySymbol property, bool isNew)[] properties;
|
||||
|
||||
if (baseTypeInfo.isBaseType)
|
||||
{
|
||||
var baseProperties = typeSymbol.BaseType.GetProperties()
|
||||
.Where(x => x.IsReadOnly && IsAutoProperty(x))
|
||||
.Select(x => x.Name)
|
||||
|
||||
.ToArray();
|
||||
|
||||
properties = typeSymbol
|
||||
.GetProperties()
|
||||
.Where(x => x.IsReadOnly && IsAutoProperty(x))
|
||||
.Select(x => (x, baseProperties.Contains(x.Name))) // remove properties already present in base class
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
properties = typeSymbol
|
||||
.GetProperties()
|
||||
.Where(x => x.IsReadOnly && IsAutoProperty(x))
|
||||
.Select(x => (x, false))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
var prop1Name = properties.Select(p => p.property.Name).FirstOrDefault() ?? symbolName + "Property";
|
||||
builder.AppendLineInvariant($"/// <summary>");
|
||||
builder.AppendLineInvariant($"/// Stateful builder to construct immutable instance(s) of {symbolNameForXml}.");
|
||||
builder.AppendLineInvariant($"/// </summary>");
|
||||
builder.AppendLineInvariant($"/// <remarks>");
|
||||
builder.AppendLineInvariant($"/// This builder is mutable. You can change their properties as you want or");
|
||||
builder.AppendLineInvariant($"/// use the .WithXXX() methods to do it in a fluent way. You can continue to");
|
||||
builder.AppendLineInvariant($"/// change it even after calling the `.ToImmutable()` method: it will simply");
|
||||
builder.AppendLineInvariant($"/// generate a new version from the current state.");
|
||||
builder.AppendLineInvariant($"/// **THE BUILDER IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
||||
builder.AppendLineInvariant($"/// </remarks>");
|
||||
builder.AppendLineInvariant("/// <example>");
|
||||
builder.AppendLineInvariant($"/// // The following code will create a builder using a .With{prop1Name}() method:");
|
||||
builder.AppendLineInvariant("{0}", $"/// {symbolNameForXml}.Builder b = my{symbolName}Instance.With{prop1Name}([{prop1Name} value]);");
|
||||
builder.AppendLineInvariant("///");
|
||||
builder.AppendLineInvariant($"/// // The following code will use implicit cast to create a new {symbolNameForXml} immutable instance:");
|
||||
builder.AppendLineInvariant("{0}", $"/// {symbolNameForXml} my{symbolName}Instance = new {symbolNameForXml}.Builder {{ {prop1Name} = [{prop1Name} value], ... }};");
|
||||
builder.AppendLineInvariant("/// </example>");
|
||||
using (builder.BlockInvariant($"{typeSymbol.GetAccessibilityAsCSharpCodeString()} {newModifier}class {builderTypeNameAndBaseClass}"))
|
||||
{
|
||||
if (!baseTypeInfo.isBaseType)
|
||||
{
|
||||
// _isDirty only on base builder (will be reused in derived builders)
|
||||
builder.AppendLineInvariant("// Dirty means there's a difference from `_original`.");
|
||||
builder.AppendLineInvariant("protected bool _isDirty = false;");
|
||||
builder.AppendLine();
|
||||
builder.AppendLineInvariant("// This is the original entity, if any (could be null))");
|
||||
builder.AppendLineInvariant("{0}", $"internal readonly {symbolNameWithGenerics} _original;");
|
||||
builder.AppendLine();
|
||||
builder.AppendLineInvariant("// Cached version of generated entity (flushed when the builder is updated)");
|
||||
builder.AppendLineInvariant("{0}", $"protected {symbolNameWithGenerics} _cachedResult = default({symbolNameWithGenerics});");
|
||||
builder.AppendLine();
|
||||
|
||||
using (builder.BlockInvariant($"public Builder({symbolNameWithGenerics} original)"))
|
||||
{
|
||||
builder.AppendLineInvariant($"_original = original ?? {symbolNameWithGenerics}.Default;");
|
||||
}
|
||||
builder.AppendLine();
|
||||
using (builder.BlockInvariant($"public Builder()"))
|
||||
{
|
||||
builder.AppendLineInvariant($"_original = {symbolNameWithGenerics}.Default;");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (builder.BlockInvariant($"public Builder({symbolNameWithGenerics} original) : base(original ?? {symbolNameWithGenerics}.Default)"))
|
||||
{
|
||||
builder.AppendLineInvariant($"// Default constructor, the _original field is assigned in base constructor.");
|
||||
}
|
||||
|
||||
using (builder.BlockInvariant($"public Builder() : base({symbolNameWithGenerics}.Default)"))
|
||||
{
|
||||
builder.AppendLineInvariant($"// Default constructor, the _original field is assigned in base constructor.");
|
||||
}
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
foreach (var (prop, isNew) in properties)
|
||||
{
|
||||
if (prop.IsIndexer)
|
||||
{
|
||||
builder.AppendLine($"#error Indexer {prop.Name} not supported! You must remove it for this to compile correctly.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prop.IsWithEvents)
|
||||
{
|
||||
builder.AppendLine("#error Events properties not supported!");
|
||||
}
|
||||
|
||||
var newPropertyModifier = isNew ? "new " : "";
|
||||
|
||||
builder.AppendLine($@"
|
||||
// Backing field for property {prop.Name}.
|
||||
private {prop.Type} _{prop.Name};
|
||||
|
||||
// If the property {prop.Name} has been set in the builder.
|
||||
// `false` means the property hasn't been set or has been reverted to original value `{typeSymbol.Name}.Default.{prop.Name}`.
|
||||
private bool _is{prop.Name}Set = false;
|
||||
|
||||
/// <summary>
|
||||
/// Get/Set the current builder value for {prop.Name}.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When nothing is set in the builder, the value is `default({prop.Type})`.
|
||||
/// </remarks>
|
||||
public {newPropertyModifier}{prop.Type} {prop.Name}
|
||||
{{
|
||||
get => _is{prop.Name}Set ? _{prop.Name} : (_original as {symbolNameWithGenerics}).{prop.Name};
|
||||
set
|
||||
{{
|
||||
var originalValue = (({symbolNameWithGenerics})_original).{prop.Name};
|
||||
var isSameAsOriginal = global::System.Collections.Generic.EqualityComparer<{prop.Type}>.Default.Equals(originalValue, value);
|
||||
if(isSameAsOriginal)
|
||||
{{
|
||||
// Property {prop.Name} has been set back to original value
|
||||
_is{prop.Name}Set = false;
|
||||
_{prop.Name} = default({prop.Type}); // dereference to prevent any leak (when it's a reference type)
|
||||
}}
|
||||
else
|
||||
{{
|
||||
_is{prop.Name}Set = true;
|
||||
_{prop.Name} = value;
|
||||
_isDirty = true;
|
||||
}}
|
||||
_cachedResult = null;
|
||||
}}
|
||||
}}
|
||||
");
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
builder.AppendLineInvariant("/// <summary>");
|
||||
builder.AppendLineInvariant($"/// Create an immutable instance of {symbolNameForXml}.");
|
||||
builder.AppendLineInvariant("/// </summary>");
|
||||
builder.AppendLineInvariant("/// <remarks>");
|
||||
builder.AppendLineInvariant("/// Will return original if nothing changed in the builder (and an original was specified).");
|
||||
builder.AppendLineInvariant("/// Application code should prefer the usage of implicit casting which is calling this method.");
|
||||
builder.AppendLineInvariant($"/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
||||
builder.AppendLineInvariant("/// </remarks>");
|
||||
builder.AppendLineInvariant("[global::System.Diagnostics.Contracts.Pure]");
|
||||
using (builder.BlockInvariant($"public {newModifier}{symbolNameWithGenerics} ToImmutable()"))
|
||||
{
|
||||
builder.AppendLine(
|
||||
$@"var cachedResult = _cachedResult as {symbolNameWithGenerics};
|
||||
if(cachedResult != null)
|
||||
{{
|
||||
return cachedResult; // already computed, no need to redo this.
|
||||
}}
|
||||
|
||||
if (_isDirty)
|
||||
{{
|
||||
var new{symbolName} = new {symbolNameWithGenerics}(this);
|
||||
if (!Equals(new{symbolName}, _original))
|
||||
{{
|
||||
return ({symbolNameWithGenerics})(_cachedResult = new{symbolName});
|
||||
}}
|
||||
}}
|
||||
return ({symbolNameWithGenerics})(_cachedResult = _original);");
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
if(properties.Any())
|
||||
{
|
||||
builder.AppendLineInvariant("{0}", $"#region .WithXXX() methods on {symbolNameWithGenerics}.Builder");
|
||||
foreach (var (prop, isNew) in properties)
|
||||
{
|
||||
var newPropertyModifier = isNew ? "new " : "";
|
||||
|
||||
builder.AppendLineInvariant($"/// <summary>");
|
||||
builder.AppendLineInvariant($"/// Set property {prop.Name} in a fluent declaration.");
|
||||
builder.AppendLineInvariant($"/// </summary>");
|
||||
builder.AppendLineInvariant($"/// <remarks>");
|
||||
builder.AppendLineInvariant($"/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
||||
builder.AppendLineInvariant($"/// </remarks>");
|
||||
builder.AppendLineInvariant("/// <example>");
|
||||
builder.AppendLineInvariant("{0}", $"/// var builder = new {symbolNameForXml}.Builder {{ {prop1Name} = xxx, ... }}; // create a builder instance");
|
||||
builder.AppendLineInvariant($"/// {prop.Type} new{prop.Name}Value = [...];");
|
||||
builder.AppendLineInvariant($"/// {symbolNameForXml} instance = builder.With{prop.Name}(new{prop.Name}Value); // create an immutable instance");
|
||||
builder.AppendLineInvariant("/// </example>");
|
||||
using (builder.BlockInvariant($"public {newPropertyModifier}Builder With{prop.Name}({prop.Type} value)"))
|
||||
{
|
||||
builder.AppendLineInvariant($"{prop.Name} = value;");
|
||||
builder.AppendLineInvariant("return this;");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLineInvariant($"/// <summary>");
|
||||
builder.AppendLineInvariant($"/// Set property {prop.Name} in a fluent declaration by projecting previous value.");
|
||||
builder.AppendLineInvariant($"/// </summary>");
|
||||
builder.AppendLineInvariant($"/// <remarks>");
|
||||
builder.AppendLineInvariant($"/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
||||
builder.AppendLineInvariant($"/// The selector will be called immediately. The main usage of this overload is to specify a _method group_.");
|
||||
builder.AppendLineInvariant($"/// </remarks>");
|
||||
builder.AppendLineInvariant("/// <example>");
|
||||
builder.AppendLineInvariant("{0}", $"/// var builder = new {symbolNameForXml}.Builder {{ {prop1Name} = xxx, ... }}; // create a builder instance");
|
||||
builder.AppendLineInvariant($"/// {symbolNameForXml} instance = builder.With{prop.Name}(previous{prop.Name}Value => new {prop.Type}(...)); // create an immutable instance");
|
||||
builder.AppendLineInvariant("/// </example>");
|
||||
using (builder.BlockInvariant($"public {newPropertyModifier}Builder With{prop.Name}(Func<{prop.Type}, {prop.Type}> valueSelector)"))
|
||||
{
|
||||
builder.AppendLineInvariant($"{prop.Name} = valueSelector({prop.Name});");
|
||||
builder.AppendLineInvariant("return this;");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
builder.AppendLineInvariant($"#endregion");
|
||||
}
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLineInvariant($"// Default constructor - to ensure it's not defined by application code.");
|
||||
builder.AppendLineInvariant($"//");
|
||||
builder.AppendLineInvariant($"// \"error CS0111: Type '{symbolNameWithGenerics}' already defines a member called '.ctor' with the same parameter types\":");
|
||||
builder.AppendLineInvariant($"// => You have this error? it's because you defined a default constructor on your class!");
|
||||
builder.AppendLineInvariant($"//");
|
||||
builder.AppendLineInvariant($"// New instances should use the builder instead.");
|
||||
builder.AppendLineInvariant($"// We know, it's not compatible with Newtownsoft's JSON.net: It's by-design.");
|
||||
builder.AppendLineInvariant($"// (you need to deserialize the builder, not the immutable type itself)");
|
||||
builder.AppendLineInvariant($"//");
|
||||
builder.AppendLineInvariant($"// Send your complaints or inquiried to \"architecture [-@-] nventive [.] com\".");
|
||||
builder.AppendLineInvariant($"//");
|
||||
builder.AppendLineInvariant("{0}", $"protected {symbolName}() {{}} // see previous comments if you want this removed (TL;DR: you can't)");
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLineInvariant($"/// <summary>");
|
||||
builder.AppendLineInvariant($"/// Construct a new immutable instance of {symbolNameForXml} from a builder.");
|
||||
builder.AppendLineInvariant($"/// </summary>");
|
||||
builder.AppendLineInvariant("/// <remarks>");
|
||||
builder.AppendLineInvariant("/// Application code should prefer the usage of implicit casting which is calling this constructor.");
|
||||
builder.AppendLineInvariant("/// </remarks>");
|
||||
builder.AppendLineInvariant($"/// <param name=\"builder\">The builder for {symbolNameForXml}.</param>");
|
||||
|
||||
var baseConstructorChaining = baseTypeInfo.isBaseType
|
||||
? " : base(builder)"
|
||||
: "";
|
||||
|
||||
using (builder.BlockInvariant($"public {symbolName}(Builder builder){baseConstructorChaining}"))
|
||||
{
|
||||
builder.AppendLineInvariant("if(builder == null) throw new ArgumentNullException(nameof(builder));");
|
||||
|
||||
foreach (var (prop, _) in properties)
|
||||
{
|
||||
builder.AppendLineInvariant($"{prop.Name} = builder.{prop.Name};");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLine(
|
||||
$@"// Implicit cast from {symbolNameWithGenerics} to Builder: will simply create a new instance of the builder.
|
||||
public static implicit operator Builder({symbolNameWithGenerics} original)
|
||||
{{
|
||||
return new Builder(original);
|
||||
}}
|
||||
|
||||
// Implicit cast from Builder to {symbolNameWithGenerics}: will simply create the immutable instance of the class.
|
||||
public static implicit operator {symbolNameWithGenerics}(Builder builder)
|
||||
{{
|
||||
return builder.ToImmutable();
|
||||
}}");
|
||||
builder.AppendLine();
|
||||
|
||||
if(properties.Any())
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.AppendLineInvariant($"#region .WithXXX() methods on {symbolNameWithGenerics}");
|
||||
foreach (var (prop, isNew) in properties)
|
||||
{
|
||||
var newPropertyModifier = isNew ? "new " : "";
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLineInvariant("/// <summary>");
|
||||
builder.AppendLineInvariant($"/// Set property {prop.Name} in a fluent declaration.");
|
||||
builder.AppendLineInvariant("/// </summary>");
|
||||
builder.AppendLineInvariant("/// <remarks>");
|
||||
builder.AppendLineInvariant($"/// The return value is a builder which can be casted implicitly to {symbolNameForXml} or used to make more changes.");
|
||||
builder.AppendLineInvariant("/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
||||
builder.AppendLineInvariant("/// </remarks>");
|
||||
builder.AppendLineInvariant("/// <example>");
|
||||
builder.AppendLineInvariant($"/// {symbolNameForXml} original = {symbolNameForXml}.{defaultMemberName}; // first immutable instance");
|
||||
builder.AppendLineInvariant($"/// {prop.Type} new{prop.Name}Value = [...];");
|
||||
builder.AppendLineInvariant($"/// {symbolNameForXml} modified = original.With{prop.Name}(new{prop.Name}Value); // create a new modified immutable instance");
|
||||
builder.AppendLineInvariant("/// </example>");
|
||||
builder.AppendLineInvariant("[global::System.Diagnostics.Contracts.Pure]");
|
||||
using (builder.BlockInvariant($"public {newPropertyModifier}Builder With{prop.Name}({prop.Type} value)"))
|
||||
{
|
||||
builder.AppendLineInvariant("var builder = new Builder(this);");
|
||||
builder.AppendLineInvariant($"return builder.With{prop.Name}(value);");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLineInvariant("/// <summary>");
|
||||
builder.AppendLineInvariant($"/// Set property {prop.Name} in a fluent declaration by projecting previous value.");
|
||||
builder.AppendLineInvariant("/// </summary>");
|
||||
builder.AppendLineInvariant("/// <remarks>");
|
||||
builder.AppendLineInvariant($"/// The return value is a builder which can be casted implicitly to {symbolNameForXml} or used to make more changes.");
|
||||
builder.AppendLineInvariant("/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
||||
builder.AppendLineInvariant($"/// The selector will be called immediately. The main usage of this overload is to specify a _method group_.");
|
||||
builder.AppendLineInvariant("/// </remarks>");
|
||||
builder.AppendLineInvariant("/// <example>");
|
||||
builder.AppendLineInvariant($"/// {symbolNameForXml} original = {symbolNameForXml}.{defaultMemberName}; // first immutable instance");
|
||||
builder.AppendLineInvariant($"/// {symbolNameForXml} modified = original.With{prop.Name}(previous{prop.Name}Value => new {prop.Type}(...)); // create a new modified immutable instance");
|
||||
builder.AppendLineInvariant("/// </example>");
|
||||
builder.AppendLineInvariant("[global::System.Diagnostics.Contracts.Pure]");
|
||||
using (builder.BlockInvariant($"public {newPropertyModifier}Builder With{prop.Name}(Func<{prop.Type}, {prop.Type}> valueSelector)"))
|
||||
{
|
||||
builder.AppendLineInvariant("var builder = new Builder(this);");
|
||||
builder.AppendLineInvariant($"return builder.With{prop.Name}(valueSelector({prop.Name}));");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
builder.AppendLineInvariant($"#endregion");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_context.AddCompilationUnit(resultFileName, builder.ToString());
|
||||
}
|
||||
|
||||
private static MethodInfo _isAutoPropertyGetter;
|
||||
|
||||
private static bool IsAutoProperty(IPropertySymbol symbol)
|
||||
{
|
||||
if (symbol.IsWithEvents || symbol.IsIndexer || !symbol.IsReadOnly)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
while (!Equals(symbol.OriginalDefinition, symbol))
|
||||
{
|
||||
// In some cases we're dealing with a derived type of `WrappedPropertySymbol`.
|
||||
// This code needs to deal with the SourcePropertySymbol from Roslyn,
|
||||
// the type containing the `IsAutoProperty` internal member.
|
||||
symbol = symbol.OriginalDefinition;
|
||||
}
|
||||
|
||||
if (_isAutoPropertyGetter == null)
|
||||
{
|
||||
var type = symbol.GetType();
|
||||
var propertyInfo = type.GetProperty("IsAutoProperty", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (propertyInfo == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Unable to find the internal property `IsAutoProperty` on implementation of `IPropertySymbol`. " +
|
||||
"Should be on internal class `PropertySymbol`. Maybe you are using an incompatible version of Roslyn.");
|
||||
}
|
||||
|
||||
_isAutoPropertyGetter = propertyInfo?.GetMethod;
|
||||
}
|
||||
|
||||
var isAuto = _isAutoPropertyGetter.Invoke(symbol, new object[] {});
|
||||
return (bool) isAuto;
|
||||
}
|
||||
|
||||
private IEnumerable<(INamedTypeSymbol symbol, AttributeData attribute)> EnumerateImmutableGeneratedEntities()
|
||||
=> from type in _context.Compilation.SourceModule.GlobalNamespace.GetNamespaceTypes()
|
||||
let moduleAttribute = type.FindAttributeFlattened(_generatedImmutableAttributeSymbol)
|
||||
where moduleAttribute != null
|
||||
//where (bool) moduleAttribute.ConstructorArguments[0].Value
|
||||
select (type, moduleAttribute);
|
||||
|
||||
private static bool IsFromPartialDeclaration(ISymbol symbol)
|
||||
{
|
||||
return symbol
|
||||
.DeclaringSyntaxReferences
|
||||
.Select(reference => reference.GetSyntax(CancellationToken.None))
|
||||
.OfType<ClassDeclarationSyntax>()
|
||||
.Any(node => node.Modifiers.Any(SyntaxKind.PartialKeyword));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net46</TargetFramework>
|
||||
<IsTool>true</IsTool>
|
||||
<NoWarn>1701;1702;1705;NU1701</NoWarn>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<RootNamespace>Uno</RootNamespace>
|
||||
<PackageProjectUrl>https://github.com/nventive/Uno.CodeGen</PackageProjectUrl>
|
||||
<PackageIconUrl>https://nv-assets.azurewebsites.net/logos/uno.png</PackageIconUrl>
|
||||
<RepositoryUrl>https://github.com/nventive/Uno.CodeGen</RepositoryUrl>
|
||||
<Description>This package provides tooling for code generation.</Description>
|
||||
<Copyright>Copyright (C) 2015-2018 nventive inc. - all rights reserved</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.ValueTuple" Version="4.4.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Uno.RoslynHelpers" Version="1.0.0-dev.6" PrivateAssets="all" />
|
||||
<PackageReference Include="Uno.SourceGeneration" Version="1.20.0-dev.6" PrivateAssets="all" />
|
||||
<PackageReference Include="Uno.SourceGenerationTasks" Version="1.20.0-dev.6" PrivateAssets="none" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="build/**/*.*">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>build</PackagePath>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Uno.Equality\Uno.Equality.csproj" />
|
||||
<ProjectReference Include="..\Uno.Immutables\Uno.Immutables.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Project ToolsVersion="15.0">
|
||||
<ItemGroup>
|
||||
<SourceGenerator Include="$(MSBuildThisFileDirectory)..\bin\$(Configuration)\net46\Uno.CodeGen.dll"
|
||||
Condition="Exists('$(MSBuildThisFileDirectory)..\bin')" />
|
||||
<SourceGenerator Include="$(MSBuildThisFileDirectory)..\tools\Uno.CodeGen.dll"
|
||||
Condition="Exists('$(MSBuildThisFileDirectory)..\tools')" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'net45' or ('$(ProjectTypeGuids)'=='' and ('$(TargetFrameworkVersion)'=='v4.5' or '$(TargetFrameworkVersion)'=='v4.5.1'))">
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_GEOCOORDINATE_WATCHER;HAS_GEOCOORDINATE;HAS_COMPILED_REGEX;USE_FAST_REPLAYONE</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_SEMAPHORE</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_FILE_IO</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_THREADS</UnoDefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0' or '$(ProjectTypeGuids)'=='{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}'">
|
||||
<UnoDefineConstants>$(UnoDefineConstants);WINDOWS_UAP;WINDOWS_UWP</UnoDefineConstants>
|
||||
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_WINDOWS_UI;HAS_TOP_APPBAR;HAS_BOTTOM_APPBAR</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_VIEWSIZE_PREFERENCE</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_GEOPOSITION</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);USE_SINGLE_ITEM_COLLECTIONCHANGED</UnoDefineConstants>
|
||||
|
||||
<UnoDefineConstants>$(UnoDefineConstants);NETFX_CORE</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);METRO</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);WINRT</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_WINRT_FILEIO;HAS_QUERYOPTIONS;HAS_CRIPPLEDREFLECTION;HAS_TYPEINFO;USE_FAST_REPLAYONE</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_ISTORAGEFILE</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_NATIVE_SYSTEMINFO</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_ISTORAGEFILE_ADVANCED</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_GEOLOCATOR</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_SEMAPHORE</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_HTTPCLIENT</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_DATACONTEXTCHANGED</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_HARDWARE_BUTTONS</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_TYPEINFO_EXTENSIONS</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_CAMERA_CAPTURE_UI</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_FILE_IO</UnoDefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'Xamarin.iOS10' or '$(ProjectTypeGuids)'=='{FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}'">
|
||||
<UnoDefineConstants>$(UnoDefineConstants);XAMARIN;XAMARIN_IOS</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_COMPILED_REGEX;HAS_CRIPPLEDREFLECTION;USE_FAST_REPLAYONE</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_QUERY_OPTIONS</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_BROKEN_SEMAPHORE_SLIM</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_SEMAPHORE</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_NO_CONCURRENT_DICT</UnoDefineConstants>
|
||||
<!-- Performance is worse than Sync Dic-->
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_FILE_IO</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_THREADS</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);NO_DNSENDPOINT</UnoDefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'MonoAndroid60' or '$(ProjectTypeGuids)'=='{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}'">
|
||||
<UnoDefineConstants>$(UnoDefineConstants);XAMARIN;XAMARIN_ANDROID</UnoDefineConstants>
|
||||
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_COMPILED_REGEX;HAS_CRIPPLEDREFLECTION;USE_FAST_REPLAYONE</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_BROKEN_SEMAPHORE_SLIM</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_SEMAPHORE</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_NO_WINDOWS_DISPATCHER</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_HTTPCLIENT</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_NO_CONCURRENT_DICT</UnoDefineConstants>
|
||||
<!-- Performance is worse than Sync Dic-->
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_FILE_IO</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_THREADS</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);NO_DNSENDPOINT</UnoDefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'MonoAndroid70'">
|
||||
<UnoDefineConstants>$(UnoDefineConstants);XAMARIN;XAMARIN_ANDROID</UnoDefineConstants>
|
||||
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_COMPILED_REGEX;HAS_CRIPPLEDREFLECTION;USE_FAST_REPLAYONE</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_BROKEN_SEMAPHORE_SLIM</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_SEMAPHORE</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_NO_WINDOWS_DISPATCHER</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_HTTPCLIENT</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_NO_CONCURRENT_DICT</UnoDefineConstants>
|
||||
<!-- Performance is worst than Sync Dic-->
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_FILE_IO</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);HAS_THREADS</UnoDefineConstants>
|
||||
<UnoDefineConstants>$(UnoDefineConstants);NO_DNSENDPOINT</UnoDefineConstants>
|
||||
</PropertyGroup>
|
||||
<Target Name="_UnoDefineConstantsDefines" BeforeTargets="BeforeCompile;XamlPreCompile;CoreCompile">
|
||||
<!-- Merge the AppTasksDefineConstants with the existing constants -->
|
||||
<CreateProperty Value="$(DefineConstants);$(UnoDefineConstants)">
|
||||
<Output TaskParameter="Value" PropertyName="DefineConstants" />
|
||||
</CreateProperty>
|
||||
</Target>
|
||||
</Project>
|
|
@ -0,0 +1,33 @@
|
|||
// ******************************************************************
|
||||
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// ******************************************************************
|
||||
using System;
|
||||
|
||||
namespace Uno
|
||||
{
|
||||
/// <summary>
|
||||
/// Define a field/property to use for generating the <see cref="object.GetHashCode"/> method.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use in conjonction with <see cref="GeneratedEqualityAttribute"/>.
|
||||
/// * If this attribute is not used, you must manually define a <see cref="object.GetHashCode"/> attribute
|
||||
/// * You can put this attribute to more than one member of your class. They will all be used for HashCode calculation.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
|
||||
public class EqualityHashAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// ******************************************************************
|
||||
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// ******************************************************************
|
||||
using System;
|
||||
|
||||
namespace Uno
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't use this field/property for equality generation
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Can be placed on a member (field or property) or on a type (class/struct).
|
||||
/// When placed on a type, means this value will never be used for equality generation.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Struct, Inherited = true, AllowMultiple = false)]
|
||||
public class EqualityIgnoreAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// ******************************************************************
|
||||
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// ******************************************************************
|
||||
using System;
|
||||
|
||||
namespace Uno
|
||||
{
|
||||
/// <summary>
|
||||
/// Define a field/property to use for generating the <see cref="object.GetHashCode"/> method.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use in conjonction with <see cref="GeneratedEqualityAttribute"/>.
|
||||
/// * If this attribute is not used, you must manually define a <see cref="object.GetHashCode"/> attribute
|
||||
/// * You can put this attribute to more than one member of your class. They will all be used for HashCode calculation.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
|
||||
public class EqualityKeyAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// ******************************************************************
|
||||
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// ******************************************************************
|
||||
using System;
|
||||
|
||||
namespace Uno
|
||||
{
|
||||
/// <summary>
|
||||
/// Instruct the source code generator to operate on this target class (or struct)
|
||||
/// to generate equality members.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true, AllowMultiple = false)]
|
||||
public class GeneratedEqualityAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.0;netstandard2.0;net46</TargetFrameworks>
|
||||
<Product>Equality Declarations</Product>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
<TreatSpecificWarningsAsErrors />
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
<Authors>nventive</Authors>
|
||||
<Company>nventive</Company>
|
||||
<Description>This package provides attributes for Equality source code generation.
|
||||
This package is part of the Uno.CodeGen to generate equality members in your project.</Description>
|
||||
<RootNamespace>Uno</RootNamespace>
|
||||
<Copyright>Copyright (C) 2015-2018 nventive inc. - all rights reserved</Copyright>
|
||||
<PackageProjectUrl>https://github.com/nventive/Uno.CodeGen</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/nventive/Uno.CodeGen</RepositoryUrl>
|
||||
<PackageIconUrl>https://nv-assets.azurewebsites.net/logos/uno.png</PackageIconUrl>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,32 @@
|
|||
// ******************************************************************
|
||||
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// ******************************************************************
|
||||
using System;
|
||||
|
||||
namespace Uno
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the code-gen to generate immutability builders for the target class
|
||||
/// </summary>
|
||||
[System.AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
||||
public sealed class GeneratedImmutableAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// If the generation code for Equality should be generated at the same time.
|
||||
/// </summary>
|
||||
public bool GenerateEquality { get; set; } = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// ******************************************************************
|
||||
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// ******************************************************************
|
||||
namespace Uno
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the builder of an immutable type.
|
||||
/// </summary>
|
||||
public interface IImmutableBuilder<out TImmutable>
|
||||
{
|
||||
#if !NETSTANDARD1_0
|
||||
[System.Diagnostics.Contracts.Pure]
|
||||
#endif
|
||||
TImmutable ToImmutable();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// ******************************************************************
|
||||
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// ******************************************************************
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Uno
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the attributed class is immutable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This attribute is meant to be used by Roslyn analyzers to validate for immutability.
|
||||
/// </remarks>
|
||||
[System.AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
||||
public sealed class ImmutableAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// ******************************************************************
|
||||
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// ******************************************************************
|
||||
using System;
|
||||
|
||||
namespace Uno
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the type of the builder for this class.
|
||||
/// The type must implement <see cref="IImmutableBuilder{TImmutable}"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This attribute is added on generated code and is also used by some code generators
|
||||
/// to find the builder to use for creating the entity.
|
||||
/// </remarks>
|
||||
[System.AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
||||
public sealed class ImmutableBuilderAttribute : Attribute
|
||||
{
|
||||
public Type BuilderType { get; }
|
||||
|
||||
public ImmutableBuilderAttribute(Type builderType)
|
||||
{
|
||||
BuilderType = builderType;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.0;netstandard2.0;net46</TargetFrameworks>
|
||||
<Product>Immutable Declarations</Product>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
<TreatSpecificWarningsAsErrors />
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
<Authors>nventive</Authors>
|
||||
<Company>nventive</Company>
|
||||
<Description>This package provides attributes for immutable entities source code generation.
|
||||
This package is part of the Uno.CodeGen to generate immutable entities in your project.</Description>
|
||||
<RootNamespace>Uno</RootNamespace>
|
||||
<PackageProjectUrl>https://github.com/nventive/Uno.CodeGen</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/nventive/Uno.CodeGen</RepositoryUrl>
|
||||
<PackageIconUrl>https://nv-assets.azurewebsites.net/logos/uno.png</PackageIconUrl>
|
||||
<Copyright>Copyright (C) 2015-2018 nventive inc. - all rights reserved</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Uno.Equality\Uno.Equality.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,24 @@
|
|||
<Project ToolsVersion="15.0">
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
This file is used to control the platforms compiled by visual studio, and
|
||||
allow for a faster build when testing for a single platform.
|
||||
|
||||
e.g. when compiling for unit tests, set the TargetPlatforms to net46.
|
||||
|
||||
Instructions:
|
||||
1) Copy this file and remove the ".sample" extension
|
||||
2) Adjust the TargetFrameworks properties below
|
||||
3) Make sure to do a Rebuild, so that nuget restores the proper packages for the new target
|
||||
|
||||
Original frameworks: Xamarin.iOS10;MonoAndroid71;uap10.0;net46
|
||||
-->
|
||||
<TargetFrameworks>net46;Xamarin.iOS10</TargetFrameworks>
|
||||
|
||||
<!--
|
||||
This property allows the override of the nuget local cache.
|
||||
Set it to the version you want to override, used in another app.
|
||||
-->
|
||||
<!--<UmbrellaNugetOverrideVersion>2.23.2-dev.667</UmbrellaNugetOverrideVersion>-->
|
||||
</PropertyGroup>
|
||||
</Project>
|
Загрузка…
Ссылка в новой задаче