Adding support for Platform Specific Differences generator, Roslyn analyzer and documentation

This commit is contained in:
Hermit Dave 2018-05-11 15:34:10 +01:00
Родитель 9766eb794d
Коммит 23b1208ec2
22 изменённых файлов: 1762 добавлений и 1 удалений

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

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
</configuration>

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

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{292D34E8-0F01-4FA8-951D-8232F75A88D5}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>DifferencesGen</RootNamespace>
<AssemblyName>DifferencesGen</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json">
<Version>10.0.3</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="Pack">
<!-- No-op to avoid build error when packing solution from commandline -->
</Target>
</Project>

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

@ -0,0 +1,218 @@
// ******************************************************************
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
// ******************************************************************
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Web.Script.Serialization;
using Newtonsoft.Json;
namespace DifferencesGen
{
class Program
{
static string path = @"D:\UwpApi";
static void Main(string[] args)
{
string min = null;
string max = null;
foreach (var arg in args)
{
if (arg.StartsWith("/min:"))
{
min = arg.Replace("/min:", "");
}
else if (arg.StartsWith("/max:"))
{
max = arg.Replace("/max:", "");
}
}
Version minVersion = null;
Version maxVersion = null;
Version.TryParse(min, out minVersion);
Version.TryParse(max, out maxVersion);
if (minVersion == null || maxVersion == null)
{
Console.WriteLine("The differences generator needs to be run as follows:");
Console.WriteLine("DifferencesGen /min:4.0.0.0 /max:5.0.0.0");
return;
}
string folderPath = @"C:\Program Files (x86)\Windows Kits\10\References";
string universalApiFile = "Windows.Foundation.UniversalApiContract.winmd";
string universalApiDifferencesCompressedFile = "Differences-{0}.gz";
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (sender, eventArgs) => Assembly.ReflectionOnlyLoad(eventArgs.Name);
WindowsRuntimeMetadata.ReflectionOnlyNamespaceResolve += (sender, eventArgs) =>
{
string path =
WindowsRuntimeMetadata.ResolveNamespace(eventArgs.NamespaceName, Enumerable.Empty<string>())
.FirstOrDefault();
if (path == null)
{
return;
}
eventArgs.ResolvedAssemblies.Add(Assembly.ReflectionOnlyLoadFrom(path));
};
DirectoryInfo directoryInfo = new DirectoryInfo(folderPath);
FileInfo[] files = directoryInfo.GetFiles(universalApiFile, SearchOption.AllDirectories);
List<Tuple<Version, Assembly>> assemblyList = new List<Tuple<Version, Assembly>>();
if (files.Length > 0)
{
foreach (var file in files)
{
var assembly = Assembly.ReflectionOnlyLoadFrom(file.FullName);
var nameParts = assembly.FullName.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries);
var versionParts = nameParts[1].Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
var version = Version.Parse(versionParts[1]);
if (version >= minVersion && version <= maxVersion)
{
assemblyList.Add(new Tuple<Version, Assembly>(version, assembly));
}
}
}
if (assemblyList.Count >= 2)
{
var orderedList = assemblyList.OrderBy(t => t.Item1).ToList();
for (int i = 1; i < orderedList.Count; i++)
{
var previousVersionAssembly = orderedList[i - 1].Item2;
var newerVersionAssembly = orderedList[i].Item2;
var version = orderedList[i].Item1;
var previousVersionTypes = ProcessAssembly(previousVersionAssembly);
var newerVersionTypes = ProcessAssembly(newerVersionAssembly);
var addedTypes = new Dictionary<string, List<string>>();
foreach (var type in newerVersionTypes)
{
if (!previousVersionTypes.ContainsKey(type.Key))
{
addedTypes.Add(type.Key, null);
continue;
}
HashSet<string> previousVersionTypeMembers = new HashSet<string>(previousVersionTypes[type.Key]);
HashSet<string> newerVersionTypeMembers = new HashSet<string>(type.Value);
newerVersionTypeMembers.ExceptWith(previousVersionTypeMembers);
if (newerVersionTypeMembers.Count == 0)
{
continue;
}
addedTypes.Add(type.Key, newerVersionTypeMembers.ToList());
}
StringBuilder stringBuilder = new StringBuilder();
using (var compressedFS = File.Create(Path.Combine(path, string.Format(universalApiDifferencesCompressedFile, version.ToString()))))
{
using (var compressionFS = new GZipStream(compressedFS, CompressionMode.Compress))
{
using (var writer = new StreamWriter(compressionFS))
{
foreach (var addedType in addedTypes)
{
stringBuilder.Clear();
stringBuilder.Append(addedType.Key);
if (addedType.Value != null && addedType.Value.Count > 0)
{
stringBuilder.Append(':');
stringBuilder.Append(string.Join(",", addedType.Value));
}
writer.WriteLine(stringBuilder.ToString());
}
}
}
}
stringBuilder.Length = 0;
}
}
}
private static Dictionary<string, List<string>> ProcessAssembly(Assembly assembly)
{
int pos = assembly.FullName.IndexOf(", Culture");
string fileName = $"{assembly.FullName.Substring(0, pos)}.json";
Dictionary<string, List<string>> types = new Dictionary<string, List<string>>();
foreach (var exportedType in assembly.ExportedTypes)
{
List<string> members = new List<string>();
foreach (var methodInfo in exportedType.GetMethods())
{
if (!methodInfo.IsPublic)
{
continue;
}
if (methodInfo.Name.StartsWith("get_") ||
methodInfo.Name.StartsWith("set_") ||
methodInfo.Name.StartsWith("put_") ||
methodInfo.Name.StartsWith("add_") ||
methodInfo.Name.StartsWith("remove_")
)
{
continue;
}
members.Add($"{methodInfo.Name}#{methodInfo.GetParameters().Length}");
}
foreach (var propertyInfo in exportedType.GetProperties())
{
members.Add(propertyInfo.Name);
}
types.Add(exportedType.FullName, members);
}
return types;
}
}
}

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

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -->
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MinimumVisualStudioVersion>15.0</MinimumVisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<FileUpgradeFlags>
</FileUpgradeFlags>
<OldToolsVersion>14.0</OldToolsVersion>
<UpgradeBackupLocation />
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
<Platform Condition="'$(Platform)' == ''">AnyCPU</Platform>
<PlatformTarget>AnyCPU</PlatformTarget>
<SchemaVersion>2.0</SchemaVersion>
<ProjectTypeGuids>{82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<ProjectGuid>{1603913D-6E19-4E76-ADFC-78206F68CB90}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer.Vsix</RootNamespace>
<AssemblyName>Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<GeneratePkgDefFile>false</GeneratePkgDefFile>
<IncludeAssemblyInVSIXContainer>false</IncludeAssemblyInVSIXContainer>
<IncludeDebugSymbolsInVSIXContainer>false</IncludeDebugSymbolsInVSIXContainer>
<IncludeDebugSymbolsInLocalVSIXDeployment>false</IncludeDebugSymbolsInLocalVSIXDeployment>
<CopyBuildOutputToOutputDirectory>false</CopyBuildOutputToOutputDirectory>
<CopyOutputSymbolsToOutputDirectory>false</CopyOutputSymbolsToOutputDirectory>
<VSSDKTargetPlatformRegRootSuffix>Roslyn</VSSDKTargetPlatformRegRootSuffix>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartAction>Program</StartAction>
<StartProgram>$(DevEnvDir)devenv.exe</StartProgram>
<StartArguments>/rootsuffix Roslyn</StartArguments>
</PropertyGroup>
<ItemGroup>
<None Include="source.extension.vsixmanifest">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer\Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer.csproj">
<Project>{B4C07B76-E049-4B42-BF42-102BA78E87DD}</Project>
<Name>Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.6.1">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.6.1 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

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

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
<Metadata>
<Identity Id="Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer.d75b986c-6c7c-4f29-a2c4-2d873af77c70" Version="1.0" Language="en-US" Publisher="hermi"/>
<DisplayName>Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer</DisplayName>
<Description xml:space="preserve">This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn").</Description>
</Metadata>
<Installation>
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[15.0,)" />
</Installation>
<Dependencies>
<Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.5,)" />
</Dependencies>
<Assets>
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer" Path="|Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer|"/>
<Asset Type="Microsoft.VisualStudio.Analyzer" d:Source="Project" d:ProjectName="Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer" Path="|Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer|"/>
</Assets>
<Prerequisites>
<Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[15.0,16.0)" DisplayName="Visual Studio core editor" />
<Prerequisite Id="Microsoft.VisualStudio.Component.Roslyn.LanguageServices" Version="[15.0,16.0)" DisplayName="Roslyn Language Services" />
</Prerequisites>
</PackageManifest>

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

