Skip to content

Commit 6367d96

Browse files
committed
fix(docs): improve xml docs hover display and version bump
- Remove unnecessary separator when only XML docs are present - Ignore common XML tags already covered by Dot Rush - Change section headers from ## to ### for better hierarchy - Update version to 1.0.3 and add changelog entry
1 parent 3861fab commit 6367d96

File tree

6 files changed

+84
-52
lines changed

6 files changed

+84
-52
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## [1.0.3] - 2025-07-07
4+
5+
### 🔧 Improvements & Bug Fixes
6+
7+
### Fixed
8+
- 🛠️ **Documentation Hover** - Fixed unnecessary separator display in hover tooltips when only XML documentation is present without documentation links.
9+
- 🛠️ **Documentation Hover** - Now ignoring summary, returns, param, and exception tags from XML docs in hover display because Dot Rush already covered these.
10+
311
## [1.0.2] - 2025-07-05
412

513
### 🚀 Features

docs/ToDo.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
- [x] 28. For links for official Unity site(not packages), we should specify the Unity version of the project there, eg. "https://docs.unity3d.com/2020.3/Documentation/ScriptReference/Debug.html"
2929
- [x] 29. Add a search bar for Unity Console to filter logs
3030
- [x] 30. The line break in xml docs are not proper in output markdown(they are still in the same line)
31-
- [ ] 31. Dot Rush just is about to release a new version that shows XML docs, try it out and determine should we remove our xml docs feature?
31+
- [x] 31. Dot Rush just is about to release a new version that shows XML docs, try it out and determine should we remove our xml docs feature?
3232
- [x] 32. Should we add analyzers of Dot Rush to Unity pacakge's detection?
3333
- [x] 33. Debugger, there seems to be too many threads, that's unrelated to the user shown.
3434
- [x] 34. ~~Now that we do include analyzers in our package, but it is not working as expected, roslyn from Dot Rush doesn't seem to use it. Should I report an issue?~~

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Unity Code Pro",
44
"icon": "assets/icon.png",
55
"description": "Unity IDE right inside of VS Code!",
6-
"version": "1.0.2",
6+
"version": "1.0.3",
77
"publisher": "hackerzhuli",
88
"repository": {
99
"type": "git",

src/csharpDocHoverProvider.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -625,22 +625,27 @@ export class CSharpDocHoverProvider implements vscode.HoverProvider {
625625
*/
626626
private createHoverWithDocLink(symbolInfo: SymbolInfo, docLinkInfo?: DocLinkInfo): vscode.Hover {
627627
const hoverContent = new vscode.MarkdownString(); // Show XML documentation if available (at the top)
628-
if (symbolInfo.xmlDocs && symbolInfo.xmlDocs.trim().length > 0) {
628+
const hasXmlDocs = symbolInfo.xmlDocs && symbolInfo.xmlDocs.trim().length > 0;
629+
if (hasXmlDocs) {
629630
// console.log(`abc Adding XML docs for symbol: ${symbolInfo.name}, docs is: ${symbolInfo.xmlDocs}`);
630631
// console.log(`abc XML docs length: ${symbolInfo.xmlDocs.length}, trimmed length: ${symbolInfo.xmlDocs.trim().length}`);
631632

632633
// Convert XML docs to Markdown format
633-
const markdownDocs = xmlToMarkdown(symbolInfo.xmlDocs);
634+
const markdownDocs = xmlToMarkdown(symbolInfo.xmlDocs!, ["summary", "returns", "param", "exception"]);
634635
// console.log(`abc Converted to markdown: ${markdownDocs}`);
635-
636-
hoverContent.appendMarkdown(markdownDocs);
637-
hoverContent.appendMarkdown('\n\n---\n\n'); // Add a separator
636+
if(markdownDocs){
637+
hoverContent.appendMarkdown(markdownDocs);
638+
}
638639
} else {
639640
// console.log(`abc No XML docs found for symbol: ${symbolInfo.name}, xmlDocs value:`, symbolInfo.xmlDocs);
640641
}
641642

642643
// Only show documentation link information if available
643644
if (docLinkInfo) {
645+
// Add separator only if we have both XML docs and doc links
646+
if (hasXmlDocs) {
647+
hoverContent.appendMarkdown('\n\n---\n\n');
648+
}
644649
// Add package information if available
645650
if (docLinkInfo.packageInfo) {
646651
let embedded = '';

src/test/suite/xmlToMarkdown.test.ts

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Every class and member should have a one sentence
1515
summary describing its purpose.
1616
</summary>`;
1717

18-
const expected = `## Summary
18+
const expected = `### Summary
1919
2020
Every class and member should have a one sentence
2121
summary describing its purpose.`;
@@ -76,8 +76,8 @@ The right operand of the addition.
7676
</param>`;
7777

7878
const result = xmlToMarkdown(input);
79-
// Parameters should be grouped into a "## Parameters" section with unordered list
80-
assert.ok(result.includes('## Parameters'));
79+
// Parameters should be grouped into a "### Parameters" section with unordered list
80+
assert.ok(result.includes('### Parameters'));
8181
assert.ok(result.includes('- **`left`**: The left operand of the addition.'));
8282
assert.ok(result.includes('- **`right`**: The right operand of the addition.'));
8383
});
@@ -87,7 +87,7 @@ The right operand of the addition.
8787
The sum of two integers.
8888
</returns>`;
8989

90-
const expected = `## Return Value\n\nThe sum of two integers.`;
90+
const expected = `### Return Value\n\nThe sum of two integers.`;
9191
assert.strictEqual(xmlToMarkdown(input), expected);
9292
});
9393

@@ -102,16 +102,16 @@ if (c > 10)
102102
</code>
103103
</example>`;
104104
const result = xmlToMarkdown(input);
105-
assert.ok(result.includes('## Example'));
105+
assert.ok(result.includes('### Example'));
106106
assert.ok(result.includes('```'));
107107
assert.ok(result.includes('int c = Math.Add(4, 5);'));
108108
}); it('should convert exception documentation', () => {
109109
const input = `<exception cref="System.OverflowException">
110110
Thrown when one parameter is greater than MaxValue and the other is greater than 0.
111111
</exception>`;
112112
const result = xmlToMarkdown(input);
113-
// Single exception now uses "## Exceptions" with list format
114-
assert.ok(result.includes('## Exceptions'));
113+
// Single exception now uses "### Exceptions" with list format
114+
assert.ok(result.includes('### Exceptions'));
115115
assert.ok(result.includes('- **`System.OverflowException`**: Thrown when one parameter is greater than MaxValue'));
116116
});
117117

@@ -121,7 +121,7 @@ The <c>Label</c> property represents a label
121121
for this instance.
122122
</value>`;
123123
const result = xmlToMarkdown(input);
124-
assert.ok(result.includes('## Value'));
124+
assert.ok(result.includes('### Value'));
125125
assert.ok(result.includes('The `Label` property represents a label'));
126126
});
127127

@@ -154,7 +154,7 @@ This is the first line.<br/>This is the second line.<br/>This is the third line.
154154
const result = xmlToMarkdown(input);
155155

156156
// Verify the summary section is created
157-
assert.ok(result.includes('## Summary'));
157+
assert.ok(result.includes('### Summary'));
158158

159159
// Verify that each <br/> tag produces double newlines for proper markdown line breaks
160160
assert.ok(result.includes('This is the first line.\n\nThis is the second line.\n\nThis is the third line.'));
@@ -178,16 +178,16 @@ Thrown when one parameter is
178178
<see cref="Int32.MaxValue">MaxValue</see> and the other is
179179
greater than 0.
180180
</exception>`; const result = xmlToMarkdown(input);
181-
assert.ok(result.includes('## Summary'));
181+
assert.ok(result.includes('### Summary'));
182182
assert.ok(result.includes('Adds two integers and returns the result.'));
183-
// Returns should be "## Return Value" format
184-
assert.ok(result.includes('## Return Value'));
183+
// Returns should be "### Return Value" format
184+
assert.ok(result.includes('### Return Value'));
185185
assert.ok(result.includes('The sum of two integers.'));
186-
// Parameters should be grouped into "## Parameters" section
187-
assert.ok(result.includes('## Parameters'));
186+
// Parameters should be grouped into "### Parameters" section
187+
assert.ok(result.includes('### Parameters'));
188188
assert.ok(result.includes('- **`left`**: The left operand of the addition.')); assert.ok(result.includes('- **`right`**: The right operand of the addition.'));
189-
// Single exception now uses "## Exceptions" with list format
190-
assert.ok(result.includes('## Exceptions'));
189+
// Single exception now uses "### Exceptions" with list format
190+
assert.ok(result.includes('### Exceptions'));
191191
assert.ok(result.includes('- **`System.OverflowException`**: Thrown when one parameter is'));
192192
assert.ok(result.includes('`Int32.MaxValue`'));
193193
});
@@ -224,8 +224,8 @@ This is typically a more detailed description of the class or member
224224
</list>
225225
</remarks>`;
226226

227-
const result = xmlToMarkdown(input); assert.ok(result.includes('## Summary'));
228-
assert.ok(result.includes('## Remarks'));
227+
const result = xmlToMarkdown(input); assert.ok(result.includes('### Summary'));
228+
assert.ok(result.includes('### Remarks'));
229229
assert.ok(result.includes('`ExampleClass`'));
230230
assert.ok(result.includes('- **Summary**: This should provide a one sentence summary'));
231231
assert.ok(result.includes('- **Remarks**: This is typically a more detailed description'));
@@ -245,10 +245,10 @@ This tag will apply to the primary constructor parameter.
245245
<param name="LastName">
246246
This tag will apply to the primary constructor parameter.
247247
</param>`; const result = xmlToMarkdown(input);
248-
assert.ok(result.includes('## Summary'));
249-
assert.ok(result.includes('This is an example of a positional record.')); assert.ok(result.includes('## Remarks'));
250-
// Parameters should be grouped into "## Parameters" section
251-
assert.ok(result.includes('## Parameters'));
248+
assert.ok(result.includes('### Summary'));
249+
assert.ok(result.includes('This is an example of a positional record.')); assert.ok(result.includes('### Remarks'));
250+
// Parameters should be grouped into "### Parameters" section
251+
assert.ok(result.includes('### Parameters'));
252252
assert.ok(result.includes('- **`FirstName`**: This tag will apply to the primary constructor parameter.'));
253253
assert.ok(result.includes('- **`LastName`**: This tag will apply to the primary constructor parameter.'));
254254
});
@@ -261,44 +261,44 @@ This tag will apply to the primary constructor parameter.
261261
<param name="z">Third parameter</param>`;
262262

263263
const result = xmlToMarkdown(input);
264-
assert.ok(result.includes('## Parameters'));
264+
assert.ok(result.includes('### Parameters'));
265265
assert.ok(result.includes('- **`x`**: First parameter'));
266266
assert.ok(result.includes('- **`y`**: Second parameter'));
267267
assert.ok(result.includes('- **`z`**: Third parameter'));
268268
}); it('should use grouped format for single parameter', () => {
269269
const input = `<param name="value">Single parameter</param>`;
270270

271271
const result = xmlToMarkdown(input);
272-
assert.ok(result.includes('## Parameters'));
272+
assert.ok(result.includes('### Parameters'));
273273
assert.ok(result.includes('- **`value`**: Single parameter'));
274-
assert.ok(!result.includes('## Parameter `value`'));
274+
assert.ok(!result.includes('### Parameter `value`'));
275275
});
276276

277277
it('should group multiple consecutive exceptions', () => {
278278
const input = `<exception cref="ArgumentException">First exception</exception>
279279
<exception cref="InvalidOperationException">Second exception</exception>`;
280280

281281
const result = xmlToMarkdown(input);
282-
assert.ok(result.includes('## Exceptions'));
282+
assert.ok(result.includes('### Exceptions'));
283283
assert.ok(result.includes('- **`ArgumentException`**: First exception'));
284284
assert.ok(result.includes('- **`InvalidOperationException`**: Second exception'));
285285
}); it('should use grouped format for single exception', () => {
286286
const input = `<exception cref="ArgumentException">Single exception</exception>`;
287287

288288
const result = xmlToMarkdown(input);
289-
assert.ok(result.includes('## Exceptions'));
289+
assert.ok(result.includes('### Exceptions'));
290290
assert.ok(result.includes('- **`ArgumentException`**: Single exception'));
291-
assert.ok(!result.includes('## Exception `ArgumentException`'));
291+
assert.ok(!result.includes('### Exception `ArgumentException`'));
292292
});it('should not group non-consecutive parameters', () => {
293293
const input = `<param name="x">First parameter</param>
294294
<summary>A summary in between</summary>
295295
<param name="y">Second parameter</param>`;
296296

297297
const result = xmlToMarkdown(input);
298-
// Non-consecutive params each get their own "## Parameters" section
299-
assert.ok(result.includes('## Parameters'));
298+
// Non-consecutive params each get their own "### Parameters" section
299+
assert.ok(result.includes('### Parameters'));
300300
assert.ok(result.includes('- **`x`**: First parameter'));
301-
assert.ok(result.includes('## Summary'));
301+
assert.ok(result.includes('### Summary'));
302302
assert.ok(result.includes('- **`y`**: Second parameter'));
303303
}); it('should handle complex documentation with proper grouping', () => {
304304
const input = `<summary>A complex method</summary>
@@ -309,13 +309,13 @@ This tag will apply to the primary constructor parameter.
309309
<exception cref="Exception2">Second exception</exception>`;
310310

311311
const result = xmlToMarkdown(input);
312-
assert.ok(result.includes('## Summary'));
313-
assert.ok(result.includes('## Parameters'));
312+
assert.ok(result.includes('### Summary'));
313+
assert.ok(result.includes('### Parameters'));
314314
assert.ok(result.includes('- **`first`**: First param'));
315315
assert.ok(result.includes('- **`second`**: Second param'));
316-
assert.ok(result.includes('## Return Value'));
316+
assert.ok(result.includes('### Return Value'));
317317
assert.ok(result.includes('The result'));
318-
assert.ok(result.includes('## Exceptions'));
318+
assert.ok(result.includes('### Exceptions'));
319319
assert.ok(result.includes('- **`Exception1`**: First exception'));
320320
assert.ok(result.includes('- **`Exception2`**: Second exception')); }); it('should format code blocks with class and method properly', () => {
321321
const input = `<example>

