@adecore/editor-react 0.31.12 → 0.33.0

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.
Files changed (137) hide show
  1. package/dist/AnchoredPopup.js +73 -19
  2. package/dist/AttributionCard.js +162 -4
  3. package/dist/BaselineStatus.js +62 -8
  4. package/dist/CallHierarchyPanel.js +977 -60
  5. package/dist/ChangeReview.js +76 -25
  6. package/dist/CodeAuthorsCard.js +314 -14
  7. package/dist/CompletionPopup.js +490 -83
  8. package/dist/DiffView.js +548 -181
  9. package/dist/EditorContextMenu.js +352 -34
  10. package/dist/EditorView.js +276 -99
  11. package/dist/FindReplace.js +525 -67
  12. package/dist/HoverCard.js +378 -38
  13. package/dist/HoverSections.js +367 -56
  14. package/dist/LanguagePopups.js +95 -34
  15. package/dist/Markdown.js +41 -6
  16. package/dist/PanelResizeHandle.js +199 -66
  17. package/dist/PathText.js +49 -7
  18. package/dist/PeekPanel.js +675 -72
  19. package/dist/PickPopup.js +304 -34
  20. package/dist/RenameCard.js +679 -85
  21. package/dist/RenderedDocs.js +329 -58
  22. package/dist/SignatureCard.js +91 -7
  23. package/dist/SplitDiffView.js +344 -254
  24. package/dist/SymbolPicker.js +440 -85
  25. package/dist/call-hierarchy-model.js +102 -68
  26. package/dist/call-hierarchy.js +458 -359
  27. package/dist/code-actions-model.js +88 -76
  28. package/dist/code-actions.js +372 -283
  29. package/dist/code-authors.js +64 -55
  30. package/dist/code-vision-model.js +134 -127
  31. package/dist/code-vision.js +224 -206
  32. package/dist/completion-call.js +94 -67
  33. package/dist/completion-model.js +334 -244
  34. package/dist/completion.js +840 -649
  35. package/dist/conflict-model.js +35 -29
  36. package/dist/context-menu-rows.js +42 -17
  37. package/dist/context-menu.js +64 -46
  38. package/dist/definition-link.js +88 -85
  39. package/dist/diagnostics-model.js +74 -55
  40. package/dist/diagnostics.js +121 -100
  41. package/dist/diff-expansion.js +38 -35
  42. package/dist/diff-overlays.js +144 -22
  43. package/dist/diff-syntax.js +65 -42
  44. package/dist/diff-wordmarks.js +26 -23
  45. package/dist/doc-comment-model.js +265 -216
  46. package/dist/editor-language.js +177 -152
  47. package/dist/error-message.js +2 -2
  48. package/dist/find-query.js +49 -36
  49. package/dist/folding.js +81 -62
  50. package/dist/format-on-type.js +86 -67
  51. package/dist/highlight-layers.js +19 -20
  52. package/dist/highlights.js +105 -83
  53. package/dist/history.js +44 -38
  54. package/dist/holder.js +18 -18
  55. package/dist/host-types.js +1 -1
  56. package/dist/hover-content.js +167 -129
  57. package/dist/hover.js +285 -245
  58. package/dist/index.js +10 -10
  59. package/dist/inlay-hints.js +369 -324
  60. package/dist/key-priority.js +34 -31
  61. package/dist/language-ids.js +45 -42
  62. package/dist/line-edits.js +74 -28
  63. package/dist/line-text.js +18 -3
  64. package/dist/locales/en.json +38 -38
  65. package/dist/locales/nl.json +41 -41
  66. package/dist/models.js +1 -1
  67. package/dist/navigation-history.js +40 -41
  68. package/dist/navigation.js +141 -123
  69. package/dist/panel-size.js +8 -8
  70. package/dist/patch-decorations.js +236 -160
  71. package/dist/patch-layout.js +331 -262
  72. package/dist/paths.js +3 -3
  73. package/dist/peek-model.js +86 -69
  74. package/dist/peek.js +254 -192
  75. package/dist/pick.js +164 -157
  76. package/dist/popup-focus.js +5 -5
  77. package/dist/popup-placement.js +36 -21
  78. package/dist/popups.js +13 -13
  79. package/dist/project-language.js +228 -211
  80. package/dist/project-problems.js +81 -69
  81. package/dist/proposal-model.js +163 -126
  82. package/dist/recent-choices.js +25 -25
  83. package/dist/refresher.js +44 -49
  84. package/dist/rename-model.js +113 -113
  85. package/dist/rename.js +321 -233
  86. package/dist/rendered-docs.d.ts +4 -2
  87. package/dist/rendered-docs.js +384 -318
  88. package/dist/rendering-context.js +5 -3
  89. package/dist/rendering.js +22 -4
  90. package/dist/review-group.js +42 -39
  91. package/dist/row-host.d.ts +4 -2
  92. package/dist/row-host.js +90 -78
  93. package/dist/selection-ranges.js +21 -17
  94. package/dist/semantic-delta.js +22 -19
  95. package/dist/semantic-model.js +86 -73
  96. package/dist/semantic-tokens.js +201 -162
  97. package/dist/shortcut-keys.js +16 -10
  98. package/dist/signature-model.js +34 -28
  99. package/dist/signature.js +177 -129
  100. package/dist/snippet-field-transforms.js +118 -106
  101. package/dist/snippet-indent.js +48 -43
  102. package/dist/snippet-range-order.js +11 -11
  103. package/dist/snippet-session.js +703 -667
  104. package/dist/snippet-transform-client.js +59 -59
  105. package/dist/snippet-transform-format.js +285 -263
  106. package/dist/snippet-transform-protocol.js +1 -1
  107. package/dist/snippet-transform-queue.js +163 -153
  108. package/dist/snippet-transform-worker.js +42 -35
  109. package/dist/snippet-variable-names.js +1 -36
  110. package/dist/snippet-variables.js +53 -63
  111. package/dist/snippet.js +477 -405
  112. package/dist/split-alignment.js +47 -35
  113. package/dist/split-scroll.js +42 -40
  114. package/dist/symbol-links.js +33 -25
  115. package/dist/symbol-picker-model.js +126 -116
  116. package/dist/symbol-picker.js +98 -59
  117. package/dist/symbols.js +117 -88
  118. package/dist/testing.js +248 -165
  119. package/dist/timers.js +35 -30
  120. package/dist/use-panel-body.js +49 -8
  121. package/dist/use-problem-counts.js +29 -15
  122. package/dist/view-state.js +54 -33
  123. package/dist/word-diff.js +18 -18
  124. package/dist/workspace-edit.js +300 -249
  125. package/package.json +6 -6
  126. package/src/CallHierarchyPanel.tsx +2 -2
  127. package/src/DiffView.tsx +3 -3
  128. package/src/FindReplace.tsx +2 -2
  129. package/src/LanguagePopups.tsx +2 -0
  130. package/src/PeekPanel.tsx +3 -3
  131. package/src/RenderedDocs.tsx +9 -10
  132. package/src/SymbolPicker.tsx +2 -2
  133. package/src/diff-overlays.tsx +6 -4
  134. package/src/locales/en.json +38 -38
  135. package/src/locales/nl.json +41 -41
  136. package/src/rendered-docs.ts +14 -3
  137. package/src/row-host.ts +14 -3
