Skip to content

Commit c0c2ddb

Browse files
committed
feat: 函数注释支持 rust
Co-authored-by: ygqygq2 <[email protected]>
1 parent ffe0e55 commit c0c2ddb

14 files changed

+310
-1
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "turbo-file-header",
33
"displayName": "Turbo File Header",
44
"description": "%description%",
5-
"version": "0.2.6",
5+
"version": "0.2.7",
66
"icon": "resources/icons/icon.png",
77
"repository": {
88
"type": "git",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* @description
3+
* @return default {String}
4+
*/
5+
fn foo() -> String {
6+
"a"
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn foo() -> String {
2+
"a"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* @description
3+
* @return default {auto}
4+
*/
5+
fn foo(x) {
6+
println!("a");
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn foo(x) {
2+
println!("a");
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* @description
3+
* @return default {i32}
4+
* @param bar {Option<i32>}
5+
*/
6+
fn foo(bar: Option<i32>) -> i32 {
7+
let bar = bar.unwrap_or(42);
8+
println!("{}", bar);
9+
bar
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fn foo(bar: Option<i32>) -> i32 {
2+
let bar = bar.unwrap_or(42);
3+
println!("{}", bar);
4+
bar
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* @description
3+
* @return default {auto}
4+
* @param bar {Option<i32>}
5+
*/
6+
fn foo(bar: Option<i32>) {
7+
let bar = bar.unwrap_or(42);
8+
println!("{}", bar);
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
fn foo(bar: Option<i32>) {
2+
let bar = bar.unwrap_or(42);
3+
println!("{}", bar);
4+
}

sampleWorkspace/test.code-workspace

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
{ "path": "function-comment-for-java" },
88
{ "path": "function-comment-for-python" },
99
{ "path": "function-comment-for-php" },
10+
{ "path": "function-comment-for-rust" },
1011
{ "path": "workspace" },
1112
],
1213
"settings": {},

src/function-params-parser/FunctionParserLoader.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { JavaParser } from './JavaProvider';
88
import { JavascriptParser } from './JavascriptProvider';
99
import { PhpParser } from './PhpProvider';
1010
import { PythonParser } from './PythonProvider';
11+
import { RustParser } from './RustProvider';
1112
import { TypescriptParser } from './TypescriptProvider';
1213

1314
export class FunctionParserLoader {
@@ -33,6 +34,7 @@ export class FunctionParserLoader {
3334
java: JavaParser,
3435
python: PythonParser,
3536
php: PhpParser,
37+
rust: RustParser,
3638
};
3739

3840
public async loadParser(languageId: string): Promise<FunctionParamsParser | null> {
+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import * as vscode from 'vscode';
2+
3+
import { ConfigManager } from '@/configuration/ConfigManager';
4+
import { logger } from '@/extension';
5+
import { LanguageFunctionCommentSettings } from '@/typings/types';
6+
import { escapeRegexString } from '@/utils/str';
7+
8+
import { extractFunctionParamsString } from './extractFunctionParamsString';
9+
import { FunctionParamsParser } from './FunctionParamsParser';
10+
import { splitParams } from './rust-splitParams';
11+
import { FunctionParamsInfo, ParamsInfo, ReturnInfo } from './types';
12+
13+
function matchNormalFunction(
14+
functionDefinition: string,
15+
languageSettings: LanguageFunctionCommentSettings,
16+
): {
17+
matched: boolean;
18+
returnType: ReturnInfo;
19+
params: ParamsInfo;
20+
} {
21+
const { defaultReturnName = 'default', defaultReturnType = 'auto' } = languageSettings;
22+
const returnType: ReturnInfo = {};
23+
let matched = false;
24+
let params: ParamsInfo = {};
25+
26+
// 提取参数括号里的字符串
27+
const functionParamsStr = extractFunctionParamsString(functionDefinition);
28+
const functionParamsRegStr = escapeRegexString(functionParamsStr);
29+
const functionPattern = new RegExp(
30+
`fn\\s+([a-zA-Z0-9_]+)\\s*(<.*>)?\\s*\\(${functionParamsRegStr}\\)\\s*(->\\s*(.*))?\\s*{`,
31+
'm',
32+
);
33+
34+
const match = functionPattern.exec(functionDefinition);
35+
if (match) {
36+
matched = true;
37+
const returnTypeStr = match[4] ? match[4].trim() : defaultReturnType;
38+
39+
returnType[defaultReturnName] = {
40+
type: returnTypeStr,
41+
description: '',
42+
};
43+
44+
params = splitParams(functionParamsStr, languageSettings);
45+
}
46+
47+
return { matched, returnType, params };
48+
}
49+
50+
function matchAssociatedFunction(
51+
functionDefinition: string,
52+
languageSettings: LanguageFunctionCommentSettings,
53+
): {
54+
matched: boolean;
55+
returnType: ReturnInfo;
56+
params: ParamsInfo;
57+
} {
58+
const { defaultReturnName = 'default', defaultReturnType = 'auto' } = languageSettings;
59+
const returnType: ReturnInfo = {};
60+
let matched = false;
61+
let params: ParamsInfo = {};
62+
63+
// 提取参数括号里的字符串
64+
const functionParamsStr = extractFunctionParamsString(functionDefinition);
65+
const functionParamsRegStr = escapeRegexString(functionParamsStr);
66+
const functionPattern = new RegExp(
67+
`impl\\s+([a-zA-Z0-9_]+)\\s*{\\s*fn\\s+([a-zA-Z0-9_]+)\\s*(<.*>)?\\s*\\(${functionParamsRegStr}\\)\\s*(->\\s*(.*))?\\s*{`,
68+
'm',
69+
);
70+
71+
const match = functionPattern.exec(functionDefinition);
72+
73+
if (match) {
74+
matched = true;
75+
const returnTypeStr = match[5] ? match[5].trim() : defaultReturnType;
76+
77+
returnType[defaultReturnName] = {
78+
type: returnTypeStr,
79+
description: '',
80+
};
81+
82+
params = splitParams(functionParamsStr, languageSettings);
83+
}
84+
85+
return { matched, returnType, params };
86+
}
87+
88+
/**
89+
* @description
90+
* @return default {auto}
91+
*/
92+
function matchFunction(
93+
functionDefinition: string,
94+
languageSettings: LanguageFunctionCommentSettings,
95+
): { matched: boolean; returnType: ReturnInfo; params: ParamsInfo } {
96+
const { defaultReturnName = 'default', defaultReturnType = 'Null' } = languageSettings;
97+
let returnType: ReturnInfo = {
98+
[defaultReturnName]: { type: defaultReturnType, description: '' },
99+
};
100+
let matched = false;
101+
let params: ParamsInfo = {};
102+
103+
const matchers = [matchNormalFunction, matchAssociatedFunction];
104+
105+
for (const matcher of matchers) {
106+
const result = matcher(functionDefinition, languageSettings);
107+
if (result.matched) {
108+
matched = result.matched;
109+
params = result.params;
110+
returnType = result.returnType;
111+
break;
112+
}
113+
}
114+
115+
return { matched, returnType, params };
116+
}
117+
118+
export class RustParser extends FunctionParamsParser {
119+
constructor(configManager: ConfigManager, languageId: string) {
120+
super(configManager, languageId);
121+
}
122+
123+
private getFunctionString(document: vscode.TextDocument, startLine: number) {
124+
let functionDefinition = '';
125+
let bracketCount = 0; // 大括号计数
126+
let parenthesisCount = 0; // 小括号计数
127+
128+
for (let i = startLine; i < document.lineCount; i++) {
129+
const line = document.lineAt(i);
130+
functionDefinition += line.text + '\n';
131+
132+
for (const char of line.text) {
133+
if (char === '(') {
134+
parenthesisCount++;
135+
} else if (char === ')') {
136+
parenthesisCount--;
137+
} else if (char === '{') {
138+
bracketCount++;
139+
} else if (char === '}') {
140+
bracketCount--;
141+
}
142+
}
143+
144+
if (bracketCount === 0 && parenthesisCount === 0) {
145+
break;
146+
}
147+
}
148+
149+
return functionDefinition;
150+
}
151+
152+
public getFunctionParamsAtCursor(
153+
activeEditor: vscode.TextEditor,
154+
languageSettings: LanguageFunctionCommentSettings = this.languageSettings,
155+
): FunctionParamsInfo {
156+
let functionParams: ParamsInfo = {};
157+
let matchedFunction = false;
158+
let returnType: ReturnInfo = {};
159+
const document = activeEditor.document;
160+
const cursorLine = activeEditor.selection.start.line;
161+
let startLine = cursorLine;
162+
// 如果光标所在行为空行或者注释,则从下一行开始
163+
const cursorLineText = document.lineAt(cursorLine).text.trim();
164+
if (
165+
cursorLineText === '' ||
166+
cursorLineText === '//' ||
167+
cursorLineText === '#' ||
168+
cursorLineText === '*/'
169+
) {
170+
startLine = cursorLine + 1;
171+
}
172+
173+
const functionDefinition = this.getFunctionString(document, startLine);
174+
const {
175+
matched,
176+
returnType: returnTypeTmp,
177+
params,
178+
} = matchFunction(functionDefinition, languageSettings);
179+
if (matched) {
180+
matchedFunction = true;
181+
returnType = returnTypeTmp;
182+
functionParams = params;
183+
}
184+
185+
if (!matchFunction) {
186+
logger.info(vscode.l10n.t('No function found at the cursor'));
187+
}
188+
189+
return {
190+
matchedFunction,
191+
returnType,
192+
params: functionParams,
193+
insertPosition: new vscode.Position(startLine, 0),
194+
};
195+
}
196+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { LanguageFunctionCommentSettings } from '@/typings/types';
2+
3+
import { ParamsInfo } from './types';
4+
5+
export function splitParams(
6+
paramsStr: string,
7+
languageSettings: LanguageFunctionCommentSettings,
8+
): ParamsInfo {
9+
const { defaultParamType = 'mixed', defaultReturnName = 'default' } = languageSettings;
10+
let bracketCount = 0;
11+
let paramStartIndex = 0;
12+
let defaultCount = 0;
13+
const params: ParamsInfo = {};
14+
for (let i = 0; i < paramsStr?.length; i++) {
15+
const char = paramsStr[i];
16+
if (char === '(' || char === '[' || char === '{' || char === '<') {
17+
bracketCount++;
18+
} else if (char === ')' || char === ']' || char === '}' || char === '>') {
19+
bracketCount--;
20+
}
21+
22+
if (
23+
(char === ',' && bracketCount === 0) ||
24+
(i === paramsStr.length - 1 && bracketCount === 0)
25+
) {
26+
const paramStr = paramsStr
27+
.slice(paramStartIndex, i === paramsStr.length - 1 ? i + 1 : i)
28+
.trim();
29+
30+
const paramPattern = /^(&self|&mut\s+self)?(\w+)\s*:\s*(.*)$/;
31+
const match = paramPattern.exec(paramStr);
32+
if (match) {
33+
const name =
34+
match[2] ||
35+
(defaultCount > 0 ? `${defaultReturnName}${defaultCount++}` : defaultReturnName);
36+
const type = match[3]?.trim() || defaultParamType;
37+
38+
params[name] = { type, description: '' };
39+
}
40+
paramStartIndex = i + 1;
41+
}
42+
}
43+
44+
return params;
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { functionCommentTester } from './common/functionCommentTester';
2+
import { TestInfo } from './types';
3+
4+
const testInfo: TestInfo = [
5+
{
6+
testName: 'rust-function',
7+
workspaceName: 'function-comment-for-rust',
8+
files: [
9+
{ fileName: 'function-none-params-with-return.rs', cursorLine: 0 },
10+
{ fileName: 'function-none-params-without-return.rs', cursorLine: 0 },
11+
{ fileName: 'function-with-params-with-return.rs', cursorLine: 0 },
12+
{ fileName: 'function-with-params-without-return.rs', cursorLine: 0 },
13+
],
14+
},
15+
];
16+
17+
functionCommentTester(testInfo);

0 commit comments

Comments
 (0)