@adecore/editor-react 0.31.11 → 0.32.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.
- package/dist/AnchoredPopup.js +73 -19
- package/dist/AttributionCard.js +162 -4
- package/dist/BaselineStatus.js +62 -8
- package/dist/CallHierarchyPanel.js +977 -60
- package/dist/ChangeReview.js +76 -25
- package/dist/CodeAuthorsCard.js +314 -14
- package/dist/CompletionPopup.js +490 -83
- package/dist/DiffView.js +548 -181
- package/dist/EditorContextMenu.js +352 -34
- package/dist/EditorView.js +276 -99
- package/dist/FindReplace.js +525 -67
- package/dist/HoverCard.js +378 -38
- package/dist/HoverSections.js +367 -56
- package/dist/LanguagePopups.js +95 -34
- package/dist/Markdown.js +41 -6
- package/dist/PanelResizeHandle.js +199 -66
- package/dist/PathText.js +49 -7
- package/dist/PeekPanel.js +675 -72
- package/dist/PickPopup.js +304 -34
- package/dist/RenameCard.js +679 -85
- package/dist/RenderedDocs.js +329 -58
- package/dist/SignatureCard.js +91 -7
- package/dist/SplitDiffView.js +344 -254
- package/dist/SymbolPicker.js +440 -85
- package/dist/call-hierarchy-model.js +102 -68
- package/dist/call-hierarchy.js +458 -359
- package/dist/code-actions-model.js +88 -76
- package/dist/code-actions.js +372 -283
- package/dist/code-authors.js +64 -55
- package/dist/code-vision-model.js +134 -127
- package/dist/code-vision.js +224 -206
- package/dist/completion-call.js +94 -67
- package/dist/completion-model.js +334 -244
- package/dist/completion.js +840 -649
- package/dist/conflict-model.js +35 -29
- package/dist/context-menu-rows.js +42 -17
- package/dist/context-menu.js +64 -46
- package/dist/definition-link.js +88 -85
- package/dist/diagnostics-model.js +74 -55
- package/dist/diagnostics.js +121 -100
- package/dist/diff-expansion.js +38 -35
- package/dist/diff-overlays.js +144 -22
- package/dist/diff-syntax.js +65 -42
- package/dist/diff-wordmarks.js +26 -23
- package/dist/doc-comment-model.js +265 -216
- package/dist/editor-language.js +177 -152
- package/dist/error-message.js +2 -2
- package/dist/find-query.js +49 -36
- package/dist/folding.js +81 -62
- package/dist/format-on-type.js +86 -67
- package/dist/highlight-layers.js +19 -20
- package/dist/highlights.js +105 -83
- package/dist/history.js +44 -38
- package/dist/holder.js +18 -18
- package/dist/host-types.js +1 -1
- package/dist/hover-content.js +167 -129
- package/dist/hover.js +285 -245
- package/dist/index.js +10 -10
- package/dist/inlay-hints.js +369 -324
- package/dist/key-priority.js +34 -31
- package/dist/language-ids.js +45 -42
- package/dist/line-edits.js +74 -28
- package/dist/line-text.js +18 -3
- package/dist/locales/en.json +38 -38
- package/dist/locales/nl.json +41 -41
- package/dist/models.js +1 -1
- package/dist/navigation-history.js +40 -41
- package/dist/navigation.js +141 -123
- package/dist/panel-size.js +8 -8
- package/dist/patch-decorations.js +236 -160
- package/dist/patch-layout.js +331 -262
- package/dist/paths.js +3 -3
- package/dist/peek-model.js +86 -69
- package/dist/peek.js +254 -192
- package/dist/pick.js +164 -157
- package/dist/popup-focus.js +5 -5
- package/dist/popup-placement.js +36 -21
- package/dist/popups.js +13 -13
- package/dist/project-language.js +228 -211
- package/dist/project-problems.js +81 -69
- package/dist/proposal-model.js +163 -126
- package/dist/recent-choices.js +25 -25
- package/dist/refresher.js +44 -49
- package/dist/rename-model.js +113 -113
- package/dist/rename.js +321 -233
- package/dist/rendered-docs.d.ts +4 -2
- package/dist/rendered-docs.js +384 -318
- package/dist/rendering-context.js +5 -3
- package/dist/rendering.js +22 -4
- package/dist/review-group.js +42 -39
- package/dist/row-host.d.ts +4 -2
- package/dist/row-host.js +90 -78
- package/dist/selection-ranges.js +21 -17
- package/dist/semantic-delta.js +22 -19
- package/dist/semantic-model.js +86 -73
- package/dist/semantic-tokens.js +201 -162
- package/dist/shortcut-keys.js +16 -10
- package/dist/signature-model.js +34 -28
- package/dist/signature.js +177 -129
- package/dist/snippet-field-transforms.js +118 -106
- package/dist/snippet-indent.js +48 -43
- package/dist/snippet-range-order.js +11 -11
- package/dist/snippet-session.js +703 -667
- package/dist/snippet-transform-client.js +59 -59
- package/dist/snippet-transform-format.js +285 -263
- package/dist/snippet-transform-protocol.js +1 -1
- package/dist/snippet-transform-queue.js +163 -153
- package/dist/snippet-transform-worker.js +42 -35
- package/dist/snippet-variable-names.js +1 -36
- package/dist/snippet-variables.js +53 -63
- package/dist/snippet.js +477 -405
- package/dist/split-alignment.js +47 -35
- package/dist/split-scroll.js +42 -40
- package/dist/symbol-links.js +33 -25
- package/dist/symbol-picker-model.js +126 -116
- package/dist/symbol-picker.js +98 -59
- package/dist/symbols.js +117 -88
- package/dist/testing.js +248 -165
- package/dist/timers.js +35 -30
- package/dist/use-panel-body.js +49 -8
- package/dist/use-problem-counts.js +29 -15
- package/dist/view-state.js +54 -33
- package/dist/word-diff.js +18 -18
- package/dist/workspace-edit.js +300 -249
- package/package.json +6 -6
- package/src/CallHierarchyPanel.tsx +2 -2
- package/src/DiffView.tsx +3 -3
- package/src/FindReplace.tsx +2 -2
- package/src/LanguagePopups.tsx +2 -0
- package/src/PeekPanel.tsx +3 -3
- package/src/RenderedDocs.tsx +9 -10
- package/src/SymbolPicker.tsx +2 -2
- package/src/diff-overlays.tsx +6 -4
- package/src/locales/en.json +38 -38
- package/src/locales/nl.json +41 -41
- package/src/rendered-docs.ts +14 -3
- package/src/row-host.ts +14 -3
package/dist/hover-content.js
CHANGED
|
@@ -5,61 +5,75 @@ const FENCE = /^```([\w+#.-]*)[^\n]*\n([\s\S]*?)\n```[ \t]*(?:\n|$)/;
|
|
|
5
5
|
* in the documentation, stays where it is.
|
|
6
6
|
*/
|
|
7
7
|
export function splitSignatures(markdown) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
const signatures = [];
|
|
9
|
+
let rest = markdown.replace(/^\s+/, '');
|
|
10
|
+
for (let match = FENCE.exec(rest); match !== null; match = FENCE.exec(rest)) {
|
|
11
|
+
signatures.push({
|
|
12
|
+
language: match[1] || 'text',
|
|
13
|
+
code: match[2]
|
|
14
|
+
});
|
|
15
|
+
rest = rest.slice(match[0].length).replace(/^\s+/, '');
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
signatures,
|
|
19
|
+
markdown: rest.replace(/^(?:-{3,}|\*{3,})\s*(?:\n|$)/, '').trim()
|
|
20
|
+
};
|
|
15
21
|
}
|
|
16
22
|
function markedStringText(content) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
if (typeof content !== 'string') {
|
|
24
|
+
return {
|
|
25
|
+
signatures: [{
|
|
26
|
+
language: content.language || 'text',
|
|
27
|
+
code: content.value
|
|
28
|
+
}],
|
|
29
|
+
markdown: ''
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return splitSignatures(content);
|
|
21
33
|
}
|
|
22
34
|
/* The text of a hover, whichever of the three shapes the protocol allows it in. */
|
|
23
35
|
export function hoverTextOf(hover) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
const {
|
|
37
|
+
contents
|
|
38
|
+
} = hover;
|
|
39
|
+
if (Array.isArray(contents)) {
|
|
40
|
+
const parts = contents.map(markedStringText);
|
|
41
|
+
return {
|
|
42
|
+
signatures: parts.flatMap(part => part.signatures),
|
|
43
|
+
markdown: parts.map(part => part.markdown).filter(Boolean).join('\n\n')
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (typeof contents === 'string' || 'language' in contents) {
|
|
47
|
+
return markedStringText(contents);
|
|
48
|
+
}
|
|
49
|
+
return contents.kind === 'plaintext' ? {
|
|
50
|
+
signatures: [],
|
|
51
|
+
markdown: escapeMarkdown(contents.value).trim()
|
|
52
|
+
} : splitSignatures(contents.value);
|
|
39
53
|
}
|
|
40
54
|
/* Plain text as markdown that reads the same, its markdown characters escaped. */
|
|
41
55
|
export function escapeMarkdown(text) {
|
|
42
|
-
|
|
56
|
+
return text.replace(/([\\`*_{}[\]()#+\-.!|<>~])/g, '\\$1');
|
|
43
57
|
}
|
|
44
58
|
export function isEmptyHover(text) {
|
|
45
|
-
|
|
59
|
+
return text.signatures.length === 0 && text.markdown === '';
|
|
46
60
|
}
|
|
47
61
|
function isLink(entry) {
|
|
48
|
-
|
|
62
|
+
return 'targetUri' in entry;
|
|
49
63
|
}
|
|
50
64
|
/* Where a navigation result leads, as plain locations: the part of a link that names the symbol is the one to land on. */
|
|
51
65
|
export function locationsOf(result) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
if (result === null) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
const list = Array.isArray(result) ? result : [result];
|
|
70
|
+
return list.map(entry => isLink(entry) ? {
|
|
71
|
+
uri: entry.targetUri,
|
|
72
|
+
range: entry.targetSelectionRange
|
|
73
|
+
} : entry);
|
|
57
74
|
}
|
|
58
75
|
export function sameRange(left, right) {
|
|
59
|
-
|
|
60
|
-
left.start.character === right.start.character &&
|
|
61
|
-
left.end.line === right.end.line &&
|
|
62
|
-
left.end.character === right.end.character);
|
|
76
|
+
return left.start.line === right.start.line && left.start.character === right.start.character && left.end.line === right.end.line && left.end.character === right.end.character;
|
|
63
77
|
}
|
|
64
78
|
const TAG_LINE = /^(?:_@([\w-]+)_|\*@([\w-]+)\*|@([\w-]+))(?:\s+|$)([\s\S]*)$/;
|
|
65
79
|
/* Tags whose text is a block of its own, which a row of the tag list has no room for. */
|
|
@@ -70,59 +84,72 @@ const BLOCK_TAGS = new Set(['example']);
|
|
|
70
84
|
* `@` stays prose.
|
|
71
85
|
*/
|
|
72
86
|
export function splitDocTags(markdown) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
matches.forEach((match, index) => {
|
|
84
|
-
// The TypeScript server puts a dash between a tag, its parameter and its text.
|
|
85
|
-
const text = match[4].replace(/^[\u2014\u2013-]\s*/, '').replace(/^(`[^`]+`)\s+[\u2014\u2013-]\s+/, '$1 ');
|
|
86
|
-
tags.push({ name: names[index], markdown: text.trim() });
|
|
87
|
-
});
|
|
87
|
+
const tags = [];
|
|
88
|
+
const kept = [];
|
|
89
|
+
for (const paragraph of markdown.split(/\n{2,}/)) {
|
|
90
|
+
const lines = paragraph.split('\n');
|
|
91
|
+
const matches = lines.map(line => TAG_LINE.exec(line.trim()));
|
|
92
|
+
const names = matches.map(match => match === null ? null : match[1] ?? match[2] ?? match[3]);
|
|
93
|
+
if (paragraph.trim() === '' || names.some(name => name === null || BLOCK_TAGS.has(name))) {
|
|
94
|
+
kept.push(paragraph);
|
|
95
|
+
continue;
|
|
88
96
|
}
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
matches.forEach((match, index) => {
|
|
98
|
+
// The TypeScript server puts a dash between a tag, its parameter and its text.
|
|
99
|
+
const text = match[4].replace(/^[\u2014\u2013-]\s*/, '').replace(/^(`[^`]+`)\s+[\u2014\u2013-]\s+/, '$1 ');
|
|
100
|
+
tags.push({
|
|
101
|
+
name: names[index],
|
|
102
|
+
markdown: text.trim()
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// By name, and in the order written within one name, so parameters keep the order of the signature.
|
|
107
|
+
return {
|
|
108
|
+
markdown: kept.join('\n\n').trim(),
|
|
109
|
+
tags: tags.toSorted((left, right) => left.name.localeCompare(right.name))
|
|
110
|
+
};
|
|
91
111
|
}
|
|
92
112
|
/* A CSS server draws the status as a data URL image in front of an italic sentence, and a data URL is not an address the markdown renderer lets through. */
|
|
93
113
|
const BASELINE = /!\[Baseline icon\]\([^)\s]*\)[ \t]*_([^\n]+?)_[ \t]*(?:\n|$)/;
|
|
94
114
|
/* The Baseline status a text carries, which the card draws with a mark of its own, and the text without it. */
|
|
95
115
|
function takeBaseline(markdown) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
116
|
+
const match = BASELINE.exec(markdown);
|
|
117
|
+
if (match === null) {
|
|
118
|
+
return {
|
|
119
|
+
baseline: null,
|
|
120
|
+
rest: markdown
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const text = match[1].trim();
|
|
124
|
+
const level = text.startsWith('Widely') ? 'widely' : text.startsWith('Newly') ? 'newly' : 'limited';
|
|
125
|
+
return {
|
|
126
|
+
baseline: {
|
|
127
|
+
level,
|
|
128
|
+
text
|
|
129
|
+
},
|
|
130
|
+
rest: (markdown.slice(0, match.index) + markdown.slice(match.index + match[0].length)).replace(/\n{3,}/g, '\n\n')
|
|
131
|
+
};
|
|
103
132
|
}
|
|
104
133
|
const RULE = /^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/m;
|
|
105
134
|
const TITLE = /^(?:__(.+)__|\*\*(.+)\*\*)\s*(?:\n|$)/;
|
|
106
135
|
const ANY_FENCE = /```([\w+#.-]*)[^\n]*\n([\s\S]*?)\n```[ \t]*(?:\n|$)/;
|
|
107
136
|
/* The PHP server opens every snippet with a tag the reader does not need to see. */
|
|
108
137
|
function withoutOpenTag(block) {
|
|
109
|
-
|
|
138
|
+
return {
|
|
139
|
+
language: block.language,
|
|
140
|
+
code: block.code.replace(/^<\?php\s*\n/, '')
|
|
141
|
+
};
|
|
110
142
|
}
|
|
111
143
|
const HTML_CODE = /<(code|pre)>([\s\S]*?)<\/\1>/g;
|
|
112
144
|
const INLINE_TAG = /\{@(see|link|linkplain)\s+([^}\s]+)(?:\s+([^}]+?))?\s*\}/g;
|
|
113
145
|
function decodeEntities(text) {
|
|
114
|
-
|
|
115
|
-
.replace(/</g, '<')
|
|
116
|
-
.replace(/>/g, '>')
|
|
117
|
-
.replace(/"/g, '"')
|
|
118
|
-
.replace(/�?39;/g, "'")
|
|
119
|
-
.replace(/&/g, '&');
|
|
146
|
+
return text.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/�?39;/g, "'").replace(/&/g, '&');
|
|
120
147
|
}
|
|
121
148
|
function dedent(text) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
149
|
+
const lines = text.replace(/^\n+|\s+$/g, '').split('\n');
|
|
150
|
+
const indents = lines.filter(line => line.trim() !== '').map(line => line.length - line.trimStart().length);
|
|
151
|
+
const common = indents.length === 0 ? 0 : Math.min(...indents);
|
|
152
|
+
return lines.map(line => line.slice(common)).join('\n');
|
|
126
153
|
}
|
|
127
154
|
/*
|
|
128
155
|
* What a docblock writes for a reader of its source, written for one of a hover: a `<code>` or `<pre>`
|
|
@@ -130,70 +157,81 @@ function dedent(text) {
|
|
|
130
157
|
* the name in code. Servers pass both on as they are, and markdown would show the tags as text.
|
|
131
158
|
*/
|
|
132
159
|
export function docblockMarkdown(markdown, language) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
})
|
|
138
|
-
.replace(INLINE_TAG, (_match, _tag, target, label) => (label === undefined ? `\`${target}\`` : label))
|
|
139
|
-
.replace(/\n{3,}/g, '\n\n')
|
|
140
|
-
.trim();
|
|
160
|
+
return markdown.replace(HTML_CODE, (_match, _tag, body) => {
|
|
161
|
+
const code = decodeEntities(body);
|
|
162
|
+
return code.includes('\n') ? `\n\n\`\`\`${language}\n${dedent(code)}\n\`\`\`\n\n` : `\`${code.trim()}\``;
|
|
163
|
+
}).replace(INLINE_TAG, (_match, _tag, target, label) => label === undefined ? `\`${target}\`` : label).replace(/\n{3,}/g, '\n\n').trim();
|
|
141
164
|
}
|
|
142
165
|
export function markdownParts(markdown) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
if (rest.trim() !== '') {
|
|
154
|
-
parts.push({ kind: 'prose', text: rest.trim() });
|
|
166
|
+
const parts = [];
|
|
167
|
+
let rest = markdown;
|
|
168
|
+
for (let match = ANY_FENCE.exec(rest); match !== null; match = ANY_FENCE.exec(rest)) {
|
|
169
|
+
const prose = rest.slice(0, match.index).trim();
|
|
170
|
+
if (prose !== '') {
|
|
171
|
+
parts.push({
|
|
172
|
+
kind: 'prose',
|
|
173
|
+
text: prose
|
|
174
|
+
});
|
|
155
175
|
}
|
|
156
|
-
|
|
176
|
+
parts.push({
|
|
177
|
+
kind: 'code',
|
|
178
|
+
block: {
|
|
179
|
+
language: match[1] || 'text',
|
|
180
|
+
code: match[2]
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
rest = rest.slice(match.index + match[0].length);
|
|
184
|
+
}
|
|
185
|
+
if (rest.trim() !== '') {
|
|
186
|
+
parts.push({
|
|
187
|
+
kind: 'prose',
|
|
188
|
+
text: rest.trim()
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return parts;
|
|
157
192
|
}
|
|
158
193
|
/* The title is drawn as text, so the escapes that kept it from reading as markdown come off: `A\\B::\_\_construct` is `A\B::__construct`. */
|
|
159
194
|
function unescapeMarkdown(text) {
|
|
160
|
-
|
|
195
|
+
return text.replace(/\\([\\`*_{}[\]()#+\-.!|<>~])/g, '$1');
|
|
161
196
|
}
|
|
162
197
|
function sectionOf(chunk, leading) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
198
|
+
let rest = chunk.trim();
|
|
199
|
+
const title = TITLE.exec(rest);
|
|
200
|
+
if (title !== null) {
|
|
201
|
+
rest = rest.slice(title[0].length).trim();
|
|
202
|
+
}
|
|
203
|
+
const signatures = [...leading];
|
|
204
|
+
// A section's own snippet is its first block; one further down after the prose, such as an example, stays prose.
|
|
205
|
+
const fence = ANY_FENCE.exec(rest);
|
|
206
|
+
if (fence !== null && !/@example/.test(rest.slice(0, fence.index))) {
|
|
207
|
+
signatures.push({
|
|
208
|
+
language: fence[1] || 'text',
|
|
209
|
+
code: fence[2]
|
|
210
|
+
});
|
|
211
|
+
rest = (rest.slice(0, fence.index) + rest.slice(fence.index + fence[0].length)).trim();
|
|
212
|
+
}
|
|
213
|
+
const taken = takeBaseline(rest);
|
|
214
|
+
const doc = splitDocTags(docblockMarkdown(taken.rest, signatures[0]?.language ?? 'text'));
|
|
215
|
+
return {
|
|
216
|
+
title: title === null ? null : unescapeMarkdown((title[1] ?? title[2]).trim()),
|
|
217
|
+
signatures: signatures.map(withoutOpenTag),
|
|
218
|
+
markdown: doc.markdown,
|
|
219
|
+
tags: doc.tags,
|
|
220
|
+
baseline: taken.baseline
|
|
221
|
+
};
|
|
184
222
|
}
|
|
185
223
|
/* The symbols of a hover apart, split where a server draws a rule between them, each described once. */
|
|
186
224
|
export function hoverSectionsOf(text) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
225
|
+
const chunks = text.markdown === '' ? [''] : text.markdown.split(RULE);
|
|
226
|
+
const sections = chunks.map((chunk, index) => sectionOf(chunk, index === 0 ? text.signatures : []));
|
|
227
|
+
const seen = new Set();
|
|
228
|
+
return sections.filter(section => {
|
|
229
|
+
const empty = section.title === null && section.signatures.length === 0 && section.markdown === '' && section.tags.length === 0 && section.baseline === null;
|
|
230
|
+
const key = JSON.stringify([section.title, section.signatures, section.markdown, section.baseline]);
|
|
231
|
+
if (empty || seen.has(key)) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
seen.add(key);
|
|
235
|
+
return true;
|
|
236
|
+
});
|
|
237
|
+
}
|