@mintlify/common 1.0.1172 → 1.0.1174

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,7 @@ const MDX_HEADING_ID_RE = /^(\s*)(#{1,6})\s+(.+?)\s*\{\s*#([^}]+?)\s*\}\s*$/;
7
7
  // `<MDX>` or `<MDX className="x">`; not `<MDX />` (no body) and not `<MDXProvider>`
8
8
  const MDX_OPEN_RE = /<MDX(?:\s[^>]*[^/>\s])?\s*>/g;
9
9
  const MDX_CLOSE_RE = /<\/MDX\s*>/g;
10
+ const COMMENT_MASK_RE = /(`+).*?\1|"[^"\n]*"/g;
10
11
  const UNSAFE_HTML_ATTR_CHARS = /[<>"]/g;
11
12
  /**
12
13
  * Characters to strip from custom heading IDs in the default (non-editor) path.
@@ -44,6 +45,7 @@ export function preprocessCustomHeadingIds(content, options) {
44
45
  let fenceChar;
45
46
  let fenceCount = 0;
46
47
  let mdxDepth = 0;
48
+ let inComment = false;
47
49
  for (const line of lines) {
48
50
  const stripped = line.trimStart();
49
51
  if (fenceChar !== undefined) {
@@ -53,15 +55,19 @@ export function preprocessCustomHeadingIds(content, options) {
53
55
  result.push(line);
54
56
  continue;
55
57
  }
56
- const fence = parseOpeningFence(stripped);
58
+ const fence = !inComment && parseOpeningFence(stripped);
57
59
  if (fence) {
58
60
  fenceChar = fence.char;
59
61
  fenceCount = fence.count;
60
62
  result.push(line);
61
63
  continue;
62
64
  }
63
- const heading = (mdxDepth > 0 ? MDX_HEADING_ID_RE : HEADING_ID_RE).exec(line);
64
- if ((heading === null || heading === void 0 ? void 0 : heading[2]) && heading[3] && heading[4]) {
65
+ const { visible, inComment: endsInComment } = scanComments(line, inComment);
66
+ const commented = inComment || endsInComment;
67
+ inComment = endsInComment;
68
+ const headingRe = mdxDepth > 0 ? MDX_HEADING_ID_RE : HEADING_ID_RE;
69
+ const heading = !commented && headingRe.exec(line);
70
+ if (heading && heading[2] && heading[3] && heading[4]) {
65
71
  const indent = (_a = heading[1]) !== null && _a !== void 0 ? _a : '';
66
72
  const level = heading[2].length;
67
73
  const sanitizedId = (options === null || options === void 0 ? void 0 : options.preserveOriginal)
@@ -70,11 +76,32 @@ export function preprocessCustomHeadingIds(content, options) {
70
76
  result.push(`${indent}<h${level} id="${sanitizedId}">`, `${indent}${heading[3]}`, `${indent}</h${level}>`);
71
77
  continue;
72
78
  }
73
- mdxDepth = Math.max(0, mdxDepth + ((_c = (_b = line.match(MDX_OPEN_RE)) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0) - ((_e = (_d = line.match(MDX_CLOSE_RE)) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0));
79
+ mdxDepth = Math.max(0, mdxDepth +
80
+ ((_c = (_b = visible.match(MDX_OPEN_RE)) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0) -
81
+ ((_e = (_d = visible.match(MDX_CLOSE_RE)) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0));
74
82
  result.push(line);
75
83
  }
76
84
  return result.join('\n');
77
85
  }
86
+ function scanComments(line, inComment) {
87
+ const masked = line.replace(COMMENT_MASK_RE, (span) => ' '.repeat(span.length));
88
+ let visible = '';
89
+ let pos = 0;
90
+ for (;;) {
91
+ if (inComment) {
92
+ const close = line.indexOf('*/', pos);
93
+ if (close === -1)
94
+ return { visible, inComment };
95
+ pos = close + 2;
96
+ }
97
+ const open = masked.indexOf('{/*', pos);
98
+ if (open === -1)
99
+ return { visible: visible + line.slice(pos), inComment: false };
100
+ visible += line.slice(pos, open);
101
+ pos = open + 3;
102
+ inComment = true;
103
+ }
104
+ }
78
105
  export function parseOpeningFence(stripped) {
79
106
  const char = stripped[0];
80
107
  if (char !== '`' && char !== '~')
@@ -73,6 +73,91 @@ describe('preprocessCustomHeadingIds', () => {
73
73
  \`\`\``;
74
74
  expect(preprocessCustomHeadingIds(input)).toBe(input);
75
75
  });
76
+ it('converts headings after a comment whose closing fence shares a line with */}', () => {
77
+ const input = `{/* Note
78
+
79
+ \`\`\`python
80
+ run.log({'loss': 0.2})
81
+ \`\`\` */}
82
+
83
+ ## After {#after}`;
84
+ expect(preprocessCustomHeadingIds(input)).toContain('<h2 id="after">\nAfter\n</h2>');
85
+ });
86
+ it('converts headings after a comment whose closing fence is on its own line', () => {
87
+ const input = `{/* Note
88
+
89
+ \`\`\`python
90
+ run.log({'loss': 0.2})
91
+ \`\`\`
92
+ */}
93
+
94
+ ## After {#after}`;
95
+ expect(preprocessCustomHeadingIds(input)).toContain('<h2 id="after">\nAfter\n</h2>');
96
+ });
97
+ it('converts headings after an inline comment whose closing fence shares a line with */}', () => {
98
+ const input = `<Note>{/* Note
99
+
100
+ \`\`\`python
101
+ x = 1
102
+ \`\`\` */}</Note>
103
+
104
+ ## After {#after}`;
105
+ expect(preprocessCustomHeadingIds(input)).toContain('<h2 id="after">\nAfter\n</h2>');
106
+ });
107
+ it('ignores comment openers inside inline code', () => {
108
+ expect(preprocessCustomHeadingIds('Use `{/*` to open a comment.\n\n## After {#after}')).toContain('<h2 id="after">\nAfter\n</h2>');
109
+ });
110
+ it('tracks a comment reopened on the line that closes the previous one', () => {
111
+ const input = '{/* one\n*/} {/* two\n## Hidden {#hidden}\n*/}\n\n## After {#after}';
112
+ const output = preprocessCustomHeadingIds(input);
113
+ expect(output).toContain('## Hidden {#hidden}');
114
+ expect(output).toContain('<h2 id="after">\nAfter\n</h2>');
115
+ });
116
+ it('ignores comment openers inside quoted attribute values', () => {
117
+ expect(preprocessCustomHeadingIds('<Note title="{/*">\nBody\n</Note>\n\n## After {#after}')).toContain('<h2 id="after">\nAfter\n</h2>');
118
+ });
119
+ it('tracks a comment opened between apostrophes in prose', () => {
120
+ const input = "Don't miss {/* it's hidden\n## Hidden {#hidden}\n*/}\n\n## After {#after}";
121
+ const output = preprocessCustomHeadingIds(input);
122
+ expect(output).toContain('## Hidden {#hidden}');
123
+ expect(output).toContain('<h2 id="after">\nAfter\n</h2>');
124
+ });
125
+ it('counts an <MDX> tag that shares a line with a comment', () => {
126
+ const input = '<MDX>{/* note\n*/}\n ## Heading {#heading}\n</MDX>';
127
+ expect(preprocessCustomHeadingIds(input)).toContain(' <h2 id="heading">\n Heading\n </h2>');
128
+ });
129
+ it('ignores <MDX> tags inside comments', () => {
130
+ for (const input of [
131
+ '{/* <MDX> */}\n\n ## Still code {#not-converted}',
132
+ '{/*\n<MDX>\n*/}\n\n ## Still code {#not-converted}',
133
+ ]) {
134
+ expect(preprocessCustomHeadingIds(input)).toBe(input);
135
+ }
136
+ });
137
+ it('ends a comment at */ regardless of what precedes the closing brace', () => {
138
+ for (const comment of [
139
+ '{/* note */ }',
140
+ '{/*\n note\n*/\n}',
141
+ '<Note data-x={/* x */ 1}>\nBody\n</Note>',
142
+ ]) {
143
+ expect(preprocessCustomHeadingIds(`${comment}\n\n## After {#after}`)).toContain('<h2 id="after">\nAfter\n</h2>');
144
+ }
145
+ });
146
+ it('converts headings after a single-line comment', () => {
147
+ expect(preprocessCustomHeadingIds('{/* Note */}\n\n## After {#after}')).toContain('<h2 id="after">\nAfter\n</h2>');
148
+ });
149
+ it('leaves headings inside a comment alone', () => {
150
+ const input = '{/*\n## Heading {#custom-id}\n*/}';
151
+ expect(preprocessCustomHeadingIds(input)).toBe(input);
152
+ });
153
+ it('does not treat a comment opener inside a code fence as a comment', () => {
154
+ const input = `\`\`\`mdx
155
+ {/* Note
156
+ \`\`\`
157
+
158
+ ## After {#after}`;
159
+ expect(preprocessCustomHeadingIds(input)).toContain('<h2 id="after">\nAfter\n</h2>');
160
+ });
76
161
  it('normalizes ids by default', () => {
77
162
  expect(preprocessCustomHeadingIds(' ## Heading {#my id!}')).toBe(' <h2 id="my-id">\n Heading\n </h2>');
78
163
  });