diff --git a/README.md b/README.md index 0b345536..7d7ba1de 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ Is your favorite doc standard not supported? | :white_check_mark: | C | [Doxygen][doxygen], [KernelDoc][kerneldoc] | | :white_check_mark: | Bash | [Google][sh-google] | | :white_check_mark: | Rust | [RustDoc][rustdoc] | +| :white_check_mark: | C-Sharp | [XMLDoc][xmldoc] | # Getting started @@ -155,7 +156,8 @@ Here is the full list of available doc standards per filetype: | `g:doge_doc_standard_cpp` | `'doxygen_javadoc'` | `'doxygen_javadoc'`, `'doxygen_javadoc_no_asterisk'`, `'doxygen_javadoc_banner'`, `'doxygen_qt'`, `'doxygen_qt_no_asterisk'` | | `g:doge_doc_standard_c` | `'doxygen_javadoc'` | `'kernel_doc'`, `'doxygen_javadoc'`, `'doxygen_javadoc_no_asterisk'`, `'doxygen_javadoc_banner'`, `'doxygen_qt'`, `'doxygen_qt_no_asterisk'` | | `g:doge_doc_standard_sh` | `'google'` | `'google'` | -| `g:doge_doc_standard_rs` | `'rustdoc'` | `'rustdoc'` | +| `g:doge_doc_standard_rs` | `'rustdoc'` | `'rustdoc'` | +| `g:doge_doc_standard_cs` | `'xmldoc'` | `'xmldoc'` | ## Options diff --git a/ftplugin/cs.vim b/ftplugin/cs.vim new file mode 100644 index 00000000..9a44b340 --- /dev/null +++ b/ftplugin/cs.vim @@ -0,0 +1,59 @@ +" ============================================================================== +" The PHP documentation should follow the 'phpdoc' conventions. +" see https://www.phpdoc.org +" ============================================================================== + +let s:save_cpo = &cpoptions +set cpoptions&vim + +let b:doge_parser = 'cs' +let b:doge_insert = 'above' + +let b:doge_supported_doc_standards = doge#buffer#get_supported_doc_standards(['csxml']) +let b:doge_doc_standard = doge#buffer#get_doc_standard('cs') +let b:doge_patterns = doge#buffer#get_patterns() + +" ============================================================================== +" +" Define the doc standards. +" +" ============================================================================== + +call doge#buffer#register_doc_standard('csxml', [ +\ { +\ 'nodeTypes': ['method_declaration', 'operator_declaration', 'delegate_declaration'], +\ 'parameters': { +\ 'format': '!description' +\ }, +\ 'template': [ +\ '/// ', +\ '/// !description', +\ '/// ', +\ '%(parameters|/// {parameters})%', +\ '%(hasReturn|/// !description)%', +\ ], +\ }, +\ { +\ 'nodeTypes': ['constructor_declaration'], +\ 'parameters': { +\ 'format': '!description' +\ }, +\ 'template': [ +\ '/// ', +\ '/// !description', +\ '/// ', +\ '%(parameters|/// {parameters})%', +\ ], +\ }, +\ { +\ 'nodeTypes': ['class_declaration', 'variable_declaration', 'property_declaration', 'field_declaration', 'enum_declaration'], +\ 'template': [ +\ '/// ', +\ '/// !description', +\ '/// ', +\ ], +\ }, +\]) + +let &cpoptions = s:save_cpo +unlet s:save_cpo diff --git a/package-lock.json b/package-lock.json index 446c823f..ee42f74e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "vim-doge", - "version": "3.14.3", + "version": "3.14.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vim-doge", - "version": "3.14.3", + "version": "3.14.0", "license": "GPL-3.0", "dependencies": { "@muniftanjim/tree-sitter-lua": "^0.0.10", "tree-sitter": "^0.20.0", "tree-sitter-bash": "^0.19.0", "tree-sitter-c": "^0.20.1", + "tree-sitter-c-sharp": "^0.19.1", "tree-sitter-cpp": "^0.20.0", "tree-sitter-java": "^0.19.1", "tree-sitter-php": "^0.19.0", @@ -48,6 +49,9 @@ "rimraf": "^3.0.2", "ts-node": "^10.8.1", "typescript": "^4.7.4" + }, + "engines": { + "node": ">=12.0.0 <18.0.0" } }, "node_modules/@babel/code-frame": { @@ -11733,6 +11737,15 @@ "nan": "^2.14.0" } }, + "node_modules/tree-sitter-c-sharp": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/tree-sitter-c-sharp/-/tree-sitter-c-sharp-0.19.1.tgz", + "integrity": "sha512-MaUqxVBH9UU+FEKptqOK6EhQ/vuRolZfavtWlXDx+HK7J5b8g8mz4b1SBhaXZaVY4sl3P1wIwVupSV6rxscrUw==", + "hasInstallScript": true, + "dependencies": { + "nan": "^2.14.0" + } + }, "node_modules/tree-sitter-cpp": { "version": "0.20.0", "resolved": "https://registry.npmjs.org/tree-sitter-cpp/-/tree-sitter-cpp-0.20.0.tgz", @@ -21626,6 +21639,14 @@ "nan": "^2.14.0" } }, + "tree-sitter-c-sharp": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/tree-sitter-c-sharp/-/tree-sitter-c-sharp-0.19.1.tgz", + "integrity": "sha512-MaUqxVBH9UU+FEKptqOK6EhQ/vuRolZfavtWlXDx+HK7J5b8g8mz4b1SBhaXZaVY4sl3P1wIwVupSV6rxscrUw==", + "requires": { + "nan": "^2.14.0" + } + }, "tree-sitter-cpp": { "version": "0.20.0", "resolved": "https://registry.npmjs.org/tree-sitter-cpp/-/tree-sitter-cpp-0.20.0.tgz", diff --git a/package.json b/package.json index 36bbaa9b..1814a627 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "tree-sitter": "^0.20.0", "tree-sitter-bash": "^0.19.0", "tree-sitter-c": "^0.20.1", + "tree-sitter-c-sharp": "^0.19.1", "tree-sitter-cpp": "^0.20.0", "tree-sitter-java": "^0.19.1", "tree-sitter-php": "^0.19.0", diff --git a/src/constants.ts b/src/constants.ts index e575de80..55c94b1b 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -3,6 +3,7 @@ export enum Language { TYPESCRIPT = 'typescript', PYTHON = 'python', C = 'c', + CSHARP = 'cs', CPP = 'cpp', BASH = 'bash', RUBY = 'ruby', diff --git a/src/helpers.ts b/src/helpers.ts index 87bd017e..a91ad4a6 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,6 +1,7 @@ import Bash from 'tree-sitter-bash'; import C from 'tree-sitter-c'; import CPP from 'tree-sitter-cpp'; +import CSharp from 'tree-sitter-c-sharp'; import Java from 'tree-sitter-java'; import Lua from '@muniftanjim/tree-sitter-lua'; import PHP from 'tree-sitter-php'; @@ -18,6 +19,7 @@ export function loadParserPackage(language: ValueOf): any { [Language.PYTHON]: Python, [Language.C]: C, [Language.CPP]: CPP, + [Language.CSHARP]: CSharp, [Language.BASH]: Bash, [Language.RUBY]: Ruby, [Language.LUA]: Lua, diff --git a/src/index.ts b/src/index.ts index c7dd1edb..d850e0f5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,7 @@ const lineNumber: number = Math.max(0, Number(args.shift()) - 1); const nodeTypes: string[] = args; const languageParser = loadParserPackage(language); + if (languageParser) { const parser = new Parser(); parser.setLanguage(languageParser); diff --git a/src/parsers/base-parser.service.ts b/src/parsers/base-parser.service.ts index afb73ff8..22816efc 100644 --- a/src/parsers/base-parser.service.ts +++ b/src/parsers/base-parser.service.ts @@ -1,12 +1,26 @@ import { SyntaxNode } from 'tree-sitter'; export class BaseParserService { + /** + * The result that will be printed for vim to handle later. + */ protected result: Record = {}; + /** + * Whether the parsing process is done or not. + */ protected done = false; + /** + * Force an empty object to be printed. Can be useful if you want to render a + * template without any tokens. + */ + protected forceOutput = false; + public output(): void { - if (Object.keys(this.result).length > 0) { + if (this.forceOutput) { + console.log('{}'); + } else if (Object.keys(this.result).length > 0) { console.log(JSON.stringify(this.result)); } } diff --git a/src/parsers/cs.service.ts b/src/parsers/cs.service.ts new file mode 100644 index 00000000..564de525 --- /dev/null +++ b/src/parsers/cs.service.ts @@ -0,0 +1,97 @@ +import { SyntaxNode } from 'tree-sitter'; +import { BaseParserService } from './base-parser.service'; +import { CustomParserService } from './custom-parser-service.interface'; + +enum NodeType { + METHOD_DECLARATION = 'method_declaration', + CLASS_DECLARATION = 'class_declaration', + VARIABLE_DECLARATION = 'variable_declaration', + PROPERTY_DECLARATION = 'property_declaration', + CONSTRUCTOR_DECLARATION = 'constructor_declaration', + CONSTANT_DECLARATION = 'field_declaration', + OPERATOR_DECLARATION = 'operator_declaration', + DELEGATE_DECLARATION = 'delegate_declaration', + ENUM_DECLARATION = 'enum_declaration' +} + +export class CSharpParserService extends BaseParserService implements CustomParserService { + constructor( + readonly rootNode: SyntaxNode, + private readonly lineNumber: number, + private readonly nodeTypes: string[], + ) { + super(); + } + + public traverse(node: SyntaxNode): void { + if ( + node.startPosition.row !== this.lineNumber + || !this.nodeTypes.includes(node.type) + || this.done !== false + ) { + if (node.childCount > 0) { + node.children.forEach((childNode: SyntaxNode) => { + this.traverse(childNode); + }); + } + + return; + } + + switch (node.type) { + case NodeType.METHOD_DECLARATION: + case NodeType.DELEGATE_DECLARATION: + case NodeType.OPERATOR_DECLARATION: + this.result = { parameters: [], hasReturn: true }; + this.runNodeParser(this.parseFunction, node); + break; + + case NodeType.CONSTRUCTOR_DECLARATION: + this.result = { parameters: [] }; + this.runNodeParser(this.parseConstructor, node); + break; + + case NodeType.VARIABLE_DECLARATION: + case NodeType.ENUM_DECLARATION: + case NodeType.CONSTANT_DECLARATION: + case NodeType.PROPERTY_DECLARATION: + case NodeType.CLASS_DECLARATION: + this.result = {}; + this.forceOutput = true; + break; + + + default: + console.error(`Unable to handle node type: ${node.type}`); + break; + } + } + + private parseFunction(node: SyntaxNode): void { + node.children.forEach((childNode: SyntaxNode) => { + if (childNode.type === 'void_keyword') { + this.result.hasReturn = false; + } else if (childNode.type === 'parameter_list') { + this.extractParamsFromList(childNode); + } + }); + } + + private parseConstructor(node: SyntaxNode): void { + node.children.forEach((childNode: SyntaxNode) => { + if (childNode.type === 'parameter_list') { + this.extractParamsFromList(childNode); + } + }); + } + + private extractParamsFromList(parameterList: SyntaxNode) { + parameterList.children.forEach((parameterNode: SyntaxNode) => { + if (parameterNode.type === 'parameter') { + this.result.parameters.push({ + name: parameterNode.children?.filter((n: SyntaxNode) => n.type === 'identifier').pop()?.text + }); + } + }); + } +} diff --git a/src/parsers/index.ts b/src/parsers/index.ts index a7f2615b..eae79627 100644 --- a/src/parsers/index.ts +++ b/src/parsers/index.ts @@ -4,6 +4,7 @@ import { ValueOf } from '../types'; import { BashParserService } from './bash.service'; import { CParserService } from './c.service'; import { CppParserService } from './cpp.service'; +import { CSharpParserService } from './cs.service'; import { JavaParserService } from './java.service'; import { LuaParserService } from './lua.service'; import { PhpParserService } from './php.service'; @@ -18,6 +19,7 @@ export type ParserService = | PythonParserService | CParserService | CppParserService + | CSharpParserService | BashParserService | RubyParserService | LuaParserService @@ -44,6 +46,9 @@ export function getParserService( case Language.CPP: return new CppParserService(...args); + case Language.CSHARP: + return new CSharpParserService(...args); + case Language.BASH: return new BashParserService(...args); diff --git a/src/vim-doge.d.ts b/src/vim-doge.d.ts index 89bbea36..61017d67 100644 --- a/src/vim-doge.d.ts +++ b/src/vim-doge.d.ts @@ -2,6 +2,7 @@ declare module 'tree-sitter-php'; declare module 'tree-sitter-typescript/tsx'; declare module 'tree-sitter-python'; declare module 'tree-sitter-c'; +declare module 'tree-sitter-c-sharp'; declare module 'tree-sitter-cpp'; declare module 'tree-sitter-bash'; declare module 'tree-sitter-ruby'; diff --git a/test/filetypes/cs/class-constructor.vader b/test/filetypes/cs/class-constructor.vader new file mode 100644 index 00000000..1dc966b5 --- /dev/null +++ b/test/filetypes/cs/class-constructor.vader @@ -0,0 +1,53 @@ +# ============================================================================== +# Construcor without params +# ============================================================================== +Given cs (constructor without params): + public class MyClass + { + public MyClass() + { + } + } + +Do (run doge): + :3\ + \ + +Expect cs (constructor with summary only): + public class MyClass + { + /// + /// [TODO:description] + /// + public MyClass() + { + } + } + +# ============================================================================== +# Constructor with params +# ============================================================================== +Given cs (constructor without params): + public class MyClass + { + public MyClass(string arg1, MyClass arg2) + { + } + } + +Do (run doge): + :3\ + \ + +Expect cs (constructor with summary only): + public class MyClass + { + /// + /// [TODO:description] + /// + /// [TODO:description] + /// [TODO:description] + public MyClass(string arg1, MyClass arg2) + { + } + } diff --git a/test/filetypes/cs/class-members.vader b/test/filetypes/cs/class-members.vader new file mode 100644 index 00000000..cca5810f --- /dev/null +++ b/test/filetypes/cs/class-members.vader @@ -0,0 +1,139 @@ +# ============================================================================== +# Class constant +# ============================================================================== +Given cs (class constant): + public class MyClass + { + public const doule PI = 3.14; + } + +Do (run doge): + :3\ + \ + +Expect cs (class constant with summary block): + public class MyClass + { + /// + /// [TODO:description] + /// + public const doule PI = 3.14; + } + +# ============================================================================== +# Class variable +# ============================================================================== +Given cs (class variable): + public class MyClass + { + public int one = 1; + } + +Do (run doge): + :3\ + \ + +Expect cs (class variable with summary block): + public class MyClass + { + /// + /// [TODO:description] + /// + public int one = 1; + } + +# ============================================================================== +# Class property +# ============================================================================== +Given cs (class propery): + public class MyClass + { + public string Prop + { + get + { + return "catch me if you can"; + } + } + } + +Do (run doge): + :3\ + \ + +Expect cs (class property with summary block): + public class MyClass + { + /// + /// [TODO:description] + /// + public string Prop + { + get + { + return "catch me if you can"; + } + } + } + +# ============================================================================== +# Class property (setter) +# ============================================================================== +Given cs (class propery with setter): + public class MyClass + { + public string Prop + { + get + { + return "catch me if you can"; + } + set + { + } + } + } + +Do (run doge): + :3\ + \ + +Expect cs (class property with setter with summary block): + public class MyClass + { + /// + /// [TODO:description] + /// + public string Prop + { + get + { + return "catch me if you can"; + } + set + { + } + } + } + +# ============================================================================== +# Class event +# ============================================================================== +Given cs (class event): + public class MyClass + { + public event Del EventHappened; + } + +Do (run doge): + :3\ + \ + +Expect cs (class event with summary block): + public class MyClass + { + /// + /// [TODO:description] + /// + public event Del EventHappened; + } diff --git a/test/filetypes/cs/class-methods.vader b/test/filetypes/cs/class-methods.vader new file mode 100644 index 00000000..8189b607 --- /dev/null +++ b/test/filetypes/cs/class-methods.vader @@ -0,0 +1,339 @@ +# ============================================================================== +# Class method without params or returns +# ============================================================================== +Given cs (class method without params or return): + public class MyClass + { + public void Meth() + { + } + } + +Do (run doge): + :3\ + \ + +Expect cs (class method with only summary block): + public class MyClass + { + /// + /// [TODO:description] + /// + public void Meth() + { + } + } + +# ============================================================================== +# Class method with params but no return +# ============================================================================== +Given cs (class method with params but no return): + public class MyClass + { + public void Meth(string arg1) + { + } + } + +Do (run doge): + :3\ + \ + +Expect cs (class method with summary and params): + public class MyClass + { + /// + /// [TODO:description] + /// + /// [TODO:description] + public void Meth(string arg1) + { + } + } + +# ============================================================================== +# Class method with params and return +# ============================================================================== +Given cs (class method with params and return): + public class MyClass + { + public MyClass Meth(string arg1, int arg2) + { + } + } + +Do (run doge): + :3\ + \ + +Expect cs (class method with summary, params and return blocks): + public class MyClass + { + /// + /// [TODO:description] + /// + /// [TODO:description] + /// [TODO:description] + /// [TODO:description] + public MyClass Meth(string arg1, int arg2) + { + } + } + +# ============================================================================== +# Class method with complex params and return +# ============================================================================== +Given cs (class method with complex params and return): + public class MyClass + { + public MyClass Meth(List arg1, int[] arg2) + { + } + } + +Do (run doge): + :3\ + \ + +Expect cs (class method with summary, params and return blocks): + public class MyClass + { + /// + /// [TODO:description] + /// + /// [TODO:description] + /// [TODO:description] + /// [TODO:description] + public MyClass Meth(List arg1, int[] arg2) + { + } + } + + + +# ============================================================================== +# static method without params or returns +# ============================================================================== +Given cs (static method without params or return): + public class MyClass + { + public static void Meth() + { + } + } + +Do (run doge): + :3\ + \ + +Expect cs (static method with only summary block): + public class MyClass + { + /// + /// [TODO:description] + /// + public static void Meth() + { + } + } + +# ============================================================================== +# static method with params but no return +# ============================================================================== +Given cs (static method with params but no return): + public class MyClass + { + public static void Meth(string arg1) + { + } + } + +Do (run doge): + :3\ + \ + +Expect cs (static method with summary and params): + public class MyClass + { + /// + /// [TODO:description] + /// + /// [TODO:description] + public static void Meth(string arg1) + { + } + } + +# ============================================================================== +# static method with params and return +# ============================================================================== +Given cs (static method with params and return): + public class MyClass + { + public static MyClass Meth(string arg1, int arg2) + { + } + } + +Do (run doge): + :3\ + \ + +Expect cs (static method with summary, params and return blocks): + public class MyClass + { + /// + /// [TODO:description] + /// + /// [TODO:description] + /// [TODO:description] + /// [TODO:description] + public static MyClass Meth(string arg1, int arg2) + { + } + } + +# ============================================================================== +# Class operator without params or returns +# ============================================================================== +Given cs (class operator without params or return): + public class MyClass + { + public static void operator +() + { + } + } + +Do (run doge): + :3\ + \ + +Expect cs (class operator with only summary block): + public class MyClass + { + /// + /// [TODO:description] + /// + public static void operator +() + { + } + } + +# ============================================================================== +# Class operator with params but no return +# ============================================================================== +Given cs (class operator with params but no return): + public class MyClass + { + public static void operator +(ref TestCS first, in TestCS second) + } + +Do (run doge): + :3\ + \ + +Expect cs (class operator with summary and params): + public class MyClass + { + /// + /// [TODO:description] + /// + /// [TODO:description] + /// [TODO:description] + public static void operator +(ref TestCS first, in TestCS second) + } + +# ============================================================================== +# Class operator with params and return +# ============================================================================== +Given cs (class operator with params and return): + public class MyClass + { + public static TestCS operator +(ref TestCS first, in TestCS second) + } + +Do (run doge): + :3\ + \ + +Expect cs (class operator with summary, params and return blocks): + public class MyClass + { + /// + /// [TODO:description] + /// + /// [TODO:description] + /// [TODO:description] + /// [TODO:description] + public static TestCS operator +(ref TestCS first, in TestCS second) + } + +# ============================================================================== +# Class delegate without params or returns +# ============================================================================== +Given cs (class delegate without params or return): + public class MyClass + { + public delegate void Del(); + } + +Do (run doge): + :3\ + \ + +Expect cs (class delegate with only summary block): + public class MyClass + { + /// + /// [TODO:description] + /// + public delegate void Del(); + } + +# ============================================================================== +# Class delegate with params but no return +# ============================================================================== +Given cs (class delegate with params but no return): + public class MyClass + { + public delegate void Del(string arg1); + } + +Do (run doge): + :3\ + \ + +Expect cs (class delegate with summary and params): + public class MyClass + { + /// + /// [TODO:description] + /// + /// [TODO:description] + public delegate void Del(string arg1); + } + +# ============================================================================== +# Class delegate with params and return +# ============================================================================== +Given cs (class delegate with params and return): + public class MyClass + { + public delegate string Del(string arg1, int dontForgetMe); + } + +Do (run doge): + :3\ + \ + +Expect cs (class delegate with summary, params and return blocks): + public class MyClass + { + /// + /// [TODO:description] + /// + /// [TODO:description] + /// [TODO:description] + /// [TODO:description] + public delegate string Del(string arg1, int dontForgetMe); + } diff --git a/test/filetypes/cs/class.vader b/test/filetypes/cs/class.vader new file mode 100644 index 00000000..55f1be86 --- /dev/null +++ b/test/filetypes/cs/class.vader @@ -0,0 +1,45 @@ +# ============================================================================== +# Plain Class +# ============================================================================== +Given cs (plain class): + public class Myclass + { + } + +Do (run doge): + \ + +Expect cs (plain class with summary): + /// + /// [TODO:description] + /// + public class Myclass + { + } + +# ============================================================================== +# Nested Class +# ============================================================================== +Given cs (plain class): + public class Myclass + { + private class SubClass + { + } + } + +Do (run doge): + :3\ + \ + +Expect cs (plain class with summary): + public class Myclass + { + /// + /// [TODO:description] + /// + private class SubClass + { + } + } + diff --git a/test/filetypes/cs/enum.vader b/test/filetypes/cs/enum.vader new file mode 100644 index 00000000..17b8f6b0 --- /dev/null +++ b/test/filetypes/cs/enum.vader @@ -0,0 +1,24 @@ +# ============================================================================== +# Enum +# ============================================================================== +Given cs (enum): + public enum TestEnum + { + One, + Two, + Three + } + +Do (trigger doge): + \ + +Expect cs (enum with summary): + /// + /// [TODO:description] + /// + public enum TestEnum + { + One, + Two, + Three + }