Merge pull request #1 from nventive/dev/cdb/initial-publish

Initial commit
This commit is contained in:
Jérôme Laban 2018-02-02 08:13:09 -05:00 коммит произвёл GitHub
Родитель 49bcc0ab3d 574d37cf45
Коммит 206bea22e1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
34 изменённых файлов: 3160 добавлений и 3 удалений

31
.appveyor.yml Normal file
Просмотреть файл

@ -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/

63
.gitattributes поставляемый Normal file
Просмотреть файл

@ -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

232
.gitignore поставляемый Normal file
Просмотреть файл

@ -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

18
build/AssemblyVersion.cs Normal file
Просмотреть файл

@ -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")]

3
build/UpdateHeaders.cmd Normal file
Просмотреть файл

@ -0,0 +1,3 @@
@ECHO OFF
PowerShell.exe -file build.ps1 -target=UpdateHeaders -Verbosity Verbose
PAUSE

145
build/build.cake Normal file
Просмотреть файл

@ -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);

235
build/build.ps1 Normal file
Просмотреть файл

@ -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

16
build/header.txt Normal file
Просмотреть файл

@ -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.
//
// ******************************************************************

231
doc/Equality Generation.md Normal file
Просмотреть файл

@ -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 &lt;Field/Property name&gt; + "`_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 &lt;Field/Propertyname&gt;`_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.

193
doc/Immutable Generation.md Normal file
Просмотреть файл

@ -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.

15
gitversion.yml Normal file
Просмотреть файл

@ -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: []

Просмотреть файл

@ -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>

53
src/Uno.CodeGen.sln Normal file
Просмотреть файл

@ -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&lt;T1, T2&gt;
var symbolForXml = $"{symbolName}&lt;{arguments}&gt;";
// 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>

81
src/Uno.Common.props Normal file
Просмотреть файл

@ -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>