@@ -6,13 +6,13 @@
6
6
  /* The prefix of an address that stands for a name a `{@link}` points at, which the view resolves instead of following. */
7
7
  export const DOC_LINK_PREFIX = '#doc-link:';
8
8
  const ALIASES = {
9
- returns: 'return',
10
- exception: 'throws',
11
- arg: 'param',
12
- argument: 'param',
13
- prop: 'property',
14
- yields: 'yield',
15
- link: 'see'
9
+ returns: 'return',
10
+ exception: 'throws',
11
+ arg: 'param',
12
+ argument: 'param',
13
+ prop: 'property',
14
+ yields: 'yield',
15
+ link: 'see'
16
16
  };
17
17
  /* Tags that carry a type before their subject. */
18
18
  const TYPED = new Set(['param', 'property', 'return', 'throws', 'var', 'type', 'yield', 'template', 'this']);
@@ -25,130 +25,167 @@ const DANGEROUS = /<(script|style|iframe|object|embed|svg|math|form)\b[\s\S]*?<\
25
25
  const SAFE_SCHEME = /^(https?:|mailto:)/i;
26
26
  /* Reads a type that starts the text: in braces as JSDoc writes it, or as one word with its brackets as PHPDoc does. */
27
27
  function takeType(text, braces) {
28
- const trimmed = text.trimStart();
29
- if (trimmed.startsWith('{')) {
30
- let depth = 0;
31
- for (let i = 0; i < trimmed.length; i++) {
32
- depth += trimmed[i] === '{' ? 1 : trimmed[i] === '}' ? -1 : 0;
33
- if (depth === 0) {
34
- return { type: trimmed.slice(1, i).trim(), rest: trimmed.slice(i + 1).trimStart() };
35
- }
36
- }
37
- return { type: null, rest: trimmed };
38
- }
39
- if (braces || /^[$&.]/.test(trimmed)) {
40
- return { type: null, rest: trimmed };
41
- }
28
+ const trimmed = text.trimStart();
29
+ if (trimmed.startsWith('{')) {
42
30
  let depth = 0;
43
- let end = 0;
44
- for (; end < trimmed.length; end++) {
45
- const character = trimmed[end];
46
- if ('<([{'.includes(character)) {
47
- depth++;
48
- }
49
- else if ('>)]}'.includes(character)) {
50
- depth--;
51
- }
52
- else if (/\s/.test(character) && depth <= 0) {
53
- break;
54
- }
31
+ for (let i = 0; i < trimmed.length; i++) {
32
+ depth += trimmed[i] === '{' ? 1 : trimmed[i] === '}' ? -1 : 0;
33
+ if (depth === 0) {
34
+ return {
35
+ type: trimmed.slice(1, i).trim(),
36
+ rest: trimmed.slice(i + 1).trimStart()
37
+ };
38
+ }
39
+ }
40
+ return {
41
+ type: null,
42
+ rest: trimmed
43
+ };
44
+ }
45
+ if (braces || /^[$&.]/.test(trimmed)) {
46
+ return {
47
+ type: null,
48
+ rest: trimmed
49
+ };
50
+ }
51
+ let depth = 0;
52
+ let end = 0;
53
+ for (; end < trimmed.length; end++) {
54
+ const character = trimmed[end];
55
+ if ('<([{'.includes(character)) {
56
+ depth++;
57
+ } else if ('>)]}'.includes(character)) {
58
+ depth--;
59
+ } else if (/\s/.test(character) && depth <= 0) {
60
+ break;
55
61
  }
56
- return { type: end === 0 ? null : trimmed.slice(0, end), rest: trimmed.slice(end).trimStart() };
62
+ }
63
+ return {
64
+ type: end === 0 ? null : trimmed.slice(0, end),
65
+ rest: trimmed.slice(end).trimStart()
66
+ };
57
67
  }
58
68
  /* The first word of a tag's text: a name with its sigils and the `[name=default]` form of JSDoc. */
59
69
  function takeSubject(text) {
60
- const optional = /^\[([^\]]+)\]\s*/.exec(text);
61
- if (optional !== null) {
62
- return { subject: optional[1], rest: text.slice(optional[0].length) };
63
- }
64
- const word = /^(?:\.\.\.|&)?[$\w.\\:-]+/.exec(text);
65
- if (word === null) {
66
- return { subject: null, rest: text };
67
- }
68
- return { subject: word[0], rest: text.slice(word[0].length).trimStart() };
70
+ const optional = /^\[([^\]]+)\]\s*/.exec(text);
71
+ if (optional !== null) {
72
+ return {
73
+ subject: optional[1],
74
+ rest: text.slice(optional[0].length)
75
+ };
76
+ }
77
+ const word = /^(?:\.\.\.|&)?[$\w.\\:-]+/.exec(text);
78
+ if (word === null) {
79
+ return {
80
+ subject: null,
81
+ rest: text
82
+ };
83
+ }
84
+ return {
85
+ subject: word[0],
86
+ rest: text.slice(word[0].length).trimStart()
87
+ };
69
88
  }
