diff --git a/CHANGELOG.md b/CHANGELOG.md index fc75427..77773d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to the "turbo-file-header" extension will be documented in this file. +# [0.2.8] + +## 新增功能 🌱 + +- feat: 函数注释支持 c + +## 问题修复 🐛 + +- fix: 修复 paramNameBeforeType 解构时未设置默认值引起问题 + # [0.2.7] ## 新增功能 🌱 diff --git a/package.json b/package.json index f2b6dba..f6af22c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "turbo-file-header", "displayName": "Turbo File Header", "description": "%description%", - "version": "0.2.7", + "version": "0.2.8", "icon": "resources/icons/icon.png", "repository": { "type": "git", diff --git a/sampleWorkspace/function-comment-for-c/function-none-params-without-return.c b/sampleWorkspace/function-comment-for-c/function-none-params-without-return.c new file mode 100644 index 0000000..ca5581c --- /dev/null +++ b/sampleWorkspace/function-comment-for-c/function-none-params-without-return.c @@ -0,0 +1,3 @@ +void func() { + printf("test\n"); +} diff --git a/sampleWorkspace/function-comment-for-c/function-none-params-without-return.result.c b/sampleWorkspace/function-comment-for-c/function-none-params-without-return.result.c new file mode 100644 index 0000000..72e3ada --- /dev/null +++ b/sampleWorkspace/function-comment-for-c/function-none-params-without-return.result.c @@ -0,0 +1,7 @@ +/** + * @description + * @return default {void} + */ +void func() { + printf("test\n"); +} diff --git a/sampleWorkspace/function-comment-for-c/function-with-params-with-return.c b/sampleWorkspace/function-comment-for-c/function-with-params-with-return.c new file mode 100644 index 0000000..d553018 --- /dev/null +++ b/sampleWorkspace/function-comment-for-c/function-with-params-with-return.c @@ -0,0 +1,4 @@ +int func(char* a, int b) { + printf("test\n"); + return 1; +} diff --git a/sampleWorkspace/function-comment-for-c/function-with-params-with-return.result.c b/sampleWorkspace/function-comment-for-c/function-with-params-with-return.result.c new file mode 100644 index 0000000..ae9ee76 --- /dev/null +++ b/sampleWorkspace/function-comment-for-c/function-with-params-with-return.result.c @@ -0,0 +1,10 @@ +/** + * @description + * @return default {int} + * @param a {char*} + * @param b {int} + */ +int func(char* a, int b) { + printf("test\n"); + return 1; +} diff --git a/sampleWorkspace/function-comment-for-java/function-none-params-with-return.java b/sampleWorkspace/function-comment-for-java/function-none-params-without-return.java similarity index 100% rename from sampleWorkspace/function-comment-for-java/function-none-params-with-return.java rename to sampleWorkspace/function-comment-for-java/function-none-params-without-return.java diff --git a/sampleWorkspace/function-comment-for-java/function-none-params-with-return.result.java b/sampleWorkspace/function-comment-for-java/function-none-params-without-return.result.java similarity index 100% rename from sampleWorkspace/function-comment-for-java/function-none-params-with-return.result.java rename to sampleWorkspace/function-comment-for-java/function-none-params-without-return.result.java diff --git a/sampleWorkspace/test.code-workspace b/sampleWorkspace/test.code-workspace index cb6853f..c532fc2 100644 --- a/sampleWorkspace/test.code-workspace +++ b/sampleWorkspace/test.code-workspace @@ -8,6 +8,7 @@ { "path": "function-comment-for-python" }, { "path": "function-comment-for-php" }, { "path": "function-comment-for-rust" }, + { "path": "function-comment-for-c" }, { "path": "workspace" }, ], "settings": {}, diff --git a/src/function-params-parser/CProvider.ts b/src/function-params-parser/CProvider.ts new file mode 100644 index 0000000..71b5295 --- /dev/null +++ b/src/function-params-parser/CProvider.ts @@ -0,0 +1,164 @@ +import * as vscode from 'vscode'; + +import { ConfigManager } from '@/configuration/ConfigManager'; +import { logger } from '@/extension'; +import { LanguageFunctionCommentSettings } from '@/typings/types'; +import { escapeRegexString } from '@/utils/str'; + +import { FunctionParamsParser } from './FunctionParamsParser'; +import { splitParams } from './c-splitParams'; +import { extractFunctionParamsString } from './extractFunctionParamsString'; +import { FunctionParamsInfo, ParamsInfo, ReturnInfo } from './types'; + +function matchNormalFunction( + functionDefinition: string, + languageSettings: LanguageFunctionCommentSettings, +): { + matched: boolean; + returnType: ReturnInfo; + params: ParamsInfo; +} { + const { defaultReturnName = 'default' } = languageSettings; + const returnType: ReturnInfo = {}; + let matched = false; + let params: ParamsInfo = {}; + + // 提取参数括号里的字符串 + const functionParamsStr = extractFunctionParamsString(functionDefinition); + const functionParamsRegStr = escapeRegexString(functionParamsStr); + const functionPattern = new RegExp( + `(.*?)\\s+([a-zA-Z0-9_]+)\\s*\\(${functionParamsRegStr}\\)\\s*{[\\s\\S]*?}`, + 'm', + ); + + const match = functionPattern.exec(functionDefinition); + const modifiers = ['static', 'inline']; + + if (match) { + matched = true; + const prefixParts = match[1].split(' ').filter((part) => part !== ''); + let returnTypeIndex = 0; + + for (let i = 0; i < prefixParts.length; i++) { + if (!modifiers.includes(prefixParts[i])) { + returnTypeIndex = i; + break; + } + } + + const returnTypeStr = prefixParts.slice(returnTypeIndex).join(' '); + returnType[defaultReturnName] = { + type: returnTypeStr, + description: '', + }; + + params = splitParams(functionParamsStr, languageSettings); + } + + return { matched, returnType, params }; +} + +/** + * @description + * @return default {auto} + */ +function matchFunction( + functionDefinition: string, + languageSettings: LanguageFunctionCommentSettings, +): { matched: boolean; returnType: ReturnInfo; params: ParamsInfo } { + const { defaultReturnName = 'default', defaultReturnType = 'auto' } = languageSettings; + let returnType: ReturnInfo = { + [defaultReturnName]: { type: defaultReturnType, description: '' }, + }; + let matched = false; + let params: ParamsInfo = {}; + + const matchers = [matchNormalFunction]; + + for (const matcher of matchers) { + const result = matcher(functionDefinition, languageSettings); + if (result.matched) { + matched = result.matched; + params = result.params; + returnType = result.returnType; + break; + } + } + + return { matched, returnType, params }; +} + +export class CParser extends FunctionParamsParser { + constructor(configManager: ConfigManager, languageId: string) { + super(configManager, languageId); + } + + private getFunctionString(document: vscode.TextDocument, startLine: number) { + let functionDefinition = ''; + let bracketCount = 0; // 大括号计数 + let parenthesisCount = 0; // 小括号计数 + + for (let i = startLine; i < document.lineCount; i++) { + const line = document.lineAt(i); + functionDefinition += line.text + '\n'; + + for (const char of line.text) { + if (char === '(') { + parenthesisCount++; + } else if (char === ')') { + parenthesisCount--; + } else if (char === '{') { + bracketCount++; + } else if (char === '}') { + bracketCount--; + } + } + + if (bracketCount === 0 && parenthesisCount === 0) { + break; + } + } + + return functionDefinition; + } + + public getFunctionParamsAtCursor( + activeEditor: vscode.TextEditor, + languageSettings: LanguageFunctionCommentSettings = this.languageSettings, + ): FunctionParamsInfo { + let functionParams: ParamsInfo = {}; + let matchedFunction = false; + let returnType: ReturnInfo = {}; + const document = activeEditor.document; + const cursorLine = activeEditor.selection.start.line; + let startLine = cursorLine; + // 如果光标所在行为空行或者注释,则从下一行开始 + const cursorLineText = document.lineAt(cursorLine).text.trim(); + if (cursorLineText === '' || cursorLineText === '//' || cursorLineText === '*/') { + startLine = cursorLine + 1; + } + + const functionDefinition = this.getFunctionString(document, startLine); + const { + matched, + returnType: returnTypeTmp, + params, + } = matchFunction(functionDefinition, languageSettings); + if (matched) { + matchedFunction = true; + returnType = returnTypeTmp; + functionParams = params; + } + + if (!matchFunction) { + logger.info(vscode.l10n.t('No function found at the cursor')); + } + + return { + matchedFunction, + returnType, + params: functionParams, + insertPosition: new vscode.Position(startLine, 0), + }; + } +} diff --git a/src/function-params-parser/FunctionParamsParser.ts b/src/function-params-parser/FunctionParamsParser.ts index 3858069..471a178 100644 --- a/src/function-params-parser/FunctionParamsParser.ts +++ b/src/function-params-parser/FunctionParamsParser.ts @@ -143,7 +143,7 @@ export abstract class FunctionParamsParser { document: vscode.TextDocument, range: vscode.Range, ): FunctionCommentInfo { - const { paramNameBeforeType } = this.languageSettings; + const { paramNameBeforeType = true } = this.languageSettings; const functionCommentLines = document.getText(range).split('\n'); return paramNameBeforeType @@ -154,8 +154,7 @@ export abstract class FunctionParamsParser { protected parseFunctionCommentNameFirst(functionCommentLines: string[]): FunctionCommentInfo { const paramPattern = /@param\s+(?:\[\s*([^=\]]+)(?:=(.*?))?\s*\]|([^=\]]+))\s*\{((?:[^}]|\}(?!\s*$))*)\}\s*(.*)/; - const returnPattern = /@return\s+(?:(\w+)\s*)?\{((?:[^}]|\}(?!\s*$))*)\}\s*(.*)/; - + const returnPattern = /@return\s+(\w+)\s*\{((?:[^}]|\}(?!\s*$))*)\}\s*(.*)/; return this.parseFunctionCommentLines(functionCommentLines, paramPattern, returnPattern, true); } @@ -182,39 +181,61 @@ export abstract class FunctionParamsParser { defaultParamType = 'any', } = this.languageSettings; const descriptionPattern = /@description\s+(.*)/; + + const handleParamNameBeforeType = (match: RegExpExecArray) => { + const [ + _, + optionalName, + defaultValue, + name, + type = defaultParamType as string, + description = '', + ] = match; + const realName = optionalName || name; + paramsInfo[realName] = { + type, + description, + ...(defaultValue && { defaultValue }), + ...(!defaultValue && optionalName && { optional: true }), + }; + }; + + const handleParamTypeBeforeName = (match: RegExpExecArray) => { + const [ + _, + type = defaultParamType as string, + optionalName, + defaultValue, + name, + description = '', + ] = match; + const realName = optionalName || name; + paramsInfo[realName] = { + type, + description, + ...(defaultValue && { defaultValue }), + ...(!defaultValue && optionalName && { optional: true }), + }; + }; + + const handleReturn = (match: RegExpExecArray) => { + let [_, name, type, description = ''] = match; + if (!paramNameBeforeType) { + [_, type, name, description = ''] = match; + } + returnInfo[name || defaultReturnName] = { type: type || defaultReturnType, description }; + }; + for (const line of functionCommentLines) { let match; if ((match = paramPattern.exec(line)) !== null) { - let _, optionalName, defaultValue, name, type, description; if (paramNameBeforeType) { - [ - _, - optionalName, - defaultValue, - name, - type = defaultParamType as string, - description = '', - ] = match; + handleParamNameBeforeType(match); } else { - [ - _, - type = defaultParamType as string, - optionalName, - defaultValue, - name, - description = '', - ] = match; + handleParamTypeBeforeName(match); } - const realName = optionalName || name; - paramsInfo[realName] = { - type, - description, - ...(defaultValue && { defaultValue }), - ...(!defaultValue && optionalName && { optional: true }), - }; } else if ((match = returnPattern.exec(line)) !== null) { - const [_, key = defaultReturnName, type = defaultReturnType, description = ''] = match; - returnInfo[key] = { type, description }; + handleReturn(match); } else if ((match = descriptionPattern.exec(line)) !== null) { const [_, description] = match; descriptionInfo = description.trim(); diff --git a/src/function-params-parser/FunctionParserLoader.ts b/src/function-params-parser/FunctionParserLoader.ts index 4230b90..06f7075 100644 --- a/src/function-params-parser/FunctionParserLoader.ts +++ b/src/function-params-parser/FunctionParserLoader.ts @@ -2,6 +2,7 @@ import { ConfigManager } from '@/configuration/ConfigManager'; import { CustomError, ErrorCode } from '@/error'; import { logger } from '@/extension'; +import { CParser } from './CProvider'; import { FunctionParamsParser } from './FunctionParamsParser'; import { GoParser } from './GoProvider'; import { JavaParser } from './JavaProvider'; @@ -30,11 +31,13 @@ export class FunctionParserLoader { typescriptreact: TypescriptParser, javascript: JavascriptParser, javascriptreact: JavascriptParser, + vue: TypescriptParser, go: GoParser, java: JavaParser, python: PythonParser, php: PhpParser, rust: RustParser, + c: CParser, }; public async loadParser(languageId: string): Promise { diff --git a/src/function-params-parser/c-splitParams.ts b/src/function-params-parser/c-splitParams.ts new file mode 100644 index 0000000..b9ca44f --- /dev/null +++ b/src/function-params-parser/c-splitParams.ts @@ -0,0 +1,44 @@ +import { LanguageFunctionCommentSettings } from '@/typings/types'; + +import { ParamsInfo } from './types'; + +export function splitParams( + paramsStr: string, + languageSettings: LanguageFunctionCommentSettings, +): ParamsInfo { + const { defaultParamType = 'any', defaultReturnName = 'default' } = languageSettings; + let bracketCount = 0; + let paramStartIndex = 0; + let defaultCount = 0; + const params: ParamsInfo = {}; + for (let i = 0; i < paramsStr?.length; i++) { + const char = paramsStr[i]; + if (char === '(' || char === '[' || char === '{' || char === '<') { + bracketCount++; + } else if (char === ')' || char === ']' || char === '}' || char === '>') { + bracketCount--; + } + + if ( + (char === ',' && bracketCount === 0) || + (i === paramsStr.length - 1 && bracketCount === 0) + ) { + const paramStr = paramsStr + .slice(paramStartIndex, i === paramsStr.length - 1 ? i + 1 : i) + .trim(); + + const lastNonSpaceIndex = paramStr.search(/\S\s*$/); + let name, type; + if (lastNonSpaceIndex !== -1) { + name = paramStr.slice(lastNonSpaceIndex).trim(); + type = paramStr.slice(0, lastNonSpaceIndex).trim() || defaultParamType; + } else { + name = defaultCount > 0 ? `${defaultReturnName}${defaultCount++}` : defaultReturnName; + type = paramStr.trim() || defaultParamType; + } + params[name] = { type, description: '' }; + paramStartIndex = i + 1; + } + } + return params; +} diff --git a/src/test/suite/c-functionComment.test.ts b/src/test/suite/c-functionComment.test.ts new file mode 100644 index 0000000..746a0be --- /dev/null +++ b/src/test/suite/c-functionComment.test.ts @@ -0,0 +1,15 @@ +import { functionCommentTester } from './common/functionCommentTester'; +import { TestInfo } from './types'; + +const testInfo: TestInfo = [ + { + testName: 'c-function', + workspaceName: 'function-comment-for-c', + files: [ + { fileName: 'function-none-params-without-return.c', cursorLine: 0 }, + { fileName: 'function-with-params-with-return.c', cursorLine: 0 }, + ], + }, +]; + +functionCommentTester(testInfo); diff --git a/src/test/suite/java-functionComment.test.ts b/src/test/suite/java-functionComment.test.ts index 2dd9726..7e8e346 100644 --- a/src/test/suite/java-functionComment.test.ts +++ b/src/test/suite/java-functionComment.test.ts @@ -6,7 +6,7 @@ const testInfo: TestInfo = [ testName: 'java-function', workspaceName: 'function-comment-for-java', files: [ - { fileName: 'function-none-params-with-return.java', cursorLine: 0 }, + { fileName: 'function-none-params-without-return.java', cursorLine: 0 }, { fileName: 'function-with-params-with-return.java', cursorLine: 0 }, ], },