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
|
||||
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}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebView", "WebView", "{EF53C82B-8622-42C5-A318-CF24A695D913}"
|
||||
EndProject
|
||||
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|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
|
||||
{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|ARM.ActiveCfg = Debug|Any CPU
|
||||
|
@ -689,6 +726,8 @@ 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}
|
||||
{EF53C82B-8622-42C5-A318-CF24A695D913} = {B30036C4-D514-4E5B-A323-587A061772CE}
|
||||
{10129749-7761-49B8-96B9-94BC833BA60B} = {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 |
Загрузка…
Ссылка в новой задаче