├── .gitattributes ├── .vscode ├── settings.json └── launch.json ├── .gitignore ├── .npmignore ├── .eslintrc.json ├── fetch └── index.js ├── src ├── debug.js ├── index.js ├── csharp │ ├── README.md │ ├── CSharpPreprocessorParser.g4 │ ├── CSharpParser.g4 │ └── CSharpLexer.g4 └── parser.js ├── test ├── index.js ├── AllInOne.cs └── AllInOne.Formatted.cs ├── .travis.yml ├── LICENSE ├── package.json └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[csharp]": { 3 | "files.trimTrailingWhitespace": false 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | .antlr 4 | bin 5 | 6 | src/csharp/*.interp 7 | src/csharp/*.tokens 8 | src/csharp/*.js 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | *.java 3 | *.interp 4 | *.tokens 5 | *.g4 6 | *.cs 7 | .eslintrc.json 8 | .gitignore 9 | .npmignore 10 | .travis.yml 11 | yarn-error.log 12 | yarn.lock 13 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "extends": ["eslint:recommended", "prettier"], 6 | "parserOptions": { 7 | "ecmaVersion": 2018 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /fetch/index.js: -------------------------------------------------------------------------------- 1 | const https = require("https"); 2 | var fs = require("fs"); 3 | var bin = "./bin"; 4 | 5 | if (!fs.existsSync(bin)) { 6 | fs.mkdirSync(bin); 7 | } 8 | 9 | const file = fs.createWriteStream("bin/antlr-4.7.2-complete.jar"); 10 | https.get("https://www.antlr.org/download/antlr-4.7.2-complete.jar", response => 11 | response.pipe(file) 12 | ); 13 | -------------------------------------------------------------------------------- /src/debug.js: -------------------------------------------------------------------------------- 1 | const prettier = require("prettier"); 2 | const fs = require("fs"); 3 | 4 | if (process.argc < 3) { 5 | throw new Error("Missing file to format"); 6 | } 7 | 8 | const file = process.argv[2]; 9 | 10 | const referenceCode = fs.readFileSync(file, "utf8"); 11 | 12 | const formattedCode = prettier.format(referenceCode, { 13 | parser: "cs", 14 | plugins: ["."], 15 | printWidth: 120 16 | }); 17 | 18 | fs.writeFileSync(file, formattedCode, "utf8"); 19 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const prettier = require("prettier"); 2 | const fs = require("fs"); 3 | 4 | const tests = ["AllInOne"]; 5 | 6 | for (let test of tests) { 7 | const referenceFile = `test/${test}.cs`; 8 | const formattedFile = `test/${test}.Formatted.cs`; 9 | 10 | const referenceCode = fs.readFileSync(referenceFile, "utf8"); 11 | 12 | const formattedCode = prettier.format(referenceCode, { 13 | parser: "cs", 14 | plugins: ["."] 15 | }); 16 | 17 | fs.writeFileSync(formattedFile, formattedCode, "utf8"); 18 | } 19 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const parser = require("./parser"); 4 | const printer = require("./printer"); 5 | 6 | const languages = [ 7 | { 8 | name: "C#", 9 | parsers: ["cs"], 10 | tmScope: "source.cs", 11 | aceMode: "csharp", 12 | codemirrorMode: "clike", 13 | extensions: [".cs", ".cake", ".cshtml", ".csx"], 14 | vscodeLanguageIds: ["csharp"], 15 | linguistLanguageId: 42 16 | } 17 | ]; 18 | 19 | const parsers = { 20 | cs: parser 21 | }; 22 | 23 | const printers = { 24 | cs: printer 25 | }; 26 | 27 | const options = {}; 28 | 29 | module.exports = { 30 | languages, 31 | printers, 32 | parsers, 33 | options, 34 | defaultOptions: { 35 | tabWidth: 4 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Debug regression tests", 11 | "runtimeExecutable": "yarn", 12 | "runtimeArgs": ["test:debug"], 13 | "port": 9229, 14 | "outputCapture": "std" 15 | }, 16 | { 17 | "type": "node", 18 | "request": "launch", 19 | "name": "Debug on a real-life file", 20 | "runtimeExecutable": "yarn", 21 | "runtimeArgs": ["prettier:debug", "File.cs"], 22 | "port": 9229, 23 | "outputCapture": "std" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: 2 | - node_js 3 | - java 4 | jdk: 5 | - openjdk11 6 | node_js: 7 | - stable 8 | cache: 9 | yarn: true 10 | directories: 11 | - node_modules 12 | script: 13 | - yarn fetch-antlr && yarn generate-parser && yarn test 14 | deploy: 15 | provider: npm 16 | email: warren.seine@gmail.com 17 | api_key: 18 | secure: iBiBmHEI0ZSa+JQM/ccvvraGUqOoqPEdZUfg5BBOG36/fc1pST9W83PAQS3qseygnHbFJ6aNh5tDSkoovQyhMYusywH9Sb8Ay48bdBcEqj+j66x64zit/sVZMGPERzBee1sGXP0MLn8ZPo+Me6nnctVoJvAABHV06wNzEer2RBVbjtDO3u5Fz1Sun1J9otFMeDkF0+HrrBJB+/0CytfYncW4S12NMbDjRl3noVWMKMG8TrbWpnfP7dDqU6WyH7y77L881ZjvbrYYVBoAdAnKgJCItQs9Ks4a/W3dSd1RSHk5paeL2+ULaOXkh12xGkEMu4+t09XZe2gsgRbfs8pK6TmZv8q5mi2cP79c81GLddxVK1cxlb5BXL1JVRVcjKitnJWswV8BBPjGl/U1zyIHruwgG4Eo5bgM/NzMS4G3MqI40bMR4hRJaFhwm6QrGMMqmmVbovYAoza+Eho/lYI6GmNOwFqRCmawC0cU7EMgfGNbo+8H+i0OBzepd8zku1MwbmA73E/9q5YoX7NgTQzaTOIb0YdE9HTi9qLaMKY3MlJt9Jnfi5KQ69cPAVeYJmcNgT/hfZSf/gJVXby4/Yc8tCV7TRwv67gpfdcrJNKZdIPwsqJKN8jx5vG8rdfhyUNqalWPfoNFKfYwr+MZWBJrId0tTUG9wm9e7yCLHI1/IUY= 19 | on: 20 | tags: true 21 | repo: warrenseine/prettier-plugin-csharp 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Warren Seine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prettier-plugin-csharp", 3 | "version": "0.6.0-development", 4 | "description": "Prettier C# Plugin", 5 | "repository": "warrenseine/prettier-plugin-csharp", 6 | "author": "Warren Seine <@warrenseine>", 7 | "license": "MIT", 8 | "main": "src", 9 | "dependencies": { 10 | "antlr4": "^4.7.2", 11 | "is-alphanumerical": "^1.0.2", 12 | "lodash": "^4.17.11", 13 | "prettier": "^1.15.3" 14 | }, 15 | "scripts": { 16 | "format": "prettier --write \"{src,test}/**/*.js\"", 17 | "lint": "eslint src/**", 18 | "test:debug": "node --lazy --inspect-brk test", 19 | "test": "node test && git diff --exit-code -- test/*.Formatted.cs", 20 | "fetch-antlr": "node fetch", 21 | "generate-parser": "java -jar bin/antlr-4.7.2-complete.jar -Dlanguage=JavaScript src/csharp/*.g4", 22 | "prettier": "prettier --plugin=. --parser=cs", 23 | "prettier:debug": "node --lazy --inspect-brk src/debug.js" 24 | }, 25 | "devDependencies": { 26 | "eslint": "^5.11.1", 27 | "eslint-config-prettier": "^3.3.0", 28 | "husky": "^1.3.1", 29 | "lint-staged": "^8.1.0" 30 | }, 31 | "husky": { 32 | "hooks": { 33 | "pre-commit": "lint-staged" 34 | } 35 | }, 36 | "lint-staged": { 37 | "*.{js,json,css,md}": [ 38 | "prettier --write", 39 | "git add" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/csharp/README.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | C# grammar with full support of 4 | [C# 6 features](https://github.com/dotnet/roslyn/wiki/New-Language-Features-in-C%23-6) and below. 5 | CSharpLexer.g4 and CSharpPreprocessor.g4 are runtime dependent (contains specific actions), 6 | because of directive preprocessors and string interpolation (these things make grammar context-sensitive). 7 | C# and Java versions supported. But CSharpParser.g4 is runtime independent. 8 | This ANTLR 4 C# grammar is based on the C# ANTLR 3 grammar from 9 | [ChristianWulf/CSharpGrammar](https://github.com/ChristianWulf/CSharpGrammar). 10 | 11 | ## Using 12 | 13 | Due to uncompiled code can be placed after such preprocessor directives: 14 | 15 | ```CSharp 16 | #if SILVERLIGHT && WINDOWS_PHONE || DEBUG || foo == true 17 | // Some code 18 | #endif 19 | ``` 20 | 21 | at first stage it's necessary to calculate whether condition satisfied in directive. 22 | This step performed by runtime code in gist: 23 | [CSharpAntlrParser](https://gist.github.com/KvanTTT/d95579de257531a3cc15). 24 | 25 | ## Testing 26 | 27 | I put into repository **AllInOne.cs** file (from Roslyn) with most common syntax and also 28 | I tested this grammar on the following most popular .NET projects: 29 | 30 | - [Roslyn-1.1.1](https://github.com/dotnet/roslyn). 31 | - [Corefx-1.0.0-rc2](https://github.com/dotnet/corefx). 32 | - [EntityFramework-7.0.0-rc1](https://github.com/aspnet/EntityFramework). 33 | - [aspnet-mvc-6.0.0-rc1](https://github.com/aspnet/Mvc). 34 | - [ImageProcessor-2.3.0](https://github.com/JimBobSquarePants/ImageProcessor). 35 | - [Newtonsoft.Json-8.0.2](https://github.com/JamesNK/Newtonsoft.Json). 36 | 37 | There are some problems with deep recursion ([TestData.g.cs in CoreFx](https://github.com/dotnet/corefx/blob/master/src/Common/tests/System/Xml/XmlCoreTest/TestData.g.cs)), 38 | unicode chars 39 | ([sjis.cs in Roslyn](https://github.com/dotnet/roslyn/blob/master/src/Compilers/Test/Resources/Core/Encoding/sjis.cs)) 40 | and preprocessor directives. 41 | 42 | ANTLR parser from this grammar approximately ~5-7 slowly than Roslyn parser. 43 | 44 | ## License 45 | 46 | [Eclipse Public License - v 1.0](http://www.eclipse.org/legal/epl-v10.html) 47 | -------------------------------------------------------------------------------- /src/parser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const antlr4 = require("antlr4"); 4 | const { ErrorListener } = require("antlr4/error/ErrorListener"); 5 | const CSharpLexer = require("./csharp/CSharpLexer"); 6 | const CSharpParser = require("./csharp/CSharpParser"); 7 | const _ = require("lodash"); 8 | 9 | class ThrowingErrorListener extends ErrorListener { 10 | syntaxError(recognizer, offendingSymbol, line, column, msg, e) { 11 | const error = new SyntaxError(msg + " (" + line + ":" + column + ")"); 12 | error.loc = { 13 | start: { 14 | line, 15 | column 16 | } 17 | }; 18 | throw error; 19 | } 20 | } 21 | 22 | function parseCSharp(text) { 23 | const errorListener = new ThrowingErrorListener(); 24 | const chars = new antlr4.InputStream(text); 25 | const lexer = new CSharpLexer.CSharpLexer(chars); 26 | lexer.removeErrorListeners(); 27 | lexer.addErrorListener(errorListener); 28 | 29 | const tokens = new antlr4.CommonTokenStream(lexer); 30 | const parser = new CSharpParser.CSharpParser(tokens); 31 | parser.removeErrorListeners(); 32 | parser.addErrorListener(errorListener); 33 | 34 | const compilationUnit = parser.compilation_unit(); 35 | const result = simplifyTree(compilationUnit); 36 | result.comments = tokens.tokens 37 | .filter( 38 | token => 39 | token.channel == CSharpLexer.CSharpLexer.COMMENTS_CHANNEL || 40 | token.channel == CSharpLexer.CSharpLexer.DIRECTIVE 41 | ) 42 | .map(token => buildComment(token)); 43 | return result; 44 | } 45 | 46 | function getNodeTypeFromChannel(token) { 47 | switch (token.channel) { 48 | case CSharpLexer.CSharpLexer.COMMENTS_CHANNEL: 49 | return "comment"; 50 | case CSharpLexer.CSharpLexer.DIRECTIVE: 51 | return "directive"; 52 | } 53 | return "unknown"; 54 | } 55 | 56 | function buildComment(token) { 57 | return { 58 | nodeType: getNodeTypeFromChannel(token), 59 | start: token.start, 60 | end: token.stop, 61 | lineStart: token.line, 62 | lineEnd: token.line, 63 | value: token.text 64 | }; 65 | } 66 | 67 | const nodeNameRegex = /Context$|NodeImpl$/; 68 | 69 | function simplifyTree(node) { 70 | const nodeType = _.snakeCase( 71 | node.constructor.name.replace(nodeNameRegex, "") 72 | ); 73 | 74 | if (!node.children) { 75 | if (node.symbol) { 76 | return { 77 | nodeType, 78 | start: node.symbol.start, 79 | end: node.symbol.stop, 80 | lineStart: node.symbol.line, 81 | lineEnd: node.symbol.line, 82 | value: node.symbol.text 83 | }; 84 | } 85 | } 86 | 87 | const children = {}; 88 | const orderedChildren = []; 89 | 90 | for (let child of node.children) { 91 | child = simplifyTree(child); 92 | if (!children[child.nodeType]) { 93 | children[child.nodeType] = []; 94 | } 95 | children[child.nodeType].push(child); 96 | orderedChildren.push(child); 97 | } 98 | 99 | return { 100 | ...children, 101 | children: orderedChildren, 102 | nodeType, 103 | start: node.start.start, 104 | end: node.stop.stop, 105 | lineStart: node.start.line, 106 | lineEnd: node.stop.line 107 | }; 108 | } 109 | 110 | function loc(prop) { 111 | return function(node) { 112 | return node[prop]; 113 | }; 114 | } 115 | 116 | module.exports = { 117 | parse: parseCSharp, 118 | astFormat: "cs", 119 | locStart: loc("start"), 120 | locEnd: loc("end") 121 | }; 122 | -------------------------------------------------------------------------------- /src/csharp/CSharpPreprocessorParser.g4: -------------------------------------------------------------------------------- 1 | // Eclipse Public License - v 1.0, http://www.eclipse.org/legal/epl-v10.html 2 | // Copyright (c) 2013, Christian Wulf (chwchw@gmx.de) 3 | // Copyright (c) 2016-2017, Ivan Kochurkin (kvanttt@gmail.com), Positive Technologies. 4 | 5 | parser grammar CSharpPreprocessorParser; 6 | 7 | options { tokenVocab=CSharpLexer; } 8 | 9 | @parser::members 10 | { 11 | this.conditions = [true]; 12 | this.conditionalSymbols = new Set(["DEBUG"]); 13 | 14 | this.prototype.allConditions = function() { 15 | return this.conditions.indexOf(false) < 0; 16 | } 17 | } 18 | 19 | preprocessor_directive returns [boolean value] 20 | : DEFINE CONDITIONAL_SYMBOL directive_new_line_or_sharp{ this.conditionalSymbols.add($CONDITIONAL_SYMBOL.text); 21 | $value = this.allConditions(); } #preprocessorDeclaration 22 | 23 | | UNDEF CONDITIONAL_SYMBOL directive_new_line_or_sharp{ this.conditionalSymbols.remove($CONDITIONAL_SYMBOL.text); 24 | $value = this.allConditions(); } #preprocessorDeclaration 25 | 26 | | IF expr=preprocessor_expression directive_new_line_or_sharp 27 | { $value = $expr.value === "true" && this.allConditions();; this.conditions.push($expr.value === "true"); } 28 | #preprocessorConditional 29 | 30 | | ELIF expr=preprocessor_expression directive_new_line_or_sharp 31 | { if (!this.conditions[this.conditions.length - 1]) { this.conditions.pop(); $value = $expr.value === "true" && this.allConditions();; 32 | this.conditions.push($expr.value === "true"); } else $value = false;; } 33 | #preprocessorConditional 34 | 35 | | ELSE directive_new_line_or_sharp 36 | { if (!this.conditions[this.conditions.length - 1]) { this.conditions.pop(); $value = true && this.allConditions();; this.conditions.push(true); } 37 | else $value = false;; } #preprocessorConditional 38 | 39 | | ENDIF directive_new_line_or_sharp { this.conditions.pop(); $value = this.conditions.peek();; } 40 | #preprocessorConditional 41 | | LINE (DIGITS STRING? | DEFAULT | DIRECTIVE_HIDDEN) directive_new_line_or_sharp { $value = this.allConditions(); } 42 | #preprocessorLine 43 | 44 | | ERROR TEXT directive_new_line_or_sharp { $value = this.allConditions(); } #preprocessorDiagnostic 45 | 46 | | WARNING TEXT directive_new_line_or_sharp { $value = this.allConditions(); } #preprocessorDiagnostic 47 | 48 | | REGION TEXT? directive_new_line_or_sharp { $value = this.allConditions(); } #preprocessorRegion 49 | 50 | | ENDREGION TEXT? directive_new_line_or_sharp { $value = this.allConditions(); } #preprocessorRegion 51 | 52 | | PRAGMA TEXT directive_new_line_or_sharp { $value = this.allConditions(); } #preprocessorPragma 53 | ; 54 | 55 | directive_new_line_or_sharp 56 | : DIRECTIVE_NEW_LINE 57 | | EOF 58 | ; 59 | 60 | preprocessor_expression returns [String value] 61 | : TRUE { $value = "true"; } 62 | | FALSE { $value = "false"; } 63 | | CONDITIONAL_SYMBOL { $value = this.conditionalSymbols.has($CONDITIONAL_SYMBOL.text) ? "true" : "false"; } 64 | | OPEN_PARENS expr=preprocessor_expression CLOSE_PARENS { $value = $expr.value; } 65 | | BANG expr=preprocessor_expression { $value = $expr.value === "true" ? "false" : "true"; } 66 | | expr1=preprocessor_expression OP_EQ expr2=preprocessor_expression 67 | { $value = ($expr1.value == $expr2.value ? "true" : "false"); } 68 | | expr1=preprocessor_expression OP_NE expr2=preprocessor_expression 69 | { $value = ($expr1.value != $expr2.value ? "true" : "false"); } 70 | | expr1=preprocessor_expression OP_AND expr2=preprocessor_expression 71 | { $value = ($expr1.value === "true" && $expr2.value === "true" ? "true" : "false"); } 72 | | expr1=preprocessor_expression OP_OR expr2=preprocessor_expression 73 | { $value = ($expr1.value === "true" || $expr2.value === "true" ? "true" : "false"); } 74 | ; 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | ❌ This repository is unmaintained and the project is incomplete, don't expect anything else than a toy C# project to reformat correctly! ❌ 3 |
4 |5 | Have a look at CSharpier instead. 6 |
7 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
114 |
115 |
116 |
117 | Warren Seine
118 |
119 | |
120 |