70
89
  /* A comment's lines cut where a tag starts at the beginning of a line, outside a fenced block. */
71
90
  function splitTags(body) {
72
- const description = [];
73
- const raw = [];
74
- let fenced = false;
75
- for (const line of body.split('\n')) {
76
- if (FENCE.test(line)) {
77
- fenced = !fenced;
78
- }
79
- const start = fenced ? null : TAG_START.exec(line.trimStart());
80
- if (start !== null) {
81
- raw.push({ name: start[1].toLowerCase(), text: start[2] });
82
- }
83
- else if (raw.length > 0) {
84
- raw[raw.length - 1].text += `\n${line}`;
85
- }
86
- else {
87
- description.push(line);
88
- }
91
+ const description = [];
92
+ const raw = [];
93
+ let fenced = false;
94
+ for (const line of body.split('\n')) {
95
+ if (FENCE.test(line)) {
96
+ fenced = !fenced;
97
+ }
98
+ const start = fenced ? null : TAG_START.exec(line.trimStart());
99
+ if (start !== null) {
100
+ raw.push({
101
+ name: start[1].toLowerCase(),
102
+ text: start[2]
103
+ });
104
+ } else if (raw.length > 0) {
105
+ raw[raw.length - 1].text += `\n${line}`;
106
+ } else {
107
+ description.push(line);
89
108
  }
90
- return { description: description.join('\n').trim(), raw };
109
+ }
110
+ return {
111
+ description: description.join('\n').trim(),
112
+ raw
113
+ };
91
114
  }
92
115
  function entryOf(name, text, php) {
93
- let rest = text.trim();
94
- let type = null;
95
- let subject = null;
96
- if (TYPED.has(name)) {
97
- const taken = name === 'return' && rest.startsWith('$this') ? { type: '$this', rest: rest.slice(5).trimStart() } : takeType(rest, !php);
98
- type = taken.type;
99
- rest = taken.rest;
100
- // A type that stands alone is the whole text of a tag that has no subject.
101
- if (SUBJECT_FIRST.has(name) || (name === 'throws' && type === null)) {
102
- const word = takeSubject(rest);
103
- subject = word.subject;
104
- rest = word.rest;
105
- }
106
- }
107
- else if (name === 'see' && !rest.startsWith('{')) {
108
- const target = /^\S+/.exec(rest);
109
- subject = target === null ? null : target[0];
110
- rest = target === null ? rest : rest.slice(target[0].length).trimStart();
111
- }
112
- if (name === 'throws' && type === null && subject !== null) {
113
- type = subject;
114
- subject = null;
116
+ let rest = text.trim();
117
+ let type = null;
118
+ let subject = null;
119
+ if (TYPED.has(name)) {
120
+ const taken = name === 'return' && rest.startsWith('$this') ? {
121
+ type: '$this',
122
+ rest: rest.slice(5).trimStart()
123
+ } : takeType(rest, !php);
124
+ type = taken.type;
125
+ rest = taken.rest;
126
+ // A type that stands alone is the whole text of a tag that has no subject.
127
+ if (SUBJECT_FIRST.has(name) || name === 'throws' && type === null) {
128
+ const word = takeSubject(rest);
129
+ subject = word.subject;
130
+ rest = word.rest;
115
131
  }
116
- return { name, type, subject, text: rest.replace(DASH, '').trim() };
132
+ } else if (name === 'see' && !rest.startsWith('{')) {
133
+ const target = /^\S+/.exec(rest);
134
+ subject = target === null ? null : target[0];
135
+ rest = target === null ? rest : rest.slice(target[0].length).trimStart();
136
+ }
137
+ if (name === 'throws' && type === null && subject !== null) {
138
+ type = subject;
139
+ subject = null;
140
+ }
141
+ return {
142
+ name,
143
+ type,
144
+ subject,
145
+ text: rest.replace(DASH, '').trim()
146
+ };
117
147
  }
118
148
  /* Text outside backtick code, which is the only text HTML and inline tags are read in. */
119
149
  function outsideCode(markdown, convert) {
120
- return markdown
121
- .split(/(```[\s\S]*?```|~~~[\s\S]*?~~~|`[^`\n]*`)/)
122
- .map((part, index) => (index % 2 === 1 ? part : convert(part)))
123
- .join('');
150
+ return markdown.split(/(```[\s\S]*?```|~~~[\s\S]*?~~~|`[^`\n]*`)/).map((part, index) => index % 2 === 1 ? part : convert(part)).join('');
124
151
  }
125
- const ENTITIES = { lt: '<', gt: '>', amp: '&', quot: '"', apos: "'", nbsp: ' ', ndash: '-', mdash: '-', hellip: '...' };
152
+ const ENTITIES = {
153
+ lt: '<',
154
+ gt: '>',
155
+ amp: '&',
156
+ quot: '"',
157
+ apos: "'",
158
+ nbsp: ' ',
159
+ ndash: '-',
160
+ mdash: '-',
161
+ hellip: '...'
162
+ };
126
163
  /* Named entities of the small set docblocks use, and numeric ones; anything else stays as written. */
127
164
  export function decodeEntities(text) {
128
- return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (match, code) => {
129
- if (code[0] === '#') {
130
- const point = code[1].toLowerCase() === 'x' ? Number.parseInt(code.slice(2), 16) : Number.parseInt(code.slice(1), 10);
131
- return Number.isFinite(point) && point > 0 && point <= 0x10ffff ? String.fromCodePoint(point) : match;
132
- }
133
- return ENTITIES[code.toLowerCase()] ?? match;
134
- });
165
+ return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (match, code) => {
166
+ if (code[0] === '#') {
167
+ const point = code[1].toLowerCase() === 'x' ? Number.parseInt(code.slice(2), 16) : Number.parseInt(code.slice(1), 10);
168
+ return Number.isFinite(point) && point > 0 && point <= 0x10ffff ? String.fromCodePoint(point) : match;
169
+ }
170
+ return ENTITIES[code.toLowerCase()] ?? match;
171
+ });
135
172
  }
