├── .gitignore ├── images ├── animation.gif └── animation2.gif ├── .vscodeignore ├── .vscode ├── extensions.json ├── tasks.json └── launch.json ├── jsconfig.json ├── .eslintrc.json ├── parser ├── README.md └── csharp.pegjs ├── test ├── extension.test.js └── index.js ├── extension.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── vsc-extension-quickstart.md ├── package.json ├── single.js ├── util.js ├── multi.js └── views └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | parser/csharp.js 6 | -------------------------------------------------------------------------------- /images/animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bonelol/TypeSharp/HEAD/images/animation.gif -------------------------------------------------------------------------------- /images/animation2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bonelol/TypeSharp/HEAD/images/animation2.gif -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | test/** 4 | .gitignore 5 | vsc-extension-quickstart.md 6 | **/jsconfig.json 7 | **/*.map 8 | **/.eslintrc.json 9 | 10 | images/** 11 | parser/csharp.pegjs -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "checkJs": false, /* Typecheck .js files. */ 6 | "lib": [ 7 | "es6" 8 | ] 9 | }, 10 | "exclude": [ 11 | "node_modules" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "install", 9 | "problemMatcher": [] 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "parserOptions": { 9 | "ecmaFeatures": { 10 | "jsx": true 11 | }, 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "no-const-assign": "warn", 16 | "no-this-before-super": "warn", 17 | "no-undef": "warn", 18 | "no-unreachable": "warn", 19 | "no-unused-vars": "warn", 20 | "constructor-super": "warn", 21 | "valid-typeof": "warn" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /parser/README.md: -------------------------------------------------------------------------------- 1 | ## Support 2 | - Class 3 | * Modifer 4 | * Attribute 5 | * Generic 6 | * Constructor 7 | - base 8 | * Inheritance 9 | * Member 10 | - Property 11 | - Method 12 | - Field 13 | - Event 14 | - Delegate 15 | 16 | - Property, Field 17 | * Modifer 18 | * Attribute 19 | * Generic 20 | * Initializer 21 | * Lambda Experssion 22 | 23 | - Method 24 | * Modifer 25 | * Attribute 26 | * Generic 27 | * Type Constraint 28 | * this, out, ref keywords 29 | 30 | - Enum 31 | * Modifer 32 | * Attribute -------------------------------------------------------------------------------- /test/extension.test.js: -------------------------------------------------------------------------------- 1 | /* global suite, test */ 2 | 3 | // 4 | // Note: This example test is leveraging the Mocha test framework. 5 | // Please refer to their documentation on https://mochajs.org/ for help. 6 | // 7 | 8 | // The module 'assert' provides assertion methods from node 9 | const assert = require('assert'); 10 | 11 | // You can import and use all API from the 'vscode' module 12 | // as well as import your extension to test it 13 | // const vscode = require('vscode'); 14 | // const myExtension = require('../extension'); 15 | 16 | // Defines a Mocha test suite to group tests of similar kind together 17 | suite("Extension Tests", function() { 18 | 19 | // Defines a Mocha unit test 20 | test("Something 1", function() { 21 | assert.equal(-1, [1, 2, 3].indexOf(5)); 22 | assert.equal(-1, [1, 2, 3].indexOf(0)); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 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 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ] 16 | }, 17 | { 18 | "name": "Extension Tests", 19 | "type": "extensionHost", 20 | "request": "launch", 21 | "runtimeExecutable": "${execPath}", 22 | "args": [ 23 | "--extensionDevelopmentPath=${workspaceFolder}", 24 | "--extensionTestsPath=${workspaceFolder}/test" 25 | ] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /extension.js: -------------------------------------------------------------------------------- 1 | const registerConvertCommand = require('./single'); 2 | const registerOpenCommand = require('./multi'); 3 | 4 | // this method is called when your extension is activated 5 | // your extension is activated the very first time the command is executed 6 | 7 | /** 8 | * @param {vscode.ExtensionContext} context 9 | */ 10 | function activate(context) { 11 | // The command has been defined in the package.json file 12 | // Now provide the implementation of the command with registerCommand 13 | // The commandId parameter must match the command field in package.json 14 | let disposable1 = registerConvertCommand(context); 15 | context.subscriptions.push(disposable1); 16 | 17 | let disposable2 = registerOpenCommand(context); 18 | context.subscriptions.push(disposable2); 19 | } 20 | 21 | exports.activate = activate; 22 | 23 | // this method is called when your extension is deactivated 24 | function deactivate() {} 25 | 26 | module.exports = { 27 | activate, 28 | deactivate 29 | } 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "typesharp" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [0.6.0] - 2020-02-10 8 | 9 | - Byte type will be convert to number in output. 10 | - Use Prettier for output formatting. 11 | 12 | ## [0.3.0] - 2019-07-20 13 | 14 | - Fix constructor parsing issue 15 | - Add an option to generate all fields as optional 16 | - Support nullable fields 17 | 18 | ## [0.2.0] - 2019-05-15 19 | 20 | - Add settings 21 | 22 | ## [0.1.2] - 2019-05-10 23 | 24 | - Support converting c# classes to interfaces 25 | 26 | ## [0.1.0] - 2019-05-07 27 | 28 | - Support convert multiple files. Run command 'Open TypeSharp convert window' 29 | 30 | ## [0.0.3] - 2019-03-28 31 | 32 | - Support enums 33 | - Support initializers (property and field) 34 | - Supoort calling base constructor 35 | - Support lambda expression in properties 36 | - Fix bugs 37 | 38 | ## [0.0.2] - 2019-03-27 39 | 40 | - Initial release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Max 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 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testsRoot: string, clb: (error: Error, failures?: number) => void): void 9 | // that the extension host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | const testRunner = require('vscode/lib/testrunner'); 14 | 15 | // You can directly control Mocha options by configuring the test runner below 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options 17 | // for more info 18 | testRunner.configure({ 19 | ui: 'tdd', // the TDD UI is being used in extension.test.js (suite, test, etc.) 20 | useColors: true // colored output from test results 21 | }); 22 | 23 | module.exports = testRunner; 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeSharp 2 | 3 | Convert C# POCOs to TypeScript 4 | 5 | ## How to use 6 | 7 | In editor window press CTL+ALT+T OR run command 'Convert C# to TypeScript'. To convert multiple files, run command 'Open TypeSharp convert window' 8 | 9 |  10 | 11 | Convert multiple files 12 |  13 | 14 | ## Requirements 15 | 16 | To generate parser, you will need [PEG.js](https://pegjs.org/). 17 | 18 | ## Known Issues 19 | 20 | N/A 21 | 22 | ## Release Notes 23 | 24 | ### [0.6.0] - 2020-02-10 25 | 26 | - Byte type will be convert to number in output. 27 | - Use Prettier for output formatting. 28 | 29 | ### [0.3.0] - 2019-07-20 30 | 31 | - Fix constructor parsing issue 32 | - Fix camel case issue 33 | - Add an option to generate all fields as optional 34 | - Support nullable fields 35 | 36 | ### [0.2.0] - 2019-05-15 37 | 38 | - Add settings 39 | 40 | ### [0.1.2] - 2019-05-10 41 | 42 | - Support converting c# classes to interfaces 43 | 44 | ### [0.1.0] - 2019-05-07 45 | 46 | - Support convert multiple files. Run command 'Open TypeSharp convert window' 47 | 48 | ### [0.0.3] - 2019-03-28 49 | 50 | - Support enums 51 | - Support initializers (property and field) 52 | - Supoort calling base constructor 53 | - Support lambda expression in properties 54 | - Fix bugs 55 | 56 | ### [0.0.2] - 2019-03-27 57 | 58 | - Initial release 59 | -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `extension.js` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | * Press `F5` to open a new window with your extension loaded. 15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 16 | * Set breakpoints in your code inside `extension.js` to debug your extension. 17 | * Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 21 | * You can relaunch the extension from the debug toolbar after changing code in `extension.js`. 22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | ## Explore the API 25 | 26 | * You can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts`. 27 | 28 | ## Run tests 29 | 30 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 31 | * Press `F5` to run the tests in a new window with your extension loaded. 32 | * See the output of the test result in the debug console. 33 | * Make changes to `test/extension.test.js` or create new test files inside the `test` folder. 34 | * By convention, the test runner will only consider files matching the name pattern `**.test.js`. 35 | * You can create folders inside the `test` folder to structure your tests any way you want. 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typesharp", 3 | "displayName": "TypeSharp", 4 | "description": "Convert C# POCOs to TypeScript", 5 | "version": "0.6.0", 6 | "publisher": "bonelol", 7 | "repository": "https://github.com/Bonelol/TypeSharp", 8 | "engines": { 9 | "vscode": "^1.32.0" 10 | }, 11 | "categories": [ 12 | "Other", 13 | "Formatters" 14 | ], 15 | "keywords": [ 16 | "c#", 17 | "typescript", 18 | "csharp", 19 | "keybindings" 20 | ], 21 | "activationEvents": [ 22 | "onCommand:extension.typesharp.convert", 23 | "onCommand:extension.typesharp.open" 24 | ], 25 | "main": "./extension.js", 26 | "contributes": { 27 | "commands": [ 28 | { 29 | "command": "extension.typesharp.convert", 30 | "title": "Convert C# to TypeScript" 31 | }, 32 | { 33 | "command": "extension.typesharp.open", 34 | "title": "Open TypeSharp convert window" 35 | } 36 | ], 37 | "keybindings": [ 38 | { 39 | "command": "extension.typesharp.convert", 40 | "key": "ctrl+alt+t" 41 | } 42 | ], 43 | "configuration": { 44 | "type": "object", 45 | "title": "TypeSharp", 46 | "properties": { 47 | "typesharp.propertyConvention": { 48 | "type": "string", 49 | "default": "unchange", 50 | "enum": [ 51 | "nochange", 52 | "camel", 53 | "pascal" 54 | ], 55 | "enumDescriptions": [ 56 | "No change", 57 | "Camel Case", 58 | "Pascal Case" 59 | ], 60 | "description": "Options are camelCase, pascal and unchange. Default is unchange" 61 | }, 62 | "typesharp.newWindow": { 63 | "type": "boolean", 64 | "default": true, 65 | "description": "True to show converted results in a new window. Default is true" 66 | }, 67 | "typesharp.classToInterface": { 68 | "type": "boolean", 69 | "default": false, 70 | "description": "True to convert class to interface. Default is false" 71 | }, 72 | "typesharp.optionalField": { 73 | "type": "boolean", 74 | "default": false, 75 | "description": "True to output fields as optional. Default is false" 76 | }, 77 | "typesharp.prettier": { 78 | "type": "boolean", 79 | "default": true, 80 | "description": "Format outputs" 81 | } 82 | } 83 | } 84 | }, 85 | "scripts": { 86 | "postinstall": "node ./node_modules/vscode/bin/install", 87 | "test": "node ./node_modules/vscode/bin/test" 88 | }, 89 | "devDependencies": { 90 | "@types/mocha": "^2.2.42", 91 | "@types/node": "^10.17.11", 92 | "eslint": "^5.16.0", 93 | "typescript": "^3.7.4", 94 | "vscode": "^1.1.36" 95 | }, 96 | "dependencies": { 97 | "camelcase": "^5.3.1", 98 | "js-yaml": "^3.13.1", 99 | "prettier": "^1.19.1" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /single.js: -------------------------------------------------------------------------------- 1 | // The module 'vscode' contains the VS Code extensibility API 2 | // Import the module and reference it with the alias vscode in your code below 3 | const vscode = require('vscode'); 4 | const prettier = require('prettier'); 5 | const util = require('./util'); 6 | const parser = require('./parser/csharp'); 7 | 8 | function registerConvertCommand(context) { 9 | return vscode.commands.registerCommand('extension.typesharp.convert', function () { 10 | const editor = vscode.window.activeTextEditor; 11 | 12 | if(!editor){ 13 | return; 14 | } 15 | 16 | const content = editor.document.getText(); 17 | 18 | if(!content){ 19 | return; 20 | } 21 | 22 | const language = vscode.window.activeTextEditor.document.languageId; 23 | const isTypeScript = language === 'typescript'; 24 | // const isJavaScript = language === 'javascript'; 25 | 26 | // if(!isTypeScript && !isJavaScript) { 27 | // return; 28 | // } 29 | 30 | try { 31 | const parsed = parser.parse(content); 32 | const config = util.getConfiguration(); 33 | const outputOptions = { 34 | outputTs: true, 35 | interface: config.classToInterface, 36 | convention: config.propertyConvention, 37 | optional: config.optionalField 38 | } 39 | const members = parsed.members && parsed.members.length > 0 ? parsed.members : util.flatMap(parsed.namespace_blocks, namespace => namespace.members); 40 | const memberOutputs = members.map(c => util.createMemberOutput(c, outputOptions)); 41 | 42 | let output = memberOutputs.join('\n'); 43 | 44 | if(config.prettier){ 45 | output = prettier.format(output); 46 | } 47 | 48 | if(config.newWindow) { 49 | vscode.workspace.openTextDocument({ language:'typescript', content: output }) 50 | .then(doc => vscode.window.showTextDocument(doc, editor.viewColumn + 1)); 51 | return; 52 | } 53 | 54 | editor.edit(builder => { 55 | const document = editor.document; 56 | const lastLine = document.lineAt(document.lineCount - 1); 57 | 58 | const start = new vscode.Position(0, 0); 59 | const end = new vscode.Position(document.lineCount - 1, lastLine.text.length); 60 | 61 | builder.replace(new vscode.Range(start, end), output); 62 | }); 63 | } catch (error) { 64 | console.log(error); 65 | const message = error.name === 'SyntaxError' ? `${error.message}\nline: ${error.location.start.line}, column: ${error.location.start.column}` 66 | : (error.message || error); 67 | 68 | vscode.window.showInformationMessage(message); 69 | } 70 | }); 71 | } 72 | 73 | module.exports = registerConvertCommand; 74 | -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | const vscode = require('vscode'); 2 | const camelCase = require('camelCase'); 3 | const flatMap = (xs, f) => 4 | xs.reduce((acc,x) => 5 | acc.concat(f(x)), []); 6 | const TAB = ' ' 7 | 8 | function createMemberOutput(member, options) { 9 | if(member.type === 'class') { 10 | return createClassOutput(member, options); 11 | } 12 | 13 | if(member.type === 'enum' && options) { 14 | return createEnumOutput(member); 15 | } 16 | 17 | return ''; 18 | } 19 | 20 | function createClassOutput(c, options) { 21 | const properties = c.members.filter(m => m.type === 'property' && m.modifiers); 22 | const propertyOuts = properties.map(p => createPropertyOutput(p, options)); 23 | const exportType = options.interface ? 'interface' : 'class' 24 | 25 | return `export ${exportType} ${c.name} {\n${TAB}${propertyOuts.join(`\n${TAB}`)}\n}` 26 | } 27 | 28 | function createPropertyOutput(property, options) { 29 | let name = property.name; 30 | let optional = options.optional ? '?' : ''; 31 | 32 | if (options.convention === 'camel') { 33 | name = camelCase(name) 34 | } else if (options.convention === 'pascal') { 35 | name = name.replace(/\w+/g, 36 | function(w){return w[0].toUpperCase() + w.slice(1);}) 37 | } 38 | 39 | return options.outputTs ? `${name}${optional}: ${createTypeOutput(property.return_type)};` : name + ';'; 40 | } 41 | 42 | function createTypeOutput(type) { 43 | if(type.is_array) { 44 | return `${createTypeOutput(type.base_type)}${type.arrays.map(() => '[]').join('')}` 45 | } 46 | 47 | if(!type.is_generic) { 48 | return checkType(type); 49 | } 50 | 51 | const typeName = checkType(type.base_type); 52 | const output = `${typeName}<${type.generic_parameters.map(parameter => createTypeOutput(parameter)).join(',')}>` 53 | 54 | return type.is_nullable ? `${output} | undefined | null` : output; 55 | } 56 | 57 | function createEnumOutput(e) { 58 | const memberOuts = e.members.map(p => createEnumMemberOutput(p)); 59 | return `export enum ${e.name} {\n${TAB}${memberOuts.join(`,\n${TAB}`)}\n}` 60 | } 61 | 62 | function createEnumMemberOutput(member) { 63 | return member.value == null ? member.name : `${member.name} = ${member.value}` 64 | } 65 | 66 | function checkType(type) { 67 | let output = typeNameMappings[type.name] || type.name; 68 | return type.is_nullable ? `${output} | undefined | null` : output; 69 | } 70 | 71 | const typeNameMappings = { 72 | 'short' :'number', 73 | 'Short' :'number', 74 | 'ushort' :'number', 75 | 'long' :'number', 76 | 'ulong' :'number', 77 | 'int' :'number', 78 | 'Int16' :'number', 79 | 'UInt16' :'number', 80 | 'Int32' :'number', 81 | 'UInt32' :'number', 82 | 'Int64' :'number', 83 | 'UInt64' :'number', 84 | 'double' :'number', 85 | 'Double' :'number', 86 | 'decimal' :'number', 87 | 'Decimal' :'number', 88 | 'float' :'number', 89 | 'Float' :'number', 90 | 'byte' :'number', 91 | 'Byte' :'number', 92 | 'sbyte' :'number', 93 | 'SByte' :'number', 94 | 95 | 'bool' :'boolean', 96 | 'Boolean' :'boolean', 97 | 'DateTime' :'Date', 98 | 'Task' : 'Promise', 99 | 100 | 'IEnumerable' :'Array', 101 | 'ICollection' :'Array', 102 | 'IList' :'Array', 103 | 'List' :'Array', 104 | 'HashSet' :'Array', 105 | 'DbSet' :'Array', 106 | 'Dictionary' :'Map', 107 | } 108 | 109 | const packagename = 'typesharp' 110 | 111 | function getConfiguration() { 112 | const propertyConvention = vscode.workspace.getConfiguration(packagename).get("propertyConvention"); 113 | const newWindow = vscode.workspace.getConfiguration(packagename).get("newWindow"); 114 | const classToInterface = vscode.workspace.getConfiguration(packagename).get("classToInterface"); 115 | const optionalField = vscode.workspace.getConfiguration(packagename).get("optionalField"); 116 | const prettier = vscode.workspace.getConfiguration(packagename).get("prettier"); 117 | 118 | return { 119 | propertyConvention, 120 | newWindow, 121 | classToInterface, 122 | optionalField, 123 | prettier 124 | }; 125 | } 126 | 127 | module.exports = { 128 | flatMap: flatMap, 129 | createMemberOutput: createMemberOutput, 130 | getConfiguration: getConfiguration 131 | }; -------------------------------------------------------------------------------- /multi.js: -------------------------------------------------------------------------------- 1 | // The module 'vscode' contains the VS Code extensibility API 2 | // Import the module and reference it with the alias vscode in your code below 3 | const vscode = require('vscode'); 4 | const path = require('path'); 5 | const prettier = require('prettier'); 6 | const parser = require('./parser/csharp'); 7 | const fs = require('fs'); 8 | const util = require('./util'); 9 | 10 | function registerOpenCommand(context) { 11 | return vscode.commands.registerCommand('extension.typesharp.open', function () { 12 | const panel = vscode.window.createWebviewPanel( 13 | 'typeSharp', 14 | 'TypeSharp', 15 | vscode.ViewColumn.One, 16 | { 17 | enableScripts: true, 18 | // Only allow the webview to access resources in our extension's media directory 19 | localResourceRoots: [vscode.Uri.file(path.join(context.extensionPath, 'media'))] 20 | } 21 | ); 22 | 23 | const indexFilePath = vscode.Uri.file(path.join(context.extensionPath, 'views', 'index.html')); 24 | panel.webview.html = fs.readFileSync(indexFilePath.fsPath, 'utf8'); 25 | 26 | //load configs 27 | const config = util.getConfiguration(); 28 | panel.webview.postMessage({ 29 | command: 'load.config', 30 | data: config 31 | }) 32 | 33 | // Handle messages from the webview 34 | panel.webview.onDidReceiveMessage( 35 | message => handleMessage(panel.webview, message), 36 | undefined, 37 | context.subscriptions 38 | ); 39 | }); 40 | } 41 | 42 | function handleMessage(context, message) { 43 | switch (message.command) { 44 | case 'open.folder': { 45 | let uri = vscode.Uri.file(message.data); 46 | vscode.commands.executeCommand('revealFileInOS', uri).then(function() { 47 | 48 | }); 49 | return; 50 | } 51 | case 'open.filePicker': { 52 | const options = { 53 | canSelectMany: true, 54 | openLabel: 'Open', 55 | filters: { 56 | 'C# files': ['cs'], 57 | 'All files': ['*'] 58 | } 59 | }; 60 | 61 | vscode.window.showOpenDialog(options).then(fileUri => { 62 | context.postMessage({ 63 | command: 'update.fileList', 64 | data: fileUri 65 | }) 66 | }); 67 | return; 68 | } 69 | case 'convert.selectedFiles': { 70 | console.debug(message.data); 71 | const options = { 72 | canSelectFiles: false, 73 | canSelectFolders:true, 74 | filters: { 75 | 'TypeScript files': ['ts'], 76 | 'All files': ['*'] 77 | } 78 | }; 79 | 80 | vscode.window.showOpenDialog(options).then(fileUri => { 81 | const path = fileUri[0].fsPath; 82 | const outputOptions = { 83 | outputTs: true, 84 | interface: message.data.interface, 85 | convention: message.data.convention, 86 | overwrite: message.data.overwrite, 87 | optional: message.optional 88 | } 89 | 90 | handleOpenDialog(context, path, message.data.files, outputOptions); 91 | }); 92 | return; 93 | } 94 | } 95 | } 96 | 97 | const handleOpenDialog = (context, path, files, options) => { 98 | files.forEach(f => { 99 | f.tsPath = null; 100 | f.error = null; 101 | f.created = false; 102 | }); 103 | 104 | const config = util.getConfiguration(); 105 | const file_member_map = {}; 106 | const memberName_file_map = {}; 107 | const filePromises = files.map(f => fs.promises.readFile(f.path, 'utf8').then(content => { 108 | if(content.charCodeAt(0) == 65279) 109 | content = content.substr(1); 110 | 111 | const parsed = parser.parse(content); 112 | const members = parsed.members && parsed.members.length > 0 ? parsed.members : util.flatMap(parsed.namespace_blocks, namespace => namespace.members); 113 | 114 | file_member_map[f.name] = members; 115 | members.forEach(m => { 116 | memberName_file_map[m.name] = f; 117 | }) 118 | }).catch(err=> { 119 | console.log(err); 120 | file_member_map[f.name] = []; 121 | f.error = err; 122 | })); 123 | 124 | Promise.all(filePromises).then(() => { 125 | const promises = files.filter(f => !f.error).map(f => { 126 | const members = file_member_map[f.name]; 127 | const memberOutputs = members.map(c => util.createMemberOutput(c, options)); 128 | const name = getFileNameWithoutExt(f.name); 129 | const referencedTypes = util.flatMap(members.map(member => findReferencedTypes(member)), m => m); 130 | const imports = referencedTypes.filter(rt => memberName_file_map[rt]) 131 | .map(rt => `import { ${rt} } from './${getFileNameWithoutExt(memberName_file_map[rt].name)}';`) 132 | .join('\n'); 133 | let fileContent = imports + '\n\n' + memberOutputs.join('\n') + '\n\n'; 134 | 135 | if(config.prettier) { 136 | fileContent = prettier.format(fileContent, { parser: 'typescript' }); 137 | } 138 | 139 | const filePath = `${path}\\${name}.ts`; 140 | 141 | f.tsPath = filePath; 142 | 143 | return fs.promises.writeFile(filePath, fileContent) 144 | .then(()=>{ 145 | f.checked = false; 146 | f.created = true; 147 | 148 | return f; 149 | }).catch(err=> { 150 | console.log(err); 151 | f.error = err; 152 | 153 | return f; 154 | })}); 155 | 156 | Promise.all(promises).then(results => { 157 | console.log(results); 158 | 159 | context.postMessage({ 160 | command: 'convert.response', 161 | data: files, 162 | uri: path 163 | }) 164 | }); 165 | }); 166 | } 167 | 168 | const findReferencedTypes = (target) => { 169 | let types = []; 170 | 171 | if (target.implemented_types) { 172 | types = types.concat(util.flatMap(target.implemented_types.filter(t => t.name !== target.name).map(t => getAllTypes(t)), m => m)); 173 | } 174 | 175 | if (target.members) { 176 | const temp = target.members.filter(m => m.type === 'property' && m.return_type.name !== target.name).map(m => getAllTypes(m.return_type)); 177 | types = types.concat(util.flatMap(temp, m => m)); 178 | } 179 | 180 | return [...new Set(types)]; 181 | } 182 | 183 | const getAllTypes = (target) => { 184 | let types = [target.name]; 185 | 186 | if (target.is_generic) { 187 | types = types.concat(util.flatMap(target.generic_parameters.map(p => getAllTypes(p)), m => m)); 188 | } 189 | 190 | if (target.base_type) { 191 | types = types.concat(getAllTypes(target.base_type)) 192 | } 193 | 194 | if (target.generic_parameters) { 195 | types = types.concat(util.flatMap(target.generic_parameters.map(gp => getAllTypes(gp)), m => m)); 196 | } 197 | 198 | return [...new Set(types)]; 199 | }; 200 | 201 | const getFileNameWithoutExt = (name) => { 202 | return name.split('.').slice(0, -1).join('.') 203 | } 204 | 205 | module.exports = registerOpenCommand; -------------------------------------------------------------------------------- /parser/csharp.pegjs: -------------------------------------------------------------------------------- 1 | { 2 | function createList(head, tail) { 3 | var result = [head]; 4 | for (var i = 0; i < tail.length; i++) { 5 | result.push(tail[i][3]); 6 | } 7 | return result; 8 | } 9 | 10 | function isEvent(modifiers) { 11 | return modifiers && modifiers.includes('event'); 12 | } 13 | 14 | function isDelegate(modifiers) { 15 | return modifiers && modifiers.includes('delegate'); 16 | } 17 | } 18 | 19 | start 20 | = __ u:using_statments? __ n:namespace_blocks? __ members:namespace_member* { 21 | return { 22 | using_statments: u, 23 | namespace_blocks: n, 24 | members: members, 25 | classes: members.filter(m => m.type === 'class') 26 | } 27 | } 28 | 29 | namespace_blocks 30 | = n:namespace_block (__ namespace_block)* { 31 | return [n] 32 | } 33 | 34 | comments_list 35 | = comments (__ comments)* 36 | 37 | comments 38 | = xml_comments 39 | / single_line_comments 40 | / multi_line_comments 41 | 42 | single_line_comments 43 | = '//' [^\n\r]* EOL 44 | 45 | multi_line_comments 46 | = '/*' (!"*/" .)* '*/' 47 | 48 | xml_comments 49 | = '///' [^\n\r]* EOL 50 | 51 | using_statments 52 | = head:using_statment tail:(__ using_statment)* { 53 | return [head].concat(tail.map(t => t[1])) 54 | } 55 | 56 | using_statment 57 | = 'using' __ ident:ident __? SEMICOLON { 58 | return ident; 59 | } 60 | 61 | namespace_block 62 | = 'namespace' __ ident:ident 63 | __ LBRACE 64 | __ members:namespace_member* 65 | __ RBRACE { 66 | return { 67 | namespace: ident, 68 | members: members, 69 | classes: members.filter(m => m.type === 'class') 70 | } 71 | } 72 | 73 | namespace_member 74 | = class_definition 75 | / enum_definition 76 | 77 | attribute_list 78 | = a:(__ attributes)* { 79 | return a.map(b=>b[1]).reduce((accumulator, currentValue) => { return currentValue.concat(accumulator); } , []) 80 | } 81 | 82 | attributes = LBRAKE __ head:attribute tail:(__ COMMA __ attribute)* __ RBRAKE { 83 | return createList(head, tail) 84 | } 85 | 86 | attribute 87 | = n:attribute_name p:(__ LPAREN __ attribute_parameters? __ RPAREN)? { 88 | return { 89 | name: n, 90 | parameters: (p && p.length === 6) ? p[3] : null, 91 | type: 'attribute' 92 | } 93 | } 94 | 95 | attribute_name 96 | = ident:ident { 97 | return ident 98 | } 99 | 100 | attribute_parameters 101 | = head:attribute_parameter tail:(__ COMMA __ attribute_parameter)* { 102 | return createList(head, tail) 103 | } 104 | 105 | attribute_parameter 106 | = ident:ident __ EQUALS __ v:attribute_parameter_value { 107 | return { 108 | name: ident, 109 | value: v 110 | } 111 | } 112 | / v:attribute_parameter_value { 113 | return { 114 | name: null, 115 | value: v 116 | } 117 | } 118 | 119 | attribute_parameter_value 120 | = parameter_value 121 | 122 | parameter_value 123 | = TRUE 124 | / FALSE 125 | / NULL 126 | / digit:digit+ { return digit.join('') } 127 | / string 128 | / new_operator 129 | 130 | string 131 | = QUOTE parts:[^"]* QUOTE { return '"' + parts.join('') + '"' } 132 | 133 | class_definition 134 | = a:attribute_list? 135 | __ m:modifiers? 136 | __ p:partial? 137 | __ 'class' 138 | __ n:class_name 139 | __ g:class_generic_parameters? 140 | __ i:implemented_types? 141 | __ t:type_constraints? 142 | __ LBRACE 143 | __ members:class_member* 144 | __ RBRACE { 145 | return { 146 | attributes: a, 147 | modifiers: m, 148 | name: n, 149 | generic_parameters: g, 150 | implemented_types: i, 151 | type_constraints: t, 152 | members: members, 153 | partial: p === 'partial' ? true :false, 154 | type: 'class' 155 | } 156 | } 157 | 158 | class_name 159 | = ident:ident { 160 | return ident 161 | } 162 | 163 | class_generic_parameters 164 | = LESS_THAN __ head:type tail:(__ COMMA __ type)* __ GREATER_THAN { 165 | return createList(head, tail) 166 | } 167 | 168 | implemented_types = COLON __ head:type tail:(__ COMMA __ type)* { 169 | return createList(head, tail) 170 | } 171 | 172 | class_member 173 | = constructor_definition 174 | / property_definition 175 | / method_definition 176 | / field_definition 177 | 178 | constructor_definition 179 | = __ a:attribute_list? 180 | __ m:modifiers? 181 | __ n:class_name 182 | __ g:method_generic_parameters? 183 | __ LPAREN __ params:method_parameters __ RPAREN 184 | __ b:base_class_constructor? 185 | __ t:type_constraints? 186 | __ d:block? { 187 | return { 188 | attributes: a, 189 | modifiers: m, 190 | name:n, 191 | parameters: params, 192 | generic_parameters: g, 193 | type_constraints: t, 194 | type: 'constructor', 195 | base: b 196 | } 197 | } 198 | 199 | base_class_constructor 200 | = COLON __ 'base' __ LPAREN __ head:base_class_constructor_parameter tail:(__ COMMA __ base_class_constructor_parameter)* __ RPAREN { 201 | const first = head 202 | const others = tail.map(t => t[3]) 203 | 204 | return [first].concat(others) 205 | } 206 | 207 | base_class_constructor_parameter 208 | = parameter_value 209 | / ident 210 | 211 | new_operator = NEW 212 | __ i:ident 213 | __ LPAREN 214 | __ p:new_operator_parameters? 215 | __ RPAREN 216 | __ block? { 217 | return { 218 | type: 'new', 219 | name: i, 220 | parameters: p 221 | } 222 | } 223 | 224 | new_operator_parameters 225 | = head:ident? tail:(__ COMMA __ ident)* { 226 | return createList(head, tail) 227 | } 228 | 229 | enum_definition 230 | = a:attribute_list? 231 | __ m:modifiers? 232 | __ 'enum' 233 | __ n:ident 234 | __ t:(COLON __ type)? 235 | __ LBRACE 236 | __ members:enum_members 237 | __ RBRACE { 238 | return { 239 | attributes: a, 240 | modifiers: m, 241 | name: n, 242 | members: members, 243 | type: 'enum', 244 | enum_type: t ? t[2] : null 245 | } 246 | } 247 | 248 | enum_members 249 | = head:enum_member tail:(__ COMMA __ enum_member)* { 250 | return createList(head, tail).filter(e => e.name) 251 | } 252 | 253 | enum_member 254 | = i:ident v:(__ EQUALS __ enum_value)? { 255 | return { 256 | name: i, 257 | value: v ? v[3] : null 258 | } 259 | } 260 | 261 | enum_value 262 | = number 263 | 264 | field_definition 265 | = __ a:attribute_list? 266 | __ m:modifiers? 267 | __ r:return_type 268 | __ n:field_name 269 | __ i:(SEMICOLON / object_initializer) { 270 | return { 271 | attributes: a, 272 | modifiers: m, 273 | return_type:r, 274 | name:n, 275 | type: isEvent(m) ? 'event' : 'field' 276 | } 277 | } 278 | 279 | field_name 280 | = ident:ident { 281 | return ident 282 | } 283 | 284 | property_definition 285 | = __ a:attribute_list? 286 | __ m:modifiers? 287 | __ r:return_type 288 | __ n:property_name 289 | __ l:((LBRACE __ accessor_list __ RBRACE)) 290 | __ i:object_initializer? { 291 | return { 292 | attributes: a, 293 | modifiers: m, 294 | return_type:r, 295 | name:n, 296 | accessor_list:l[2], 297 | type: 'property' 298 | } 299 | } 300 | 301 | property_name 302 | = ident:ident { 303 | return ident 304 | } 305 | 306 | object_initializer 307 | = EQUALS __ p:parameter_value __ SEMICOLON { 308 | return p 309 | } 310 | 311 | accessor_list 312 | = head:accessor __? tail:accessor? { 313 | return tail ? [head, tail] : [head] 314 | } 315 | 316 | accessor 317 | = ak:accessor_keyword __ d:accessor_lambda_expression __ SEMICOLON { 318 | return { 319 | accessor_keyword: ak, 320 | definition:d 321 | } 322 | } 323 | /ak:accessor_keyword __ d:block? __ SEMICOLON? { 324 | return { 325 | accessor_keyword: ak, 326 | definition:d 327 | } 328 | } 329 | 330 | accessor_lambda_expression 331 | = EQUALS __ parts:[^;]* { 332 | return parts.join('') 333 | } 334 | 335 | accessor_keyword = 'get' / 'set' 336 | 337 | method_definition 338 | = __ a:attribute_list? 339 | __ m:modifiers? 340 | __ p:partial? 341 | __ r:return_type 342 | __ n:method_name 343 | __ g:method_generic_parameters? 344 | __ LPAREN __ params:method_parameters __ RPAREN 345 | __ t:type_constraints? 346 | __ s:SEMICOLON? 347 | __ d:block? { 348 | return { 349 | attributes: a, 350 | modifiers: m, 351 | return_type:r, 352 | name:n, 353 | parameters: params, 354 | generic_parameters: g, 355 | type_constraints: t, 356 | partial: p === 'partial' ? true :false, 357 | type: isDelegate(m) ? 'delegate' : 'method' 358 | } 359 | } 360 | 361 | method_name = ident:ident { 362 | return ident 363 | } 364 | 365 | method_generic_parameters 366 | = LESS_THAN __ head:type tail:(__ COMMA __ type)* __ GREATER_THAN { 367 | return createList(head, tail) 368 | } 369 | 370 | method_parameters 371 | = head:method_parameter? tail:(__ COMMA __ method_parameter)* { 372 | return createList(head, tail) 373 | } 374 | 375 | method_parameter_keywords 376 | = 'params' 377 | / 'in' 378 | / 'out' 379 | / 'ref' 380 | / 'this' 381 | 382 | method_parameter 383 | = a:attribute_list? 384 | __ k:method_parameter_keywords? 385 | __ t:type 386 | __ ident:ident { 387 | return { 388 | attributes: a, 389 | type:t, 390 | name:ident, 391 | method_parameter_keyword: k 392 | } 393 | } 394 | 395 | block = LBRACE __ head:block_head __ tail:(block_tail)* __ RBRACE { 396 | return head + tail.join('') 397 | } 398 | 399 | block_head = parts:[^{}]* { 400 | return parts.join('') 401 | } 402 | 403 | block_tail = block:block __ parts:[^{}]* { 404 | return block + parts.join('') 405 | } 406 | 407 | return_type 408 | = type 409 | 410 | type = array_type 411 | / generic_type 412 | / non_generic_type 413 | 414 | array_type 415 | = t:generic_type __ a:array_syntax __ q:QUESTION_MARK? { 416 | return { 417 | base_type: t, 418 | is_array: true, 419 | is_nullable: q ? true : false, 420 | arrays: a, 421 | name: t.name + a.map(i => '[]').join('') 422 | } 423 | } 424 | / t:non_generic_type __ a:array_syntax __ q:QUESTION_MARK? { 425 | return { 426 | base_type: t, 427 | is_array: true, 428 | is_nullable: q ? true : false, 429 | arrays: a, 430 | name: t.name + a.map(i => '[]').join('') 431 | } 432 | } 433 | 434 | array_syntax 435 | = array_syntax_jagged 436 | / array_syntax_d 437 | 438 | array_syntax_d 439 | = LBRAKE __ d:(COMMA __)* RBRAKE { 440 | return { 441 | type: 'array', 442 | dimension: d.length + 1 443 | } 444 | } 445 | 446 | array_syntax_jagged 447 | = a:(array_syntax_d __)+ { 448 | return a.map(m => m[0]) 449 | } 450 | 451 | generic_type 452 | = type:non_generic_type LESS_THAN head:(type) tail:(__ COMMA __ type)* GREATER_THAN __ q:QUESTION_MARK? { 453 | const parameters = createList(head, tail); 454 | 455 | return { 456 | name: type.name + '<' + parameters.map(p=>p.name).join(',') + '>', 457 | base_type: type, 458 | generic_parameters: parameters, 459 | is_generic: true, 460 | is_nullable: q ? true : false 461 | } 462 | } 463 | 464 | non_generic_type = ident:ident __ q:QUESTION_MARK? { 465 | return { 466 | name: ident, 467 | base_type: null, 468 | generic_parameters: null, 469 | is_generic: false, 470 | is_nullable: q ? true : false 471 | } 472 | } 473 | 474 | type_constraints 475 | = head:type_constraint tail:(__ type_constraint)* { 476 | return [head].concat(tail.map(t => t[1])) 477 | } 478 | 479 | type_constraint 480 | = 'where' __ ident:ident __ COLON __ head:type_constraint_arg tail:(__ COMMA __ type_constraint_arg)* { 481 | return { 482 | type_constraint: ident, 483 | type_constraint_args: createList(head, tail) 484 | } 485 | } 486 | 487 | type_constraint_arg 488 | = 'struct' 489 | / 'class' 490 | / 'unmanaged' 491 | / 'new()' 492 | / type 493 | 494 | access_modifier 495 | = 'public' 496 | / 'private' 497 | / 'protected' 498 | / 'internal' 499 | / 'protected internal' 500 | / 'private protected' 501 | 502 | modifiers 503 | = head:modifier tail:(__ modifier)* { 504 | return [head].concat(tail.map(t => t[1])) 505 | } 506 | 507 | modifier 508 | = 'abstract' 509 | / 'async' 510 | / 'const' 511 | / 'event' 512 | / 'extern' 513 | / 'override' 514 | / 'readonly' 515 | / 'sealed' 516 | / 'static' 517 | / 'unsafe' 518 | / 'virtual' 519 | / 'volatile' 520 | / access_modifier 521 | 522 | partial = 'partial' 523 | 524 | ident 525 | = parts:ident_part* { 526 | return parts.join('') 527 | } 528 | 529 | ident_part 530 | = [A-Za-z0-9_.] 531 | 532 | number 533 | = d:digit* { 534 | return d.join('') 535 | } 536 | 537 | digit 538 | = [0-9-.] 539 | 540 | TRUE = 'true' 541 | FALSE = 'false' 542 | NULL = 'null' 543 | NEW = 'new' 544 | 545 | //specail character 546 | DOT = '.' 547 | COMMA = ',' 548 | STAR = '*' 549 | COLON = ':' 550 | SEMICOLON = ';' 551 | EQUALS = '=' 552 | QUOTE = '"' 553 | QUESTION_MARK = '?' 554 | 555 | LESS_THAN = '<' 556 | GREATER_THAN = '>' 557 | 558 | LPAREN = '(' 559 | RPAREN = ')' 560 | 561 | LBRAKE = '[' 562 | RBRAKE = ']' 563 | 564 | LBRACE = '{' 565 | RBRACE = '}' 566 | 567 | __ = 568 | whitespace* comments_list? whitespace* 569 | 570 | char = . 571 | 572 | whitespace = 573 | [ \t\n\r] 574 | 575 | EOL 576 | = EOF 577 | / [\n\r]+ 578 | 579 | EOF = !. -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |