From 53750c97e11371dc878008f45bf700bf8ec31151 Mon Sep 17 00:00:00 2001 From: Agustina Chaer Date: Sun, 16 Aug 2020 12:06:51 -0300 Subject: [PATCH 1/2] feat: make optional files list customizable --- README.md | 64 ++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 4 +-- package.json | 17 +++++++++-- src/folderize.ts | 49 +++++++++++++++++--------------- src/format/format.ts | 22 +++++++++------ src/options/options.ts | 56 +++++++++++++++++++++++++----------- src/options/presets.ts | 32 +++++++++++++++++++++ src/types/types.ts | 2 ++ src/utils/index.ts | 1 + src/utils/utils.ts | 12 ++++++++ 10 files changed, 209 insertions(+), 50 deletions(-) create mode 100644 src/options/presets.ts create mode 100644 src/utils/index.ts create mode 100644 src/utils/utils.ts diff --git a/README.md b/README.md index f829336..e706d3f 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,70 @@ turn javascript files into folders ### Install Download the latest version from the Visual Studio Marketplace [here](https://marketplace.visualstudio.com/items?itemName=ee92.folderize) +### Customize +Not everybody uses the same folder structure, that is why Folderize also gives you the possibility to customize the options you see when selecting which optional files to create. + +In order to customize this extension, go to Visual Studio Code settings and search for `Folderize`, you will see an option called `optionalFilesToGenerate`. As a value it expects an array of a mix of presets(strings) or custom options(structured objects). These are explained a bit more in depth in the following subsections. + +Before that, here is a simple demo of how this setting could look like: + +```json +... + "folderize.filesToGenerate": [ + "styles", + "spec", + { + "id": "storybook", + "label": "Add stories file", + "description": "{fileName}.stories.{ext}", + "createFile": "{fileName}.stories.{ext}", + "importComponent": true + } + ] +... +``` + +#### Presets +As mentioned before, not everybody uses the same folder structure. That being said, there are some files that are quite common to most structures so to make it easier for you, we created a set of presets that can be of help. + +##### Current list of presets: +* test: creates a `{Component}.test.{ext}` file with the Component already imported +* spec: creates a `{Component}.spec.{ext}` file with the Component already imported +* css_module: creates a `{Component}.module.css` file and imports it in the Component +* styles: creates a `{Component}.styles.{ext}` file and imports it in the Component + +*Note:* both css_module and styles presets are imported in the Component with the name `styles`. If for whatever reason you need both files to be created for a component, you can override one of them with a custom option and change the import name. + +#### Custom Option +If you need a file to be created that is not part of our presets, there is still a way for you to add it. + +Custom options should follow the following structure: +``` +{ + id: string, + label: string, + description: string, + // OPTIONALS: + createFile: string, + importInComponentName: string, + importComponent: boolean +} +```` + +##### Property breakdown + +- *id:* this is used to identify the preset, if you use an id of an existing preset, it will replace it with your custom version. +- *label:* Name of the option that appears in the menu. +- *description:* Description of the option that appears in the menu. You can use `{fileName}` and `{ext}` and it will be replaced with the correct data extracted from the original file. This is useful when trying to display how the file name will end up looking like. +- *createFile:* this is the file name to create. As with description you can write `"{fileName}.example.{ext}"` if you want to make use of those properties. +- *importInComponentName:* If you want the file to be imported in the component, just give it an import name and the extension will know how and where to add it. +- *importComponent:* If you want the file to import the component, set this to true and the extension will take care of it. + +##### Overriding presets +In order to override a preset you just need to define a new custom option with the same id as the preset you want to override. You need to fully override it by providing all the necessary parameter. + +We recommend going to the [presets file](../blob/master/src/options/presets.ts) and copying the config you want to override to have a working base and go from there. + ### Donate If you you find this extension useful, you can make a donation [here](https://paypal.me/650egor) diff --git a/package-lock.json b/package-lock.json index 9b8e163..4fdecca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "folderize", - "version": "0.0.1", + "version": "0.0.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -4653,4 +4653,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 7347df1..d506477 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "folderize", "displayName": "folderize", "description": "Easily convert a Javascript file into a folder with an index file without breaking imports / exports. Supports .js, .jsx, .ts, .tsx.", - "version": "0.0.5", + "version": "0.0.6", "publisher": "ee92", "repository": "https://github.com/ee92/folderize", "icon": "assets/logo.png", @@ -43,6 +43,19 @@ "group": "boilerplate@1" } ] + }, + "configuration": { + "title": "Folderize", + "properties": { + "folderize.optionalFilesToGenerate": { + "type": "array", + "default": [ + "test", + "css_module" + ], + "markdownDescription": "Customize the files you want to generate by selecting from the presets or creating your own templates. To learn more about how to customize this setting go to the [documentation](https://github.com/ee92/folderize)." + } + } } }, "scripts": { @@ -61,4 +74,4 @@ "webpack-cli": "^3.3.6" }, "license": "SEE LICENSE IN LICENSE.md" -} +} \ No newline at end of file diff --git a/src/folderize.ts b/src/folderize.ts index 89bb44a..cb0356f 100644 --- a/src/folderize.ts +++ b/src/folderize.ts @@ -1,15 +1,15 @@ import * as vscode from 'vscode'; import { - mkdirSync, - readFileSync, - writeFileSync, - renameSync + mkdirSync, + readFileSync, + writeFileSync, + renameSync } from 'fs'; -import {Context, Option} from './types'; -import {getOptions} from './options'; -import {stringRegex, importRegex, firstEmptyLineRegex} from './regex'; -import {formatIndexFile, formatImportPath, formatCssImport} from './format'; +import { Context, Option } from './types'; +import { getOptions } from './options'; +import { stringRegex, importRegex, firstEmptyLineRegex } from './regex'; +import { formatIndexFile, formatImportPath, formatImportInComponent, formatImportComponent } from './format'; const createEmptyFolder = (c: Context) => { mkdirSync(c.folderPath); @@ -31,12 +31,15 @@ const updateLocalImports = (c: Context, o: Option[]) => { const newPath = match.replace(stringRegex, formatImportPath); return newPath; }); - const importCss = o.some(option => option.id === 'css_module'); - if (importCss) { - updatedText = updatedText.replace(firstEmptyLineRegex, () => { - return formatCssImport(c.fileName); - }); - } + + o.forEach(option => { + if (option.importInComponentName && option.createFile) { + updatedText = updatedText.replace(firstEmptyLineRegex, () => { + return formatImportInComponent(option.importInComponentName, option.createFile, c.fileName, c.filePath); + }); + } + }) + writeFileSync(c.newPath, updatedText); }; @@ -45,7 +48,9 @@ const createOptionalFiles = (c: Context, o: Option[]) => { if (option.createFile) { const name = option.createFile; const path = c.folderPath + '/' + name; - writeFileSync(path, ''); + + const fileContent = option.importComponent ? formatImportComponent(c.fileName) : ''; + writeFileSync(path, fileContent); } }); }; @@ -72,7 +77,7 @@ const showOptions = (context: Context) => { export const init = (event: vscode.Uri) => { const path = event.fsPath; const filePath = path.split('.').pop() || ''; - const folderPath = path.split('.').slice(0,-1).join('.'); + const folderPath = path.split('.').slice(0, -1).join('.'); const fileName = folderPath.split('/').pop() || ''; const newPath = folderPath + '/' + fileName + '.' + filePath; @@ -83,10 +88,10 @@ export const init = (event: vscode.Uri) => { fileName, newPath }; - - showOptions(context).then((selectedOptions) => { - if (selectedOptions) { - createFolder(context, selectedOptions); - } - }); + + showOptions(context).then((selectedOptions) => { + if (selectedOptions) { + createFolder(context, selectedOptions); + } + }); }; \ No newline at end of file diff --git a/src/format/format.ts b/src/format/format.ts index 7b0562a..aa729d5 100644 --- a/src/format/format.ts +++ b/src/format/format.ts @@ -1,18 +1,24 @@ +import { parsePath } from './../utils/utils'; + export const formatIndexFile = (name: string) => { return `export { default } from './${name}';\n`; }; -export const formatCssImport = (name: string) => { - return `\nimport styles from './${name}.module.css'\n\n`; +export const formatImportInComponent = (importName: string, path: string, name: string, ext: string) => { + return `\nimport ${importName} from './${parsePath(path, name, ext)}'\n\n`; +}; + +export const formatImportComponent = (name: string) => { + return `import ${name} from './${name}';\n`; }; export const formatImportPath = (s: string) => { - const prefix = s.slice(1,3); - switch(prefix) { - case('./'): - return s.substr(0,1) + '.' + s.substr(1); - case('..'): - return s.substr(0,1) + '../' + s.substr(1); + const prefix = s.slice(1, 3); + switch (prefix) { + case ('./'): + return s.substr(0, 1) + '.' + s.substr(1); + case ('..'): + return s.substr(0, 1) + '../' + s.substr(1); default: return s; } diff --git a/src/options/options.ts b/src/options/options.ts index 83da2bb..5ce0368 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -1,20 +1,44 @@ -import {Context} from '../types'; +import { parsePath } from './../utils/utils'; +import * as vscode from 'vscode'; +import { Context, Option } from '../types'; +import { checkOptionType } from '../utils'; +import { presets } from './presets'; + +const parseOption = (option: Option, c: Context) => { + const parsedOption = { ...option }; + + ['description', 'createFile'].forEach(attribute => { + if (!parsedOption[attribute]) return; + + parsedOption[attribute] = parsePath(parsedOption[attribute], c.fileName, c.filePath) + }) + + return parsedOption; +} export const getOptions = (c: Context) => { - return [ - { - 'id': 'test', - 'label': 'Add test file', - 'description': `${c.fileName}.test.${c.filePath}`, - 'createFile': `${c.fileName}.test.${c.filePath}` - }, - { - 'id': 'css_module', - 'label': 'Add CSS module', - 'description': `${c.fileName}.module.css`, - 'createFile': `${c.fileName}.module.css` + const filesToGenerateConfig = vscode.workspace.getConfiguration('folderize') + .get('optionalFilesToGenerate'); + + console.log(filesToGenerateConfig); + + const selectedPresets = filesToGenerateConfig.filter(fileConfig => typeof fileConfig === 'string'); + const addedOptions = filesToGenerateConfig.filter(checkOptionType).map(option => parseOption(option, c)); + + const presetOptions = presets.filter(({ id }) => selectedPresets.includes(id)) + .map(preset => parseOption(preset, c)); + + const options = [...addedOptions]; + + presetOptions.forEach(presetOption => { + const existingOptionIndex = options.findIndex(option => option.id === presetOption.id); + if (existingOptionIndex !== -1) { + options[existingOptionIndex] = presetOption; + } else { + options.push(presetOption); } - ]; -}; + }) + + return options; -// TODO: add optional congif file \ No newline at end of file +}; diff --git a/src/options/presets.ts b/src/options/presets.ts new file mode 100644 index 0000000..aeb5ef7 --- /dev/null +++ b/src/options/presets.ts @@ -0,0 +1,32 @@ +export const presets = [ + // TESTS + { + 'id': 'test', + 'label': 'Add test file', + 'description': '{fileName}.test.{ext}', + 'createFile': '{fileName}.test.{ext}', + 'importComponent': true + }, + { + 'id': 'spec', + 'label': 'Add test file (spec)', + 'description': '{fileName}.spec.{ext}', + 'createFile': '{fileName}.spec.{ext}', + 'importComponent': true + }, + // STYLES + { + 'id': 'css_module', + 'label': 'Add CSS module', + 'description': '{fileName}.module.css', + 'createFile': '{fileName}.module.css', + 'importInComponentName': 'styles' + }, + { + 'id': 'styles', + 'label': 'Add styles file', + 'description': '{fileName}.styles.{ext}', + 'createFile': '{fileName}.styles.{ext}', + 'importInComponentName': 'styles' + }, +] \ No newline at end of file diff --git a/src/types/types.ts b/src/types/types.ts index f894be7..546ce53 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -11,4 +11,6 @@ export type Option = { label: string, description: string, createFile?: string, + importInComponentName?: string, + importComponent?: boolean }; \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..038522d --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1 @@ +export * from './utils'; \ No newline at end of file diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 0000000..fb8a017 --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,12 @@ +import { Option } from './../types/types'; + +export const checkOptionType = (element: any): element is Option => { + if (!element) return false; + + return ['id', 'label', 'description', 'createFile'] + .every(name => !!element[name] && typeof (element[name]) === 'string'); +} + +export const parsePath = (path: string, fileName: string, ext: string) => { + return path.replace('{fileName}', fileName).replace('{ext}', ext); +} \ No newline at end of file From ddda92bc40c6a3dc0e9fe0e5b51961227844c5a0 Mon Sep 17 00:00:00 2001 From: Agustina Chaer Date: Tue, 18 Aug 2020 18:17:32 -0300 Subject: [PATCH 2/2] fix: import optional files in the component file not working --- src/folderize.ts | 13 +++++++++---- src/format/format.ts | 5 +++-- src/options/presets.ts | 2 +- src/regex/regex.ts | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/folderize.ts b/src/folderize.ts index cb0356f..f4b8c20 100644 --- a/src/folderize.ts +++ b/src/folderize.ts @@ -34,11 +34,16 @@ const updateLocalImports = (c: Context, o: Option[]) => { o.forEach(option => { if (option.importInComponentName && option.createFile) { - updatedText = updatedText.replace(firstEmptyLineRegex, () => { - return formatImportInComponent(option.importInComponentName, option.createFile, c.fileName, c.filePath); - }); + const lineToAdd = formatImportInComponent( + option.importInComponentName, + option.createFile, + c.fileName, + c.filePath, + !updatedText + ); + updatedText = !updatedText ? lineToAdd : updatedText.replace(firstEmptyLineRegex, lineToAdd); } - }) + }); writeFileSync(c.newPath, updatedText); }; diff --git a/src/format/format.ts b/src/format/format.ts index aa729d5..88cdf89 100644 --- a/src/format/format.ts +++ b/src/format/format.ts @@ -4,8 +4,9 @@ export const formatIndexFile = (name: string) => { return `export { default } from './${name}';\n`; }; -export const formatImportInComponent = (importName: string, path: string, name: string, ext: string) => { - return `\nimport ${importName} from './${parsePath(path, name, ext)}'\n\n`; +export const formatImportInComponent = (importName: string, path: string, name: string, ext: string, fileIsEmpty: boolean) => { + const importText = `import ${importName} from './${parsePath(path, name, ext)}'\n`; + return fileIsEmpty ? importText : `\n${importText}\n`; }; export const formatImportComponent = (name: string) => { diff --git a/src/options/presets.ts b/src/options/presets.ts index aeb5ef7..bfdcab9 100644 --- a/src/options/presets.ts +++ b/src/options/presets.ts @@ -29,4 +29,4 @@ export const presets = [ 'createFile': '{fileName}.styles.{ext}', 'importInComponentName': 'styles' }, -] \ No newline at end of file +]; diff --git a/src/regex/regex.ts b/src/regex/regex.ts index 46f3d50..cbd0a5e 100644 --- a/src/regex/regex.ts +++ b/src/regex/regex.ts @@ -1,4 +1,4 @@ export const importRegex = /(import[^'"]*|require\()('(.*?)'|"(.*?)")/g; export const exportRegex = /export /g; export const stringRegex = /('(.*?)'|"(.*?)")/; -export const firstEmptyLineRegex = /\n\n/; \ No newline at end of file +export const firstEmptyLineRegex = /\n\n/;