Adding support for Platform Specific Differences generator, Roslyn analyzer and documentation
This commit is contained in:
Родитель
9766eb794d
Коммит
23b1208ec2
|
@ -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)
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 47 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 66 KiB |
Загрузка…
Ссылка в новой задаче