Merge pull request #2083 from Microsoft/HD-PSA
Platform Specific Analyzer
This commit is contained in:
Коммит
0d970c6105
|
@ -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,268 @@
|
||||||
|
// ******************************************************************
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace DifferencesGen
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
private static HashSet<string> enumTypes = new HashSet<string>();
|
||||||
|
private static HashSet<string> typeEvents = new HashSet<string>();
|
||||||
|
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
string min = null;
|
||||||
|
string max = null;
|
||||||
|
|
||||||
|
foreach (var arg in args)
|
||||||
|
{
|
||||||
|
if (arg.StartsWith("/min:"))
|
||||||
|
{
|
||||||
|
min = arg.Replace("/min:", string.Empty);
|
||||||
|
}
|
||||||
|
else if (arg.StartsWith("/max:"))
|
||||||
|
{
|
||||||
|
max = arg.Replace("/max:", string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Version.TryParse(min, out Version minVersion);
|
||||||
|
Version.TryParse(max, out Version 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);
|
||||||
|
|
||||||
|
if (enumTypes.Contains(type.Key))
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"New enum {type.Key}");
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enumTypes.Contains(type.Key))
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Enum {type.Key} has new members: {string.Join(",", newerVersionTypeMembers)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var member in newerVersionTypeMembers)
|
||||||
|
{
|
||||||
|
if (typeEvents.Contains($"{type.Key}-{member}"))
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Type {type.Key} has new event: {member}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addedTypes.Add(type.Key, newerVersionTypeMembers.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
using (var compressedFS = File.Create(Path.Combine(AssemblyDirectory, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AssemblyDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
|
||||||
|
UriBuilder uri = new UriBuilder(codeBase);
|
||||||
|
string path = Uri.UnescapeDataString(uri.Path);
|
||||||
|
return Path.GetDirectoryName(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>();
|
||||||
|
|
||||||
|
if (exportedType.IsEnum)
|
||||||
|
{
|
||||||
|
if (!enumTypes.Contains(exportedType.FullName))
|
||||||
|
{
|
||||||
|
enumTypes.Add(exportedType.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var member in exportedType.GetFields())
|
||||||
|
{
|
||||||
|
if (member.Name.Equals("value__"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
members.Add(member.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var eventInfo in exportedType.GetEvents())
|
||||||
|
{
|
||||||
|
typeEvents.Add($"{exportedType.FullName}-{eventInfo.Name}");
|
||||||
|
members.Add(eventInfo.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
types.Add(exportedType.FullName, members);
|
||||||
|
}
|
||||||
|
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,367 @@
|
||||||
|
// ******************************************************************
|
||||||
|
// 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 Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class offers loads platform differences for use by Code Analyzer and Code Fixer.
|
||||||
|
/// </summary>
|
||||||
|
public static class Analyzer
|
||||||
|
{
|
||||||
|
internal enum TypePresenceIndicator
|
||||||
|
{
|
||||||
|
New,
|
||||||
|
Changes,
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, Dictionary<string, List<NewMember>>> _differencesDictionary = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Embedded differences between API contract version 4 and 5.
|
||||||
|
/// </summary>
|
||||||
|
public const string N1DifferencesRes = "Differences-5.0.0.0.gz";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Embedded differences between API contract version 5 and 6.
|
||||||
|
/// </summary>
|
||||||
|
public const string N0DifferencesRes = "Differences-6.0.0.0.gz";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Earliest supported SDK version.
|
||||||
|
/// </summary>
|
||||||
|
public const string N2SDKVersion = "15063";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Intermediate SDK version.
|
||||||
|
/// </summary>
|
||||||
|
public const string N1SDKVersion = "16299";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Latest SDK version.
|
||||||
|
/// </summary>
|
||||||
|
public const string N0SDKVersion = "17134";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Platform related diagnostic descriptor
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Version related diagnostic descriptor
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
|
||||||
|
private static char[] typeMemberSeparator = { ':' };
|
||||||
|
private static char[] memberSeparator = { ',' };
|
||||||
|
|
||||||
|
static Analyzer()
|
||||||
|
{
|
||||||
|
_differencesDictionary = new Dictionary<string, Dictionary<string, List<NewMember>>>();
|
||||||
|
_differencesDictionary.Add(N0DifferencesRes, GetApiAdditions(N0DifferencesRes));
|
||||||
|
_differencesDictionary.Add(N1DifferencesRes, GetApiAdditions(N1DifferencesRes));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the API differences from specified resource.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="resourceName">name of embedded resource</param>
|
||||||
|
/// <returns>Dictionary with Fully qualified name of type as key and list of new members as value</returns>
|
||||||
|
public static Dictionary<string, List<NewMember>> GetUniversalApiAdditions(string resourceName)
|
||||||
|
{
|
||||||
|
return _differencesDictionary[resourceName];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, List<NewMember>> GetApiAdditions(string resourceName)
|
||||||
|
{
|
||||||
|
Dictionary<string, List<NewMember>> apiAdditionsDictionary = new Dictionary<string, List<NewMember>>();
|
||||||
|
|
||||||
|
Assembly assembly = typeof(Analyzer).GetTypeInfo().Assembly;
|
||||||
|
|
||||||
|
var resource = assembly.GetManifestResourceStream("Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer." + resourceName);
|
||||||
|
|
||||||
|
if (resource == null)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Resource {resourceName} not found.");
|
||||||
|
return new Dictionary<string, List<NewMember>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 GetPlatformForSymbol(ISymbol symbol)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
|
TypePresenceIndicator presentInN0ApiDiff = CheckCollectionForType(Analyzer.GetUniversalApiAdditions(Analyzer.N0DifferencesRes), typeName, symbol);
|
||||||
|
|
||||||
|
if (presentInN0ApiDiff == TypePresenceIndicator.New)
|
||||||
|
{
|
||||||
|
// the entire type was found in Target Version
|
||||||
|
return new Platform(PlatformKind.Uwp, Analyzer.N0SDKVersion);
|
||||||
|
}
|
||||||
|
else if (presentInN0ApiDiff == TypePresenceIndicator.Changes)
|
||||||
|
{
|
||||||
|
// the entire type was found in Target Version with matching parameter lengths
|
||||||
|
return new Platform(PlatformKind.Uwp, Analyzer.N0SDKVersion, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TypePresenceIndicator presentInN1ApiDiff = CheckCollectionForType(Analyzer.GetUniversalApiAdditions(Analyzer.N1DifferencesRes), typeName, symbol);
|
||||||
|
|
||||||
|
if (presentInN1ApiDiff == TypePresenceIndicator.New)
|
||||||
|
{
|
||||||
|
// the entire type was found in Target Version
|
||||||
|
return new Platform(PlatformKind.Uwp, Analyzer.N1SDKVersion);
|
||||||
|
}
|
||||||
|
else if (presentInN1ApiDiff == TypePresenceIndicator.Changes)
|
||||||
|
{
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
return new Platform(PlatformKind.Unchecked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 GetGuardForSymbol(ISymbol target)
|
||||||
|
{
|
||||||
|
var plat = Analyzer.GetPlatformForSymbol(target);
|
||||||
|
|
||||||
|
switch (plat.Kind)
|
||||||
|
{
|
||||||
|
case PlatformKind.ExtensionSDK:
|
||||||
|
return new HowToGuard()
|
||||||
|
{
|
||||||
|
TypeToCheck = target.Kind == SymbolKind.NamedType ? target.ToDisplayString() : target.ContainingType.ToDisplayString(),
|
||||||
|
KindOfCheck = "IsTypePresent"
|
||||||
|
};
|
||||||
|
case PlatformKind.Uwp:
|
||||||
|
if (target.Kind == SymbolKind.NamedType)
|
||||||
|
{
|
||||||
|
return new HowToGuard()
|
||||||
|
{
|
||||||
|
TypeToCheck = target.ToDisplayString(),
|
||||||
|
KindOfCheck = "IsTypePresent"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException("oops! don't know why I was asked to check something that's fine");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TypePresenceIndicator CheckCollectionForType(Dictionary<string, List<NewMember>> collection, string typeName, ISymbol symbol)
|
||||||
|
{
|
||||||
|
List<NewMember> newMembers = null;
|
||||||
|
|
||||||
|
if (!collection.TryGetValue(typeName, out newMembers))
|
||||||
|
{
|
||||||
|
return TypePresenceIndicator.NotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newMembers == null || newMembers.Count == 0)
|
||||||
|
{
|
||||||
|
return TypePresenceIndicator.New;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (symbol.Kind == SymbolKind.NamedType)
|
||||||
|
{
|
||||||
|
return TypePresenceIndicator.NotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
var memberName = symbol.Name;
|
||||||
|
|
||||||
|
foreach (var newMember in newMembers)
|
||||||
|
{
|
||||||
|
if (memberName == newMember.Name && !newMember.ParameterCount.HasValue)
|
||||||
|
{
|
||||||
|
return TypePresenceIndicator.New;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 TypePresenceIndicator.Changes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this member existed in a different collection
|
||||||
|
return TypePresenceIndicator.NotFound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -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>
|
||||||
|
/// The struct provides guard related info.
|
||||||
|
/// </summary>
|
||||||
|
public struct HowToGuard
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Type being checked
|
||||||
|
/// </summary>
|
||||||
|
public string TypeToCheck;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Member being checked
|
||||||
|
/// </summary>
|
||||||
|
public string MemberToCheck;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether parameter count will be used for the check
|
||||||
|
/// </summary>
|
||||||
|
public int? ParameterCountToCheck;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Type of check
|
||||||
|
/// </summary>
|
||||||
|
public string KindOfCheck;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard1.3</TargetFramework><Title>Windows Community Toolkit UI</Title>
|
||||||
|
<Description>This .NET standard library provides analyzer and code fixer to ensure that version / platform specific code is well guarded. It is part of the Windows Community Toolkit.</Description>
|
||||||
|
<PackageTags>UWP Toolkit Windows</PackageTags>
|
||||||
|
<Title>Windows Community Toolkit UWP Platform Specific Analyzer</Title>
|
||||||
|
|
||||||
|
<!-- This is a temporary workaround for https://github.com/dotnet/sdk/issues/955 -->
|
||||||
|
<DebugType>Full</DebugType>
|
||||||
|
</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 Include="Microsoft.CodeAnalysis.VisualBasic" Version="2.4.0" />
|
||||||
|
<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,54 @@
|
||||||
|
// ******************************************************************
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class wraps a new data members
|
||||||
|
/// </summary>
|
||||||
|
public struct NewMember
|
||||||
|
{
|
||||||
|
private static char[] methodCountSeparator = { '#' };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Member name
|
||||||
|
/// </summary>
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parameter count (if its a method)
|
||||||
|
/// </summary>
|
||||||
|
public int? ParameterCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="NewMember"/> struct.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="s">data containing name and optionally parameter count</param>
|
||||||
|
public NewMember(string s)
|
||||||
|
{
|
||||||
|
string[] parts = s.Split(methodCountSeparator, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
if (parts.Length == 2)
|
||||||
|
{
|
||||||
|
Name = parts[0];
|
||||||
|
ParameterCount = int.Parse(parts[1]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Name = s;
|
||||||
|
ParameterCount = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
// ******************************************************************
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Simple struct to hold platform / version / param count info for given symbol
|
||||||
|
/// </summary>
|
||||||
|
public struct Platform
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Platform Kind
|
||||||
|
/// </summary>
|
||||||
|
public PlatformKind Kind;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For UWP, this is version 15063 or 16299etc. 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byParameterCount && kind != PlatformKind.Uwp)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Only UWP can be distinguished by parameter count");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// ******************************************************************
|
||||||
|
// 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>
|
||||||
|
/// Platform kind 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,241 @@
|
||||||
|
// ******************************************************************
|
||||||
|
// 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.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
|
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is a Roslyn code analyzer that checks for types / members that should be guarded against.
|
||||||
|
/// </summary>
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class PlatformSpecificAnalyzerCS : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets supported diagnostics
|
||||||
|
/// </summary>
|
||||||
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
|
||||||
|
{
|
||||||
|
get { return ImmutableArray.Create(Analyzer.PlatformRule, Analyzer.VersionRule); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets instance of symbol from sytax node
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">instance of <see cref="SyntaxNode"/></param>
|
||||||
|
/// <param name="semanticModel"><see cref="SemanticModel"/></param>
|
||||||
|
/// <returns><see cref="ISymbol"/></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetKind = target.Kind;
|
||||||
|
|
||||||
|
if (targetKind == SymbolKind.Method || targetKind == SymbolKind.Event || targetKind == SymbolKind.Property || targetKind == SymbolKind.NamedType)
|
||||||
|
{
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetKind == SymbolKind.Field && target.ContainingType.TypeKind == TypeKind.Enum)
|
||||||
|
{
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialises the analyzer, registering for code analysis.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"><see cref="AnalysisContext"/></param>
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
ConcurrentDictionary<int, Diagnostic> reportsDictionary = new ConcurrentDictionary<int, Diagnostic>();
|
||||||
|
|
||||||
|
context.RegisterSyntaxNodeAction((c) => AnalyzeExpression(c, reportsDictionary), SyntaxKind.VariableDeclaration, SyntaxKind.FieldDeclaration, SyntaxKind.IdentifierName, SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.QualifiedName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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))
|
||||||
|
{
|
||||||
|
var symbolKind = symbol.Kind;
|
||||||
|
|
||||||
|
if (symbolKind == SymbolKind.Field || symbolKind == 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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = Analyzer.GetPlatformForSymbol(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>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsProperlyGuarded(SyntaxNode node, SemanticModel semanticModel)
|
||||||
|
{
|
||||||
|
foreach (var symbol in GetGuards(node, semanticModel))
|
||||||
|
{
|
||||||
|
if (symbol.ContainingType?.Name == "ApiInformation")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,265 @@
|
||||||
|
// ******************************************************************
|
||||||
|
// 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.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
using Microsoft.CodeAnalysis.VisualBasic;
|
||||||
|
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
|
||||||
|
|
||||||
|
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is a Roslyn code analyzer that checks for types / members that should be guarded against.
|
||||||
|
/// </summary>
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.VisualBasic)]
|
||||||
|
public class PlatformSpecificAnalyzerVB : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets supported diagnostics
|
||||||
|
/// </summary>
|
||||||
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
|
||||||
|
{
|
||||||
|
get { return ImmutableArray.Create(Analyzer.PlatformRule, Analyzer.VersionRule); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets instance of symbol from sytax node
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">instance of <see cref="SyntaxNode"/></param>
|
||||||
|
/// <param name="semanticModel"><see cref="SemanticModel"/></param>
|
||||||
|
/// <returns><see cref="ISymbol"/></returns>
|
||||||
|
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.AddressOfExpression)
|
||||||
|
{
|
||||||
|
// AddressOf <target>
|
||||||
|
return semanticModel.GetSymbolInfo(node).Symbol; // points to the method after overload resolution
|
||||||
|
}
|
||||||
|
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 if (parentKind == SyntaxKind.AddHandlerStatement && node == ((AddRemoveHandlerStatementSyntax)node.Parent).EventExpression)
|
||||||
|
{
|
||||||
|
// AddHandler <target>, delegate
|
||||||
|
return semanticModel.GetSymbolInfo(node).Symbol; // points to the event
|
||||||
|
}
|
||||||
|
else if (parentKind == SyntaxKind.NameOfExpression)
|
||||||
|
{
|
||||||
|
// NameOf(<target>)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// f(Of <target>)(...) -- no warning
|
||||||
|
// Dim x As <target> = ... -- no warning
|
||||||
|
// property access -- warning
|
||||||
|
// field access -- only warning on enum fields
|
||||||
|
// method access without arguments -- warning
|
||||||
|
var target = semanticModel.GetSymbolInfo(node).Symbol;
|
||||||
|
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetKind = target.Kind;
|
||||||
|
|
||||||
|
if (targetKind == SymbolKind.Method || targetKind == SymbolKind.Property || targetKind == SymbolKind.NamedType)
|
||||||
|
{
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetKind == SymbolKind.Field && target.ContainingType.TypeKind == TypeKind.Enum)
|
||||||
|
{
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialises the analyzer, registering for code analysis.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"><see cref="AnalysisContext"/></param>
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
ConcurrentDictionary<int, Diagnostic> reportsDictionary = new ConcurrentDictionary<int, Diagnostic>();
|
||||||
|
|
||||||
|
context.RegisterSyntaxNodeAction((c) => AnalyzeExpression(c, reportsDictionary), SyntaxKind.LocalDeclarationStatement, SyntaxKind.IdentifierName, SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.QualifiedName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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 == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var symbolKind = symbol.Kind;
|
||||||
|
|
||||||
|
if (symbolKind == SymbolKind.Field || symbolKind == SymbolKind.Property)
|
||||||
|
{
|
||||||
|
yield return symbol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<ExpressionSyntax> GetConditions(SyntaxNode node)
|
||||||
|
{
|
||||||
|
var check1 = node.FirstAncestorOrSelf<MultiLineIfBlockSyntax>();
|
||||||
|
|
||||||
|
while (check1 != null)
|
||||||
|
{
|
||||||
|
yield return check1.IfStatement.Condition;
|
||||||
|
check1 = check1.Parent.FirstAncestorOrSelf<MultiLineIfBlockSyntax>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var check2 = node.FirstAncestorOrSelf<SingleLineIfStatementSyntax>();
|
||||||
|
|
||||||
|
while (check2 != null)
|
||||||
|
{
|
||||||
|
yield return check2.Condition;
|
||||||
|
check2 = check2.Parent.FirstAncestorOrSelf<SingleLineIfStatementSyntax>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = Analyzer.GetPlatformForSymbol(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?
|
||||||
|
DeclarationStatementSyntax containingMember = context.Node.FirstAncestorOrSelf<MethodBlockBaseSyntax>();
|
||||||
|
|
||||||
|
if (containingMember is AccessorBlockSyntax)
|
||||||
|
{
|
||||||
|
containingMember = containingMember.FirstAncestorOrSelf<PropertyBlockSyntax>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this invocation properly guarded? See readme.md for explanations.
|
||||||
|
if (IsProperlyGuarded(context.Node, context.SemanticModel))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containingMember != null)
|
||||||
|
{
|
||||||
|
foreach (var ret in containingMember.DescendantNodes().OfType<ReturnStatementSyntax>())
|
||||||
|
{
|
||||||
|
if (IsProperlyGuarded(ret, context.SemanticModel))
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsProperlyGuarded(SyntaxNode node, SemanticModel semanticModel)
|
||||||
|
{
|
||||||
|
foreach (var symbol in GetGuards(node, semanticModel))
|
||||||
|
{
|
||||||
|
if (symbol.ContainingType?.Name == "ApiInformation")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
// ******************************************************************
|
||||||
|
// 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.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.CodeActions;
|
||||||
|
using Microsoft.CodeAnalysis.CodeFixes;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Formatting;
|
||||||
|
using Microsoft.CodeAnalysis.Simplification;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class provides guard suggestion and can make the suggested changes.
|
||||||
|
/// </summary>
|
||||||
|
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PlatformSpecificFixerCS))]
|
||||||
|
[Shared]
|
||||||
|
public class PlatformSpecificFixerCS : CodeFixProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the list of Diagnotics that can be fixed.
|
||||||
|
/// </summary>
|
||||||
|
public sealed override ImmutableArray<string> FixableDiagnosticIds
|
||||||
|
{
|
||||||
|
get { return ImmutableArray.Create(Analyzer.PlatformRule.Id, Analyzer.VersionRule.Id); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Fix All provider
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><see cref="WellKnownFixAllProviders"/></returns>
|
||||||
|
public sealed override FixAllProvider GetFixAllProvider()
|
||||||
|
{
|
||||||
|
return WellKnownFixAllProviders.BatchFixer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers for code fix.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"><see cref="CodeFixContext"/></param>
|
||||||
|
/// <returns>awaitable <see cref="Task"/></returns>
|
||||||
|
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 = Analyzer.GetGuardForSymbol(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) };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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,176 @@
|
||||||
|
// ******************************************************************
|
||||||
|
// 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.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.CodeActions;
|
||||||
|
using Microsoft.CodeAnalysis.CodeFixes;
|
||||||
|
using Microsoft.CodeAnalysis.Formatting;
|
||||||
|
using Microsoft.CodeAnalysis.Simplification;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
using Microsoft.CodeAnalysis.VisualBasic;
|
||||||
|
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
|
||||||
|
|
||||||
|
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class provides guard suggestion and can make the suggested changes.
|
||||||
|
/// </summary>
|
||||||
|
[ExportCodeFixProvider(LanguageNames.VisualBasic, Name = nameof(PlatformSpecificFixerCS))]
|
||||||
|
[Shared]
|
||||||
|
public class PlatformSpecificFixerVB : CodeFixProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the list of Diagnotics that can be fixed.
|
||||||
|
/// </summary>
|
||||||
|
public sealed override ImmutableArray<string> FixableDiagnosticIds
|
||||||
|
{
|
||||||
|
get { return ImmutableArray.Create(Analyzer.PlatformRule.Id, Analyzer.VersionRule.Id); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Fix All provider
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><see cref="WellKnownFixAllProviders"/></returns>
|
||||||
|
public sealed override FixAllProvider GetFixAllProvider()
|
||||||
|
{
|
||||||
|
return WellKnownFixAllProviders.BatchFixer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers for code fix.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"><see cref="CodeFixContext"/></param>
|
||||||
|
/// <returns>awaitable <see cref="Task"/></returns>
|
||||||
|
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 = PlatformSpecificAnalyzerVB.GetTargetOfNode(node, semanticModel);
|
||||||
|
var g = Analyzer.GetGuardForSymbol(target);
|
||||||
|
|
||||||
|
// Introduce a guard? (only if it is a method/accessor/constructor, i.e. somewhere that allows code)
|
||||||
|
var containingBlock = node.FirstAncestorOrSelf<MethodBlockBaseSyntax>();
|
||||||
|
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) Then
|
||||||
|
// old-statement
|
||||||
|
// + End If
|
||||||
|
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.StringLiteralExpression(SyntaxFactory.StringLiteralToken($"\"{g.TypeToCheck}\"", g.TypeToCheck));
|
||||||
|
conditionArgument = SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList<ArgumentSyntax>(SyntaxFactory.SimpleArgument(conditionString1)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var conditionString1 = SyntaxFactory.StringLiteralExpression(SyntaxFactory.StringLiteralToken($"\"{g.TypeToCheck}\"", g.TypeToCheck));
|
||||||
|
var conditionString2 = SyntaxFactory.StringLiteralExpression(SyntaxFactory.StringLiteralToken($"\"{g.MemberToCheck}\"", 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.SimpleArgument(conditionString1), SyntaxFactory.SimpleArgument(conditionString2), SyntaxFactory.SimpleArgument(conditionInt3) };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
conditions = new ArgumentSyntax[] { SyntaxFactory.SimpleArgument(conditionString1), SyntaxFactory.SimpleArgument(conditionString2) };
|
||||||
|
}
|
||||||
|
|
||||||
|
conditionArgument = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(conditions));
|
||||||
|
}
|
||||||
|
|
||||||
|
var condition = SyntaxFactory.InvocationExpression(conditionReceiver, conditionArgument);
|
||||||
|
|
||||||
|
var ifStatement = SyntaxFactory.IfStatement(condition);
|
||||||
|
var thenStatements = SyntaxFactory.SingletonList(oldStatement.WithoutLeadingTrivia());
|
||||||
|
var ifBlock = SyntaxFactory.MultiLineIfBlock(ifStatement).WithStatements(thenStatements).WithLeadingTrivia(oldLeadingTrivia).WithAdditionalAnnotations(Formatter.Annotation);
|
||||||
|
|
||||||
|
var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
var newRoot = oldRoot.ReplaceNode(oldStatement, ifBlock);
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,6 +69,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Toolkit.Uwp.UI.Co
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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
|
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}"
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebView", "WebView", "{EF53C82B-8622-42C5-A318-CF24A695D913}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebView", "WebView", "{EF53C82B-8622-42C5-A318-CF24A695D913}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.WebView.WinForms", "UnitTests\UnitTests.WebView.WinForms\UnitTests.WebView.WinForms.csproj", "{10129749-7761-49B8-96B9-94BC833BA60B}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.WebView.WinForms", "UnitTests\UnitTests.WebView.WinForms\UnitTests.WebView.WinForms.csproj", "{10129749-7761-49B8-96B9-94BC833BA60B}"
|
||||||
|
@ -507,6 +512,38 @@ Global
|
||||||
{42CA4935-54BE-42EA-AC19-992378C08DE6}.Release|x64.Build.0 = Release|Any CPU
|
{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.ActiveCfg = Release|Any CPU
|
||||||
{42CA4935-54BE-42EA-AC19-992378C08DE6}.Release|x86.Build.0 = 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
|
||||||
{10129749-7761-49B8-96B9-94BC833BA60B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{10129749-7761-49B8-96B9-94BC833BA60B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{10129749-7761-49B8-96B9-94BC833BA60B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{10129749-7761-49B8-96B9-94BC833BA60B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{10129749-7761-49B8-96B9-94BC833BA60B}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
{10129749-7761-49B8-96B9-94BC833BA60B}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
@ -689,6 +726,8 @@ Global
|
||||||
{94994424-5F60-4CD8-ABA2-101779066208} = {9333C63A-F64F-4797-82B3-017422668A5D}
|
{94994424-5F60-4CD8-ABA2-101779066208} = {9333C63A-F64F-4797-82B3-017422668A5D}
|
||||||
{EFA96B3C-857E-4659-B942-6BEF7719F4CA} = {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}
|
{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}
|
||||||
{EF53C82B-8622-42C5-A318-CF24A695D913} = {B30036C4-D514-4E5B-A323-587A061772CE}
|
{EF53C82B-8622-42C5-A318-CF24A695D913} = {B30036C4-D514-4E5B-A323-587A061772CE}
|
||||||
{10129749-7761-49B8-96B9-94BC833BA60B} = {EF53C82B-8622-42C5-A318-CF24A695D913}
|
{10129749-7761-49B8-96B9-94BC833BA60B} = {EF53C82B-8622-42C5-A318-CF24A695D913}
|
||||||
{B220BE88-944E-45D1-9248-6FAEF53868A3} = {EF53C82B-8622-42C5-A318-CF24A695D913}
|
{B220BE88-944E-45D1-9248-6FAEF53868A3} = {EF53C82B-8622-42C5-A318-CF24A695D913}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
- visualbasic
|
||||||
|
---
|
||||||
|
|
||||||
|
# Platform Specific Analyzer
|
||||||
|
|
||||||
|
When writing [version](https://docs.microsoft.com/windows/uwp/debug-test-perf/version-adaptive-code) or platform adaptive 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
|
||||||
|
|
||||||
|
* 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 and supports both C# and Visual Basic
|
||||||
|
|
||||||
|
C#
|
||||||
|
![Code Analysis](../resources/images/CodeAnalysis.png)
|
||||||
|
|
||||||
|
![Code Analysis](../resources/images/CodeFixSuggestion.png)
|
||||||
|
|
||||||
|
Visual Basic
|
||||||
|
![Code Analysis](../resources/images/CodeAnalysisVB.png)
|
||||||
|
|
||||||
|
![Code Analysis](../resources/images/CodeFixSuggestionVB.png)
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
| Device family | Universal, 10.0.15063.0 or higher |
|
||||||
|
| ---------------------------------------------------------------- | ----------------------------------- |
|
||||||
|
| Namespace | Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer |
|
||||||
|
| NuGet package | [Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer](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 |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 47 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 66 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 60 KiB |
Загрузка…
Ссылка в новой задаче