Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ jobs:
working-directory: boost-root/libs/corosio/doc
run: node lint/check-no-new-violations.mjs --show-baseline

# The gate: reports any NEW A1/A6/B2/D2/ANCHOR violation, any NEW MrDocs
# The gate: reports any NEW A1/A6/B2/B6/D2/ANCHOR violation, any NEW MrDocs
# reference-surface warning, any NEW C2 sentence-length violation in the hard
# slice, and any NEW C4/C9/C10 wording violation on EITHER surface.
#
Expand All @@ -325,7 +325,7 @@ jobs:
# So `^C2:` is the correct shape THERE, and it deliberately cannot reach
# the `advisory-C2` slice (doc/STYLE_GUIDE.md C2 relaxes the 25-word limit
# for 2.networking-tutorial/, which is essay-style protocol theory).
# * doc_lint is also rule-at-the-head, hence `^(A1|A6|B2|D2|ANCHOR):`. SHOULD
# * doc_lint is also rule-at-the-head, hence `^(A1|A6|B2|B6|D2|ANCHOR):`. SHOULD
# a rule be added there, note SHAPE is deliberately excluded: it is an
# advisory content heuristic, not a defect.
#
Expand All @@ -341,7 +341,7 @@ jobs:
# THE GATE IS SPLIT IN TWO. Everything blocks EXCEPT the MrDocs
# reference-surface check, and that exception is evidence-based.
#
# A1/A6/B2/D2/ANCHOR, C2, and the C4/C9/C10/A7 wording rules are all STRICT.
# A1/A6/B2/B6/D2/ANCHOR, C2, and the C4/C9/C10/A7 wording rules are all STRICT.
# baseline.json is now authored by this job (workflow_dispatch reseed), so a
# strict comparison is CI-against-CI and carries no environment drift. The
# wording rules additionally have an EMPTY gated subset — zero baselined
Expand All @@ -366,7 +366,7 @@ jobs:
working-directory: boost-root/libs/corosio/doc
run: |
node lint/check-no-new-violations.mjs --strict \
--gate 'doc_lint:^(A1|A6|B2|D2|ANCHOR):' \
--gate 'doc_lint:^(A1|A6|B2|B6|D2|ANCHOR):' \
--gate 'sentence_length:^C2:' \
--gate 'vale_adoc:Corosio\.PartHeadings$' \
--gate 'vale_adoc:(Corosio\.SimpleTense|Corosio\.NoFluff|Corosio\.Terminology)$' \
Expand Down
11 changes: 10 additions & 1 deletion doc/STYLE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ The Antora pipeline provides two mechanisms; use them instead of hand-authoring:
suppressions live in `test/doc/doc_warnings.hpp`, included once per file outside every tag; a
warning that fires in one fragment stays in that fragment with a comment saying why. B5 is
enforced by review only — no script checks it.
- **B6. Every `@see` target in a docstring is an `@ref`.** Write `@see @ref delay, @ref wait_traits`,
never `@see delay, wait_traits`. MrDocs treats the text after `@see` as an ordinary paragraph
and does not auto-link names in it (Doxygen does), so a bare name renders as plain text in the
"See Also" section. Nothing warns about it either: `warn-broken-ref` checks only `@ref`
targets. Keep the line to the list of links. Prose after `@see` ("for the non-throwing form")
renders unlinked too, so move it into the description. When a name inside a class resolves
to the wrong symbol (`@ref tcp_server` inside `tcp_server::worker_base` finds the
constructor), qualify it. `doc-lint.mjs` checks every `@see` paragraph in the public
headers, including the lines it wraps onto.

## Part C — Wording (pragmatic Simplified Technical English)

Expand Down Expand Up @@ -250,7 +259,7 @@ Not every rule is machine-checkable. Each rule sits in one of three tiers:

| Tier | Rules |
|---|---|
| **Gate** | A1, A6, A7, B2, B3, C2, C4, C9, C10, D2 |
| **Gate** | A1, A6, A7, B2, B3, B6, C2, C4, C9, C10, D2 |
| **Warning** | A2, B1, C1, C3, C5, C6, D4, D5, E1 |
| **Review** | A3, A4, A5, B4, B5, C7, C8, C11, D1, D3, E2, E3, E4 |

