Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ words:
- dedenting
- declspec
- deletedmembersclass
- desync
- Dema
- dllexport
- docstrings
Expand Down Expand Up @@ -62,6 +63,7 @@ words:
- Linq
- listheader
- maxcount
- Memberless
- misattributed
- modreq
- msbuild
Expand Down
1 change: 1 addition & 0 deletions .markdownlint-cli2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ ignores:
- '**/third-party/**'
- '**/3rd-party/**'
- '**/generated/**'
- '**/artifacts/**'
- '**/VhdlAst/Antlr/**'
- '**/.agent-logs/**'
- '**/bin/**'
Expand Down
12 changes: 12 additions & 0 deletions .reviewmark.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ reviews:
- docs/design/api-mark-dot-net/xml-doc-reader.md
- docs/verification/api-mark-dot-net/xml-doc-reader.md
- src/ApiMark.DotNet/XmlDocReader.cs
- src/ApiMark.DotNet/CrefLinkContext.cs
- test/ApiMark.DotNet.Tests/XmlDocReaderTests.cs
- id: ApiMark-DotNet-ExternalXmlDocResolver
title: Review that ApiMark DotNet ExternalXmlDocResolver Implementation is Correct
Expand Down Expand Up @@ -337,6 +338,17 @@ reviews:
- docs/verification/api-mark-dot-net/type-link-resolver.md
- src/ApiMark.DotNet/TypeLinkResolver.cs
- test/ApiMark.DotNet.Tests/TypeLinkResolverTests.cs
- id: ApiMark-DotNet-CrefTargetResolver
title: Review that ApiMark DotNet CrefTargetResolver Implementation is Correct
context:
- docs/design/api-mark-dot-net.md
- docs/reqstream/api-mark-dot-net.yaml
paths:
- docs/reqstream/api-mark-dot-net/cref-target-resolver.yaml
- docs/design/api-mark-dot-net/cref-target-resolver.md
- docs/verification/api-mark-dot-net/cref-target-resolver.md
- src/ApiMark.DotNet/CrefTargetResolver.cs
- test/ApiMark.DotNet.Tests/CrefTargetResolverTests.cs
- id: ApiMark-DotNet-TypeNameSimplifier
title: Review that ApiMark DotNet TypeNameSimplifier Implementation is Correct
context:
Expand Down
47 changes: 44 additions & 3 deletions docs/design/api-mark-dot-net.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

ApiMarkDotNet provides C#/.NET language support. It reads a compiled .NET assembly
and its associated XML documentation file, then produces the Markdown output
defined by the Core interfaces. The system contains ten units:
defined by the Core interfaces. The system contains eleven units:

