λ1201: Prefer the static ParseOrNone member on the parsed type

Since 3.8.0 every Parse…OrNone extension on string and on spans also exists as a static extension member on the type it parses. text.ParseInt32OrNone() is int.ParseOrNone(text), which reads like the int.TryParse it wraps.

Cause

A Parse…OrNone extension of ParseExtensions is called on a string, a ReadOnlySpan<char> or a ReadOnlySpan<byte>, the file is compiled as C# 14 or later, and the static member on the parsed type binds for the same arguments. The rule covers the generated members (ParseInt32OrNone, ParseExactDateTimeOrNone, …), the enum form (ParseEnumOrNone<TEnum>) and the generic forms (ParseOrNone<T>, ParseNumberOrNone<T>); the parsed type comes from the Option<T> the extension returns.

A method group (texts.Select(ParseExtensions.ParseInt32OrNone)) is not reported: a static extension member has no method-group form of the same shape.

Reason for rule

The static form is the one that mirrors the base class library, and it is the form that survives if 4.0.0 retires the string family. Adopting it in one sweep is what the code fix is for.

How to fix violations

Call the static member on the parsed type with the text as the first argument. The code fix does this, keeps the other arguments and their names, and supports fix-all.

Examples

Disallowed

Option<int> number = text.ParseInt32OrNone();
Option<DateTime> date = text.ParseExactDateTimeOrNone("O", CultureInfo.InvariantCulture);
Option<Color> color = text.ParseEnumOrNone<Color>(ignoreCase: true);

Allowed

Option<int> number = int.ParseOrNone(text);
Option<DateTime> date = DateTime.ParseExactOrNone(text, "O", CultureInfo.InvariantCulture);
Option<Color> color = Color.ParseOrNone(text, ignoreCase: true);

// A method group has no static counterpart of the same shape.
IEnumerable<Option<int>> numbers = texts.Select(ParseExtensions.ParseInt32OrNone);