Expand Down
6 changes: 3 additions & 3 deletions doc/lint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ rationale. What is Corosio-specific is recorded here.

| Script | What it checks |
|---|---|
| `doc-lint.mjs` | Structural AsciiDoc/nav rules (A1, A6, B2, ANCHOR, SHAPE, D2). JSON on stdout. |
| `doc-lint.mjs` | Structural AsciiDoc/nav rules (A1, A6, B2, ANCHOR, SHAPE, D2), plus B6 (`@see` without `@ref`) over the public headers. JSON on stdout. |
| `extract-docstrings.mjs` | Extracts header docstrings into `.docstrings/*.adoc` so Vale can lint them. Doxygen targets (`@ref`/`@p`/`@c`/`@see`) are re-emitted as code spans, and `@par !example` directives are dropped — see below. |
| `sentence-length.mjs` | **The authority for C2** (no sentence over 25 words), over both corpora. |
| `check-include-tags.mjs` | Every `include::example$…[tag=…]` resolves to a live tag in a compiled source. |
Expand Down Expand Up @@ -213,7 +213,7 @@ The gate runs as two steps:

| Step | Checks | Posture |
|---|---|---|
| `Lint: gate (BLOCKING)` | `doc_lint` (A1/A6/B2/D2/ANCHOR), `sentence_length` (C2), and the C4/C9/C10/A7 wording rules on both corpora | **`--strict`** |
| `Lint: gate (BLOCKING)` | `doc_lint` (A1/A6/B2/B6/D2/ANCHOR), `sentence_length` (C2), and the C4/C9/C10/A7 wording rules on both corpora | **`--strict`** |
| `Lint: gate (reference surface, reporting)` | `mrdocs_warnings` | reports, does not fail |