- **DotNetGenerator** — reads the assembly via Mono.Cecil, processes XML doc
comments, applies visibility filtering, builds an inheritance chain map from
Expand All @@ -29,6 +29,11 @@ defined by the Core interfaces. The system contains ten units:
`enforceTier` is null or empty.
- **DotNetAstModel** — immutable data class holding all parsed assembly data
(namespaces, types, XML docs, resolver, options) produced by DotNetGenerator.Parse.
Also holds the cref cross-reference linking indices: `CrefTargets` (a
`CrefTargetResolver`, built eagerly in the constructor since it needs only the
assembly) and `MemberPageIndex`/`EmittedTypeIds` (populated via an internal
post-construction call from `DotNetGenerator.Parse`, after `DotNetEmitter` exists,
since they require the emitter's own visibility rules).
- **DotNetEmitter** — IApiEmitter dispatcher; reads EmitConfig.Format and forwards
the call to DotNetEmitterGradualDisclosure or DotNetEmitterSingleFile. Also provides
shared static helper methods used by both sub-emitters, including
Expand All @@ -44,12 +49,29 @@ defined by the Core interfaces. The system contains ten units:
the containing type's folder.
- **DotNetEmitterSingleFile** — writes all documentation into a single api.md file.
- **TypeLinkResolver** — resolves Mono.Cecil TypeReference instances to Markdown link
text for use in table cells.
text for use in table cells; also resolves already-resolved `<see cref>`/`<seealso
cref>` targets (a `TypeDefinition` or `IMemberDefinition`) to Markdown links via
`LinkifyResolvedType`/`LinkifyResolvedMember`, for full cref cross-reference linking.
- **CrefTargetResolver** — indexes every type and member declared in a single
assembly, keyed by XML-doc identifier, so a raw `cref` attribute string can be
resolved to the Mono.Cecil symbol it names. Applies no visibility filtering of
its own; answers only whether a symbol with that identifier exists in the
assembly, letting `XmlDocReader` distinguish "external/malformed cref" from
"filtered out by visibility" when deciding whether to render a real link.
- **TypeNameSimplifier** — applies a deterministic set of simplification rules to
Mono.Cecil type references to produce idiomatic C# type names in output.
- **XmlDocReader** — reads and indexes a .NET XML documentation file for fast
member-level lookups; resolves `<inheritdoc />` references using the inheritance
chain map supplied by DotNetGenerator.
chain map supplied by DotNetGenerator. Every public rendering method is
internally backed by a new `internal` overload accepting an optional,
trailing `CrefLinkContext?` parameter (default `null`) bundling
`CrefTargetResolver`, `TypeLinkResolver`, the member-page index, and
visibility-emitted delegates — the public overloads themselves are
unchanged, so the public API surface of `XmlDocReader` is unaffected; when
the internal overload is supplied a context and a `<see cref>`/`<seealso
cref>` target resolves to a symbol that will actually be emitted, the
reference renders as a real relative Markdown link instead of its default
code-span-only fallback.
- **ExternalXmlDocResolver** — locates and lazily parses the XML documentation
files of externally referenced assemblies (e.g. NuGet package dependencies),
caching both per-file results and per-(declaring-assembly-hint, member-ID)
Expand All @@ -70,6 +92,9 @@ flowchart TD
DotNetGenerator --> XmlDocReader
DotNetGenerator --> ExternalXmlDocResolver
XmlDocReader --> ExternalXmlDocResolver
XmlDocReader --> CrefTargetResolver
XmlDocReader --> TypeLinkResolver
DotNetAstModel --> CrefTargetResolver
DotNetGenerator --> MonoCecil["Mono.Cecil (OTS)"]
DotNetGenerator --> DocumentationCoverageChecker
DocumentationCoverageChecker --> XmlDocReader
Expand Down Expand Up @@ -234,6 +259,22 @@ N/A - not a safety-classified software item.
falls back to scanning every remaining configured reference path in
order, so a miss is not guaranteed to avoid the full scan.

11. `DotNetAstModel`'s constructor builds `CrefTargets` (a `CrefTargetResolver`)
eagerly from the assembly, since indexing every declared type/member by its
XML-doc identifier requires nothing beyond the `AssemblyDefinition` itself.
After `DotNetGenerator.Parse` constructs the `DotNetEmitter` (which owns the
visibility rules needed to decide which symbols will actually be emitted), it
calls `BuildCrefLinkIndices`, which walks the visible type tree (recursing into
nested types via `DotNetEmitter.GetVisibleNestedTypes`) calling
`DotNetEmitterGradualDisclosure.BuildMemberPageIndex` per type to merge a
complete `{member XML-doc ID -> gradual-disclosure page path}` map, and records
every visible type's own XML-doc ID into a companion `emittedTypeIds` set. Both
results are stored on the model via `DotNetAstModel.SetMemberPageIndex`. At
render time, `XmlDocReader` uses `CrefTargets` to resolve a raw `cref` string to
a symbol, the `emittedTypeIds`/`MemberPageIndex` to decide whether that symbol
will actually be emitted, and `TypeLinkResolver.LinkifyResolvedType`/
`LinkifyResolvedMember` to build the Markdown link text when it will be.

## Design Constraints

- Platform: targets .NET 8 as a class library; no platform-specific code.
Expand Down
121 changes: 121 additions & 0 deletions docs/design/api-mark-dot-net/cref-target-resolver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
## CrefTargetResolver

![CrefTargetResolver Structure](ApiMarkDotNetView.svg)

<!-- All sections below are MANDATORY. If a section does not apply, write
"N/A - {justification}" rather than removing it. -->

### Purpose

CrefTargetResolver resolves a raw XML-doc `cref` attribute string (e.g.
`T:MyNamespace.MyClass` or `M:MyNamespace.MyClass.DoWork(System.Int32)`) to the
Mono.Cecil type or member it names, when that symbol is declared in a single,
specific assembly. It is the first stage of full `<see cref>`/`<seealso cref>`
cross-reference linking: before a reference can be rendered as a real Markdown
link, the engine must know whether a symbol with that exact XML-doc identifier
even exists in the assembly being documented (as opposed to an external
framework type, or a malformed/misspelled cref string).

This resolver intentionally applies no visibility, obsolete, or
exclude-pattern filtering — it answers only "does a symbol with this
XML-doc-ID string exist in this assembly". Deciding whether a resolved symbol
will actually be *emitted* as a page under the active visibility settings is a
separate, caller-supplied concern (see `CrefLinkContext`'s
`IsTypeEmitted`/`IsMemberEmitted` delegates in the `XmlDocReader` design doc).
Keeping these concerns separate lets a caller distinguish "no such symbol"
(external assembly, or a malformed cref) from "the symbol exists but is
filtered out of the generated documentation" — both cases fall back to the
pre-existing code-span-only rendering, but for different reasons.

### Data Model

CrefTargetResolver is immutable after construction; all indexing happens once,
in the constructor.

- *_types* (`Dictionary<string, TypeDefinition>`): Index of type definitions
keyed by their XML-doc type identifier (e.g. `T:Namespace.Type`), built via
`DotNetEmitter.BuildTypeId`.
- *_members* (`Dictionary<string, IMemberDefinition>`): Index of member
definitions keyed by their XML-doc member identifier (e.g.
`M:Namespace.Type.Method`, `P:Namespace.Type.Property`), built via
`DotNetEmitter.BuildMemberId`.

No new identifier-formatting or parsing logic is introduced: both indexes are
built with the exact same builders `XmlDocReader` uses to key its own
documentation index, so a dictionary lookup against either index is
byte-for-byte compatible with the identifier format that appears in a raw
`cref="..."` attribute value for an intra-assembly target.

### Key Methods

**CrefTargetResolver constructor**: Builds both indexes by walking every type
(including nested types, at every depth) and member declared in the supplied
assembly.

- *Parameters*: `AssemblyDefinition assembly` — the assembly whose types and
members to index.
- *Algorithm*: Iterates `assembly.MainModule.GetTypes()` — Mono.Cecil's
flattening enumerator, which already includes nested types at every depth,
so no separate recursive walk is needed. For each type, computes its type ID
via `DotNetEmitter.BuildTypeId` and adds it to `_types` (first occurrence
wins on a duplicate — tolerated defensively the same way `XmlDocReader`
tolerates duplicate member IDs in the XML documentation file, though this
should not occur for well-formed assemblies). For each of the type's
`Methods`/`Properties`/`Fields`/`Events`, computes the member ID via
`DotNetEmitter.BuildMemberId` and adds it to `_members`, skipping any member
for which `BuildMemberId` returns `string.Empty` (a member kind the builder
does not recognize — indexing an empty key would be collision-prone).

**CrefTargetResolver.TryResolveType**: Attempts to resolve a `cref` string to a
type declared in the indexed assembly.

- *Parameters*: `string crefId` — the raw `cref` attribute value, including its
`T:` kind prefix (e.g. `T:MyNamespace.MyClass`); `out TypeDefinition type` —
the resolved type when the method returns `true`.
- *Returns*: `true` when a type with this exact identifier was indexed;
otherwise `false`.

**CrefTargetResolver.TryResolveMember**: Attempts to resolve a `cref` string to
a member declared in the indexed assembly.

- *Parameters*: `string crefId` — the raw `cref` attribute value, including its
`M:`/`P:`/`F:`/`E:` kind prefix (e.g.
`M:MyNamespace.MyClass.DoWork(System.Int32)`); `out IMemberDefinition member`
— the resolved member when the method returns `true`.
- *Returns*: `true` when a member with this exact identifier was indexed;
otherwise `false`.

### Error Handling

CrefTargetResolver does not throw. An unresolvable `crefId` (external
assembly, malformed string, or any other reason) simply yields `false` from
`TryResolveType`/`TryResolveMember`, matching the existing fallback behavior
for crefs that cannot be linked.

### Dependencies

- **Mono.Cecil** — CrefTargetResolver walks `AssemblyDefinition`,
`ModuleDefinition.GetTypes()`, `TypeDefinition`, and `IMemberDefinition`.
- **DotNetEmitter.BuildTypeId** / **DotNetEmitter.BuildMemberId** — reused
as-is to compute index keys; CrefTargetResolver never duplicates any of
their XML-doc-ID formatting logic, so any future change to how those
builders encode an ID (e.g. the indexer parameter-list and generic
positional-notation fixes) is automatically reflected here without any
change to this unit.

### Callers

- **DotNetAstModel** — constructs a `CrefTargetResolver` eagerly in its own
constructor (needs only the assembly, so no deferred post-construction step
is required, unlike `MemberPageIndex`) and exposes it via the `CrefTargets`
property.
- **XmlDocReader** — consults `CrefLinkContext.Targets.TryResolveType`/
`TryResolveMember` from `GetInlineReferenceText` to decide whether a `<see
cref>`/`<seealso cref>` reference can participate in cross-reference
linking at all, before consulting the caller-supplied
`IsTypeEmitted`/`IsMemberEmitted` delegates to decide whether it actually
will be.

### External Interfaces

N/A — this is an internal class with no external interfaces exposed beyond its assembly.
Loading
Loading