@ -0,0 +1,166 @@
// ******************************************************************
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
// ******************************************************************
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
public static class Analyzer
{
public static readonly DiagnosticDescriptor PlatformRule = new DiagnosticDescriptor("UWP001", "Platform-specific", "Platform-specific code detected. Consider using ApiInformation.IsTypePresent to guard against failure", "Safety", DiagnosticSeverity.Warning, true);
public static readonly DiagnosticDescriptor VersionRule = new DiagnosticDescriptor("UWP002", "Version-specific", "Version-specific code detected. Consider using ApiInformation.IsTypePresent / ApiInformation.IsMethodPresent / ApiInformation.IsPropertyPresent to guard against failure", "Safety", DiagnosticSeverity.Warning, true);
public const string N1DifferencesRes = "Differences-5.0.0.0.gz";
public const string N0DifferencesRes = "Differences-6.0.0.0.gz";
public const string N2SDKVersion = "15063";
public const string N1SDKVersion = "16299";
public const string N0SDKVersion = "17134";
private static char[] typeMemberSeparator = { ':' };
private static char[] memberSeparator = { ',' };
public static Dictionary<string, List<NewMember>> GetUniversalApiAdditions(string resourceName)
{
Dictionary<string, List<NewMember>> apiAdditionsDictionary = new Dictionary<string, List<NewMember>>();
Assembly assembly = typeof(Analyzer).GetTypeInfo().Assembly;
var resource = assembly.GetManifestResourceStream("PlatformSpecific." + resourceName);
if (resource == null)
{
System.Diagnostics.Debug.WriteLine($"Resource {resourceName} not found.");
return apiAdditionsDictionary;
}
System.Diagnostics.Debug.WriteLine($"Resource {resourceName} found.");
Dictionary<string, List<string>> differencesDictionary = new Dictionary<string, List<string>>();
using (GZipStream decompressionStream = new GZipStream(resource, CompressionMode.Decompress))
{
using (StreamReader reader = new StreamReader(decompressionStream))
{
while (!reader.EndOfStream)
{
var typeDetails = reader.ReadLine();
var typeMemberParts = typeDetails.Split(typeMemberSeparator, StringSplitOptions.RemoveEmptyEntries);
if (typeMemberParts.Length == 1)
{
differencesDictionary.Add(typeMemberParts[0], null);
continue;
}
var membersAddedToType = typeMemberParts[1].Split(memberSeparator, StringSplitOptions.RemoveEmptyEntries);
differencesDictionary.Add(typeMemberParts[0], new List<string>(membersAddedToType));
}
}
}
if (differencesDictionary == null)
{
return apiAdditionsDictionary;
}
foreach (var kvp in differencesDictionary)
{
var list = new List<NewMember>();
if (kvp.Value != null)
{
list.AddRange(kvp.Value.Select(v => new NewMember(v)));
}
apiAdditionsDictionary.Add(kvp.Key, list);
}
return apiAdditionsDictionary;
}
public static int GetTargetPlatformMinVersion(ImmutableArray<AdditionalText> additionalFiles)
{
// When PlatformSpecificAnalyzer is build as a NuGet package, the package includes
// a.targets File with the following lines. The effect is to add a fake file,
// which doesn't show up in SolnExplorer and which doesn't even exist, but whose
// FILENAME encodes the TargetPlatformMinVersion. That way, when the user modifies
// TargetPlatformMinVersion from within the ProjectProperties, msbuild re-evaluates
// the AdditionalFiles, and Roslyn re-runs its analyzers and can pick it up.
// Thanks Jason Malinowski for the hint on how to do this. He instructed me to
// write in the comments "this is a terrible hack and no one should ever copy it".
// <AdditionalFileItemNames>PlatformSpecificAnalyzerInfo</AdditionalFileItemNames>
// <ItemGroup>
// <PlatformSpecificAnalyzerInfo Include = "tpmv_$(TargetPlatformMinVersion).tpmv"><Visible>False</Visible></PlatformSpecificAnalyzerInfo>
// </ItemGroup>
// I'm caching the value because, heck, it seems weird to recompute it every time.
ImmutableArray<AdditionalText> cacheKey = default(ImmutableArray<AdditionalText>);
int minSDK = int.Parse(N2SDKVersion);
int cacheValue = minSDK;
// if we don't find that terrible hack, assume min version of sdk
if (additionalFiles == cacheKey)
{
return cacheValue;
}
else
{
cacheKey = additionalFiles;
}
var tpmv = additionalFiles.FirstOrDefault(af => af.Path.EndsWith(".tpmv"))?.Path;
if (tpmv == null)
{
cacheValue = minSDK;
}
else
{
tpmv = Path.GetFileNameWithoutExtension(tpmv).Replace("tpmv_10.0.", string.Empty).Replace(".0", string.Empty);
cacheValue = int.TryParse(tpmv, out int i) ? i : cacheValue;
}
return cacheValue;
}
public static string GetPlatformSpecificAttribute(ISymbol symbol)
{
if (symbol == null)
{
return null;
}
foreach (var attr in symbol.GetAttributes())
{
if (attr.AttributeClass.Name.EndsWith("SpecificAttribute"))
{
return attr.AttributeClass.ToDisplayString().Replace("Attribute", string.Empty);
}
}
return null;
}
public static bool HasPlatformSpecificAttribute(ISymbol symbol)
{
return GetPlatformSpecificAttribute(symbol) != null;
}
}
}

