├── .gitignore ├── src ├── file.ts ├── utils.ts ├── extension.ts ├── add-files-extended.ts ├── file-contents.ts ├── add-files.ts └── file-contents-extended.ts ├── images └── icon.png ├── .vscodeignore ├── tsconfig.json ├── .vscode ├── settings.json ├── launch.json └── tasks.json ├── CHANGELOG.md ├── LICENSE.md ├── test ├── index.ts └── extension.test.ts ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | assets -------------------------------------------------------------------------------- /src/file.ts: -------------------------------------------------------------------------------- 1 | export interface IFiles { 2 | name: string; 3 | content: string; 4 | } -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastianbaar/vscode-add-angular-files/HEAD/images/icon.png -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | typings/** 3 | out/test/** 4 | test/** 5 | src/** 6 | **/*.map 7 | .gitignore 8 | tsconfig.json -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "." 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | ".vscode-test" 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version 10 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.0.1 (2017-07-25) 2 | 3 | Update README.md 4 | 5 | # 2.0.0 (2017-04-26) 6 | 7 | Change commands & menus entries to just "Angular" 8 | 9 | ### BREAKING CHANGES 10 | * user settings: update `"addNg2.addTestFile"` to `"addAngular.addTestFile"` 11 | * user settings: update `"addNg2.stylesheet"` to `"addAngular.stylesheet"` 12 | 13 | # 1.1.3 (2016-11-08) 14 | 15 | * user settings: include test file (.spec) in generation of files in the user settings `"addNg2.addTestFile": VALUE`, where `VALUE` is true (default) or false 16 | 17 | # 1.1.2 (2016-11-07) 18 | 19 | * user settings: custom stylesheet language define the stylesheet language in the user settings `"addNg2.stylesheet": "VALUE"`, where `VALUE` is "css" (default), "sass", or "less" 20 | 21 | # 1.1.3 (2016-10-05) 22 | 23 | Updated for Angular 2.0.0 release -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from 'vscode'; 2 | 3 | export class Utils { 4 | 5 | public static getStylesheetConfig(): string { 6 | var addAngularConfigStylesheet: string = workspace.getConfiguration('addAngular')['stylesheet']; 7 | 8 | var stylesheetFileExtension: string = 'css'; 9 | switch (addAngularConfigStylesheet) { 10 | case 'css': 11 | break; 12 | case 'sass': 13 | stylesheetFileExtension = 'scss'; 14 | break; 15 | case 'less': 16 | stylesheetFileExtension = 'less'; 17 | break; 18 | default: 19 | stylesheetFileExtension = 'css'; 20 | } 21 | 22 | return stylesheetFileExtension; 23 | } 24 | 25 | public static getAddTestFileConfig(): boolean { 26 | return workspace.getConfiguration('addAngular')['addTestFile']; 27 | } 28 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sebastian Baar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension 9 | // host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | var testRunner = require('vscode/lib/testrunner'); 14 | 15 | // You can directly control Mocha options by uncommenting the following lines 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info 17 | testRunner.configure({ 18 | ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) 19 | useColors: true // colored output from test results 20 | }); 21 | 22 | module.exports = testRunner; -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outDir": "${workspaceRoot}/out/src", 14 | "preLaunchTask": "npm" 15 | }, 16 | { 17 | "name": "Launch Tests", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], 22 | "stopOnEntry": false, 23 | "sourceMaps": true, 24 | "outDir": "${workspaceRoot}/out/test", 25 | "preLaunchTask": "npm" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls a custom npm script that compiles the extension. 10 | { 11 | "version": "0.1.0", 12 | 13 | // we want to run npm 14 | "command": "npm", 15 | 16 | // the command is a shell script 17 | "isShellCommand": true, 18 | 19 | // show the output window only if unrecognized errors occur. 20 | "showOutput": "silent", 21 | 22 | // we run the custom script "compile" as defined in package.json 23 | "args": ["run", "compile", "--loglevel", "silent"], 24 | 25 | // The tsc compiler is started in watching mode 26 | "isWatching": true, 27 | 28 | // use the standard tsc in watch mode problem matcher to find compile problems in the output. 29 | "problemMatcher": "$tsc-watch" 30 | } -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, commands, window } from 'vscode'; 2 | import { AddFiles } from './add-files'; 3 | import { AddFilesExtended } from './add-files-extended'; 4 | 5 | export function activate(context: ExtensionContext) { 6 | console.log('Congratulations, your extension is now active!'); 7 | 8 | var addAngularFiles = commands.registerCommand('extension.addAngularFiles', (args) => { 9 | const addFiles: AddFiles = new AddFiles(); 10 | addFiles.showFileNameDialog(args) 11 | .then(addFiles.createFolder) 12 | .then(addFiles.createFiles) 13 | .then(addFiles.openFileInEditor) 14 | .catch((err) => { 15 | if (err) { 16 | window.showErrorMessage(err); 17 | } 18 | }); 19 | }); 20 | 21 | var addAngularFilesExtended = commands.registerCommand('extension.addAngularFilesExtended', (args) => { 22 | const addFilesExtended: AddFilesExtended = new AddFilesExtended(); 23 | addFilesExtended.showFileNameDialog(args) 24 | .then(addFilesExtended.createFolder) 25 | .then(addFilesExtended.createFiles) 26 | .then(addFilesExtended.openFileInEditor) 27 | .catch((err) => { 28 | if (err) { 29 | window.showErrorMessage(err); 30 | } 31 | }); 32 | }); 33 | 34 | context.subscriptions.push(addAngularFiles); 35 | context.subscriptions.push(addAngularFilesExtended); 36 | } -------------------------------------------------------------------------------- /src/add-files-extended.ts: -------------------------------------------------------------------------------- 1 | import { window, workspace, TextEditor } from 'vscode'; 2 | import { FileContentsExtended } from './file-contents-extended'; 3 | import { AddFiles } from './add-files'; 4 | import { Utils } from './utils' 5 | import { IFiles } from './file'; 6 | import * as fs from 'fs'; 7 | import * as path from 'path'; 8 | import * as Q from 'q'; 9 | 10 | export class AddFilesExtended extends AddFiles { 11 | 12 | // Create the new "shared" folder for model and service 13 | public createFolder(folderName): Q.Promise { 14 | const deferred: Q.Deferred = Q.defer(); 15 | var fileExists: boolean = fs.existsSync(folderName); 16 | 17 | if (!fileExists) { 18 | fs.mkdir(folderName, (err) => { 19 | fs.mkdirSync(path.join(folderName, 'shared')); 20 | deferred.resolve(folderName); 21 | }); 22 | } else { 23 | deferred.reject('Folder already exists'); 24 | } 25 | return deferred.promise; 26 | } 27 | 28 | // Get file contents and create the new files in the folder 29 | public createFiles(folderName: string): Q.Promise { 30 | const deferred: Q.Deferred = Q.defer(); 31 | var inputName: string = path.parse(folderName).name; 32 | const fc: FileContentsExtended = new FileContentsExtended(); 33 | const afe: AddFilesExtended = new AddFilesExtended(); 34 | let stylesheetFileExtension = Utils.getStylesheetConfig(); 35 | let addTestFile: boolean = Utils.getAddTestFileConfig(); 36 | 37 | // create an IFiles array including file names and contents 38 | var files: IFiles[] = [ 39 | { 40 | name: path.join(folderName, `${inputName}.component.ts`), 41 | content: fc.componentContent(inputName) 42 | }, 43 | { 44 | name: path.join(folderName, `${inputName}.component.html`), 45 | content: fc.templateContent(inputName) 46 | }, 47 | { 48 | name: path.join(folderName, `${inputName}.component.${stylesheetFileExtension}`), 49 | content: fc.cssContent(inputName) 50 | }, 51 | { 52 | name: path.join(folderName, 'shared', `${inputName}.service.ts`), 53 | content: fc.serviceContent(inputName) 54 | }, 55 | { 56 | name: path.join(folderName, 'shared', `${inputName}.model.ts`), 57 | content: fc.modelContent(inputName) 58 | } 59 | ]; 60 | 61 | if (addTestFile) { 62 | files.push( 63 | { 64 | name: path.join(folderName, `${inputName}.component.spec.ts`), 65 | content: fc.specContent(inputName) 66 | } 67 | ); 68 | } 69 | 70 | // write files 71 | afe.writeFiles(files).then((errors) => { 72 | if (errors.length > 0) { 73 | window.showErrorMessage(`${errors.length} file(s) could not be created. I'm sorry :-(`); 74 | } 75 | else { 76 | deferred.resolve(folderName); 77 | } 78 | }); 79 | 80 | return deferred.promise; 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-add-angular2-files", 3 | "displayName": "Add Angular Files", 4 | "description": "Add Angular files including snippets to your Visual Studio Code project", 5 | "version": "2.0.1", 6 | "icon": "images/icon.png", 7 | "publisher": "sebastianbaar", 8 | "license": "SEE LICENSE IN LICENSE.md", 9 | "author": { 10 | "name": "Sebastian Baar" 11 | }, 12 | "keywords": [ 13 | "Angular", 14 | "Angular 2", 15 | "Angular 4", 16 | "TypeScript", 17 | "Add files" 18 | ], 19 | "engines": { 20 | "vscode": "^1.5.0" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/sebastianbaar/vscode-add-angular-files" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/sebastianbaar/vscode-add-angular-files/issues" 28 | }, 29 | "categories": [ 30 | "Other", 31 | "Languages", 32 | "Snippets" 33 | ], 34 | "scripts": { 35 | "vscode:prepublish": "tsc -p ./", 36 | "compile": "tsc -watch -p ./", 37 | "postinstall": "node ./node_modules/vscode/bin/install" 38 | }, 39 | "activationEvents": [ 40 | "*" 41 | ], 42 | "main": "./out/src/extension", 43 | "contributes": { 44 | "commands": [ 45 | { 46 | "command": "extension.addAngularFiles", 47 | "title": "Add Angular Files" 48 | }, 49 | { 50 | "command": "extension.addAngularFilesExtended", 51 | "title": "Add Angular Files (Extended)" 52 | } 53 | ], 54 | "menus": { 55 | "explorer/context": [ 56 | { 57 | "when": "", 58 | "command": "extension.addAngularFiles", 59 | "group": "Add Files" 60 | }, 61 | { 62 | "when": "", 63 | "command": "extension.addAngularFilesExtended", 64 | "group": "Add Files" 65 | } 66 | ] 67 | }, 68 | "configuration": { 69 | "type": "object", 70 | "title": "Add Angular Files configuration", 71 | "properties": { 72 | "addAngular.stylesheet": { 73 | "type": "string", 74 | "default": "css", 75 | "description": "Specifies stylesheet language ('css', 'sass', 'less').", 76 | "enum": [ 77 | "css", 78 | "sass", 79 | "less" 80 | ] 81 | }, 82 | "addAngular.addTestFile": { 83 | "type": "boolean", 84 | "default": true, 85 | "description": "Create a test file (.spec)" 86 | } 87 | } 88 | } 89 | }, 90 | "devDependencies": { 91 | "typescript": "^2.0.3", 92 | "vscode": "^1.1.0", 93 | "rimraf": "2.6.1", 94 | "mocha": "^2.3.3", 95 | "@types/node": "^6.0.40", 96 | "@types/mocha": "^2.2.32", 97 | "@types/q": "^1.0.0", 98 | "@types/rimraf": "0.0.28" 99 | }, 100 | "dependencies": { 101 | "q": "^1.4.1", 102 | "path": "^0.12.7" 103 | } 104 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](images/icon.png) 2 | 3 | # Add Angular Files for VS Code 4 | 5 | This extension allows you to add **Angular typescript files including snippets** to your VS Code project. 6 | 7 | > Inspired by [Dominik Kundel](https://github.com/dkundel)'s [Advanced New File - Visual Studio Code Extension](https://github.com/dkundel/vscode-new-file) and [John Papa](https://github.com/johnpapa)'s [Angular TypeScript Snippets for VS Code](https://github.com/johnpapa/vscode-angular-snippets). 8 | 9 | ## Features 10 | 11 | Right click on a file or a folder in your current project. There are two options added to the context menu `Add Angular Files` and `Add Angular Files (Extended)`: 12 | 13 | ### Add Angular Files 14 | 15 | This command adds the following files to your new folder (let's assume you typed in `home`): 16 | ``` 17 | home/home.component.ts 18 | home/home.component.html 19 | home/home.component.css | .less | .scss 20 | home/home.component.spec.ts 21 | ``` 22 | 23 | ![alt text](https://cloud.githubusercontent.com/assets/7135276/16797373/83bd9ffc-48e7-11e6-9ac0-9874a4387a3a.gif "Add Angular Files") 24 | 25 | ### Add Angular Files (Extended) 26 | 27 | This command adds the following extended files to your new folder (let's assume you typed in `home`): 28 | ``` 29 | home/home.component.ts 30 | home/home.component.html 31 | home/home.component.css | .less | .scss 32 | home/home.component.spec.ts 33 | home/shared/home.service.ts 34 | home/shared/home.ts 35 | ``` 36 | 37 | ![alt text](https://cloud.githubusercontent.com/assets/7135276/16797375/861bd246-48e7-11e6-8cc8-2fc688197388.gif "Add Angular Files (Extended)") 38 | 39 | **The naming of the files as well as the (boilerplate) snippets are based on the [official Angular Style Guide](https://angular.io/docs/ts/latest/guide/style-guide.html)** 40 | 41 | ### Customize stylesheet language 42 | 43 | You can choose between your preferred stylesheet language CSS, SASS or LESS. 44 | 45 | 1. Go to user settings (File > Preferences > user settings) 46 | 2. Add the following key-value pair: 47 | `"addAngular.stylesheet": "VALUE"`, where `VALUE` is an ENUM of "css" (default), "sass", or "less" 48 | 49 | ### Customize test (.spec) files 50 | 51 | 1. Go to user settings (File > Preferences > user settings) 52 | 2. Add the following key-value pair: 53 | `"addAngular.addTestFile": VALUE`, where `VALUE` is true (default) or false 54 | 55 | ## Installation 56 | 57 | 1. Install Visual Studio Code 1.3.0 or higher 58 | 2. Launch Code 59 | 3. From the command palette `Ctrl`-`Shift`-`P` (Windows, Linux) or `Cmd`-`Shift`-`P` (OSX) 60 | 4. Select `Install Extension` 61 | 5. Type `add angular files` and press enter 62 | 6. Reload Visual Studio Code 63 | 64 | # Disclaimer 65 | 66 | **Important:** This extension due to the nature of it's purpose will create 67 | files on your hard drive and if necessary create the respective folder structure. 68 | While it should not override any files during this process, I'm not giving any guarantees 69 | or take any responsibility in case of lost data. 70 | 71 | # License 72 | 73 | MIT 74 | -------------------------------------------------------------------------------- /src/file-contents.ts: -------------------------------------------------------------------------------- 1 | export class FileContents { 2 | 3 | private camelCase (input: string): string { 4 | return input.replace( /-([a-z])/ig, function( all, letter ) { 5 | return letter.toUpperCase(); 6 | }); 7 | } 8 | 9 | public componentContent(inputName: string): string { 10 | var inputUpperCase: string; 11 | inputUpperCase = inputName.charAt(0).toUpperCase() + inputName.slice(1); 12 | inputUpperCase = this.camelCase(inputUpperCase); 13 | 14 | var componentContent: string = "import { Component, OnInit } from '@angular/core';\n" + 15 | "\n" + 16 | "@Component({\n" + 17 | "\tselector: '" + inputName + "',\n" + 18 | "\ttemplateUrl: '" + inputName + ".component.html'\n" + 19 | "})\n" + 20 | "\n" + 21 | "export class " + inputUpperCase + "Component implements OnInit {\n" + 22 | "\n" + 23 | "\tngOnInit() { }\n" + 24 | "}"; 25 | return componentContent; 26 | } 27 | 28 | public templateContent(inputName: string): string { 29 | var inputUpperCase: string; 30 | inputUpperCase = inputName.charAt(0).toUpperCase() + inputName.slice(1); 31 | inputUpperCase = this.camelCase(inputUpperCase); 32 | var templateContent: string = `
Hello ${inputUpperCase}Component!
`; 33 | return templateContent; 34 | } 35 | 36 | public cssContent(inputName: string): string { 37 | var inputUpperCase: string = inputName.charAt(0).toUpperCase() + inputName.slice(1); 38 | var cssContent: string = `.${inputName} {\n\n}`; 39 | return cssContent; 40 | } 41 | 42 | public specContent(inputName: string): string { 43 | var inputUpperCase: string; 44 | inputUpperCase = inputName.charAt(0).toUpperCase() + inputName.slice(1); 45 | inputUpperCase = this.camelCase(inputUpperCase); 46 | 47 | var specContent: string = "import { TestBed, inject } from '@angular/core/testing';\n\n" + 48 | "import { " + inputUpperCase + "Component } from './" + inputName + ".component';\n" + 49 | "\n" + 50 | "describe('a "+ inputName +" component', () => {\n" + 51 | "\tlet component: " + inputUpperCase + "Component;\n" + 52 | "\n" + 53 | "\t// register all needed dependencies\n" + 54 | "\tbeforeEach(() => {\n" + 55 | "\t\tTestBed.configureTestingModule({\n" + 56 | "\t\t\tproviders: [\n" + 57 | "\t\t\t\t" + inputUpperCase + "Component\n" + 58 | "\t\t\t]\n" + 59 | "\t\t});\n" + 60 | "\t});\n" + 61 | "\n" + 62 | "\t// instantiation through framework injection\n" + 63 | "\tbeforeEach(inject([" + inputUpperCase + "Component], (" + inputUpperCase + "Component) => {\n" + 64 | "\t\tcomponent = " + inputUpperCase + "Component;\n" + 65 | "\t}));\n" + 66 | "\n" + 67 | "\tit('should have an instance', () => {\n" + 68 | "\t\texpect(component).toBeDefined();\n" + 69 | "\t});\n" + 70 | "});"; 71 | return specContent; 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /src/add-files.ts: -------------------------------------------------------------------------------- 1 | import { window, workspace, TextEditor } from 'vscode'; 2 | import { FileContents } from './file-contents'; 3 | import { Utils } from './utils' 4 | import { IFiles } from './file'; 5 | import * as fs from 'fs'; 6 | import * as path from 'path'; 7 | import * as Q from 'q'; 8 | 9 | export class AddFiles { 10 | 11 | // Show input prompt for folder name 12 | // The imput is also used to create the files with the respective name as defined in the Angular style guide [https://angular.io/docs/ts/latest/guide/style-guide.html] 13 | public showFileNameDialog(args): Q.Promise { 14 | const deferred: Q.Deferred = Q.defer(); 15 | 16 | var clickedFolderPath: string; 17 | if (args) { 18 | clickedFolderPath = args.fsPath 19 | } 20 | else { 21 | if (!window.activeTextEditor) { 22 | deferred.reject('Please open a file first.. or just right-click on a file/folder and use the context menu!'); 23 | return deferred.promise; 24 | } else { 25 | clickedFolderPath = path.dirname(window.activeTextEditor.document.fileName); 26 | } 27 | } 28 | var newFolderPath: string = fs.lstatSync(clickedFolderPath).isDirectory() ? clickedFolderPath : path.dirname(clickedFolderPath); 29 | 30 | if (workspace.rootPath === undefined) { 31 | deferred.reject('Please open a project first. Thanks! :-)'); 32 | } 33 | else { 34 | window.showInputBox({ 35 | prompt: 'What\'s the name of the new folder?', 36 | value: 'folder' 37 | }).then( 38 | (fileName) => { 39 | if (!fileName || /[~`!#$%\^&*+=\[\]\\';,/{}|\\":<>\?\s]/g.test(fileName)) { 40 | deferred.reject('That\'s not a valid name! (no whitespaces or special characters)'); 41 | } else { 42 | deferred.resolve(path.join(newFolderPath, fileName)); 43 | } 44 | }, 45 | (error) => console.error(error) 46 | ); 47 | } 48 | return deferred.promise; 49 | } 50 | 51 | // Create the new folder 52 | public createFolder(folderName): Q.Promise { 53 | const deferred: Q.Deferred = Q.defer(); 54 | 55 | fs.exists(folderName, (exists) => { 56 | if (!exists) { 57 | fs.mkdirSync(folderName); 58 | deferred.resolve(folderName); 59 | } else { 60 | deferred.reject('Folder already exists'); 61 | } 62 | }); 63 | return deferred.promise; 64 | } 65 | 66 | // Get file contents and create the new files in the folder 67 | public createFiles(folderName: string): Q.Promise { 68 | const deferred: Q.Deferred = Q.defer(); 69 | var inputName: string = path.parse(folderName).name; 70 | const fc: FileContents = new FileContents(); 71 | const af: AddFiles = new AddFiles(); 72 | let stylesheetFileExtension: string = Utils.getStylesheetConfig(); 73 | let addTestFile: boolean = Utils.getAddTestFileConfig(); 74 | 75 | // create an IFiles array including file names and contents 76 | var files: IFiles[] = [ 77 | { 78 | name: path.join(folderName, `${inputName}.component.ts`), 79 | content: fc.componentContent(inputName) 80 | }, 81 | { 82 | name: path.join(folderName, `${inputName}.component.html`), 83 | content: fc.templateContent(inputName) 84 | }, 85 | { 86 | name: path.join(folderName, `${inputName}.component.${stylesheetFileExtension}`), 87 | content: fc.cssContent(inputName) 88 | } 89 | ]; 90 | 91 | if (addTestFile) { 92 | files.push( 93 | { 94 | name: path.join(folderName, `${inputName}.component.spec.ts`), 95 | content: fc.specContent(inputName) 96 | } 97 | ); 98 | } 99 | 100 | // write files 101 | af.writeFiles(files).then((errors) => { 102 | if (errors.length > 0) { 103 | window.showErrorMessage(`${errors.length} file(s) could not be created. I'm sorry :-(`); 104 | } 105 | else { 106 | deferred.resolve(folderName); 107 | } 108 | }); 109 | 110 | return deferred.promise; 111 | } 112 | 113 | public writeFiles( files: IFiles[]): Q.Promise { 114 | const deferred: Q.Deferred = Q.defer(); 115 | var errors: string[] = []; 116 | files.forEach(file => { 117 | fs.writeFile(file.name, file.content, (err) => { 118 | if (err) { errors.push(err.message) } 119 | deferred.resolve(errors); 120 | }); 121 | }); 122 | return deferred.promise; 123 | } 124 | 125 | // Open the created component in the editor 126 | public openFileInEditor(folderName): Q.Promise { 127 | const deferred: Q.Deferred = Q.defer(); 128 | var inputName: string = path.parse(folderName).name;; 129 | var fullFilePath: string = path.join(folderName, `${inputName}.component.ts`); 130 | 131 | workspace.openTextDocument(fullFilePath).then((textDocument) => { 132 | if (!textDocument) { return; } 133 | window.showTextDocument(textDocument).then((editor) => { 134 | if (!editor) { return; } 135 | deferred.resolve(editor); 136 | }); 137 | }); 138 | 139 | return deferred.promise; 140 | } 141 | 142 | } -------------------------------------------------------------------------------- /test/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { AddFiles } from '../src/add-files'; 3 | import { AddFilesExtended } from '../src/add-files-extended'; 4 | import * as fs from 'fs'; 5 | import * as path from 'path'; 6 | import * as rimraf from 'rimraf'; 7 | import * as assert from 'assert'; 8 | 9 | const testPath = path.join(__dirname, 'test-path'); 10 | 11 | suite("Extension Tests:", () => { 12 | 13 | suite("adding files command", () => { 14 | 15 | suiteTeardown((done) => { 16 | checkIfTestFolderExistsAndDelete(done); 17 | }); 18 | 19 | const addFiles = new AddFiles(); 20 | 21 | test('should create a new folder ', (done) => { 22 | addFiles.createFolder(testPath) 23 | .then((folderName) => { 24 | assert.strictEqual(folderName, testPath); 25 | assert.strictEqual(fs.existsSync(testPath), true); 26 | checkIfTestFolderExistsAndDelete(); 27 | done(); 28 | }); 29 | }); 30 | 31 | test('should alert if folder already exists', (done) => { 32 | addFiles.createFolder(testPath).then( 33 | (folderName) => { 34 | addFiles.createFolder(testPath) 35 | .then((folderName) => { 36 | // error handling test 37 | checkIfTestFolderExistsAndDelete(); 38 | done(); 39 | }, (err) => { 40 | assert.strictEqual(err, 'Folder already exists'); 41 | checkIfTestFolderExistsAndDelete(); 42 | done(); 43 | }); 44 | }, 45 | (err) => { 46 | console.log(err); 47 | done(); 48 | }); 49 | }); 50 | 51 | test('should create the files', (done) => { 52 | addFiles.createFolder(testPath).then( 53 | (folderName) => { 54 | addFiles.createFiles(testPath) 55 | .then((folderName) => { 56 | assert.strictEqual(folderName, testPath); 57 | assert.strictEqual(fs.existsSync(testPath), true); 58 | fs.readdir(testPath, (err, files) => { 59 | assert.strictEqual(files.length, 4); 60 | checkIfTestFolderExistsAndDelete(); 61 | done(); 62 | }); 63 | }).catch((err) => { 64 | console.log(err); 65 | done(); 66 | });; 67 | }, 68 | (err) => { 69 | checkIfTestFolderExistsAndDelete(); 70 | console.log(err); 71 | done(); 72 | }).catch((err) => { 73 | console.log(err); 74 | done(); 75 | }); 76 | }); 77 | }); 78 | 79 | suite("adding extended files command", () => { 80 | 81 | suiteTeardown((done) => { 82 | checkIfTestFolderExistsAndDelete(done); 83 | }); 84 | teardown((done) => { 85 | checkIfOutTestFolderExistsAndDelete(done); 86 | }); 87 | 88 | const addFilesExtended = new AddFilesExtended(); 89 | 90 | test('should create new folders ', (done) => { 91 | addFilesExtended.createFolder(testPath) 92 | .then((folderName) => { 93 | assert.strictEqual(folderName, testPath); 94 | assert.strictEqual(fs.existsSync(testPath), true); 95 | checkIfTestFolderExistsAndDelete(); 96 | done(); 97 | }); 98 | }); 99 | 100 | test('should alert if folder already exists', (done) => { 101 | addFilesExtended.createFolder(testPath).then( 102 | (folderName) => { 103 | addFilesExtended.createFolder(testPath) 104 | .then((folderName) => { 105 | // error handling test 106 | checkIfTestFolderExistsAndDelete(); 107 | done(); 108 | }, (err) => { 109 | assert.strictEqual(err, 'Folder already exists'); 110 | checkIfTestFolderExistsAndDelete(); 111 | done(); 112 | }); 113 | }, 114 | (err) => { 115 | console.log(err); 116 | }); 117 | }); 118 | 119 | test('should create the files', (done) => { 120 | addFilesExtended.createFolder(testPath).then( 121 | (folderName) => { 122 | addFilesExtended.createFiles(testPath) 123 | .then((folderName) => { 124 | assert.strictEqual(folderName, testPath); 125 | assert.strictEqual(fs.existsSync(testPath), true); 126 | fs.readdir(testPath, (err, files) => { 127 | assert.strictEqual(files.length, 5); 128 | fs.readdir(path.join(testPath, 'shared'), (err, files) => { 129 | assert.strictEqual(files.length, 2); 130 | checkIfTestFolderExistsAndDelete(); 131 | checkIfOutTestFolderExistsAndDelete(); 132 | done(); 133 | }); 134 | }); 135 | }); 136 | }, 137 | (err) => { 138 | console.log(err); 139 | }); 140 | }); 141 | }); 142 | }); 143 | 144 | function checkIfTestFolderExistsAndDelete(done?: MochaDone) { 145 | if (fs.exists(testPath) || testPath !== '/') { 146 | rimraf(testPath, () => { if(done) done(); }); 147 | } 148 | } 149 | function checkIfOutTestFolderExistsAndDelete(done?: MochaDone) { 150 | if (path.join(testPath, 'shared') !== '/') { 151 | rimraf(path.join(testPath, 'shared'), () => { if(done) done(); }); 152 | } 153 | } -------------------------------------------------------------------------------- /src/file-contents-extended.ts: -------------------------------------------------------------------------------- 1 | export class FileContentsExtended { 2 | 3 | private camelCase (input: string): string { 4 | return input.replace( /-([a-z])/ig, function( all, letter ) { 5 | return letter.toUpperCase(); 6 | }); 7 | } 8 | 9 | public componentContent(inputName: string): string { 10 | var inputUpperCase: string; 11 | inputUpperCase = inputName.charAt(0).toUpperCase() + inputName.slice(1); 12 | inputUpperCase = this.camelCase(inputUpperCase); 13 | 14 | var componentContent: string = "import { Component, OnInit } from '@angular/core';\n\n" + 15 | "import { " + inputUpperCase + " } from './shared/" + inputName + ".model';\n" + 16 | "import { " + inputUpperCase + "Service } from './shared/" + inputName + ".service';\n" + 17 | "\n" + 18 | "@Component({\n" + 19 | "\tselector: '" + inputName + "',\n" + 20 | "\ttemplateUrl: '" + inputName + ".component.html',\n" + 21 | "\tproviders: [" + inputUpperCase + "Service]\n" + 22 | "})\n" + 23 | "\n" + 24 | "export class " + inputUpperCase + "Component implements OnInit {\n" + 25 | "\t"+ this.camelCase(inputName) +": "+ inputUpperCase +"[] = [];\n" + 26 | "\n" + 27 | "\tconstructor(private " + this.camelCase(inputName) + "Service: " + inputUpperCase + "Service) { }\n" + 28 | "\n" + 29 | "\tngOnInit() {\n" + 30 | "\t\tthis."+ this.camelCase(inputName) + "Service.getList().subscribe((res) => {\n" + 31 | "\t\t\tthis."+ this.camelCase(inputName) +" = res;\n" + 32 | "\t\t});\n" + 33 | "\t}\n" + 34 | "}"; 35 | return componentContent; 36 | } 37 | 38 | public serviceContent(inputName: string): string { 39 | var inputUpperCase: string; 40 | inputUpperCase = inputName.charAt(0).toUpperCase() + inputName.slice(1); 41 | inputUpperCase = this.camelCase(inputUpperCase); 42 | var serviceContent: string = "import { Injectable } from '@angular/core';\n" + 43 | "import { Http } from '@angular/http';\n" + 44 | "import { Observable } from 'rxjs/Observable';\n" + 45 | "import 'rxjs/add/operator/map';\n" + 46 | "\n" + 47 | "import { "+ inputUpperCase +" } from './"+ inputName +".model';\n" + 48 | "\n" + 49 | "@Injectable()\n" + 50 | "export class " + inputUpperCase + "Service {\n" + 51 | "\n" + 52 | "\tconstructor(private http: Http) { }\n" + 53 | "\n" + 54 | "\tgetList(): Observable<"+ inputUpperCase +"[]> {\n" + 55 | "\t\treturn this.http.get('/api/list').map(res => res.json() as "+ inputUpperCase +"[]);\n" + 56 | "\t}\n" + 57 | "}"; 58 | return serviceContent; 59 | } 60 | 61 | public modelContent(inputName: string): string { 62 | var inputUpperCase: string; 63 | inputUpperCase = inputName.charAt(0).toUpperCase() + inputName.slice(1); 64 | inputUpperCase = this.camelCase(inputUpperCase); 65 | var modelContent: string = "export class "+ inputUpperCase +" {\n" + 66 | "\tid: number;\n" + 67 | "\tname: string;\n" + 68 | "}"; 69 | return modelContent; 70 | } 71 | 72 | public templateContent(inputName: string): string { 73 | var inputUpperCase: string; 74 | inputUpperCase = inputName.charAt(0).toUpperCase() + inputName.slice(1); 75 | inputUpperCase = this.camelCase(inputUpperCase); 76 | var templateContent: string = `
Hello ${inputUpperCase}Component!
`; 77 | return templateContent; 78 | } 79 | 80 | public cssContent(inputName: string): string { 81 | var inputUpperCase: string = inputName.charAt(0).toUpperCase() + inputName.slice(1); 82 | var cssContent: string = `.${inputName} {\n\n}`; 83 | return cssContent; 84 | } 85 | 86 | public specContent(inputName: string): string { 87 | var inputUpperCase: string; 88 | inputUpperCase = inputName.charAt(0).toUpperCase() + inputName.slice(1); 89 | inputUpperCase = this.camelCase(inputUpperCase); 90 | 91 | var specContent: string = "import { TestBed, inject } from '@angular/core/testing';\n" + 92 | "import { HttpModule } from '@angular/http';\n" + 93 | "import { Observable } from 'rxjs/Observable';\n" + 94 | "import 'rxjs/Rx';\n\n" + 95 | "import { " + inputUpperCase + "Component } from './" + inputName + ".component';\n" + 96 | "import { " + inputUpperCase + "Service } from './shared/" + inputName + ".service';\n" + 97 | "import { " + inputUpperCase + " } from './shared/" + inputName + ".model';\n" + 98 | "\n" + 99 | "describe('a "+ inputName +" component', () => {\n" + 100 | "\tlet component: " + inputUpperCase + "Component;\n" + 101 | "\n" + 102 | "\t// register all needed dependencies\n" + 103 | "\tbeforeEach(() => {\n" + 104 | "\t\tTestBed.configureTestingModule({\n" + 105 | "\t\t\timports: [HttpModule],\n" + 106 | "\t\t\tproviders: [\n" + 107 | "\t\t\t\t{ provide: " + inputUpperCase + "Service, useClass: Mock" + inputUpperCase + "Service },\n" + 108 | "\t\t\t\t" + inputUpperCase + "Component\n" + 109 | "\t\t\t]\n" + 110 | "\t\t});\n" + 111 | "\t});\n" + 112 | "\n" + 113 | "\t// instantiation through framework injection\n" + 114 | "\tbeforeEach(inject([" + inputUpperCase + "Component], (" + inputUpperCase + "Component) => {\n" + 115 | "\t\tcomponent = " + inputUpperCase + "Component;\n" + 116 | "\t}));\n" + 117 | "\n" + 118 | "\tit('should have an instance', () => {\n" + 119 | "\t\texpect(component).toBeDefined();\n" + 120 | "\t});\n" + 121 | "});\n" + 122 | "\n" + 123 | "// Mock of the original " + inputName + " service\n" + 124 | "class Mock" + inputUpperCase + "Service extends " + inputUpperCase + "Service {\n" + 125 | "\tgetList(): Observable {\n" + 126 | "\t\treturn Observable.from([ { id: 1, name: 'One'}, { id: 2, name: 'Two'} ]);\n" + 127 | "\t}\n" + 128 | "}\n"; 129 | return specContent; 130 | } 131 | 132 | } --------------------------------------------------------------------------------