Skip to content

Commit c67c080

Browse files
JosmithrjumyhreJustin Myhres
authored
feat: Refactor api-markdown-documenter to be written in terms of ASTs and functional transformations (#14201)
## BREAKING CHANGE This is a complete overhaul of the library, greatly changing its API surface. The general consumption patterns remain mostly the same, but the largely operate on our new AST types. ## New Architecture The old architecture was based on (and greatly re-used) the base API-Documenter library. This PR moves mostly away from this. Rather than operating on a flow that resembled something like: ``` ApiItem (w/ DocNode) -> DocNode -> stateful render ``` We now transform in input ApiItem tree (generated by API-Extractor) into an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree)-based Documentation Domain. From there, we provide a more-functional rendering library for rendering that domain as Markdown. So something more like: ``` ApiItem -> Documentation Domain -> functional render ``` ### Benefits - Functional renderer is much easier to understand, easy to unit test (see the `RenderFoo` test modules), and made it easy to clean up a number of rendering inconsistencies hidden by the API-Documenter-based `MarkdownEmitter` renderer. - Intermediary Documentation Domain is easy to understand, `AST` compatible, and requires no auxiliary state to process (old DocNode domain required a tsdoc context to be able to walk, and also had to reach back into the ApiItem domain to resolve links, etc.). - Uses existing [unist](https://github.com/syntax-tree/unist) library for AST representation (with domain-specific type-safety). ## Notes for reviewers The actual code diff is probably not all that useful. Some of the configuration types will look roughly the same, but otherwise the majority of the code has been re-written. I would recommend referring to the README's architectural overview / changes to the prescribed workflow. Additionally, you can view the diffs in our rendering snapshot tests to confirm that behavioral changes are relatively small, and in my opinion, are all improvements. --------- Co-authored-by: Justin Myhres <[email protected]> Co-authored-by: Justin Myhres <[email protected]>
1 parent 1350ff5 commit c67c080

File tree

209 files changed

+10381
-4895
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

209 files changed

+10381
-4895
lines changed

.vscode/settings.json

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"pseudorandomly",
3434
"Tinylicious",
3535
"tombstoned",
36+
"TSDoc",
3637
"unaugmented"
3738
],
3839

tools/api-markdown-documenter/.eslintrc.js

+21-17
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@
44
*/
55

