Search code examples
typescriptvisual-studio-codereact-typescripttsxvscode-snippets

Implement VS Code tsx (typescript react) user defined snippet using conditional regular expression substitution


I am trying to write a simple tsx function component snippet. The snippet should look like this:

import * as React from 'react'

interface HomeProps {
  $2
}

const Home: React.FC<HomeProps> = ({ $2 }) => {
  return <div>Home</div>
}

export default Home

Let's focus on $2. All I want to achieve is that when user finish the interface, then we should get some processed $2 in function props. Something like this:

interface HomeProps{
  age: number
  name: string
}

const Home: React.FC<HomeProps> = ({ age, name }) => {
...

Here is my snippet code in JSON:

        "TypeScript React Function Component with Props": {
            "prefix": "trfp",
            "body": [
                "import * as React from 'react'",
                "",
                "interface ${1:${TM_FILENAME/(^.)(.*)\\..*/${1:/upcase}$2/}}Props {",
                "\t$2",
                "}"
                "",
                "const $1: React.FC<$1Props> = ({ ${2/(.*):.*/$1, /g} }) => {",
                "\treturn $3",
                "}",
                "",
                "export default $1"
            ],
            "description": "TypeScript React Function Component with Props",
        }

I want some help with this line: "const $1: React.FC<$1Props> = ({ ${2/(.*):.*/$1, /g} }) => {",

With this line I can get the result like:

interface HomeProps{
  age: number
  name: string
}

const Home: React.FC<HomeProps> = ({ age, 
name, }) => {
...
  1. How can I join groups in one line?
  2. How can I join them with comma but without trailing comma?

It takes me hours but I still can't figure it out. Can someone help?

Here is my goal:

interface HomeProps{
  age: number
  name: string
}

const Home: React.FC<HomeProps> = ({ age, name }) => {
...

Update

I try some conditional regex to change the line to "const $1: React.FC<$1Props> = ({ ${2/(.*):.*(\r?\n)/$1${2:+, }/g} }) => {",

Now I can get

interface HomeProps {
  key: string
  val: number
}

const Index: React.FC<HomeProps> = ({ key, val: number }) => {
  return
}

I also try ternary condition as described in https://code.visualstudio.com/docs/editor/userdefinedsnippets. ${2:?, :}, which means or what I want if group 2 (next line) exists, replace it with , , otherwise replace with a empty string. However I also got the wrong result:

interface HomeProps {
  key: string
  val: number
}

const Index: React.FC<HomeProps> = ({ key${2:?, :}val: number }) => {
  return 
}

It seems like I am getting close to the answer but...


Solution

  • Try this snippet:

    "TypeScript React Function Component with Props": {
        "prefix": "trfp",
        "body": [
                "import * as React from 'react'",
                "",
                "interface ${1:${TM_FILENAME/(^.)(.*)\\..*/${1:/upcase}$2/}}Props {",
                "\t$2",
                "}"
                "",
                         // note double-backslashes \\?
                "const $1: React.FC<$1Props> = ({ ${2/(.*?)\\??:\\s*.*(\n?)/$1${2:+, }/g} }) => {",
                         // this also works: [?]?
                // "const $1: React.FC<$1Props> = ({ ${2/(.*?)[?]?:\\s*.*(\n?)/$1${2:+, }/g} }) => {",
                "\treturn $3",
                "}",
                "",
                "export default $1"
        ],
        "description": "TypeScript React Function Component with Props",
    }
    

    Here is where I made changes:

    ${2/(.*):\\s*.*(\n?)/$1${2:+, }/g}
    

    First, you didn't seem to be accounting for the space after the :, so I added \\s* to the regex (\s must be double-escaped.

    Second, you need to match (but won't use) the newline between your entries. But your last entry won't have a newline so it is optionally captured (\n?).

    Third, that optional new line is captured in group 2. After the last entry (like your name: string) is added and you hit Tab you do not want another , added to the end. That should only be added if there is another entry - in which case there would be a newline between them.

    So ${2:+, } is a conditional IF. If there is a capture group 2, add , . If there is no group 2, nothing is added after group 1.


    I don't know if it is required but the snippet also works if there are commas after the entries, like

      age: number,           // space after the :, comma at end
      name?:string,           // no space after :, comma. Optional ? removed  
      true?     : boolean     // lots of space after :, no trailing comma