`baseline.json` is now authored by the Documentation job itself, so a strict comparison is
Expand All @@ -237,7 +237,7 @@ pin that skipped this check on the first reseed. Promote it only alongside a pin
>
> ```sh
> node lint/check-no-new-violations.mjs --strict \
> --gate 'doc_lint:^(A1|A6|B2|D2|ANCHOR):' --gate 'sentence_length:^C2:' \
> --gate 'doc_lint:^(A1|A6|B2|B6|D2|ANCHOR):' --gate 'sentence_length:^C2:' \
> --gate 'vale_adoc:Corosio\.PartHeadings$' \
> --gate 'vale_adoc:(Corosio\.SimpleTense|Corosio\.NoFluff|Corosio\.Terminology)$' \
> --gate 'vale_docstrings:(Corosio\.SimpleTense|Corosio\.NoFluff|Corosio\.Terminology)$'
Expand Down
8 changes: 6 additions & 2 deletions doc/lint/baseline.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,13 @@ function docLintFingerprints() {
for (const it of items) {
const fp = occurrenceKey(seen, `${check}:${it.file}`, it.message);
fingerprints.push(fp);
// doc-lint reports paths relative to the pages tree, not to doc/.
// doc-lint reports paths relative to the pages tree, not to doc/ --
// except B6, which scans headers and reports repo-relative paths.
if (wantDetails) {
details[fp] = { ...resolveSource(`modules/ROOT/pages/${it.file}`, it.line ?? null, null),
const src = it.file.startsWith('include/')
? { file: it.file, line: it.line ?? null }
: resolveSource(`modules/ROOT/pages/${it.file}`, it.line ?? null, null);
details[fp] = { ...src,
rule: check, message: it.message, excerpt: null };
}
}
Expand Down
70 changes: 69 additions & 1 deletion doc/lint/doc-lint.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@
// block-depth skip rather than a whole-file regex. Bite-test per
// style-guide F4: put `` `[[nodiscard]]` `` in a page and confirm
// ANCHOR fires.
// B6 — every `@see` line in a public header names its targets with
// `@ref`. MrDocs does not auto-link names in `@see` (Doxygen does),
// so `@see delay` renders as plain text in "See Also" and nothing
// warns: `warn-broken-ref` only checks `@ref` targets. The line must
// be `@see @ref a, @ref b` and nothing else; prose after `@see` also
// renders unlinked, so it fires too. The lines a `@see` paragraph
// wraps onto are held to the same rule. Scans include/boost/corosio
// minus detail/ (the same directories as extract-docstrings.mjs).
// This is the one rule here that reads headers, not pages, so its
// `file` is repo-relative (`include/...`). Bite-test per style-guide
// F4: write `@see delay` in a header and confirm B6 fires.
// D2 — every page in a CONCEPT_DIRS chapter (or quick-start.adoc) has
// >=1 include::example$. D2's "concept page" is a pedagogical
// category, not a Diátaxis mode — deliberately independent of
Expand All @@ -79,6 +90,8 @@ const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..')
// pages-tree override is for (exercising B2 in isolation).
const PAGES_DIR = process.argv[2] ? path.resolve(process.argv[2]) : path.join(ROOT, 'modules/ROOT/pages');
const NAV_FILE = path.join(ROOT, 'modules/ROOT/nav.adoc');
// B6's header tree; a second CLI arg overrides it for the self-test.
const HEADERS_DIR = process.argv[3] ? path.resolve(process.argv[3]) : path.join(ROOT, '../include/boost/corosio');

// The four Diátaxis modes doc/STYLE_GUIDE.md Part A defines. A1 rejects
// anything else, including the legacy `concept` value (never a Diátaxis
Expand Down Expand Up @@ -264,7 +277,55 @@ function scanAnchors(lines) {
return out;
}

const findings = { A1: [], A6: [], B2: [], SHAPE: [], ANCHOR: [], D2: [] };
function walkHeaders(dir) {
let out = [];
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, ent.name);
if (ent.isDirectory()) { if (ent.name !== 'detail') out = out.concat(walkHeaders(p)); }
else if (ent.name.endsWith('.hpp')) out.push(p);
}
return out;
}

// A `@see` paragraph holding anything other than `@ref name` items joined by
// commas. The paragraph runs past the `@see` line, as Doxygen's does, until a
// blank line, `*/`, a line opening with any command but `@ref`, or (for a
// `///` comment) a line that is not `///`. Each offending line is reported.
const SEE_LINE = /^(\s*)(\/\/\/)?\s*[@\\]see\b(.*)$/;
const SEE_REF = /[@\\]ref\s+[\w:~<>]+(?:\([^)]*\))?/g;
const NOT_REF_COMMAND = /^[@\\](?!ref\b)\w/;
function scanSeeLines(lines) {
const out = [];
const check = (text, i) => {
const body = text.split('*/')[0];
if (body.replace(SEE_REF, '').replace(/[\s,]/g, '') !== '')
out.push({ line: i + 1, message: `@see entry without @ref renders unlinked: ${body.trim()}` });
return !text.includes('*/');
};
let open = false; // inside a @see paragraph
let slashes = false; // ...that lives in a `///` run
lines.forEach((l, i) => {
const m = SEE_LINE.exec(l);
if (m) {
slashes = m[2] !== undefined;
open = check(m[3], i);
return;
}
if (!open) return;
let text = l;
if (slashes) {
const s = /^\s*\/\/\/(.*)$/.exec(l);
if (!s) { open = false; return; }
text = s[1];
}
const t = text.trim();
if (t === '' || t.startsWith('*/') || NOT_REF_COMMAND.test(t)) { open = false; return; }
open = check(text, i);
});
return out;
}

const findings = { A1: [], A6: [], B2: [], B6: [], SHAPE: [], ANCHOR: [], D2: [] };
const files = walk(PAGES_DIR);

for (const file of files) {
Expand Down Expand Up @@ -294,6 +355,13 @@ for (const file of files) {
}
}

for (const file of walkHeaders(HEADERS_DIR)) {
const rel = path.join('include/boost/corosio', path.relative(HEADERS_DIR, file));
for (const s of scanSeeLines(fs.readFileSync(file, 'utf8').split('\n'))) {
findings.B6.push({ file: rel, line: s.line, message: s.message });
}
}

const navText = fs.readFileSync(NAV_FILE, 'utf8');
const topEntries = navText.split('\n').filter(l => /^\* xref:/.test(l));
const qsIndex = topEntries.findIndex(l => /quick-start/.test(l));
Expand Down
48 changes: 47 additions & 1 deletion doc/lint/selftest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,44 @@ try {
'',
].join('\n'));