Двоичный файл не отображается.

Двоичный файл не отображается.

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

@ -0,0 +1,129 @@
// ******************************************************************
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
// ******************************************************************
using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
public class HowToGuard
{
public string TypeToCheck { get; set; }
public string MemberToCheck { get; set; }
public int? ParameterCountToCheck { get; set; }
public string KindOfCheck { get; set; }
public string AttributeToIntroduce { get; set; }
public string AttributeFriendlyName { get; set; }
public HowToGuard()
{
KindOfCheck = "IsTypePresent";
AttributeToIntroduce = "System.Runtime.CompilerServices.PlatformSpecific";
AttributeFriendlyName = "PlatformSpecific";
}
/// <summary>
/// returns instance of <see cref="HowToGuard"/> for <see cref="ISymbol"/>
/// </summary>
/// <param name="target">instance of <see cref="ISymbol"/></param>
/// <returns>instance of <see cref="HowToGuard"/></returns>
public static HowToGuard Symbol(ISymbol target)
{
var plat = Platform.OfSymbol(target);
if (plat.Kind == PlatformKind.User)
{
var lastDot = plat.Version.LastIndexOf('.');
var attrName = lastDot == -1 ? plat.Version : plat.Version.Substring(lastDot + 1);
return new HowToGuard()
{
AttributeToIntroduce = plat.Version,
AttributeFriendlyName = attrName,
TypeToCheck = "??"
};
}
else if (plat.Kind == PlatformKind.ExtensionSDK)
{
return new HowToGuard()
{
TypeToCheck = target.Kind == SymbolKind.NamedType ? target.ToDisplayString() : target.ContainingType.ToDisplayString()
};
}
else if (plat.Kind == PlatformKind.Uwp && target.Kind == SymbolKind.NamedType)
{
return new HowToGuard()
{
TypeToCheck = target.ToDisplayString()
};
}
else if (plat.Kind == PlatformKind.Uwp && target.Kind != SymbolKind.NamedType)
{
var g = new HowToGuard
{
TypeToCheck = target.ContainingType.ToDisplayString()
};
var d0 = Analyzer.GetUniversalApiAdditions(Analyzer.N0DifferencesRes);
var d1 = Analyzer.GetUniversalApiAdditions(Analyzer.N1DifferencesRes);
if (!d0.TryGetValue(g.TypeToCheck, out List<NewMember> newMembers))
{
d1.TryGetValue(g.TypeToCheck, out newMembers);
}
if (newMembers == null)
{
throw new InvalidOperationException("oops! expected this UWP version API to be in the dictionary of new things");
}
g.MemberToCheck = target.Name;
if (target.Kind == SymbolKind.Field)
{
// the only fields in WinRT are enum fields
g.KindOfCheck = "IsEnumNamedValuePresent";
}
else if (target.Kind == SymbolKind.Event)
{
g.KindOfCheck = "IsEventPresent";
}
else if (target.Kind == SymbolKind.Property)
{
// TODO: if SDK starts introducing additional accessors on properties, we'll have to change this
g.KindOfCheck = "IsPropertyPresent";
}
else if (target.Kind == SymbolKind.Method)
{
g.KindOfCheck = "IsMethodPresent";
if (target.Kind == SymbolKind.Method && plat.ByParameterCount)
{
g.ParameterCountToCheck = (target as IMethodSymbol).Parameters.Length;
}
}
return g;
}
throw new InvalidOperationException("oops! don't know why I was asked to check something that's fine");
}
}
}

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

