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
2 changes: 1 addition & 1 deletion src/generators/jsx-ast/utils/buildContent.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export const processEntry = (entry, remark) => {
// Transform typed lists into property tables
visit(
content,
createQueries.UNIST.isTypedList,
createQueries.UNIST.isStronglyTypedList,
(node, idx, parent) => (parent.children[idx] = createPropertyTable(node))
);

Expand Down
4 changes: 2 additions & 2 deletions src/generators/jsx-ast/utils/buildPropertyTable.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export const parseListIntoProperties = node => {

// Clean up leading whitespace in remaining description
if (children[0]?.type === 'text') {
children[0].value = children[0].value.trimStart();
children[0].value = children[0].value.replace(/^[\s:]+/, '');
Copy link
Member

Choose a reason for hiding this comment

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

make the regex a constant

}

properties.push({
Expand All @@ -133,7 +133,7 @@ export const parseListIntoProperties = node => {
// The remaining children are the description
desc: children,
// Is there a list within this list?
sublist: sublists.find(createQueries.UNIST.isTypedList),
sublist: sublists.find(createQueries.UNIST.isLooselyTypedList),
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/generators/jsx-ast/utils/buildSignature.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const getFullName = ({ name, text }, fallback = name) => {
*/
export default ({ children }, { data }, idx) => {
// Try to locate the parameter list immediately following the heading
const listIdx = children.findIndex(createQueries.UNIST.isTypedList);
const listIdx = children.findIndex(createQueries.UNIST.isStronglyTypedList);

// Parse parameters from the list, if found
const params =
Expand Down
2 changes: 1 addition & 1 deletion src/generators/legacy-json/constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const DEFAULT_EXPRESSION = /\s*\*\*Default:\*\*\s*([^]+)$/i;

// Grabs the parameters from a method's signature
// ex/ 'new buffer.Blob([sources[, options]])'.match(PARAM_EXPRESSION) === ['([sources[, options]])', '[sources[, options]]']
export const PARAM_EXPRESSION = /\((.+)\);?$/;
export const PARAM_EXPRESSION = /\(([^)]+)\);?$/;

// The plurals associated with each section type.
export const SECTION_TYPE_PLURALS = {
Expand Down
26 changes: 13 additions & 13 deletions src/generators/legacy-json/utils/__tests__/parseList.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ import {
parseList,
} from '../parseList.mjs';

const validTypedList = [
{ type: 'inlineCode', value: 'option' }, // inline code
{ type: 'text', value: ' ' }, // space
{
type: 'link',
children: [{ type: 'text', value: '<boolean>' }], // link with < value
},
{ type: 'text', value: ' option description' },
];

describe('transformTypeReferences', () => {
it('replaces template syntax with curly braces', () => {
const result = transformTypeReferences('`<string>`');
Expand Down Expand Up @@ -90,7 +100,7 @@ describe('parseList', () => {
children: [
{
type: 'paragraph',
children: [{ type: 'text', value: '{string} description' }],
children: validTypedList,
},
],
},
Expand Down Expand Up @@ -134,9 +144,7 @@ describe('parseList', () => {
children: [
{
type: 'paragraph',
children: [
{ type: 'text', value: 'param1 {string} first parameter' },
],
children: validTypedList,
},
// This is a nested typed list
{
Expand All @@ -146,15 +154,7 @@ describe('parseList', () => {
children: [
{
type: 'paragraph',
children: [
{ type: 'inlineCode', value: 'option' }, // inline code
{ type: 'text', value: ' ' }, // space
{
type: 'link',
children: [{ type: 'text', value: '<boolean>' }], // link with < value
},
{ type: 'text', value: ' option description' },
],
children: validTypedList,
},
],
},
Expand Down
23 changes: 15 additions & 8 deletions src/generators/legacy-json/utils/buildSection.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const createSectionBuilder = () => {

meta.changes = changes;

if (n_api_version?.length) {
if (typeof n_api_version === 'number' || n_api_version?.length) {
meta.napiVersion = enforceArray(n_api_version);
}

Expand Down Expand Up @@ -102,13 +102,17 @@ export const createSectionBuilder = () => {
* @param {Array} nodes - The remaining AST nodes.
* @param {import('../types.d.ts').HierarchizedEntry} entry - The entry providing stability information.
*/
const parseStability = (section, nodes, { stability }) => {
const stabilityInfo = stability.children.map(node => node.data)?.[0];
const parseStability = (section, nodes, { stability, content }) => {
const stabilityNode = stability.children[0];

if (stabilityInfo) {
section.stability = Number(stabilityInfo.index);
section.stabilityText = stabilityInfo.description;
nodes.shift(); // Remove stability node from processing
if (stabilityNode) {
section.stability = Number(stabilityNode.data.index);
section.stabilityText = stabilityNode.data.description;

const nodeToRemove = content.children.findIndex(
Copy link
Member

Choose a reason for hiding this comment

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

This feels like an expensive search, we should have the node index.

Copy link
Member Author

Choose a reason for hiding this comment

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

We don't have it stored anywhere

Copy link
Member

Choose a reason for hiding this comment

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

I'm pretty sure we have the Node IDs.

({ data }) => data === stabilityNode.data
);
nodes.splice(nodeToRemove - 1, 1);
}
};

Expand Down Expand Up @@ -153,7 +157,10 @@ export const createSectionBuilder = () => {
* @param {import('../types.d.ts').Section} parent - The parent section.
*/
const addToParent = (section, parent) => {
const key = SECTION_TYPE_PLURALS[section.type] || 'miscs';
const key =
SECTION_TYPE_PLURALS[section.__promote ?? section.type] || 'miscs';

delete section.__promote;

parent[key] ??= [];
parent[key].push(section);
Expand Down
23 changes: 16 additions & 7 deletions src/generators/legacy-json/utils/parseList.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const extractPattern = (text, pattern, key, current) => {
export function parseListItem(child) {
const current = {};

const subList = child.children.find(createQueries.UNIST.isTypedList);
const subList = child.children.find(createQueries.UNIST.isLooselyTypedList);

// Extract and clean raw text from the node, excluding nested lists
current.textRaw = transformTypeReferences(
Expand Down Expand Up @@ -89,10 +89,13 @@ export function parseListItem(child) {
* @param {import('@types/mdast').RootContent[]} nodes
*/
export function parseList(section, nodes) {
const list = nodes[0]?.type === 'list' ? nodes.shift() : null;
const listIdx = nodes.findIndex(createQueries.UNIST.isStronglyTypedList);
const list = nodes[listIdx];

const values = list ? list.children.map(parseListItem) : [];

let removeList = true;

// Update the section based on its type and parsed values
switch (section.type) {
case 'ctor':
Expand All @@ -109,7 +112,12 @@ export function parseList(section, nodes) {
case 'property':
// For properties, update type and other details if values exist
if (values.length) {
leftHandAssign(section, values[0]);
delete values[0].name;

Object.assign(section, values[0]);

// TODO(@avivkeller): There is probably a better way to do this.
section.__promote = 'property';
Copy link
Member

Choose a reason for hiding this comment

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

What is this?

Copy link
Member Author

Choose a reason for hiding this comment

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

When we Object.assign(section, values[0]);, we replace section.type with values[0].type (the type of the property, i.e. string, etc)

When promoting properties to their parent (moving the section into misc, properties, modules, whatever), we check the .type, if property, we move to properties. However, since the type has been overwritten, we still need to move the object to the correct parent section, properties

Copy link
Member

Choose a reason for hiding this comment

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

I'm unfamiliar with the __promote property, where does that come from?

Copy link
Member Author

@avivkeller avivkeller Dec 22, 2025

Choose a reason for hiding this comment

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

My brain xD, I just added it for this purpose. It's deleted during promotion

Copy link
Member

Choose a reason for hiding this comment

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

When "promotion" happens? Can we avoid using dark hidden properties?

}
break;

Expand All @@ -119,9 +127,10 @@ export function parseList(section, nodes) {
break;

default:
// If no specific handling, re-add the list for further processing
if (list) {
nodes.unshift(list);
}
removeList = false;
}

if (removeList && list) {
nodes.splice(listIdx, 1);
}
}
9 changes: 6 additions & 3 deletions src/utils/parser/constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,20 @@ const FUNCTION_CALL = '\\([^)]*\\)';
// Matches "bar":
// Group 1: foo[bar]
// Group 2: foo.bar
const PROPERTY = `${CAMEL_CASE}(?:(\\[${CAMEL_CASE}\\])|\\.(\\w+))`;
const PROPERTY = `${CAMEL_CASE}(?:(\\[[^\\]]+\\])|\\.(\\w+))`;

// An array of objects defining the different types of API doc headings we want to
// capture and their respective regex to match against the heading text.
// The regex are case-insensitive.
export const DOC_API_HEADING_TYPES = [
{
type: 'method',
regex: new RegExp(`^\`${PROPERTY}${FUNCTION_CALL}\`$`, 'i'),
regex: new RegExp(
`^\`(?:${PROPERTY}|(${CAMEL_CASE}))${FUNCTION_CALL}\`$`,
'i'
),
},
{ type: 'event', regex: /^Event: +`'?([^']+)'`$/i },
{ type: 'event', regex: /^Event: +`'?([^`]*?)'?`$/i },
{
type: 'class',
regex: new RegExp(
Expand Down
14 changes: 10 additions & 4 deletions src/utils/queries/__tests__/index.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,23 @@ describe('createQueries', () => {
});

describe('UNIST', () => {
describe('isTypedList', () => {
describe('isStronglyTypedList', () => {
it('returns false for non-list nodes', () => {
strictEqual(
createQueries.UNIST.isTypedList({ type: 'paragraph', children: [] }),
createQueries.UNIST.isStronglyTypedList({
type: 'paragraph',
children: [],
}),
false
);
});

it('returns false for empty lists', () => {
strictEqual(
createQueries.UNIST.isTypedList({ type: 'list', children: [] }),
createQueries.UNIST.isStronglyTypedList({
type: 'list',
children: [],
}),
false
);
});
Expand Down Expand Up @@ -211,7 +217,7 @@ describe('createQueries', () => {

cases.forEach(({ name, node, expected }) => {
it(`returns ${expected} for ${name}`, () => {
strictEqual(createQueries.UNIST.isTypedList(node), expected);
strictEqual(createQueries.UNIST.isStronglyTypedList(node), expected);
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion src/utils/queries/constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
export const DOC_API_STABILITY_SECTION_REF_URL =
'documentation.html#stability-index';

export const VALID_JAVASCRIPT_PROPERTY = /^[.a-z0-9$_]+$/i;
export const VALID_JAVASCRIPT_PROPERTY = /^[.a-z0-9$_'-]+$/i;
62 changes: 21 additions & 41 deletions src/utils/queries/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
import { u as createTree } from 'unist-builder';
import { SKIP } from 'unist-util-visit';

import {
DOC_API_STABILITY_SECTION_REF_URL,
VALID_JAVASCRIPT_PROPERTY,
} from './constants.mjs';
import { DOC_API_STABILITY_SECTION_REF_URL } from './constants.mjs';
import {
extractYamlContent,
parseHeadingIntoMetadata,
Expand All @@ -16,6 +13,7 @@ import {
} from '../parser/index.mjs';
import { getRemark } from '../remark.mjs';
import { transformNodesToString } from '../unist.mjs';
import { isTypedList } from './utils.mjs';

/**
* Creates an instance of the Query Manager, which allows to do multiple sort
Expand Down Expand Up @@ -272,46 +270,28 @@ createQueries.UNIST = {
* @param {import('@types/mdast').List} list
* @returns {boolean}
*/
isTypedList: list => {
// Exit early if not a list node
if (list.type !== 'list') {
return false;
}

// Get the content nodes of the first list item's paragraph
const [node, ...contentNodes] =
list?.children?.[0]?.children?.[0]?.children ?? [];

// Exit if no node
if (!node) {
return false;
}

const possibleProperty = node?.value?.trimStart();

// Check for other starters
if (possibleProperty?.match(createQueries.QUERIES.typedListStarters)) {
return true;
}
isLooselyTypedList: list => Boolean(isTypedList(list)),

// Check for direct type link pattern (starts with '<')
if (node.type === 'link' && node.children?.[0]?.value?.[0] === '<') {
return true;
}

// Check for inline code + space + type link pattern
if (
node.type === 'inlineCode' &&
possibleProperty?.match(VALID_JAVASCRIPT_PROPERTY) &&
contentNodes[0]?.value?.trim() === '' &&
contentNodes[1]?.type === 'link' &&
contentNodes[1]?.children?.[0]?.value?.[0] === '<'
) {
return true;
/**
* @param {import('@types/mdast').List} list
* @returns {boolean}
*/
isStronglyTypedList: list => {
const confidence = isTypedList(list);

if (confidence === 1) {
// This is a loosely typed list, but we can still check if it is strongly typed.
const [, secondNode, thirdNode] =
list.children?.[0]?.children?.[0]?.children ?? [];

return (
secondNode?.value?.trim() === '' &&
thirdNode?.type === 'link' &&
thirdNode?.children?.[0]?.value?.[0] === '<'
);
}

// Not a typed list
return false;
return Boolean(confidence);
},
};

Expand Down
48 changes: 48 additions & 0 deletions src/utils/queries/utils.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { VALID_JAVASCRIPT_PROPERTY } from './constants.mjs';
import createQueries from './index.mjs';

/**
* @param {import('@types/mdast').List} list
* @returns {0 | 1 | 2} confidence
*
* 0: This is not a typed list
* 1: This is a loosely typed list
* 2: This is a strongly typed list
*/
export const isTypedList = list => {
if (!list || list.type !== 'list') {
return 0;
}

const firstNode = list.children?.[0]?.children?.[0]?.children[0];

if (!firstNode) {
return 0;
}

const value = firstNode?.value?.trimStart();

// Typed list starters (strong signal)
if (value && createQueries.QUERIES.typedListStarters.test(value)) {
return 2;
}

// Direct type link: <Type>
if (
firstNode.type === 'link' &&
firstNode.children?.[0]?.value?.startsWith('<')
) {
return 2;
}

// inlineCode + space (weaker signal)
if (
firstNode.type === 'inlineCode' &&
value &&
VALID_JAVASCRIPT_PROPERTY.test(value)
) {
return 1;
}

return 0;
};
Loading