const d = spawnSync('node', [path.join(SCRIPT_DIR, 'doc-lint.mjs'), DOCLINT_TMP],
// B6 header fixture, passed to doc-lint.mjs as its header tree. Line
// numbers are asserted below: 3, 4 and 5 must fire; 6 and 7 must not.
// detail/ is outside B6's scope, so its bare `@see` must not fire either.
write('hdr/see.hpp', [
'/** Brief.', // 1
'', // 2
' @see delay', // 3 <- must fire
' @see @ref delay, wait_traits', // 4 <- must fire (one name unlinked)
' @see @ref add, for the non-throwing form.', // 5 <- must fire (prose)
' @see @ref delay, @ref io_context_options::locking', // 6 <- must NOT fire
' @see @ref accept(tcp_socket&)', // 7 <- must NOT fire
'*/',
'',
].join('\n'));
write('hdr/detail/hidden.hpp', '/** Brief.\n @see delay\n*/\n');
// B6 continuation fixture: a `@see` paragraph wraps onto following lines
// until a blank line, `*/`, a non-@ref command, or the end of a `///` run.
// 3 and 12 must fire; nothing else may.
write('hdr/see-wrap.hpp', [
'/** Brief.', // 1
' @see @ref a,', // 2
' b', // 3 <- must fire (bare name, wrapped)
' @see @ref a,', // 4
' @ref b', // 5 <- must NOT fire (wrapped @ref)
' @see @ref a', // 6
'', // 7 blank line ends the paragraph
' Plain prose after the list.', // 8 <- must NOT fire
' @see @ref a', // 9
' @par Note', // 10 a command ends the paragraph
' More prose.', // 11 <- must NOT fire
' @see @ref a, b */', // 12 <- must fire (bare name before */)
'/// @see @ref a,', // 13
'/// @ref b', // 14 <- must NOT fire
'int prose_outside_the_comment;', // 15 <- must NOT fire (/// run ended)
'',
].join('\n'));

const d = spawnSync('node', [path.join(SCRIPT_DIR, 'doc-lint.mjs'), DOCLINT_TMP, path.join(DOCLINT_TMP, 'hdr')],
{ encoding: 'utf8', cwd: DOC_DIR, maxBuffer: 16 * 1024 * 1024 });
check('doc-lint.mjs exits 0 against the B2 fixture tree', d.status === 0,
`exited ${d.status}: ${(d.stderr || '').trim().slice(-200)}`);
Expand Down Expand Up @@ -498,6 +535,15 @@ try {
`attr-anchor.adoc:10 is the passthrough fix and must not fire; got: ${JSON.stringify(anchorRule)}`);
check('ANCHOR finds exactly the one planted prose violation',
anchorRule.length === 1, `expected exactly 1 ANCHOR finding, got: ${JSON.stringify(anchorRule)}`);

// B6: MrDocs renders a bare `@see` name as plain text and warns about
// nothing, so a regression here would be silent on the site too.
const seeLines = dOut.findings.B6.map((f) => `${f.file}:${f.line}`).sort();
check('B6 flags exactly the bare-name, mixed and prose @see lines, nothing in detail/',
JSON.stringify(seeLines) === JSON.stringify(
['include/boost/corosio/see-wrap.hpp:12', 'include/boost/corosio/see-wrap.hpp:3',
'include/boost/corosio/see.hpp:3', 'include/boost/corosio/see.hpp:4', 'include/boost/corosio/see.hpp:5']),
`got: ${JSON.stringify(seeLines)}`);
}
} finally {
fs.rmSync(DOCLINT_TMP, { recursive: true, force: true });
Expand Down
4 changes: 2 additions & 2 deletions include/boost/corosio/delay.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ emplace_delay_timer(std::optional<timer>& t, capy::execution_context& ctx)
auto-enabled at `concurrency_hint` == 1. Cross-thread cancellation
requires a multi-threaded-capable context.

@see delay
@see @ref delay
*/
class delay_awaitable
{
Expand Down Expand Up @@ -217,7 +217,7 @@ class delay_awaitable
resumes inline with `error::canceled`; stop while suspended
cancels the pending wait, including between re-arms.

@see delay, wait_traits
@see @ref delay, @ref wait_traits
*/
template<class Clock, class Traits>
class clock_delay_awaitable
Expand Down
4 changes: 3 additions & 1 deletion include/boost/corosio/endpoint.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,13 @@ class endpoint
@li IPv6 without port: `::1` or `2001:db8::1`
@li IPv6 with port (bracketed): `[::1]:8080`

@ref make_endpoint is the non-throwing form.

@param s The string to parse.

@throws std::system_error on parse failure.

@see make_endpoint for the non-throwing form.
@see @ref make_endpoint
*/
explicit endpoint(std::string_view s);

Expand Down
3 changes: 2 additions & 1 deletion include/boost/corosio/io/io_object.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
// Copyright (c) 2026 Steve Gerbino
// Copyright (c) 2026 Michael Vandeberg
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand Down Expand Up @@ -43,7 +44,7 @@ namespace boost::corosio {
@note Intended as a protected base class. The handle member
`h_` is accessible to derived classes.

@see io_stream, tcp_socket, tcp_acceptor
@see @ref io_stream, @ref tcp_socket, @ref tcp_acceptor
*/
class BOOST_COROSIO_DECL io_object
{
Expand Down
5 changes: 3 additions & 2 deletions include/boost/corosio/io/io_read_stream.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright (c) 2026 Steve Gerbino
// Copyright (c) 2026 Michael Vandeberg
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand Down Expand Up @@ -41,7 +42,7 @@ namespace boost::corosio {
Distinct objects: Safe.
Shared objects: Unsafe.

@see io_write_stream, io_stream, io_object
@see @ref io_write_stream, @ref io_stream, @ref io_object
*/
class BOOST_COROSIO_DECL io_read_stream : virtual public io_object
{
Expand Down Expand Up @@ -123,7 +124,7 @@ class BOOST_COROSIO_DECL io_read_stream : virtual public io_object

@return An awaitable yielding `(error_code, std::size_t)`.

@see io_stream::write_some
@see @ref io_stream::write_some
*/
template<capy::MutableBufferSequence MB>
[[nodiscard]] auto read_some(MB const& buffers)
Expand Down
3 changes: 2 additions & 1 deletion include/boost/corosio/io/io_signal_set.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright (c) 2026 Steve Gerbino
// Copyright (c) 2026 Michael Vandeberg
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand Down Expand Up @@ -36,7 +37,7 @@ namespace boost::corosio {
Distinct objects: Safe.
Shared objects: Unsafe.

@see signal_set, io_object
@see @ref signal_set, @ref io_object
*/
class BOOST_COROSIO_DECL io_signal_set : public io_object
{
Expand Down
2 changes: 1 addition & 1 deletion include/boost/corosio/io/io_stream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ namespace boost::corosio {
@par Example
@par !example io_stream

@see io_read_stream, io_write_stream, tcp_socket
@see @ref io_read_stream, @ref io_write_stream, @ref tcp_socket
*/
class BOOST_COROSIO_DECL io_stream
: public io_read_stream
Expand Down
5 changes: 3 additions & 2 deletions include/boost/corosio/io/io_write_stream.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright (c) 2026 Steve Gerbino
// Copyright (c) 2026 Michael Vandeberg
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand Down Expand Up @@ -41,7 +42,7 @@ namespace boost::corosio {
Distinct objects: Safe.
Shared objects: Unsafe.

@see io_read_stream, io_stream, io_object
@see @ref io_read_stream, @ref io_stream, @ref io_object
*/
class BOOST_COROSIO_DECL io_write_stream : virtual public io_object
{
Expand Down Expand Up @@ -122,7 +123,7 @@ class BOOST_COROSIO_DECL io_write_stream : virtual public io_object

@return An awaitable yielding `(error_code, std::size_t)`.

@see io_stream::read_some
@see @ref io_stream::read_some
*/
template<capy::ConstBufferSequence CB>
[[nodiscard]] auto write_some(CB const& buffers)
Expand Down
Loading
Loading