@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
</PropertyGroup>
<PropertyGroup>
<PackageId>Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer</PackageId>
<PackageVersion>1.0.0.0</PackageVersion>
<Authors>hermi</Authors>
<PackageLicenseUrl>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE</PackageLicenseUrl>
<PackageProjectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</PackageProjectUrl>
<PackageIconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</PackageIconUrl>
<RepositoryUrl>http://REPOSITORY_URL_HERE_OR_DELETE_THIS_LINE</RepositoryUrl>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<Description>Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer</Description>
<PackageReleaseNotes>Summary of changes made in this release of the package.</PackageReleaseNotes>
<Copyright>Copyright</Copyright>
<PackageTags>Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer, analyzers</PackageTags>
<NoPackageAnalysis>true</NoPackageAnalysis>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Differences-5.0.0.0.gz" />
<EmbeddedResource Include="Differences-6.0.0.0.gz" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.4.0" PrivateAssets="all" />
<PackageReference Update="NETStandard.Library" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Update="tools\*.ps1" CopyToOutputDirectory="Always" Pack="true" PackagePath="" />
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
</Project>

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

@ -0,0 +1,41 @@
// ******************************************************************
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
// ******************************************************************
using System;
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
public struct NewMember
{
private static char[] methodCountSeparator = { '#' };
public string Name;
public int? ParameterCount;
public NewMember(string s)
{
string[] parts = s.Split(methodCountSeparator, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 2)
{
Name = parts[0];
ParameterCount = int.Parse(s.Substring(s.Length - 1));
}
else
{
Name = s;
ParameterCount = null;
}
}
}
}

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

@ -0,0 +1,247 @@
// ******************************************************************
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
// ******************************************************************
using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
/// <summary>
///
/// </summary>
public struct Platform
{
/// <summary>
/// Platform Kind
/// </summary>
public PlatformKind Kind;
/// <summary>
/// For UWP, this is version 10240 or 10586 etc. For User, the fully qualified name of the attribute in use
/// </summary>
public string Version;
/// <summary>
/// For UWP only
/// </summary>
public bool ByParameterCount;
/// <summary>
/// Initializes a new instance of the <see cref="Platform"/> struct.
/// </summary>
/// <param name="kind"><see cref="PlatformKind"/></param>
/// <param name="version">version</param>
/// <param name="byParameterCount">boolean</param>
public Platform(PlatformKind kind, string version = null, bool byParameterCount = false)
{
Kind = kind;
Version = version;
ByParameterCount = byParameterCount;
switch (kind)
{
case PlatformKind.Unchecked:
if (version != null)
{
throw new ArgumentException("No version expected");
}
break;
case PlatformKind.Uwp:
break;
case PlatformKind.ExtensionSDK:
if (version != null)
{
throw new ArgumentException("Don't specify versions for extension SDKs");
}
break;
case PlatformKind.User:
if (version != null && !version.EndsWith("Specific"))
{
throw new ArgumentException("User specific should end in Specific");
}
break;
}
if (byParameterCount && kind != PlatformKind.Uwp)
{
throw new ArgumentException("Only UWP can be distinguished by parameter count");
}
}
/// <summary>
/// This function tells which version/platform the symbol is from.
/// </summary>
/// <param name="symbol">represents a compiler <see cref="ISymbol"/></param>
/// <returns>instance of <see cref="Platform"/></returns>
public static Platform OfSymbol(ISymbol symbol)
{
// This function is hard-coded with knowledge up to SDK 10586.
// I could have made it a general-purpose function which looks up the SDK
// files on disk. But I think it's more elegant to hard-code it into the analyzer,
// so as to reduce disk-access while the analyzer runs.
if (symbol == null)
{
return new Platform(PlatformKind.Unchecked);
}
if (symbol.ContainingNamespace != null && symbol.ContainingNamespace.ToDisplayString().StartsWith("Windows."))
{
var assembly = symbol.ContainingAssembly.Name;
var version = symbol.ContainingAssembly.Identity.Version.Major;
// Any call to ApiInformation.* is allowed without warning
if (symbol.ContainingType?.Name == "ApiInformation")
{
return new Platform(PlatformKind.Uwp, Analyzer.N2SDKVersion);
}
// Don't want to give warning when analyzing code in an PCL project.
// In those two targets, every Windows type is found in Windows.winmd, so that's how we'll suppress it:
if (assembly == "Windows")
{
return new Platform(PlatformKind.Unchecked);
}
// Some WinRT types like Windows.UI.Color get projected to come from .NET assemblies, always present:
if (assembly.StartsWith("System.Runtime."))
{
return new Platform(PlatformKind.Uwp, Analyzer.N2SDKVersion);
}
// Some things are emphatically part of UWP.10240
if (assembly == "Windows.Foundation.FoundationContract" || (assembly == "Windows.Foundation.UniversalApiContract" && version == 1))
{
return new Platform(PlatformKind.Uwp, Analyzer.N2SDKVersion);
}
if (assembly == "Windows.Foundation.UniversalApiContract")
{
var isType = symbol.Kind == SymbolKind.NamedType;
var typeName = isType ? symbol.ToDisplayString() : symbol.ContainingType.ToDisplayString();
bool? presentInN0ApiDiff = CheckCollectionForType(Analyzer.GetUniversalApiAdditions(Analyzer.N0DifferencesRes), typeName, symbol);
if (presentInN0ApiDiff == null)
{
// the entire type was found in Target Version
return new Platform(PlatformKind.Uwp, Analyzer.N0SDKVersion);
}
else if (presentInN0ApiDiff.Value)
{
// the entire type was found in Target Version with matching parameter lengths
return new Platform(PlatformKind.Uwp, Analyzer.N0SDKVersion, true);
}
else
{
bool? presentInN1ApiDiff = CheckCollectionForType(Analyzer.GetUniversalApiAdditions(Analyzer.N1DifferencesRes), typeName, symbol);
if (presentInN1ApiDiff == null)
{
// the entire type was found in Target Version
return new Platform(PlatformKind.Uwp, Analyzer.N1SDKVersion);
}
else if (presentInN1ApiDiff.Value)
{
// the entire type was found in Target Version with matching parameter lengths
return new Platform(PlatformKind.Uwp, Analyzer.N1SDKVersion, true);
}
else
{
// the type was in Min version
return new Platform(PlatformKind.Uwp, Analyzer.N2SDKVersion);
}
}
}
// All other Windows.* types come from platform-specific extensions
return new Platform(PlatformKind.ExtensionSDK);
}
else
{
var attr = GetPlatformSpecificAttribute(symbol);
if (attr != null)
{
return new Platform(PlatformKind.User, attr);
}
return new Platform(PlatformKind.Unchecked);
}
}
private static bool? CheckCollectionForType(Dictionary<string, List<NewMember>> collection, string typeName, ISymbol symbol)
{
List<NewMember> newMembers = null;
// the entire type was new in this collection
if (!collection.TryGetValue(typeName, out newMembers) || newMembers == null || newMembers.Count == 0)
{
return null;
}
if (symbol.Kind == SymbolKind.NamedType)
{
return false;
}
var memberName = symbol.Name;
foreach (var newMember in newMembers)
{
if (memberName == newMember.Name && !newMember.ParameterCount.HasValue)
{
return null;
}
// this member was new in collection
if (symbol.Kind != SymbolKind.Method)
{
// TODO: Continue For... Warning!!! not translated
}
if (memberName == newMember.Name && ((IMethodSymbol)symbol).Parameters.Length == newMember.ParameterCount)
{
return true;
}
}
// this member existed in a different collection
return false;
}
private static string GetPlatformSpecificAttribute(ISymbol symbol)
{
if (symbol == null)
{
return null;
}
foreach (var attr in symbol.GetAttributes())
{
if (attr.AttributeClass.Name.EndsWith("SpecificAttribute"))
{
return attr.AttributeClass.ToDisplayString().Replace("Attribute", string.Empty);
}
}
return null;
}
}
}

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

