Skip to content
Merged
27 changes: 21 additions & 6 deletions docs/coding-guidelines/adding-api-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,38 @@ New public APIs must be documented with triple-slash comments on top of them. Vi
[API writing guidelines](https://github.com/dotnet/dotnet-api-docs/wiki) has information about language and proper style for writing API documentation.
If your new API or the APIs it calls throw any exceptions, those need to be manually documented by adding the `<exception></exception>` elements.

After your change is merged, we will eventually port them to the [dotnet-api-docs](https://github.com/dotnet/dotnet-api-docs) repo. The tools used for this port live in [api-docs-sync](https://github.com/dotnet/api-docs-sync) repo. Once the dotnet-api-docs change
is merged, your comments will start showing up in the official API documentation at https://learn.microsoft.com, and later they'll appear in IntelliSense
in Visual Studio and Visual Studio Code.

The rest of the documentation workflow depends on whether the assembly has the `UseCompilerGeneratedDocXmlFile` property set in its project file:

**For libraries without this property (or with it set to `true`, which is the default):**
- Source comments in this repo are the source of truth for documentation.
- Triple-slash comments in source code are synced to dotnet-api-docs periodically (every preview).
- More recently introduced libraries typically follow this workflow.

**For libraries with this property set to `false`:**
- The docs in the [dotnet-api-docs](https://github.com/dotnet/dotnet-api-docs) repo are the source of truth for documentation.
- After your change is merged into the runtime repo, we will eventually port the docs to the dotnet-api-docs repo. The tools used for this port live in
the [api-docs-sync](https://github.com/dotnet/api-docs-sync) repo. Once the dotnet-api-docs change
is merged, your doc comments will appear in
the official API documentation at https://learn.microsoft.com, and later they'll appear in IntelliSense
in Visual Studio and Visual Studio Code.
- Older libraries typically follow this workflow.

### API usage examples

API usage examples are included in the test sources so they can be validated during regular test runs. These examples should either be placed in the `examples` subdirectory or use a filename with the `Examples` suffix. depending on what's the best fit for the test project structure. The specific code intended for the published documentation is marked with a #region directive, which allows test-related boilerplate (such as the [Fact] attribute) to be excluded from the final documentation.

The API usage examples are referenced from the documentation using the code-csharp directive. For example:

`[!code-csharp[](../../../../tests/System.Text.RegularExpressions/FunctionalTests/Regex.Examples.cs#Match)]`

This directive pulls the code snippet identified by the specified `#region` from the source file and embeds it directly into the documentation.

### Documentation placement in platform-specific libraries

When a library targets platform-specific frameworks (e.g. `net11.0-windows`, `net11.0-linux`),
only **one** platform's compiler-generated doc XML is selected as the source of truth and shipped
to all customers in the IntelliSense package. This means that if XML doc comments for a public API
appear only in a platform-specific partial file, they may be missing from the shipped docs on other
appear only in a platform-specific partial file, they might be missing from the shipped docs on other
platforms.

To ensure consistent documentation across all platforms, follow these rules:
Expand Down Expand Up @@ -106,7 +121,7 @@ established `TypeNameAsync.cs` pattern), suppress the specific diagnostic with
updates must be made directly in the dotnet-api-docs repo.
- It's fine to make updates to the triple-slash comments later to aid local development, they just won't automatically flow into the official docs. Copilot can help with porting small changes
in triple-slash comments to dotnet-api-docs. [PortToDocs](https://github.com/dotnet/api-docs-sync/blob/main/docs/PortToDocs.md) tool works better for ports of large changes.
- Older libraries typically follow this workflow. Libraries in this mode can work towards a better workflow in the future by using api-docs-sync tools to port back docs to source, then removing the `UseCompilerGeneratedDocXmlFile` property.
- Older libraries typically follow this workflow. Libraries in this mode can work towards a better workflow in the future by using api-docs-sync tools or an AI agent to port back docs to source, then removing the `UseCompilerGeneratedDocXmlFile` property.

## FAQ

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,11 +442,67 @@ public Match Match(string input)
/// <paramref name="startat"/> is less than zero or greater than the length of <paramref name="input"/>.
/// </exception>
/// <exception cref="RegexMatchTimeoutException">A time-out occurred.</exception>
/// <remarks>
/// For more information about <paramref name="startat"/>, see
/// <see href="https://github.com/dotnet/docs/blob/main/docs/fundamentals/runtime-libraries/system-text-regularexpressions-regex-match.md">
/// Supplemental API remarks for Regex.Match</see>.
/// </remarks>
/// <remarks><format type="text/markdown"><![CDATA[
/// The <xref:System.Text.RegularExpressions.Regex.Match(System.String,System.Int32)> method returns the first substring that matches a regular expression pattern,
/// starting at or after the `startat` character position, in an input string. The regular expression pattern for which the
/// <xref:System.Text.RegularExpressions.Regex.Match(System.String,System.Int32)> method searches is defined by the call to one of the <xref:System.Text.RegularExpressions.Regex>
/// class constructors. For information about the language elements used to build a regular expression pattern,
/// see [Regular Expression Language - Quick Reference](https://learn.microsoft.com/dotnet/standard/base-types/regular-expression-language-quick-reference).
Comment thread
AaronRobinsonMSFT marked this conversation as resolved.
///
/// ## The `startat` parameter
///
/// Use the `startat` parameter to specify the character position at which the search starts. In a left-to-right search, any matches that begin before `startat` in the string are ignored.
/// To start at the default position, use the <xref:System.Text.RegularExpressions.Regex.Match(System.String)> overload (or pass 0 for a left-to-right search and `input.Length` for a right-to-left search).
/// Despite starting the search at `startat`, the index of any returned match is relative to the start of the string.
///
/// Although the regular expression engine doesn't return any match starting before `startat`, it doesn't ignore the string before `startat`. This means that assertions
/// such as [anchors](https://learn.microsoft.com/dotnet/standard/base-types/anchors-in-regular-expressions) or [lookbehind assertions](https://learn.microsoft.com/dotnet/standard/base-types/backtracking-in-regular-expressions#lookbehind-assertions)
/// still apply to the input as a whole. For example, the following code includes a pattern with a lookbehind assertion that's satisfied even though it occurs
/// before the `startat` index of 5 in the input string.
///
/// [!code-csharp[](../../../../tests/FunctionalTests/Regex.Examples.cs#Match)]
///
/// > [!TIP]
/// > - If a pattern starts with the `^` anchor, `startat` is greater than 0, and the regular expression isn't using <xref:System.Text.RegularExpressions.RegexOptions.Multiline?displayProperty=nameWithType>, no matches will ever be found since they are constrained by `^` to start at index 0.
/// > - The [`\G` anchor](https://learn.microsoft.com/dotnet/standard/base-types/anchors-in-regular-expressions#contiguous-matches-g) is satisfied at `startat`. Because of this, if you want to restrict
/// > a match so that it begins exactly at a particular character position in the string, anchor the regular expression with a `\G` on the left for a left-to-right pattern.
/// > This restricts the match so it must start exactly at `startat` (or, when multiple matches are desired, so the matches are contiguous).
///
/// ## Right-to-left searches
///
/// A right-to-left search, that is, when the regular expression pattern is constructed with the
/// <xref:System.Text.RegularExpressions.RegexOptions.RightToLeft?displayProperty=nameWithType> option, behaves in the following ways:
///
/// - The scan moves in the opposite direction and the pattern is matched from back (right) to front (left).
/// - The default starting position is the right end of the input string.
/// - If `startat` is specified, the right-to-left scan begins at the character at `startat` - 1 (not `startat`).
/// - When the `\G` anchor is specified at the right end of a pattern, it restricts the (first) match to end exactly at `startat` - 1.
///
/// For more information about right-to-left searches, see [Right-to-left mode](https://learn.microsoft.com/dotnet/standard/base-types/regular-expression-options#right-to-left-mode).
///
/// ## Determine whether a match is found
///
/// You can determine whether the regular expression pattern has been found in the input string by checking the value of the returned <xref:System.Text.RegularExpressions.Match>
/// object's <xref:System.Text.RegularExpressions.Group.Success> property. If a match is found, the returned <xref:System.Text.RegularExpressions.Match> object's
/// <xref:System.Text.RegularExpressions.Capture.Value> property contains the substring from `input` that matches the regular expression pattern.
/// If no match is found, its value is <xref:System.String.Empty?displayProperty=nameWithType>.
///
/// ## First or multiple matches
///
/// This method returns the first substring found by scanning `input` starting at the `startat` position (or, for right-to-left searches, at `startat` - 1) that matches the regular expression pattern.
/// You can retrieve subsequent matches by repeatedly calling the returned <xref:System.Text.RegularExpressions.Match> object's
/// <xref:System.Text.RegularExpressions.Match.NextMatch*?displayProperty=nameWithType> method. You can also retrieve all matches in a single method call by calling the
/// <xref:System.Text.RegularExpressions.Regex.Matches(System.String,System.Int32)?displayProperty=nameWithType> method.
///
/// ## Time-out exceptions
///
/// The <xref:System.Text.RegularExpressions.RegexMatchTimeoutException> exception is thrown if the execution time of the matching operation exceeds the time-out
/// interval specified by the <xref:System.Text.RegularExpressions.Regex.%23ctor(System.String,System.Text.RegularExpressions.RegexOptions,System.TimeSpan)?displayProperty=nameWithType>
/// constructor. If you do not set a time-out interval when you call the constructor, the exception is thrown if the operation exceeds any time-out value established
/// for the application domain in which the <xref:System.Text.RegularExpressions.Regex> object is created. If no time-out is defined in the <xref:System.Text.RegularExpressions.Regex>
/// constructor call or in the application domain's properties, or if the time-out value is <xref:System.Text.RegularExpressions.Regex.InfiniteMatchTimeout?displayProperty=nameWithType>,
/// no exception is thrown.
/// ]]></format></remarks>
public Match Match(string input, int startat)
{
if (input is null)
Expand Down
Comment thread
jkotas marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Text.RegularExpressions;
Comment thread
gewarren marked this conversation as resolved.
Comment thread
gewarren marked this conversation as resolved.
using Xunit;

namespace System.Text.RegularExpressions.Examples
{
public class MatchExamples
{
[Fact]
public static void MatchZipCode()
{
#region Match
string input = "Zip code: 98052";
var regex = new Regex(@"(?<=Zip code: )\d{5}");
Match match = regex.Match(input, 5);
if (match.Success)
Console.WriteLine($"Match found: {match.Value}");

// This code prints the following output:
//
// Match found: 98052
#endregion

Assert.True(match.Success);
Assert.Equal("98052", match.Value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<Compile Include="Regex.Ctor.Tests.cs" />
<Compile Include="Regex.Cache.Tests.cs" />
<Compile Include="Regex.EscapeUnescape.Tests.cs" />
<Compile Include="Regex.Examples.cs" />
<Compile Include="Regex.GetGroupNames.Tests.cs" />
<Compile Include="Regex.Groups.Tests.cs" />
<Compile Include="Regex.KnownPattern.Tests.cs" />
Expand Down
Loading