136
173
  function dedent(text) {
137
- const lines = text.replace(/^\n+|\s+$/g, '').split('\n');
138
- const indents = lines.filter((line) => line.trim() !== '').map((line) => line.length - line.trimStart().length);
139
- const common = indents.length === 0 ? 0 : Math.min(...indents);
140
- return lines.map((line) => line.slice(common)).join('\n');
174
+ const lines = text.replace(/^\n+|\s+$/g, '').split('\n');
175
+ const indents = lines.filter(line => line.trim() !== '').map(line => line.length - line.trimStart().length);
176
+ const common = indents.length === 0 ? 0 : Math.min(...indents);
177
+ return lines.map(line => line.slice(common)).join('\n');
141
178
  }
142
179
  function fenced(body) {
143
- return `\n\n\`\`\`\n${dedent(decodeEntities(stripTags(body)))}\n\`\`\`\n\n`;
180
+ return `\n\n\`\`\`\n${dedent(decodeEntities(stripTags(body)))}\n\`\`\`\n\n`;
144
181
  }
145
182
  function stripTags(html) {
146
- return html.replace(/<\/?[a-zA-Z][^>]*>/g, '');
183
+ return html.replace(/<\/?[a-zA-Z][^>]*>/g, '');
147
184
  }
148
185
  /* The address of a link when it is one a reader may follow; null for everything else, such as a script address. */