@ -0,0 +1,40 @@
// ******************************************************************
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
// ******************************************************************
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
/// <summary>
/// Enum
/// </summary>
public enum PlatformKind
{
/// <summary>
/// .NET and Pre-UWP WinRT
/// </summary>
Unchecked,
/// <summary>
/// Core UWP platform
/// </summary>
Uwp,
/// <summary>
/// Desktop, Mobile, IOT, Xbox extension SDK
/// </summary>
ExtensionSDK,
/// <summary>
/// User specified *Specific attribute on something
/// </summary>
User,
}
}

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

@ -0,0 +1,252 @@
// ******************************************************************
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
// ******************************************************************
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class PlatformSpecificAnalyzerCS : DiagnosticAnalyzer
{
// You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization
private const string Category = "Safety";
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Analyzer.PlatformRule, Analyzer.VersionRule); } }
public override void Initialize(AnalysisContext context)
{
// TODO: Consider registering other actions that act on syntax instead of or in addition to symbols
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information
ConcurrentDictionary<int, Diagnostic> reportsDictionary = new ConcurrentDictionary<int, Diagnostic>();
context.RegisterSyntaxNodeAction((c) => AnalyzeExpression(c, reportsDictionary), SyntaxKind.IdentifierName, SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.QualifiedName);
}
private void AnalyzeExpression(SyntaxNodeAnalysisContext context, ConcurrentDictionary<int, Diagnostic> reports)
{
var parentKind = context.Node.Parent.Kind();
// will be handled at higher level
if (parentKind == SyntaxKind.SimpleMemberAccessExpression || parentKind == SyntaxKind.QualifiedName)
{
return;
}
var target = GetTargetOfNode(context.Node, context.SemanticModel);
if (target == null)
{
return;
}
var platform = Platform.OfSymbol(target);
// Some quick escapes
if (platform.Kind == PlatformKind.Unchecked)
{
return;
}
if (platform.Kind == PlatformKind.Uwp && platform.Version == Analyzer.N2SDKVersion)
{
return;
}
// Is this expression inside a method/constructor/property that claims to be specific?
var containingBlock = context.Node.FirstAncestorOrSelf<BlockSyntax>();
// for constructors and methods
MemberDeclarationSyntax containingMember = containingBlock?.FirstAncestorOrSelf<BaseMethodDeclarationSyntax>();
if (containingBlock == null || containingBlock?.Parent is AccessorDeclarationSyntax)
{
containingMember = context.Node.FirstAncestorOrSelf<PropertyDeclarationSyntax>();
}
if (containingMember != null)
{
var containingMemberSymbol = context.SemanticModel.GetDeclaredSymbol(containingMember);
if (Analyzer.HasPlatformSpecificAttribute(containingMemberSymbol))
{
return;
}
}
// Is this invocation properly guarded? See readme.md for explanations.
if (IsProperlyGuarded(context.Node, context.SemanticModel))
{
return;
}
if (containingBlock != null)
{
foreach (var ret in containingBlock.DescendantNodes().OfType<ReturnStatementSyntax>())
{
if (IsProperlyGuarded(ret, context.SemanticModel))
{
return;
}
}
}
// Some things we can't judge whether to report until after we've looked up the project version...
//if (platform.Kind == PlatformKind.Uwp && platform.Version != Analyzer.N2SDKVersion)
//{
// var projMinVersion = Analyzer.GetTargetPlatformMinVersion(context.Options.AdditionalFiles);
// if (projMinVersion >= Convert.ToInt32(platform.Version))
// {
// return;
// }
//}
// We'll report only a single diagnostic per line, the first.
var loc = context.Node.GetLocation();
if (!loc.IsInSource)
{
return;
}
var line = loc.GetLineSpan().StartLinePosition.Line;
Diagnostic diagnostic = null;
if (reports.TryGetValue(line, out diagnostic) && diagnostic.Location.SourceSpan.Start <= loc.SourceSpan.Start)
{
return;
}
diagnostic = Diagnostic.Create(platform.Kind == PlatformKind.Uwp ? Analyzer.VersionRule : Analyzer.PlatformRule, loc);
reports[line] = diagnostic;
context.ReportDiagnostic(diagnostic);
}
public static ISymbol GetTargetOfNode(SyntaxNode node, SemanticModel semanticModel)
{
var parentKind = node.Parent.Kind();
if (parentKind == SyntaxKind.InvocationExpression && node == ((InvocationExpressionSyntax)node.Parent).Expression)
{
// <target>(...)
// points to the method after overload resolution
return semanticModel.GetSymbolInfo((InvocationExpressionSyntax)node.Parent).Symbol;
}
else if (parentKind == SyntaxKind.ObjectCreationExpression && node == ((ObjectCreationExpressionSyntax)node.Parent).Type)
{
// New <target>
var objectCreationExpression = (ObjectCreationExpressionSyntax)node.Parent;
var target = semanticModel.GetSymbolInfo(objectCreationExpression).Symbol;
// points to the constructor after overload resolution
return target;
}
else
{
// f<target>(...)
// <target> x = ...
// Action x = <target> -- note that following code does pick the right overload
// <target> += delegate -- the following code does recognize events
// nameof(<target>) -- I think it's nicer to report on this, even if not technically needed
// Field access? I'll disallow it for enum values, and allow it for everything else
var target = semanticModel.GetSymbolInfo(node).Symbol;
if (target == null)
{
return null;
}
if (target.Kind == SymbolKind.Method || target.Kind == SymbolKind.Event || target.Kind == SymbolKind.Property)
{
return target;
}
if (target.Kind == SymbolKind.Field && target.ContainingType.TypeKind == TypeKind.Enum)
{
return target;
}
return null;
}
}
private bool IsProperlyGuarded(SyntaxNode node, SemanticModel semanticModel)
{
foreach (var symbol in GetGuards(node, semanticModel))
{
if (symbol.ContainingType?.Name == "ApiInformation")
{
return true;
}
if (Analyzer.HasPlatformSpecificAttribute(symbol))
{
return true;
}
}
return false;
}
public static IEnumerable<ISymbol> GetGuards(SyntaxNode node, SemanticModel semanticModel)
{
foreach (var condition in GetConditions(node))
{
// First check for invocations of ApiInformation.IsTypePresent
foreach (var invocation in condition.DescendantNodesAndSelf(i => i is InvocationExpressionSyntax))
{
var targetMethod = semanticModel.GetSymbolInfo(invocation).Symbol;
if (targetMethod?.ContainingType?.Name == "ApiInformation")
{
yield return targetMethod;
}
}
// Next check for any property/field access
var accesses1 = condition.DescendantNodesAndSelf(d => d is MemberAccessExpressionSyntax).Select(n => semanticModel.GetSymbolInfo(n).Symbol);
var accesses2 = condition.DescendantNodesAndSelf(d => d is IdentifierNameSyntax).Select(n => semanticModel.GetSymbolInfo(n).Symbol);
foreach (var symbol in accesses1.Concat(accesses2))
{
if (symbol?.Kind == SymbolKind.Field || symbol?.Kind == SymbolKind.Property)
{
yield return symbol;
}
}
}
}
private static IEnumerable<ExpressionSyntax> GetConditions(SyntaxNode node)
{
var check = node.FirstAncestorOrSelf<IfStatementSyntax>();
while (check != null)
{
yield return check.Condition;
check = check.Parent.FirstAncestorOrSelf<IfStatementSyntax>();
}
}
}
}

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