src/xmlToMarkdown.ts

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ interface XmlAttributeNode extends XmlNode {
2020
* The input should already have the /// markers removed.
2121
*
2222
* @param xmlDocs The XML documentation string (without /// markers)
23+
* @param ignoredTags Optional array of tag names to ignore at the top level
2324
* @returns The converted Markdown string
2425
*/
25-
export function xmlToMarkdown(xmlDocs: string): string {
26+
export function xmlToMarkdown(xmlDocs: string, ignoredTags: string[] = []): string {
2627
if (!xmlDocs || xmlDocs.trim().length === 0) {
2728
return '';
2829
}
@@ -51,10 +52,28 @@ export function xmlToMarkdown(xmlDocs: string): string {
5152
rootContent = result.root;
5253
}
5354

54-
// If rootContent is an array, apply grouping logic at the top level
55+
// If rootContent is an array, filter out ignored tags and apply grouping logic at the top level
5556
if (Array.isArray(rootContent)) {
56-
const groupedContent = groupConsecutiveElements(rootContent);
57+
// Filter out ignored tags at the top level
58+
const filteredContent = rootContent.filter((item: unknown) => {
59+
if (typeof item === 'object' && item !== null) {
60+
const keys = Object.keys(item as Record<string, unknown>);
61+
const tagName = keys.find(key => key !== ':@');
62+
return !tagName || !ignoredTags.includes(tagName.toLowerCase());
63+
}
64+
return true;
65+
});
66+
const groupedContent = groupConsecutiveElements(filteredContent);
5767
rootContent = groupedContent;
68+
} else if (typeof rootContent === 'object' && rootContent !== null) {
69+
// For single object, filter out ignored tags
70+
const filteredContent: Record<string, unknown> = {};
71+
for (const [key, value] of Object.entries(rootContent as Record<string, unknown>)) {
72+
if (key === ':@' || !ignoredTags.includes(key.toLowerCase())) {
73+
filteredContent[key] = value;
74+
}
75+
}
76+
rootContent = filteredContent;
5877
}
5978
// Process the parsed XML and convert to Markdown
6079
const markdown = processXmlNode(rootContent);
@@ -137,18 +156,18 @@ function processXmlElement(tagName: string, node: unknown): string {
137156
? (node as Record<string, unknown>)[tagName]
138157
: node;
139158
switch (tagName.toLowerCase()) { case 'summary':
140-
return `## Summary\n\n${processContent(content)}\n\n`;
159+
return `### Summary\n\n${processContent(content)}\n\n`;
141160

142161
case 'remarks':
143-
return `## Remarks\n\n${processContent(content)}\n\n`; case 'param': {
162+
return `### Remarks\n\n${processContent(content)}\n\n`; case 'param': {
144163
// Individual param (not grouped) - use old format for multi-line content
145164
const paramName = extractAttribute(node, 'name');
146165
const paramText = processContent(content);
147166
return `**Parameter \`${paramName}\`:** ${paramText}\n\n`;
148167
}
149168

150169
case 'returns':
151-
return `## Return Value\n\n${processContent(content)}\n\n`;
170+
return `### Return Value\n\n${processContent(content)}\n\n`;
152171

153172
case 'exception': {
154173
// Individual exception (not grouped) - use old format for multi-line content
@@ -158,9 +177,9 @@ function processXmlElement(tagName: string, node: unknown): string {
158177
}
159178

160179
case 'value':
161-
return `## Value\n\n${processContent(content)}\n\n`;
180+
return `### Value\n\n${processContent(content)}\n\n`;
162181
case 'example':
163-
return `## Example\n\n${processContent(content)}\n\n`; case 'code': {
182+
return `### Example\n\n${processContent(content)}\n\n`; case 'code': {
164183
const codeContent = processContent(content);
165184
const normalizedCode = normalizeCodeIndentation(codeContent);
166185
return `\`\`\`\n${normalizedCode}\n\`\`\``;
@@ -671,7 +690,7 @@ function processSpecialGroup(group: Record<string, unknown>): string {
671690
return `- **\`${paramName}\`**: ${paramText}`;
672691
});
673692

674-
return `## Parameters\n\n${items.join('\n')}\n\n`;
693+
return `### Parameters\n\n${items.join('\n')}\n\n`;
675694
} else if (groupType === 'exception') {
676695
const items = elements.map(element => {
677696
const exceptionType = extractAttribute(element, 'cref');
@@ -682,7 +701,7 @@ function processSpecialGroup(group: Record<string, unknown>): string {
682701
return `- **\`${exceptionType}\`**: ${exceptionText}`;
683702
});
684703

685-
return `## Exceptions\n\n${items.join('\n')}\n\n`;
704+
return `### Exceptions\n\n${items.join('\n')}\n\n`;
686705
}
687706

688707
return '';

0 commit comments

Comments
 (0)