149
186
  export function safeAddress(address) {
150
- const trimmed = decodeEntities(address.trim());
151
- return SAFE_SCHEME.test(trimmed) || trimmed.startsWith(DOC_LINK_PREFIX) ? trimmed.replace(/\s/g, '%20').replace(/\(/g, '%28').replace(/\)/g, '%29') : null;
187
+ const trimmed = decodeEntities(address.trim());
188
+ return SAFE_SCHEME.test(trimmed) || trimmed.startsWith(DOC_LINK_PREFIX) ? trimmed.replace(/\s/g, '%20').replace(/\(/g, '%28').replace(/\)/g, '%29') : null;
152
189
  }
153
190
  /*
154
191
  * The HTML subset docblocks use, as Markdown: paragraphs and breaks, bold, italic, code, preformatted
@@ -157,135 +194,147 @@ export function safeAddress(address) {
157
194
  * as the text it was, which the view draws escaped.
158
195
  */
159
196
  export function htmlToMarkdown(source) {
160
- if (!source.includes('<') && !source.includes('&')) {
161
- return source;
162
- }
163
- const lists = [];
164
- const counters = [];
165
- let anchor = null;
166
- const blocks = source
167
- .replace(/<pre\b[^>]*>([\s\S]*?)<\/pre\s*>/gi, (_match, body) => fenced(body))
168
- .replace(/<code>([^<]*\n[^<]*)<\/code>/gi, (_match, body) => fenced(body));
169
- const converted = outsideCode(blocks, (text) => text
170
- .replace(DANGEROUS, '')
171
- .replace(/<(\/?)(p|br|b|strong|i|em|u|code|tt|kbd|samp|var|a|ul|ol|li|h[1-6]|blockquote|hr|div|span|dl|dt|dd|sup|sub|small|center)\b([^>]*?)(\/?)>/gi, (_match, close, tag, attributes) => {
172
- const closing = close === '/';
173
- switch (tag.toLowerCase()) {
174
- case 'p':
175
- case 'div':
176
- case 'dl':
177
- return '\n\n';
178
- case 'br':
179
- return '\n';
180
- case 'hr':
181
- return '\n\n---\n\n';
182
- case 'b':
183
- case 'strong':
184
- return '**';
185
- case 'i':
186
- case 'em':
187
- case 'var':
188
- return '*';
189
- case 'code':
190
- case 'tt':
191
- case 'kbd':
192
- case 'samp':
193
- return '`';
194
- case 'a': {
195
- if (closing) {
196
- const target = anchor;
197
- anchor = null;
198
- return target === null ? '' : `](${target})`;
199
- }
200
- const href = /\bhref\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(attributes);
201
- anchor = href === null ? null : safeAddress(href[1] ?? href[2] ?? href[3] ?? '');
202
- return anchor === null ? '' : '[';
203
- }
204
- case 'ul':
205
- case 'ol':
206
- if (closing) {
207
- lists.pop();
208
- counters.pop();
209
- }
210
- else {
211
- lists.push(tag.toLowerCase());
212
- counters.push(0);
213
- }
214
- return '\n';
215
- case 'li': {
216
- if (closing) {
217
- return '';
218
- }
219
- const depth = Math.max(0, lists.length - 1);
220
- const marker = lists.at(-1) === 'ol' ? `${(counters[depth] = (counters[depth] ?? 0) + 1)}.` : '-';
221
- return `\n${' '.repeat(depth)}${marker} `;
222
- }
223
- case 'blockquote':
224
- return closing ? '\n\n' : '\n\n> ';
225
- case 'dt':
226
- return closing ? '**\n\n' : '\n\n**';
227
- case 'dd':
228
- return '\n\n';
229
- default:
230
- if (/^h[1-6]$/i.test(tag)) {
231
- return closing ? '\n\n' : `\n\n${'#'.repeat(Number(tag[1]))} `;
232
- }
233
- return '';
197
+ if (!source.includes('<') && !source.includes('&')) {
198
+ return source;
199
+ }
200
+ const lists = [];
201
+ const counters = [];
202
+ let anchor = null;
203
+ const blocks = source.replace(/<pre\b[^>]*>([\s\S]*?)<\/pre\s*>/gi, (_match, body) => fenced(body)).replace(/<code>([^<]*\n[^<]*)<\/code>/gi, (_match, body) => fenced(body));
204
+ const converted = outsideCode(blocks, text => text.replace(DANGEROUS, '').replace(/<(\/?)(p|br|b|strong|i|em|u|code|tt|kbd|samp|var|a|ul|ol|li|h[1-6]|blockquote|hr|div|span|dl|dt|dd|sup|sub|small|center)\b([^>]*?)(\/?)>/gi, (_match, close, tag, attributes) => {
205
+ const closing = close === '/';
206
+ switch (tag.toLowerCase()) {
207
+ case 'p':
208
+ case 'div':
209
+ case 'dl':
210
+ return '\n\n';
211
+ case 'br':
212
+ return '\n';
213
+ case 'hr':
214
+ return '\n\n---\n\n';
215
+ case 'b':
216
+ case 'strong':
217
+ return '**';
218
+ case 'i':
219
+ case 'em':
220
+ case 'var':
221
+ return '*';
222
+ case 'code':
223
+ case 'tt':
224
+ case 'kbd':
225
+ case 'samp':
226
+ return '`';
227
+ case 'a':
228
+ {
229
+ if (closing) {
230
+ const target = anchor;
231
+ anchor = null;
232
+ return target === null ? '' : `](${target})`;
233
+ }
234
+ const href = /\bhref\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(attributes);
235
+ anchor = href === null ? null : safeAddress(href[1] ?? href[2] ?? href[3] ?? '');
236
+ return anchor === null ? '' : '[';
237
+ }
238
+ case 'ul':
239
+ case 'ol':
240
+ if (closing) {
241
+ lists.pop();
242
+ counters.pop();
243
+ } else {
244
+ lists.push(tag.toLowerCase());
245
+ counters.push(0);
246
+ }
247
+ return '\n';
248
+ case 'li':
249
+ {
250
+ if (closing) {
251
+ return '';
252
+ }
253
+ const depth = Math.max(0, lists.length - 1);
254
+ const marker = lists.at(-1) === 'ol' ? `${counters[depth] = (counters[depth] ?? 0) + 1}.` : '-';
255
+ return `\n${' '.repeat(depth)}${marker} `;
234
256
  }
235
- }));
236
- // Entities last, so `&lt;b&gt;` stays the text it spells and is not read as a tag above.
237
- return outsideCode(converted, decodeEntities).replace(/\n{3,}/g, '\n\n');
257
+ case 'blockquote':
258
+ return closing ? '\n\n' : '\n\n> ';
259
+ case 'dt':
260
+ return closing ? '**\n\n' : '\n\n**';
261
+ case 'dd':
262
+ return '\n\n';
263
+ default:
264
+ if (/^h[1-6]$/i.test(tag)) {
265
+ return closing ? '\n\n' : `\n\n${'#'.repeat(Number(tag[1]))} `;
266
+ }
267
+ return '';
268
+ }
269
+ }));
270
+ // Entities last, so `&lt;b&gt;` stays the text it spells and is not read as a tag above.
271
+ return outsideCode(converted, decodeEntities).replace(/\n{3,}/g, '\n\n');
238
272
  }
239
273
  /*
240
274
  * Inline `{@link Name}`, `{@link Name label}`, `{@link Name|label}`, the `linkcode` and `linkplain` forms, `{@see Name}`
241
275
  * and `{@code text}`. A name becomes a link the view resolves; an address becomes a link to it.
242
276
  */
243
277
  export function inlineTagsToMarkdown(markdown) {
244
- return outsideCode(markdown, (text) => text
245
- .replace(/\{@(?:link|linkcode|linkplain|see)\s+([^}\s|]+)(?:\s*[|\s]\s*([^}]+?))?\s*\}/g, (_match, target, label) => {
246
- const address = /^https?:\/\//i.test(target) ? safeAddress(target) : `${DOC_LINK_PREFIX}${encodeURIComponent(target)}`;
247
- const shown = label ?? target;
248
- return address === null ? `\`${shown}\`` : `[${label === undefined ? `\`${shown}\`` : shown}](${address})`;
249
- })
250
- .replace(/\{@code\s+([^}]*)\}/g, (_match, code) => `\`${code.trim()}\``)
251
- .replace(/\{@inheritDoc\}/g, ''));
278
+ return outsideCode(markdown, text => text.replace(/\{@(?:link|linkcode|linkplain|see)\s+([^}\s|]+)(?:\s*[|\s]\s*([^}]+?))?\s*\}/g, (_match, target, label) => {
279
+ const address = /^https?:\/\//i.test(target) ? safeAddress(target) : `${DOC_LINK_PREFIX}${encodeURIComponent(target)}`;
280
+ const shown = label ?? target;
281
+ return address === null ? `\`${shown}\`` : `[${label === undefined ? `\`${shown}\`` : shown}](${address})`;
282
+ }).replace(/\{@code\s+([^}]*)\}/g, (_match, code) => `\`${code.trim()}\``).replace(/\{@inheritDoc\}/g, ''));
252
283
  }