@ -0,0 +1,166 @@
// ******************************************************************
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
// ******************************************************************
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Rename;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Simplification;
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PlatformSpecificFixerCS)), Shared]
public class PlatformSpecificFixerCS : CodeFixProvider
{
private const string title = "Make uppercase";
public sealed override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(Analyzer.PlatformRule.Id, Analyzer.VersionRule.Id); }
}
public sealed override FixAllProvider GetFixAllProvider()
{
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
return WellKnownFixAllProviders.BatchFixer;
}
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
try
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
// Which node are we interested in? -- if the squiggle is over A.B().C,
// then we need the largest IdentifierName/SimpleMemberAccess/QualifiedName
// that encompasses "C" itself
var diagnostic = context.Diagnostics.First();
var span = new TextSpan(diagnostic.Location.SourceSpan.End - 1, 1);
var node = root.FindToken(span.Start).Parent;
SyntaxKind nodeKind = node.Kind();
while (nodeKind != SyntaxKind.IdentifierName && nodeKind != SyntaxKind.SimpleMemberAccessExpression && nodeKind != SyntaxKind.QualifiedName)
{
node = node.Parent;
if (node == null)
{
return;
}
nodeKind = node.Kind();
}
while (true)
{
if (node.Parent?.Kind() == SyntaxKind.SimpleMemberAccessExpression)
{
node = node.Parent;
continue;
}
if (node.Parent?.Kind() == SyntaxKind.QualifiedName)
{
node = node.Parent;
continue;
}
break;
}
var target = PlatformSpecificAnalyzerCS.GetTargetOfNode(node, semanticModel);
var g = HowToGuard.Symbol(target);
// Introduce a guard? (only if it is a method/accessor/constructor, i.e. somewhere that allows code)
var containingBlock = node.FirstAncestorOrSelf<BlockSyntax>();
if (containingBlock != null)
{
var act1 = CodeAction.Create($"Add 'If ApiInformation.{g.KindOfCheck}'", (c) => IntroduceGuardAsync(context.Document, node, g, c), "PlatformSpecificGuard");
context.RegisterCodeFix(act1, diagnostic);
}
}
catch
{
}
}
private async Task<Document> IntroduceGuardAsync(Document document, SyntaxNode node, HowToGuard g, CancellationToken cancellationToken)
{
// + if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent(targetContainingType))
// {
// old-statement
// + }
try
{
var oldStatement = node.FirstAncestorOrSelf<StatementSyntax>();
var oldLeadingTrivia = oldStatement.GetLeadingTrivia();
var conditionReceiver = SyntaxFactory.ParseName($"Windows.Foundation.Metadata.ApiInformation.{g.KindOfCheck}").WithAdditionalAnnotations(Simplifier.Annotation);
ArgumentListSyntax conditionArgument = null;
if (g.MemberToCheck == null)
{
var conditionString1 = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(g.TypeToCheck));
conditionArgument = SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(conditionString1)));
}
else
{
var conditionString1 = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(g.TypeToCheck));
var conditionString2 = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(g.MemberToCheck));
var conditionInt3 = SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(g?.ParameterCountToCheck ?? 0));
IEnumerable<ArgumentSyntax> conditions = null;
if (g.ParameterCountToCheck.HasValue)
{
conditions = new ArgumentSyntax[] { SyntaxFactory.Argument(conditionString1), SyntaxFactory.Argument(conditionString2), SyntaxFactory.Argument(conditionInt3) };
}
if (!g.ParameterCountToCheck.HasValue)
{
conditions = new ArgumentSyntax[] { SyntaxFactory.Argument(conditionString1), SyntaxFactory.Argument(conditionString2) };
}
conditionArgument = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(conditions));
}
var condition = SyntaxFactory.InvocationExpression(conditionReceiver, conditionArgument);
var thenStatements = SyntaxFactory.Block(oldStatement.WithoutLeadingTrivia());
var ifStatement = SyntaxFactory.IfStatement(condition, thenStatements).WithLeadingTrivia(oldLeadingTrivia).WithAdditionalAnnotations(Formatter.Annotation);
var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var newRoot = oldRoot.ReplaceNode(oldStatement, ifStatement);
return document.WithSyntaxRoot(newRoot);
}
catch
{
}
return document;
}
}
}

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

