├── .eslintrc.json ├── .github └── dependabot.yml ├── .gitignore ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── images ├── GenerateMermaid.gif ├── ListAllImports.gif ├── ModulesToMarkdown.gif ├── PackageMarkdown.gif ├── SaveAsDot.gif ├── Settings.gif ├── ShowComponentHierarchy.gif ├── ShowComponentHierarchy2.gif ├── ShowComponentHierarchy3.gif ├── ShowDependencyInjectionGraph.gif ├── ShowModuleHierarchy.gif ├── ShowProjectStructure.gif └── logo.xcf ├── javascript └── vis-network.min.js ├── logo.png ├── package-lock.json ├── package.json ├── package.py ├── src ├── arrayUtils.ts ├── commands │ ├── commandBase.ts │ ├── componentHierarchyMarkdown.ts │ ├── generateDependencyInjectionGraph.ts │ ├── index.ts │ ├── listAllImports.ts │ ├── modulesToMarkdown.ts │ ├── packageJsonToMarkdown.ts │ ├── projectDirectoryStructure.ts │ ├── showComponentHierarchy.ts │ ├── showHierarchyBase.ts │ └── showModuleHierarchy.ts ├── componentManager.ts ├── config.ts ├── dgmlManager.ts ├── extension.ts ├── filesystemUtils.ts ├── graphvizManager.ts ├── index.ts ├── model │ ├── ArrowType.ts │ ├── Attribute.ts │ ├── BoundingBox.ts │ ├── Category.ts │ ├── Component.ts │ ├── Edge.ts │ ├── GraphState.ts │ ├── NamedEntity.ts │ ├── NetworkNode.ts │ ├── NgModule.ts │ ├── Node.ts │ ├── NodeType.ts │ ├── Position.ts │ ├── Project.ts │ ├── Settings.ts │ └── index.ts ├── moduleManager.ts └── stringUtils.ts ├── stylesheet ├── all.min.css ├── showHierarchy.css └── vis-network.min.css ├── templates ├── showHierarchy_Template.html └── showHierarchy_Template.js ├── tsconfig.json ├── webfonts ├── fa-brands-400.eot ├── fa-brands-400.ttf ├── fa-brands-400.woff ├── fa-brands-400.woff2 ├── fa-regular-400.eot ├── fa-regular-400.ttf ├── fa-regular-400.woff ├── fa-regular-400.woff2 ├── fa-solid-900.eot ├── fa-solid-900.ttf ├── fa-solid-900.woff └── fa-solid-900.woff2 ├── webpack-resolve-tsconfig-path-to-webpack-alias.js └── webpack.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "extends": "eslint:recommended", 12 | "rules": { 13 | "@typescript-eslint/naming-convention": "warn", 14 | "@typescript-eslint/semi": "warn", 15 | "curly": "warn", 16 | "eqeqeq": "warn", 17 | "no-throw-literal": "warn", 18 | "semi": "warn", 19 | "no-undef": "off", 20 | "no-unused-vars": "warn" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Enable version updates for npm 9 | - package-ecosystem: "npm" 10 | # Look for `package.json` and `lock` files in the `root` directory 11 | directory: "/" 12 | # Check the npm registry for updates every day (weekdays) 13 | schedule: 14 | interval: "weekly" 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | out 4 | dist 5 | showComponentHierarchy.js 6 | showModuleHierarchy.js 7 | *.vsix 8 | stylesheet/all.vscode.min.css 9 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | images/** 4 | out/** 5 | src/** 6 | node_modules/** 7 | .github 8 | .gitignore 9 | .yarnrc 10 | vsc-extension-quickstart.md 11 | **/tsconfig.json 12 | **/tslint.json 13 | **/.eslintrc.json 14 | **/*.map 15 | **/*.ts 16 | **/*.xcf 17 | **/*.bak 18 | **/*.py 19 | **/*.cmd 20 | showComponentHierarchy.js 21 | showModuleHierarchy.js 22 | out.txt 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## Version 1.14.0 4 | 5 | - Fixed bug in regex to account for comments before class definitions. Thanks @FBosito for the error report and bugfix suggestion. 6 | - Bump Packages to latest 7 | 8 | ## Version 1.13.1 9 | 10 | - Maintenance: Bump packages to latest 11 | 12 | ## Version 1.13.0 13 | 14 | - Fixed issue with case sensitive filenames when using the .vscodeangulartools setting file. 15 | - Updated vis-network.min.js to latest v9.1.9 16 | - Maintenance: Bump packages to latest 17 | 18 | ## Version 1.12.0 19 | 20 | - Updated vis-network.min.js to latest v9.1.7 21 | - Maintenance: Bump packages to latest 22 | 23 | ## Version 1.11.0 24 | 25 | - It is now possible to include or exclude folders by placing a .vscodeangulartools config file in the project folder 26 | - Bugfix: Mermaid markdown graph representing the component hierarchy now shows unrelated components correctly. 27 | 28 | ## Version 1.10.2 29 | 30 | - Maintenance: Bump packages to latest 31 | 32 | ## Version 1.10.1 33 | 34 | - Adding label to links in generated dgml file. 35 | - Bugfix: TsFilename path in generated dgml file are now correct for injected services. 36 | 37 | ## Version 1.10.0 38 | 39 | - In the dependency injection graph, now all injected entities are shown. 40 | 41 | ## Version 1.9.1 42 | 43 | - Maintenance: Bump packages to latest 44 | 45 | ## Version 1.9.0 46 | 47 | - Routing relations between components are now visualized. Notice that routes defined with module destinations are not yet visualized. 48 | 49 | ## Version 1.8.5 50 | 51 | - Security issue fixed: Now using npm package @xmldom/xmldom instead of xmldom 52 | 53 | ## Version 1.8.4 54 | 55 | - Maintenance: Bump packages to latest 56 | 57 | ## Version 1.8.3 58 | 59 | - When edges have the same source and target the edges no longer overlap. 60 | - A popup is now shown when the mouse pointer is hovered above an edge or a node. 61 | - It is now possible to click a node in the graphs to open the file the node is associated with. 62 | - Bugfix: Save as Png is now working correctly 63 | 64 | ## Version 1.8.2 65 | 66 | - The state of the checkbox and the dropdown boxes are now persisted. 67 | 68 | ## Version 1.8.1 69 | 70 | - The position of nodes are now persisted. If you rearrange the nodes you work won't be lost when changing to another tab and back again. 71 | - You now has to click the 'Regenerate graph' button to generate a new random graph. 72 | 73 | ## Version 1.8.0 74 | 75 | - All three graph type can now be saved as a dot file. 76 | - A file icon is added to nodes when saving to a dgml file. 77 | - Bugfix: If a graph window has already been opened for a specific graph then it is reused next time the graph is generated instead of opening a new window every time. 78 | 79 | ## Version 1.7.3 80 | 81 | - Bugfix: GraphDirection is now saved correctly when saving as Dgml. 82 | 83 | ## Version 1.7.2 84 | 85 | - Added missing xml prolog to generated dgml file. 86 | - Added missing properties to generated dgml file. 87 | - Removed unused properties in generated dgml file. 88 | - Changed property TsFilename to TypescriptFilepath and made it a reference attribute. 89 | - Now only show message in the vscode status bar when dgml or png file has been saved. 90 | 91 | ## Version 1.7.1 92 | 93 | - Bugfix: input, outputs, viewchilds, etc. are now shown correctly in the dependency injection graph. 94 | 95 | ## Version 1.7.0 96 | 97 | - All three graph types: Dependency injection graph, module hierarchy graph and component hierarchy graph can now be saved as a dgml file. 98 | - Component hierarchy dgml command has been removed because its now obsolete. 99 | 100 | ## Version 1.6.0 101 | 102 | - Added Dependency injection graph command that visualizes what services are injected into the components. Graph also include all the inputs, outputs, viewchild, viewchildren, contentchild and contentchildren of each component. 103 | 104 | ## Version 1.5.1 105 | 106 | - Now using webpack to reduce size of extension. 107 | 108 | ## Version 1.5.0 109 | 110 | - Added Show module hierarchy command that visualizes the module structure and their import and exports. 111 | - Improved the layout of nodes in the Show Component hierarchy command 112 | 113 | ## Version 1.4.5 114 | 115 | - Bugfix: The Package json to Markdown command failed if the node_modules folder did not exist. 116 | 117 | ## Version 1.4.4 118 | 119 | - Bugfix: Not all modules was recognized by the 'Generate a Markdown file of all modules in the current project.' command. 120 | - Various refactoring. 121 | 122 | ## Version 1.4.3 123 | 124 | - Updated: PackageJsonToMarkdown command now shows the package license for each package. 125 | 126 | ## Version 1.4.2 127 | 128 | - You can now select a random layout for the component hierarchy graph. 129 | - Bugfix: Savíng a selection did not work if you had change the layout of the graph. 130 | 131 | ## Version 1.4.1 132 | 133 | - Updated: PackageJsonToMarkdown command now shows the local version pattern and the latest version number of the packages. 134 | - Bugfix: The list of packages in the generated markdown file for the packages is now sorted. 135 | 136 | ## Version 1.4.0 137 | 138 | - New feature: You can now choose how to auto-layout the component hierarchy graph. 139 | 140 | ## Version 1.3.0 141 | 142 | - New feature: You can now generate a markdown file that summarizes all the Angular modules. 143 | - Various refactoring. 144 | 145 | ## Version 1.2.2 146 | 147 | - Fixed bug: Show component hierarchy no longer fails when the Angular application contains recursive component. 148 | 149 | ## Version 1.2.1 150 | 151 | - Fixed bug in Readme.md 152 | 153 | ## Version 1.2.0 154 | 155 | - New feature: It is now possible to change shape, line colors, default filenames and a lot more for the commands in the extension. Go to the Preferences in the File menu and find the AngularTools section under the Extensions section. 156 | 157 | ## Version 1.1.1 158 | 159 | - Bugfix: The edges in the component hierarchy graph are now straight and the dynamics of the graph is turned off. 160 | 161 | ## Version 1.1.0 162 | 163 | - New feature: You can now generate a markdown(Mermaid) file with the component hierarchy. 164 | - Bugfix: The List imports command now also find the require(...) packages. 165 | 166 | ## Version 1.0.1 167 | 168 | - Bugfix: The Show component hierarchy graph command did not work 169 | 170 | ## Version 1.0.0 171 | 172 | - Initial release 173 | 174 | [Keep a Changelog](http://keepachangelog.com/) 175 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Allan Simonsen 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AngularTools 2 | 3 | ![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/coderAllan.vscode-angulartools) ![Visual Studio Marketplace Installs](https://img.shields.io/visual-studio-marketplace/i/coderAllan.vscode-angulartools) ![GitHub top language](https://img.shields.io/github/languages/top/CoderAllan/vscode-angulartools.svg) ![GitHub last commit](https://img.shields.io/github/last-commit/CoderAllan/vscode-angulartools.svg) ![GitHub](https://img.shields.io/github/license/CoderAllan/vscode-angulartools.svg) 4 | 5 | AngularTools is a Visual Studio Code extension with a collection of tools for exploring an Angular project, help you with documenting, reverse engineering a project or help when refactoring. 6 | 7 | Some of the tools may seem very basic, but can be a powerful help when you have them right at your fingertips. 8 | 9 | Find it on the [Visual Studio Code marketplace](https://marketplace.visualstudio.com/items?itemName=coderAllan.vscode-angulartools). 10 | 11 | ## Features 12 | 13 | * Show the module hierarchy 14 | * Show the dependency injection graph 15 | * Show the component hierarchy 16 | * Save the graphs as png, dgml or dot files 17 | * Summarizes all the Angular modules 18 | * Generate a markdown file with the component hierarchy in Mermaid format. 19 | * Show the directory structure of the project 20 | * Generate list of packages used in the project 21 | * List all imports 22 | 23 | Below is a detailed description of each feature. 24 | 25 | ### Show the module hierarchy [#](#show-module-hierarchy- 'Show the module hierarchy') 26 | 27 | The 'Show module hierarchy' command is used for visualizing the hierarchy of the modules in an Angular application. The command scans all the *.ts files in the project to identify classes decorated with the `@NgModule` class decorator and then visualize how each module is imported by other modules. It will also visualize the classes specified in the imports and the exports section of the NgModule definition. 28 | 29 | When you click a node the file it is associated with will be opened in the editor. 30 | 31 | In the visualization the classes will be colored depending on the class decorator of each class: 32 | 33 | * Modules are red 34 | * Components are blue 35 | * Pipes are yellow 36 | * Directives are green 37 | 38 | ![Show module hierarchy](https://github.com/CoderAllan/vscode-angulartools/raw/main/images/ShowModuleHierarchy.gif) 39 | 40 | ### Show the dependency injection graph [#](#show-dependency-injection-graph- 'Show the dependency injection graph') 41 | 42 | The 'Show a graph representing the components and the injected dependencies' command generates a directed graph representing the components and the services injected into the components of an Angular application. The command scans all *.ts files of the application and for each class decorated with the @Component decorator, it analyses the constructor and each field in the class to identify all injected classes and to identify all the fields decorated with the Input, Output, ViewChild, ViewChildren, ContentChild and ContentChildren decorators. 43 | 44 | When you click a node the file it is associated with will be opened in the editor. 45 | 46 | In the visualization the components will by default be colored dark blue and the injected classes will be colored light blue. 47 | 48 | ![Show dependency injection graph](https://github.com/CoderAllan/vscode-angulartools/raw/main/images/ShowDependencyInjectionGraph.gif) 49 | 50 | ### Show the component hierarchy [#](#show-component-hierarchy- 'Show the component hierarchy') 51 | 52 | The 'Show the component hierarchy' command is used for visualizing the component hierarchy and Angular application. It analyses all the \*.component.ts files and all the corresponding \*.component.html template files to determine how the component use each other and then generates a directed graph showing the component hierarchy. 53 | 54 | When you click a node the file it is associated with will be opened in the editor. 55 | 56 | The command uses a vscode webview extension component to render the hierarchy using html, javascript and the [Vis.js](https://visjs.org/index.html) javascript library. This has the downside that copying the generated graph to the clipboard is not yet possible due to limitations in the vscode extension api. So to overcome this limitation the generated graph can be saved as a Png file to the root of the project you are analyzing. You can also save the graph in the Dgml format or in the GraphViz Dot format. 57 | 58 | ![Show component hierarchy](https://github.com/CoderAllan/vscode-angulartools/raw/main/images/ShowComponentHierarchy.gif) 59 | 60 | Save as GraphViz dot format and generate a graph from the dot file: 61 | ![Save as GraphViz dot format](https://github.com/CoderAllan/vscode-angulartools/raw/main/images/SaveAsDot.gif) 62 | 63 | You can also choose to save a selection from the graph as shown in the example below. 64 | 65 | ![Show component hierarchy](https://github.com/CoderAllan/vscode-angulartools/raw/main/images/ShowComponentHierarchy2.gif) 66 | 67 | By default the component hierarchy graph is rendered using a random layout. You can change the layout method to use a hierarchical layout to line up the nodes in the graph in different ways and also change how the layout engine sorts the nodes in the graph. This is all done by checking the 'Change layout' checkbox to show the drop down boxes with the available options. 68 | 69 | ![Show component hierarchy](https://github.com/CoderAllan/vscode-angulartools/raw/main/images/ShowComponentHierarchy3.gif) 70 | 71 | ### Generate a markdown file with the component hierarchy in Mermaid format [#](#component-hierarchy-markdown- 'Component hierarchy in Mermaid format') 72 | 73 | This command will generate the component hierarchy in markdown format using [Mermaid notation](https://mermaid-js.github.io/mermaid/#/). 74 | 75 | Please notice that some online tools and sites do not support Mermaid markdown format yet, like GitHub and Visual Studio Code. To preview markdown files using the mermaid notation in Visual Studio Code i'm using the [Markdown Preview Mermaid Support](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid) extension. 76 | 77 | ![Generate graph in Mermaid notation](https://github.com/CoderAllan/vscode-angulartools/raw/main/images/GenerateMermaid.gif) 78 | 79 | ### Generate a markdown file that summarizes all the Angular modules [#](modules-to-markdown- 'Modules to Markdown') 80 | 81 | This command will scan all *.ts files in the workspace folders and find those classes that are decorated with the '@NgModule' class decorator and parse the module definition and then summarize each module into a markdown file. 82 | 83 | The summarization will consist of two parts: First a table listing how many imports, exports, declarations and so on each module contains. The second part shows what each module contain. 84 | 85 | ![Generate markdown file from all modules](https://github.com/CoderAllan/vscode-angulartools/raw/main/images/ModulesToMarkdown.gif) 86 | 87 | ### Show the directory structure of the project [#](#directory-structure- 'Show the directory structure of the project') 88 | 89 | This command lists the entire directory structure of the currently open project. Sometimes this can be a quick way to get an overview of the project if you are new to the project before og maybe need to document it. 90 | 91 | The directory structure will be listed in the output window for the AngularTools extension for easy copy/pasting. 92 | 93 | ![Show the directory structure of the project](https://github.com/CoderAllan/vscode-angulartools/raw/main/images/ShowProjectStructure.gif) 94 | 95 | ### Generate list of packages used in the project [#](#package-json-to-markdown- 'Generate list of packages used in the project') 96 | 97 | Sometimes your boss or a customer requires documentation of which packages is used in the project. This command can save you a lot of tedious manual work by analyzing the package.json file and for each referenced package queries the [npmjs.com](https://www.npmjs.com/) website to fetch the description for the package and from that generates a Markdown file with a table of the packages with their descriptions. 98 | 99 | The license information is retrieved from the package.json file for each package in the node_modules folder in the root of the workspace, this mean that the license will show 'N/A' if you have not run the 'npm install' yet. 100 | 101 | ![Generate list of packages used in the project](https://github.com/CoderAllan/vscode-angulartools/raw/main/images/PackageMarkdown.gif) 102 | 103 | ### List all imports [#](#list-all-imports- 'List all imports') 104 | 105 | This command will analyse all Angular components in the project and collect all the imports of the components together with the number of times the imported component is used. This can be useful when refactoring or identifying frequently used modules. Usually frequently used modules needs extra attention or care before being changed during refactoring. You can also use the command to identify modules that are not referenced in a consistent way where some components are using relative path and some components using path-aliases. 106 | 107 | ![List all imports](https://github.com/CoderAllan/vscode-angulartools/raw/main/images/ListAllImports.gif) 108 | 109 | ## Settings 110 | 111 | ### Project settings 112 | 113 | You can place a file named ".vscodeangulartools" in the workspace root folder where you can specify folders to exclude or include in json format. 114 | 115 | Only the two parameters "includeFolders" and "excludeFolders" are used at the moment. They are both semicolon separated list of folder names. 116 | 117 | If you add a includeFolders parameter, then only child folders of the folders in the list will be included. If you add a folder to the excludeFolders parameter then no files from the folder or its child-folders will be included. 118 | 119 | Notice that the filename comparison from v1.13.0 is case-sensitive. This may affect Windows user because the Windows filesystem is not case sensitive and if you previously added a path then it may not work anymore. So if you experience that your graph is no longer rendered as it used to be, then it may be because your includeFolders paths have the wrong casing in the settings file. 120 | 121 | Example: 122 | 123 | ```json 124 | { 125 | "includeFolders": "d:\\Src\\Angular-Examples\\angular8-example-app\\src", 126 | "excludeFolders": "d:\\Src\\Angular-Examples\\angular8-example-app\\src\\app\\shared" 127 | } 128 | ``` 129 | 130 | ### Global settings 131 | 132 | In the Visual Studio Code settings you find under File --> Preferences --> Settings, under Extensions, there is a section with all the settings for AngularTools. It is possible to change the default filenames used when the extension saves a file to the workspace folder. You can change how the component hierarchy graph nodes are rendered, the edge endings, the color of the root nodes and a lot more. 133 | 134 | See the full list of settings below the screenshot. 135 | 136 | ![AngularTools settings](https://github.com/CoderAllan/vscode-angulartools/raw/main/images/Settings.gif) 137 | 138 | | Setting | Description | 139 | | --- | --- | 140 | | angularTools.excludeDirectories | Semicolon separated list of directories that should be excluded when scanning for for Angular components. | 141 | | angularTools.dgmlGraph.graphLayout | This is the algorithm used to layout the nodes of the graph. Sugiyama wil try to avoid crossing edges as far as possible. ForceDirected will try to cluster the nodes. | 142 | | angularTools.dgmlGraph.graphDirection | This will make the layout algorithm position the graph nodes in the specified direction. | 143 | | angularTools.dependencyInjectionGraph.pngGraphFilename | The default name used when saving the dependency injection hierarchy graph to a Png file. | 144 | | angularTools.dependencyInjectionGraph.dgmlGraphFilename| The default filename used when saving a dependency injection hierarchy graph to a Directed Graph Markup Language (Dgml) file. | 145 | | angularTools.dependencyInjectionGraph.edgeArrowToType | The default ending of the edges. | 146 | | angularTools.showComponentHierarchy.componentHierarchyPngFilename | The default name used when saving the component hierarchy to a Png file. | 147 | | angularTools.showComponentHierarchy.componentHierarchyDgmlGraphFilename | The default name used when saving the component hierarchy to a Dgml file. | 148 | | angularTools.showComponentHierarchy.edgeArrowToType | The default ending of the edges. | 149 | | angularTools.showModuleHierarchy.moduleHierarchyPngFilename | The default name used when saving the module hierarchy to a Png file. | 150 | | angularTools.showModuleHierarchy.moduleHierarchyDgmlGraphFilename | The default name used when saving the module hierarchy to a Dgml file. | 151 | | angularTools.showModuleHierarchy.edgeArrowToType | The default ending of the edges. | 152 | | angularTools.graphSelection.graphSelectionGuidelineColor | The color of the guidelines used when selecting part of a component hierarchy graph. The string should be in rgb format or standard css color names. | 153 | | angularTools.graphSelection.graphSelectionGuidelineWidth | The width of the guide lines shown when selecting part of a component hierarchy graph | 154 | | angularTools.graphSelection.graphSelectionColor | The color of the selection rectangle used when selecting part of a component hierarchy graph. The string should be in rgb format or standard css color names. | 155 | | angularTools.graphSelection.graphSelectionWidth | The width of the selection rectangle shown when selecting part of a component hierarchy graph | 156 | | angularTools.graphNodes.rootNodeBackgroundColor | The color of the root nodes of the component hierarchy graph. The string should be in rgba format or standard css color names. | 157 | | angularTools.graphNodes.rootNodeNodeShape | The default shape of the nodes representing root nodes. Notice that 'ellipse','circle','database','box' and 'text' have the label inside the shape, the rest have the label outside the shape. | 158 | | angularTools.graphNodes.componentNodeShape | The default shape of the nodes representing components. Notice that 'ellipse','circle','database','box' and 'text' have the label inside the shape, the rest have the label outside the shape. | 159 | | angularTools.graphNodes.moduleNodeBackgroundColor | The default color of the nodes representing modules. | 160 | | angularTools.graphNodes.moduleNodeShape | The default shape of the nodes representing modules. Notice that 'ellipse','circle','database','box' and 'text' have the label inside the shape, the rest have the label outside the shape. | 161 | | angularTools.graphNodes.pipeNodeBackgroundColor | The default color of the nodes representing pipes. | 162 | | angularTools.graphNodes.pipeNodeShape | The default shape of the nodes representing pipes. Notice that 'ellipse','circle','database','box' and 'text' have the label inside the shape, the rest have the label outside the shape. | 163 | | angularTools.graphNodes.directiveNodeBackgroundColor | The default color of the nodes representing directives. | 164 | | angularTools.graphNodes.directiveNodeShape | The default shape of the nodes representing directives. Notice that 'ellipse','circle','database','box' and 'text' have the label inside the shape, the rest have the label outside the shape. | 165 | | angularTools.graphNodes.injectableNodeBackgroundColor | The default color of the nodes representing injected components. | 166 | | angularTools.graphNodes.injectableNodeShape | The default shape of the nodes representing directives. Notice that 'ellipse','circle','database','box' and 'text' have the label inside the shape, the rest have the label outside the shape. | 167 | | angularTools.graphNodes.maximumNodeLabelLength | The maximum length of the label for the nodes in the hierarchy. If the class name, module definition, module imports, exports, directives or pipes are longer than the specified number of characters, it will be truncated to this length. | 168 | | angularTools.edges.importEdgeColor | The default color of the edges representing imports. | 169 | | angularTools.edges.exportEdgeColor | The default color of the edges representing exports. | 170 | | angularTools.edges.injectableEdgeColor | The default color of the edges representing injectables. | 171 | | angularTools.edges.usesEdgeColor | The default color of the edges representing components using other components. | 172 | | angularTools.packageJsonMarkdownFilename | The default filename used when saving the packages.json as a markdown file. | 173 | | angularTools.projectDirectoryStructureMarkdownFilename | The default filename used when saving the project directory structure as a markdown file. | 174 | | angularTools.componentHierarchyMarkdownFilename | The default name used when saving the component hierarchy to a markdown file. | 175 | | angularTools.modulesToMarkdownFilename | The default name used when saving the project module to a markdown file. | 176 | 177 | 178 | ## Third party components and resources 179 | 180 | * [Vis.js](https://visjs.org/index.html) - Used for generating the directed graph for showing the component hierarchy. 181 | * [npmjs.com](https://www.npmjs.com/) - The extension queries the NpmJs.com api. 182 | * [Fontawesome](https://fontawesome.com/) - Provides icons for the buttons. 183 | * [Directed Graph Markup Language (DGML) reference](https://schemas.microsoft.com/vs/2009/dgml/) 184 | * [DGMLViewer VsCode extension](https://marketplace.visualstudio.com/items?itemName=coderAllan.vscode-dgmlviewer) 185 | * [Mermaid markdown notation](https://mermaid-js.github.io/mermaid/#/) 186 | -------------------------------------------------------------------------------- /images/GenerateMermaid.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/images/GenerateMermaid.gif -------------------------------------------------------------------------------- /images/ListAllImports.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/images/ListAllImports.gif -------------------------------------------------------------------------------- /images/ModulesToMarkdown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/images/ModulesToMarkdown.gif -------------------------------------------------------------------------------- /images/PackageMarkdown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/images/PackageMarkdown.gif -------------------------------------------------------------------------------- /images/SaveAsDot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/images/SaveAsDot.gif -------------------------------------------------------------------------------- /images/Settings.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/images/Settings.gif -------------------------------------------------------------------------------- /images/ShowComponentHierarchy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/images/ShowComponentHierarchy.gif -------------------------------------------------------------------------------- /images/ShowComponentHierarchy2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/images/ShowComponentHierarchy2.gif -------------------------------------------------------------------------------- /images/ShowComponentHierarchy3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/images/ShowComponentHierarchy3.gif -------------------------------------------------------------------------------- /images/ShowDependencyInjectionGraph.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/images/ShowDependencyInjectionGraph.gif -------------------------------------------------------------------------------- /images/ShowModuleHierarchy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/images/ShowModuleHierarchy.gif -------------------------------------------------------------------------------- /images/ShowProjectStructure.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/images/ShowProjectStructure.gif -------------------------------------------------------------------------------- /images/logo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/images/logo.xcf -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-angulartools", 3 | "publisher": "coderAllan", 4 | "displayName": "AngularTools", 5 | "description": "AngularTools is a collection of tools for exploring an Angular project, help you with documenting, reverse engineering a project or help when refactoring.", 6 | "icon": "logo.png", 7 | "version": "1.14.0", 8 | "license": "MIT", 9 | "repository": "https://github.com/CoderAllan/vscode-angulartools", 10 | "author": { 11 | "name": "Allan Simonsen", 12 | "url": "https://github.com/CoderAllan" 13 | }, 14 | "engines": { 15 | "vscode": "^1.95.0" 16 | }, 17 | "categories": [ 18 | "Other", 19 | "Visualization" 20 | ], 21 | "keywords": [ 22 | "angular", 23 | "dgml", 24 | "typescript", 25 | "markdown", 26 | "tools" 27 | ], 28 | "activationEvents": [], 29 | "main": "./dist/extension.js", 30 | "contributes": { 31 | "commands": [ 32 | { 33 | "command": "angulartools.listAllImports", 34 | "title": "AngularTools: List all imports" 35 | }, 36 | { 37 | "command": "angulartools.projectDirectoryStructure", 38 | "title": "AngularTools: Show the project directory structure" 39 | }, 40 | { 41 | "command": "angulartools.modulesToMarkdown", 42 | "title": "AngularTools: Generate a Markdown file of all modules in the current project." 43 | }, 44 | { 45 | "command": "angulartools.packageJsonToMarkdown", 46 | "title": "AngularTools: Generate a Markdown file from package.json files in the workspace." 47 | }, 48 | { 49 | "command": "angulartools.componentHierarchyMarkdown", 50 | "title": "AngularTools: Generate a Mermaid Markdown file representing the component hierarchy.." 51 | }, 52 | { 53 | "command": "angulartools.showComponentHierarchy", 54 | "title": "AngularTools: Show a graph representing the component hierarchy." 55 | }, 56 | { 57 | "command": "angulartools.showModuleHierarchy", 58 | "title": "AngularTools: Show a graph representing the module hierarchy." 59 | }, 60 | { 61 | "command": "angulartools.generateDependencyInjectionGraph", 62 | "title": "AngularTools: Show a graph representing the components and the injected dependencies." 63 | } 64 | ], 65 | "configuration": { 66 | "title": "AngularTools", 67 | "properties": { 68 | "angularTools.excludeDirectories": { 69 | "type": "string", 70 | "default": "bin;obj;node_modules;dist;packages;.git;.vs;.github", 71 | "description": "Semicolon separated list of directories that should be excluded when scanning for for Angular components." 72 | }, 73 | "angularTools.dgmlGraph.graphLayout": { 74 | "type": "string", 75 | "default": "Sugiyama", 76 | "enum": [ 77 | "Sugiyama", 78 | "ForceDirected" 79 | ], 80 | "description": "This is the algorithm used to layout the nodes of the graph. Sugiyama wil try to avoid crossing edges as far as possible. ForceDirected will try to cluster the nodes." 81 | }, 82 | "angularTools.dgmlGraph.graphDirection": { 83 | "type": "string", 84 | "default": "LeftToRight", 85 | "enum": [ 86 | "TopToBottom", 87 | "BottomToTop", 88 | "LeftToRight", 89 | "RightToLeft" 90 | ], 91 | "description": "This will make the layout algorithm position the graph nodes in the specified direction." 92 | }, 93 | "angularTools.dependencyInjectionGraph.pngGraphFilename": { 94 | "type": "string", 95 | "default": "DependencyInjectionGraph.png", 96 | "description": "The default name used when saving the dependency injection hierarchy graph to a Png file." 97 | }, 98 | "angularTools.dependencyInjectionGraph.dgmlGraphFilename": { 99 | "type": "string", 100 | "default": "DependencyInjectionGraph.dgml", 101 | "description": "The default filename used when saving a dependency injection hierarchy graph to a Directed Graph Markup Language (Dgml) file." 102 | }, 103 | "angularTools.dependencyInjectionGraph.edgeArrowToType": { 104 | "type": "string", 105 | "default": "triangle", 106 | "enum": [ 107 | "arrow", 108 | "bar", 109 | "box", 110 | "circle", 111 | "crow", 112 | "curve", 113 | "diamond", 114 | "inv_curve", 115 | "inv_triangle", 116 | "triangle", 117 | "vee" 118 | ], 119 | "description": "The default ending of the edges." 120 | }, 121 | "angularTools.showComponentHierarchy.componentHierarchyPngFilename": { 122 | "type": "string", 123 | "default": "ComponentHierarchy.png", 124 | "description": "The default name used when saving the component hierarchy to a Png file." 125 | }, 126 | "angularTools.showComponentHierarchy.componentHierarchyDgmlGraphFilename": { 127 | "type": "string", 128 | "default": "ComponentHierarchy.dgml", 129 | "description": "The default name used when saving the component hierarchy to a Dgml file." 130 | }, 131 | "angularTools.showComponentHierarchy.edgeArrowToType": { 132 | "type": "string", 133 | "default": "triangle", 134 | "enum": [ 135 | "arrow", 136 | "bar", 137 | "box", 138 | "circle", 139 | "crow", 140 | "curve", 141 | "diamond", 142 | "inv_curve", 143 | "inv_triangle", 144 | "triangle", 145 | "vee" 146 | ], 147 | "description": "The default ending of the edges." 148 | }, 149 | "angularTools.showModuleHierarchy.moduleHierarchyPngFilename": { 150 | "type": "string", 151 | "default": "ModuleHierarchy.png", 152 | "description": "The default name used when saving the module hierarchy to a Png file." 153 | }, 154 | "angularTools.showModuleHierarchy.moduleHierarchyDgmlGraphFilename": { 155 | "type": "string", 156 | "default": "ModuleHierarchy.dgml", 157 | "description": "The default name used when saving the module hierarchy to a Dgml file." 158 | }, 159 | "angularTools.showModuleHierarchy.edgeArrowToType": { 160 | "type": "string", 161 | "default": "triangle", 162 | "enum": [ 163 | "arrow", 164 | "bar", 165 | "box", 166 | "circle", 167 | "crow", 168 | "curve", 169 | "diamond", 170 | "inv_curve", 171 | "inv_triangle", 172 | "triangle", 173 | "vee" 174 | ], 175 | "description": "The default ending of the edges." 176 | }, 177 | "angularTools.graphSelection.graphSelectionGuidelineColor": { 178 | "type": "string", 179 | "default": "blue", 180 | "description": "The color of the guidelines used when selecting part of a component hierarchy graph. The string should be in rgb format or standard css color names." 181 | }, 182 | "angularTools.graphSelection.graphSelectionGuidelineWidth": { 183 | "type": "number", 184 | "default": "1", 185 | "description": "The width of the guide lines shown when selecting part of a component hierarchy graph" 186 | }, 187 | "angularTools.graphSelection.graphSelectionColor": { 188 | "type": "string", 189 | "default": "red", 190 | "description": "The color of the selection rectangle used when selecting part of a component hierarchy graph. The string should be in rgb format or standard css color names." 191 | }, 192 | "angularTools.graphSelection.graphSelectionWidth": { 193 | "type": "number", 194 | "default": "2", 195 | "description": "The width of the selection rectangle shown when selecting part of a component hierarchy graph" 196 | }, 197 | "angularTools.graphNodes.rootNodeBackgroundColor": { 198 | "type": "string", 199 | "default": "#00ff00", 200 | "description": "The color of the root nodes of the component hierarchy graph. The string should be in rgb format or standard css color names." 201 | }, 202 | "angularTools.graphNodes.rootNodeNodeShape": { 203 | "type": "string", 204 | "default": "box", 205 | "enum": [ 206 | "ellipse", 207 | "circle", 208 | "database", 209 | "box", 210 | "text", 211 | "diamond", 212 | "dot", 213 | "star", 214 | "triangle", 215 | "triangleDown", 216 | "hexagon", 217 | "square" 218 | ], 219 | "description": "The default shape of the nodes representing root nodes. Notice that 'ellipse','circle','database','box' and 'text' have the label inside the shape, the rest have the label outside the shape." 220 | }, 221 | "angularTools.graphNodes.componentNodeBackgroundColor": { 222 | "type": "string", 223 | "default": "#0288d1", 224 | "description": "The default color of the nodes representing components." 225 | }, 226 | "angularTools.graphNodes.componentNodeShape": { 227 | "type": "string", 228 | "default": "box", 229 | "enum": [ 230 | "ellipse", 231 | "circle", 232 | "database", 233 | "box", 234 | "text", 235 | "diamond", 236 | "dot", 237 | "star", 238 | "triangle", 239 | "triangleDown", 240 | "hexagon", 241 | "square" 242 | ], 243 | "description": "The default shape of the nodes representing components. Notice that 'ellipse','circle','database','box' and 'text' have the label inside the shape, the rest have the label outside the shape." 244 | }, 245 | "angularTools.graphNodes.moduleNodeBackgroundColor": { 246 | "type": "string", 247 | "default": "#e53935", 248 | "description": "The default color of the nodes representing modules." 249 | }, 250 | "angularTools.graphNodes.moduleNodeShape": { 251 | "type": "string", 252 | "default": "box", 253 | "enum": [ 254 | "ellipse", 255 | "circle", 256 | "database", 257 | "box", 258 | "text", 259 | "diamond", 260 | "dot", 261 | "star", 262 | "triangle", 263 | "triangleDown", 264 | "hexagon", 265 | "square" 266 | ], 267 | "description": "The default shape of the nodes representing modules. Notice that 'ellipse','circle','database','box' and 'text' have the label inside the shape, the rest have the label outside the shape." 268 | }, 269 | "angularTools.graphNodes.pipeNodeBackgroundColor": { 270 | "type": "string", 271 | "default": "#00897b", 272 | "description": "The default color of the nodes representing pipes." 273 | }, 274 | "angularTools.graphNodes.pipeNodeShape": { 275 | "type": "string", 276 | "default": "box", 277 | "enum": [ 278 | "ellipse", 279 | "circle", 280 | "database", 281 | "box", 282 | "text", 283 | "diamond", 284 | "dot", 285 | "star", 286 | "triangle", 287 | "triangleDown", 288 | "hexagon", 289 | "square" 290 | ], 291 | "description": "The default shape of the nodes representing pipes. Notice that 'ellipse','circle','database','box' and 'text' have the label inside the shape, the rest have the label outside the shape." 292 | }, 293 | "angularTools.graphNodes.directiveNodeBackgroundColor": { 294 | "type": "string", 295 | "default": "#ffc107", 296 | "description": "The default color of the nodes representing directives." 297 | }, 298 | "angularTools.graphNodes.directiveNodeShape": { 299 | "type": "string", 300 | "default": "box", 301 | "enum": [ 302 | "ellipse", 303 | "circle", 304 | "database", 305 | "box", 306 | "text", 307 | "diamond", 308 | "dot", 309 | "star", 310 | "triangle", 311 | "triangleDown", 312 | "hexagon", 313 | "square" 314 | ], 315 | "description": "The default shape of the nodes representing directives. Notice that 'ellipse','circle','database','box' and 'text' have the label inside the shape, the rest have the label outside the shape." 316 | }, 317 | "angularTools.graphNodes.injectableNodeBackgroundColor": { 318 | "type": "string", 319 | "default": "#97c2fc", 320 | "description": "The default color of the nodes representing injected components." 321 | }, 322 | "angularTools.graphNodes.injectableNodeShape": { 323 | "type": "string", 324 | "default": "box", 325 | "enum": [ 326 | "ellipse", 327 | "circle", 328 | "database", 329 | "box", 330 | "text", 331 | "diamond", 332 | "dot", 333 | "star", 334 | "triangle", 335 | "triangleDown", 336 | "hexagon", 337 | "square" 338 | ], 339 | "description": "The default shape of the nodes representing directives. Notice that 'ellipse','circle','database','box' and 'text' have the label inside the shape, the rest have the label outside the shape." 340 | }, 341 | "angularTools.graphNodes.maximumNodeLabelLength": { 342 | "type": "number", 343 | "default": "40", 344 | "description": "The maximum length of the label for the nodes in the hierarchy. If the class name, module definition, module imports, exports, directives or pipes are longer than the specified number of characters, it will be truncated to this length." 345 | }, 346 | "angularTools.edges.importEdgeColor": { 347 | "type": "string", 348 | "default": "#43a047", 349 | "description": "The default color of the edges representing imports." 350 | }, 351 | "angularTools.edges.exportEdgeColor": { 352 | "type": "string", 353 | "default": "#0288d1", 354 | "description": "The default color of the edges representing exports." 355 | }, 356 | "angularTools.edges.injectableEdgeColor": { 357 | "type": "string", 358 | "default": "#0288d1", 359 | "description": "The default color of the edges representing injectables." 360 | }, 361 | "angularTools.edges.usesEdgeColor": { 362 | "type": "string", 363 | "default": "#0288d1", 364 | "description": "The default color of the edges representing components using other components." 365 | }, 366 | "angularTools.edges.routeEdgeColor": { 367 | "type": "string", 368 | "default": "#FF8C00", 369 | "description": "The default color of the edges representing routes." 370 | }, 371 | "angularTools.packageJsonMarkdownFilename": { 372 | "type": "string", 373 | "default": "ReadMe-PackagesJson.md", 374 | "description": "The default filename used when saving the packages.json as a markdown file." 375 | }, 376 | "angularTools.projectDirectoryStructureMarkdownFilename": { 377 | "type": "string", 378 | "default": "ReadMe-ProjectDirectoryStructure.md", 379 | "description": "The default filename used when saving the project directory structure as a markdown file." 380 | }, 381 | "angularTools.componentHierarchyMarkdownFilename": { 382 | "type": "string", 383 | "default": "ComponentHierarchy.md", 384 | "description": "The default name used when saving the component hierarchy to a markdown file." 385 | }, 386 | "angularTools.modulesToMarkdownFilename": { 387 | "type": "string", 388 | "default": "Modules.md", 389 | "description": "The default name used when saving the project module to a markdown file." 390 | }, 391 | "angularTools.showPopupsOverNodesAndEdges": { 392 | "type": "boolean", 393 | "default": true, 394 | "description": "When the setting is set to true a popup with various information from node or edge will be shown when the mouse pointer hovers over nodes and edges." 395 | } 396 | } 397 | } 398 | }, 399 | "scripts": { 400 | "vscode:prepublish": "webpack --mode production", 401 | "webpack": "webpack --mode development", 402 | "webpack-dev": "webpack --mode development --watch", 403 | "compile": "tsc -p ./", 404 | "lint": "eslint src --ext ts", 405 | "watch": "tsc -watch -p ./", 406 | "pretest": "npm run compile && npm run lint", 407 | "test": "node ./out/test/runTest.js" 408 | }, 409 | "devDependencies": { 410 | "@types/glob": "^8.1.0", 411 | "@types/mocha": "^10.0.10", 412 | "@types/node": "^22.15.29", 413 | "@types/vscode": "^1.100.0", 414 | "glob": "^11.0.2", 415 | "supports-color": "^10.0.0", 416 | "ts-loader": "^9.5.2", 417 | "typescript": "^5.8.3", 418 | "webpack": "^5.99.9", 419 | "webpack-cli": "^6.0.1" 420 | }, 421 | "dependencies": { 422 | "@types/xmldom": "^0.1.34", 423 | "@xmldom/xmldom": "^0.9.8", 424 | "bluebird": "^3.7.2", 425 | "browserslist": "^4.25.0", 426 | "js-base64": "^3.7.7", 427 | "npm-registry-fetch": "^18.0.2", 428 | "prettify-xml": "^1.2.0", 429 | "xmlserializer": "^0.6.1" 430 | } 431 | } -------------------------------------------------------------------------------- /package.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import json 3 | import re 4 | from git import Repo, TagReference 5 | 6 | 7 | def readVersionFromPackageJson() -> None: 8 | packageJson = open("package.json", "r") 9 | contentRaw = packageJson.read() 10 | contentJson = json.loads(contentRaw) 11 | packageJson.close() 12 | return contentJson["version"] 13 | 14 | 15 | def isPackageJsonVersionTagged(repo, packageJsonVersion) -> bool: 16 | packageJsonVersionTagFound = False 17 | for tag in repo.tags: 18 | if tag.name == packageJsonVersion: 19 | packageJsonVersionTagFound = True 20 | break 21 | return packageJsonVersionTagFound 22 | 23 | 24 | def isChangeLogUpdatedWithPackageJsonVersion(packageJsonVersion) -> bool: 25 | packageJsonVersionChangeLogEntryFound = False 26 | changeLog = open("CHANGELOG.md", "r") 27 | changeLogContent = changeLog.readlines() 28 | changeLog.close() 29 | for line in changeLogContent: 30 | match = re.search(f"^## Version {packageJsonVersion}$", line) 31 | if match: 32 | packageJsonVersionChangeLogEntryFound = True 33 | break 34 | return packageJsonVersionChangeLogEntryFound 35 | 36 | 37 | def isAllPackagesInstalledLocally() -> bool: 38 | process = subprocess.Popen(["cmd", "/c", "npm", "list", "--production", "--parseable", "--depth=99999", "--loglevel=error"], stderr=subprocess.PIPE) 39 | out = process.stderr.read() 40 | success = (len(out) == 0) 41 | if (not success): 42 | print(f"Error while checking if all packages is installed locally: {out}") 43 | return success 44 | 45 | 46 | def packageExtension() -> bool: 47 | process = subprocess.Popen(["cmd", "/c", "vsce", "package"], stderr=subprocess.PIPE) 48 | out = process.stderr.read() 49 | success = (len(out) == 0) 50 | if (not success): 51 | print(f"Error while packaging: {out}") 52 | return success 53 | 54 | 55 | def main(): 56 | packageJsonVersion = readVersionFromPackageJson() 57 | repo = Repo("./") 58 | packageJsonVersionTagFound = isPackageJsonVersionTagged(repo, packageJsonVersion) 59 | packageJsonVersionChangeLogEntryFound = isChangeLogUpdatedWithPackageJsonVersion(packageJsonVersion) 60 | allPackagesInstalledLocally = isAllPackagesInstalledLocally() 61 | 62 | if not packageJsonVersionChangeLogEntryFound: 63 | print("Fail: CHANGELOG.md not updated.") 64 | else: 65 | if not allPackagesInstalledLocally: 66 | print("Fail: Not all packages installed locally. Please run 'npm install' to install packages, or run 'npm list --production --parseable --depth=99999 --loglevel=error' to identify the issue.") 67 | else: 68 | if packageJsonVersionTagFound: 69 | print(f"Fail: Version already tagged: {packageJsonVersion}.") 70 | print("No vsix package created") 71 | else: 72 | print("Creating vsix package...") 73 | isPackagingOk = packageExtension() 74 | if not isPackagingOk: 75 | print("Fail: Packaging failed.") 76 | else: 77 | print(f"New version found in package.json: {packageJsonVersion}.") 78 | print("Creating tag in Git...") 79 | repo.create_tag(packageJsonVersion) 80 | 81 | 82 | main() 83 | -------------------------------------------------------------------------------- /src/arrayUtils.ts: -------------------------------------------------------------------------------- 1 | import path = require("path"); 2 | 3 | export class ArrayUtils { 4 | 5 | public static arrayToMarkdown(array: string[] | undefined): string { 6 | if (array === undefined || array.length === 0) { 7 | return ''; 8 | } else { 9 | if (typeof (array) === 'string') { 10 | return array; 11 | } else { 12 | try { 13 | return array.sort(this.sortStrings).join(',
'); 14 | } catch (ex) { 15 | return `ex: ${ex} - Len:${array.length} - type:${typeof (array)} - ${array}\n`; 16 | } 17 | } 18 | } 19 | } 20 | 21 | public static sortStrings(stringA: string, stringB: string): number { 22 | stringA = path.basename(stringA).toUpperCase(); 23 | stringB = path.basename(stringB).toUpperCase(); 24 | if (stringA < stringB) { 25 | return -1; 26 | } 27 | if (stringA > stringB) { 28 | return 1; 29 | } 30 | return 0; 31 | } 32 | 33 | public static arrayLength(array: string[]): number { 34 | if (typeof (array) === 'string') { 35 | return 1; 36 | } else { 37 | return array.length; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/commands/commandBase.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export class CommandBase { 4 | protected checkForOpenWorkspace() { 5 | if (!vscode.workspace.workspaceFolders) { 6 | vscode.window.showErrorMessage('AngularTools: Must be in an open WorkSpace. Choose menu File -> Open Folder... to open a folder.'); 7 | return; 8 | } 9 | } 10 | 11 | protected isTypescriptFile(filename: string): boolean { 12 | return filename.endsWith('.ts') && !filename.endsWith('index.ts'); 13 | } 14 | } -------------------------------------------------------------------------------- /src/commands/componentHierarchyMarkdown.ts: -------------------------------------------------------------------------------- 1 | import { CommandBase } from '@commands'; 2 | import { Component, Settings } from '@model'; 3 | import { Config, ComponentManager, FileSystemUtils } from '@src'; 4 | import * as path from 'path'; 5 | 6 | export class ComponentHierarchyMarkdown extends CommandBase { 7 | private config = new Config(); 8 | public static get commandName(): string { return 'componentHierarchyMarkdown'; } 9 | 10 | public execute() { 11 | this.checkForOpenWorkspace(); 12 | const fsUtils = new FileSystemUtils(); 13 | var workspaceFolder: string = fsUtils.getWorkspaceFolder(); 14 | const settings: Settings = fsUtils.readProjectSettings(this.config); 15 | const components = ComponentManager.scanWorkspaceForComponents(workspaceFolder, settings); 16 | 17 | let relations: string = ''; 18 | const addRelation = (from: string, to: string, visible: boolean) => { 19 | const relationType = visible ? "-->" : "~~~"; 20 | relations = relations + ` ${from}${relationType}${to}\n`; 21 | }; 22 | try { 23 | this.addMermaidRelations(components, addRelation); 24 | const markdownContent = '# Component hierarchy\n\n' + 25 | '```mermaid\n' + 26 | 'graph TD;\n' + 27 | relations + 28 | '```\n'; 29 | fsUtils.writeFileAndOpen(path.join(workspaceFolder, this.config.componentHierarchyMarkdownFilename), markdownContent); 30 | } catch (ex) { 31 | console.log('Angular Tools Exception: ' + ex); 32 | } 33 | } 34 | 35 | private addMermaidRelations(componentHash: { [selector: string]: Component; }, addRelation: (from: string, to: string, visible: boolean) => void) { 36 | for (let selector in componentHash) { 37 | const component = componentHash[selector]; 38 | if (component.isRoot) { 39 | this.generateMermaidRelation(component.subComponents, selector, "", addRelation); 40 | } 41 | } 42 | } 43 | 44 | private generateMermaidRelation(subComponents: Component[], displayName: string, parentDisplayName: string, addRelation: (from: string, to: string, visible: boolean) => void) { 45 | if (parentDisplayName.length > 0) { 46 | addRelation(parentDisplayName, displayName, true); 47 | } 48 | else { 49 | addRelation(displayName, displayName, false); 50 | } 51 | if (subComponents.length > 0) { 52 | subComponents.forEach((subComponent) => { 53 | this.generateMermaidRelation(subComponent.subComponents, subComponent.selector, displayName, addRelation); 54 | }); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/commands/generateDependencyInjectionGraph.ts: -------------------------------------------------------------------------------- 1 | import { ShowHierarchyBase } from './showHierarchyBase'; 2 | import { ModuleManager } from '@src'; 3 | import { ArrowType, Component, Edge, GraphState, NamedEntity, Node, NodeType, Project } from '@model'; 4 | import * as fs from 'fs'; 5 | import * as path from 'path'; 6 | import * as vscode from 'vscode'; 7 | 8 | export class GenerateDependencyInjectionGraph extends ShowHierarchyBase { 9 | static get commandName() { return 'generateDependencyInjectionGraph'; } 10 | public execute(webview: vscode.Webview) { 11 | this.checkForOpenWorkspace(); 12 | webview.onDidReceiveMessage( 13 | message => { 14 | switch (message.command) { 15 | case 'saveAsPng': { 16 | this.saveAsPng(this.config.dependencyInjectionPngFilename, message.text); 17 | return; 18 | } 19 | case 'saveAsDgml': { 20 | this.saveAsDgml(this.config.dependencyInjectionDgmlGraphFilename, message.text, `'The components hierarchy has been analyzed and a Directed Graph Markup Language (dgml) file '${this.config.dependencyInjectionDgmlGraphFilename}' has been created'`); 21 | return; 22 | } 23 | case 'saveAsDot': { 24 | this.saveAsDot(this.config.dependencyInjectionDotGraphFilename, message.text, 'dependencyInjectionGraph', `'The components hierarchy has been analyzed and a GraphViz (dot) file '${this.config.dependencyInjectionDotGraphFilename}' has been created'`); 25 | return; 26 | } 27 | case 'setGraphState': { 28 | const newGraphState: GraphState = JSON.parse(message.text); 29 | this.graphState = newGraphState; 30 | this.setNewState(this.graphState); 31 | this.nodes.forEach(node => { 32 | node.position = this.graphState.nodePositions[node.id]; 33 | }); 34 | this.addNodesAndEdges(project); 35 | this.generateAndSaveJavascriptContent(() => { }); 36 | return; 37 | } 38 | case 'openFile': { 39 | const filename = message.text; 40 | if (this.fsUtils.fileExists(filename)) { 41 | var openPath = vscode.Uri.parse("file:///" + filename); 42 | vscode.workspace.openTextDocument(openPath).then(doc => { 43 | vscode.window.showTextDocument(doc); 44 | }); 45 | } 46 | return; 47 | } 48 | } 49 | }, 50 | undefined, 51 | this.extensionContext.subscriptions 52 | ); 53 | var workspaceFolder = this.fsUtils.getWorkspaceFolder(); 54 | const errors: string[] = []; 55 | const project: Project = ModuleManager.scanProject(workspaceFolder, errors, this.isTypescriptFile); 56 | this.nodes = []; 57 | this.edges = []; 58 | this.addNodesAndEdges(project); 59 | let htmlContent = this.generateHtmlContent(webview, this.showModuleHierarchyJsFilename); 60 | // this.fsUtils.writeFile(this.extensionContext?.asAbsolutePath(path.join('out', GenerateDependencyInjectionGraph.commandName + '.html')), htmlContent, () => { }); // For debugging 61 | this.generateAndSaveJavascriptContent(() => { webview.html = htmlContent; }); 62 | if (errors.length > 0) { 63 | this.showErrors(errors, `Parsing of ${errors.length > 1 ? 'some' : 'one'} of the project files failed.\n`); 64 | } 65 | } 66 | 67 | private generateAndSaveJavascriptContent(callback: () => any) { 68 | const nodesJson = this.nodes 69 | .map(node => { return node.toJsonString(); }) 70 | .join(',\n'); 71 | const edgesJson = this.edges 72 | .map(edge => { return edge.toJsonString(); }) 73 | .join(',\n'); 74 | 75 | try { 76 | const jsContent = this.generateJavascriptContent(nodesJson, edgesJson); 77 | this.fsUtils.writeFile( 78 | this.extensionContext?.asAbsolutePath(path.join('.', this.showModuleHierarchyJsFilename)), 79 | jsContent, 80 | callback 81 | ); 82 | } 83 | catch (ex) { 84 | console.log('Angular Tools Exception:' + ex); 85 | } 86 | } 87 | 88 | private getNodeLabel(entity: Component | NamedEntity): string { 89 | let nodeContent: string = ''; 90 | nodeContent = `${entity.name}`; 91 | if (entity instanceof Component) { 92 | if (entity.inputs.length > 0) { 93 | const inputs = entity.inputs.map(i => i.name).join(", "); 94 | nodeContent += `\\nInputs: ${inputs}`; 95 | } 96 | if (entity.outputs.length > 0) { 97 | const outputs = entity.outputs.map(i => i.name).join(", "); 98 | nodeContent += `\\nOutputs: ${outputs}`; 99 | } 100 | if (entity.viewChilds.length > 0) { 101 | const viewchilds = entity.viewChilds.map(i => i.name).join(", "); 102 | nodeContent += `\\nViewchilds: ${viewchilds}`; 103 | } 104 | if (entity.viewChildren.length > 0) { 105 | const viewchildren = entity.viewChildren.map(i => i.name).join(", "); 106 | nodeContent += `\\nViewchildren: ${viewchildren}`; 107 | } 108 | if (entity.contentChilds.length > 0) { 109 | const contentchilds = entity.contentChilds.map(i => i.name).join(", "); 110 | nodeContent += `\\nContentchilds: ${contentchilds}`; 111 | } 112 | if (entity.contentChildren.length > 0) { 113 | const contentchildren = entity.contentChildren.map(i => i.name).join(", "); 114 | nodeContent += `\\nContentchildren: ${contentchildren}`; 115 | } 116 | } 117 | return nodeContent; 118 | } 119 | 120 | private addNodesAndEdges(project: Project) { 121 | this.addNamedEntityNodeAndEdges(project.components, NodeType.component, ArrowType.injectable); 122 | this.addNamedEntityNodeAndEdges(project.injectables, NodeType.injectable, ArrowType.injectable); 123 | this.addNamedEntityNodeAndEdges(project.directives, NodeType.directive, ArrowType.injectable); 124 | this.addNamedEntityNodeAndEdges(project.pipes, NodeType.pipe, ArrowType.injectable); 125 | } 126 | 127 | private addNamedEntityNodeAndEdges(namedEntityMap: Map, noteType: NodeType, arrowType: ArrowType) { 128 | namedEntityMap.forEach(namedEntity => { 129 | const entityPosition = this.graphState.nodePositions[namedEntity.name]; 130 | this.appendNodes([new Node(namedEntity.name, this.getNodeLabel(namedEntity), this.fixTsFilename(namedEntity.filename), namedEntity.filename, false, noteType, entityPosition)]); 131 | namedEntity.dependencies.forEach(dependency => { 132 | const dependencyPosition = this.graphState.nodePositions[dependency.name]; 133 | this.appendNodes([new Node(dependency.name, dependency.name, this.fixTsFilename(dependency.filename), dependency.filename, false, NodeType.injectable, dependencyPosition)]); 134 | this.appendEdges([new Edge((this.edges.length + 1).toString(), dependency.name, namedEntity.name, arrowType)]); 135 | }); 136 | }); 137 | } 138 | 139 | private fixTsFilename(filename: string): string { 140 | let entityFilename = filename.replace(this.workspaceDirectory, '.'); 141 | entityFilename = entityFilename.split('\\').join('/'); 142 | return entityFilename; 143 | } 144 | 145 | private generateJavascriptContent(nodesJson: string, edgesJson: string) { 146 | let template = fs.readFileSync(this.extensionContext?.asAbsolutePath(path.join('templates', this.templateJsFilename)), 'utf8'); 147 | let jsContent = template.replace('const nodes = new vis.DataSet([]);', `var nodes = new vis.DataSet([${nodesJson}]);`); 148 | jsContent = jsContent.replace('const edges = new vis.DataSet([]);', `var edges = new vis.DataSet([${edgesJson}]);`); 149 | jsContent = jsContent.replace('type: "triangle" // edge arrow to type', `type: "${this.config.dependencyInjectionEdgeArrowToType}" // edge arrow to type}`); 150 | jsContent = jsContent.replace('ctx.strokeStyle = \'blue\'; // graph selection guideline color', `ctx.strokeStyle = '${this.config.graphSelectionGuidelineColor}'; // graph selection guideline color`); 151 | jsContent = jsContent.replace('ctx.lineWidth = 1; // graph selection guideline width', `ctx.lineWidth = ${this.config.graphSelectionGuidelineWidth}; // graph selection guideline width`); 152 | jsContent = jsContent.replace('selectionCanvasContext.strokeStyle = \'red\';', `selectionCanvasContext.strokeStyle = '${this.config.graphSelectionColor}';`); 153 | jsContent = jsContent.replace('selectionCanvasContext.lineWidth = 2;', `selectionCanvasContext.lineWidth = ${this.config.graphSelectionWidth};`); 154 | jsContent = jsContent.replace('let showHierarchicalOptionsCheckboxChecked = false;', `let showHierarchicalOptionsCheckboxChecked = ${this.graphState.showHierarchicalOptions};`); 155 | jsContent = jsContent.replace('let hierarchicalOptionsDirectionSelectValue = undefined;', `let hierarchicalOptionsDirectionSelectValue = '${this.graphState.graphDirection}';`); 156 | jsContent = jsContent.replace('let hierarchicalOptionsSortMethodSelectValue = undefined;', `let hierarchicalOptionsSortMethodSelectValue = '${this.graphState.graphLayout}';`); 157 | jsContent = this.setGraphState(jsContent); 158 | return jsContent; 159 | } 160 | } -------------------------------------------------------------------------------- /src/commands/index.ts: -------------------------------------------------------------------------------- 1 | export * from './commandBase'; 2 | export * from './listAllImports'; 3 | export * from './modulesToMarkdown'; 4 | export * from './packageJsonToMarkdown'; 5 | export * from './projectDirectoryStructure'; 6 | export * from './showComponentHierarchy'; 7 | export * from './showModuleHierarchy'; 8 | export * from './componentHierarchyMarkdown'; 9 | export * from './generateDependencyInjectionGraph'; 10 | -------------------------------------------------------------------------------- /src/commands/listAllImports.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as vscode from 'vscode'; 3 | 4 | import { ArrayUtils, Config, FileSystemUtils } from '@src'; 5 | import { CommandBase } from '@commands'; 6 | import { Settings } from '@model'; 7 | 8 | export class ListAllImports extends CommandBase { 9 | private config = new Config(); 10 | public static get commandName(): string { return 'listAllImports'; } 11 | 12 | public execute() { 13 | this.checkForOpenWorkspace(); 14 | const fsUtils = new FileSystemUtils(); 15 | var workspaceFolder: string = fsUtils.getWorkspaceFolder(); 16 | const settings: Settings = fsUtils.readProjectSettings(this.config); 17 | const files = fsUtils.listFiles(workspaceFolder, settings, this.isTypescriptFile); 18 | this.writeResult(workspaceFolder, files); 19 | } 20 | 21 | private writeResult(workspaceDirectory: string, results: string[]) { 22 | const imports: { [module: string]: number } = {}; 23 | if (!results) { return; } 24 | for (let i = 0; i < results.length; i++) { 25 | var file = results[i]; 26 | const regexImports: RegExp = new RegExp('.*?\\s+from\\s+[\'"](.+)[\'"]', 'ig'); 27 | const regexRequires: RegExp = new RegExp('.*?\\s+require\\s*\\(\\s*[\'"](.+)[\'"]\\s*\\)', 'ig'); 28 | const content = fs.readFileSync(file, 'utf8'); 29 | const lines: string[] = content.split('\n'); 30 | lines.forEach(line => { 31 | const matchImports = regexImports.exec(line); 32 | const matchRequires = regexRequires.exec(line); 33 | if (matchImports || matchRequires) { 34 | let key: string = ''; 35 | if (matchImports) { 36 | key = matchImports[1]; 37 | } 38 | if (matchRequires) { 39 | key = matchRequires[1]; 40 | } 41 | if (Object.prototype.hasOwnProperty.call(imports, key)) { 42 | imports[key] = imports[key] + 1; 43 | } else { 44 | imports[key] = 1; 45 | } 46 | } 47 | }); 48 | } 49 | 50 | const angularToolsOutput = vscode.window.createOutputChannel(this.config.angularToolsOutputChannel); 51 | angularToolsOutput.clear(); 52 | angularToolsOutput.appendLine(`Imports for files in workspace: ${workspaceDirectory}`); 53 | angularToolsOutput.appendLine('The number following each import in the list is the number of occurrences of the package import.\n'); 54 | for (const key of Object.keys(imports).sort(ArrayUtils.sortStrings)) { 55 | angularToolsOutput.appendLine(`${key}: ${imports[key]}`); 56 | } 57 | angularToolsOutput.show(); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/commands/modulesToMarkdown.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from 'path'; 3 | import { ArrayUtils, Config, FileSystemUtils, ModuleManager } from '@src'; 4 | import { CommandBase } from '@commands'; 5 | import { NgModule, Project } from '@model'; 6 | 7 | export class ModulesToMarkdown extends CommandBase { 8 | private config = new Config(); 9 | public static get commandName(): string { return 'modulesToMarkdown'; } 10 | 11 | public execute() { 12 | this.checkForOpenWorkspace(); 13 | const fsUtils = new FileSystemUtils(); 14 | var workspaceDirectory: string = fsUtils.getWorkspaceFolder(); 15 | let markdownContent = '# Modules\n\n'; 16 | const errors: string[] = []; 17 | const project: Project = ModuleManager.scanProject(workspaceDirectory, errors, this.isTypescriptFile); 18 | markdownContent = markdownContent + 19 | '## Modules in workspace\n\n' + 20 | '| Module | Declarations | Imports | Exports | Bootstrap | Providers | Entry points |\n' + 21 | '| ---| --- | --- | --- | --- | --- | --- |\n'; 22 | let modulesMarkdown: string = ''; 23 | project.modules.forEach(module => { 24 | markdownContent = markdownContent + '| ' + module.moduleName + ' | ' + module.moduleStats().join(' | ') + ' |\n'; 25 | modulesMarkdown = modulesMarkdown + this.generateModuleMarkdown(module); 26 | }); 27 | markdownContent = markdownContent + '\n' + modulesMarkdown; 28 | if (errors.length > 0) { 29 | this.showErrors(errors); 30 | } 31 | fsUtils.writeFileAndOpen(path.join(workspaceDirectory, this.config.modulesToMarkdownFilename), markdownContent); 32 | } 33 | 34 | private generateModuleMarkdown(module: NgModule): string { 35 | let markdown = `## ${module.moduleName}\n\n`; 36 | markdown = markdown + 37 | 'Filename: ' + module.filename + '\n\n' + 38 | '| Section | Classes, service, modules |\n' + 39 | '| ---- |:-----------|\n' + 40 | '| Declarations | ' + ArrayUtils.arrayToMarkdown(module.declarations) + ' |\n' + 41 | '| Imports | ' + ArrayUtils.arrayToMarkdown(module.imports) + ' |\n' + 42 | '| Exports | ' + ArrayUtils.arrayToMarkdown(module.exports) + ' |\n' + 43 | '| Bootstrap | ' + ArrayUtils.arrayToMarkdown(module.bootstrap) + ' |\n' + 44 | '| Providers | ' + ArrayUtils.arrayToMarkdown(module.providers) + ' |\n' + 45 | '| Entry components | ' + ArrayUtils.arrayToMarkdown(module.entryComponents) + ' |\n' + 46 | '\n'; 47 | 48 | return markdown; 49 | } 50 | 51 | private showErrors(errors: string[]) { 52 | const angularToolsOutput = vscode.window.createOutputChannel(this.config.angularToolsOutputChannel); 53 | angularToolsOutput.clear(); 54 | angularToolsOutput.appendLine(`Parsing of ${errors.length > 1 ? 'some' : 'one'} of the modules failed.\n`); 55 | angularToolsOutput.appendLine('Below is a list of the errors.'); 56 | angularToolsOutput.appendLine(errors.join('\n')); 57 | angularToolsOutput.show(); 58 | } 59 | } -------------------------------------------------------------------------------- /src/commands/packageJsonToMarkdown.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | import { ArrayUtils, Config, FileSystemUtils } from '@src'; 5 | import { CommandBase } from '@commands'; 6 | 7 | const fetch = require('npm-registry-fetch'); 8 | 9 | export class PackageJsonToMarkdown extends CommandBase { 10 | private config = new Config(); 11 | public static get commandName(): string { return 'packageJsonToMarkdown'; } 12 | 13 | public execute() { 14 | this.checkForOpenWorkspace(); 15 | const fsUtils = new FileSystemUtils(); 16 | const directoryPath: string = fsUtils.getWorkspaceFolder(); 17 | const projectSettings = fsUtils.readProjectSettings(this.config); 18 | const isPackageJson = (filename: string): boolean => filename.toLowerCase().endsWith('package.json'); 19 | const files = fsUtils.listFiles(directoryPath, projectSettings, isPackageJson); 20 | this.writeMarkdownFile(directoryPath, files); 21 | } 22 | 23 | private writeMarkdownFile(workspaceDirectory: string, packageJsonFiles: string[]) { 24 | let devDependencies: string[] = []; 25 | let dependencies: string[] = []; 26 | let peerDependencies: string[] = []; 27 | const localPackages: { [pkgName: string]: string; } = {}; 28 | packageJsonFiles.forEach(packageJsonFile => { 29 | const contents = fs.readFileSync(packageJsonFile).toString('utf8'); 30 | const packageJson = JSON.parse(contents); 31 | if (packageJson.devDependencies) { 32 | devDependencies = [...new Set([...devDependencies, ...Object.keys(packageJson.devDependencies)])]; 33 | this.updateLocalPackagesDictionary(packageJson.devDependencies, localPackages); 34 | } 35 | if (packageJson.dependencies) { 36 | dependencies = [...new Set([...dependencies, ...Object.keys(packageJson.dependencies)])]; 37 | this.updateLocalPackagesDictionary(packageJson.dependencies, localPackages); 38 | } 39 | if (packageJson.peerDependencies) { 40 | peerDependencies = [...new Set([...peerDependencies, ...Object.keys(packageJson.peerDependencies)])]; 41 | this.updateLocalPackagesDictionary(packageJson.peerDependencies, localPackages); 42 | } 43 | }); 44 | 45 | let dependenciesMarkdown = ''; 46 | let devDependenciesMarkdown = ''; 47 | let peerDependenciesMarkdown = ''; 48 | const dependenciesRequests: Promise<{ name: string, version: string, description: string, license: string }>[] = []; 49 | dependencies.sort(ArrayUtils.sortStrings).forEach(pckName => { 50 | dependenciesRequests.push(this.fetchPackageInformation(pckName, workspaceDirectory)); 51 | }); 52 | Promise.all(dependenciesRequests).then(responses => { 53 | dependenciesMarkdown = this.updateMarkdownRow(responses, localPackages); 54 | }).then(() => { 55 | const devDependenciesRequests: Promise<{ name: string, version: string, description: string, license: string }>[] = []; 56 | devDependencies.sort(ArrayUtils.sortStrings).forEach(pckName => { 57 | devDependenciesRequests.push(this.fetchPackageInformation(pckName, workspaceDirectory)); 58 | }); 59 | Promise.all(devDependenciesRequests).then(responses => { 60 | devDependenciesMarkdown = this.updateMarkdownRow(responses, localPackages); 61 | }).then(() => { 62 | const peerDependenciesRequests: Promise<{ name: string, version: string, description: string, license: string }>[] = []; 63 | peerDependencies.sort(ArrayUtils.sortStrings).forEach(pckName => { 64 | peerDependenciesRequests.push(this.fetchPackageInformation(pckName, workspaceDirectory)); 65 | }); 66 | Promise.all(peerDependenciesRequests).then(responses => { 67 | peerDependenciesMarkdown = this.updateMarkdownRow(responses, localPackages); 68 | }).then(() => { 69 | if (dependenciesMarkdown === '') { 70 | dependenciesMarkdown = 'No dependencies specified.'; 71 | } else { 72 | dependenciesMarkdown = 73 | '| Name | Local version | Latest Version | License | Description|\n' + 74 | '| ---- | ---- | ---- | ---- |:-----------|\n' + 75 | dependenciesMarkdown; 76 | } 77 | if (devDependenciesMarkdown === '') { 78 | devDependenciesMarkdown = 'No dev dependencies specified.'; 79 | } else { 80 | devDependenciesMarkdown = 81 | '| Name | Local version | Latest Version | License | Description|\n' + 82 | '| ---- | ---- | ---- | ---- |:-----------|\n' + 83 | devDependenciesMarkdown; 84 | } 85 | if (peerDependenciesMarkdown === '') { 86 | peerDependenciesMarkdown = 'No peer dependencies specified.'; 87 | } else { 88 | peerDependenciesMarkdown = 89 | '| Name | Local version | Latest Version | License | Description|\n' + 90 | '| ---- | ---- | ---- | ---- |:-----------|\n' + 91 | peerDependenciesMarkdown; 92 | } 93 | const markdownContent = 94 | '# Package.json\n\n' + 95 | '## Dependencies\n\n' + 96 | dependenciesMarkdown + '\n' + 97 | '## Dev dependencies\n\n' + 98 | devDependenciesMarkdown + '\n' + 99 | '## Peer dependencies\n\n' + 100 | peerDependenciesMarkdown + '\n'; 101 | const fsUtils = new FileSystemUtils(); 102 | fsUtils.writeFileAndOpen(path.join(workspaceDirectory, this.config.packageJsonMarkdownFilename), markdownContent); 103 | }); 104 | }); 105 | }); 106 | } 107 | 108 | private updateMarkdownRow(responses: { name: string; version: string; description: string; license: string }[], localPackages: { [pkgName: string]: string; }): string { 109 | let markdownStr: string = ''; 110 | responses.sort((first, second) => (first.name.replace('@', '') < second.name.replace('@', '') ? -1 : 1)).forEach(response => { 111 | if (response) { 112 | const localVersion = localPackages[response.name]; 113 | markdownStr += `| ${response.name} | ${localVersion} | ${response.version} | ${response.license} | ${response.description} |\n`; 114 | } 115 | }); 116 | return markdownStr; 117 | } 118 | 119 | private updateLocalPackagesDictionary(dict: { [pkgName: string]: string; }, localPackages: { [pkgName: string]: string; }) { 120 | Object.entries(dict).forEach(([pkgName, version]) => { 121 | if (localPackages[pkgName] !== undefined) { 122 | if (!localPackages[pkgName].includes(String(version))) { 123 | localPackages[pkgName] += ', ' + String(version); 124 | } 125 | } else { 126 | localPackages[pkgName] = String(version); 127 | } 128 | }); 129 | } 130 | 131 | private fetchPackageInformation(pckName: string, workspaceDirectory: string): Promise<{ name: string, version: string, description: string, license: string }> { 132 | const uri = `/${pckName}`; 133 | const license = this.getLicenseInformationFromNodeModulesFolder(workspaceDirectory, pckName); 134 | const result = fetch.json(uri) 135 | .then((json: any) => { 136 | let packageName = json.name; 137 | packageName = packageName?.replace('|', '|'); 138 | let packageDescription = json.description; 139 | packageDescription = packageDescription?.replace('|', '|'); 140 | let packageVersion = json['dist-tags'].latest; 141 | packageVersion = packageVersion?.replace('|', '|'); 142 | return { name: packageName, description: packageDescription, version: packageVersion, license: license }; 143 | }) 144 | .catch(() => { 145 | return { name: pckName, description: 'N/A', version: 'N/A', license: license }; 146 | }); 147 | return result; 148 | } 149 | 150 | private getLicenseInformationFromNodeModulesFolder(workspaceDirectory: string, pckName: string): string { 151 | const pckFolder = path.join(workspaceDirectory, 'node_modules', pckName); 152 | const packageJsonFile = path.join(pckFolder, 'package.json'); 153 | let packageJson = undefined; 154 | try { 155 | const contents = fs.readFileSync(packageJsonFile).toString('utf8'); 156 | packageJson = JSON.parse(contents); 157 | } 158 | catch { 159 | packageJson = undefined; 160 | } 161 | if (packageJson !== undefined && packageJson.license) { 162 | return packageJson.license; 163 | } else { 164 | return 'N/A'; 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /src/commands/projectDirectoryStructure.ts: -------------------------------------------------------------------------------- 1 | import { Config, FileSystemUtils } from '@src'; 2 | import { CommandBase } from '@commands'; 3 | import * as vscode from 'vscode'; 4 | import { Settings } from '@model'; 5 | 6 | export class ProjectDirectoryStructure extends CommandBase { 7 | private config = new Config(); 8 | public static get commandName(): string { return 'projectDirectoryStructure'; } 9 | 10 | public execute() { 11 | this.checkForOpenWorkspace(); 12 | const fsUtils = new FileSystemUtils(); 13 | var workspaceFolder: string = fsUtils.getWorkspaceFolder(); 14 | const settings: Settings = fsUtils.readProjectSettings(this.config); 15 | const directories: string[] = fsUtils.listDirectories(workspaceFolder, settings); 16 | this.writeDirectoryStructure(workspaceFolder, this.config.projectDirectoryStructureMarkdownFilename, directories); 17 | } 18 | 19 | private writeDirectoryStructure(workspaceFolder: string, filename: string, directories: string[]) { 20 | const angularToolsOutput = vscode.window.createOutputChannel(this.config.angularToolsOutputChannel); 21 | angularToolsOutput.clear(); 22 | angularToolsOutput.appendLine('Project Directory Structure'); 23 | angularToolsOutput.appendLine(`Workspace directory: ${workspaceFolder}\n`); 24 | angularToolsOutput.appendLine('Directories:'); 25 | directories?.forEach(directoryFullPath => { 26 | var directoryName = directoryFullPath.replace(workspaceFolder, '.'); 27 | angularToolsOutput.appendLine(directoryName); 28 | }); 29 | angularToolsOutput.show(); 30 | } 31 | } -------------------------------------------------------------------------------- /src/commands/showComponentHierarchy.ts: -------------------------------------------------------------------------------- 1 | import { ShowHierarchyBase } from './showHierarchyBase'; 2 | import { ComponentManager, FileSystemUtils } from '@src'; 3 | import { ArrowType, Component, Edge, GraphState, Node, NodeType, Settings } from '@model'; 4 | import * as fs from 'fs'; 5 | import * as path from 'path'; 6 | import * as vscode from 'vscode'; 7 | 8 | export class ShowComponentHierarchy extends ShowHierarchyBase { 9 | public static get commandName(): string { return 'showComponentHierarchy'; } 10 | 11 | private workspaceFolder: string = this.fsUtils.getWorkspaceFolder(); 12 | 13 | public execute(webview: vscode.Webview) { 14 | this.checkForOpenWorkspace(); 15 | webview.onDidReceiveMessage( 16 | message => { 17 | switch (message.command) { 18 | case 'saveAsPng': { 19 | this.saveAsPng(this.config.componentHierarchyPngFilename, message.text); 20 | return; 21 | } 22 | case 'saveAsDgml': { 23 | this.saveAsDgml(this.config.componentHierarchyDgmlGraphFilename, message.text, `'The component hierarchy has been analyzed and a Directed Graph Markup Language (dgml) file '${this.config.componentHierarchyDgmlGraphFilename}' has been created'`); 24 | return; 25 | } 26 | case 'saveAsDot': { 27 | this.saveAsDot(this.config.componentHierarchyDotGraphFilename, message.text, 'componentHierarchy', `'The component hierarchy has been analyzed and a GraphViz (dot) file '${this.config.componentHierarchyDotGraphFilename}' has been created'`); 28 | return; 29 | } 30 | case 'setGraphState': { 31 | const newGraphState: GraphState = JSON.parse(message.text); 32 | this.graphState = newGraphState; 33 | this.setNewState(this.graphState); 34 | this.nodes.forEach(node => { 35 | node.position = this.graphState.nodePositions[node.id]; 36 | }); 37 | this.addNodesAndEdges(components, this.appendNodes, this.appendEdges); 38 | this.generateAndSaveJavascriptContent(() => { }); 39 | return; 40 | } 41 | case 'openFile': { 42 | const filename = message.text; 43 | if (this.fsUtils.fileExists(filename)) { 44 | var openPath = vscode.Uri.parse("file:///" + filename); 45 | vscode.workspace.openTextDocument(openPath).then(doc => { 46 | vscode.window.showTextDocument(doc); 47 | }); 48 | } 49 | return; 50 | } 51 | } 52 | }, 53 | undefined, 54 | this.extensionContext.subscriptions 55 | ); 56 | 57 | const fsUtils = new FileSystemUtils(); 58 | const settings: Settings = fsUtils.readProjectSettings(this.config); 59 | const components = ComponentManager.scanWorkspaceForComponents(this.workspaceFolder, settings); 60 | 61 | this.nodes = []; 62 | this.edges = []; 63 | this.addNodesAndEdges(components, this.appendNodes, this.appendEdges); 64 | const htmlContent = this.generateHtmlContent(webview, this.showComponentHierarchyJsFilename); 65 | //this.fsUtils.writeFile(this.extensionContext?.asAbsolutePath(path.join('out', ShowComponentHierarchy.Name + '.html')), htmlContent, () => { }); // For debugging 66 | this.generateAndSaveJavascriptContent(() => { webview.html = htmlContent; }); 67 | } 68 | 69 | private generateAndSaveJavascriptContent(callback: () => any) { 70 | const nodesJson = this.nodes 71 | .map(node => { return node.toJsonString(); }) 72 | .join(',\n'); 73 | const rootNodesJson = this.nodes 74 | .filter(node => node.isRoot) 75 | .map(node => { return '"' + node.id + '"'; }) 76 | .join(',\n'); 77 | const edgesJson = this.edges 78 | .map(edge => { return edge.toJsonString(); }) 79 | .join(',\n'); 80 | 81 | try { 82 | const jsContent = this.generateJavascriptContent(nodesJson, rootNodesJson, edgesJson); 83 | this.fsUtils.writeFile( 84 | this.extensionContext?.asAbsolutePath(path.join('.', this.showComponentHierarchyJsFilename)), 85 | jsContent, 86 | callback 87 | ); 88 | } catch (ex) { 89 | console.log('Angular Tools Exception:' + ex); 90 | } 91 | } 92 | 93 | private addNodesAndEdges(componentDict: { [selector: string]: Component; }, appendNodes: (nodeList: Node[]) => void, appendEdges: (edgeList: Edge[]) => void) { 94 | for (let selector in componentDict) { 95 | const component = componentDict[selector]; 96 | if (component.isRoot) { 97 | this.generateDirectedGraphNodes(component.subComponents, component, true, '', appendNodes); 98 | this.generateDirectedGraphEdges(componentDict, component.subComponents, component, "", appendEdges); 99 | } 100 | } 101 | } 102 | 103 | private generateDirectedGraphNodes(components: Component[], component: Component, isRoot: boolean, parentSelector: string, appendNodes: (nodeList: Node[]) => void) { 104 | let componentFilename = component.filename.replace(this.workspaceFolder, '.'); 105 | componentFilename = componentFilename.split('\\').join('/'); 106 | const componentPosition = this.graphState.nodePositions[component.selector]; 107 | appendNodes([new Node(component.selector, component.selector, componentFilename, component.filename, isRoot, isRoot ? NodeType.rootNode : NodeType.component, componentPosition)]); 108 | if (components.length > 0) { 109 | components.forEach((subComponent) => { 110 | if (parentSelector !== subComponent.selector) { 111 | this.generateDirectedGraphNodes(subComponent.subComponents, subComponent, subComponent.isRoot, component.selector, appendNodes); 112 | } 113 | }); 114 | } 115 | } 116 | 117 | private generateDirectedGraphEdges(componentDict: { [selector: string]: Component; }, subComponents: Component[], currentComponent: Component, parentSelector: string, appendEdges: (edgeList: Edge[]) => void) { 118 | if (parentSelector.length > 0) { 119 | const id = this.edges.length; 120 | appendEdges([new Edge(id.toString(), parentSelector, currentComponent.selector, ArrowType.uses)]); 121 | } 122 | if (currentComponent.componentsRoutingToThis !== undefined && currentComponent.componentsRoutingToThis.length > 0) { 123 | currentComponent.componentsRoutingToThis.forEach(componentRoutingToThis => { 124 | const id = this.edges.length; 125 | appendEdges([new Edge(id.toString(), componentRoutingToThis.selector, currentComponent.selector, ArrowType.route)]); 126 | }); 127 | } 128 | if (subComponents.length > 0 && currentComponent.selector !== parentSelector) { 129 | subComponents.forEach((subComponent) => { 130 | this.generateDirectedGraphEdges(componentDict, subComponent.subComponents, subComponent, currentComponent.selector, appendEdges); 131 | }); 132 | } 133 | } 134 | 135 | private generateJavascriptContent(nodesJson: string, rootNodesJson: string, edgesJson: string): string { 136 | let template = fs.readFileSync(this.extensionContext?.asAbsolutePath(path.join('templates', this.templateJsFilename)), 'utf8'); 137 | let jsContent = template.replace('const nodes = new vis.DataSet([]);', `var nodes = new vis.DataSet([${nodesJson}]);`); 138 | jsContent = jsContent.replace('const rootNodes = [];', `var rootNodes = [${rootNodesJson}];`); 139 | jsContent = jsContent.replace('const edges = new vis.DataSet([]);', `var edges = new vis.DataSet([${edgesJson}]);`); 140 | jsContent = jsContent.replace('background: "#00FF00" // rootNode background color', `background: "${this.config.rootNodeBackgroundColor}" // rootNode background color`); 141 | jsContent = jsContent.replace('shape: \'box\' // The shape of the nodes.', `shape: '${this.config.rootNodeShape}'// The shape of the nodes.`); 142 | jsContent = jsContent.replace('type: "triangle" // edge arrow to type', `type: "${this.config.componentHierarchyEdgeArrowToType}" // edge arrow to type}`); 143 | jsContent = jsContent.replace('ctx.strokeStyle = \'blue\'; // graph selection guideline color', `ctx.strokeStyle = '${this.config.graphSelectionGuidelineColor}'; // graph selection guideline color`); 144 | jsContent = jsContent.replace('ctx.lineWidth = 1; // graph selection guideline width', `ctx.lineWidth = ${this.config.graphSelectionGuidelineWidth}; // graph selection guideline width`); 145 | jsContent = jsContent.replace('selectionCanvasContext.strokeStyle = \'red\';', `selectionCanvasContext.strokeStyle = '${this.config.graphSelectionColor}';`); 146 | jsContent = jsContent.replace('selectionCanvasContext.lineWidth = 2;', `selectionCanvasContext.lineWidth = ${this.config.graphSelectionWidth};`); 147 | jsContent = jsContent.replace('let showHierarchicalOptionsCheckboxChecked = false;', `let showHierarchicalOptionsCheckboxChecked = ${this.graphState.showHierarchicalOptions};`); 148 | jsContent = jsContent.replace('let hierarchicalOptionsDirectionSelectValue = undefined;', `let hierarchicalOptionsDirectionSelectValue = '${this.graphState.graphDirection}';`); 149 | jsContent = jsContent.replace('let hierarchicalOptionsSortMethodSelectValue = undefined;', `let hierarchicalOptionsSortMethodSelectValue = '${this.graphState.graphLayout}';`); 150 | jsContent = this.setGraphState(jsContent); 151 | return jsContent; 152 | } 153 | } -------------------------------------------------------------------------------- /src/commands/showHierarchyBase.ts: -------------------------------------------------------------------------------- 1 | import { CommandBase } from '@commands'; 2 | import { Config, DgmlManager, FileSystemUtils, GraphVizManager } from '@src'; 3 | import { Edge, GraphState, Node } from '@model'; 4 | import { Base64 } from 'js-base64'; 5 | import * as fs from 'fs'; 6 | import * as path from 'path'; 7 | import * as vscode from 'vscode'; 8 | import * as xmldom from '@xmldom/xmldom'; 9 | 10 | const prettifyXml = require('prettify-xml'); 11 | const xmlSerializer = require('xmlserializer'); 12 | 13 | export class ShowHierarchyBase extends CommandBase { 14 | protected fsUtils: FileSystemUtils = new FileSystemUtils(); 15 | protected config = new Config(); 16 | protected extensionContext: vscode.ExtensionContext; 17 | protected graphState: GraphState; 18 | protected setNewState: (newGraphState: GraphState) => any; 19 | protected nodes: Node[] = []; 20 | protected edges: Edge[] = []; 21 | protected templateJsFilename: string = 'showHierarchy_Template.js'; 22 | protected templateHtmlFilename: string = 'showHierarchy_Template.html'; 23 | protected showComponentHierarchyJsFilename: string = 'showComponentHierarchy.js'; 24 | protected showModuleHierarchyJsFilename: string = 'showModuleHierarchy.js'; 25 | protected showHierarchyCssFilename: string = 'showHierarchy.css'; 26 | protected fontAwesomeCssFilename: string = 'all.min.css'; 27 | protected fontAwesomeFontFilename: string = '../webfonts/fa-'; 28 | 29 | protected workspaceDirectory = this.fsUtils.getWorkspaceFolder(); 30 | 31 | constructor(context: vscode.ExtensionContext, graphState: GraphState, setNewState: (newGraphState: GraphState) => any) { 32 | super(); 33 | this.extensionContext = context; 34 | this.setNewState = setNewState; 35 | this.graphState = graphState; 36 | } 37 | protected appendNodes = (nodeList: Node[]) => { 38 | nodeList.forEach(newNode => { 39 | newNode.showPopupsOverNodesAndEdges = this.config.showPopupsOverNodesAndEdges; 40 | if (!this.nodes.some(node => node.id === newNode.id)) { 41 | this.nodes.push(newNode); 42 | } 43 | else { 44 | const existingNode = this.nodes.find(node => node.id === newNode.id); 45 | if (existingNode && (!existingNode.tsFilename || existingNode.tsFilename?.length === 0) && newNode.tsFilename && newNode.tsFilename.length > 0) { 46 | existingNode.tsFilename = newNode.tsFilename; 47 | existingNode.filename = newNode.filename; 48 | } 49 | } 50 | }); 51 | }; 52 | protected appendEdges = (edgeList: Edge[]) => { 53 | edgeList.forEach(newEdge => { 54 | newEdge.showPopupsOverNodesAndEdges = this.config.showPopupsOverNodesAndEdges; 55 | const mutualEdges = this.edges.filter(edge => edge.target === newEdge.source && edge. source=== newEdge.target); 56 | if (mutualEdges.length > 0) { 57 | newEdge.mutualEdgeCount += 1; 58 | mutualEdges.forEach(e => e.mutualEdgeCount += 1 ); 59 | } 60 | if (!this.edges.some(edge => edge.source === newEdge.source && edge.target === newEdge.target)) { 61 | this.edges.push(newEdge); 62 | } 63 | }); 64 | }; 65 | 66 | protected getNonce() { 67 | let text = ''; 68 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 69 | for (let i = 0; i < 32; i++) { 70 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 71 | } 72 | return text; 73 | } 74 | 75 | protected saveAsPng(pngFilename: string, messageText: string) { 76 | const dataUrl = messageText.split(','); 77 | if (dataUrl.length > 0) { 78 | const u8arr = Base64.toUint8Array(dataUrl[1]); 79 | 80 | const newFilePath = path.join(this.workspaceDirectory, pngFilename); 81 | this.fsUtils.writeFile(newFilePath, u8arr, () => { }); 82 | 83 | vscode.window.setStatusBarMessage(`The file ${pngFilename} has been created in the root of the workspace.`); 84 | } 85 | } 86 | 87 | protected saveAsDgml(dgmlGraphFilename: string, messageText: string, popMessageText: string) { 88 | const message = JSON.parse(messageText); 89 | const direction = message.direction; 90 | const domImpl = new xmldom.DOMImplementation(); 91 | const dgmlManager = new DgmlManager(); 92 | const xmlDocument = dgmlManager.createNewDirectedGraph(domImpl, this.fixGraphDirection(direction), "Sugiyama", "-1"); 93 | dgmlManager.addNodesAndLinks(xmlDocument, this.nodes, message.nodes, this.edges); 94 | // Serialize the xml into a string 95 | const xmlAsString = xmlSerializer.serializeToString(xmlDocument.documentElement); 96 | let fileContent = prettifyXml(xmlAsString); 97 | const xmlProlog = '\n'; 98 | fileContent = xmlProlog + fileContent.replace('HasCategory('RootComponent')', "HasCategory('RootComponent')"); 99 | 100 | // Write the prettified xml string to the ReadMe-ProjectStructure.dgml file. 101 | var directoryPath: string = this.fsUtils.getWorkspaceFolder(); 102 | this.fsUtils.writeFile(path.join(directoryPath, dgmlGraphFilename), fileContent, () => { 103 | vscode.window.setStatusBarMessage(popMessageText, 10000); 104 | }); 105 | } 106 | 107 | private fixGraphDirection(direction: string): string { 108 | let fixedDirection: string; 109 | switch (direction) { 110 | case 'UD': 111 | fixedDirection = 'TopToBottom'; 112 | break; 113 | case 'DU': 114 | fixedDirection = 'BottomToTop'; 115 | break; 116 | case 'LR': 117 | fixedDirection = 'LeftToRight'; 118 | break; 119 | case 'RL': 120 | fixedDirection = 'RightToLeft'; 121 | break; 122 | default: 123 | fixedDirection = ''; 124 | break; 125 | } 126 | return fixedDirection; 127 | } 128 | 129 | protected saveAsDot(graphVizFilename: string, messageText: string, graphType: string, popMessageText: string) { 130 | const message = JSON.parse(messageText); 131 | const graphVizManager = new GraphVizManager(); 132 | const fileContent = graphVizManager.createGraphVizDiagram(graphType, this.nodes, message.nodes, this.edges); 133 | 134 | // Write the prettified xml string to the ReadMe-ProjectStructure.dgml file. 135 | var directoryPath: string = this.fsUtils.getWorkspaceFolder(); 136 | this.fsUtils.writeFile(path.join(directoryPath, graphVizFilename), fileContent, () => { 137 | vscode.window.setStatusBarMessage(popMessageText, 10000); 138 | }); 139 | } 140 | 141 | protected setGraphState(jsContent: string): string { 142 | if (this.graphState.networkSeed !== undefined) { 143 | jsContent = jsContent.replace('var seed = network.getSeed();', `var seed = '${this.graphState.networkSeed}';`); 144 | } 145 | return jsContent; 146 | } 147 | 148 | protected generateHtmlContent(webview: vscode.Webview, outputJsFilename: string): string { 149 | let htmlContent = fs.readFileSync(this.extensionContext?.asAbsolutePath(path.join('templates', this.templateHtmlFilename)), 'utf8'); 150 | 151 | const visPath = vscode.Uri.joinPath(this.extensionContext.extensionUri, 'javascript', 'vis-network.min.js'); 152 | const visUri = webview.asWebviewUri(visPath); 153 | htmlContent = htmlContent.replace('vis-network.min.js', visUri.toString()); 154 | 155 | let cssPath = vscode.Uri.joinPath(this.extensionContext.extensionUri, 'stylesheet', this.showHierarchyCssFilename); 156 | let cssUri = webview.asWebviewUri(cssPath); 157 | htmlContent = htmlContent.replace(this.showHierarchyCssFilename, cssUri.toString()); 158 | 159 | const vscodyfiedFontAwesomeCssFilename = this.fixFontAwesomeFontUri(webview); 160 | cssPath = vscode.Uri.joinPath(this.extensionContext.extensionUri, 'stylesheet', vscodyfiedFontAwesomeCssFilename); 161 | cssUri = webview.asWebviewUri(cssPath); 162 | htmlContent = htmlContent.replace(this.fontAwesomeCssFilename, cssUri.toString()); 163 | 164 | const visJsMinCss = 'vis-network.min.css'; 165 | const visCssPath = vscode.Uri.joinPath(this.extensionContext.extensionUri, 'stylesheet', visJsMinCss); 166 | const visCssUri = webview.asWebviewUri(visCssPath); 167 | htmlContent = htmlContent.replace(visJsMinCss, visCssUri.toString()); 168 | 169 | const nonce = this.getNonce(); 170 | htmlContent = htmlContent.replace('nonce-nonce', `nonce-${nonce}`); 171 | htmlContent = htmlContent.replace(/ 13 | 14 | 15 | 16 | 17 |
18 | 26 | 27 | 28 |
Change layout:
29 |
30 | Direction: 37 |
38 |
39 | Sort method: 43 |
44 |
Click and drag to select the area to be saved.
45 |
46 |
47 |
48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /templates/showHierarchy_Template.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const nodes = new vis.DataSet([]); 3 | 4 | const rootNodes = []; 5 | rootNodes.forEach(nodeId => { 6 | nodes.get(nodeId).color = { 7 | background: "#00FF00" // rootNode background color 8 | }; 9 | }); 10 | 11 | const arrowAttr = { 12 | to: { 13 | enabled: true, 14 | type: "triangle" // edge arrow to type 15 | } 16 | }; 17 | const edges = new vis.DataSet([]); 18 | 19 | const data = { 20 | nodes: nodes, 21 | edges: edges 22 | }; 23 | const options = { 24 | interaction: { 25 | hover: true 26 | }, 27 | nodes: { 28 | font: { multi: 'html' }, 29 | shape: 'box' // The shape of the nodes. 30 | } 31 | }; 32 | setRandomLayout(); 33 | const container = document.getElementById('network'); 34 | let network = new vis.Network(container, data, options); 35 | let seed = network.getSeed(); 36 | 37 | network.on("stabilizationIterationsDone", function () { 38 | network.setOptions({ 39 | physics: false 40 | }); 41 | unfixNodes(); 42 | postGraphState(); 43 | network.on("selectNode", function (params) { 44 | if (params.nodes.length === 1) { 45 | var node = nodes.get(params.nodes[0]); 46 | openFileInVsCode(node.filepath); 47 | } 48 | }); 49 | network.on("hoverNode", function (params) { 50 | var node = nodes.get(params.node); 51 | if (node.filepath && node.filepath.length > 0) { 52 | network.canvas.body.container.style.cursor = 'pointer'; 53 | } 54 | }); 55 | network.on("blurNode", function (params) { 56 | network.canvas.body.container.style.cursor = 'default'; 57 | }); 58 | }); 59 | network.on('dragEnd', postGraphState); 60 | 61 | const vscode = acquireVsCodeApi(); 62 | const helpTextDiv = document.getElementById('helpText'); 63 | let lastMouseX = lastMouseY = 0; 64 | let mouseX = mouseY = 0; 65 | let selection; 66 | 67 | // get the vis.js canvas 68 | const graphDiv = document.getElementById('network'); 69 | const visDiv = graphDiv.firstElementChild; 70 | const graphCanvas = visDiv.firstElementChild; 71 | const selectionLayer = document.getElementById('selectionLayer'); 72 | const selectionCanvas = selectionLayer.firstElementChild; 73 | let selectionCanvasContext; 74 | 75 | const hierarchicalOptionsDirectionSelect = document.getElementById('direction'); 76 | 77 | // add button event listeners 78 | const saveAsPngButton = document.getElementById('saveAsPngButton'); 79 | saveAsPngButton.addEventListener('click', saveAsPng); 80 | const saveAsDgmlButton = document.getElementById('saveAsDgmlButton'); 81 | saveAsDgmlButton.addEventListener('click', saveAsDgml); 82 | const saveAsDotButton = document.getElementById('saveAsDotButton'); 83 | saveAsDotButton.addEventListener('click', saveAsDot); 84 | const regenerateGraphButton = document.getElementById('regenerateGraphButton'); 85 | regenerateGraphButton.addEventListener('click', regenerateGraph); 86 | const saveSelectionAsPngButton = document.getElementById('saveSelectionAsPngButton'); 87 | saveSelectionAsPngButton.addEventListener('click', saveSelectionAsPng); 88 | const showHierarchicalOptionsCheckbox = document.getElementById('showHierarchicalOptions'); 89 | showHierarchicalOptionsCheckbox.addEventListener('click', showHierarchicalOptions); 90 | const hierarchicalDirectionSelect = document.getElementById('direction'); 91 | hierarchicalDirectionSelect.addEventListener('change', setNetworkLayout); 92 | const hierarchicalSortMethodSelect = document.getElementById('sortMethod'); 93 | hierarchicalSortMethodSelect.addEventListener('change', setNetworkLayout); 94 | const hierarchicalOptionsSortMethodSelect = document.getElementById('sortMethod'); 95 | const hierarchicalOptionsDirection = document.getElementById('hierarchicalOptions_direction'); 96 | const hierarchicalOptionsSortMethod = document.getElementById('hierarchicalOptions_sortmethod'); 97 | 98 | let hierarchicalOptionsDirectionSelectValue = undefined; 99 | let hierarchicalOptionsSortMethodSelectValue = undefined; 100 | let showHierarchicalOptionsCheckboxChecked = false; 101 | showHierarchicalOptionsCheckbox.checked = showHierarchicalOptionsCheckboxChecked; 102 | showHierarchicalOptions(); 103 | 104 | function mouseUpEventListener(event) { 105 | // Convert the canvas to image data that can be saved 106 | const aspectRatioX = graphCanvas.width / selectionCanvas.width; 107 | const aspectRatioY = graphCanvas.height / selectionCanvas.height; 108 | const finalSelectionCanvas = document.createElement('canvas'); 109 | finalSelectionCanvas.width = selection.width; 110 | finalSelectionCanvas.height = selection.height; 111 | const finalSelectionCanvasContext = finalSelectionCanvas.getContext('2d'); 112 | finalSelectionCanvasContext.drawImage(graphCanvas, selection.top * aspectRatioX, selection.left * aspectRatioY, selection.width * aspectRatioX, selection.height * aspectRatioY, 0, 0, selection.width, selection.height); 113 | 114 | // Call back to the extension context to save the selected image to the workspace folder. 115 | vscode.postMessage({ 116 | command: 'saveAsPng', 117 | text: finalSelectionCanvas.toDataURL() 118 | }); 119 | // Remove the temporary canvas 120 | finalSelectionCanvas.remove(); 121 | // Reset the state variables 122 | selectionCanvasContext = undefined; 123 | selection = {}; 124 | // hide the help text 125 | helpTextDiv.style['display'] = 'none'; 126 | // hide selection layer and remove event listeners 127 | selectionLayer.removeEventListener('mouseup', mouseUpEventListener); 128 | selectionLayer.removeEventListener('mousedown', mouseDownEventListener); 129 | selectionLayer.removeEventListener('mousemove', mouseMoveEventListener); 130 | selectionLayer.style['display'] = 'none'; 131 | } 132 | 133 | function mouseDownEventListener(event) { 134 | lastMouseX = parseInt(event.clientX - selectionCanvas.offsetLeft); 135 | lastMouseY = parseInt(event.clientY - selectionCanvas.offsetTop); 136 | selectionCanvasContext = selectionCanvas.getContext("2d"); 137 | } 138 | 139 | function drawGuideLine(ctx, mouseX, mouseY) { 140 | ctx.beginPath(); 141 | ctx.setLineDash([3, 7]); 142 | if (mouseX > -1) { 143 | ctx.moveTo(mouseX, 0); 144 | ctx.lineTo(mouseX, selectionCanvas.height); 145 | } else if (mouseY > -1) { 146 | ctx.moveTo(0, mouseY); 147 | ctx.lineTo(selectionCanvas.width, mouseY); 148 | } 149 | ctx.strokeStyle = 'blue'; // graph selection guideline color 150 | ctx.lineWidth = 1; // graph selection guideline width 151 | ctx.stroke(); 152 | } 153 | 154 | function showGuideLines() { 155 | const tmpSelectionCanvasContext = selectionCanvas.getContext("2d"); 156 | tmpSelectionCanvasContext.clearRect(0, 0, selectionCanvas.width, selectionCanvas.height); 157 | drawGuideLine(tmpSelectionCanvasContext, mouseX, -1); 158 | drawGuideLine(tmpSelectionCanvasContext, -1, mouseY); 159 | } 160 | 161 | function mouseMoveEventListener(event) { 162 | mouseX = parseInt(event.clientX - selectionCanvas.offsetLeft); 163 | mouseY = parseInt(event.clientY - selectionCanvas.offsetTop); 164 | showGuideLines(); 165 | if (selectionCanvasContext != undefined) { 166 | selectionCanvasContext.beginPath(); 167 | selectionCanvasContext.setLineDash([]); 168 | const width = mouseX - lastMouseX; 169 | const height = mouseY - lastMouseY; 170 | selectionCanvasContext.rect(lastMouseX, lastMouseY, width, height); 171 | selection = { // Save the current position and size to be used when the mouseup event is fired 172 | 'top': lastMouseX, 173 | 'left': lastMouseY, 174 | 'height': height, 175 | 'width': width 176 | }; 177 | selectionCanvasContext.strokeStyle = 'red'; 178 | selectionCanvasContext.lineWidth = 2; 179 | selectionCanvasContext.stroke(); 180 | } 181 | } 182 | 183 | function saveSelectionAsPng() { 184 | // show the help text 185 | helpTextDiv.style['display'] = 'block'; 186 | 187 | // show the selection layer 188 | selectionLayer.style['display'] = 'block'; 189 | 190 | // make sure the selection canvas covers the whole screen 191 | selectionCanvas.width = window.innerWidth; 192 | selectionCanvas.height = window.innerHeight; 193 | // reset the current context and selection 194 | selectionCanvasContext = undefined; 195 | selection = {}; 196 | 197 | selectionLayer.addEventListener("mouseup", mouseUpEventListener, true); 198 | selectionLayer.addEventListener("mousedown", mouseDownEventListener, true); 199 | selectionLayer.addEventListener("mousemove", mouseMoveEventListener, true); 200 | } 201 | 202 | function saveAsPng() { 203 | // Calculate the bounding box of all the elements on the canvas 204 | const boundingBox = getBoundingBox(); 205 | 206 | // copy the imagedata within the bounding box 207 | const finalSelectionCanvas = document.createElement('canvas'); 208 | finalSelectionCanvas.width = boundingBox.width; 209 | finalSelectionCanvas.height = boundingBox.height; 210 | const finalSelectionCanvasContext = finalSelectionCanvas.getContext('2d'); 211 | finalSelectionCanvasContext.drawImage(graphCanvas, boundingBox.top, boundingBox.left, boundingBox.width, boundingBox.height, 0, 0, boundingBox.width, boundingBox.height); 212 | 213 | // Call back to the extension context to save the image of the graph to the workspace folder. 214 | vscode.postMessage({ 215 | command: 'saveAsPng', 216 | text: finalSelectionCanvas.toDataURL() 217 | }); 218 | 219 | // Remove the temporary canvas 220 | finalSelectionCanvas.remove(); 221 | } 222 | 223 | function getBoundingBox() { 224 | const ctx = graphCanvas.getContext('2d'); 225 | const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); 226 | const bytesPerPixels = 4; 227 | const cWidth = graphCanvas.width * bytesPerPixels; 228 | const cHeight = graphCanvas.height; 229 | let minY = minX = maxY = maxX = -1; 230 | for (let y = cHeight; y > 0 && maxY === -1; y--) { 231 | for (let x = 0; x < cWidth; x += bytesPerPixels) { 232 | const arrayPos = x + y * cWidth; 233 | if (imgData.data[arrayPos + 3] > 0 && maxY === -1) { 234 | maxY = y; 235 | break; 236 | } 237 | } 238 | } 239 | for (let x = cWidth; x >= 0 && maxX === -1; x -= bytesPerPixels) { 240 | for (let y = 0; y < maxY; y++) { 241 | const arrayPos = x + y * cWidth; 242 | if (imgData.data[arrayPos + 3] > 0 && maxX === -1) { 243 | maxX = x / bytesPerPixels; 244 | break; 245 | } 246 | } 247 | } 248 | for (let x = 0; x < maxX * bytesPerPixels && minX === -1; x += bytesPerPixels) { 249 | for (let y = 0; y < maxY; y++) { 250 | const arrayPos = x + y * cWidth; 251 | if (imgData.data[arrayPos + 3] > 0 && minX === -1) { 252 | minX = x / bytesPerPixels; 253 | break; 254 | } 255 | } 256 | } 257 | for (let y = 0; y < maxY && minY === -1; y++) { 258 | for (let x = minX * bytesPerPixels; x < maxX * bytesPerPixels; x += bytesPerPixels) { 259 | const arrayPos = x + y * cWidth; 260 | if (imgData.data[arrayPos + 3] > 0 && minY === -1) { 261 | minY = y; 262 | break; 263 | } 264 | } 265 | } 266 | return { 267 | 'top': minX, 268 | 'left': minY, 269 | 'width': maxX - minX, 270 | 'height': maxY - minY 271 | }; 272 | } 273 | 274 | function saveAsDgml() { 275 | postSaveAsCommand('saveAsDgml'); 276 | } 277 | 278 | function saveAsDot() { 279 | postSaveAsCommand('saveAsDot'); 280 | } 281 | 282 | function postSaveAsCommand(command) { 283 | const nodeExport = {}; 284 | nodes.forEach(node => { 285 | nodeExport[node.id] = { 286 | id: node.id, 287 | label: command === 'saveAsDgml' ? cleanLabelDgml(node.label) : cleanLabelDot(node.label), 288 | position: network.getPosition(node.id), 289 | boundingBox: network.getBoundingBox(node.id) 290 | }; 291 | }); 292 | const direction = hierarchicalOptionsDirectionSelect.value ? hierarchicalOptionsDirectionSelect.value : 'UD'; 293 | vscode.postMessage({ 294 | command: command, 295 | text: JSON.stringify({ 296 | nodes: nodeExport, 297 | direction: direction 298 | }) 299 | }); 300 | } 301 | 302 | function cleanLabelDgml(label) { 303 | let cleanedLabel = removeHtmlTags(label); 304 | cleanedLabel = removeNewlines(cleanedLabel); 305 | return cleanedLabel; 306 | } 307 | 308 | function cleanLabelDot(label) { 309 | let cleanedLabel = convertNewlinesToDotNewlines(label); 310 | return cleanedLabel; 311 | } 312 | 313 | function removeHtmlTags(label) { 314 | let cleanedLabel = label.replace(/(<([^>]+)>)/ig, ''); 315 | return cleanedLabel; 316 | } 317 | 318 | function removeNewlines(label) { 319 | let cleanedLabel = label.replace(/\s+/g, ''); 320 | return cleanedLabel; 321 | } 322 | 323 | function convertNewlinesToDotNewlines(label) { 324 | let cleanedLabel = label.replace(/\n/g, '
'); 325 | return cleanedLabel; 326 | } 327 | 328 | function openFileInVsCode(filepath) { 329 | vscode.postMessage({ 330 | command: 'openFile', 331 | text: filepath 332 | }); 333 | } 334 | 335 | function regenerateGraph() { 336 | seed = Math.random(); 337 | hierarchicalOptionsDirectionSelect.value = 'Random'; 338 | hierarchicalOptionsDirectionSelectValue = hierarchicalOptionsDirectionSelect.value; 339 | hierarchicalOptionsSortMethodSelect.value = 'hubsize'; 340 | hierarchicalOptionsSortMethodSelectValue = hierarchicalOptionsSortMethodSelect.value; 341 | removeNodePositions(); 342 | setNetworkLayout(); 343 | } 344 | 345 | function setRandomLayout() { 346 | options.layout = { 347 | hierarchical: { 348 | enabled: false 349 | } 350 | }; 351 | options.physics = { 352 | enabled: true, 353 | barnesHut: { 354 | springConstant: 0, 355 | avoidOverlap: 0.8 356 | } 357 | }; 358 | } 359 | 360 | function setHierarchicalLayout(direction, sortMethod) { 361 | options.layout = { 362 | hierarchical: { 363 | enabled: true, 364 | levelSeparation: 200, 365 | nodeSpacing: 200, 366 | direction: direction, 367 | sortMethod: sortMethod 368 | } 369 | }; 370 | options.physics = { 371 | enabled: true, 372 | hierarchicalRepulsion: { 373 | springConstant: 0, 374 | avoidOverlap: 0.2 375 | } 376 | }; 377 | } 378 | 379 | function showHierarchicalOptions(){ 380 | showHierarchicalOptionsCheckboxChecked = showHierarchicalOptionsCheckbox.checked; 381 | if (showHierarchicalOptionsCheckbox.checked) { 382 | hierarchicalOptionsDirection.style['display'] = 'block'; 383 | hierarchicalOptionsDirectionSelect.value = hierarchicalOptionsDirectionSelectValue ? hierarchicalOptionsDirectionSelectValue : 'Random'; 384 | hierarchicalOptionsSortMethod.style['display'] = 'block'; 385 | hierarchicalOptionsSortMethodSelect.value = hierarchicalOptionsSortMethodSelectValue ? hierarchicalOptionsSortMethodSelectValue : 'hubsize'; 386 | if (hierarchicalOptionsDirectionSelect.value && hierarchicalOptionsDirectionSelect.value === 'Random') { 387 | regenerateGraphButton.style['display'] = 'block'; 388 | } else { 389 | regenerateGraphButton.style['display'] = 'none'; 390 | } 391 | } else { 392 | hierarchicalOptionsDirection.style['display'] = 'none'; 393 | hierarchicalOptionsSortMethod.style['display'] = 'none'; 394 | regenerateGraphButton.style['display'] = 'block'; 395 | } 396 | postGraphState(); 397 | } 398 | 399 | function setNetworkLayout() { 400 | if (showHierarchicalOptionsCheckbox.checked) { 401 | if (hierarchicalOptionsDirectionSelect.value && hierarchicalOptionsDirectionSelect.value === 'Random') { 402 | setRandomLayout(); 403 | seed = Math.random(); 404 | removeNodePositions(); 405 | regenerateGraphButton.style['display'] = 'block'; 406 | } else { 407 | const direction = hierarchicalOptionsDirectionSelect.value ? hierarchicalOptionsDirectionSelect.value : 'UD'; 408 | const sortMethod = hierarchicalOptionsSortMethodSelect.value ? hierarchicalOptionsSortMethodSelect.value : 'hubsize'; 409 | setHierarchicalLayout(direction, sortMethod); 410 | regenerateGraphButton.style['display'] = 'none'; 411 | } 412 | } else { 413 | options.layout = {}; 414 | regenerateGraphButton.style['display'] = 'block'; 415 | } 416 | options.layout.randomSeed = seed; 417 | network = new vis.Network(container, data, options); 418 | network.on("stabilizationIterationsDone", function () { 419 | network.setOptions({ 420 | physics: false 421 | }); 422 | unfixNodes(); 423 | postGraphState(); 424 | }); 425 | network.on('dragEnd', postGraphState); 426 | postGraphState(); 427 | network.on("selectNode", function (params) { 428 | if (params.nodes.length === 1) { 429 | var node = nodes.get(params.nodes[0]); 430 | openFileInVsCode(node.filepath); 431 | } 432 | }); 433 | network.on("hoverNode", function (params) { 434 | var node = nodes.get(params.node); 435 | if (node.filepath && node.filepath.length > 0) { 436 | network.canvas.body.container.style.cursor = 'pointer'; 437 | } 438 | }); 439 | network.on("blurNode", function (params) { 440 | network.canvas.body.container.style.cursor = 'default'; 441 | }); 442 | } 443 | 444 | function postGraphState() { 445 | const message = JSON.stringify({ 446 | networkSeed: seed, 447 | graphDirection: showHierarchicalOptionsCheckbox.checked ? hierarchicalOptionsDirectionSelect.value : 'Random', 448 | graphLayout: showHierarchicalOptionsCheckbox.checked ? hierarchicalOptionsSortMethodSelect.value : 'hubsize', 449 | showHierarchicalOptions: showHierarchicalOptionsCheckbox.checked, 450 | nodePositions: getNodePositions() 451 | }); 452 | vscode.postMessage({ 453 | command: 'setGraphState', 454 | text: message 455 | }); 456 | } 457 | 458 | function getNodePositions() { 459 | const nodePositions = {}; 460 | nodes.forEach(node => { 461 | const position = network.getPosition(node.id); 462 | nodePositions[node.id] = { 463 | x: position.x, 464 | y: position.y 465 | }; 466 | }); 467 | return nodePositions; 468 | } 469 | 470 | function unfixNodes() { 471 | nodes.forEach(function (node) { 472 | nodes.update({ 473 | id: node.id, 474 | fixed: false 475 | }); 476 | }); 477 | } 478 | 479 | function removeNodePositions() { 480 | nodes.forEach(function (node) { 481 | nodes.update({ 482 | id: node.id, 483 | fixed: false, 484 | x: undefined, 485 | y: undefined 486 | }); 487 | }); 488 | } 489 | 490 | }()); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2018", 5 | "outDir": "out", 6 | "lib": [ 7 | "es2018" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true, 12 | "baseUrl": ".", 13 | "paths": { 14 | "@commands": ["src/commands"], 15 | "@src": ["src"], 16 | "@model": ["src/model"] 17 | } 18 | }, 19 | "exclude": [ 20 | "node_modules", 21 | ".vscode-test" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderAllan/vscode-angulartools/4f66f46b91c81f7e30a1ce1a3c7c39702084d160/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /webpack-resolve-tsconfig-path-to-webpack-alias.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | function resolveTsconfigPathsToAlias() { 4 | const { paths } = require('./tsconfig.json').compilerOptions; 5 | 6 | const aliases = {}; 7 | 8 | Object.keys(paths).forEach((item) => { 9 | const key = item.replace('/*', ''); 10 | const value = resolve(__dirname, paths[item][0].replace('/*', '').replace('*', '')); 11 | 12 | aliases[key] = value; 13 | }); 14 | 15 | return aliases; 16 | } 17 | 18 | module.exports = resolveTsconfigPathsToAlias; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | const resolveTsconfigPathsToAlias = require('./webpack-resolve-tsconfig-path-to-webpack-alias'); 7 | 8 | 9 | /**@type {import('webpack').Configuration}*/ 10 | const config = { 11 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 12 | 13 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 14 | output: { 15 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 16 | path: path.resolve(__dirname, 'dist'), 17 | filename: 'extension.js', 18 | libraryTarget: 'commonjs2', 19 | devtoolModuleFilenameTemplate: '../[resource-path]' 20 | }, 21 | devtool: 'source-map', 22 | externals: { 23 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 24 | }, 25 | resolve: { 26 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 27 | extensions: ['.ts', '.js'], 28 | alias: resolveTsconfigPathsToAlias(), 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.ts$/, 34 | exclude: /node_modules/, 35 | use: [ 36 | { 37 | loader: 'ts-loader' 38 | } 39 | ] 40 | } 41 | ] 42 | } 43 | }; 44 | module.exports = config; --------------------------------------------------------------------------------