253
284
  /* An example's lines without the caption, and without the indentation they share; the text after the tag on its own line is not indented like the rest. */
254
285
  function exampleOf(text) {
255
- const code = text.replace(/^\s*<caption>[\s\S]*?<\/caption>[ \t]*\n?/i, '');
256
- const [head = '', ...tail] = code.split('\n');
257
- return head.trim() === '' ? dedent(code) : [head.trim(), dedent(tail.join('\n'))].join('\n').trim();
286
+ const code = text.replace(/^\s*<caption>[\s\S]*?<\/caption>[ \t]*\n?/i, '');
287
+ const [head = '', ...tail] = code.split('\n');
288
+ return head.trim() === '' ? dedent(code) : [head.trim(), dedent(tail.join('\n'))].join('\n').trim();
258
289
  }
259
290
  function proseOf(text) {
260
- return inlineTagsToMarkdown(htmlToMarkdown(text)).trim();
291
+ return inlineTagsToMarkdown(htmlToMarkdown(text)).trim();
261
292
  }
262
293
  /* Reads the body of a documentation comment, as `findDocComments` returns it. `php` lets a type stand without braces. */
263
294
  export function parseDocComment(body, options = {}) {
264
- const php = options.php === true;
265
- const { description, raw } = splitTags(body);
266
- const tags = raw.map(({ name, text }) => {
267
- const bare = name.replace(/^(?:psalm|phpstan)-/, '');
268
- const canonical = ALIASES[bare] ?? bare;
269
- const entry = entryOf(canonical, text, php);
270
- // An example is code: it keeps its lines and gets no prose treatment, only its caption removed.
271
- if (canonical === 'example') {
272
- return { ...entry, text: exampleOf(text) };
273
- }
274
- return { ...entry, text: proseOf(entry.text) };
275
- });
276
- return { description: proseOf(description), tags };
295
+ const php = options.php === true;
296
+ const {
297
+ description,
298
+ raw
299
+ } = splitTags(body);
300
+ const tags = raw.map(({
301
+ name,
302
+ text
303
+ }) => {
304
+ const bare = name.replace(/^(?:psalm|phpstan)-/, '');
305
+ const canonical = ALIASES[bare] ?? bare;
306
+ const entry = entryOf(canonical, text, php);
307
+ // An example is code: it keeps its lines and gets no prose treatment, only its caption removed.
308
+ if (canonical === 'example') {
309
+ return {
310
+ ...entry,
311
+ text: exampleOf(text)
312
+ };
313
+ }
314
+ return {
315
+ ...entry,
316
+ text: proseOf(entry.text)
317
+ };
318
+ });
319
+ return {
320
+ description: proseOf(description),
321
+ tags
322
+ };
277
323
  }