@ -0,0 +1,58 @@
param($installPath, $toolsPath, $package, $project)
if($project.Object.SupportsPackageDependencyResolution)
{
if($project.Object.SupportsPackageDependencyResolution())
{
# Do not install analyzers via install.ps1, instead let the project system handle it.
return
}
}
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
foreach($analyzersPath in $analyzersPaths)
{
if (Test-Path $analyzersPath)
{
# Install the language agnostic analyzers.
foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
{
if($project.Object.AnalyzerReferences)
{
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
}
}
}
}
# $project.Type gives the language name like (C# or VB.NET)
$languageFolder = ""
if($project.Type -eq "C#")
{
$languageFolder = "cs"
}
if($project.Type -eq "VB.NET")
{
$languageFolder = "vb"
}
if($languageFolder -eq "")
{
return
}
foreach($analyzersPath in $analyzersPaths)
{
# Install language specific analyzers.
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
if (Test-Path $languageAnalyzersPath)
{
foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
{
if($project.Object.AnalyzerReferences)
{
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
}
}
}
}

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

@ -0,0 +1,65 @@
param($installPath, $toolsPath, $package, $project)
if($project.Object.SupportsPackageDependencyResolution)
{
if($project.Object.SupportsPackageDependencyResolution())
{
# Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it.
return
}
}
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
foreach($analyzersPath in $analyzersPaths)
{
# Uninstall the language agnostic analyzers.
if (Test-Path $analyzersPath)
{
foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
{
if($project.Object.AnalyzerReferences)
{
$project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
}
}
}
}
# $project.Type gives the language name like (C# or VB.NET)
$languageFolder = ""
if($project.Type -eq "C#")
{
$languageFolder = "cs"
}
if($project.Type -eq "VB.NET")
{
$languageFolder = "vb"
}
if($languageFolder -eq "")
{
return
}
foreach($analyzersPath in $analyzersPaths)
{
# Uninstall language specific analyzers.
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
if (Test-Path $languageAnalyzersPath)
{
foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
{
if($project.Object.AnalyzerReferences)
{
try
{
$project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
}
catch
{
}
}
}
}
}

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

