Skip to content

Commit 9285e74

Browse files
authored
fix(legacy-json): misc promotion logic fix (#493)
* fix(legacy-json): misc promotion logic fix Re nodejs/node#57343 (comment) Signed-off-by: flakey5 <[email protected]> * unit tests Signed-off-by: flakey5 <[email protected]> --------- Signed-off-by: flakey5 <[email protected]>
1 parent a97df05 commit 9285e74

File tree

2 files changed

+160
-22
lines changed

2 files changed

+160
-22
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
'use strict';
2+
3+
import assert from 'node:assert/strict';
4+
import { describe, test } from 'node:test';
5+
6+
import { UNPROMOTED_KEYS } from '../../constants.mjs';
7+
import { promoteMiscChildren } from '../buildSection.mjs';
8+
9+
describe('promoteMiscChildren', () => {
10+
/**
11+
* @template {object} T
12+
*
13+
* @param {T} base
14+
* @param {'section'|'parent'} [type='section']
15+
* @returns {T}
16+
*/
17+
function buildReadOnlySection(base, type = 'section') {
18+
return new Proxy(base, {
19+
set(_, key) {
20+
throw new Error(`${type} property '${String(key)} modified`);
21+
},
22+
});
23+
}
24+
25+
test('ignores non-misc section', () => {
26+
const section = buildReadOnlySection({
27+
type: 'text',
28+
});
29+
30+
const parent = buildReadOnlySection(
31+
{
32+
type: 'text',
33+
},
34+
'parent'
35+
);
36+
37+
promoteMiscChildren(section, parent);
38+
});
39+
40+
test('ignores misc parent', () => {
41+
const section = buildReadOnlySection({
42+
type: 'misc',
43+
});
44+
45+
const parent = buildReadOnlySection(
46+
{
47+
type: 'misc',
48+
},
49+
'parent'
50+
);
51+
52+
promoteMiscChildren(section, parent);
53+
});
54+
55+
test('ignores keys in UNPROMOTED_KEYS', () => {
56+
const sectionRaw = {
57+
type: 'misc',
58+
promotableKey: 'this should be promoted',
59+
};
60+
61+
UNPROMOTED_KEYS.forEach(key => {
62+
if (key === 'type') {
63+
return;
64+
}
65+
66+
sectionRaw[key] = 'this should be ignored';
67+
});
68+
69+
const section = buildReadOnlySection(sectionRaw);
70+
71+
const parent = {
72+
type: 'module',
73+
};
74+
75+
promoteMiscChildren(section, parent);
76+
77+
UNPROMOTED_KEYS.forEach(key => {
78+
if (key === 'type') {
79+
return;
80+
}
81+
82+
if (parent[key]) {
83+
throw new Error(`'${key}' was promoted`);
84+
}
85+
});
86+
87+
assert.strictEqual(parent.promotableKey, section.promotableKey);
88+
});
89+
90+
describe('merges properties correctly', () => {
91+
test('pushes child property if parent is an array', () => {
92+
const section = buildReadOnlySection({
93+
type: 'misc',
94+
someValue: 'bar',
95+
});
96+
97+
const parent = {
98+
type: 'module',
99+
someValue: ['foo'],
100+
};
101+
102+
promoteMiscChildren(section, parent);
103+
104+
assert.deepStrictEqual(parent.someValue, ['foo', 'bar']);
105+
});
106+
107+
test('ignores child property if parent has a value that is not an array', () => {
108+
const section = buildReadOnlySection({
109+
type: 'misc',
110+
someValue: 'bar',
111+
});
112+
113+
const parent = {
114+
type: 'module',
115+
someValue: 'foo',
116+
};
117+
118+
promoteMiscChildren(section, parent);
119+
120+
assert.strictEqual(parent.someValue, 'foo');
121+
});
122+
123+
test('promotes child property if parent does not have the property', () => {
124+
const section = buildReadOnlySection({
125+
type: 'misc',
126+
someValue: 'bar',
127+
});
128+
129+
const parent = {
130+
type: 'module',
131+
};
132+
133+
promoteMiscChildren(section, parent);
134+
135+
assert.deepStrictEqual(parent.someValue, 'bar');
136+
});
137+
});
138+
});

src/generators/legacy-json/utils/buildSection.mjs

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,28 @@ import { getRemarkRehype } from '../../../utils/remark.mjs';
55
import { transformNodesToString } from '../../../utils/unist.mjs';
66
import { SECTION_TYPE_PLURALS, UNPROMOTED_KEYS } from '../constants.mjs';
77

8+
/**
9+
* Promotes children properties to the parent level if the section type is 'misc'.
10+
* @param {import('../types.d.ts').Section} section - The section to promote.
11+
* @param {import('../types.d.ts').Section} parent - The parent section.
12+
*/
13+
export const promoteMiscChildren = (section, parent) => {
14+
// Only promote if the current section is of type 'misc' and the parent is not 'misc'
15+
if (section.type === 'misc' && parent.type !== 'misc') {
16+
Object.entries(section).forEach(([key, value]) => {
17+
// Only promote certain keys
18+
if (!UNPROMOTED_KEYS.includes(key)) {
19+
// Merge the section's properties into the parent section
20+
if (parent[key] && Array.isArray(parent[key])) {
21+
parent[key] = parent[key].concat(value);
22+
} else {
23+
parent[key] ||= value;
24+
}
25+
}
26+
});
27+
}
28+
};
29+
830
/**
931
*
1032
*/
@@ -104,28 +126,6 @@ export const createSectionBuilder = () => {
104126
parent[key].push(section);
105127
};
106128

107-
/**
108-
* Promotes children properties to the parent level if the section type is 'misc'.
109-
* @param {import('../types.d.ts').Section} section - The section to promote.
110-
* @param {import('../types.d.ts').Section} parent - The parent section.
111-
*/
112-
const promoteMiscChildren = (section, parent) => {
113-
// Only promote if the current section is of type 'misc' and the parent is not 'misc'
114-
if (section.type === 'misc' && parent.type !== 'misc') {
115-
Object.entries(section).forEach(([key, value]) => {
116-
// Only promote certain keys
117-
if (!UNPROMOTED_KEYS.includes(key)) {
118-
// Merge the section's properties into the parent section
119-
parent[key] = parent[key]
120-
? // If the parent already has this key, concatenate the values
121-
[].concat(parent[key], value)
122-
: // Otherwise, directly assign the section's value to the parent
123-
[];
124-
}
125-
});
126-
}
127-
};
128-
129129
/**
130130
* Processes children of a given entry and updates the section.
131131
* @param {import('../types.d.ts').HierarchizedEntry} entry - The current entry.

0 commit comments

Comments
 (0)