278
324
  /* Tags in the order a reader looks for them; every other tag follows in the order it was written. */
279
325
  const ORDER = ['deprecated', 'template', 'param', 'property', 'type', 'var', 'return', 'yield', 'throws', 'see', 'example', 'since', 'version', 'author'];
280
326
  /* The tags gathered by name, so a list of parameters is one section, in reading order. */
281
327
  export function sectionsOf(tags) {
282
- const byName = new Map();
283
- for (const tag of tags) {
284
- byName.set(tag.name, [...(byName.get(tag.name) ?? []), tag]);
285
- }
286
- const rank = (name) => {
287
- const index = ORDER.indexOf(name);
288
- return index === -1 ? ORDER.length : index;
289
- };
290
- return [...byName].map(([name, entries]) => ({ name, tags: entries })).sort((left, right) => rank(left.name) - rank(right.name));
291
- }
328
+ const byName = new Map();
329
+ for (const tag of tags) {
330
+ byName.set(tag.name, [...(byName.get(tag.name) ?? []), tag]);
331
+ }
332
+ const rank = name => {
333
+ const index = ORDER.indexOf(name);
334
+ return index === -1 ? ORDER.length : index;
335
+ };
336
+ return [...byName].map(([name, entries]) => ({
337
+ name,
338
+ tags: entries
339
+ })).sort((left, right) => rank(left.name) - rank(right.name));
340
+ }