@ -65,7 +65,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Toolkit.Uwp.UI.Co
{E9FAABFB-D726-42C1-83C1-CB46A29FEA81} = {E9FAABFB-D726-42C1-83C1-CB46A29FEA81}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Toolkit.Parsers", "Microsoft.Toolkit.Parsers\Microsoft.Toolkit.Parsers.csproj", "{42CA4935-54BE-42EA-AC19-992378C08DE6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Parsers", "Microsoft.Toolkit.Parsers\Microsoft.Toolkit.Parsers.csproj", "{42CA4935-54BE-42EA-AC19-992378C08DE6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PlatformSpecific", "PlatformSpecific", "{096ECFD7-7035-4487-9C87-81DCE9389620}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DifferencesGen", "Microsoft.Toolkit.Uwp.PlatformDifferencesGen\DifferencesGen.csproj", "{292D34E8-0F01-4FA8-951D-8232F75A88D5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer", "Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer\Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer.csproj", "{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer.Vsix", "Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer.Vsix\Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer.Vsix.csproj", "{1603913D-6E19-4E76-ADFC-78206F68CB90}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
@ -408,6 +416,54 @@ Global
{42CA4935-54BE-42EA-AC19-992378C08DE6}.Release|x64.Build.0 = Release|Any CPU
{42CA4935-54BE-42EA-AC19-992378C08DE6}.Release|x86.ActiveCfg = Release|Any CPU
{42CA4935-54BE-42EA-AC19-992378C08DE6}.Release|x86.Build.0 = Release|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|ARM.ActiveCfg = Debug|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|ARM.Build.0 = Debug|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|x64.ActiveCfg = Debug|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|x64.Build.0 = Debug|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|x86.ActiveCfg = Debug|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|x86.Build.0 = Debug|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|Any CPU.Build.0 = Release|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|ARM.ActiveCfg = Release|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|ARM.Build.0 = Release|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|x64.ActiveCfg = Release|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|x64.Build.0 = Release|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|x86.ActiveCfg = Release|Any CPU
{292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|x86.Build.0 = Release|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|ARM.ActiveCfg = Debug|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|ARM.Build.0 = Debug|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|x64.ActiveCfg = Debug|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|x64.Build.0 = Debug|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|x86.ActiveCfg = Debug|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|x86.Build.0 = Debug|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|Any CPU.Build.0 = Release|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|ARM.ActiveCfg = Release|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|ARM.Build.0 = Release|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|x64.ActiveCfg = Release|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|x64.Build.0 = Release|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|x86.ActiveCfg = Release|Any CPU
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|x86.Build.0 = Release|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Debug|ARM.ActiveCfg = Debug|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Debug|ARM.Build.0 = Debug|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Debug|x64.ActiveCfg = Debug|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Debug|x64.Build.0 = Debug|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Debug|x86.ActiveCfg = Debug|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Debug|x86.Build.0 = Debug|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Release|Any CPU.Build.0 = Release|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Release|ARM.ActiveCfg = Release|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Release|ARM.Build.0 = Release|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Release|x64.ActiveCfg = Release|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Release|x64.Build.0 = Release|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Release|x86.ActiveCfg = Release|Any CPU
{1603913D-6E19-4E76-ADFC-78206F68CB90}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -425,6 +481,9 @@ Global
{94994424-5F60-4CD8-ABA2-101779066208} = {9333C63A-F64F-4797-82B3-017422668A5D}
{EFA96B3C-857E-4659-B942-6BEF7719F4CA} = {9333C63A-F64F-4797-82B3-017422668A5D}
{7AEFC959-ED7C-4D96-9E92-72609B40FBE0} = {F1AFFFA7-28FE-4770-BA48-10D76F3E59BC}
{292D34E8-0F01-4FA8-951D-8232F75A88D5} = {096ECFD7-7035-4487-9C87-81DCE9389620}
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF} = {096ECFD7-7035-4487-9C87-81DCE9389620}
{1603913D-6E19-4E76-ADFC-78206F68CB90} = {096ECFD7-7035-4487-9C87-81DCE9389620}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5403B0C4-F244-4F73-A35C-FE664D0F4345}

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

@ -0,0 +1,45 @@
---
title: UWP Platform Specific Analyzer
author: hermitdave
description: Platform Specific Analyzer is a Roslyn analyzer that analyzes and suggests code fixes to ensure that any version / platform specific API are guarded by correct runtime checks
keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, plaform specific, platform specific analyzer, roslyn analyzer
dev_langs:
- csharp
---
# Platform Specific Analyzer
The [writing version adaptive](https://docs.microsoft.com/windows/uwp/debug-test-perf/version-adaptive-code) code, the developers should ensure that code checks for presence of API before calling it.
The platform specific analyzer is a Roslyn Analyzer that can parse through code and suggest fixes where appropriate.
## Installation
The analyzer is available both as a nuget package and also as Visual Studio Extention
* References > Manage NuGet References > install [Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer](https://www.nuget.org/packages/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer)
## Sample Output
The analyzer automatically kicks in when code is opened in Visual Studio.
![Code Analysis](../resources/images/CodeAnalysis.png)
![Code Analysis](../resources/images/CodeFixSuggestion.png)
## Requirements
| Device family | Universal, 10.0.15063.0 or higher |
| ---------------------------------------------------------------- | ----------------------------------- |
| Namespace | Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer |
| NuGet package | [Microsoft.Toolkit.Uwp.UI.Animations](https://www.nuget.org/packages/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/) |
## API Source Code
- [Platform Specific Analyzer](https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer)
## Related Topics
<!-- Optional -->
- [Platform Specific Differences Generator](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.uwp.ui.platformspecificanalyzerdifferencesgen)

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

@ -0,0 +1,49 @@
---
title: UWP Platform Specific Differences Generator
author: hermitdave
description: Given the min and max SDK versions, the generator loads the appropriate Windows.Foundation.UniversalApiContract.winmd and builds differences in terms of new types and new members.
keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, plaform specific, platform specific differences, platform specific differences generator
dev_langs:
- csharp
---
# Platform Specific Differences Generator
A Platform Specific Analyzer would require to know the differences between various versions of UWP SDK. The Differences Generator provides a means of generating a differences dataset that can then be embedded in the analyzer.
## Usage
```cmd
DifferencesGen /min:4.0.0.0 /max:6.0.0.0
```
## Sample Output
Differences-6.0.0.0.gz
Differences-5.0.0.0.gz
## Data format
All types are fully qualified
##### Namespace.Type
*Windows.Management.Update.PreviewBuildsState*
*Windows.Management.Update.PreviewBuildsManager*
A new type does not have additional methods and properties listed.
For a type that has additions, the additions are listed alongside
##### Namespace.Type:Method#ParamCount,Property
*Windows.Networking.NetworkOperators.MobileBroadbandModem:TryGetPcoAsync#0,IsInEmergencyCallMode*
## API Source Code
- [DifferencesGen](https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.PlatformDifferencesGen/Program.cs)
## Related Topics
<!-- Optional -->
- [Platform Specific Analyzer](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.uwp.ui.platformspecificanalyzer)

Двоичные данные
docs/resources/images/CodeAnalysis.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 47 KiB

Двоичные данные
docs/resources/images/CodeFixSuggestion.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 66 KiB