Skip to content

Commit 4836640

Browse files
committed
- Updated the docgen item type for fucntion to include matching based on possible generic implementations '<'.
- Updated the function generator to account for possible generics defined in a functions signature and still generate the comments. Additionally, an update to parsing function arguments where the parameters were split due to ',' now properly handles things like "test: HashMap<String, String>" as a single parameter to the argument. - Added unit test for new functionality.
1 parent 074e5c4 commit 4836640

File tree

3 files changed

+74
-12
lines changed

3 files changed

+74
-12
lines changed

src/docgen.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function generateDocComment(line, options) {
4646
* @returns {"function"|"struct"|"enum"|null} - The detected item type.
4747
*/
4848
function getRustItemType(line) {
49-
if (/fn\s+\w+\s*\(/.test(line)) return 'function';
49+
if (/fn\s+\w+\s*[(<]/.test(line)) return 'function';
5050
if (/struct\s+\w+/.test(line)) return 'struct';
5151
if (/enum\s+\w+/.test(line)) return 'enum';
5252
// if (/trait\s+\w+/.test(line)) return 'trait'; // TODO

src/gen_fn_doc.js

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,22 @@ function generateFunctionDoc(line, includeExamples, examplesOnlyForPublicOrExter
3535
const hasExtern = !!modifierMatch?.[3];
3636

3737
// Attempt to match a Rust function signature.
38-
const fnMatch = cleanLine.match(/fn\s+(\w+)\s*\(([^)]*)\)\s*(?:->\s*([^;{]+))?/);
38+
const fnMatch = cleanLine.match(/fn\s+(\w+)\s*(<[^>]*>)?\s*\(([^)]*)\)\s*(?:->\s*([^;{]+))?/);
3939
if (!fnMatch) return null; // If no match, return nothing (unsupported line)
4040

4141
// Destructure the match results: function name, arguments, and optional return type.
42-
const [, name, args, returnType] = fnMatch;
42+
const [, name, _generics, args, returnType] = fnMatch;
4343

4444
// Gets the return type.
4545
const cleanedReturn = returnType ? returnType.trim() : null;
4646

4747
// Sets the tab stop count for the parameter. Description is always 1.
4848
let currentTabStop = 2;
4949

50-
// Parse and format each function argument into a markdown line with snippet placeholder.
51-
const params = args
52-
.split(',')
53-
.map(p => p.trim())
54-
.filter(Boolean) // Remove empty strings
55-
.map(param => {
56-
const [argName, argType] = param.split(':').map(s => s.trim());
57-
return `- \`${argName}\` (\`${argType}\`) - \${${currentTabStop++}:Describe this parameter.}`;
58-
});
50+
const params = splitFunctionArgs(args).map(p => p.trim()).filter(Boolean).map(param => {
51+
const [argName, argType] = param.split(':').map(s => s.trim());
52+
return `- \`${argName}\` (\`${argType}\`) - \${${currentTabStop++}:Describe this parameter.}`;
53+
});
5954

6055
// Build the full documentation block line by line.
6156
const docLines = [];
@@ -117,6 +112,47 @@ function generateFunctionDoc(line, includeExamples, examplesOnlyForPublicOrExter
117112
].join('\n');
118113
}
119114

115+
/**
116+
* Splits a Rust function's argument list into individual parameters,
117+
* correctly handling nested generics and parentheses.
118+
*
119+
* This function is necessary because Rust function arguments may contain nested
120+
* angle brackets (e.g., `HashMap<String, Vec<u8>>`) or tuples
121+
* (e.g., `(i32, f64)`), which would cause a naive `.split(',')` to fail.
122+
*
123+
* It performs a linear scan of the argument string, tracking nesting levels of
124+
* `< >` and `( )`, and only splits on commas that are outside these constructs.
125+
*
126+
* @param {string} argString - The comma-separated argument list from a Rust function signature.
127+
* @returns {string[]} - An array of individual argument strings.
128+
*
129+
* @example
130+
* splitFunctionArgs("x: u32, y: HashMap<String, Vec<u8>>")
131+
* // returns: ["x: u32", "y: HashMap<String, Vec<u8>>"]
132+
*/
133+
function splitFunctionArgs(argString) {
134+
const args = [];
135+
let current = '';
136+
let angle = 0, paren = 0;
137+
138+
for (let char of argString) {
139+
if (char === '<') angle++;
140+
else if (char === '>') angle--;
141+
else if (char === '(') paren++;
142+
else if (char === ')') paren--;
143+
144+
if (char === ',' && angle === 0 && paren === 0) {
145+
args.push(current.trim());
146+
current = '';
147+
} else {
148+
current += char;
149+
}
150+
}
151+
152+
if (current.trim()) args.push(current.trim());
153+
return args;
154+
}
155+
120156
/**
121157
* Generates the Rust documentation example section based on the function's modifiers.
122158
*

src/test/extension.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,32 @@ describe('generateFunctionDoc()', () => {
7878
assert.ok(output.includes('`i32`'));
7979
});
8080

81+
it('handles generic parameters before arguments', () => {
82+
const input = 'pub fn write_display<W: FmtWrite>(&self, w: &mut W) -> std::fmt::Result';
83+
const output = generateFunctionDoc(input, false, false, false);
84+
85+
assert.ok(output.includes('`w` (`&mut W`)'), 'Missing w parameter with generic type');
86+
assert.ok(output.includes('&self'), 'Missing &self parameter');
87+
assert.ok(output.includes('# Returns'), 'Missing return section');
88+
assert.ok(output.includes('`std::fmt::Result`'), 'Missing return type');
89+
});
90+
91+
it('handles nested generic types in arguments', () => {
92+
const input = 'fn test(map: HashMap<String, Vec<u8>>)';
93+
const output = generateFunctionDoc(input, false, false, false);
94+
95+
assert.ok(output.includes('# Arguments'), 'Missing arguments section');
96+
assert.ok(output.includes('`map` (`HashMap<String, Vec<u8>>`)'), 'Missing correct nested generic param');
97+
});
98+
99+
it('handles multiple complex arguments with nested generics', () => {
100+
const input = 'fn process(data: Option<Result<u8, E>>, map: HashMap<String, Vec<u8>>)';
101+
const output = generateFunctionDoc(input, false, false, false);
102+
103+
assert.ok(output.includes('`data` (`Option<Result<u8, E>>`)'), 'Missing complex param `data`');
104+
assert.ok(output.includes('`map` (`HashMap<String, Vec<u8>>`)'), 'Missing complex param `map`');
105+
});
106+
81107
it('handles unsafe functions', () => {
82108
const input = 'pub unsafe fn access_raw(ptr: *const u8) -> u8 {';
83109
const output = generateFunctionDoc(input, true, true, true);

0 commit comments

Comments
 (0)