├── .gitattributes ├── .github ├── CONTRIBUTING.md └── ISSUE_TEMPLATE.md ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── TypeScriptDefinitionGenerator.sln ├── appveyor.yml ├── art ├── context-menu.png ├── nested-file.png └── settings.png ├── lib └── Microsoft.VisualStudio.Telemetry.dll └── src ├── Commands └── ToggleCustomTool.cs ├── Constants.cs ├── DtsPackage.cs ├── DtsPackage.vsct ├── DtsPackage1.cs ├── Generator ├── DtsGenerator.cs ├── GenerationService.cs ├── IntellisenseObject.cs ├── IntellisenseParser.cs ├── IntellisenseProperty.cs ├── IntellisenseType.cs └── IntellisenseWriter.cs ├── Helpers ├── Utility.cs └── VSHelpers.cs ├── Options.cs ├── Properties └── AssemblyInfo.cs ├── Resources └── Icon.png ├── TypeScriptDefinitionGenerator.csproj ├── source.extension.cs ├── source.extension.vsixmanifest └── website.pkgdef /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Looking to contribute something? **Here's how you can help.** 4 | 5 | Please take a moment to review this document in order to make the contribution 6 | process easy and effective for everyone involved. 7 | 8 | Following these guidelines helps to communicate that you respect the time of 9 | the developers managing and developing this open source project. In return, 10 | they should reciprocate that respect in addressing your issue or assessing 11 | patches and features. 12 | 13 | 14 | ## Using the issue tracker 15 | 16 | The issue tracker is the preferred channel for [bug reports](#bug-reports), 17 | [features requests](#feature-requests) and 18 | [submitting pull requests](#pull-requests), but please respect the 19 | following restrictions: 20 | 21 | * Please **do not** use the issue tracker for personal support requests. Stack 22 | Overflow is a better place to get help. 23 | 24 | * Please **do not** derail or troll issues. Keep the discussion on topic and 25 | respect the opinions of others. 26 | 27 | * Please **do not** open issues or pull requests which *belongs to* third party 28 | components. 29 | 30 | 31 | ## Bug reports 32 | 33 | A bug is a _demonstrable problem_ that is caused by the code in the repository. 34 | Good bug reports are extremely helpful, so thanks! 35 | 36 | Guidelines for bug reports: 37 | 38 | 1. **Use the GitHub issue search** — check if the issue has already been 39 | reported. 40 | 41 | 2. **Check if the issue has been fixed** — try to reproduce it using the 42 | latest `master` or development branch in the repository. 43 | 44 | 3. **Isolate the problem** — ideally create an 45 | [SSCCE](http://www.sscce.org/) and a live example. 46 | Uploading the project on cloud storage (OneDrive, DropBox, et el.) 47 | or creating a sample GitHub repository is also helpful. 48 | 49 | 50 | A good bug report shouldn't leave others needing to chase you up for more 51 | information. Please try to be as detailed as possible in your report. What is 52 | your environment? What steps will reproduce the issue? What browser(s) and OS 53 | experience the problem? Do other browsers show the bug differently? What 54 | would you expect to be the outcome? All these details will help people to fix 55 | any potential bugs. 56 | 57 | Example: 58 | 59 | > Short and descriptive example bug report title 60 | > 61 | > A summary of the issue and the Visual Studio, browser, OS environments 62 | > in which it occurs. If suitable, include the steps required to reproduce the bug. 63 | > 64 | > 1. This is the first step 65 | > 2. This is the second step 66 | > 3. Further steps, etc. 67 | > 68 | > `` - a link to the project/file uploaded on cloud storage or other publicly accessible medium. 69 | > 70 | > Any other information you want to share that is relevant to the issue being 71 | > reported. This might include the lines of code that you have identified as 72 | > causing the bug, and potential solutions (and your opinions on their 73 | > merits). 74 | 75 | 76 | ## Feature requests 77 | 78 | Feature requests are welcome. But take a moment to find out whether your idea 79 | fits with the scope and aims of the project. It's up to *you* to make a strong 80 | case to convince the project's developers of the merits of this feature. Please 81 | provide as much detail and context as possible. 82 | 83 | 84 | ## Pull requests 85 | 86 | Good pull requests, patches, improvements and new features are a fantastic 87 | help. They should remain focused in scope and avoid containing unrelated 88 | commits. 89 | 90 | **Please ask first** before embarking on any significant pull request (e.g. 91 | implementing features, refactoring code, porting to a different language), 92 | otherwise you risk spending a lot of time working on something that the 93 | project's developers might not want to merge into the project. 94 | 95 | Please adhere to the [coding guidelines](#code-guidelines) used throughout the 96 | project (indentation, accurate comments, etc.) and any other requirements 97 | (such as test coverage). 98 | 99 | Adhering to the following process is the best way to get your work 100 | included in the project: 101 | 102 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, 103 | and configure the remotes: 104 | 105 | ```bash 106 | # Clone your fork of the repo into the current directory 107 | git clone https://github.com//.git 108 | # Navigate to the newly cloned directory 109 | cd 110 | # Assign the original repo to a remote called "upstream" 111 | git remote add upstream https://github.com/madskristensen/.git 112 | ``` 113 | 114 | 2. If you cloned a while ago, get the latest changes from upstream: 115 | 116 | ```bash 117 | git checkout master 118 | git pull upstream master 119 | ``` 120 | 121 | 3. Create a new topic branch (off the main project development branch) to 122 | contain your feature, change, or fix: 123 | 124 | ```bash 125 | git checkout -b 126 | ``` 127 | 128 | 4. Commit your changes in logical chunks. Please adhere to these [git commit 129 | message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 130 | or your code is unlikely be merged into the main project. Use Git's 131 | [interactive rebase](https://help.github.com/articles/interactive-rebase) 132 | feature to tidy up your commits before making them public. Also, prepend name of the feature 133 | to the commit message. For instance: "SCSS: Fixes compiler results for IFileListener.\nFixes `#123`" 134 | 135 | 5. Locally merge (or rebase) the upstream development branch into your topic branch: 136 | 137 | ```bash 138 | git pull [--rebase] upstream master 139 | ``` 140 | 141 | 6. Push your topic branch up to your fork: 142 | 143 | ```bash 144 | git push origin 145 | ``` 146 | 147 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) 148 | with a clear title and description against the `master` branch. 149 | 150 | 151 | ## Code guidelines 152 | 153 | - Always use proper indentation. 154 | - In Visual Studio under `Tools > Options > Text Editor > C# > Advanced`, make sure 155 | `Place 'System' directives first when sorting usings` option is enabled (checked). 156 | - Before committing, organize usings for each updated C# source file. Either you can 157 | right-click editor and select `Organize Usings > Remove and sort` OR use extension 158 | like [BatchFormat](http://visualstudiogallery.msdn.microsoft.com/a7f75c34-82b4-4357-9c66-c18e32b9393e). 159 | - Before committing, run Code Analysis in `Debug` configuration and follow the guidelines 160 | to fix CA issues. Code Analysis commits can be made separately. 161 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Installed product versions 2 | - Visual Studio: [example 2015 Professional] 3 | - This extension: [example 1.1.21] 4 | 5 | ### Description 6 | Replace this text with a short description 7 | 8 | ### Steps to recreate 9 | 1. Replace this 10 | 2. text with 11 | 3. the steps 12 | 4. to recreate 13 | 14 | ### Current behavior 15 | Explain what it's doing and why it's wrong 16 | 17 | ### Expected behavior 18 | Explain what it should be doing after it's fixed. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | packages 2 | 3 | # User files 4 | *.suo 5 | *.user 6 | *.sln.docstates 7 | .vs/ 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Rr]elease/ 12 | x64/ 13 | [Bb]in/ 14 | [Oo]bj/ 15 | 16 | # MSTest test Results 17 | [Tt]est[Rr]esult*/ 18 | [Bb]uild[Ll]og.* 19 | 20 | # NCrunch 21 | *.ncrunchsolution 22 | *.ncrunchproject 23 | _NCrunch_WebCompiler -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Road map 2 | 3 | - [ ] Test on VB files 4 | 5 | Features that have a checkmark are complete and available for 6 | download in the 7 | [CI build](http://vsixgallery.com/extension/cad7b20b-4b83-4ca6-bf24-ca36a494241c/). 8 | 9 | # Change log 10 | 11 | These are the changes to each version that has been released 12 | on the official Visual Studio extension gallery. 13 | 14 | ## 1.2 15 | 16 | - [x] Support for Visual Studio 2019 17 | - [x] Converted to AsyncPackage with background load 18 | 19 | ## 1.1 20 | 21 | - [x] Option for using producing <filename>.cs.d.ts 22 | 23 | ## 1.0 24 | 25 | - [x] Initial release 26 | - [x] Single File Generator 27 | - [x] Command to toggle custom tool 28 | - [x] Update readme file with screenshots etc. 29 | - [x] Website project support -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Mads Kristensen 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeScript Definition Generator 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/l61k3vbx5jsf6o0i?svg=true)](https://ci.appveyor.com/project/madskristensen/typescriptdefinitiongenerator) 4 | 5 | For Visual Studio 2022: 6 | Download this extension from the [Marketplace](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TSDefGenerator2022) 7 | or get the [CI build](https://www.vsixgallery.com/extension/cad7b20b-4b83-4ca6-bf24-ca36a494241d). 8 | 9 | For Visual Studio 2019: 10 | Download this extension from the [Marketplace](https://marketplace.visualstudio.com/vsgallery/7ef40759-8802-4b48-b4d6-3c250fb4916e) 11 | or get the [CI build](http://vsixgallery.com/extension/cad7b20b-4b83-4ca6-bf24-ca36a494241c/). 12 | 13 | --------------------------------------- 14 | 15 | Creates and synchronizes TypeScript Definition files (d.ts) from C#/VB model classes to build strongly typed web application where the server- and client-side models are in sync. Works on all .NET project types 16 | 17 | See the [change log](CHANGELOG.md) for changes and road map. 18 | 19 | For Visual Studio 2015 support, install [Web Essentials 2015](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.WebEssentials20153) 20 | 21 | ## From C# to .d.ts file 22 | This extension will automatically generate .d.ts files from any C#/VB file you specify. It will turn the following C# class into a TypeScript interface. 23 | 24 | ```csharp 25 | class Customer 26 | { 27 | public int Id { get; set; } 28 | public string Name { get; set; } 29 | public IEnumerable Tags { get; set; } 30 | } 31 | ``` 32 | 33 | Becomes this .d.ts file: 34 | 35 | ```typescript 36 | declare module server { 37 | interface customer { 38 | id: number; 39 | name: string; 40 | tags: string[]; 41 | } 42 | } 43 | ``` 44 | 45 | The generated .d.ts file can then be consumed from your own TypeScript files by referencing `server.` interfaces. 46 | 47 | ## Generate d.ts file from C#/VB 48 | To generate a .d.ts file, right-click any .cs or .vb file and select **Generate TypeScript Definition**. 49 | 50 | ![Context menu](art/context-menu.png) 51 | 52 | A .d.ts file is created and nested under the parent C#/VB file. 53 | 54 | ![Nested file](art/nested-file.png) 55 | 56 | Every time the C#/VB file is modified and saved, the content of the .d.ts file is updated to reflect the changes. 57 | 58 | ## Settings 59 | Configure this extension from the **Tools -> Options** dialog. 60 | 61 | ![Settings](art/settings.png) 62 | 63 | ## Contribute 64 | Check out the [contribution guidelines](.github/CONTRIBUTING.md) 65 | if you want to contribute to this project. 66 | 67 | For cloning and building this project yourself, make sure 68 | to install the 69 | [Extensibility Tools 2015](https://visualstudiogallery.msdn.microsoft.com/ab39a092-1343-46e2-b0f1-6a3f91155aa6) 70 | extension for Visual Studio which enables some features 71 | used by this project. 72 | 73 | ## License 74 | [Apache 2.0](LICENSE) 75 | -------------------------------------------------------------------------------- /TypeScriptDefinitionGenerator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26014.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeScriptDefinitionGenerator", "src\TypeScriptDefinitionGenerator.csproj", "{F8E2F6C6-880A-4C64-A9EE-0CF04F9C3CC6}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{558B2E74-81C9-4A3B-B749-9BAA5A12E768}" 9 | ProjectSection(SolutionItems) = preProject 10 | appveyor.yml = appveyor.yml 11 | CHANGELOG.md = CHANGELOG.md 12 | README.md = README.md 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {F8E2F6C6-880A-4C64-A9EE-0CF04F9C3CC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {F8E2F6C6-880A-4C64-A9EE-0CF04F9C3CC6}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {F8E2F6C6-880A-4C64-A9EE-0CF04F9C3CC6}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {F8E2F6C6-880A-4C64-A9EE-0CF04F9C3CC6}.Release|Any CPU.Build.0 = Release|Any CPU 25 | EndGlobalSection 26 | GlobalSection(SolutionProperties) = preSolution 27 | HideSolutionNode = FALSE 28 | EndGlobalSection 29 | EndGlobal 30 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2019 2 | 3 | install: 4 | - ps: (new-object Net.WebClient).DownloadString("https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1") | iex 5 | 6 | before_build: 7 | - ps: Vsix-IncrementVsixVersion | Vsix-UpdateBuildVersion 8 | - ps: Vsix-TokenReplacement src\source.extension.cs 'Version = "([0-9\\.]+)"' 'Version = "{version}"' 9 | 10 | build_script: 11 | - nuget restore -Verbosity quiet 12 | - msbuild /p:configuration=Release /p:DeployExtension=false /p:ZipPackageCompressionLevel=normal /v:m 13 | 14 | after_test: 15 | - ps: Vsix-PushArtifacts | Vsix-PublishToGallery 16 | -------------------------------------------------------------------------------- /art/context-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/TypeScriptDefinitionGenerator/e8f5890040763c43176b85bed65748788aea6352/art/context-menu.png -------------------------------------------------------------------------------- /art/nested-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/TypeScriptDefinitionGenerator/e8f5890040763c43176b85bed65748788aea6352/art/nested-file.png -------------------------------------------------------------------------------- /art/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/TypeScriptDefinitionGenerator/e8f5890040763c43176b85bed65748788aea6352/art/settings.png -------------------------------------------------------------------------------- /lib/Microsoft.VisualStudio.Telemetry.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/TypeScriptDefinitionGenerator/e8f5890040763c43176b85bed65748788aea6352/lib/Microsoft.VisualStudio.Telemetry.dll -------------------------------------------------------------------------------- /src/Commands/ToggleCustomTool.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using EnvDTE80; 3 | using Microsoft.VisualStudio.Shell; 4 | using System; 5 | using System.ComponentModel.Design; 6 | using System.IO; 7 | using System.Linq; 8 | using Tasks = System.Threading.Tasks; 9 | 10 | namespace TypeScriptDefinitionGenerator 11 | { 12 | internal sealed class ToggleCustomTool 13 | { 14 | private readonly Package _package; 15 | private ProjectItem _item; 16 | private DTE2 _dte; 17 | 18 | private ToggleCustomTool(Package package, OleMenuCommandService commandService, DTE2 dte) 19 | { 20 | _package = package; 21 | _dte = dte; 22 | 23 | var cmdId = new CommandID(PackageGuids.guidDtsPackageCmdSet, PackageIds.ToggleCustomToolId); 24 | var cmd = new OleMenuCommand(Execute, cmdId); 25 | cmd.BeforeQueryStatus += BeforeQueryStatus; 26 | commandService.AddCommand(cmd); 27 | } 28 | 29 | public static ToggleCustomTool Instance 30 | { 31 | get; 32 | private set; 33 | } 34 | 35 | private IServiceProvider ServiceProvider 36 | { 37 | get { return _package; } 38 | } 39 | 40 | public static async Tasks.Task InitializeAsync(AsyncPackage package) 41 | { 42 | var commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 43 | var dte = await package.GetServiceAsync(typeof(DTE)) as DTE2; 44 | 45 | Instance = new ToggleCustomTool(package, commandService, dte); 46 | } 47 | 48 | private void BeforeQueryStatus(object sender, EventArgs e) 49 | { 50 | var button = (OleMenuCommand)sender; 51 | button.Visible = button.Enabled = false; 52 | 53 | if (_dte.SelectedItems.Count != 1) 54 | return; 55 | 56 | _item = _dte.SelectedItems?.Item(1)?.ProjectItem; 57 | Options.ReadOptionOverrides(_item, false); 58 | 59 | if (_item == null || _item.ContainingProject == null || _item.FileCodeModel == null) 60 | return; 61 | 62 | var fileName = _item.FileNames[1]; 63 | var ext = Path.GetExtension(fileName); 64 | 65 | if (Constants.SupportedSourceExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase)) 66 | { 67 | if (_item.ContainingProject.IsKind(ProjectTypes.DOTNET_Core, ProjectTypes.ASPNET_5, ProjectTypes.WEBSITE_PROJECT)) 68 | { 69 | string dtsFile = GenerationService.GenerateFileName(_item.FileNames[1]); 70 | button.Checked = File.Exists(dtsFile); 71 | } 72 | else 73 | { 74 | button.Checked = _item.Properties.Item("CustomTool").Value.ToString() == DtsGenerator.Name; 75 | } 76 | 77 | button.Visible = button.Enabled = true; 78 | } 79 | } 80 | 81 | private void Execute(object sender, EventArgs e) 82 | { 83 | Options.ReadOptionOverrides(_item, false); 84 | // .NET Core and Website projects 85 | if (_item.ContainingProject.IsKind(ProjectTypes.DOTNET_Core, ProjectTypes.ASPNET_5, ProjectTypes.WEBSITE_PROJECT)) 86 | { 87 | string dtsFile = GenerationService.GenerateFileName(_item.FileNames[1]); 88 | bool synOn = File.Exists(dtsFile); 89 | 90 | if (synOn) 91 | { 92 | var dtsItem = VSHelpers.GetProjectItem(dtsFile); 93 | dtsItem?.Delete(); 94 | File.Delete(dtsFile); 95 | } 96 | else 97 | { 98 | GenerationService.CreateDtsFile(_item); 99 | } 100 | } 101 | // Legacy .NET projects 102 | else 103 | { 104 | bool synOn = _item.Properties.Item("CustomTool").Value.ToString() == DtsGenerator.Name; 105 | 106 | if (synOn) 107 | { 108 | _item.Properties.Item("CustomTool").Value = ""; 109 | } 110 | else 111 | { 112 | _item.Properties.Item("CustomTool").Value = DtsGenerator.Name; 113 | } 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace TypeScriptDefinitionGenerator 2 | { 3 | public static class Constants 4 | { 5 | public const string FileExtension = ".d.ts"; 6 | public static string[] SupportedSourceExtensions { get; } = { ".cs", ".vb" }; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/DtsPackage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio; 2 | using Microsoft.VisualStudio.Shell; 3 | using Microsoft.VisualStudio.Shell.Interop; 4 | using Microsoft.VisualStudio.TextTemplating.VSHost; 5 | using System; 6 | using System.Runtime.InteropServices; 7 | using System.Threading; 8 | using Tasks = System.Threading.Tasks; 9 | 10 | namespace TypeScriptDefinitionGenerator 11 | { 12 | [Guid(PackageGuids.guidDtsPackageString)] 13 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 14 | [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] 15 | [ProvideMenuResource("Menus.ctmenu", 1)] 16 | [ProvideLanguageEditorOptionPage(typeof(OptionsDialogPage), "TypeScript", null, "Generate d.ts", null, new[] { "d.ts" })] 17 | [ProvideCodeGenerator(typeof(DtsGenerator), DtsGenerator.Name, DtsGenerator.Description, true)] 18 | [ProvideAutoLoad(PackageGuids.UIContextRuleString, PackageAutoLoadFlags.BackgroundLoad)] 19 | [ProvideUIContextRule(PackageGuids.UIContextRuleString, 20 | name: "Auto load", 21 | expression: "cs | vb", 22 | termNames: new[] { "cs", "vb" }, 23 | termValues: new[] { "HierSingleSelectionName:.cs$", "HierSingleSelectionName:.vb$" })] 24 | public sealed class DtsPackage : AsyncPackage 25 | { 26 | public static OptionsDialogPage Options 27 | { 28 | get; 29 | private set; 30 | } 31 | 32 | public static void EnsurePackageLoad() 33 | { 34 | if (Options == null) 35 | { 36 | var shell = GetGlobalService(typeof(SVsShell)) as IVsShell; 37 | ErrorHandler.ThrowOnFailure(shell.LoadPackage(PackageGuids.guidDtsPackage, out var ppPackage)); 38 | } 39 | } 40 | 41 | protected override async Tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 42 | { 43 | await JoinableTaskFactory.SwitchToMainThreadAsync(); 44 | 45 | Options = (OptionsDialogPage)GetDialogPage(typeof(OptionsDialogPage)); 46 | 47 | await ToggleCustomTool.InitializeAsync(this); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/DtsPackage.vsct: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/DtsPackage1.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // This file was generated by VSIX Synchronizer 4 | // 5 | // ------------------------------------------------------------------------------ 6 | namespace TypeScriptDefinitionGenerator 7 | { 8 | using System; 9 | 10 | /// 11 | /// Helper class that exposes all GUIDs used across VS Package. 12 | /// 13 | internal sealed partial class PackageGuids 14 | { 15 | public const string guidDtsPackageString = "d1e45432-d98b-4053-b3d7-e16d258c2959"; 16 | public static Guid guidDtsPackage = new Guid(guidDtsPackageString); 17 | 18 | public const string UIContextRuleString = "8ca1b9b9-fd6e-4a5a-a036-86da7e62912b"; 19 | public static Guid UIContextRule = new Guid(UIContextRuleString); 20 | 21 | public const string guidDtsPackageCmdSetString = "cf29ca9f-817e-4ce6-baa5-5fbe652b97ce"; 22 | public static Guid guidDtsPackageCmdSet = new Guid(guidDtsPackageCmdSetString); 23 | } 24 | /// 25 | /// Helper class that encapsulates all CommandIDs uses across VS Package. 26 | /// 27 | internal sealed partial class PackageIds 28 | { 29 | public const int MyMenuGroup = 0x1020; 30 | public const int ToggleCustomToolId = 0x0100; 31 | } 32 | } -------------------------------------------------------------------------------- /src/Generator/DtsGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | using EnvDTE; 6 | using EnvDTE80; 7 | using Microsoft.VisualStudio.TextTemplating.VSHost; 8 | 9 | namespace TypeScriptDefinitionGenerator 10 | { 11 | [Guid("d1e92907-20ee-4b6f-ba64-142297def4e4")] 12 | public sealed class DtsGenerator : BaseCodeGeneratorWithSite 13 | { 14 | public const string Name = nameof(DtsGenerator); 15 | public const string Description = "Automatically generates the .d.ts file based on the C#/VB model class."; 16 | 17 | private string originalExt { get; set; } 18 | 19 | public override string GetDefaultExtension() 20 | { 21 | if (Options.WebEssentials2015) 22 | { 23 | return originalExt + Constants.FileExtension; 24 | } 25 | else 26 | { 27 | return Constants.FileExtension; 28 | } 29 | } 30 | 31 | protected override byte[] GenerateCode(string inputFileName, string inputFileContent) 32 | { 33 | ProjectItem item = (Dte as DTE2).Solution.FindProjectItem(inputFileName); 34 | originalExt = Path.GetExtension(inputFileName); 35 | if (item != null) 36 | { 37 | try 38 | { 39 | var dts = GenerationService.ConvertToTypeScript(item); 40 | 41 | return Encoding.UTF8.GetBytes(dts); 42 | } 43 | catch (Exception ex) 44 | { } 45 | 46 | } 47 | 48 | return new byte[0]; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Generator/GenerationService.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using Microsoft.VisualStudio.Text; 3 | using Microsoft.VisualStudio.Text.Editor; 4 | using Microsoft.VisualStudio.Utilities; 5 | using System; 6 | using System.ComponentModel.Composition; 7 | using System.IO; 8 | using System.Windows.Threading; 9 | 10 | namespace TypeScriptDefinitionGenerator 11 | { 12 | [Export(typeof(IWpfTextViewCreationListener))] 13 | [ContentType("csharp")] 14 | [ContentType("basic")] 15 | [TextViewRole(PredefinedTextViewRoles.PrimaryDocument)] 16 | public class GenerationService : IWpfTextViewCreationListener 17 | { 18 | private ProjectItem _item; 19 | 20 | [Import] 21 | public ITextDocumentFactoryService _documentService { get; set; } 22 | 23 | public void TextViewCreated(IWpfTextView textView) 24 | { 25 | if (!_documentService.TryGetTextDocument(textView.TextBuffer, out var doc)) 26 | return; 27 | 28 | _item = VSHelpers.GetProjectItem(doc.FilePath); 29 | 30 | if (_item?.ContainingProject == null || 31 | !_item.ContainingProject.IsKind(ProjectTypes.DOTNET_Core, ProjectTypes.ASPNET_5, ProjectTypes.WEBSITE_PROJECT)) 32 | return; 33 | 34 | doc.FileActionOccurred += FileActionOccurred; 35 | } 36 | 37 | private void FileActionOccurred(object sender, TextDocumentFileActionEventArgs e) 38 | { 39 | if (e.FileActionType != FileActionTypes.ContentSavedToDisk) 40 | return; 41 | _item = VSHelpers.GetProjectItem(e.FilePath); 42 | Options.ReadOptionOverrides(_item, false); 43 | string fileName = GenerationService.GenerateFileName(e.FilePath); 44 | 45 | if (File.Exists(fileName)) 46 | { 47 | DtsPackage.EnsurePackageLoad(); 48 | CreateDtsFile(_item); 49 | } 50 | } 51 | 52 | public static string ConvertToTypeScript(ProjectItem sourceItem) 53 | { 54 | try 55 | { 56 | Options.ReadOptionOverrides(sourceItem); 57 | VSHelpers.WriteOnOutputWindow(string.Format("{0} - Started", sourceItem.Name)); 58 | var list = IntellisenseParser.ProcessFile(sourceItem); 59 | VSHelpers.WriteOnOutputWindow(string.Format("{0} - Completed", sourceItem.Name)); 60 | return IntellisenseWriter.WriteTypeScript(list); 61 | } 62 | catch (Exception ex) 63 | { 64 | VSHelpers.WriteOnOutputWindow(string.Format("{0} - Failure", sourceItem.Name)); 65 | return null; 66 | } 67 | } 68 | 69 | public static string GenerateFileName(string sourceFile) 70 | { 71 | if (Options.WebEssentials2015) 72 | { 73 | return sourceFile + Constants.FileExtension; 74 | } 75 | else 76 | { 77 | return Path.ChangeExtension(sourceFile, Constants.FileExtension); 78 | } 79 | } 80 | 81 | public static void CreateDtsFile(ProjectItem sourceItem) 82 | { 83 | string sourceFile = sourceItem.FileNames[1]; 84 | string dtsFile = GenerationService.GenerateFileName(sourceFile); 85 | string dts = ConvertToTypeScript(sourceItem); 86 | 87 | VSHelpers.CheckFileOutOfSourceControl(dtsFile); 88 | File.WriteAllText(dtsFile, dts); 89 | 90 | if (sourceItem.ContainingProject.IsKind(ProjectTypes.DOTNET_Core, ProjectTypes.ASPNET_5)) 91 | { 92 | Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => 93 | { 94 | var dtsItem = VSHelpers.GetProjectItem(dtsFile); 95 | 96 | if (dtsItem != null) 97 | dtsItem.Properties.Item("DependentUpon").Value = sourceItem.Name; 98 | 99 | }), DispatcherPriority.ApplicationIdle, null); 100 | } 101 | else if (sourceItem.ContainingProject.IsKind(ProjectTypes.WEBSITE_PROJECT)) 102 | { 103 | sourceItem.ContainingProject.ProjectItems.AddFromFile(dtsFile); 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Generator/IntellisenseObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace TypeScriptDefinitionGenerator 5 | { 6 | public class IntellisenseObject : IEquatable 7 | { 8 | public string Namespace { get; set; } 9 | public string Name { get; set; } 10 | public string BaseNamespace { get; set; } 11 | public string BaseName { get; set; } 12 | public string FullName { get; set; } 13 | public bool IsEnum { get; set; } 14 | public string Summary { get; set; } 15 | public IList Properties { get; private set; } 16 | public HashSet References { get; private set; } 17 | 18 | public IntellisenseObject() 19 | { 20 | Properties = new List(); 21 | References = new HashSet(); 22 | } 23 | 24 | public IntellisenseObject(IList properties) 25 | { 26 | Properties = properties; 27 | } 28 | 29 | public IntellisenseObject(IList properties, HashSet references) 30 | { 31 | Properties = properties; 32 | References = references; 33 | } 34 | 35 | public void UpdateReferences(IEnumerable moreReferences) 36 | { 37 | References.UnionWith(moreReferences); 38 | } 39 | 40 | public bool Equals(IntellisenseObject other) 41 | { 42 | return !ReferenceEquals(other, null) && 43 | other.Name == Name && 44 | other.Namespace == Namespace && 45 | other.BaseName == BaseName && 46 | other.BaseNamespace == BaseNamespace && 47 | other.FullName == FullName; 48 | } 49 | 50 | public override bool Equals(object obj) 51 | { 52 | return Equals(obj as IntellisenseObject); 53 | } 54 | 55 | public override int GetHashCode() 56 | { 57 | return Name.GetHashCode() ^ 58 | Namespace.GetHashCode() ^ 59 | FullName.GetHashCode(); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/Generator/IntellisenseParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text.RegularExpressions; 7 | using System.Xml.Linq; 8 | using EnvDTE; 9 | using EnvDTE80; 10 | using TypeScriptDefinitionGenerator.Helpers; 11 | 12 | namespace TypeScriptDefinitionGenerator 13 | { 14 | public static class IntellisenseParser 15 | { 16 | private static readonly string DefaultModuleName = Options.DefaultModuleName; 17 | private const string ModuleNameAttributeName = "TypeScriptModule"; 18 | private static readonly Regex IsNumber = new Regex("^[0-9a-fx]+[ul]{0,2}$", RegexOptions.IgnoreCase | RegexOptions.Compiled); 19 | private static Project _project; 20 | 21 | //internal static class Ext 22 | //{ 23 | // public const string TypeScript = ".d.ts"; 24 | //} 25 | 26 | internal static IEnumerable ProcessFile(ProjectItem item, HashSet underProcess = null) 27 | { 28 | if (item.FileCodeModel == null || item.ContainingProject == null) 29 | { 30 | return null; 31 | } 32 | 33 | _project = item.ContainingProject; 34 | 35 | var list = new List(); 36 | 37 | if (underProcess == null) 38 | { 39 | underProcess = new HashSet(); 40 | } 41 | 42 | foreach (CodeElement element in item.FileCodeModel.CodeElements) 43 | { 44 | if (element.Kind == vsCMElement.vsCMElementNamespace) 45 | { 46 | var cn = (CodeNamespace)element; 47 | 48 | foreach (CodeElement member in cn.Members) 49 | { 50 | if (ShouldProcess(member)) 51 | { 52 | ProcessElement(member, list, underProcess); 53 | } 54 | } 55 | } 56 | else if (ShouldProcess(element)) 57 | { 58 | ProcessElement(element, list, underProcess); 59 | } 60 | } 61 | 62 | return new HashSet(list); 63 | } 64 | private static void ProcessElement(CodeElement element, List list, HashSet underProcess) 65 | { 66 | if (element.Kind == vsCMElement.vsCMElementEnum) 67 | { 68 | ProcessEnum((CodeEnum)element, list); 69 | } 70 | else if (element.Kind == vsCMElement.vsCMElementClass) 71 | { 72 | var cc = (CodeClass)element; 73 | 74 | // Don't re-generate the intellisense. 75 | if (list.Any(x => x.Name == GetClassName(cc) && x.Namespace == GetNamespace(cc))) 76 | { 77 | return; 78 | } 79 | 80 | // Collect inherit classes. 81 | CodeClass baseClass = null; 82 | 83 | try 84 | { 85 | // To recuse from throwing from a weird case 86 | // where user inherit class from struct and save. As such inheritance is disallowed. 87 | baseClass = cc.Bases.Cast() 88 | .FirstOrDefault(c => c.FullName != "System.Object"); 89 | } 90 | catch { /* Silently continue. */ } 91 | 92 | ProcessClass(cc, baseClass, list, underProcess); 93 | 94 | var references = new HashSet(); 95 | try 96 | { 97 | // Process Inheritence. 98 | if (baseClass != null && !underProcess.Contains(baseClass) && !HasIntellisense(baseClass.ProjectItem, references)) 99 | { 100 | list.Last().UpdateReferences(references); 101 | underProcess.Add(baseClass); 102 | list.AddRange(ProcessFile(baseClass.ProjectItem, underProcess)); 103 | } 104 | } 105 | catch 106 | { 107 | 108 | } 109 | } 110 | } 111 | 112 | private static bool ShouldProcess(CodeElement member) 113 | { 114 | return 115 | member.Kind == vsCMElement.vsCMElementClass 116 | || member.Kind == vsCMElement.vsCMElementEnum; 117 | } 118 | 119 | private static void ProcessEnum(CodeEnum element, List list) 120 | { 121 | var data = new IntellisenseObject 122 | { 123 | Name = element.Name, 124 | IsEnum = element.Kind == vsCMElement.vsCMElementEnum, 125 | FullName = element.FullName, 126 | Namespace = GetNamespace(element), 127 | Summary = GetSummary(element) 128 | }; 129 | 130 | foreach (CodeVariable codeEnum in element.Members.OfType()) 131 | { 132 | var prop = new IntellisenseProperty 133 | { 134 | Name = codeEnum.Name, 135 | Summary = GetSummary(codeEnum), 136 | InitExpression = GetInitializer(codeEnum.InitExpression) 137 | }; 138 | 139 | data.Properties.Add(prop); 140 | } 141 | 142 | if (data.Properties.Count > 0) 143 | { 144 | list.Add(data); 145 | } 146 | } 147 | 148 | private static void ProcessClass(CodeClass cc, CodeClass baseClass, List list, HashSet underProcess) 149 | { 150 | string baseNs = null; 151 | string baseClassName = null; 152 | var ns = GetNamespace(cc); 153 | var className = GetClassName(cc); 154 | var references = new HashSet(); 155 | IList properties = GetProperties(cc.Members, new HashSet(), references).ToList(); 156 | 157 | foreach (CodeElement member in cc.Members) 158 | { 159 | if (ShouldProcess(member)) 160 | { 161 | ProcessElement(member, list, underProcess); 162 | } 163 | } 164 | 165 | if (baseClass != null) 166 | { 167 | baseClassName = GetClassName(baseClass); 168 | baseNs = GetNamespace(baseClass); 169 | } 170 | 171 | var intellisenseObject = new IntellisenseObject(properties.ToList(), references) 172 | { 173 | Namespace = ns, 174 | Name = className, 175 | BaseNamespace = baseNs, 176 | BaseName = baseClassName, 177 | FullName = cc.FullName, 178 | Summary = GetSummary(cc) 179 | }; 180 | 181 | list.Add(intellisenseObject); 182 | } 183 | 184 | private static IEnumerable GetProperties(CodeElements props, HashSet traversedTypes, HashSet references = null) 185 | { 186 | return from p in props.OfType() 187 | where !p.Attributes.Cast().Any(HasIgnoreAttribute) 188 | where vsCMAccess.vsCMAccessPublic == p.Access && p.Getter != null && !p.Getter.IsShared && IsPublic(p.Getter) 189 | select new IntellisenseProperty 190 | { 191 | Name = GetName(p), 192 | Type = GetType(p.Parent, p.Type, traversedTypes, references), 193 | Summary = GetSummary(p) 194 | }; 195 | } 196 | 197 | private static bool HasIgnoreAttribute(CodeAttribute attribute) 198 | { 199 | return attribute.FullName == "System.Runtime.Serialization.IgnoreDataMemberAttribute" || 200 | attribute.FullName == "Newtonsoft.Json.JsonIgnoreAttribute" || 201 | attribute.FullName == "System.Web.Script.Serialization.ScriptIgnoreAttribute"; 202 | } 203 | 204 | private static bool IsPublic(CodeFunction cf) 205 | { 206 | vsCMElement fun = cf.Kind; 207 | 208 | var retVal = false; 209 | try 210 | { 211 | retVal = cf.Access == vsCMAccess.vsCMAccessPublic; 212 | } 213 | catch (COMException) 214 | { 215 | var cp = cf.Parent as CodeProperty; 216 | if (cp != null) 217 | { 218 | retVal = cp.Access == vsCMAccess.vsCMAccessPublic; 219 | } 220 | 221 | } 222 | return retVal; 223 | } 224 | 225 | private static string GetClassName(CodeClass cc) 226 | { 227 | return GetDataContractName(cc, "Name") ?? cc.Name; 228 | } 229 | 230 | private static string GetNamespace(CodeClass cc) 231 | { 232 | return GetDataContractName(cc, "Namespace") ?? GetNamespace(cc.Attributes); 233 | } 234 | 235 | private static string GetDataContractName(CodeClass cc, string attrName) 236 | { 237 | IEnumerable dataContractAttribute = cc.Attributes.Cast().Where(a => a.Name == "DataContract"); 238 | return GetDataContractNameInner(dataContractAttribute, attrName); 239 | } 240 | 241 | private static string GetNamespace(CodeEnum cc) 242 | { 243 | return GetDataContractName(cc, "Namespace") ?? GetNamespace(cc.Attributes); 244 | } 245 | private static string GetDataContractName(CodeEnum cc, string attrName) 246 | { 247 | IEnumerable dataContractAttribute = cc.Attributes.Cast().Where(a => a.Name == "DataContract"); 248 | return GetDataContractNameInner(dataContractAttribute, attrName); 249 | } 250 | private static string GetDataContractNameInner(IEnumerable dataContractAttribute, string attrName) 251 | { 252 | if (!dataContractAttribute.Any()) 253 | { 254 | return null; 255 | } 256 | 257 | string name = null; 258 | var keyValues = dataContractAttribute.First().Children.OfType() 259 | .ToDictionary(a => a.Name, a => (a.Value ?? "").Trim('\"', '\'')); 260 | 261 | if (keyValues.ContainsKey(attrName)) 262 | { 263 | name = keyValues[attrName]; 264 | } 265 | 266 | return name; 267 | } 268 | 269 | private static string GetNamespace(CodeElements attrs) 270 | { 271 | if (attrs == null) 272 | { 273 | return DefaultModuleName; 274 | } 275 | 276 | IEnumerable namespaceFromAttr = from a in attrs.Cast() 277 | where a.Name.EndsWith(ModuleNameAttributeName, StringComparison.OrdinalIgnoreCase) 278 | from arg in a.Arguments.Cast() 279 | let v = (arg.Value ?? "").Trim('\"') 280 | where !string.IsNullOrWhiteSpace(v) 281 | select v; 282 | 283 | return namespaceFromAttr.FirstOrDefault() ?? DefaultModuleName; 284 | } 285 | 286 | private static IntellisenseType GetType(CodeClass rootElement, CodeTypeRef codeTypeRef, HashSet traversedTypes, HashSet references) 287 | { 288 | var isArray = codeTypeRef.TypeKind == vsCMTypeRef.vsCMTypeRefArray; 289 | var isCollection = codeTypeRef.AsString.StartsWith("System.Collections", StringComparison.Ordinal); 290 | var isDictionary = false; 291 | 292 | CodeTypeRef effectiveTypeRef = codeTypeRef; 293 | if (isArray && codeTypeRef.ElementType != null) 294 | { 295 | effectiveTypeRef = effectiveTypeRef.ElementType; 296 | } 297 | else if (isCollection) 298 | { 299 | effectiveTypeRef = TryToGuessGenericArgument(rootElement, effectiveTypeRef); 300 | } 301 | 302 | if (isCollection) 303 | { 304 | isDictionary = codeTypeRef.AsString.StartsWith("System.Collections.Generic.Dictionary", StringComparison.Ordinal) 305 | || codeTypeRef.AsString.StartsWith("System.Collections.Generic.IDictionary", StringComparison.Ordinal); 306 | } 307 | 308 | var typeName = effectiveTypeRef.AsFullName; 309 | 310 | try 311 | { 312 | 313 | var codeClass = effectiveTypeRef.CodeType as CodeClass2; 314 | var codeEnum = effectiveTypeRef.CodeType as CodeEnum; 315 | var isPrimitive = IsPrimitive(effectiveTypeRef); 316 | 317 | var result = new IntellisenseType 318 | { 319 | IsArray = !isDictionary && (isArray || isCollection), 320 | IsDictionary = isDictionary, 321 | CodeName = effectiveTypeRef.AsString, 322 | ClientSideReferenceName = 323 | effectiveTypeRef.TypeKind == vsCMTypeRef.vsCMTypeRefCodeType && 324 | effectiveTypeRef.CodeType.InfoLocation == vsCMInfoLocation.vsCMInfoLocationProject 325 | ? 326 | (codeClass != null && HasIntellisense(codeClass.ProjectItem, references) ? (GetNamespace(codeClass) + "." + Utility.CamelCaseClassName(GetClassName(codeClass))) : null) ?? 327 | (codeEnum != null && HasIntellisense(codeEnum.ProjectItem, references) ? (GetNamespace(codeEnum) + "." + Utility.CamelCaseClassName(codeEnum.Name)) : null) 328 | : null 329 | }; 330 | 331 | if (!isPrimitive && codeClass != null && !traversedTypes.Contains(effectiveTypeRef.CodeType.FullName) && !isCollection) 332 | { 333 | traversedTypes.Add(effectiveTypeRef.CodeType.FullName); 334 | result.Shape = GetProperties(effectiveTypeRef.CodeType.Members, traversedTypes, references).ToList(); 335 | traversedTypes.Remove(effectiveTypeRef.CodeType.FullName); 336 | } 337 | 338 | return result; 339 | } 340 | catch (InvalidCastException) 341 | { 342 | VSHelpers.WriteOnOutputWindow(string.Format("ERROR - Cannot find definition for {0}", typeName)); 343 | throw new ArgumentException(string.Format("Cannot find definition of {0}", typeName)); 344 | } 345 | } 346 | 347 | private static CodeTypeRef TryToGuessGenericArgument(CodeClass rootElement, CodeTypeRef codeTypeRef) 348 | { 349 | var codeTypeRef2 = codeTypeRef as CodeTypeRef2; 350 | if (codeTypeRef2 == null || !codeTypeRef2.IsGeneric) 351 | { 352 | return codeTypeRef; 353 | } 354 | 355 | // There is no way to extract generic parameter as CodeTypeRef or something similar 356 | // (see http://social.msdn.microsoft.com/Forums/vstudio/en-US/09504bdc-2b81-405a-a2f7-158fb721ee90/envdte-envdte80-codetyperef2-and-generic-types?forum=vsx) 357 | // but we can make it work at least for some simple case with the following heuristic: 358 | // 1) get the argument's local name by parsing the type reference's full text 359 | // 2) if it's a known primitive (i.e. string, int, etc.), return that 360 | // 3) otherwise, guess that it's a type from the same namespace and same project, 361 | // and use the project CodeModel to retrieve it by full name 362 | // 4) if CodeModel returns null - well, bad luck, don't have any more guesses 363 | var typeNameAsInCode = codeTypeRef2.AsString.Split('<', '>').ElementAtOrDefault(1) ?? ""; 364 | CodeModel projCodeModel; 365 | 366 | try 367 | { 368 | projCodeModel = rootElement.ProjectItem.ContainingProject.CodeModel; 369 | } 370 | catch (COMException) 371 | { 372 | projCodeModel = _project.CodeModel; 373 | } 374 | 375 | CodeType codeType = projCodeModel.CodeTypeFromFullName(TryToGuessFullName(typeNameAsInCode)); 376 | 377 | if (codeType != null) 378 | { 379 | return projCodeModel.CreateCodeTypeRef(codeType); 380 | } 381 | 382 | return codeTypeRef; 383 | } 384 | 385 | private static readonly Dictionary _knownPrimitiveTypes = new Dictionary(StringComparer.OrdinalIgnoreCase) { 386 | { "string", typeof( string ) }, 387 | { "int", typeof( int ) }, 388 | { "long", typeof( long ) }, 389 | { "short", typeof( short ) }, 390 | { "byte", typeof( byte ) }, 391 | { "uint", typeof( uint ) }, 392 | { "ulong", typeof( ulong ) }, 393 | { "ushort", typeof( ushort ) }, 394 | { "sbyte", typeof( sbyte ) }, 395 | { "float", typeof( float ) }, 396 | { "double", typeof( double ) }, 397 | { "decimal", typeof( decimal ) }, 398 | }; 399 | 400 | private static string TryToGuessFullName(string typeName) 401 | { 402 | if (_knownPrimitiveTypes.TryGetValue(typeName, out Type primitiveType)) 403 | { 404 | return primitiveType.FullName; 405 | } 406 | 407 | return typeName; 408 | } 409 | 410 | private static bool IsPrimitive(CodeTypeRef codeTypeRef) 411 | { 412 | if (codeTypeRef.TypeKind != vsCMTypeRef.vsCMTypeRefOther && codeTypeRef.TypeKind != vsCMTypeRef.vsCMTypeRefCodeType) 413 | { 414 | return true; 415 | } 416 | 417 | if (codeTypeRef.AsString.EndsWith("DateTime", StringComparison.Ordinal)) 418 | { 419 | return true; 420 | } 421 | 422 | return false; 423 | } 424 | 425 | private static bool HasIntellisense(ProjectItem projectItem, HashSet references) 426 | { 427 | for (short i = 0; i < projectItem.FileCount; i++) 428 | { 429 | var fileName = GenerationService.GenerateFileName(projectItem.FileNames[i]); 430 | 431 | if (File.Exists(fileName)) 432 | { 433 | references.Add(fileName); 434 | return true; 435 | } 436 | } 437 | 438 | return false; 439 | } 440 | 441 | // Maps attribute name to array of attribute properties to get resultant name from 442 | private static readonly IReadOnlyDictionary nameAttributes = new Dictionary 443 | { 444 | { "DataMember", new [] { "Name" } }, 445 | { "JsonProperty", new [] { "", "PropertyName" } } 446 | }; 447 | 448 | private static string GetName(CodeProperty property) 449 | { 450 | foreach (CodeAttribute attr in property.Attributes) 451 | { 452 | var className = Path.GetExtension(attr.Name); 453 | 454 | if (string.IsNullOrEmpty(className)) 455 | { 456 | className = attr.Name; 457 | } 458 | 459 | if (!nameAttributes.TryGetValue(className, out var argumentNames)) 460 | { 461 | continue; 462 | } 463 | 464 | CodeAttributeArgument value = attr.Children.OfType().FirstOrDefault(a => argumentNames.Contains(a.Name)); 465 | 466 | if (value == null) 467 | { 468 | break; 469 | } 470 | 471 | // Strip the leading & trailing quotes 472 | return value.Value.Trim('@', '\'', '"'); 473 | } 474 | 475 | return property.Name.Trim('@'); 476 | } 477 | 478 | // External items throw an exception from the DocComment getter 479 | private static string GetSummary(CodeProperty property) { return property.InfoLocation != vsCMInfoLocation.vsCMInfoLocationProject ? null : GetSummary(property.InfoLocation, property.DocComment, property.Comment, property.FullName); } 480 | 481 | private static string GetSummary(CodeClass property) { return GetSummary(property.InfoLocation, property.DocComment, property.Comment, property.FullName); } 482 | 483 | private static string GetSummary(CodeEnum property) { return GetSummary(property.InfoLocation, property.DocComment, property.Comment, property.FullName); } 484 | 485 | private static string GetSummary(CodeVariable property) { return GetSummary(property.InfoLocation, property.DocComment, property.Comment, property.FullName); } 486 | 487 | private static string GetSummary(vsCMInfoLocation location, string xmlComment, string inlineComment, string fullName) 488 | { 489 | if (location != vsCMInfoLocation.vsCMInfoLocationProject || (string.IsNullOrWhiteSpace(xmlComment) && string.IsNullOrWhiteSpace(inlineComment))) 490 | { 491 | return null; 492 | } 493 | 494 | try 495 | { 496 | var summary = ""; 497 | if (!string.IsNullOrWhiteSpace(xmlComment)) 498 | { 499 | summary = XElement.Parse(xmlComment) 500 | .Descendants("summary") 501 | .Select(x => x.Value) 502 | .FirstOrDefault(); 503 | } 504 | if (!string.IsNullOrEmpty(summary)) 505 | { 506 | return summary.Trim(); 507 | } 508 | 509 | if (!string.IsNullOrWhiteSpace(inlineComment)) 510 | { 511 | return inlineComment.Trim(); 512 | } 513 | 514 | return null; 515 | } 516 | catch (Exception ex) 517 | { 518 | return null; 519 | } 520 | } 521 | 522 | private static string GetInitializer(object initExpression) 523 | { 524 | if (initExpression != null) 525 | { 526 | var initializer = initExpression.ToString(); 527 | if (IsNumber.IsMatch(initializer)) 528 | { 529 | return initializer; 530 | } 531 | } 532 | return null; 533 | } 534 | } 535 | } 536 | -------------------------------------------------------------------------------- /src/Generator/IntellisenseProperty.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace TypeScriptDefinitionGenerator 4 | { 5 | public class IntellisenseProperty 6 | { 7 | public IntellisenseProperty() 8 | { 9 | 10 | } 11 | public IntellisenseProperty(IntellisenseType type, string propertyName) 12 | { 13 | Type = type; 14 | Name = propertyName; 15 | } 16 | 17 | public string Name { get; set; } 18 | 19 | public string NameWithOption { get { return (this.Type != null && this.Type.IsOptional) ? this.Name + "?" : this.Name; } } 20 | 21 | [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", 22 | Justification = "Unambiguous in this context.")] 23 | public IntellisenseType Type { get; set; } 24 | 25 | public string Summary { get; set; } 26 | public string InitExpression { get; set; } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Generator/IntellisenseType.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Globalization; 3 | 4 | namespace TypeScriptDefinitionGenerator 5 | { 6 | public class IntellisenseType 7 | { 8 | /// 9 | /// This is the name of this type as it appears in the source code 10 | /// 11 | public string CodeName { get; set; } 12 | 13 | /// 14 | /// Indicates whether this type is array. Is this property is true, then all other properties 15 | /// describe not the type itself, but rather the type of the array's elements. 16 | /// 17 | public bool IsArray { get; set; } 18 | 19 | public bool IsDictionary { get; set; } 20 | 21 | public bool IsOptional { get { return CodeName.EndsWith("?"); } } 22 | 23 | /// 24 | /// If this type is itself part of a source code file that has a .d.ts definitions file attached, 25 | /// this property will contain the full (namespace-qualified) client-side name of that type. 26 | /// Otherwise, this property is null. 27 | /// 28 | public string ClientSideReferenceName { get; set; } 29 | 30 | /// 31 | /// This is TypeScript-formed shape of the type (i.e. inline type definition). It is used for the case where 32 | /// the type is not primitive, but does not have its own named client-side definition. 33 | /// 34 | public IEnumerable Shape { get; set; } 35 | 36 | public bool IsKnownType 37 | { 38 | get { return TypeScriptName != "any"; } 39 | } 40 | 41 | public string TypeScriptName 42 | { 43 | get 44 | { 45 | if (IsDictionary) return GetKVPTypes(); 46 | return GetTargetName(CodeName, false); 47 | } 48 | } 49 | 50 | private string GetTargetName(string codeName, bool js) 51 | { 52 | var t = codeName.ToLowerInvariant().TrimEnd('?'); 53 | switch (t) 54 | { 55 | case "int16": 56 | case "int32": 57 | case "int64": 58 | case "short": 59 | case "int": 60 | case "long": 61 | case "float": 62 | case "double": 63 | case "decimal": 64 | case "biginteger": 65 | return js ? "Number" : "number"; 66 | 67 | case "datetime": 68 | case "datetimeoffset": 69 | case "system.datetime": 70 | case "system.datetimeoffset": 71 | return "Date"; 72 | 73 | case "string": 74 | return js ? "String" : "string"; 75 | 76 | case "bool": 77 | case "boolean": 78 | return js ? "Boolean" : "boolean"; 79 | } 80 | return js ? "Object" : GetComplexTypeScriptName(); 81 | } 82 | 83 | private string GetComplexTypeScriptName() 84 | { 85 | return ClientSideReferenceName ?? "any"; 86 | } 87 | 88 | private string GetKVPTypes() 89 | { 90 | var type = CodeName.ToLowerInvariant().TrimEnd('?'); 91 | var types = type.Split('<', '>')[1].Split(','); 92 | string keyType = GetTargetName(types[0].Trim(), false); 93 | 94 | if (keyType != "string" && keyType != "number") 95 | { // only string or number are allowed for keys 96 | keyType = "string"; 97 | } 98 | 99 | string valueType = GetTargetName(types[1].Trim(), false); 100 | 101 | return string.Format(CultureInfo.CurrentCulture, "{{ [index: {0}]: {1} }}", keyType, valueType); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /src/Generator/IntellisenseWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | 7 | using TypeScriptDefinitionGenerator.Helpers; 8 | 9 | namespace TypeScriptDefinitionGenerator 10 | { 11 | internal static class IntellisenseWriter 12 | { 13 | private static readonly Regex _whitespaceTrimmer = new Regex(@"^\s+|\s+$|\s*[\r\n]+\s*", RegexOptions.Compiled); 14 | 15 | public static string WriteTypeScript(IEnumerable objects) 16 | { 17 | var sb = new StringBuilder(); 18 | 19 | foreach (IGrouping ns in objects.GroupBy(o => o.Namespace)) 20 | { 21 | if (!Options.GlobalScope) 22 | { 23 | sb.AppendFormat("declare module {0} {{\r\n", ns.Key); 24 | } 25 | 26 | foreach (IntellisenseObject io in ns) 27 | { 28 | if (!string.IsNullOrEmpty(io.Summary)) 29 | { 30 | sb.AppendLine("\t/** " + _whitespaceTrimmer.Replace(io.Summary, "") + " */"); 31 | } 32 | 33 | if (io.IsEnum) 34 | { 35 | if (!Options.StringInsteadOfEnum) 36 | { 37 | sb.AppendLine("\tconst enum " + Utility.CamelCaseClassName(io.Name) + " {"); 38 | 39 | foreach (IntellisenseProperty p in io.Properties) 40 | { 41 | WriteTypeScriptComment(p, sb); 42 | 43 | if (p.InitExpression != null) 44 | { 45 | sb.AppendLine("\t\t" + Utility.CamelCaseEnumValue(p.Name) + " = " + CleanEnumInitValue(p.InitExpression) + ","); 46 | } 47 | else 48 | { 49 | sb.AppendLine("\t\t" + Utility.CamelCaseEnumValue(p.Name) + ","); 50 | } 51 | } 52 | 53 | sb.AppendLine("\t}"); 54 | } 55 | else 56 | { 57 | IEnumerable propsNames = io.Properties.Select(p => "'" + Utility.CamelCaseEnumValue(p.Name) + "'"); 58 | var propsString = string.Join(" | ", propsNames); 59 | 60 | sb.AppendLine("\ttype " + Utility.CamelCaseClassName(io.Name) + " = " + propsString + ";"); 61 | } 62 | } 63 | else 64 | { 65 | var type = Options.ClassInsteadOfInterface ? "\tclass " : "\tinterface "; 66 | sb.Append(type).Append(Utility.CamelCaseClassName(io.Name)).Append(" "); 67 | 68 | if (!string.IsNullOrEmpty(io.BaseName)) 69 | { 70 | sb.Append("extends "); 71 | 72 | if (!string.IsNullOrEmpty(io.BaseNamespace) && io.BaseNamespace != io.Namespace) 73 | { 74 | sb.Append(io.BaseNamespace).Append("."); 75 | } 76 | 77 | sb.Append(Utility.CamelCaseClassName(io.BaseName)).Append(" "); 78 | } 79 | 80 | WriteTSInterfaceDefinition(sb, "\t", io.Properties); 81 | sb.AppendLine(); 82 | } 83 | } 84 | 85 | if (!Options.GlobalScope) 86 | { 87 | sb.AppendLine("}"); 88 | } 89 | } 90 | 91 | return sb.ToString(); 92 | } 93 | 94 | private static string CleanEnumInitValue(string value) 95 | { 96 | value = value.TrimEnd('u', 'U', 'l', 'L'); //uint ulong long 97 | if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) 98 | { 99 | return value; 100 | } 101 | 102 | var trimedValue = value.TrimStart('0'); // prevent numbers to be parsed as octal in js. 103 | if (trimedValue.Length > 0) 104 | { 105 | return trimedValue; 106 | } 107 | 108 | return "0"; 109 | } 110 | 111 | 112 | private static void WriteTypeScriptComment(IntellisenseProperty p, StringBuilder sb) 113 | { 114 | if (string.IsNullOrEmpty(p.Summary)) 115 | { 116 | return; 117 | } 118 | 119 | sb.AppendLine("\t\t/** " + _whitespaceTrimmer.Replace(p.Summary, "") + " */"); 120 | } 121 | 122 | private static void WriteTSInterfaceDefinition(StringBuilder sb, string prefix, 123 | IEnumerable props) 124 | { 125 | sb.AppendLine("{"); 126 | 127 | foreach (IntellisenseProperty p in props) 128 | { 129 | WriteTypeScriptComment(p, sb); 130 | sb.AppendFormat("{0}\t{1}: ", prefix, Utility.CamelCasePropertyName(p.NameWithOption)); 131 | 132 | if (p.Type.IsKnownType) 133 | { 134 | sb.Append(p.Type.TypeScriptName); 135 | } 136 | else 137 | { 138 | if (p.Type.Shape == null) 139 | { 140 | sb.Append("any"); 141 | } 142 | else 143 | { 144 | WriteTSInterfaceDefinition(sb, prefix + "\t", p.Type.Shape); 145 | } 146 | } 147 | if (p.Type.IsArray) 148 | { 149 | sb.Append("[]"); 150 | } 151 | 152 | sb.AppendLine(";"); 153 | } 154 | 155 | sb.Append(prefix).Append("}"); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/Helpers/Utility.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace TypeScriptDefinitionGenerator.Helpers 4 | { 5 | internal static class Utility 6 | { 7 | public static string CamelCaseClassName(string name) 8 | { 9 | if (Options.CamelCaseTypeNames) 10 | { 11 | name = CamelCase(name); 12 | } 13 | return name; 14 | } 15 | 16 | public static string CamelCaseEnumValue(string name) 17 | { 18 | if (Options.CamelCaseEnumerationValues) 19 | { 20 | name = CamelCase(name); 21 | } 22 | return name; 23 | } 24 | 25 | public static string CamelCasePropertyName(string name) 26 | { 27 | if (Options.CamelCasePropertyNames) 28 | { 29 | name = CamelCase(name); 30 | } 31 | return name; 32 | } 33 | 34 | private static string CamelCase(string name) 35 | { 36 | if (string.IsNullOrWhiteSpace(name)) 37 | { 38 | return name; 39 | } 40 | return name[0].ToString(CultureInfo.CurrentCulture).ToLower(CultureInfo.CurrentCulture) + name.Substring(1); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Helpers/VSHelpers.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using EnvDTE80; 3 | using Microsoft.VisualStudio; 4 | using Microsoft.VisualStudio.Shell; 5 | using Microsoft.VisualStudio.Shell.Interop; 6 | using System; 7 | using System.Diagnostics; 8 | using System.IO; 9 | 10 | namespace TypeScriptDefinitionGenerator 11 | { 12 | public static class VSHelpers 13 | { 14 | private static DTE2 DTE { get; } = Package.GetGlobalService(typeof(DTE)) as DTE2; 15 | 16 | public static ProjectItem GetProjectItem(string fileName) 17 | { 18 | return DTE.Solution.FindProjectItem(fileName); 19 | } 20 | 21 | public static void CheckFileOutOfSourceControl(string file) 22 | { 23 | if (!File.Exists(file) || DTE.Solution.FindProjectItem(file) == null) 24 | return; 25 | 26 | if (DTE.SourceControl.IsItemUnderSCC(file) && !DTE.SourceControl.IsItemCheckedOut(file)) 27 | DTE.SourceControl.CheckOutItem(file); 28 | 29 | var info = new FileInfo(file) 30 | { 31 | IsReadOnly = false 32 | }; 33 | } 34 | 35 | public static bool IsKind(this Project project, params string[] kindGuids) 36 | { 37 | foreach (var guid in kindGuids) 38 | { 39 | if (project.Kind.Equals(guid, StringComparison.OrdinalIgnoreCase)) 40 | return true; 41 | } 42 | 43 | return false; 44 | } 45 | 46 | internal static readonly Guid outputPaneGuid = new Guid(); 47 | 48 | internal static void WriteOnOutputWindow(string text) 49 | { 50 | WriteOnOutputWindow("TypeScript Definition Generator: " + text, outputPaneGuid); 51 | } 52 | internal static void WriteOnBuildOutputWindow(string text) 53 | { 54 | WriteOnOutputWindow(text, Microsoft.VisualStudio.VSConstants.OutputWindowPaneGuid.BuildOutputPane_guid); 55 | } 56 | 57 | internal static void WriteOnOutputWindow(string text, Guid guidBuildOutput) 58 | { 59 | if (!text.EndsWith(Environment.NewLine)) 60 | { 61 | text += Environment.NewLine; 62 | } 63 | 64 | // At first write the text on the debug output. 65 | Debug.Write(text); 66 | 67 | // Now get the SVsOutputWindow service from the service provider. 68 | IVsOutputWindow outputWindow = ServiceProvider.GlobalProvider.GetService(typeof(SVsOutputWindow)) as IVsOutputWindow; 69 | if (null == outputWindow) 70 | { 71 | // If the provider doesn't expose the service there is nothing we can do. 72 | // Write a message on the debug output and exit. 73 | Debug.WriteLine("Can not get the SVsOutputWindow service."); 74 | return; 75 | } 76 | 77 | // We can not write on the Output window itself, but only on one of its panes. 78 | // Here we try to use the "General" pane. 79 | IVsOutputWindowPane windowPane; 80 | if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.GetPane(ref guidBuildOutput, out windowPane)) || 81 | (null == windowPane)) 82 | { 83 | if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.CreatePane(ref guidBuildOutput, "TypeScript Definition Generator", 1, 0))) 84 | { 85 | // Nothing to do here, just debug output and exit 86 | Debug.WriteLine("Failed to create the Output window pane."); 87 | return; 88 | } 89 | if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.GetPane(ref guidBuildOutput, out windowPane)) || 90 | (null == windowPane)) 91 | { 92 | // Again, there is nothing we can do to recover from this error, so write on the 93 | // debug output and exit. 94 | Debug.WriteLine("Failed to get the Output window pane."); 95 | return; 96 | } 97 | } 98 | if (Microsoft.VisualStudio.ErrorHandler.Failed(windowPane.Activate())) 99 | { 100 | Debug.WriteLine("Failed to activate the Output window pane."); 101 | return; 102 | } 103 | 104 | // Finally we can write on the window pane. 105 | if (Microsoft.VisualStudio.ErrorHandler.Failed(windowPane.OutputString(text))) 106 | { 107 | Debug.WriteLine("Failed to write on the Output window pane."); 108 | } 109 | } 110 | } 111 | 112 | public static class ProjectTypes 113 | { 114 | public const string ASPNET_5 = "{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}"; 115 | public const string DOTNET_Core = "{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"; 116 | public const string WEBSITE_PROJECT = "{E24C65DC-7377-472B-9ABA-BC803B73C61A}"; 117 | public const string UNIVERSAL_APP = "{262852C6-CD72-467D-83FE-5EEB1973A190}"; 118 | public const string NODE_JS = "{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}"; 119 | public const string SSDT = "{00d1a9c2-b5f0-4af3-8072-f6c62b433612}"; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Options.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using Microsoft.VisualStudio.Shell; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.ComponentModel; 6 | using System.IO; 7 | 8 | namespace TypeScriptDefinitionGenerator 9 | { 10 | public class OptionsDialogPage : DialogPage 11 | { 12 | internal const bool _defCamelCaseEnumerationValues = true; 13 | internal const bool _defCamelCasePropertyNames = true; 14 | internal const bool _defCamelCaseTypeNames = true; 15 | internal const bool _defClassInsteadOfInterface = false; 16 | internal const bool _defStringInsteadOfEnum = false; 17 | internal const bool _defGlobalScope = false; 18 | internal const bool _defWebEssentials2015 = true; 19 | internal const string _defModuleName = "server"; 20 | 21 | [Category("Casing")] 22 | [DisplayName("Camel case enum values")] 23 | [DefaultValue(_defCamelCaseEnumerationValues)] 24 | public bool CamelCaseEnumerationValues { get; set; } = _defCamelCaseEnumerationValues; 25 | 26 | [Category("Casing")] 27 | [DisplayName("Camel case property names")] 28 | [DefaultValue(_defCamelCasePropertyNames)] 29 | public bool CamelCasePropertyNames { get; set; } = _defCamelCasePropertyNames; 30 | 31 | [Category("Casing")] 32 | [DisplayName("Camel case type names")] 33 | [DefaultValue(_defCamelCaseTypeNames)] 34 | public bool CamelCaseTypeNames { get; set; } = _defCamelCaseTypeNames; 35 | 36 | [Category("Settings")] 37 | [DisplayName("Default Module name")] 38 | [Description("Set the top-level module name for the generated .d.ts file. Default is \"server\"")] 39 | public string DefaultModuleName { get; set; } = _defModuleName; 40 | 41 | [Category("Settings")] 42 | [DisplayName("Class instead of Interface")] 43 | [Description("Controls whether to generate a class or an interface: default is an Interface")] 44 | [DefaultValue(_defClassInsteadOfInterface)] 45 | public bool ClassInsteadOfInterface { get; set; } = _defClassInsteadOfInterface; 46 | 47 | [Category("Settings")] 48 | [DisplayName("String enumeration instead of Enum")] 49 | [Description("Controls whether to generate an enum or a string ('a' | 'b' | 'c'): default is an Interface")] 50 | [DefaultValue(_defStringInsteadOfEnum)] 51 | public bool StringInsteadOfEnum { get; set; } = _defStringInsteadOfEnum; 52 | 53 | [Category("Settings")] 54 | [DisplayName("Generate in global scope")] 55 | [Description("Controls whether to generate types in Global scope or wrapped in a module")] 56 | [DefaultValue(_defGlobalScope)] 57 | public bool GlobalScope { get; set; } = _defGlobalScope; 58 | 59 | 60 | [Category("Compatibilty")] 61 | [DisplayName("Web Essentials 2015 file names")] 62 | [Description("Web Essentials 2015 format is .cs.d.ts instead of .d.ts")] 63 | [DefaultValue(_defWebEssentials2015)] 64 | public bool WebEssentials2015 { get; set; } = _defWebEssentials2015; 65 | } 66 | 67 | public class Options 68 | { 69 | const string OVERRIDE_FILE_NAME = "tsdefgen.json"; 70 | static OptionsOverride overrides { get; set; } = null; 71 | static public bool CamelCaseEnumerationValues 72 | { 73 | get 74 | { 75 | return overrides != null ? overrides.CamelCaseEnumerationValues : DtsPackage.Options.CamelCaseEnumerationValues; 76 | } 77 | } 78 | 79 | static public bool CamelCasePropertyNames 80 | { 81 | get 82 | { 83 | return overrides != null ? overrides.CamelCasePropertyNames : DtsPackage.Options.CamelCasePropertyNames; 84 | } 85 | } 86 | 87 | static public bool CamelCaseTypeNames 88 | { 89 | get 90 | { 91 | return overrides != null ? overrides.CamelCaseTypeNames : DtsPackage.Options.CamelCaseTypeNames; 92 | } 93 | } 94 | 95 | static public string DefaultModuleName 96 | { 97 | get 98 | { 99 | return overrides != null ? overrides.DefaultModuleName : DtsPackage.Options.DefaultModuleName; 100 | } 101 | } 102 | 103 | static public bool ClassInsteadOfInterface 104 | { 105 | get 106 | { 107 | return overrides != null ? overrides.ClassInsteadOfInterface : DtsPackage.Options.ClassInsteadOfInterface; 108 | } 109 | } 110 | 111 | static public bool StringInsteadOfEnum 112 | { 113 | get 114 | { 115 | return overrides != null ? overrides.StringInsteadOfEnum : DtsPackage.Options.StringInsteadOfEnum; 116 | } 117 | } 118 | 119 | static public bool GlobalScope 120 | { 121 | get 122 | { 123 | return overrides != null ? overrides.GlobalScope : DtsPackage.Options.GlobalScope; 124 | } 125 | } 126 | 127 | static public bool WebEssentials2015 128 | { 129 | get 130 | { 131 | return overrides != null ? overrides.WebEssentials2015 : DtsPackage.Options.WebEssentials2015; 132 | } 133 | } 134 | 135 | public static void ReadOptionOverrides(ProjectItem sourceItem, bool display = true) 136 | { 137 | Project proj = sourceItem.ContainingProject; 138 | 139 | string jsonName = ""; 140 | 141 | foreach (ProjectItem item in proj.ProjectItems) 142 | { 143 | if (item.Name.ToLower() == OVERRIDE_FILE_NAME.ToLower()) 144 | { 145 | jsonName = item.FileNames[0]; 146 | break; 147 | } 148 | } 149 | 150 | if (!string.IsNullOrEmpty(jsonName)) 151 | { 152 | // it has been modified since last read - so read again 153 | try 154 | { 155 | overrides = JsonConvert.DeserializeObject(File.ReadAllText(jsonName)); 156 | if (display) 157 | { 158 | VSHelpers.WriteOnOutputWindow(string.Format("Override file processed: {0}", jsonName)); 159 | } 160 | else 161 | { 162 | System.Diagnostics.Debug.WriteLine(string.Format("Override file processed: {0}", jsonName)); 163 | } 164 | } 165 | catch (Exception e) when (e is Newtonsoft.Json.JsonReaderException || e is Newtonsoft.Json.JsonSerializationException) 166 | { 167 | overrides = null; // incase the read fails 168 | VSHelpers.WriteOnOutputWindow(string.Format("Error in Override file: {0}", jsonName)); 169 | VSHelpers.WriteOnOutputWindow(e.Message); 170 | throw; 171 | } 172 | } 173 | else 174 | { 175 | if (display) 176 | { 177 | VSHelpers.WriteOnOutputWindow("Using Global Settings"); 178 | } 179 | else 180 | { 181 | System.Diagnostics.Debug.WriteLine("Using Global Settings"); 182 | } 183 | overrides = null; 184 | } 185 | } 186 | 187 | } 188 | 189 | internal class OptionsOverride 190 | { 191 | // [JsonRequired] 192 | public bool CamelCaseEnumerationValues { get; set; } = OptionsDialogPage._defCamelCaseEnumerationValues; 193 | 194 | // [JsonRequired] 195 | public bool CamelCasePropertyNames { get; set; } = OptionsDialogPage._defCamelCasePropertyNames; 196 | 197 | // [JsonRequired] 198 | public bool CamelCaseTypeNames { get; set; } = OptionsDialogPage._defCamelCaseTypeNames; 199 | 200 | // [JsonRequired] 201 | public string DefaultModuleName { get; set; } = OptionsDialogPage._defModuleName; 202 | 203 | // [JsonRequired] 204 | public bool ClassInsteadOfInterface { get; set; } = OptionsDialogPage._defClassInsteadOfInterface; 205 | 206 | // [JsonRequired] 207 | public bool StringInsteadOfEnum { get; set; } = OptionsDialogPage._defStringInsteadOfEnum; 208 | 209 | // [JsonRequired] 210 | public bool GlobalScope { get; set; } = OptionsDialogPage._defGlobalScope; 211 | 212 | // [JsonRequired] 213 | public bool WebEssentials2015 { get; set; } = OptionsDialogPage._defWebEssentials2015; 214 | 215 | } 216 | 217 | } 218 | -------------------------------------------------------------------------------- /src/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using TypeScriptDefinitionGenerator; 4 | 5 | [assembly: AssemblyTitle(Vsix.Name)] 6 | [assembly: AssemblyDescription(Vsix.Description)] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany(Vsix.Author)] 9 | [assembly: AssemblyProduct(Vsix.Name)] 10 | [assembly: AssemblyCopyright(Vsix.Author)] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: AssemblyVersion(Vsix.Version)] 17 | [assembly: AssemblyFileVersion(Vsix.Version)] 18 | -------------------------------------------------------------------------------- /src/Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/TypeScriptDefinitionGenerator/e8f5890040763c43176b85bed65748788aea6352/src/Resources/Icon.png -------------------------------------------------------------------------------- /src/TypeScriptDefinitionGenerator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 15.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | Program 7 | $(DevEnvDir)\devenv.exe 8 | /rootsuffix Exp 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | Debug 17 | AnyCPU 18 | 2.0 19 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 20 | {F8E2F6C6-880A-4C64-A9EE-0CF04F9C3CC6} 21 | Library 22 | Properties 23 | TypeScriptDefinitionGenerator 24 | TypeScriptDefinitionGenerator 25 | v4.7.2 26 | true 27 | true 28 | true 29 | true 30 | true 31 | false 32 | 33 | 34 | true 35 | full 36 | false 37 | bin\Debug\ 38 | DEBUG;TRACE 39 | prompt 40 | 4 41 | 42 | 43 | pdbonly 44 | true 45 | bin\Release\ 46 | TRACE 47 | prompt 48 | 4 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | True 57 | True 58 | DtsPackage.vsct 59 | 60 | 61 | 62 | 63 | Component 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | True 74 | True 75 | source.extension.vsixmanifest 76 | 77 | 78 | 79 | 80 | Resources\LICENSE 81 | true 82 | 83 | 84 | Designer 85 | VsixManifestGenerator 86 | source.extension.cs 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Menus.ctmenu 101 | VsctGenerator 102 | DtsPackage1.cs 103 | 104 | 105 | 106 | 107 | true 108 | 109 | 110 | true 111 | 112 | 113 | 114 | 115 | 17.0.32112.339 116 | 117 | 118 | 17.1.4054 119 | runtime; build; native; contentfiles; analyzers; buildtransitive 120 | all 121 | 122 | 123 | 124 | 125 | 132 | -------------------------------------------------------------------------------- /src/source.extension.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // This file was generated by VSIX Synchronizer 4 | // 5 | // ------------------------------------------------------------------------------ 6 | namespace TypeScriptDefinitionGenerator 7 | { 8 | internal sealed partial class Vsix 9 | { 10 | public const string Id = "cad7b20b-4b83-4ca6-bf24-ca36a494241d"; 11 | public const string Name = "TypeScript Definition Generator 2022"; 12 | public const string Description = @"Creates and synchronizes TypeScript Definition files (d.ts) from C#/VB model classes to build strongly typed web application where the server- and client-side models are in sync. Works on all .NET project types"; 13 | public const string Language = "en-US"; 14 | public const string Version = "1.3"; 15 | public const string Author = "Mads Kristensen"; 16 | public const string Tags = "typescript, d.ts, model"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | TypeScript Definition Generator 2022 6 | Creates and synchronizes TypeScript Definition files (d.ts) from C#/VB model classes to build strongly typed web application where the server- and client-side models are in sync. Works on all .NET project types 7 | https://github.com/madskristensen/TypeScriptDefinitionGenerator 8 | Resources\LICENSE 9 | https://github.com/madskristensen/TypeScriptDefinitionGenerator/blob/master/CHANGELOG.md 10 | Resources\Icon.png 11 | Resources\Icon.png 12 | typescript, d.ts, model 13 | 14 | 15 | 16 | amd64 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/website.pkgdef: -------------------------------------------------------------------------------- 1 | ; C# 2 | [$RootKey$\Projects\{E24C65DC-7377-472b-9ABA-BC803B73C61A}\RelatedFiles\.cs] 3 | "RelationType"=dword:00000001 4 | 5 | [$RootKey$\Projects\{E24C65DC-7377-472b-9ABA-BC803B73C61A}\RelatedFiles\.cs\.d.ts] 6 | 7 | ; VB 8 | [$RootKey$\Projects\{E24C65DC-7377-472b-9ABA-BC803B73C61A}\RelatedFiles\.vb] 9 | "RelationType"=dword:00000001 10 | 11 | [$RootKey$\Projects\{E24C65DC-7377-472b-9ABA-BC803B73C61A}\RelatedFiles\.vb\.d.ts] --------------------------------------------------------------------------------