Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/short-deprecation-links.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@node-core/doc-kit': minor
---

Use short `DEP` codes for deprecation heading anchors.
5 changes: 0 additions & 5 deletions src/generators/legacy-html/constants.mjs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use strict';

import assert from 'node:assert/strict';
import { before, describe, it } from 'node:test';

import { setConfig } from '../../../../utils/configuration/index.mjs';
import buildContent from '../buildContent.mjs';

const createEntry = slug => {
const text = 'DEP0001: deprecated API';
const heading = {
type: 'heading',
depth: 3,
children: [{ type: 'text', value: text }],
data: { name: text, text, slug },
};

return {
api: 'deprecations',
path: '/deprecations',
basename: 'deprecations',
heading,
content: { type: 'root', children: [heading] },
};
};

describe('buildContent', () => {
before(() => setConfig({}));

it('does not duplicate a legacy anchor matching the primary slug', () => {
const output = buildContent([], [createEntry('DEP0001')]);

assert.strictEqual((output.match(/id="DEP0001"/g) ?? []).length, 1);
});

it('preserves a legacy anchor that differs from the primary slug', () => {
const output = buildContent([], [createEntry('dep0001-deprecated-api')]);

assert.match(output, /id="DEP0001"/);
assert.match(output, /id="dep0001-deprecated-api"/);
});
});
4 changes: 3 additions & 1 deletion src/generators/legacy-html/utils/buildContent.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ const buildHeading = ({ data, children, depth }, index, parent, legacySlug) => {
// to be converted into Rehype nodes
...children,
// Legacy anchor alias to preserve old external links
createElement('span', createElement(`a#${legacySlug}`)),
...(legacySlug === data.slug
? []
: [createElement('span', createElement(`a#${legacySlug}`))]),
Comment on lines +32 to +34

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated, no?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is required by the short-anchor change. On the deprecations page, both legacySlug and data.slug are now DEP0001, so emitting both anchors creates duplicate id="DEP0001" values. I added legacy HTML regressions that verify the matching alias is omitted while a differing legacy alias is still preserved.

// Creates the element that references the link to the heading
// (The `#` anchor on the right of each Heading section)
createElement(
Expand Down
2 changes: 1 addition & 1 deletion src/generators/legacy-html/utils/slugger.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import { DEPRECATION_HEADING_REGEX } from '../constants.mjs';
import { DEPRECATION_HEADING_REGEX } from '../../metadata/constants.mjs';

/**
* Creates a stateful slugger for legacy anchor links.
Expand Down
3 changes: 3 additions & 0 deletions src/generators/metadata/constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export const DOC_API_SLUGS_REPLACEMENTS = [
{ from: /^(?!-)(?!-+$).*?(--+)/g, to: '-' }, // Replace multiple hyphens
];

// Matches Node.js deprecation headings such as "DEP0001: Description".
export const DEPRECATION_HEADING_REGEX = /^(DEP\d+):/;
Comment thread
cursor[bot] marked this conversation as resolved.

// These are regular expressions used to determine if a given Markdown heading
// is a specific type of API Doc entry (e.g., Event, Class, Method, etc)
// and to extract the inner content of said Heading to be used as the API doc entry name
Expand Down
32 changes: 32 additions & 0 deletions src/generators/metadata/utils/__tests__/parse.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,38 @@ describe('parseApiDoc', () => {
});
});

describe('deprecation headings', () => {
it('uses the DEP code as the slug on the deprecations page', () => {
const tree = u('root', [
h('DEP0001: http.OutgoingMessage.prototype.flush'),
]);
const [entry] = parseApiDoc({ path: '/deprecations', tree }, typeMap);

assert.strictEqual(entry.heading.data.slug, 'DEP0001');
});

it('deduplicates repeated DEP code slugs', () => {
const tree = u('root', [
h('DEP0001: first deprecated API'),
h('DEP0001: second deprecated API'),
]);
const entries = parseApiDoc({ path: '/deprecations', tree }, typeMap);

assert.strictEqual(entries[0].heading.data.slug, 'DEP0001');
assert.strictEqual(entries[1].heading.data.slug, 'DEP0001-1');
});

it('uses normal slugs for DEP headings outside the deprecations page', () => {
const tree = u('root', [h('DEP0190: some section heading')]);
const [entry] = parseApiDoc({ path, tree }, typeMap);

assert.strictEqual(
entry.heading.data.slug,
'dep0190-some-section-heading'
);
});
});

describe('YAML metadata', () => {
it('extracts added_in', () => {
const tree = u('root', [h('fs'), yaml('added: v0.1.0')]);
Expand Down
20 changes: 18 additions & 2 deletions src/generators/metadata/utils/parse.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import {
import { UNIST } from '../../../utils/queries/index.mjs';
import { getRemark as remark } from '../../../utils/remark.mjs';
import { relative } from '../../../utils/url.mjs';
import { IGNORE_STABILITY_STEMS } from '../constants.mjs';
import {
DEPRECATION_HEADING_REGEX,
IGNORE_STABILITY_STEMS,
} from '../constants.mjs';
import { resolveTypeAnnotations } from './resolveTypes.mjs';

/**
Expand All @@ -44,6 +47,19 @@ export const parseApiDoc = ({ path, tree, mdx = false }, typeMap) => {
// Slug the API (We use a non-class slugger, since we are fairly certain that `path` is unique)
const api = slug(path.slice(1).replace(sep, '-'));

/**
* Creates a stable slug for a heading in the current API document.
* @param {string} text Heading text to slug.
* @returns {string} The generated heading slug.
*/
const getHeadingSlug = text => {
const deprecationHeading = DEPRECATION_HEADING_REGEX.exec(text);

return api === 'deprecations' && deprecationHeading
? nodeSlugger.slug(deprecationHeading[1]).toUpperCase()
: nodeSlugger.slug(text);
};

// Get all Markdown Footnote definitions from the tree
const markdownDefinitions = selectAll('definition', tree);

Expand Down Expand Up @@ -96,7 +112,7 @@ export const parseApiDoc = ({ path, tree, mdx = false }, typeMap) => {
});

// Generate slug and update heading data
metadata.heading.data.slug = nodeSlugger.slug(metadata.heading.data.text);
metadata.heading.data.slug = getHeadingSlug(metadata.heading.data.text);

// Find the next heading to determine section boundaries
const nextHeadingNode =
Expand Down