66
module.exports = {
7-
extends: [require.resolve("@fluidframework/eslint-config-fluid/strict")],
7+
extends: [require.resolve("@fluidframework/eslint-config-fluid/strict"), "prettier"],
88
parserOptions: {
99
project: ["./tsconfig.json"],
1010
},
1111
rules: {
1212
/**
1313
* This package utilizes internals of api-documenter that are not exported by the package root.
1414
*
15-
* TODO: file issue to expose node types, etc. in main package so we don't need to do this, and have better
16-
* guarantees about support.
15+
* TODO: remove once we have completely migrated off of this library.
1716
*/
1817
"import/no-internal-modules": [
1918
"error",
@@ -24,23 +23,28 @@ module.exports = {
2423

2524
"unicorn/prefer-module": "off",
2625
"unicorn/prefer-negative-index": "off",
26+
"unicorn/no-array-push-push": "off",
2727

2828
// This package is exclusively used in a Node.js context
2929
"import/no-nodejs-modules": "off",
30+
},
31+
overrides: [
32+
{
33+
// Overrides for test files
34+
files: ["src/**/test/**"],
35+
plugins: ["chai-expect", "chai-friendly"],
36+
extends: ["plugin:chai-expect/recommended", "plugin:chai-friendly/recommended"],
37+
rules: {
38+
"import/no-extraneous-dependencies": [
39+
"error",
40+
{
41+
devDependencies: true,
42+
},
43+
],
3044

31-
// TODO: remove once dependency on base config has been updated.
32-
"@typescript-eslint/explicit-member-accessibility": [
33-
"error",
34-
{
35-
accessibility: "explicit",
36-
overrides: {
37-
accessors: "explicit",
38-
constructors: "explicit",
39-
methods: "explicit",
40-
properties: "explicit",
41-
parameterProperties: "explicit",
42-
},
45+
// Handled by chai-friendly instead.
46+
"@typescript-eslint/no-unused-expressions": "off",
4347
},
44-
],
45-
},
48+
},
49+
],
4650
};

tools/api-markdown-documenter/.mocharc.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
"use strict";
77

8-
const config = {
9-
require: ["@fluidframework/mocha-test-setup"],
10-
};
8+
const getFluidTestMochaConfig = require("@fluidframework/mocha-test-setup/mocharc-common");
119

10+
const packageDir = __dirname;
11+
const config = getFluidTestMochaConfig(packageDir);
1212
module.exports = config;
+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
nyc
22
*.log
33
**/*.tsbuildinfo
4-
src/test
5-
dist/test
4+
src/**/test
5+
dist/**/test
66
**/_api-extractor-temp/**
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
# Generated by npm / Lerna
1+
# Generated by npm
22
package-lock.json
33

44
# Build output
55
dist/*
66

77
# Test output
8-
src/test/snapshots/*
8+
src/**/test/snapshots/*
99

1010
# Dependencies
1111
node_modules/*
@@ -14,8 +14,8 @@ node_modules/*
1414
_api-extractor-temp/*
1515
api-report/*
1616

17-
# Generated documentation
18-
generated-api-docs/*
19-
2017
# Generated type-tests
2118
**/*.generated.ts
19+
20+
# Test coverage reports
21+
nyc/*

tools/api-markdown-documenter/README.md

+102-16
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
Contains a programmatic API for generating [Markdown][] documentation from an API report generated by [API-Extractor][].
44

5+
> Note: this library is specifically configured to target [GitHub flavored Markdown][].
6+
>
7+
> Compatibility with other Markdown flavors is not guaranteed, though you can always write your own renderer implementation against our [Documentation Domain](#documentation-domain)!
8+
59
It is similar to [API-Documenter][] and is heavily based upon it and uses it under the hood, but is designed to be more extensible and can be used programmatically.
610

711
**Note**: this library does not currently offer a Command Line Interface (CLI).
@@ -27,7 +31,7 @@ npm i @fluid-tools/api-markdown-documenter -D
2731

2832
## Usage
2933

30-
### Quickstart
34+
### Quick Start
3135

3236
This library is intended to be highly customizable.
3337
That said, it is also intended to be easy to use out of the box.
@@ -38,20 +42,20 @@ Are you already generating `.api.json` report files as a part of your build?
3842
If yes, create a file called `api-markdown-documenter.js` and paste the following code:
3943

4044
```javascript
41-
const { readModel, renderFiles } = require("@fluid-tools/api-markdown-documenter");
45+
const { loadModel, renderApiModelAsMarkdown } = require("@fluid-tools/api-markdown-documenter");
4246

4347
const inputDir = "<PATH-TO-YOUR-DIRECTORY-CONTAINING-API-REPORTS>";
4448
const outputDir = "<YOUR-OUTPUT-DIRECTORY-PATH>";
4549

4650
// Create the API Model from our API reports
47-
const apiModel = await readModel(inputDir);
51+
const apiModel = await loadModel(inputDir);
4852

4953
const config = {
5054
apiModel,
5155
uriRoot: ".",
5256
};
5357

54-
await renderFiles(config, outputDir);
58+
await renderApiModelAsMarkdown(config, outputDir);
5559
```
5660

5761
The above script can be invoked as an `npm` script by adding the following to your `package.json`'s `scripts` property:
@@ -67,17 +71,22 @@ For more advanced usage options, see the following sections.
6771

6872
This package contains 2 primary, programmatic entry-points for generating documentation:
6973

70-
#### renderDocuments
74+
#### transformApiModel
7175

72-
The `renderDocuments` function accepts an [ApiModel][] representing the package(s) of a repository, and generates a sequence of "Document" objects representing the resulting documentation based on the other provided configuration options.
76+
The `transformApiModel` function accepts an [ApiModel][] representing the package(s) of a repository, and generates a sequence of "Document" [Abstract Syntax Tree][] objects representing the resulting documentation based on the other provided configuration options.
7377
These objects include information about the page item, its documentation contents, and the intended output file path the document file should be rendered to, based on provided policy options.
7478

79+
- These trees are backed by [unist][]'s AST model.
80+
7581
The input `ApiModel` here will generally be the output of [API-Extractor][].
7682

77-
#### renderFiles
83+
See [Documentation Domain](#documentation-domain) below for more details on the output format.
84+
85+
#### renderApiModelAsMarkdown
86+
87+
The `renderApiModelAsMarkdown` function operates like [transformApiModel](#createdocuments), but writes the resulting documents to disk as files based on the provided configuration options.
7888

79-
The `renderFiles` function operates like [renderDocuments](#renderdocuments), but writes the resulting documents to disk as files based on the provided configuration options.
80-
This function also accepts a `MarkdownEmitter` object that does the conversion from `DocNode` trees to a Markdown stream that will ultimately be rendered to file.
89+
This function accepts overrides for all of its default `Markdown`-rendering behaviors, so feel free to customize as you see fit!
8190

8291
### Loading the API Model
8392

@@ -87,10 +96,10 @@ To generate an API model from `.api.json` files generated by `API-Extractor`, se
8796

8897
### Emitting Markdown Content
8998

90-
If you are using the [renderDocuments](#renderdocuments) option described above, one option for emitting Markdown string content is to use the `emitMarkdown` function.
99+
If you are using the [transformApiModel](#createdocuments) option described above, one option for emitting Markdown string content is to use the `emitMarkdown` function.
91100
It accepts a `MarkdownDocument` object as input, and outputs a string representing the final Markdown content.
92101

93-
Note: you can also accomplish this by just using [renderFiles](#renderfiles) if you are using default configuration / emitter options.
102+
Note: you can also accomplish this by just using [renderApiModelAsMarkdown](#renderfiles) if you are using default configuration / emitter options.
94103

95104
### Configuration Options
96105

@@ -107,25 +116,98 @@ The vast majority of these options have default values that have been crafted to
107116
- `uriRoot`: This is the root URI under which the generated documentation will be published.
108117
It will be used when generating links for API content docs.
109118

119+
## Architectural Overview
120+
121+
As noted above, this library is intended to be consumed programmatically.
122+
While we may at some point add some command line interfaces for common paths, the primary goal of this library's architecture is to be flexible and extensible.
123+
To that end, we have broken its logic into a few isolable steps, each offering its own extensibility offerings.
124+
125+
End to end, this library can be viewed as a psuedo-functional transformation pipeline mapping from an [APIModel][] generated by `API-Extractor` to one or more `Markdown`-formatted document files.
126+
But this is broken into the following internal sequences:
127+
128+
```mermaid
129+
graph LR
130+
A[ApiModel]
131+
B[Documentation AST]
132+
C[Markdown]
133+
134+
A -- transformApiModel --> B
135+
B -- renderDocumentAsMarkdown --> C
136+
A -.- renderApiModelAsMarkdown -.-> C
137+
```
138+
139+
For more details on the interior `Documentation AST` ([Abstract Syntax Tree][]) domain, see [Documentation Domain](#documentation-domain) below.
140+
141+
### API-Extractor
142+
143+
The input to our system is the [ApiModel][] generated by [API-Extractor][].
144+
145+
This library offers the `loadModel` function as an option for loading your model from the generated `.api.json` metadata files `API-Extractor` generates be default.
146+
147+
To transform this input to our [Documentation Domain][], you may call the `transformApiModel` function.
148+
This function walks the input `ApiModel` tree, transforming each `ApiItem` under it according to its configured series of transformation policies.
149+
These policies are entirely configurable, though the system offers defaults out of the box.
150+
151+
### Documentation Domain
152+
153+
This library defines its own "Documentation Domain" using [Abstract Syntax Tree][] (AST) syntax backed by [unist][].
154+
This is used as an intermediate domain between the `API-Extractor`-based input, and the `Markdown` rendering output.
155+
This domain was crafted to support [TSDoc][]'s capabilities, and to represent something resembling lowest common denominator between `Markdown` and `HTML` syntax.
156+
157+
As this domain is implemented as an `AST`, it is highly customizable.
158+
If you are interested in creating your own intermediate domain concepts, feel free to implement them.
159+
So long as you provide a corresponding [renderer policy](#markdown-renderer), the system will gladly accept them!
160+
161+
### Markdown Renderer
162+
163+
The final component of this library's transformation pipeline is its `Markdown` renderer.
164+
165+
> Note: by default, this renderer is configured to generate [GitHub flavored Markdown][].
166+
>
167+
> Compatibility with other Markdown flavors is not guaranteed.
168+
169+
As with the other logic in this library, the renderer is highly configurable.
170+
It will accept any `Documentation Domain` tree as input, and transform each node according to its configured render policies.
171+
172+
If you would like to add rendering support for a custom `Documentation Domain` node type, simply provide a renderer policy associated with that node's `type` value.
173+
174+
If you would like to change any or all of this library's default rendering policies, you may simply override the default policies for the desired `type`s.
175+
110176
## Upcoming Work
111177

178+
- Simplify input configurations
179+
- Separate configs for each pipeline segment
180+
- Clean up terminology ("policies" in particular is too vague - we should remove this)
181+
- Add extensibility options for `DocNode` transformations
182+
- If a consumer has a custom tsdoc config associated with their API-Extractor setup, this will be needed.
183+
184+
### Known Bugs
185+
186+
- Example titles are not respected. See TSDoc [@example spec](https://tsdoc.org/pages/tags/example/) - text on the same line as the tag should be treated as the example title.
187+
- Types that extend or implement types with generic parameters result in signatures rendered with missing closing `>`s.
188+
112189
### Documentation Improvements
113190

114191
- Intro sandbox (api report)
192+
- Extensibility examples (maybe use the AlertNode concept, once we move it out of the library and into the FluidFramework website build)
115193

116-
### Styling improvements
194+
### Styling Improvements
117195

118-
- Remove leading blank line in documents
119-
- Excessive blank lines in Signature sections
120196
- Fix links to the same file (only need heading component, not file path)
121197
- This will require plumbing down a context document item, so we can easily determine if the document to which the link is being generated is the same as the document being linked to.
198+
- Config options for parsing TSDoc block comment contents as Markdown (and don't escape the contents)?
199+
- Add support for Table Cell alignment
200+
201+
### Performance Improvements
202+
203+
- Rather than repeatedly walking up a given `ApiItem`'s hierarchy when evaluating paths, links, etc., we could pass down transformation context object containing a running record of the item's hierarchy as we walk the tree.
122204

123205
## Longer-term work
124206

125-
- Replace DocNode output / MarkdownEmitter with Markdown AST trees and simple interface for rendering those trees to a stream
126207
- Support placing documents _within_ their own hierarchy (support for the "index" model used by systems like DocFX)
127208
- Pre-canned policies (flat, index, adjacency)
128-
- Handle multiple package entrypoints
209+
- Handle multiple package entry-points
210+
- Add separate HTML transformation path, for consumers that want to go straight to HTML
129211

130212
<!-- AUTO-GENERATED-CONTENT:START (README_CONTRIBUTION_GUIDELINES_SECTION:includeHeading=TRUE) -->
131213

@@ -192,6 +274,10 @@ Use of Microsoft trademarks or logos in modified versions of this project must n
192274
<!-- Links -->
193275

194276
[markdown]: https://en.wikipedia.org/wiki/Markdown
277+
[abstract syntax tree]: https://en.wikipedia.org/wiki/Abstract_syntax_tree
195278
[api-extractor]: https://api-extractor.com
196279
[api-documenter]: https://github.com/microsoft/rushstack/tree/main/apps/api-documenter
197280
[apimodel]: https://api.rushstack.io/pages/api-extractor-model.apimodel
281+
[github flavored markdown]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/about-writing-and-formatting-on-github
282+
[tsdoc]: https://tsdoc.org/
283+
[unist]: https://github.com/syntax-tree/unist

tools/api-markdown-documenter/api-extractor.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
3-
"extends": "@fluidframework/build-common/api-extractor-common-strict.json",
3+
"extends": "@fluidframework/build-common/api-extractor-common-report.json",
44
"apiReport": {
55
"enabled": true,
66
"reportFolder": "<projectFolder>/api-report/"

0 commit comments

Comments
 (0)