@@ -553,6 +553,112 @@ describe('renderFileDescription — #6229: a bare path keeps its `../` prefix in
553553 } ) ;
554554} ) ;
555555
556+ /**
557+ * #6420 — a path an author wrote in PARENTHESES is prose, not a link.
558+ *
559+ * The rewriter carried a lookaround pair, `(?<!\()` … `(?!\))`, from before the
560+ * tokenizer existed. Its job was "do not touch a path that is already a link's
561+ * destination", because `](route)` puts that path between exactly those two
562+ * characters. It never could state that (lookaround cannot say "not nested
563+ * inside a link" — the module comment is explicit), and since #6136 it has had
564+ * nothing left to state: a formed link is a `link` run and this step is only
565+ * ever shown `text` runs. What the pair still did was refuse every path an
566+ * author had put in ordinary parentheses, which is neither a link nor code —
567+ * so those paths rendered as bare text on three published pages:
568+ *
569+ * automation/etl.mdx:16 `- **Enterprise Connector** (integration/connector.zod.ts) - …`
570+ * integration/connector.mdx:17 `- **ETL Pipeline** (automation/etl.zod.ts) - …`
571+ * shared/mapping.mdx:16-17 `- Integration connectors (integration/connector.zod.ts)` (+ external-lookup)
572+ *
573+ * MEASURED (reverse verification), the ordinary direction: putting either
574+ * guard back turns the four parenthesised cases below red — the two-sided pair
575+ * and each half on its own, since a path in `(…)` trips both — while the
576+ * unparenthesised cases and the whole #6229 block above stay green. Restoring
577+ * them also leaves `keeps a formed link's destination out of reach` green,
578+ * which is the point of that case: it is the tokenizer that holds the
579+ * invariant now, so removing the guards cannot re-open #6136.
580+ *
581+ * Corpus-wide the widening is exactly those four positions and nothing else
582+ * (`gen:docs` on the fixed generator: 231 files, 3 changed, 4 lines), and all
583+ * three routes it newly emits resolve to a real page.
584+ */
585+ describe ( 'renderFileDescription — #6420: a bare path in parentheses still links' , ( ) => {
586+ const ctx = {
587+ // Mirrors `build-docs.ts`'s `sourcePathToDocsRoute`, restricted to the two
588+ // categories these cases name, so an unroutable path is genuinely
589+ // unroutable rather than a stand-in that resolves everything.
590+ sourcePathToDocsRoute : ( t : string ) => {
591+ const m = / (?: ^ | \/ ) ( i n t e g r a t i o n | a u t o m a t i o n ) \/ ( [ \w - ] + ) \. z o d \. t s $ / . exec ( t ) ;
592+ return m ? `/docs/references/${ m [ 1 ] } /${ m [ 2 ] } ` : null ;
593+ } ,
594+ } ;
595+
596+ const describedBy = ( line : string ) =>
597+ renderFileDescription ( [ '/**' , ` * ${ line } ` , ' */' , '' , "import { z } from 'zod';" , '' ] . join ( '\n' ) , ctx ) ;
598+
599+ it ( 'links a parenthesised path — the published `automation/etl` line' , ( ) => {
600+ // `packages/spec/src/automation/etl.zod.ts` verbatim — the exact input
601+ // behind `content/docs/references/automation/etl.mdx:16`.
602+ expect (
603+ describedBy ( '- **Enterprise Connector** (integration/connector.zod.ts) - System integrators' ) ,
604+ ) . toBe (
605+ '- **Enterprise Connector** ([integration/connector.zod.ts](/docs/references/integration/connector)) - System integrators' ,
606+ ) ;
607+ } ) ;
608+
609+ it ( 'links a parenthesised path that closes the line — the `shared/mapping` shape' , ( ) => {
610+ // `content/docs/references/shared/mapping.mdx:16`. Distinct from the case
611+ // above on purpose: there the `)` is followed by more prose, here it ends
612+ // the line, and the trailing guard `(?!\))` refused both.
613+ expect ( describedBy ( '- Integration connectors (integration/connector.zod.ts)' ) ) . toBe (
614+ '- Integration connectors ([integration/connector.zod.ts](/docs/references/integration/connector))' ,
615+ ) ;
616+ } ) ;
617+
618+ it ( 'keeps a `../` prefix inside the link when the path is parenthesised' , ( ) => {
619+ // #6229 and this fix compose: the prefix belongs inside the link, and the
620+ // parentheses stay outside it. Neither fix implies the other.
621+ expect ( describedBy ( 'The layer (../integration/connector.zod.ts) is the widest.' ) ) . toBe (
622+ 'The layer ([../integration/connector.zod.ts](/docs/references/integration/connector)) is the widest.' ,
623+ ) ;
624+ } ) ;
625+
626+ it ( 'prints an unroutable parenthesised path as code, never as a dead link' , ( ) => {
627+ // Widening the rewriter must not widen what it is willing to LINK. A path
628+ // with no page still falls back to a code span, so the parentheses can
629+ // never produce a 404 on the site.
630+ expect ( describedBy ( 'Nothing here (nowhere/absent.zod.ts) resolves.' ) ) . toBe (
631+ 'Nothing here (`nowhere/absent.zod.ts`) resolves.' ,
632+ ) ;
633+ } ) ;
634+
635+ it ( 'still links the same path outside parentheses — the fix widens, it does not move' , ( ) => {
636+ // The vacuity guard for the four cases above. Each of them asserts an
637+ // OUTPUT for a path in parentheses; if this `ctx` had stopped resolving
638+ // that path, the parenthesised cases could have been written around a
639+ // code-span fallback and passed while proving nothing. Pinning the same
640+ // path unparenthesised fixes the only variable to the parentheses.
641+ expect ( describedBy ( 'The layer integration/connector.zod.ts is the widest.' ) ) . toBe (
642+ 'The layer [integration/connector.zod.ts](/docs/references/integration/connector) is the widest.' ,
643+ ) ;
644+ } ) ;
645+
646+ it ( 'keeps a formed link destination out of reach — the tokenizer, not the guards' , ( ) => {
647+ // The case the deleted lookaround was actually written for, and the reason
648+ // deleting it is safe. A titled `{@link }` whose target has no page emits
649+ // `[label](../nowhere/absent.zod.ts)`: the raw path is now a link
650+ // DESTINATION, sitting between the very `(` and `)` the guards tested for.
651+ // With them gone the only thing standing between that path and a second
652+ // rewrite is #6136's tokenizer, which classifies the whole construct as a
653+ // `link` run this step is never shown. Were that protection to regress,
654+ // this case reports `[the fallback](\`../nowhere/absent.zod.ts\`)` — the
655+ // #6136 shape — while every other case here stays green.
656+ expect ( describedBy ( 'See {@link ../nowhere/absent.zod.ts|the fallback} for now.' ) ) . toBe (
657+ 'See [the fallback](../nowhere/absent.zod.ts) for now.' ,
658+ ) ;
659+ } ) ;
660+ } ) ;
661+
556662/**
557663 * The corpus half: re-derive the verdict from the real sources, so the six
558664 * pages the issue measured cannot silently re-acquire a wrong opening, and so a
@@ -790,6 +896,32 @@ describe('corpus — every rendered description is well-formed markdown', () =>
790896 expect ( offenders ) . toEqual ( [ ] ) ;
791897 } ) ;
792898
899+ it ( 'never leaves a bare source path sitting in parentheses (#6420)' , ( ) => {
900+ // The corpus half of the unit block above. A path this step CAN match —
901+ // one with a category segment, `(?:\.\./)*<dir>/<file>.zod.ts` — must never
902+ // reach a page still bare: it is a link when a page renders it and a code
903+ // span when none does, and "plain text between parentheses" is the one
904+ // outcome the lookaround pair used to force. Scanned on the rendered
905+ // fragment rather than on the emitted `.mdx` for the same reason the rest
906+ // of this file is: `check:docs` reproduces the artifact faithfully and so
907+ // stayed green through all three published symptoms.
908+ //
909+ // Only the head character is examined, not a full `(…)` pair: `- Integration
910+ // connectors (integration/connector.zod.ts)` and `(…) - System integrators`
911+ // are different closers and both were victims, so what identifies the class
912+ // is a `(` immediately before the path. A path that follows `](` is a link
913+ // destination and belongs there — the tokenizer put it there.
914+ const offenders : string [ ] = [ ] ;
915+ for ( const { rel, out } of described ) {
916+ for ( const line of withoutFences ( out ) . split ( '\n' ) ) {
917+ const bare = line . replace ( / ` [ ^ ` ] * ` / g, '' ) ; // a code span is the null-route fallback
918+ const hit = / ( ^ | [ ^ \] ] ) \( (?: \. \. \/ ) * [ \w - ] + \/ [ \w . - ] + \. z o d \. t s / . exec ( bare ) ;
919+ if ( hit ) offenders . push ( `${ rel } : ${ hit [ 0 ] . trim ( ) } ` ) ;
920+ }
921+ }
922+ expect ( offenders ) . toEqual ( [ ] ) ;
923+ } ) ;
924+
793925 it ( 'keeps a description for every source that had one — #6134 selection is untouched' , ( ) => {
794926 // The rendering fix must not remove a page's opening paragraph; that is
795927 // #5059's acceptance criterion and it still binds. 185 sources carry a
0 commit comments