From f52d477f15e06a103270c4420c63627aada026a9 Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Tue, 14 Apr 2020 16:14:06 -0700 Subject: [PATCH 01/15] Add analzyer for platform-specific APIs --- .../2020/platform-checks/platform-checks.md | 450 ++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 accepted/2020/platform-checks/platform-checks.md diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md new file mode 100644 index 000000000..e99f6546b --- /dev/null +++ b/accepted/2020/platform-checks/platform-checks.md @@ -0,0 +1,450 @@ +# Annotating platform-specific APIs and detecting its use + +**PM** [Immo Landwerth](https://github.com/terrajobst) + +With .NET Core, we've made cross-platform development a mainline experience in +.NET. Even more so for library authors that use [.NET 5][net5-tfms]. We +generally strive to make it very easy to build code that works well across all +platforms. Hence, we avoid promoting technologies and APIs that only work on one +platform by not putting platform-specific APIs in the default set that is always +referenced. Rather, we're exposing them via dedicated packages that the +developer has to reference manually. + +Unfortunately, this approach doesn't address all concerns: + +* **It's hard to reason about transitive platform-specific dependencies**. A + developer doesn't necessarily know that they took a dependency on a component + that makes their code no longer work across all platforms. For example, in + principle, a package that sounds general purpose could depend on something + platform-specific that now transitively ties the consuming application to a + single platform (for example, an IOC container that depends on the Windows + registry). Equally, transitively depending on a platform-specific package + doesn't mean that the application doesn't work across all platforms -- the + library might actually guard the call so that it can provide a slightly better + experience on a particular platform. + +* **We need to stay compatible with existing APIs**. Ideally, types that provide + cross-platform functionality shouldn't offer members that only work on + specific platforms. Unfortunately, the .NET platform is two decades old and + was originally designed to be a great development platform for Windows. This + resulted in Windows-specific APIs being added to types that are otherwise + cross-platform. A good example are file system and threading types that have + methods for setting the Windows access control lists (ACL). While we could + provide them elsewhere, this would unnecessarily break a lot of code that + builds Windows-only applications, e.g. WinForms and WPF apps. + +* **OS platforms are typically targeted using the latest SDK**. Even when the + project is platform specific, such as `net5.0-ios` or `net5.0-android`, it's + not necessarily clear that the called API is actually available. For .NET + Framework, we used *reference assemblies* which provides a different set of + assemblies for every version. However, for OS bindings this approach is not + feasible. Instead, applications are generally compiled against the latest + version of the SDK with the expectations that callers have to check the OS + version if they want to run on older versions. See [Minimum OS + Version][os-minimum-version] for more details. + +.NET has always taken the position that providing platform-specific +functionality isn't a bug but a feature because it empowers developers to build +applications that feel native for the user. That's why .NET has rich interop +facilities such as P/Invoke. + +At the same time, developers really dislike the notion of "write once and test +everywhere" because it results in a delayed feedback loop. + +Fortunately .NET has also taken the position that developer productivity is key. +This document proposes: + +1. A mechanism to annotate APIs as being platform-specific, including the + version they got introduced in, the version they got removed in, and the + version they got obsoleted in. + +2. A Roslyn analyzer that informs developers when they use platform-specific + APIs from call sites where the API might not be available. + +This work draws heavily form the experience of the [API Analyzer] and improves +on it by making it a first-class platform feature. + +## Scenarios and User Experience + +### Lighting up on later versions of the OS + +Miguel is building Baby Shark, a popular iOS application. He started with .NET +that supported iOS 13 but Apple just released iOS 14, which adds the great +`NSFizBuzz` API that gives his app the extra pop. + +After upgrading the app and writing the code, Miguel decides that while the +feature is cool, it doesn't warrant cutting of all his customers who are +currently still on iOS 13. So he edits the project file and sets +`TargetPlatformMinVersion` to 13. + +```xml + + + + net5.0-ios14.0 + 13.0 + + + ... + + +``` + +After doing this, he gets a diagnostic in this code: + +> 'NSFizzBuff' requires iOS 14 or later. + +```C# +static void ProvideExtraPop() +{ + NSFizzBuff(); +} +``` + +He invokes the Roslyn code fixer which suggests to guard the call. This results +in the following code: + +```C# +static void ProvideExtraPop() +{ + if (RuntimeInformation.IsOSPlatformOrLater(OSPlatform.iOS, 14)) + NSFizzBuff(); +} +``` + +### Detecting obsoletion of OS APIs + +After upgrading to iOS 14 Miguel also got the following diagnostic: + +> 'UIApplicationExitsOnSuspend' has been deprecated since iOS 13.1 + +Miguel decides that he'll tackle that problem later as he needs to ship now. + +### Detecting removal of OS APIs + +A few year after his massively successful fizz-buzzed Baby Shark, Apple releases +iOS 15. After upgrading Miguel gets the following diagnostic: + +> 'UIApplicationExitsOnSuspend' has been removed since iOS 15.0 + +Doh! After reading more details, he releases that he can just stop calling this +API because Apple automatically suspends apps leaving the foreground when they +don't require background execution. However, since he still supports iOS 12, he +need + +### Finding usage of platform-specific APIs + +Alejandra is working at Fabrikam, where she's tasked with porting their asset +management system to .NET Core. The application consists of several web +services, a desktop application, and a set of libraries that are shared. + +She begins by porting the core business logic libraries to .NET 5. After +porting, she's getting a diagnostic in the code below: + +> 'DirectorySecurity' is only supported on 'Windows' + +```C# +private static string CreateLoggingDirectory() +{ + var appDataDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var loggingDirectory = Path.Combine(appDataDirectory, "Fabrikam", "AssetManagement", "Logging"); + + // Restrict access to the logging directory + + var rules = new DirectorySecurity(); + rules.AddAccessRule( + new FileSystemAccessRule(@"fabrikam\log-readers", + FileSystemRights.Read, + AccessControlType.Allow) + ); + rules.AddAccessRule( + new FileSystemAccessRule(@"fabrikam\log-writers", + FileSystemRights.FullControl, + AccessControlType.Allow) + ); + + Directory.CreateDirectory(loggingDirectory, rules); + + return loggingDirectory; +} +``` + +### Add platform-specific code without becoming platform-specific + +After reviewing the code she realizes that this code only makes sense for cases +where the application is installed on Windows. When running on iOS and Android, +the machine isn't in shared environment so restricting the log access doesn't +make much sense. She modifies the code accordingly: + +```C# +private static string GetLoggingPath() +{ + var appDataDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var loggingDirectory = Path.Combine(appDataDirectory, "Fabrikam", "AssetManagement", "Logging"); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Just create the directory + Directory.CreateDirectory(loggingDirectory); + } + else + { + // Create the directory and restrict access using Windows + // Access Control Lists (ACLs). + + var rules = new DirectorySecurity(); + rules.AddAccessRule( + new FileSystemAccessRule(@"fabrikam\log-readers", + FileSystemRights.Read, + AccessControlType.Allow) + ); + rules.AddAccessRule( + new FileSystemAccessRule(@"fabrikam\log-writers", + FileSystemRights.FullControl, + AccessControlType.Allow) + ); + + Directory.CreateDirectory(loggingDirectory, rules); + } + + return loggingDirectory; +} +``` + +After that, the diagnostic disappears automatically. + +## Requirements + +### Goals + +* Developers get diagnostics when they inadvertently use platform-specific APIs. + - All diagnostics are provided on the command line as well as in the IDE + - The developer doesn't have to resort to explicit suppressions to indicate + intent. Idiomatic gestures, such as guarding the call with a platform + check or marking a method as platform-specific, automatically suppress + these diagnostics. However, explicit suppressions via the usual means + (`NoWarn`, `#pragma`) will also work. +* The analyzer helps library authors to express their intent so that their + consumers can also benefit from these diagnostics. + - Library authors can annotate their platform-specific APIs without taking a + dependency. + - Library consumers don't have to install a NuGet package to get diagnostics + when using platform-specific APIs + +### Non-Goals + +* Modelling partially portable APIs (that is APIs are applicable to more than + one platform but no all platforms). Those should Be modeled as capability + APIs, as as `RuntimeInformation.IsDynamicCodeSupported` for ref emit. This + would be a separate analyzer. +* Shipping the platform-specific analyzer and/or annotations out-of-band. + +## Design + +### Attribute + +OS bindings and platforms-specific BCL APIs will be annotated using a set of +custom attributes: + +```C# +namespace System.Runtime.InteropServices +{ + public partial struct OSPlatform + { + // Existing properties + // public static OSPlatform FreeBSD { get; } + // public static OSPlatform Linux { get; } + // public static OSPlatform OSX { get; } + // public static OSPlatform Windows { get; } + public static OSPlatform Android { get; } + public static OSPlatform iOS { get; } + } + + public partial static class RuntimeInformation + { + // Existing API + // public static bool IsOSPlatform(OSPlatform osPlatform); + + // Check for the OS with a >= version comparison + // Used to guard APIs that were added in the given OS release. + public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major); + public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor); + public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor, int build); + public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor, int build, int revision); + + // Allows checking for the OS with a < version comparison + // Used to guard APIs that were obsoleted or removed in the given OS release. + public static bool IsOSPlatformAndEarlier(OSPlatform osPlatform, int major); + public static bool IsOSPlatformAndEarlier(OSPlatform osPlatform, int major, int minor); + public static bool IsOSPlatformAndEarlier(OSPlatform osPlatform, int major, int minor, int build); + public static bool IsOSPlatformAndEarlier(OSPlatform osPlatform, int major, int minor, int build, int revision); + } +} + +namespace System.Runtime.Versioning +{ + public abstract class OSPlatformAttribute : Attribute + { + protected OSPlatformAttribute(string osPlatform, + int major, + int minor, + int build, + int revision); + public string PlatformIdentifier { get; } + public int Major { get; } + public int Minor { get; } + public int Build { get; } + public int Revision { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | + AttributeTargets.Class | + AttributeTargets.Constructor | + AttributeTargets.Event | + AttributeTargets.Method | + AttributeTargets.Module | + AttributeTargets.Property | + AttributeTargets.Struct, + AllowMultiple=false, Inherited=true)] + public sealed class AddedInOSPlatformAttribute : OSPlatformAttribute + { + public PlatformSpecificAttribute(string osPlatform, + int major, + int minor, + int build, + int revision); + } + + [AttributeUsage(AttributeTargets.Assembly | + AttributeTargets.Class | + AttributeTargets.Constructor | + AttributeTargets.Event | + AttributeTargets.Method | + AttributeTargets.Module | + AttributeTargets.Property | + AttributeTargets.Struct, + AllowMultiple=false, Inherited=true)] + public sealed class RemovedInOSPlatformAttribute : OSPlatformAttribute + { + public PlatformSpecificAttribute(string osPlatform, + int major, + int minor, + int build, + int revision); + } + + [AttributeUsage(AttributeTargets.Assembly | + AttributeTargets.Class | + AttributeTargets.Constructor | + AttributeTargets.Event | + AttributeTargets.Method | + AttributeTargets.Module | + AttributeTargets.Property | + AttributeTargets.Struct, + AllowMultiple=false, Inherited=true)] + public sealed class ObsoletedInOSPlatformAttribute : OSPlatformAttribute + { + public PlatformSpecificAttribute(string osPlatform, + int major, + int minor, + int build, + int revision); + public string Url { get; set; } + } +} +``` + +### Platform context + +To determine the platform context of the call site the analyzer must consider +the following two configurations: + +1. **Call-site**. Application of `AddedInOSPlatformAttribute` to the containing + member, type, module, or assembly. + +2. **Project** The project TFMs and `TargetPlatformMinVersion`. If the project + has an platform-specific TFM (such as `net5.0-ios13.0`) we consider it to be + on the specified platform. If `TargetPlatformMinVersion` is set, we assume + that version, otherwise we assume the version as specified by the TFM. + +If (2) is platform neutral (e.g. `net5.0`), the platform context is entirely +determined by (1). + +If (2) is platform-specific it is combined with (1): + +* If the platforms between and (2) are disjoint (such as iOS and Windows), the + information from (1) is discarded. +* Otherwise the platform context's minimum version is the maximum of the + versions determined by (1) and (2). + +### Diagnostics + +The analyzer can produce three diagnostics: + +1. '{API}' requires {OS} {OS version} or later. +2. '{API}' has been deprecated since {OS} {OS version}. +3. '{API}' has been removed since {OS} {OS version}. + +### Automatic suppression via platform guards + +Diagnostic (1) can be suppressed via a call to +`RuntimeInformation.IsOSPlatformOrLater()`. This should work for for simple cases +where the call is contained inside an `if` check but also when the check causes +control flow to never reach the call: + +```C# +public void Case1() +{ + if (RuntimeInformation.IsOSPlatformOrLater(OSPlatform.iOS, 13)) + AppleApi(); +} + +public void Case2() +{ + if (!RuntimeInformation.IsOSPlatformOrLater(OSPlatform.iOS, 13)) + return; + + AppleApi(); +} +``` + +Diagnostics (2) and (3) are analogous except the guard is provided by +`RuntimeInformation.IsOSPlatformAndEarlier()`. + +### Code Fixers + +* Suggest wrapping statement in platform guard +* Suggest annotating the method with an attribute + +All diagnostics can be suppressed by surround the call site using a platform +guard. The fixer should offer surrounding the call with an `if` check using the +appropriate platform-guard. It's not necessary that the end result compiles. It +is the user's responsibility to adjust control flow and data flow to ensure +variables are declared and initialized appropriately. The value that the fixer +provides here is educational and avoids having to lookup the correct version +numbers. + +For diagnostic (1) we should also offer a fixer that allows it to be marked as +platform specific by applying `AddedInOSPlatformAttribute`. If the attribute is +already applied, it should offer to change the platform and version. + +## Q & A + +### How does this relate to platform-compat/API Analyzer? + +We have an existing project [dotnet/platform-compat], available as the +[Microsoft.DotNet.Analyzers.Compatibility] NuGet package, but there are several drawbacks: + +* The analyzer isn't on by default +* It carries a database of platform-specific framework APIs, which doesn't scale + to 3rd parties +* Suppression mechanism is somewhat lacking + +This is intended to become the productized version of +[Microsoft.DotNet.Analyzers.Compatibility]. + +[dotnet/platform-compat]: https://github.com/dotnet/platform-compat +[API Analyzer]: https://devblogs.microsoft.com/dotnet/introducing-api-analyzer/ +[Microsoft.DotNet.Analyzers.Compatibility]: https://www.nuget.org/packages/Microsoft.DotNet.Analyzers.Compatibility +[net5-tfms]: https://github.com/dotnet/designs/blob/master/accepted/2020/net5/net5.md +[os-minimum-version]: https://github.com/dotnet/designs/pull/97 From 853f7d92fcb87b0df904a753f22ee4b75e68de15 Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Tue, 14 Apr 2020 15:32:31 -0700 Subject: [PATCH 02/15] Fix typos --- .../2020/platform-checks/platform-checks.md | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md index e99f6546b..0422d719e 100644 --- a/accepted/2020/platform-checks/platform-checks.md +++ b/accepted/2020/platform-checks/platform-checks.md @@ -234,7 +234,7 @@ After that, the diagnostic disappears automatically. ### Non-Goals * Modelling partially portable APIs (that is APIs are applicable to more than - one platform but no all platforms). Those should Be modeled as capability + one platform but not all platforms). Those should be modeled as capability APIs, as as `RuntimeInformation.IsDynamicCodeSupported` for ref emit. This would be a separate analyzer. * Shipping the platform-specific analyzer and/or annotations out-of-band. @@ -308,11 +308,11 @@ namespace System.Runtime.Versioning AllowMultiple=false, Inherited=true)] public sealed class AddedInOSPlatformAttribute : OSPlatformAttribute { - public PlatformSpecificAttribute(string osPlatform, - int major, - int minor, - int build, - int revision); + public AddedInOSPlatformAttribute(string osPlatform, + int major, + int minor, + int build, + int revision); } [AttributeUsage(AttributeTargets.Assembly | @@ -326,11 +326,11 @@ namespace System.Runtime.Versioning AllowMultiple=false, Inherited=true)] public sealed class RemovedInOSPlatformAttribute : OSPlatformAttribute { - public PlatformSpecificAttribute(string osPlatform, - int major, - int minor, - int build, - int revision); + public RemovedInOSPlatformAttribute(string osPlatform, + int major, + int minor, + int build, + int revision); } [AttributeUsage(AttributeTargets.Assembly | @@ -344,11 +344,11 @@ namespace System.Runtime.Versioning AllowMultiple=false, Inherited=true)] public sealed class ObsoletedInOSPlatformAttribute : OSPlatformAttribute { - public PlatformSpecificAttribute(string osPlatform, - int major, - int minor, - int build, - int revision); + public ObsoletedInOSPlatformAttribute(string osPlatform, + int major, + int minor, + int build, + int revision); public string Url { get; set; } } } @@ -372,8 +372,8 @@ determined by (1). If (2) is platform-specific it is combined with (1): -* If the platforms between and (2) are disjoint (such as iOS and Windows), the - information from (1) is discarded. +* If the platforms between (1) and (2) are disjoint (such as iOS and Windows), + the information from (1) is discarded. * Otherwise the platform context's minimum version is the maximum of the versions determined by (1) and (2). From 4ade760aba546b2b9e9fba3d8c2d2fbd575deee0 Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Tue, 14 Apr 2020 16:01:41 -0700 Subject: [PATCH 03/15] Add modifiers --- accepted/2020/platform-checks/platform-checks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md index 0422d719e..ceb142ac7 100644 --- a/accepted/2020/platform-checks/platform-checks.md +++ b/accepted/2020/platform-checks/platform-checks.md @@ -95,7 +95,7 @@ After doing this, he gets a diagnostic in this code: > 'NSFizzBuff' requires iOS 14 or later. ```C# -static void ProvideExtraPop() +private static void ProvideExtraPop() { NSFizzBuff(); } @@ -105,7 +105,7 @@ He invokes the Roslyn code fixer which suggests to guard the call. This results in the following code: ```C# -static void ProvideExtraPop() +private static void ProvideExtraPop() { if (RuntimeInformation.IsOSPlatformOrLater(OSPlatform.iOS, 14)) NSFizzBuff(); From 157ed9172ce60e6dea9ca01aba3b8c66fee4867a Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Tue, 14 Apr 2020 16:04:18 -0700 Subject: [PATCH 04/15] Clariy action taken in user scenario --- accepted/2020/platform-checks/platform-checks.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md index ceb142ac7..c4878917b 100644 --- a/accepted/2020/platform-checks/platform-checks.md +++ b/accepted/2020/platform-checks/platform-checks.md @@ -118,7 +118,9 @@ After upgrading to iOS 14 Miguel also got the following diagnostic: > 'UIApplicationExitsOnSuspend' has been deprecated since iOS 13.1 -Miguel decides that he'll tackle that problem later as he needs to ship now. +Miguel decides that he'll tackle that problem later as he needs to ship now,so +he invokes the generic "Suppress in code" code fixer which surrounds his code +with `#pragma warning disable`. ### Detecting removal of OS APIs From 7fa1f864c2fc8c264f382fdcb6aa85649b42157c Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Tue, 14 Apr 2020 16:05:05 -0700 Subject: [PATCH 05/15] Fix typo --- accepted/2020/platform-checks/platform-checks.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md index c4878917b..001fb4d13 100644 --- a/accepted/2020/platform-checks/platform-checks.md +++ b/accepted/2020/platform-checks/platform-checks.md @@ -129,16 +129,15 @@ iOS 15. After upgrading Miguel gets the following diagnostic: > 'UIApplicationExitsOnSuspend' has been removed since iOS 15.0 -Doh! After reading more details, he releases that he can just stop calling this +Doh! After reading more details, he realizes that he can just stop calling this API because Apple automatically suspends apps leaving the foreground when they -don't require background execution. However, since he still supports iOS 12, he -need +don't require background execution. So he deletes the code that calls the API. ### Finding usage of platform-specific APIs Alejandra is working at Fabrikam, where she's tasked with porting their asset -management system to .NET Core. The application consists of several web -services, a desktop application, and a set of libraries that are shared. +management system to .NET 5. The application consists of several web services, a +desktop application, and a set of libraries that are shared. She begins by porting the core business logic libraries to .NET 5. After porting, she's getting a diagnostic in the code below: From 799c2f2e22e73a91c0e77490ed5d7318ae3467e3 Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Tue, 14 Apr 2020 16:08:39 -0700 Subject: [PATCH 06/15] Rename IsOSPlatformAndEarlier to IsOSPlatformEarlierThan --- accepted/2020/platform-checks/platform-checks.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md index 001fb4d13..3bde8c24a 100644 --- a/accepted/2020/platform-checks/platform-checks.md +++ b/accepted/2020/platform-checks/platform-checks.md @@ -275,10 +275,10 @@ namespace System.Runtime.InteropServices // Allows checking for the OS with a < version comparison // Used to guard APIs that were obsoleted or removed in the given OS release. - public static bool IsOSPlatformAndEarlier(OSPlatform osPlatform, int major); - public static bool IsOSPlatformAndEarlier(OSPlatform osPlatform, int major, int minor); - public static bool IsOSPlatformAndEarlier(OSPlatform osPlatform, int major, int minor, int build); - public static bool IsOSPlatformAndEarlier(OSPlatform osPlatform, int major, int minor, int build, int revision); + public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major); + public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor); + public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor, int build); + public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor, int build, int revision); } } @@ -410,7 +410,7 @@ public void Case2() ``` Diagnostics (2) and (3) are analogous except the guard is provided by -`RuntimeInformation.IsOSPlatformAndEarlier()`. +`RuntimeInformation.IsOSPlatformEarlierThan()`. ### Code Fixers From 16ea429edee85c51444d857521cd518ed4312d0c Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Tue, 14 Apr 2020 16:19:32 -0700 Subject: [PATCH 07/15] Clarify that the analyzer should be turned on by default --- accepted/2020/platform-checks/platform-checks.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md index 3bde8c24a..28d7894db 100644 --- a/accepted/2020/platform-checks/platform-checks.md +++ b/accepted/2020/platform-checks/platform-checks.md @@ -231,6 +231,9 @@ After that, the diagnostic disappears automatically. dependency. - Library consumers don't have to install a NuGet package to get diagnostics when using platform-specific APIs +* The analyzer must be satisfy the quality bar in order to be turned on by + default + - No false positives, low noise. ### Non-Goals From 34b0db0c7633264946bade461e94acb106b9ef2e Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Tue, 14 Apr 2020 16:36:37 -0700 Subject: [PATCH 08/15] Add missing OS platforms --- accepted/2020/platform-checks/platform-checks.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md index 28d7894db..1bbccf6eb 100644 --- a/accepted/2020/platform-checks/platform-checks.md +++ b/accepted/2020/platform-checks/platform-checks.md @@ -262,6 +262,9 @@ namespace System.Runtime.InteropServices // public static OSPlatform Windows { get; } public static OSPlatform Android { get; } public static OSPlatform iOS { get; } + public static OSPlatform macOS { get; } + public static OSPlatform tvOS { get; } + public static OSPlatform watchOS { get; } } public partial static class RuntimeInformation From 6f6c4eb2920644cd18ead1f307e6e51f1d5f9699 Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Tue, 14 Apr 2020 16:44:19 -0700 Subject: [PATCH 09/15] Rename OSPlatformAttribute to OSPlatformVersionAttribute --- .../2020/platform-checks/platform-checks.md | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md index 1bbccf6eb..f01530d75 100644 --- a/accepted/2020/platform-checks/platform-checks.md +++ b/accepted/2020/platform-checks/platform-checks.md @@ -290,13 +290,13 @@ namespace System.Runtime.InteropServices namespace System.Runtime.Versioning { - public abstract class OSPlatformAttribute : Attribute + public abstract class OSPlatformVersionAttribute : Attribute { - protected OSPlatformAttribute(string osPlatform, - int major, - int minor, - int build, - int revision); + protected OSPlatformVersionAttribute (string osPlatform, + int major, + int minor, + int build, + int revision); public string PlatformIdentifier { get; } public int Major { get; } public int Minor { get; } @@ -313,9 +313,9 @@ namespace System.Runtime.Versioning AttributeTargets.Property | AttributeTargets.Struct, AllowMultiple=false, Inherited=true)] - public sealed class AddedInOSPlatformAttribute : OSPlatformAttribute + public sealed class AddedInOSPlatformVersionAttribute : OSPlatformVersionAttribute { - public AddedInOSPlatformAttribute(string osPlatform, + public AddedInOSPlatformVersionAttribute (string osPlatform, int major, int minor, int build, @@ -331,9 +331,9 @@ namespace System.Runtime.Versioning AttributeTargets.Property | AttributeTargets.Struct, AllowMultiple=false, Inherited=true)] - public sealed class RemovedInOSPlatformAttribute : OSPlatformAttribute + public sealed class RemovedInOSPlatformVersionAttribute : OSPlatformVersionAttribute { - public RemovedInOSPlatformAttribute(string osPlatform, + public RemovedInOSPlatformVersionAttribute (string osPlatform, int major, int minor, int build, @@ -349,9 +349,9 @@ namespace System.Runtime.Versioning AttributeTargets.Property | AttributeTargets.Struct, AllowMultiple=false, Inherited=true)] - public sealed class ObsoletedInOSPlatformAttribute : OSPlatformAttribute + public sealed class ObsoletedInOSPlatformVersionAttribute : OSPlatformVersionAttribute { - public ObsoletedInOSPlatformAttribute(string osPlatform, + public ObsoletedInOSPlatformVersionAttribute (string osPlatform, int major, int minor, int build, @@ -366,7 +366,7 @@ namespace System.Runtime.Versioning To determine the platform context of the call site the analyzer must consider the following two configurations: -1. **Call-site**. Application of `AddedInOSPlatformAttribute` to the containing +1. **Call-site**. Application of `AddedInOSPlatformVersionAttribute` to the containing member, type, module, or assembly. 2. **Project** The project TFMs and `TargetPlatformMinVersion`. If the project @@ -432,8 +432,9 @@ provides here is educational and avoids having to lookup the correct version numbers. For diagnostic (1) we should also offer a fixer that allows it to be marked as -platform specific by applying `AddedInOSPlatformAttribute`. If the attribute is -already applied, it should offer to change the platform and version. +platform specific by applying `AddedInOSPlatformVersionAttribute`. If the +attribute is already applied, it should offer to change the platform and +version. ## Q & A From 2c3e217baf075fe835fe766e36e8815716a1f11a Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Tue, 14 Apr 2020 17:09:46 -0700 Subject: [PATCH 10/15] Clarify whether we use marketing version numbers --- .../2020/platform-checks/platform-checks.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md index f01530d75..5d624e99c 100644 --- a/accepted/2020/platform-checks/platform-checks.md +++ b/accepted/2020/platform-checks/platform-checks.md @@ -451,6 +451,23 @@ We have an existing project [dotnet/platform-compat], available as the This is intended to become the productized version of [Microsoft.DotNet.Analyzers.Compatibility]. +### What set of version numbers are you going to use? + +Specifically, are we going to use the "marketing" version numbers or what the +system returns as version numbers? + +This is related to the problem that some operating systems, such as Windows, +have quirked the OS API to lie to the application in order to make it compatible +(because way too often apps get version checks wrong). + +The version is part of the reason why we don't expose a version number but +instead perform the checking on behalf of the user. In general, the +implementation will do whatever it needs to get the real version number. + +As far as the reference assembly is concerned, the expectation is that the +bindings will be annotated with the version information as documented by the +native operating system SDK. + [dotnet/platform-compat]: https://github.com/dotnet/platform-compat [API Analyzer]: https://devblogs.microsoft.com/dotnet/introducing-api-analyzer/ [Microsoft.DotNet.Analyzers.Compatibility]: https://www.nuget.org/packages/Microsoft.DotNet.Analyzers.Compatibility From 7b198031c3605eebdaf4e92c27fb8bbeee442d93 Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Tue, 14 Apr 2020 17:32:13 -0700 Subject: [PATCH 11/15] Clarify role of Environment.OSVersion --- accepted/2020/platform-checks/platform-checks.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md index 5d624e99c..3700e57a3 100644 --- a/accepted/2020/platform-checks/platform-checks.md +++ b/accepted/2020/platform-checks/platform-checks.md @@ -468,6 +468,18 @@ As far as the reference assembly is concerned, the expectation is that the bindings will be annotated with the version information as documented by the native operating system SDK. +### What about `Environment.OSVersion`? + +Should the analyzer consider checks again `Environment.OSVersion`? + +No. In fact, we should obsolete `Environment.OSVersion`. Two reasons: + +1. `Environment.OSVersion` doesn't return the correct version number today (it's + shimmed). We could change but that would just further the lifetime of a + broken API. Instead, we should push folks to + `RuntimeInformation.IsOSPlatformOrLater()`. +2. It makes the analyzer more complex and more prone to false positives + [dotnet/platform-compat]: https://github.com/dotnet/platform-compat [API Analyzer]: https://devblogs.microsoft.com/dotnet/introducing-api-analyzer/ [Microsoft.DotNet.Analyzers.Compatibility]: https://www.nuget.org/packages/Microsoft.DotNet.Analyzers.Compatibility From 9a6af256e736d92edd950e37b1e8f3547d458128 Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Tue, 14 Apr 2020 17:35:42 -0700 Subject: [PATCH 12/15] Clarify why this doesn't handle partially portable APIs --- .../2020/platform-checks/platform-checks.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md index 3700e57a3..f1f97d30e 100644 --- a/accepted/2020/platform-checks/platform-checks.md +++ b/accepted/2020/platform-checks/platform-checks.md @@ -480,8 +480,32 @@ No. In fact, we should obsolete `Environment.OSVersion`. Two reasons: `RuntimeInformation.IsOSPlatformOrLater()`. 2. It makes the analyzer more complex and more prone to false positives +### What about APIs that don't work on specific operating systems? + +For example, there are some APIs that work on Windows and Linux, but not on +macOS. + +I used to think in those terms but I don't think that's useful because it gets +complicated fast. I think we're better off modelling these as two distinct +concepts: + +1. **Platform specific APIs**. These are specific to a particular OS. Examples: Registry, WinForms, NS*. +2. **Partially portable APIs**. These are features that aren't OS specific but + can only be implemented on some operating systems/execution environments. + Examples: RefEmit, file system access, thread creation. + +I think we can safely model (1) with OS checks because the set of supported OS +is fixed and known a priori, so burning metadata in the reference assemblies is +fine. + +For (2) I think we're better of modelling them as capability APIs so that when +the support matrix changes, less code is broken. We can try to formalize +capability APIs as well and [provide an analyzer for that][capability-checks] +but I consider that out of scope for this feature. + [dotnet/platform-compat]: https://github.com/dotnet/platform-compat [API Analyzer]: https://devblogs.microsoft.com/dotnet/introducing-api-analyzer/ [Microsoft.DotNet.Analyzers.Compatibility]: https://www.nuget.org/packages/Microsoft.DotNet.Analyzers.Compatibility [net5-tfms]: https://github.com/dotnet/designs/blob/master/accepted/2020/net5/net5.md [os-minimum-version]: https://github.com/dotnet/designs/pull/97 +[capability-checks]: https://github.com/dotnet/designs/pull/111 From 528b0d625744cea16102fd03ba2200e6ff9476cf Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Wed, 15 Apr 2020 17:39:31 -0700 Subject: [PATCH 13/15] Fix spacing --- .../2020/platform-checks/platform-checks.md | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md index f1f97d30e..560c331cf 100644 --- a/accepted/2020/platform-checks/platform-checks.md +++ b/accepted/2020/platform-checks/platform-checks.md @@ -292,11 +292,11 @@ namespace System.Runtime.Versioning { public abstract class OSPlatformVersionAttribute : Attribute { - protected OSPlatformVersionAttribute (string osPlatform, - int major, - int minor, - int build, - int revision); + protected OSPlatformVersionAttribute(string osPlatform, + int major, + int minor, + int build, + int revision); public string PlatformIdentifier { get; } public int Major { get; } public int Minor { get; } @@ -315,11 +315,11 @@ namespace System.Runtime.Versioning AllowMultiple=false, Inherited=true)] public sealed class AddedInOSPlatformVersionAttribute : OSPlatformVersionAttribute { - public AddedInOSPlatformVersionAttribute (string osPlatform, - int major, - int minor, - int build, - int revision); + public AddedInOSPlatformVersionAttribute(string osPlatform, + int major, + int minor, + int build, + int revision); } [AttributeUsage(AttributeTargets.Assembly | @@ -333,11 +333,11 @@ namespace System.Runtime.Versioning AllowMultiple=false, Inherited=true)] public sealed class RemovedInOSPlatformVersionAttribute : OSPlatformVersionAttribute { - public RemovedInOSPlatformVersionAttribute (string osPlatform, - int major, - int minor, - int build, - int revision); + public RemovedInOSPlatformVersionAttribute(string osPlatform, + int major, + int minor, + int build, + int revision); } [AttributeUsage(AttributeTargets.Assembly | @@ -351,7 +351,7 @@ namespace System.Runtime.Versioning AllowMultiple=false, Inherited=true)] public sealed class ObsoletedInOSPlatformVersionAttribute : OSPlatformVersionAttribute { - public ObsoletedInOSPlatformVersionAttribute (string osPlatform, + public ObsoletedInOSPlatformVersionAttribute(string osPlatform, int major, int minor, int build, From 6946c9a558ff272ad365e72b2c38b56afea9bf0d Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Fri, 17 Jul 2020 16:43:25 -0700 Subject: [PATCH 14/15] Update spec to match plan --- .../2020/platform-checks/platform-checks.md | 228 +++++++++++------- 1 file changed, 144 insertions(+), 84 deletions(-) diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md index 560c331cf..be1f11b1e 100644 --- a/accepted/2020/platform-checks/platform-checks.md +++ b/accepted/2020/platform-checks/platform-checks.md @@ -245,64 +245,46 @@ After that, the diagnostic disappears automatically. ## Design -### Attribute +### Attributes OS bindings and platforms-specific BCL APIs will be annotated using a set of custom attributes: ```C# -namespace System.Runtime.InteropServices +namespace System.Runtime.Versioning { - public partial struct OSPlatform + // Base type for all platform-specific attributes. Primarily used to allow grouping + // in documentation. + public abstract class OSPlatformAttribute : Attribute { - // Existing properties - // public static OSPlatform FreeBSD { get; } - // public static OSPlatform Linux { get; } - // public static OSPlatform OSX { get; } - // public static OSPlatform Windows { get; } - public static OSPlatform Android { get; } - public static OSPlatform iOS { get; } - public static OSPlatform macOS { get; } - public static OSPlatform tvOS { get; } - public static OSPlatform watchOS { get; } + private protected OSPlatformAttribute(string platformName); + public string PlatformName { get; } } - public partial static class RuntimeInformation + // Records the platform that the project targeted. + [AttributeUsage(AttributeTargets.Assembly, + AllowMultiple=false, Inherited=false)] + public sealed class TargetPlatformAttribute : OSPlatformAttribute { - // Existing API - // public static bool IsOSPlatform(OSPlatform osPlatform); - - // Check for the OS with a >= version comparison - // Used to guard APIs that were added in the given OS release. - public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major); - public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor); - public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor, int build); - public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor, int build, int revision); - - // Allows checking for the OS with a < version comparison - // Used to guard APIs that were obsoleted or removed in the given OS release. - public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major); - public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor); - public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor, int build); - public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor, int build, int revision); + public TargetPlatformAttribute(string platformName); + public string PlatformName { get; } } -} -namespace System.Runtime.Versioning -{ - public abstract class OSPlatformVersionAttribute : Attribute - { - protected OSPlatformVersionAttribute(string osPlatform, - int major, - int minor, - int build, - int revision); - public string PlatformIdentifier { get; } - public int Major { get; } - public int Minor { get; } - public int Build { get; } - public int Revision { get; } - } + // Records the minimum platform that is required in order to the marked thing. + // + // * When applied to an assembly, it means the entire assembly cannot be called + // into on earlier versions. It records the TargetPlatformMinVersion property. + // + // * When applied to an API, it means the API cannot be called from an earlier + // version. + // + // In either case, the caller can either mark itself with MinimumPlatformAttribute + // or guard the call with a platform check. + // + // The attribute can be applied multiple times for different operating systems. + // That means the API is supported on multiple operating systems. + // + // A given platform should only be specified once. [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | @@ -312,16 +294,16 @@ namespace System.Runtime.Versioning AttributeTargets.Module | AttributeTargets.Property | AttributeTargets.Struct, - AllowMultiple=false, Inherited=true)] - public sealed class AddedInOSPlatformVersionAttribute : OSPlatformVersionAttribute + AllowMultiple=true, Inherited=false)] + public sealed class MinimumOSPlatformAttribute : OSPlatformAttribute { - public AddedInOSPlatformVersionAttribute(string osPlatform, - int major, - int minor, - int build, - int revision); + public MinimumOSPlatformAttribute(string platformName); } + // Marks APIs that were removed in a given operating system version. + // + // Primarily used by OS bindings to indicate APIs that are only available in + // earlier versions. [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Constructor | @@ -330,16 +312,16 @@ namespace System.Runtime.Versioning AttributeTargets.Module | AttributeTargets.Property | AttributeTargets.Struct, - AllowMultiple=false, Inherited=true)] - public sealed class RemovedInOSPlatformVersionAttribute : OSPlatformVersionAttribute + AllowMultiple=true, Inherited=false)] + public sealed class RemovedInOSPlatformAttribute : OSPlatformAttribute { - public RemovedInOSPlatformVersionAttribute(string osPlatform, - int major, - int minor, - int build, - int revision); + public RemovedInPlatformAttribute(string platformName); } + // Marks APIs that were obsoleted in a given operating system version. + // + // Primarily used by OS bindings to indicate APIs that should only be used in + // earlier versions. [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Constructor | @@ -348,41 +330,118 @@ namespace System.Runtime.Versioning AttributeTargets.Module | AttributeTargets.Property | AttributeTargets.Struct, - AllowMultiple=false, Inherited=true)] - public sealed class ObsoletedInOSPlatformVersionAttribute : OSPlatformVersionAttribute + AllowMultiple=true, Inherited=false)] + public sealed class ObsoletedInOSPlatformAttribute : OSPlatformAttribute { - public ObsoletedInOSPlatformVersionAttribute(string osPlatform, - int major, - int minor, - int build, - int revision); + public ObsoletedInPlatformAttribute(string platformName); + public ObsoletedInPlatformAttribute(string platformName, string message); + public string Message { get; } public string Url { get; set; } } } ``` -### Platform context +### Platforms -To determine the platform context of the call site the analyzer must consider -the following two configurations: +The existing type `OSPlatform` is used to refer to OS platforms. In order to +support the upcoming platforms in .NET, we're going to add values for Android, +the Apple platform family, and Blazor Web Assembly which we decided to identify +as "browser". -1. **Call-site**. Application of `AddedInOSPlatformVersionAttribute` to the containing - member, type, module, or assembly. +```C# +namespace System.Runtime.InteropServices +{ + public partial struct OSPlatform + { + // Existing properties + // public static OSPlatform FreeBSD { get; } + // public static OSPlatform Linux { get; } + // public static OSPlatform Windows { get; } + + // Updated property + [EditorBrowsable(EditorBrowsableState.Never)] + public static OSPlatform OSX { get; } + + // New properties + public static OSPlatform Android { get; } + public static OSPlatform Browser { get; } + public static OSPlatform iOS { get; } + public static OSPlatform macOS { get; } + public static OSPlatform tvOS { get; } + public static OSPlatform watchOS { get; } + } +} +``` + +**Note:** We decided to also add `macOS` as that's how Apple refers to the +operating system now. The existing OSX entry will continue to exist. When +running on macOS, the query APIs will return true for both `macOS` as well as +`OSX`. Since the analyzer can't invoke the APIs, it has to have special +knowledge that treats `macOS` and `OSX`. + +### Platform guards + +We have an existing API `RuntimeInformation.IsOSPlatform(OSPlatform)` that +allows to check whether you're currently running on this platform. + +It's worth pointing out that this designs allows for having both, very specific +OS platforms, such as *Ubuntu* or classes of platforms, such as *Linux*. It's +allows by the fact that the consumer doesn't compare current platform to +particular value but instead asks "am I currently on this platform". This allows +the API to return true for both `OSPlatform.Linux` and `OSPlatform.Ubuntu`. + +To check for specific version, we're introducing analogous APIs where the caller +supplies the platform and version information and the API will perform the +check. We'll allow both string-based checks as well as checks by passing in +`OSPlatform` and version information separately. + +**Note:** The analyzer assumes that the provided values are constant. Extracting +these values into local variables/fields will work so long they are marked as +constant. Since `OSPlatform` is a struct, it can't be marked as constant, so the +expectation here is that developers pass them directly in. + +```C# +namespace System.Runtime.InteropServices +{ + public partial static class RuntimeInformation + { + // Existing API + // public static bool IsOSPlatform(OSPlatform osPlatform); -2. **Project** The project TFMs and `TargetPlatformMinVersion`. If the project - has an platform-specific TFM (such as `net5.0-ios13.0`) we consider it to be - on the specified platform. If `TargetPlatformMinVersion` is set, we assume - that version, otherwise we assume the version as specified by the TFM. + // Check for the OS with a >= version comparison + // Used to guard APIs that were added in the given OS release. + public static bool IsOSPlatformOrLater(string platformName); + public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major); + public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor); + public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor, int build); + public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor, int build, int revision); -If (2) is platform neutral (e.g. `net5.0`), the platform context is entirely -determined by (1). + // Allows checking for the OS with a < version comparison + // Used to guard APIs that were obsoleted or removed in the given OS release. The comparison + // is less than (rather than less than or equal) so that people can pass in the version where + // API became obsoleted/removed. + public static bool IsOSPlatformEarlierThan(string platformName); + public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major); + public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor); + public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor, int build); + public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor, int build, int revision); + } +} +``` -If (2) is platform-specific it is combined with (1): +### Platform context + +To determine the platform context of the call site the analyzer must consider +application of `MinimumOSPlatformAttribute` to the containing member, type, +module, or assembly. The first one will determine the platform context, in other +words the assumption is that more scoped applications of the attribute will +restrict the set of platforms. -* If the platforms between (1) and (2) are disjoint (such as iOS and Windows), - the information from (1) is discarded. -* Otherwise the platform context's minimum version is the maximum of the - versions determined by (1) and (2). +**Note:** The analyzer doesn't need to consider MSBuild properties such as +`` or `` because the value of the +`` property is injected into the generated `AssemblyInfo.cs` +file, which will have an assembly-level application of +`MinimumOSPlatformAttribute`. ### Diagnostics @@ -489,7 +548,8 @@ I used to think in those terms but I don't think that's useful because it gets complicated fast. I think we're better off modelling these as two distinct concepts: -1. **Platform specific APIs**. These are specific to a particular OS. Examples: Registry, WinForms, NS*. +1. **Platform specific APIs**. These are specific to a particular OS. Examples: + Registry, WinForms, NS*. 2. **Partially portable APIs**. These are features that aren't OS specific but can only be implemented on some operating systems/execution environments. Examples: RefEmit, file system access, thread creation. From bc177efe532a62db97357d0e76e45701f99d988a Mon Sep 17 00:00:00 2001 From: Immo Landwerth Date: Fri, 17 Jul 2020 16:46:06 -0700 Subject: [PATCH 15/15] Apply suggestions from code review Co-authored-by: Jeff Handley --- accepted/2020/platform-checks/platform-checks.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/accepted/2020/platform-checks/platform-checks.md b/accepted/2020/platform-checks/platform-checks.md index be1f11b1e..393d5cdff 100644 --- a/accepted/2020/platform-checks/platform-checks.md +++ b/accepted/2020/platform-checks/platform-checks.md @@ -61,7 +61,7 @@ This document proposes: 2. A Roslyn analyzer that informs developers when they use platform-specific APIs from call sites where the API might not be available. -This work draws heavily form the experience of the [API Analyzer] and improves +This work draws heavily from the experience of the [API Analyzer] and improves on it by making it a first-class platform feature. ## Scenarios and User Experience @@ -70,7 +70,7 @@ on it by making it a first-class platform feature. Miguel is building Baby Shark, a popular iOS application. He started with .NET that supported iOS 13 but Apple just released iOS 14, which adds the great -`NSFizBuzz` API that gives his app the extra pop. +`NSFizzBuzz` API that gives his app the extra pop. After upgrading the app and writing the code, Miguel decides that while the feature is cool, it doesn't warrant cutting of all his customers who are @@ -92,12 +92,12 @@ currently still on iOS 13. So he edits the project file and sets After doing this, he gets a diagnostic in this code: -> 'NSFizzBuff' requires iOS 14 or later. +> 'NSFizzBuzz' requires iOS 14 or later. ```C# private static void ProvideExtraPop() { - NSFizzBuff(); + NSFizzBuzz(); } ``` @@ -108,7 +108,7 @@ in the following code: private static void ProvideExtraPop() { if (RuntimeInformation.IsOSPlatformOrLater(OSPlatform.iOS, 14)) - NSFizzBuff(); + NSFizzBuzz(); } ``` @@ -118,13 +118,13 @@ After upgrading to iOS 14 Miguel also got the following diagnostic: > 'UIApplicationExitsOnSuspend' has been deprecated since iOS 13.1 -Miguel decides that he'll tackle that problem later as he needs to ship now,so +Miguel decides that he'll tackle that problem later as he needs to ship now, so he invokes the generic "Suppress in code" code fixer which surrounds his code with `#pragma warning disable`. ### Detecting removal of OS APIs -A few year after his massively successful fizz-buzzed Baby Shark, Apple releases +A few years after his massively successful fizz-buzzed Baby Shark, Apple releases iOS 15. After upgrading Miguel gets the following diagnostic: > 'UIApplicationExitsOnSuspend' has been removed since iOS 15.0