Skip to content

Commit c5a3fe8

Browse files
authored
fix(react): resolve heading names that share a substring with their receiver (#1116)
1 parent 7466893 commit c5a3fe8

3 files changed

Lines changed: 104 additions & 5 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@doc-kit/generator-react': patch
3+
---
4+
5+
fix: resolve heading names that share a substring with their receiver

‎packages/react/src/jsx-ast/utils/__tests__/signature.test.mjs‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,4 +372,71 @@ describe('getFullName', () => {
372372
);
373373
assert.strictEqual(result, 'fallbackValue');
374374
});
375+
376+
it('skips occurrences of the name within the receiver', () => {
377+
const result = getFullName({
378+
name: 'channel',
379+
text: '`diagnostics_channel.channel(name)`',
380+
});
381+
assert.strictEqual(result, 'diagnostics_channel.channel');
382+
});
383+
384+
it('skips occurrences of the name that are a prefix of the receiver', () => {
385+
const result = getFullName({
386+
name: 'read',
387+
text: '`readable.read([size])`',
388+
});
389+
assert.strictEqual(result, 'readable.read');
390+
});
391+
392+
it('ignores parameters repeating the name', () => {
393+
const result = getFullName({
394+
name: 'percentile',
395+
text: '`histogram.percentile(percentile)`',
396+
});
397+
assert.strictEqual(result, 'histogram.percentile');
398+
});
399+
400+
it('handles symbol-keyed methods', () => {
401+
const result = getFullName({
402+
name: "[Symbol.for('nodejs.rejection')]",
403+
text: "`emitter[Symbol.for('nodejs.rejection')](err, eventName[, ...args])`",
404+
});
405+
assert.strictEqual(result, "emitter[Symbol.for('nodejs.rejection')]");
406+
});
407+
408+
it('keeps quoted names intact', () => {
409+
const result = getFullName({
410+
name: 'console.log',
411+
text: "Event: `'console.log'`",
412+
});
413+
assert.strictEqual(result, 'console.log');
414+
});
415+
416+
it('does not strip "new" from within a name', () => {
417+
const result = getFullName({
418+
name: 'newListener',
419+
text: "Event: `'newListener'`",
420+
});
421+
assert.strictEqual(result, 'newListener');
422+
});
423+
424+
it('does not strip "new" from within a dotted name', () => {
425+
const result = getFullName({
426+
name: 'onnewtoken',
427+
text: '`session.onnewtoken`',
428+
});
429+
assert.strictEqual(result, 'session.onnewtoken');
430+
});
431+
432+
it('returns fallback when no occurrence terminates the name', () => {
433+
const result = getFullName(
434+
{
435+
name: 'read',
436+
text: '`readable`',
437+
},
438+
'fallback'
439+
);
440+
assert.strictEqual(result, 'fallback');
441+
});
375442
});

‎packages/react/src/jsx-ast/utils/signature.mjs‎

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,39 @@ export const getFullName = ({ name, text }, fallback = name) => {
8686
// Attempt to extract inline code from heading text
8787
const code = text.trim().match(/`([^`]+)`/)?.[1];
8888

89+
if (!code?.includes(name)) {
90+
return fallback;
91+
}
92+
93+
// Find the occurrence of `name` that denotes the documented entry: the one
94+
// immediately followed by its parameter list, a closing quote, or the end
95+
// of the code. Earlier occurrences are mere substrings of the receiver
96+
// (e.g. `channel` within `diagnostics_channel.channel`, `read` within
97+
// `readable.read`), and later ones can be parameters repeating the name.
98+
let end = -1;
99+
let index = code.indexOf(name);
100+
101+
while (index !== -1) {
102+
const next = code[index + name.length];
103+
104+
if (next === undefined || next === '(' || next === "'" || next === '"') {
105+
end = index + name.length;
106+
break;
107+
}
108+
109+
index = code.indexOf(name, index + 1);
110+
}
111+
89112
// If inline code includes the name, return a clean version of it
90-
return code?.includes(name)
91-
? code
92-
.slice(0, code.indexOf(name) + name.length) // Truncate everything after the name.
93-
.replace(/^["']|new\s*/g, '') // Strip quotes or "new" keyword
94-
: fallback;
113+
return end === -1
114+
? fallback
115+
: code
116+
.slice(0, end) // Truncate everything after the name.
117+
// Strip a leading quote and/or the "new" keyword. The latter requires
118+
// following whitespace so names containing "new" (e.g. `newListener`)
119+
// stay intact.
120+
.replace(/^["']/, '')
121+
.replace(/^new\s+/, '');
95122
};
96123

97124
/**

0 commit comments

Comments
 (0)