diff --git a/README.md b/README.md
index cf1dfb6..3fcb931 100644
--- a/README.md
+++ b/README.md
@@ -101,6 +101,40 @@ Accepts a comma-separated list of VS Code language identifiers.
Controls whether Props and State declarations use type aliases or interfaces in TypeScript snippets.
+### `typescriptPropsNaming`
+
+**Type:** `string`
+
+**Default:** `generic`
+
+**Allowed values:** `generic`, `component`
+
+Controls whether TypeScript component snippets name their props type `Props` or derive it from the component name, for example `ButtonProps`.
+
+### `componentNameSource`
+
+**Type:** `string`
+
+**Default:** `filename`
+
+**Allowed values:** `filename`, `directoryForIndex`
+
+Controls how component snippets derive their name. `directoryForIndex` uses the parent directory for an `index.*` file, so `Button/index.tsx` becomes `Button`; other filenames still use their basename.
+
+Either way the name is converted to PascalCase, so `delete-modal.tsx` becomes `DeleteModal`. Separators such as `-`, `_`, and `.` are dropped, and names that are already PascalCase are left unchanged.
+
+Redux slice snippets are not components, so they are only renamed when the filename would not be a valid identifier: `user-slice.ts` becomes `userSlice`, while `UserSlice.ts` is left alone.
+
+### `componentWrapper`
+
+**Type:** `string`
+
+**Default:** `fragment`
+
+**Allowed values:** `fragment`, `div`
+
+Controls whether generated web component content is wrapped in a React fragment or a `div`.
+
## Editor support
### VS Code · Cursor · VSCodium
diff --git a/bun.lock b/bun.lock
index 3295147..58623a0 100644
--- a/bun.lock
+++ b/bun.lock
@@ -16,7 +16,7 @@
},
},
"overrides": {
- "brace-expansion": "5.0.7",
+ "brace-expansion": "5.0.8",
"fast-uri": "3.1.4",
"form-data": "4.0.6",
"js-yaml": "4.3.0",
@@ -275,7 +275,7 @@
"boundary": ["boundary@2.0.0", "", {}, "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA=="],
- "brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
+ "brace-expansion": ["brace-expansion@5.0.8", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
diff --git a/docs/Snippets.md b/docs/Snippets.md
index 2bbc8ab..969fbcb 100644
--- a/docs/Snippets.md
+++ b/docs/Snippets.md
@@ -7,6 +7,8 @@ Snippet bodies preserve the spacing shown below; spaces inside braces and parent
_TypeScript_ has own components and own snippets. Use search or just type `ts` before every component snippet.
+Component snippets use the current filename by default. Set `componentNameSource` to `directoryForIndex` to use the parent directory for `index.*` files.
+
I.E. `tsrcc`
@@ -384,7 +386,7 @@ export default FileName;
```javascript
import { memo } from 'react';
-const FileName = memo(() => {
+const FileName = memo(function FileName() {
return <>$2>;
});
@@ -397,7 +399,7 @@ export default FileName;
import { memo } from 'react';
import PropTypes from 'prop-types';
-const FileName = memo((props) => {
+const FileName = memo(function FileName(props) {
return <>$2>;
});
@@ -479,7 +481,7 @@ const mapDispatchToProps = {};
## TypeScript Components
-All TypeScript component snippets use `type` for Props/State by default. Change to `interface` via the `typescriptPropsStatePrefix` setting.
+All TypeScript component snippets use `type` for Props/State by default. Change to `interface` via the `typescriptPropsStatePrefix` setting. Set `typescriptPropsNaming` to `component` to derive the Props type from the component name, for example `ButtonProps`.
| Prefix | Method |
| -----------: | ------------------------------------------------------- |
diff --git a/package.json b/package.json
index c89cebe..b83fb00 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,7 @@
"typescript": "6.0.3"
},
"overrides": {
- "brace-expansion": "5.0.7",
+ "brace-expansion": "5.0.8",
"fast-uri": "3.1.4",
"form-data": "4.0.6",
"js-yaml": "4.3.0",
@@ -121,6 +121,36 @@
"interface"
],
"scope": "application"
+ },
+ "reactSnippets.settings.typescriptPropsNaming": {
+ "type": "string",
+ "markdownDescription": "Controls whether TypeScript component snippets name their props type `Props` or derive it from the component name, for example `ButtonProps`.",
+ "default": "generic",
+ "enum": [
+ "generic",
+ "component"
+ ],
+ "scope": "application"
+ },
+ "reactSnippets.settings.componentNameSource": {
+ "type": "string",
+ "markdownDescription": "Controls whether component snippets use the filename or use the parent directory name for `index.*` files.",
+ "default": "filename",
+ "enum": [
+ "filename",
+ "directoryForIndex"
+ ],
+ "scope": "application"
+ },
+ "reactSnippets.settings.componentWrapper": {
+ "type": "string",
+ "markdownDescription": "Controls whether component snippets wrap their content in a React fragment or a `div`.",
+ "default": "fragment",
+ "enum": [
+ "fragment",
+ "div"
+ ],
+ "scope": "application"
}
}
},
diff --git a/src/helpers/buildSnippets.ts b/src/helpers/buildSnippets.ts
index 8bad867..4f91a03 100644
--- a/src/helpers/buildSnippets.ts
+++ b/src/helpers/buildSnippets.ts
@@ -167,7 +167,7 @@ const compileSnippet = (
): GeneratedSnippet => ({
prefix: snippet.prefix,
body: parseSnippetToBody(snippet, settings.importReactOnTop).map((line) =>
- replaceSnippetPlaceholders(line, settings.typescriptPropsStatePrefix),
+ replaceSnippetPlaceholders(line, settings),
),
...(snippet.description === undefined
? {}
diff --git a/src/helpers/extensionConfig.ts b/src/helpers/extensionConfig.ts
index 7cb6c6a..8349235 100644
--- a/src/helpers/extensionConfig.ts
+++ b/src/helpers/extensionConfig.ts
@@ -1,6 +1,12 @@
import { workspace } from 'vscode';
-import { DEFAULT_GENERATION_SETTINGS, GenerationSettings } from '../types';
+import {
+ ComponentNameSource,
+ ComponentWrapper,
+ DEFAULT_GENERATION_SETTINGS,
+ GenerationSettings,
+ TypescriptPropsNaming,
+} from '../types';
export type ExtensionSettings = GenerationSettings;
@@ -24,6 +30,18 @@ const extensionConfig = (): ExtensionSettings => {
'typescriptPropsStatePrefix',
DEFAULT_GENERATION_SETTINGS.typescriptPropsStatePrefix,
),
+ typescriptPropsNaming: config.get(
+ 'typescriptPropsNaming',
+ DEFAULT_GENERATION_SETTINGS.typescriptPropsNaming,
+ ),
+ componentNameSource: config.get(
+ 'componentNameSource',
+ DEFAULT_GENERATION_SETTINGS.componentNameSource,
+ ),
+ componentWrapper: config.get(
+ 'componentWrapper',
+ DEFAULT_GENERATION_SETTINGS.componentWrapper,
+ ),
};
};
diff --git a/src/helpers/snippetPlaceholders.ts b/src/helpers/snippetPlaceholders.ts
index 24856b8..472ada7 100644
--- a/src/helpers/snippetPlaceholders.ts
+++ b/src/helpers/snippetPlaceholders.ts
@@ -1,25 +1,59 @@
import { GenerationSettings, Mappings, Placeholders } from '../types';
+const propsPlaceholders = {
+ generic: {
+ type: Mappings.TypeProps,
+ interface: Mappings.InterfaceProps,
+ },
+ component: {
+ type: Mappings.ComponentTypeProps,
+ interface: Mappings.ComponentInterfaceProps,
+ },
+} as const;
+
+const componentNamePlaceholders = {
+ filename: Mappings.ComponentFileName,
+ directoryForIndex: Mappings.ComponentDirectoryNameForIndex,
+} as const;
+
export const replaceSnippetPlaceholders = (
snippetString: string,
- typescriptPropsStatePrefix: GenerationSettings['typescriptPropsStatePrefix'],
+ settings: GenerationSettings,
): string => {
+ const {
+ componentNameSource,
+ componentWrapper,
+ typescriptPropsNaming,
+ typescriptPropsStatePrefix,
+ } = settings;
const propsPlaceholder =
- typescriptPropsStatePrefix === 'type'
- ? Mappings.TypeProps
- : Mappings.InterfaceProps;
+ propsPlaceholders[typescriptPropsNaming][typescriptPropsStatePrefix];
+ const componentNamePlaceholder =
+ componentNamePlaceholders[componentNameSource];
+ const propsName =
+ typescriptPropsNaming === 'component'
+ ? Mappings.ComponentProps
+ : Mappings.Props;
const statePlaceholder =
typescriptPropsStatePrefix === 'type'
? Mappings.TypeState
: Mappings.InterfaceState;
+ const componentWrapperPlaceholder =
+ componentWrapper === 'fragment'
+ ? Mappings.FragmentComponentWrapper
+ : Mappings.DivComponentWrapper;
return String(snippetString)
+ .replaceAll(Placeholders.TypeProps, propsPlaceholder)
+ .replaceAll(Placeholders.TypeState, statePlaceholder)
+ .replaceAll(Placeholders.ComponentProps, propsName)
+ .replaceAll(Placeholders.ComponentName, componentNamePlaceholder)
+ .replaceAll(Placeholders.SliceName, Mappings.SliceFileName)
.replaceAll(Placeholders.FileName, Mappings.FileName)
+ .replaceAll(Placeholders.ComponentWrapper, componentWrapperPlaceholder)
.replaceAll(Placeholders.FirstTab, Mappings.FirstTab)
.replaceAll(Placeholders.SecondTab, Mappings.SecondTab)
.replaceAll(Placeholders.ThirdTab, Mappings.ThirdTab)
.replaceAll(Placeholders.Capitalize, Mappings.Capitalize)
- .replaceAll(Placeholders.TypeProps, propsPlaceholder)
- .replaceAll(Placeholders.TypeState, statePlaceholder)
.replaceAll(Placeholders.FourthTabCapitalize, Mappings.FourthTabCapitalize);
};
diff --git a/src/snippets/generated.code-snippets b/src/snippets/generated.code-snippets
index ba42746..369d765 100644
--- a/src/snippets/generated.code-snippets
+++ b/src/snippets/generated.code-snippets
@@ -22,7 +22,7 @@
"",
"type State = {}",
"",
- "export default class ${1:${TM_FILENAME_BASE}} extends Component {",
+ "export default class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends Component {",
" state = {}",
"",
" render() {",
@@ -44,7 +44,7 @@
"",
"type State = {}",
"",
- "class ${1:${TM_FILENAME_BASE}} extends Component {",
+ "class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends Component {",
" state = {}",
"",
" render() {",
@@ -54,7 +54,7 @@
" }",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "Creates a React component class with ES7 module system and TypeScript interfaces",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -64,13 +64,13 @@
"body": [
"type Props = {}",
"",
- "function ${1:${TM_FILENAME_BASE}}({}: Props) {",
+ "function ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}({}: Props) {",
" return (",
" <>${2:first}>",
" )",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "Creates a React Functional Component with ES7 module system and TypeScript interface",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -80,7 +80,7 @@
"body": [
"type Props = {}",
"",
- "export default function ${1:${TM_FILENAME_BASE}}({}: Props) {",
+ "export default function ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}({}: Props) {",
" return (",
" <>${2:first}>",
" )",
@@ -94,13 +94,13 @@
"body": [
"type Props = {}",
"",
- "const ${1:${TM_FILENAME_BASE}} = (props: Props) => {",
+ "const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = (props: Props) => {",
" return (",
" <>${2:first}>",
" )",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "Creates a React Arrow Function Component with ES7 module system and TypeScript interface",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -110,7 +110,7 @@
"body": [
"type Props = {}",
"",
- "export const ${1:${TM_FILENAME_BASE}} = (props: Props) => {",
+ "export const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = (props: Props) => {",
" return (",
" <>${2:first}>",
" )",
@@ -126,7 +126,7 @@
"",
"type Props = {}",
"",
- "export default class ${1:${TM_FILENAME_BASE}} extends PureComponent {",
+ "export default class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends PureComponent {",
" render() {",
" return (",
" <>${2:first}>",
@@ -144,7 +144,7 @@
"",
"type Props = {}",
"",
- "class ${1:${TM_FILENAME_BASE}} extends PureComponent {",
+ "class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends PureComponent {",
" render() {",
" return (",
" <>${2:first}>",
@@ -152,7 +152,7 @@
" }",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "Creates a React pure component class with ES7 module system and TypeScript interface",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -167,7 +167,7 @@
"",
"type State = {}",
"",
- "export class ${1:${TM_FILENAME_BASE}} extends Component {",
+ "export class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends Component {",
" state = {}",
"",
" render() {",
@@ -181,7 +181,7 @@
"",
"const mapDispatchToProps = {}",
"",
- "export default connect(mapStateToProps, mapDispatchToProps)(${1:${TM_FILENAME_BASE}})"
+ "export default connect(mapStateToProps, mapDispatchToProps)(${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}})"
],
"description": "Creates a React component class with connected redux and ES7 module system and TypeScript interfaces",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -192,7 +192,7 @@
"import { View, Text } from 'react-native'",
"type Props = {}",
"",
- "const ${1:${TM_FILENAME_BASE}} = (props: Props) => {",
+ "const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = (props: Props) => {",
" return (",
" ",
" ${2:first}",
@@ -200,7 +200,7 @@
" )",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "Creates a React Native Arrow Function Component with ES7 module system in TypeScript",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -211,7 +211,7 @@
"import { StyleSheet, Text, View } from 'react-native'",
"type Props = {}",
"",
- "const ${1:${TM_FILENAME_BASE}} = (props: Props) => {",
+ "const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = (props: Props) => {",
" return (",
" ",
" ${2:first}",
@@ -219,7 +219,7 @@
" )",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}",
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}",
"",
"const styles = StyleSheet.create({})"
],
@@ -241,7 +241,7 @@
" ${3:second}",
"}",
"",
- "function ${1:${TM_FILENAME_BASE}}() {",
+ "function ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}() {",
" const data = useLoaderData()",
"",
" return (",
@@ -250,7 +250,7 @@
"}",
"",
"export { loader, action }",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "React Router v6.4+ TypeScript route with loader and action",
"scope": "typescriptreact"
@@ -258,7 +258,7 @@
"reactArrowFunctionComponent": {
"prefix": "rafc",
"body": [
- "export const ${1:${TM_FILENAME_BASE}} = () => {",
+ "export const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = () => {",
" return (",
" <>${2:first}>",
" )",
@@ -273,15 +273,15 @@
"body": [
"import PropTypes from 'prop-types'",
"",
- "const ${1:${TM_FILENAME_BASE}} = props => {",
+ "const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = props => {",
" return (",
" <>${2:first}>",
" )",
"}",
"",
- "${1:${TM_FILENAME_BASE}}.propTypes = {}",
+ "${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}.propTypes = {}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "Creates a React Arrow Function Component with PropTypes (React 17-18; ignored by React 19)",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -289,13 +289,13 @@
"reactArrowFunctionExportComponent": {
"prefix": "rafce",
"body": [
- "const ${1:${TM_FILENAME_BASE}} = () => {",
+ "const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = () => {",
" return (",
" <>${2:first}>",
" )",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "Creates a React Arrow Function Component with ES7 module system",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -305,7 +305,7 @@
"body": [
"import { Component } from 'react'",
"",
- "export default class ${1:${TM_FILENAME_BASE}} extends Component {",
+ "export default class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends Component {",
" render() {",
" return (",
" <>${2:first}>",
@@ -323,7 +323,7 @@
"import PropTypes from 'prop-types'",
"import { Component } from 'react'",
"",
- "export default class ${1:${TM_FILENAME_BASE}} extends Component {",
+ "export default class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends Component {",
" static propTypes = {${3:second}: ${4:third}}",
"",
" render() {",
@@ -343,7 +343,7 @@
"import { Component } from 'react'",
"import { connect } from 'react-redux'",
"",
- "export class ${1:${TM_FILENAME_BASE}} extends Component {",
+ "export class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends Component {",
" render() {",
" return (",
" <>${2:first}>",
@@ -355,7 +355,7 @@
"",
"const mapDispatchToProps = {}",
"",
- "export default connect(mapStateToProps, mapDispatchToProps)(${1:${TM_FILENAME_BASE}})"
+ "export default connect(mapStateToProps, mapDispatchToProps)(${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}})"
],
"description": "Creates a React component class with connected redux and ES7 module system",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -367,7 +367,7 @@
"import { Component } from 'react'",
"import { connect } from 'react-redux'",
"",
- "export class ${1:${TM_FILENAME_BASE}} extends Component {",
+ "export class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends Component {",
" static propTypes = {",
" ${3:second}: ${4:third}",
" }",
@@ -383,7 +383,7 @@
"",
"const mapDispatchToProps = {}",
"",
- "export default connect(mapStateToProps, mapDispatchToProps)(${1:${TM_FILENAME_BASE}})"
+ "export default connect(mapStateToProps, mapDispatchToProps)(${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}})"
],
"description": "Creates a React component class with PropTypes with connected redux and ES7 module system",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -393,7 +393,7 @@
"body": [
"import { Component } from 'react'",
"",
- "export class ${1:${TM_FILENAME_BASE}} extends Component {",
+ "export class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends Component {",
" render() {",
" return (",
" <>${2:first}>",
@@ -401,7 +401,7 @@
" }",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "Creates a React component class with ES7 module system",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -412,7 +412,7 @@
"import PropTypes from 'prop-types'",
"import { Component } from 'react'",
"",
- "export class ${1:${TM_FILENAME_BASE}} extends Component {",
+ "export class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends Component {",
" static propTypes = {}",
"",
" render() {",
@@ -422,7 +422,7 @@
" }",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "Creates a React component class with ES7 module system",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -432,7 +432,7 @@
"body": [
"import { PureComponent } from 'react'",
"",
- "export class ${1:${TM_FILENAME_BASE}} extends PureComponent {",
+ "export class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends PureComponent {",
" render() {",
" return (",
" <>${2:first}>",
@@ -440,7 +440,7 @@
" }",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "Creates a React pure component class with ES7 module system export",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -450,7 +450,7 @@
"body": [
"import { PureComponent } from 'react'",
"",
- "export default class ${1:${TM_FILENAME_BASE}} extends PureComponent {",
+ "export default class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends PureComponent {",
" render() {",
" return (",
" <>${2:first}>",
@@ -468,7 +468,7 @@
"import PropTypes from 'prop-types'",
"import { PureComponent } from 'react'",
"",
- "export default class ${1:${TM_FILENAME_BASE}} extends PureComponent {",
+ "export default class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends PureComponent {",
" static propTypes = {}",
"",
" render() {",
@@ -487,13 +487,13 @@
"body": [
"import { memo } from 'react'",
"",
- "const ${1:${TM_FILENAME_BASE}} = memo(() => {",
+ "const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = memo(function ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}() {",
" return (",
" <>${2:first}>",
" )",
"})",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "Creates a React Memo Function Component with ES7 module system",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -504,15 +504,15 @@
"import PropTypes from 'prop-types'",
"import { memo } from 'react'",
"",
- "const ${1:${TM_FILENAME_BASE}} = memo((props) => {",
+ "const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = memo(function ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}(props) {",
" return (",
" <>${2:first}>",
" )",
"})",
"",
- "${1:${TM_FILENAME_BASE}}.propTypes = {}",
+ "${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}.propTypes = {}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "Creates a React Memo Function Component with PropTypes (React 17-18; ignored by React 19)",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -520,7 +520,7 @@
"reactFunctionalComponent": {
"prefix": "rfc",
"body": [
- "export default function ${1:${TM_FILENAME_BASE}}() {",
+ "export default function ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}() {",
" return (",
" <>${2:first}>",
" )",
@@ -535,7 +535,7 @@
"body": [
"import { connect } from 'react-redux'",
"",
- "export const ${1:${TM_FILENAME_BASE}} = (props) => {",
+ "export const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = (props) => {",
" return (",
" <>${2:first}>",
" )",
@@ -545,7 +545,7 @@
"",
"const mapDispatchToProps = {}",
"",
- "export default connect(mapStateToProps, mapDispatchToProps)(${1:${TM_FILENAME_BASE}})"
+ "export default connect(mapStateToProps, mapDispatchToProps)(${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}})"
],
"description": "Creates a React functional component with connected redux and ES7 module system",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -556,13 +556,13 @@
"import PropTypes from 'prop-types'",
"import { connect } from 'react-redux'",
"",
- "export const ${1:${TM_FILENAME_BASE}} = (props) => {",
+ "export const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = (props) => {",
" return (",
" <>${2:first}>",
" )",
"}",
"",
- "${1:${TM_FILENAME_BASE}}.propTypes = {",
+ "${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}.propTypes = {",
" ${3:second}: PropTypes.${4:third}",
"}",
"",
@@ -570,7 +570,7 @@
"",
"const mapDispatchToProps = {}",
"",
- "export default connect(mapStateToProps, mapDispatchToProps)(${1:${TM_FILENAME_BASE}})"
+ "export default connect(mapStateToProps, mapDispatchToProps)(${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}})"
],
"description": "DEPRECATED: React Redux function component with PropTypes (React 17-18; ignored by React 19)",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -580,15 +580,15 @@
"body": [
"import PropTypes from 'prop-types'",
"",
- "function ${1:${TM_FILENAME_BASE}}(props) {",
+ "function ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}(props) {",
" return (",
" <>${2:first}>",
" )",
"}",
"",
- "${1:${TM_FILENAME_BASE}}.propTypes = {}",
+ "${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}.propTypes = {}",
"",
- "export default ${1:${TM_FILENAME_BASE}}",
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}",
""
],
"description": "Creates a React Functional Component with PropTypes (React 17-18; ignored by React 19)",
@@ -597,13 +597,13 @@
"reactFunctionalExportComponent": {
"prefix": "rfce",
"body": [
- "function ${1:${TM_FILENAME_BASE}}() {",
+ "function ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}() {",
" return (",
" <>${2:first}>",
" )",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "Creates a React Functional Component with ES7 module system",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -1360,7 +1360,7 @@
"import { Text, View } from 'react-native'",
"import { Component } from 'react'",
"",
- "export default class ${1:${TM_FILENAME_BASE}} extends Component {",
+ "export default class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends Component {",
" render() {",
" return (",
" ",
@@ -1378,7 +1378,7 @@
"import { Text, View } from 'react-native'",
"import { Component } from 'react'",
"",
- "export class ${1:${TM_FILENAME_BASE}} extends Component {",
+ "export class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends Component {",
" render() {",
" return (",
" ",
@@ -1388,7 +1388,7 @@
" }",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"scope": "typescript,typescriptreact,javascript,javascriptreact"
},
@@ -1398,7 +1398,7 @@
"import { Text, StyleSheet, View } from 'react-native'",
"import { Component } from 'react'",
"",
- "export default class ${1:${TM_FILENAME_BASE}} extends Component {",
+ "export default class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends Component {",
" render() {",
" return (",
" ",
@@ -1418,7 +1418,7 @@
"import { useState, useEffect } from 'react'",
"import { View, SafeAreaView, Text, StyleSheet } from 'react-native'",
"",
- "const ${1:${TM_FILENAME_BASE}} = ({ ${2:props} }) => {",
+ "const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = ({ ${2:props} }) => {",
" const [${3:state}, ${4:setState}] = useState(${5:initialState})",
"",
" useEffect(() => {",
@@ -1442,7 +1442,7 @@
" },",
"})",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "React Native functional component with useState, useEffect, SafeAreaView and basic styling",
"scope": "typescript,typescriptreact,javascript,javascriptreact"
@@ -1451,7 +1451,7 @@
"prefix": "rnf",
"body": [
"import { View, Text } from 'react-native'",
- "export default function ${1:${TM_FILENAME_BASE}}() {",
+ "export default function ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}() {",
" return (",
" ",
" ${2:first}",
@@ -1465,7 +1465,7 @@
"prefix": "rnfs",
"body": [
"import { StyleSheet, Text, View } from 'react-native'",
- "export default function ${1:${TM_FILENAME_BASE}}() {",
+ "export default function ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}() {",
" return (",
" ",
" ${2:first}",
@@ -1481,7 +1481,7 @@
"prefix": "rnfe",
"body": [
"import { View, Text } from 'react-native'",
- "const ${1:${TM_FILENAME_BASE}} = () => {",
+ "const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = () => {",
" return (",
" ",
" ${2:first}",
@@ -1489,7 +1489,7 @@
" )",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"scope": "typescript,typescriptreact,javascript,javascriptreact"
},
@@ -1497,7 +1497,7 @@
"prefix": "rnfes",
"body": [
"import { StyleSheet, Text, View } from 'react-native'",
- "const ${1:${TM_FILENAME_BASE}} = () => {",
+ "const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = () => {",
" return (",
" ",
" ${2:first}",
@@ -1505,7 +1505,7 @@
" )",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}",
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}",
"",
"const styles = StyleSheet.create({})"
],
@@ -1524,7 +1524,7 @@
"import { Text, View } from 'react-native'",
"import { PureComponent } from 'react'",
"",
- "export default class ${1:${TM_FILENAME_BASE}} extends PureComponent {",
+ "export default class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends PureComponent {",
" render() {",
" return (",
" ",
@@ -1542,7 +1542,7 @@
"import { Text, View } from 'react-native'",
"import { PureComponent } from 'react'",
"",
- "export class ${1:${TM_FILENAME_BASE}} extends PureComponent {",
+ "export class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} extends PureComponent {",
" render() {",
" return (",
" ",
@@ -1552,7 +1552,7 @@
" }",
"}",
"",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"scope": "typescript,typescriptreact,javascript,javascriptreact"
},
@@ -1655,15 +1655,15 @@
"",
"}",
"",
- "const ${1:${TM_FILENAME_BASE}} = createSlice({",
+ "const ${1:${TM_FILENAME_BASE/^(?:([A-Za-z_$][A-Za-z0-9_$]*)|(.*))$/${1}${2:/camelcase}/}} = createSlice({",
" name: ${3:second},",
" initialState,",
" reducers: {}",
"});",
"",
- "export const {} = ${1:${TM_FILENAME_BASE}}.actions",
+ "export const {} = ${1:${TM_FILENAME_BASE/^(?:([A-Za-z_$][A-Za-z0-9_$]*)|(.*))$/${1}${2:/camelcase}/}}.actions",
"",
- "export default ${1:${TM_FILENAME_BASE}}.reducer"
+ "export default ${1:${TM_FILENAME_BASE/^(?:([A-Za-z_$][A-Za-z0-9_$]*)|(.*))$/${1}${2:/camelcase}/}}.reducer"
],
"scope": "typescript,typescriptreact,javascript,javascriptreact"
},
@@ -1677,7 +1677,7 @@
" status: 'idle',",
"}",
"",
- "const ${1:${TM_FILENAME_BASE}} = createSlice({",
+ "const ${1:${TM_FILENAME_BASE/^(?:([A-Za-z_$][A-Za-z0-9_$]*)|(.*))$/${1}${2:/camelcase}/}} = createSlice({",
" name: '${3:second}',",
" initialState,",
" reducers: {},",
@@ -1695,9 +1695,9 @@
" },",
"})",
"",
- "export const {} = ${1:${TM_FILENAME_BASE}}.actions",
+ "export const {} = ${1:${TM_FILENAME_BASE/^(?:([A-Za-z_$][A-Za-z0-9_$]*)|(.*))$/${1}${2:/camelcase}/}}.actions",
"",
- "export default ${1:${TM_FILENAME_BASE}}.reducer"
+ "export default ${1:${TM_FILENAME_BASE/^(?:([A-Za-z_$][A-Za-z0-9_$]*)|(.*))$/${1}${2:/camelcase}/}}.reducer"
],
"scope": "typescript,typescriptreact,javascript,javascriptreact"
},
@@ -2225,7 +2225,7 @@
" ${3:second}",
"}",
"",
- "function ${1:${TM_FILENAME_BASE}}() {",
+ "function ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}() {",
" const data = useLoaderData()",
"",
" return (",
@@ -2234,7 +2234,7 @@
"}",
"",
"export { loader, action }",
- "export default ${1:${TM_FILENAME_BASE}}"
+ "export default ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}"
],
"description": "React Router v6.4+ JavaScript route with loader and action",
"scope": "javascriptreact"
diff --git a/src/sourceSnippets/components.ts b/src/sourceSnippets/components.ts
index 761a5cb..02d8125 100644
--- a/src/sourceSnippets/components.ts
+++ b/src/sourceSnippets/components.ts
@@ -44,7 +44,7 @@ const reactClassComponent: ComponentsSnippet = {
body: [
...reactComponent,
'',
- `export default class ${Placeholders.FileName} extends Component {`,
+ `export default class ${Placeholders.ComponentName} extends Component {`,
...innerComponentReturn,
'}',
'',
@@ -58,7 +58,7 @@ const reactClassExportComponent: ComponentsSnippet = {
body: [
...reactComponent,
'',
- `export class ${Placeholders.FileName} extends Component {`,
+ `export class ${Placeholders.ComponentName} extends Component {`,
...innerComponentReturn,
'}',
...exportDefault,
@@ -72,7 +72,7 @@ const reactFunctionalExportComponent: ComponentsSnippet = {
body: [
...react,
'',
- `function ${Placeholders.FileName}() {`,
+ `function ${Placeholders.ComponentName}() {`,
...innerComponent,
'}',
...exportDefault,
@@ -86,7 +86,7 @@ const reactFunctionalComponent: ComponentsSnippet = {
body: [
...react,
'',
- `export default function ${Placeholders.FileName}() {`,
+ `export default function ${Placeholders.ComponentName}() {`,
...innerComponent,
'}',
'',
@@ -100,11 +100,11 @@ const reactFunctionalComponentWithPropTypes: ComponentsSnippet = {
body: [
...reactPropTypes,
'',
- `function ${Placeholders.FileName}(props) {`,
+ `function ${Placeholders.ComponentName}(props) {`,
...innerComponent,
'}',
'',
- `${Placeholders.FileName}.propTypes = {}`,
+ `${Placeholders.ComponentName}.propTypes = {}`,
...exportDefault,
'',
],
@@ -118,7 +118,7 @@ const reactArrowFunctionExportComponent: ComponentsSnippet = {
body: [
...react,
'',
- `const ${Placeholders.FileName} = () => {`,
+ `const ${Placeholders.ComponentName} = () => {`,
...innerComponent,
'}',
...exportDefault,
@@ -133,7 +133,7 @@ const reactArrowFunctionComponent: ComponentsSnippet = {
body: [
...react,
'',
- `export const ${Placeholders.FileName} = () => {`,
+ `export const ${Placeholders.ComponentName} = () => {`,
...innerComponent,
'}',
'',
@@ -148,11 +148,11 @@ const reactArrowFunctionComponentWithPropTypes: ComponentsSnippet = {
body: [
...reactPropTypes,
'',
- `const ${Placeholders.FileName} = props => {`,
+ `const ${Placeholders.ComponentName} = props => {`,
...innerComponent,
'}',
'',
- `${Placeholders.FileName}.propTypes = {}`,
+ `${Placeholders.ComponentName}.propTypes = {}`,
...exportDefault,
],
description:
@@ -166,7 +166,7 @@ const reactClassExportComponentWithPropTypes: ComponentsSnippet = {
"import PropTypes from 'prop-types'",
...reactComponent,
'',
- `export class ${Placeholders.FileName} extends Component {`,
+ `export class ${Placeholders.ComponentName} extends Component {`,
' static propTypes = {}',
'',
...innerComponentReturn,
@@ -182,7 +182,7 @@ const reactClassPureComponent: ComponentsSnippet = {
body: [
...reactPureComponent,
'',
- `export default class ${Placeholders.FileName} extends PureComponent {`,
+ `export default class ${Placeholders.ComponentName} extends PureComponent {`,
...innerComponentReturn,
'}',
'',
@@ -196,7 +196,7 @@ const reactClassExportPureComponent: ComponentsSnippet = {
body: [
...reactPureComponent,
'',
- `export class ${Placeholders.FileName} extends PureComponent {`,
+ `export class ${Placeholders.ComponentName} extends PureComponent {`,
...innerComponentReturn,
'}',
...exportDefault,
@@ -212,7 +212,7 @@ const reactClassPureComponentWithPropTypes: ComponentsSnippet = {
"import PropTypes from 'prop-types'",
...reactPureComponent,
'',
- `export default class ${Placeholders.FileName} extends PureComponent {`,
+ `export default class ${Placeholders.ComponentName} extends PureComponent {`,
' static propTypes = {}',
'',
...innerComponentReturn,
@@ -228,7 +228,7 @@ const reactFunctionMemoComponent: ComponentsSnippet = {
body: [
...reactWithMemo,
'',
- `const ${Placeholders.FileName} = memo(() => {`,
+ `const ${Placeholders.ComponentName} = memo(function ${Placeholders.ComponentName}() {`,
...innerComponent,
'})',
...exportDefault,
@@ -243,11 +243,11 @@ const reactFunctionMemoComponentWithPropTypes: ComponentsSnippet = {
"import PropTypes from 'prop-types'",
...reactWithMemo,
'',
- `const ${Placeholders.FileName} = memo((props) => {`,
+ `const ${Placeholders.ComponentName} = memo(function ${Placeholders.ComponentName}(props) {`,
...innerComponent,
'})',
'',
- `${Placeholders.FileName}.propTypes = {}`,
+ `${Placeholders.ComponentName}.propTypes = {}`,
...exportDefault,
],
description:
@@ -261,7 +261,7 @@ const reactClassComponentPropTypes: ComponentsSnippet = {
"import PropTypes from 'prop-types'",
...reactComponent,
'',
- `export default class ${Placeholders.FileName} extends Component {`,
+ `export default class ${Placeholders.ComponentName} extends Component {`,
` static propTypes = {${Placeholders.SecondTab}: ${Placeholders.ThirdTab}}`,
'',
...innerComponentReturn,
@@ -278,7 +278,7 @@ const reactClassComponentRedux: ComponentsSnippet = {
body: [
...reactComponentWithReduxConnect,
'',
- `export class ${Placeholders.FileName} extends Component {`,
+ `export class ${Placeholders.ComponentName} extends Component {`,
...innerComponentReturn,
'}',
...reduxComponentExport,
@@ -294,7 +294,7 @@ const reactClassComponentReduxPropTypes: ComponentsSnippet = {
"import PropTypes from 'prop-types'",
...reactComponentWithReduxConnect,
'',
- `export class ${Placeholders.FileName} extends Component {`,
+ `export class ${Placeholders.ComponentName} extends Component {`,
' static propTypes = {',
` ${Placeholders.SecondTab}: ${Placeholders.ThirdTab}`,
' }',
@@ -313,7 +313,7 @@ const reactFunctionalComponentRedux: ComponentsSnippet = {
body: [
...reactWithReduxConnect,
'',
- `export const ${Placeholders.FileName} = (props) => {`,
+ `export const ${Placeholders.ComponentName} = (props) => {`,
...innerComponent,
'}',
...reduxComponentExport,
@@ -329,11 +329,11 @@ const reactFunctionalComponentReduxPropTypes: ComponentsSnippet = {
"import PropTypes from 'prop-types'",
...reactWithReduxConnect,
'',
- `export const ${Placeholders.FileName} = (props) => {`,
+ `export const ${Placeholders.ComponentName} = (props) => {`,
...innerComponent,
'}',
'',
- `${Placeholders.FileName}.propTypes = {`,
+ `${Placeholders.ComponentName}.propTypes = {`,
` ${Placeholders.SecondTab}: PropTypes.${Placeholders.ThirdTab}`,
'}',
...reduxComponentExport,
diff --git a/src/sourceSnippets/others.ts b/src/sourceSnippets/others.ts
index d14a720..8023ef1 100644
--- a/src/sourceSnippets/others.ts
+++ b/src/sourceSnippets/others.ts
@@ -454,7 +454,7 @@ const routeWithLoaderAction: OthersSnippet = {
` ${Placeholders.SecondTab}`,
'}',
'',
- `function ${Placeholders.FileName}() {`,
+ `function ${Placeholders.ComponentName}() {`,
' const data = useLoaderData()',
'',
' return (',
@@ -463,7 +463,7 @@ const routeWithLoaderAction: OthersSnippet = {
'}',
'',
`export { loader, action }`,
- `export default ${Placeholders.FileName}`,
+ `export default ${Placeholders.ComponentName}`,
],
description: 'React Router v6.4+ JavaScript route with loader and action',
scope: 'javascriptreact',
diff --git a/src/sourceSnippets/reactNative.ts b/src/sourceSnippets/reactNative.ts
index ac06f4a..f529770 100644
--- a/src/sourceSnippets/reactNative.ts
+++ b/src/sourceSnippets/reactNative.ts
@@ -62,7 +62,7 @@ const reactNativeComponent: ReactNativeSnippet = {
"import { Text, View } from 'react-native'",
...reactComponent,
'',
- `export default class ${Placeholders.FileName} extends Component {`,
+ `export default class ${Placeholders.ComponentName} extends Component {`,
...reactNativeComponentReturn,
'}',
],
@@ -75,7 +75,7 @@ const reactNativeComponentWithStyles: ReactNativeSnippet = {
"import { Text, StyleSheet, View } from 'react-native'",
...reactComponent,
'',
- `export default class ${Placeholders.FileName} extends Component {`,
+ `export default class ${Placeholders.ComponentName} extends Component {`,
...reactNativeComponentReturn,
'}',
'',
@@ -90,7 +90,7 @@ const reactNativeComponentExport: ReactNativeSnippet = {
"import { Text, View } from 'react-native'",
...reactComponent,
'',
- `export class ${Placeholders.FileName} extends Component {`,
+ `export class ${Placeholders.ComponentName} extends Component {`,
...reactNativeComponentReturn,
'}',
...exportDefault,
@@ -104,7 +104,7 @@ const reactNativePureComponent: ReactNativeSnippet = {
"import { Text, View } from 'react-native'",
...reactPureComponent,
'',
- `export default class ${Placeholders.FileName} extends PureComponent {`,
+ `export default class ${Placeholders.ComponentName} extends PureComponent {`,
...reactNativeComponentReturn,
'}',
],
@@ -117,7 +117,7 @@ const reactNativePureComponentExport: ReactNativeSnippet = {
"import { Text, View } from 'react-native'",
...reactPureComponent,
'',
- `export class ${Placeholders.FileName} extends PureComponent {`,
+ `export class ${Placeholders.ComponentName} extends PureComponent {`,
...reactNativeComponentReturn,
'}',
...exportDefault,
@@ -131,7 +131,7 @@ const reactNativeFunctionalExportComponent: ReactNativeSnippet = {
"import { View, Text } from 'react-native'",
...react,
'',
- `const ${Placeholders.FileName} = () => {`,
+ `const ${Placeholders.ComponentName} = () => {`,
...reactNativeReturn,
'}',
...exportDefault,
@@ -145,7 +145,7 @@ const reactNativeFunctionalExportComponentWithStyles: ReactNativeSnippet = {
"import { StyleSheet, Text, View } from 'react-native'",
...react,
'',
- `const ${Placeholders.FileName} = () => {`,
+ `const ${Placeholders.ComponentName} = () => {`,
...reactNativeReturn,
'}',
...exportDefault,
@@ -161,7 +161,7 @@ const reactNativeFunctionalComponent: ReactNativeSnippet = {
"import { View, Text } from 'react-native'",
...react,
'',
- `export default function ${Placeholders.FileName}() {`,
+ `export default function ${Placeholders.ComponentName}() {`,
...reactNativeReturn,
'}',
],
@@ -174,7 +174,7 @@ const reactNativeFunctionalComponentWithStyles: ReactNativeSnippet = {
"import { StyleSheet, Text, View } from 'react-native'",
...react,
'',
- `export default function ${Placeholders.FileName}() {`,
+ `export default function ${Placeholders.ComponentName}() {`,
...reactNativeReturn,
'}',
'',
@@ -189,7 +189,7 @@ const reactNativeExtendedFunctional: ReactNativeSnippet = {
"import React, { useState, useEffect } from 'react'",
"import { View, SafeAreaView, Text, StyleSheet } from 'react-native'",
'',
- `const ${Placeholders.FileName} = ({ \${2:props} }) => {`,
+ `const ${Placeholders.ComponentName} = ({ \${2:props} }) => {`,
' const [${3:state}, ${4:setState}] = useState(${5:initialState})',
'',
' useEffect(() => {',
@@ -213,7 +213,7 @@ const reactNativeExtendedFunctional: ReactNativeSnippet = {
' },',
'})',
'',
- `export default ${Placeholders.FileName}`,
+ `export default ${Placeholders.ComponentName}`,
],
description:
'React Native functional component with useState, useEffect, SafeAreaView and basic styling',
diff --git a/src/sourceSnippets/redux.ts b/src/sourceSnippets/redux.ts
index a4ee4a5..b3a79e7 100644
--- a/src/sourceSnippets/redux.ts
+++ b/src/sourceSnippets/redux.ts
@@ -79,15 +79,15 @@ const reduxSlice: ReduxSnippet = {
'',
'}',
'',
- `const ${Placeholders.FileName} = createSlice({`,
+ `const ${Placeholders.SliceName} = createSlice({`,
` name: ${Placeholders.SecondTab},`,
' initialState,',
' reducers: {}',
'});',
'',
- `export const {} = ${Placeholders.FileName}.actions`,
+ `export const {} = ${Placeholders.SliceName}.actions`,
'',
- `export default ${Placeholders.FileName}.reducer`,
+ `export default ${Placeholders.SliceName}.reducer`,
],
};
@@ -102,7 +102,7 @@ const reduxSliceWithExtraReducers: ReduxSnippet = {
" status: 'idle',",
'}',
'',
- `const ${Placeholders.FileName} = createSlice({`,
+ `const ${Placeholders.SliceName} = createSlice({`,
` name: '${Placeholders.SecondTab}',`,
' initialState,',
' reducers: {},',
@@ -120,9 +120,9 @@ const reduxSliceWithExtraReducers: ReduxSnippet = {
' },',
'})',
'',
- `export const {} = ${Placeholders.FileName}.actions`,
+ `export const {} = ${Placeholders.SliceName}.actions`,
'',
- `export default ${Placeholders.FileName}.reducer`,
+ `export default ${Placeholders.SliceName}.reducer`,
],
};
diff --git a/src/sourceSnippets/sharedSnippets.ts b/src/sourceSnippets/sharedSnippets.ts
index f4b4b50..8c93d36 100644
--- a/src/sourceSnippets/sharedSnippets.ts
+++ b/src/sourceSnippets/sharedSnippets.ts
@@ -28,7 +28,7 @@ export const reduxComponentExport = [
'',
'const mapDispatchToProps = {}',
'',
- `export default connect(mapStateToProps, mapDispatchToProps)(${Placeholders.FileName})`,
+ `export default connect(mapStateToProps, mapDispatchToProps)(${Placeholders.ComponentName})`,
];
export const reduxTypeScriptComponentExport = [
@@ -37,24 +37,27 @@ export const reduxTypeScriptComponentExport = [
'',
'const mapDispatchToProps = {}',
'',
- `export default connect(mapStateToProps, mapDispatchToProps)(${Placeholders.FileName})`,
+ `export default connect(mapStateToProps, mapDispatchToProps)(${Placeholders.ComponentName})`,
];
export const innerComponent = [
' return (',
- ` <>${Placeholders.FirstTab}>`,
+ ` ${Placeholders.ComponentWrapper}`,
' )',
];
export const innerComponentReturn = [
' render() {',
' return (',
- ` <>${Placeholders.FirstTab}>`,
+ ` ${Placeholders.ComponentWrapper}`,
' )',
' }',
];
-export const exportDefault = ['', `export default ${Placeholders.FileName}`];
+export const exportDefault = [
+ '',
+ `export default ${Placeholders.ComponentName}`,
+];
export const propsTypeInterface = [Placeholders.TypeProps, ''];
export const stateTypeInterface = [Placeholders.TypeState, ''];
diff --git a/src/sourceSnippets/typescript.ts b/src/sourceSnippets/typescript.ts
index f9a1492..eca9201 100644
--- a/src/sourceSnippets/typescript.ts
+++ b/src/sourceSnippets/typescript.ts
@@ -54,7 +54,7 @@ const typescriptReactClassComponent: TypescriptSnippet = {
...reactComponent,
'',
...propsStateInterface,
- `export default class ${Placeholders.FileName} extends Component {`,
+ `export default class ${Placeholders.ComponentName} extends Component<${Placeholders.ComponentProps}, State> {`,
' state = {}',
'',
...innerComponentReturn,
@@ -69,7 +69,7 @@ const typescriptReactClassExportComponent: TypescriptSnippet = {
...reactComponent,
'',
...propsStateInterface,
- `class ${Placeholders.FileName} extends Component {`,
+ `class ${Placeholders.ComponentName} extends Component<${Placeholders.ComponentProps}, State> {`,
' state = {}',
'',
...innerComponentReturn,
@@ -87,7 +87,7 @@ const typescriptReactFunctionalExportComponent: TypescriptSnippet = {
...react,
'',
...propsTypeInterface,
- `function ${Placeholders.FileName}({}: Props) {`,
+ `function ${Placeholders.ComponentName}({}: ${Placeholders.ComponentProps}) {`,
...innerComponent,
'}',
...exportDefault,
@@ -103,7 +103,7 @@ const typescriptReactFunctionalComponent: TypescriptSnippet = {
...react,
'',
...propsTypeInterface,
- `export default function ${Placeholders.FileName}({}: Props) {`,
+ `export default function ${Placeholders.ComponentName}({}: ${Placeholders.ComponentProps}) {`,
...innerComponent,
'}',
],
@@ -118,7 +118,7 @@ const typescriptReactArrowFunctionExportComponent: TypescriptSnippet = {
...react,
'',
...propsTypeInterface,
- `const ${Placeholders.FileName} = (props: Props) => {`,
+ `const ${Placeholders.ComponentName} = (props: ${Placeholders.ComponentProps}) => {`,
...innerComponent,
'}',
...exportDefault,
@@ -134,7 +134,7 @@ const typescriptReactArrowFunctionComponent: TypescriptSnippet = {
...react,
'',
...propsTypeInterface,
- `export const ${Placeholders.FileName} = (props: Props) => {`,
+ `export const ${Placeholders.ComponentName} = (props: ${Placeholders.ComponentProps}) => {`,
...innerComponent,
'}',
],
@@ -149,7 +149,7 @@ const typescriptReactClassPureComponent: TypescriptSnippet = {
...reactPureComponent,
'',
...propsTypeInterface,
- `export default class ${Placeholders.FileName} extends PureComponent {`,
+ `export default class ${Placeholders.ComponentName} extends PureComponent<${Placeholders.ComponentProps}> {`,
...innerComponentReturn,
'}',
],
@@ -164,7 +164,7 @@ const typescriptReactClassExportPureComponent: TypescriptSnippet = {
...reactPureComponent,
'',
...propsTypeInterface,
- `class ${Placeholders.FileName} extends PureComponent {`,
+ `class ${Placeholders.ComponentName} extends PureComponent<${Placeholders.ComponentProps}> {`,
...innerComponentReturn,
'}',
...exportDefault,
@@ -181,7 +181,7 @@ const typescriptReactClassComponentRedux: TypescriptSnippet = {
...reactComponent,
'',
...propsStateInterface,
- `export class ${Placeholders.FileName} extends Component {`,
+ `export class ${Placeholders.ComponentName} extends Component<${Placeholders.ComponentProps}, State> {`,
' state = {}',
'',
...innerComponentReturn,
@@ -200,7 +200,7 @@ const typescriptReactNativeArrowFunctionComponent: TypescriptSnippet = {
...react,
'',
...propsTypeInterface,
- `const ${Placeholders.FileName} = (props: Props) => {`,
+ `const ${Placeholders.ComponentName} = (props: ${Placeholders.ComponentProps}) => {`,
' return (',
' ',
` ${Placeholders.FirstTab}`,
@@ -222,7 +222,7 @@ const typescriptReactNativeArrowFunctionComponentWithStyles: TypescriptSnippet =
...react,
'',
...propsTypeInterface,
- `const ${Placeholders.FileName} = (props: Props) => {`,
+ `const ${Placeholders.ComponentName} = (props: ${Placeholders.ComponentProps}) => {`,
' return (',
' ',
` ${Placeholders.FirstTab}`,
@@ -254,7 +254,7 @@ const routeWithLoaderActionTypescript: TypescriptSnippet = {
` ${Placeholders.SecondTab}`,
'}',
'',
- `function ${Placeholders.FileName}() {`,
+ `function ${Placeholders.ComponentName}() {`,
' const data = useLoaderData()',
'',
' return (',
@@ -263,7 +263,7 @@ const routeWithLoaderActionTypescript: TypescriptSnippet = {
'}',
'',
'export { loader, action }',
- `export default ${Placeholders.FileName}`,
+ `export default ${Placeholders.ComponentName}`,
],
description: 'React Router v6.4+ TypeScript route with loader and action',
};
diff --git a/src/types.ts b/src/types.ts
index 01422c8..dca0273 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -15,11 +15,18 @@ export const VALID_LANGUAGE_SCOPES = [
export type LanguageScope = (typeof VALID_LANGUAGE_SCOPES)[number];
+export type ComponentWrapper = 'fragment' | 'div';
+export type ComponentNameSource = 'filename' | 'directoryForIndex';
+export type TypescriptPropsNaming = 'generic' | 'component';
+
export type GenerationSettings = {
readonly languageScopes: string;
readonly importReactOnTop: boolean;
readonly typescript: boolean;
readonly typescriptPropsStatePrefix: 'type' | 'interface';
+ readonly typescriptPropsNaming: TypescriptPropsNaming;
+ readonly componentNameSource: ComponentNameSource;
+ readonly componentWrapper: ComponentWrapper;
};
export const DEFAULT_GENERATION_SETTINGS: GenerationSettings = {
@@ -27,28 +34,46 @@ export const DEFAULT_GENERATION_SETTINGS: GenerationSettings = {
importReactOnTop: false,
typescript: true,
typescriptPropsStatePrefix: 'type',
+ typescriptPropsNaming: 'generic',
+ componentNameSource: 'filename',
+ componentWrapper: 'fragment',
};
export const Placeholders = {
FileName: 'file',
+ ComponentName: 'componentName',
+ SliceName: 'sliceName',
FirstTab: 'first',
SecondTab: 'second',
ThirdTab: 'third',
Capitalize: 'capitalize',
TypeProps: 'typeProps',
TypeState: 'typeState',
+ ComponentProps: 'componentProps',
+ ComponentWrapper: 'componentWrapper',
FourthTabCapitalize: 'fourthTabCapitalize',
} as const;
export const Mappings = {
FileName: '${1:${TM_FILENAME_BASE}}',
+ ComponentFileName: '${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}',
+ ComponentDirectoryNameForIndex:
+ '${1:${TM_FILEPATH/.*?[\\\\/](?:([^\\\\/]+)[\\\\/]index|([^\\\\/]+?))(?:\\.[^\\\\/.]+)?$/${1:/pascalcase}${2:/pascalcase}/}}',
+ SliceFileName:
+ '${1:${TM_FILENAME_BASE/^(?:([A-Za-z_$][A-Za-z0-9_$]*)|(.*))$/${1}${2:/camelcase}/}}',
FirstTab: '${2:first}',
SecondTab: '${3:second}',
ThirdTab: '${4:third}',
Capitalize: '${2/(.*)/${1:/capitalize}/}',
TypeProps: 'type Props = {}',
+ ComponentTypeProps: 'type componentNameProps = {}',
TypeState: 'type State = {}',
InterfaceProps: 'interface Props {}',
+ ComponentInterfaceProps: 'interface componentNameProps {}',
InterfaceState: 'interface State {}',
+ Props: 'Props',
+ ComponentProps: 'componentNameProps',
+ FragmentComponentWrapper: '<>first>',
+ DivComponentWrapper: 'first
',
FourthTabCapitalize: '${4/(.*)/${1:/capitalize}/}',
} as const;
diff --git a/tests/generation.test.ts b/tests/generation.test.ts
index 96780d8..8168d46 100644
--- a/tests/generation.test.ts
+++ b/tests/generation.test.ts
@@ -13,6 +13,9 @@ import {
import { parseSnippet } from '../src/helpers/formatters';
import { persistGeneratedSnippets } from '../src/helpers/persistGeneratedSnippets';
import { replaceOrRemoveReactImport } from '../src/helpers/replaceOrRemoveReactImport';
+import componentsSnippets from '../src/sourceSnippets/components';
+import reactNativeSnippets from '../src/sourceSnippets/reactNative';
+import typescriptSnippets from '../src/sourceSnippets/typescript';
import {
isGeneratedSnippetAvailableForLanguage,
parseGeneratedSnippetCatalog,
@@ -21,6 +24,8 @@ import {
} from '../src/snippetTypes';
import {
DEFAULT_GENERATION_SETTINGS,
+ Mappings,
+ Placeholders,
type GenerationSettings,
} from '../src/types';
@@ -36,6 +41,14 @@ const packageManifestPath = fileURLToPath(
type JsonObject = Readonly>;
+const componentSnippetSources = [
+ ...componentsSnippets,
+ ...reactNativeSnippets,
+ ...typescriptSnippets,
+];
+const directoryNameForIndex = Mappings.ComponentDirectoryNameForIndex;
+const componentName = Mappings.ComponentFileName;
+
const requireJsonObject = (value: unknown, context: string): JsonObject => {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
throw new Error(`Expected ${context} to be an object`);
@@ -76,13 +89,21 @@ const parseManifestGenerationDefaults = (
const importReactOnTop = readDefault('importReactOnTop');
const typescript = readDefault('typescript');
const typescriptPropsStatePrefix = readDefault('typescriptPropsStatePrefix');
+ const typescriptPropsNaming = readDefault('typescriptPropsNaming');
+ const componentNameSource = readDefault('componentNameSource');
+ const componentWrapper = readDefault('componentWrapper');
if (
typeof languageScopes !== 'string' ||
typeof importReactOnTop !== 'boolean' ||
typeof typescript !== 'boolean' ||
(typescriptPropsStatePrefix !== 'type' &&
- typescriptPropsStatePrefix !== 'interface')
+ typescriptPropsStatePrefix !== 'interface') ||
+ (typescriptPropsNaming !== 'generic' &&
+ typescriptPropsNaming !== 'component') ||
+ (componentNameSource !== 'filename' &&
+ componentNameSource !== 'directoryForIndex') ||
+ (componentWrapper !== 'fragment' && componentWrapper !== 'div')
) {
throw new Error('Package generation defaults have invalid types');
}
@@ -92,6 +113,9 @@ const parseManifestGenerationDefaults = (
importReactOnTop,
typescript,
typescriptPropsStatePrefix,
+ typescriptPropsNaming,
+ componentNameSource,
+ componentWrapper,
};
};
@@ -178,14 +202,14 @@ describe('snippet compiler', () => {
const conflictingBodies = Object.values(snippets).filter((snippet) => {
const body = snippet.body.join('\n');
return (
- body.includes('${1:${TM_FILENAME_BASE}}') && body.includes('${1:first}')
+ body.includes('${1:${TM_FILENAME_BASE') && body.includes('${1:first}')
);
});
const rnxf = requireSnippet(snippets, 'reactNativeExtendedFunctional');
expect(conflictingBodies).toEqual([]);
expect(rnxf.body.join('\n')).toContain(
- 'const ${1:${TM_FILENAME_BASE}} = ({ ${2:props} }) => {',
+ `const ${componentName} = ({ ${'${2:props}'} }) => {`,
);
expect(rnxf.body.join('\n')).toContain(
'const [${3:state}, ${4:setState}] = useState(${5:initialState})',
@@ -251,6 +275,33 @@ describe('snippet compiler', () => {
typescriptPropsStatePrefix: 'interface',
}),
);
+ const divWrappedComponents = parseGeneratedSnippets(
+ buildSnippets({
+ ...DEFAULT_GENERATION_SETTINGS,
+ componentWrapper: 'div',
+ }),
+ );
+ const componentNamedProps = parseGeneratedSnippets(
+ buildSnippets({
+ ...DEFAULT_GENERATION_SETTINGS,
+ typescriptPropsNaming: 'component',
+ }),
+ );
+ const componentNamedInterfaces = parseGeneratedSnippets(
+ buildSnippets({
+ ...DEFAULT_GENERATION_SETTINGS,
+ typescriptPropsNaming: 'component',
+ typescriptPropsStatePrefix: 'interface',
+ }),
+ );
+ const directoryNamedComponents = parseGeneratedSnippets(
+ buildSnippets({
+ ...DEFAULT_GENERATION_SETTINGS,
+ componentNameSource: 'directoryForIndex',
+ typescriptPropsNaming: 'component',
+ }),
+ );
+ const fileName = '${1:${TM_FILENAME_BASE}}';
expect(legacyReact.typescriptReactClassComponent).toBeUndefined();
expect(
@@ -265,6 +316,104 @@ describe('snippet compiler', () => {
expect(
requireSnippet(interfaces, 'typescriptReactClassComponent').body,
).toContain('interface State {}');
+ expect(
+ requireSnippet(divWrappedComponents, 'reactFunctionalComponent').body,
+ ).toContain(' ${2:first}
');
+ expect(
+ requireSnippet(divWrappedComponents, 'typescriptReactClassComponent')
+ .body,
+ ).toContain(' ${2:first}
');
+ expect(
+ requireSnippet(componentNamedProps, 'typescriptReactFunctionalComponent')
+ .body,
+ ).toContain(`type ${componentName}Props = {}`);
+ expect(
+ requireSnippet(componentNamedProps, 'typescriptReactFunctionalComponent')
+ .body,
+ ).toContain(
+ `export default function ${componentName}({}: ${componentName}Props) {`,
+ );
+ expect(
+ requireSnippet(
+ componentNamedInterfaces,
+ 'typescriptReactFunctionalComponent',
+ ).body,
+ ).toContain(`interface ${componentName}Props {}`);
+ expect(
+ requireSnippet(directoryNamedComponents, 'reactFunctionalComponent').body,
+ ).toContain(`export default function ${directoryNameForIndex}() {`);
+ expect(
+ requireSnippet(
+ directoryNamedComponents,
+ 'typescriptReactFunctionalComponent',
+ ).body,
+ ).toContain(`type ${directoryNameForIndex}Props = {}`);
+ expect(
+ requireSnippet(directoryNamedComponents, 'reactNativeFunctionalComponent')
+ .body,
+ ).toContain(`export default function ${directoryNameForIndex}() {`);
+ expect(
+ requireSnippet(directoryNamedComponents, 'setupReactNativeTest').body,
+ ).toContain(`import ${fileName} from '../${fileName}'`);
+ });
+
+ test('applies directory-derived names to every component scaffold', () => {
+ const snippets = parseGeneratedSnippets(
+ buildSnippets({
+ ...DEFAULT_GENERATION_SETTINGS,
+ componentNameSource: 'directoryForIndex',
+ }),
+ );
+ const namedComponentSources = componentSnippetSources.filter((snippet) =>
+ snippet.body.some((line) => line.includes(Placeholders.ComponentName)),
+ );
+
+ expect(namedComponentSources).toHaveLength(41);
+ for (const snippet of componentSnippetSources) {
+ expect(snippet.body.join('\n')).not.toContain(Placeholders.FileName);
+ }
+ for (const snippet of namedComponentSources) {
+ expect(requireSnippet(snippets, snippet.key).body.join('\n')).toContain(
+ directoryNameForIndex,
+ );
+ }
+ });
+
+ test('derives valid identifiers from filenames of any casing', () => {
+ const snippets = defaultSnippets();
+
+ for (const snippet of componentSnippetSources) {
+ expect(
+ requireSnippet(snippets, snippet.key).body.join('\n'),
+ ).not.toContain(Mappings.FileName);
+ }
+ expect(requireSnippet(snippets, 'routeWithLoaderAction').body).toContain(
+ `function ${componentName}() {`,
+ );
+ expect(requireSnippet(snippets, 'reduxSlice').body).toContain(
+ `const ${Mappings.SliceFileName} = createSlice({`,
+ );
+ expect(
+ requireSnippet(snippets, 'reduxSliceWithExtraReducers').body,
+ ).toContain(`const ${Mappings.SliceFileName} = createSlice({`);
+ expect(requireSnippet(snippets, 'setupReactTest').body).toContain(
+ `import { ${Mappings.FileName} } from '../${Mappings.FileName}'`,
+ );
+ });
+
+ test('renames slices only when the filename is not a valid identifier', () => {
+ const pattern = Mappings.SliceFileName.match(
+ /TM_FILENAME_BASE\/(.+?)\/\$\{1\}/,
+ )?.[1];
+ if (!pattern) throw new Error('Expected SliceFileName to define a pattern');
+ const isPassedThrough = (name: string) =>
+ new RegExp(pattern).exec(name)?.[1] !== undefined;
+
+ expect(isPassedThrough('UserSlice')).toBe(true);
+ expect(isPassedThrough('userSlice')).toBe(true);
+ expect(isPassedThrough('delete_modal')).toBe(true);
+ expect(isPassedThrough('delete-modal')).toBe(false);
+ expect(isPassedThrough('user profile')).toBe(false);
});
test('uses defaults for empty scopes but rejects nonempty invalid scopes', () => {